All files / transpiler/output/codegen/generators/expressions CallExprUtils.ts

100% Statements 21/21
100% Branches 16/16
100% Functions 7/7
100% Lines 21/21

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123                          31x               20x               13x                 137x               12x                   9x 9x               5x 5x                                           204x 204x 162x     42x 33x 33x 27x 23x   23x   23x     23x                         19x          
/**
 * Pure utility functions for function call expression generation.
 * Extracted from CallExprGenerator for testability (Issue #420).
 */
import TYPE_MAP from "../../types/TYPE_MAP";
import IFunctionSignature from "../../../../types/IFunctionSignature";
import SymbolTable from "../../../../logic/symbols/SymbolTable";
import TypeResolver from "../../../../../utils/TypeResolver";
 
/**
 * Issue #315: Small primitive types that are always passed by value.
 * These match the types used in Issue #269 for pass-by-value optimization.
 */
const SMALL_PRIMITIVE_TYPES = new Set(["u8", "u16", "i8", "i16", "bool"]);
 
class CallExprUtils {
  /**
   * Issue #304: Map C-Next type to C type for static_cast.
   * Returns the input unchanged if not a known C-Next primitive type.
   */
  static mapTypeToCType(cnxType: string): string {
    return TYPE_MAP[cnxType] || cnxType;
  }
 
  /**
   * Issue #315: Check if a type is a small primitive that should be passed by value.
   * Used for cross-file function calls where modification info is unavailable.
   */
  static isSmallPrimitiveType(typeName: string): boolean {
    return SMALL_PRIMITIVE_TYPES.has(typeName);
  }
 
  /**
   * Issue #551: Check if a type is a known primitive type.
   * Known primitives use pass-by-reference (except floats and small types).
   * Unknown types (external enums, typedefs) use pass-by-value.
   */
  static isKnownPrimitiveType(typeName: string): boolean {
    return !!TYPE_MAP[typeName];
  }
 
  /**
   * Issue #551: Check if a type is a string type (string<N>).
   * String parameters use pass-by-reference (passed as char*).
   */
  static isStringType(typeName: string): boolean {
    return typeName.startsWith("string<") || typeName === "string";
  }
 
  /**
   * ADR-051: Generate the helper function name for safe_div/safe_mod.
   */
  static generateSafeDivModHelperName(
    funcName: "safe_div" | "safe_mod",
    cnxType: string,
  ): string {
    const op = funcName === "safe_div" ? "div" : "mod";
    return `cnx_safe_${op}_${cnxType}`;
  }
 
  /**
   * Issue #304: Generate a C++ static_cast expression.
   * Used for enum class to integer conversions.
   */
  static generateStaticCast(code: string, targetType: string): string {
    const cType = CallExprUtils.mapTypeToCType(targetType);
    return `static_cast<${cType}>(${code})`;
  }
 
  /**
   * Issue #315: Resolve target parameter info from local signature or cross-file SymbolTable.
   * Returns the parameter info and whether it came from a cross-file lookup.
   *
   * Issue #1139: `funcName` is a transpiled C name (`Sensors__readValue`), so
   * the cross-file lookup resolves by canonical identity. Asking the bare-name
   * index returned nothing for every scoped function, leaving `param`
   * undefined — which the caller could not distinguish from a function that
   * genuinely takes no such parameter, so a struct argument lost its `&`.
   */
  static resolveTargetParam(
    sig: IFunctionSignature | undefined,
    paramIndex: number,
    funcName: string,
    symbolTable: SymbolTable | null,
  ): {
    param: IFunctionSignature["parameters"][0] | undefined;
    isCrossFile: boolean;
  } {
    const localParam = sig?.parameters[paramIndex];
    if (localParam) {
      return { param: localParam, isCrossFile: false };
    }
 
    if (symbolTable) {
      const symbols = symbolTable.getOverloadsByCName(funcName);
      for (const sym of symbols) {
        if (sym.kind === "function" && sym.parameters?.[paramIndex]) {
          const p = sym.parameters[paramIndex];
          // Convert TType to string for C-Next symbols, use string directly for C/C++
          const paramType = p.type;
          const baseType =
            typeof paramType === "string"
              ? paramType
              : TypeResolver.getTypeName(paramType);
          return {
            param: {
              name: p.name,
              baseType,
              isConst: p.isConst,
              isArray: p.isArray,
            },
            isCrossFile: true,
          };
        }
      }
    }
 
    return { param: undefined, isCrossFile: false };
  }
}
 
export default CallExprUtils;