All files / utils CppConstructorHelper.ts

100% Statements 13/13
100% Branches 6/6
100% Functions 4/4
100% Lines 12/12

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                                                                  31x       6x   25x               26x 26x                     25x                           25x   23x 23x 23x         23x 23x          
/**
 * CppConstructorHelper
 *
 * Helper class for C++ constructor detection logic.
 * Issue #517: C++ classes with user-defined constructors are NOT aggregate types,
 * so designated initializers { .field = value } don't work with them.
 *
 * #1322: moved out of `output/codegen/helpers/` because both passes ask it.
 * "Does this type have a constructor in the symbol table?" is a question about
 * the SYMBOL MODEL, not about rendering -- it reads a lookup and a name
 * spelling and nothing else -- and pass 2.1 may not import `output/`
 * (`analyze-cannot-import-render`). Its location was the only thing making it
 * a codegen fact.
 */
 
import TSymbolKind from "../types/symbol-kinds/TSymbolKind";
import QualifiedCName from "./QualifiedCName";
 
/**
 * Symbol lookup interface for constructor detection
 */
interface ISymbolLookup {
  getSymbol(name: string): { kind: TSymbolKind } | undefined;
}
 
class CppConstructorHelper {
  /**
   * Convert underscore format to :: for namespaced types.
   * e.g., TestNS__MyClass -> TestNS::MyClass
   * A single underscore is part of a component name and is preserved:
   * Adafruit_BMP280 -> Adafruit_BMP280 (not Adafruit::BMP280)
   */
  static toQualifiedName(typeName: string): string {
    if (QualifiedCName.isQualified(typeName) && !typeName.includes("::")) {
      // Split on the qualified-name separator rather than replacing every
      // underscore: a component may legally contain single underscores
      // (`tick_count`), and a blanket replace would mangle those too.
      return QualifiedCName.toCppQualified(typeName, "::");
    }
    return typeName;
  }
 
  /**
   * Extract just the class name (part after last ::).
   * e.g., TestNS::MyClass -> MyClass, CppTestClass -> CppTestClass
   */
  static extractClassName(qualifiedName: string): string {
    const parts = qualifiedName.split("::");
    return parts.at(-1)!;
  }
 
  /**
   * Build the constructor name pattern.
   * e.g., TestNS::MyClass -> TestNS::MyClass::MyClass
   */
  static buildConstructorName(
    qualifiedName: string,
    className: string,
  ): string {
    return `${qualifiedName}::${className}`;
  }
 
  /**
   * Check if a type has a C++ constructor in the symbol table.
   *
   * @param typeName - The type name (may use underscore or :: notation)
   * @param symbolTable - Symbol table for lookup (may be null)
   * @returns true if a constructor function symbol exists
   */
  static hasConstructor(
    typeName: string,
    symbolTable: ISymbolLookup | null | undefined,
  ): boolean {
    if (!symbolTable) return false;
 
    const qualifiedName = CppConstructorHelper.toQualifiedName(typeName);
    const className = CppConstructorHelper.extractClassName(qualifiedName);
    const constructorName = CppConstructorHelper.buildConstructorName(
      qualifiedName,
      className,
    );
 
    const constructorSymbol = symbolTable.getSymbol(constructorName);
    return constructorSymbol?.kind === "function";
  }
}
 
export default CppConstructorHelper;