All files / TRANSPILE/1-Analyze ConstructorArgumentAnalyzer.ts

100% Statements 21/21
100% Branches 6/6
100% Functions 5/5
100% Lines 20/20

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                                                                      473x   473x 473x       473x     472x     472x 472x   6x 7x 7x       7x                 7x 1x             1x   6x 4x                             473x     473x 473x 473x          
/**
 * A C++ constructor argument must name a declared `const` variable.
 *
 * #1322. The rule was two throws with identical text -- `VariableDeclHelper`
 * for a file-scope declaration, `ScopeGenerator` for one inside a `scope` --
 * and, more to the point, TWO WAYS of deciding const-ness. One read
 * `CodeGenState.getVariableTypeInfo`, falling back to a scope-qualified name
 * built by hand; the other went through `orchestrator.isConstValue` on a name
 * qualified by a different helper. They agreed by construction only for as long
 * as nobody edited one of them, which is the shape CLAUDE.md calls the worst
 * anti-pattern: the same decision derived twice.
 *
 * Here it is one question -- what does this name's declaration say? -- asked of
 * the lexical frames, which already handle a scope member and a file-scope
 * variable the same way, with shadowing.
 *
 * It moves on `IDeclaredVar.isConst`, added for exactly this: the collector
 * reads `constModifier` at every declaration site, so 2.1 answers without any
 * codegen state.
 *
 * Both diagnostics of the family move together. "is not declared" was thrown
 * from the same loop one step earlier, and splitting them would leave a rule
 * half in each pass.
 */
 
import { ParseTreeWalker } from "antlr4ng";
 
import { CNextListener } from "../../PARSE/2-Parse/grammar/CNextListener";
import * as Parser from "../../PARSE/2-Parse/grammar/CNextParser";
import ParserUtils from "../../utils/ParserUtils";
import BoundDeclaration from "./helpers/BoundDeclaration";
import IConstructorArgumentError from "./types/IConstructorArgumentError";
import type IAnalysisContext from "./types/IAnalysisContext";
 
class ConstructorArgumentListener extends CNextListener {
  private readonly found: IConstructorArgumentError[] = [];
 
  public constructor(private readonly context: IAnalysisContext) {
    super();
  }
 
  public errors(): IConstructorArgumentError[] {
    return this.found;
  }
 
  override enterVariableDeclaration = (
    ctx: Parser.VariableDeclarationContext,
  ): void => {
    const args = ctx.constructorArgumentList();
    if (!args) return;
 
    for (const identifier of args.IDENTIFIER()) {
      const name = identifier.getText();
      const { line, column } = ParserUtils.getPosition(ctx);
      // What the name binds to before this declaration, through Program's
      // one binder (#1668): a const local, member or global, this file's or
      // an included one's
      const declared = BoundDeclaration.of(
        this.context.program.bindValue(
          this.context.sourceFile,
          null,
          name,
          ParserUtils.getPosition({ start: identifier.symbol }),
        ),
      );
 
      if (!declared) {
        this.found.push({
          code: "E0433",
          line,
          column,
          message: `Constructor argument '${name}' is not declared`,
          helpText: `Declare '${name}' as a const before the constructor, or pass a literal.`,
        });
        continue;
      }
      if (!declared.isConst) {
        this.found.push({
          code: "E0432",
          line,
          column,
          message: `Constructor argument '${name}' must be const`,
          helpText:
            "A C++ constructor runs during static initialization, before main, so it may only read a value fixed at compile time. Declare the argument `const`.",
        });
      }
    }
  };
}
 
class ConstructorArgumentAnalyzer {
  /** #1456: handed in rather than read off shared state. */
  constructor(private readonly context: IAnalysisContext) {}
 
  public analyze(tree: Parser.ProgramContext): IConstructorArgumentError[] {
    const listener = new ConstructorArgumentListener(this.context);
    ParseTreeWalker.DEFAULT.walk(listener, tree);
    return listener.errors();
  }
}
 
export default ConstructorArgumentAnalyzer;