fix(extraction): record instantiates for C++ stack/brace construction (#1035) (#1049)

`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:
Colby Mchenry
2026-06-28 21:12:53 -05:00
committed by GitHub
co-authored by Claude Opus 4.8
parent f4e03e9cdc
commit 2176a7a439
4 changed files with 128 additions and 0 deletions
+54
View File
@@ -3882,6 +3882,47 @@ export class TreeSitterExtractor {
}
}
/**
* Is this C++ `declaration` a stack/direct-initialization object construction
* that invokes a constructor — `Calculator calc(0)` (direct-init) or
* `Widget w{1, 2}` (brace-init) — as opposed to a plain variable or a
* function declaration? Used to emit an `instantiates` edge for the
* call-less construction syntax (#1035); heap `new T(...)` is handled
* separately by INSTANTIATION_KINDS.
*
* Two signals, both required:
* - the `type` field is a class-like NAMED type (`type_identifier`,
* `template_type`, or `qualified_identifier`). Primitives (`int x(0)`),
* `auto` (`placeholder_type_specifier` — that form always carries a real
* `call_expression`, already handled), and sized specifiers are excluded —
* they construct no class; and
* - a declarator carries constructor arguments: an `init_declarator` whose
* `value` is an `argument_list` (`(args)`) or `initializer_list` (`{args}`).
* This skips default construction `Calculator c;` (no value) and the
* most-vexing-parse `Calculator c();` (a bodyless `function_declarator`,
* a function decl — not a construction).
*/
private isCppStackConstruction(node: SyntaxNode): boolean {
const typeNode = getChildByField(node, 'type');
if (
!typeNode ||
(typeNode.type !== 'type_identifier' &&
typeNode.type !== 'template_type' &&
typeNode.type !== 'qualified_identifier')
) {
return false;
}
for (let i = 0; i < node.namedChildCount; i++) {
const child = node.namedChild(i);
if (child?.type !== 'init_declarator') continue;
const value = getChildByField(child, 'value');
if (value && (value.type === 'argument_list' || value.type === 'initializer_list')) {
return true;
}
}
return false;
}
/**
* Static-member / value-read pass. A type/enum/class used only via a member
* VALUE — `Enum.value`, `Type.CONST`, `Colors.red`, `Foo::BAR` — recorded no
@@ -4259,6 +4300,19 @@ export class TreeSitterExtractor {
}
}
// C++ stack / direct-initialization construction — `Calculator calc(0)`
// and `Widget w{1, 2}`. Unlike heap `new Calculator(0)` (a new_expression
// handled above), these carry the constructor arguments directly on the
// declarator with NO call/new node, so the body walker saw no constructor
// invocation and recorded no `instantiates` edge (#1035). A declaration's
// `type` field IS the constructed class name, so reuse extractInstantiation
// (which strips template args / namespace and emits the `instantiates`
// ref). Children still recurse below, so a nested ctor-arg call
// (`Calculator calc(make())`) keeps its own `calls` ref.
if (nodeType === 'declaration' && this.language === 'cpp' && this.isCppStackConstruction(node)) {
this.extractInstantiation(node);
}
// Static-member / value-read: `Enum.value`, `Type.CONST`, `Foo::BAR`.
this.extractStaticMemberRef(node);