`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
@@ -2763,6 +2763,42 @@ class Both : public Base<char>, public Plain {};
|
||||
});
|
||||
});
|
||||
|
||||
describe('C++ stack-allocation construction (#1035)', () => {
|
||||
// `Calculator calc(0)` (direct-init) and `Widget w{1, 2}` (brace-init) carry
|
||||
// the constructor args directly on the declarator — no call/new node — so
|
||||
// they emitted no constructor reference, unlike heap `new Calculator(0)`. An
|
||||
// `instantiates` ref to the constructed type is now emitted for both.
|
||||
const instNames = (code: string) =>
|
||||
extractFromSource('f.cpp', `void run() {\n${code}\n}`)
|
||||
.unresolvedReferences.filter((r) => r.referenceKind === 'instantiates')
|
||||
.map((r) => r.referenceName);
|
||||
|
||||
it('emits an instantiates ref for direct-init and brace-init', () => {
|
||||
expect(instNames('Calculator calc(0);')).toEqual(['Calculator']);
|
||||
expect(instNames('Widget w{1, 2};')).toEqual(['Widget']);
|
||||
});
|
||||
|
||||
it('strips template args and namespace to the bare class name', () => {
|
||||
// `std::vector<int> v(10)` → `vector`; `ns::Widget w(0)` → `Widget`.
|
||||
expect(instNames('std::vector<int> v(10);')).toEqual(['vector']);
|
||||
expect(instNames('ns::Widget w(0);')).toEqual(['Widget']);
|
||||
});
|
||||
|
||||
it('does not emit for primitives, default construction, or the most-vexing parse', () => {
|
||||
expect(instNames('int x(5);')).toEqual([]); // primitive direct-init
|
||||
expect(instNames('int y{6};')).toEqual([]); // primitive brace-init
|
||||
expect(instNames('auto z = make();')).toEqual([]); // auto + call (handled elsewhere)
|
||||
expect(instNames('Calculator deferred;')).toEqual([]); // default construction, no args
|
||||
expect(instNames('Calculator calc();')).toEqual([]); // function declaration (most-vexing parse)
|
||||
});
|
||||
|
||||
it('emits a single instantiates ref for a multi-declarator statement', () => {
|
||||
// `Calculator a(1), b(2);` shares one `type` field; both construct a
|
||||
// Calculator, so one ref suffices (it dedups to one edge regardless).
|
||||
expect(instNames('Calculator a(1), b(2);')).toEqual(['Calculator']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('C/C++ imports', () => {
|
||||
it('should extract system include', () => {
|
||||
const code = `#include <iostream>`;
|
||||
|
||||
@@ -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