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;
|