All files / PARSE/3-Declare/cnext/collectors FunctionCollector.ts

100% Statements 26/26
100% Branches 13/13
100% Functions 7/7
100% Lines 24/24

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 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176                                                                            3343x 3343x     3343x             3343x             3343x 3343x           3343x                                                                                       3322x                 3322x   3322x                       6656x 1449x 1449x 1449x 1449x     1449x       1449x 1449x           1449x       237x 237x       1449x 1449x   1449x                              
/**
 * FunctionCollector - Extracts function declarations from parse trees.
 * Handles return types, parameters, visibility, and signature generation.
 *
 * Produces TType-based IFunctionSymbol with proper IScopeSymbol references.
 */
 
import * as Parser from "../../../2-Parse/grammar/CNextParser";
import ESourceLanguage from "../../../../utils/types/ESourceLanguage";
import IFunctionSymbol from "../../../../types/symbols/IFunctionSymbol";
import IParameterInfo from "../../../../types/symbols/IParameterInfo";
import TypeUtils from "../utils/TypeUtils";
import SymbolRegistry from "../../SymbolRegistry";
import ScopeUtils from "../../../../utils/ScopeUtils";
import TVisibility from "../../../../types/TVisibility";
import ParserUtils from "../../../../utils/ParserUtils";
import DimensionResolver from "../utils/DimensionResolver";
 
class FunctionCollector {
  /**
   * Collect a function declaration and return an IFunctionSymbol.
   *
   * @param ctx The function declaration context
   * @param sourceFile Source file path
   * @param scopePath The path of the scope this function belongs to (dotted path, "" at file scope)
   * @param visibility Required: #1161 — a default here is a third source
   *   of truth for ADR-016 and drifted from it. Callers pass
   *   ScopeUtils.getDefaultVisibility() or an explicit keyword.
   * @param isScopeType ADR-057 predicate: is this *qualified* name a scope type?
   * @returns The function symbol with TType-based types and scope reference
   */
  static collect(
    ctx: Parser.FunctionDeclarationContext,
    sourceFile: string,
    scopePath: string,
    visibility: TVisibility,
    isScopeType?: (qualifiedName: string) => boolean,
  ): IFunctionSymbol {
    const name = ctx.IDENTIFIER().getText();
    const span = ParserUtils.getSpan(ctx);
 
    // Get return type string and convert to TType
    const returnTypeCtx = ctx.type();
    // #1298: members carry the scope's PATH, not the scope object. The path
    // holds every outer component, so nothing downstream can flatten it to a
    // leaf -- which is what the reference threaded here used to protect against.
    // #1472: `resolveType` defers a bare name this file cannot settle instead
    // of guessing it, so 1.4 Resolve can apply ADR-057 with the whole-program
    // scope-type set. Everything it can settle resolves exactly as before.
    const returnType = TypeUtils.resolveType(
      returnTypeCtx,
      scopePath,
      isScopeType,
    );
 
    // Collect parameters with TType
    const params = ctx.parameterList()?.parameter() ?? [];
    const parameters = FunctionCollector.collectParameters(
      params,
      scopePath,
      isScopeType,
    );
 
    return {
      kind: "function",
      name,
      scopePath,
      // #1285: identity computed once, from the scope chain, not
      // re-derived by every consumer.
      ...ScopeUtils.identityOf({ name, scopePath }),
      parameters,
      returnType,
      visibility,
      sourceFile,
      span,
      sourceLanguage: ESourceLanguage.CNext,
    };
  }
 
  /**
   * Collect a function declaration and register it in SymbolRegistry.
   *
   * This method:
   * 1. Gets or creates the appropriate scope in SymbolRegistry
   * 2. Collects the function with TType-based types
   * 3. Registers the function in that scope
   *
   * @param ctx The function declaration context
   * @param sourceFile Source file path
   * @param scopePath Declaring scope path; carries every outer component
   * @param visibility Required: #1161 — a default here is a third source
   *   of truth for ADR-016 and drifted from it. Callers pass
   *   ScopeUtils.getDefaultVisibility() or an explicit keyword.
   * @param isScopeType ADR-057 predicate: is this *qualified* name a scope type?
   * @returns The function symbol
   */
  static collectAndRegister(
    registry: SymbolRegistry,
    ctx: Parser.FunctionDeclarationContext,
    sourceFile: string,
    scopePath: string,
    visibility: TVisibility,
    isScopeType?: (qualifiedName: string) => boolean,
  ): IFunctionSymbol {
    // 1. Get or create the scope in SymbolRegistry
 
    // 2. Collect function with TType-based types and scope reference
    const symbol = FunctionCollector.collect(
      ctx,
      sourceFile,
      scopePath,
      visibility,
      isScopeType,
    );
 
    // 3. Register in SymbolRegistry
    registry.registerFunction(symbol);
 
    return symbol;
  }
 
  /**
   * Extract parameter information from parameter contexts.
   * Converts type strings to TType.
   */
  static collectParameters(
    params: Parser.ParameterContext[],
    scopePath = "",
    isScopeType?: (qualifiedName: string) => boolean,
  ): IParameterInfo[] {
    return params.map((p) => {
      const name = p.IDENTIFIER().getText();
      const typeCtx = p.type();
      const type = TypeUtils.resolveType(typeCtx, scopePath, isScopeType);
      const isConst = p.constModifier() !== null;
 
      // Check for C-Next style array type (u8[8] param, u8[4][4] param, u8[] param)
      const arrayTypeCtx = typeCtx.arrayType();
      // #1668: and the C-style dimensions E0874 admits for `main(string
      // args[])`, which this dropped -- so the declaration read `args` as a
      // scalar while the function's own plan read it as an array
      const cStyleDimensions = p.arrayDimension();
      const isArray = arrayTypeCtx !== null || cStyleDimensions.length > 0;
 
      // Each dimension folds as a declaration's does (#1760 review): parseInt
      // read `2*BUF` as 2 and `0x10` as 0, so the prototype disagreed with
      // the definition and a subscript was checked against the wrong size.
      // An unsized `[]` keeps its slot as "".
      const declared = [
        ...(arrayTypeCtx?.arrayTypeDimension() ?? []),
        ...cStyleDimensions,
      ].map((dim) => {
        const sizeExpr = dim.expression();
        return sizeExpr
          ? DimensionResolver.resolve(sizeExpr)
          : { size: "", expr: null };
      });
      const arrayDimensions = declared.map((dim) => dim.size);
      const arrayDimensionExprs = declared.map((dim) => dim.expr);
 
      return {
        name,
        type,
        isConst,
        isArray,
        arrayDimensions:
          arrayDimensions.length > 0 ? arrayDimensions : undefined,
        arrayDimensionExprs:
          arrayDimensions.length > 0 ? arrayDimensionExprs : undefined,
      };
    });
  }
}
 
export default FunctionCollector;