`instantiates` edges came only from heap `new Calculator(0)` (a
new_expression) and copy-init `Calculator c = Calculator(0)` (a
call_expression). Stack direct-init `Calculator calc(0)` and brace-init
`Widget w{1, 2}` parse as a `declaration` whose constructor arguments hang
directly off the declarator as an argument_list / initializer_list — there
is no call/new node — so the function-body walker saw no constructor
invocation and emitted no edge. A function that built objects with the
ordinary stack syntax looked like it didn't construct them, and the
dependency was missing from impact / callers.
In the body walker, a C++ `declaration` that is a stack/brace construction
now reuses extractInstantiation (a declaration's `type` field IS the
constructed class name, and extractInstantiation already strips template
args / namespace and emits the `instantiates` ref). Gated by
isCppStackConstruction, which requires BOTH a class-like type
(type_identifier / template_type / qualified_identifier — so `int x(0)`
and `auto z = …` are excluded) AND a declarator carrying args
(argument_list / initializer_list — so default `Calculator c;` and the
most-vexing-parse `Calculator c();` are excluded). The edge targets the
class node, not the same-named constructor method.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
f4e03e9cdc
commit
2176a7a439
@@ -930,6 +930,43 @@ def bootstrap():
|
||||
expect(callsToUserService).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('records instantiates for C++ stack/brace construction, targeting the class (#1035)', async () => {
|
||||
// `Calculator calc(0)` (direct-init) and `Widget w{1, 2}` (brace-init)
|
||||
// carry the constructor args directly on the declarator — there's no
|
||||
// call/new node — so they recorded no `instantiates` edge, while heap
|
||||
// `new Calculator(0)` did. Both stack forms now do.
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'm.cpp'),
|
||||
`class Calculator { public: Calculator(int seed) {} int add(int a, int b){ return a+b; } };
|
||||
class Widget { public: Widget(int a, int b) {} };
|
||||
|
||||
int runStack(int a, int b) { Calculator calc(0); return calc.add(a, b); }
|
||||
int runBrace() { Widget w{1, 2}; return 0; }
|
||||
int runHeap(int a, int b) { Calculator* c = new Calculator(0); return c->add(a, b); }
|
||||
void noise() { int x(5); int y{6}; Calculator deferred; }
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
|
||||
const fn = (name: string) => cg.getNodesByKind('function').find((n) => n.name === name)!;
|
||||
const instTargets = (name: string) =>
|
||||
cg
|
||||
.getOutgoingEdges(fn(name).id)
|
||||
.filter((e) => e.kind === 'instantiates')
|
||||
.map((e) => cg.getNode(e.target)!);
|
||||
|
||||
// Direct-init (the issue) and brace-init both instantiate, targeting the
|
||||
// CLASS node — not the same-named constructor method.
|
||||
const stack = instTargets('runStack');
|
||||
expect(stack.map((n) => `${n.kind}:${n.name}`)).toContain('class:Calculator');
|
||||
expect(instTargets('runBrace').map((n) => `${n.kind}:${n.name}`)).toContain('class:Widget');
|
||||
// Heap still works (regression guard).
|
||||
expect(instTargets('runHeap').map((n) => `${n.kind}:${n.name}`)).toContain('class:Calculator');
|
||||
// Primitives (`int x(0)`/`int y{6}`) and bare default construction
|
||||
// (`Calculator deferred;`) must NOT mint an instantiates edge.
|
||||
expect(instTargets('noise')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('resolves a cross-file static method call to the method, not the class (#825)', async () => {
|
||||
// `Foo.bar()` where `Foo` is an imported class must link to the static
|
||||
// method `Foo::bar`, NOT to the class `Foo`. Previously the import
|
||||
|
||||
Reference in New Issue
Block a user