fix(zig): anchor @declaration.variable to the binding identifier

`(variable_declaration (identifier) @declaration.name)` matched EVERY
identifier child of the node, so `const first = target;` also declared a
phantom local named `target` in the enclosing block. That phantom shadowed
the real function for later references (and starved callable-value-flow
seeds of a target). The `.` anchor pins the pattern to the first named
child — the bound name.

Regression test in resolvers/zig.test.ts pins the capture set.
This commit is contained in:
Navid EMAD 2026-08-18 03:01:48 +02:00
parent 46bd72b8ca
commit 618133db98
No known key found for this signature in database
2 changed files with 31 additions and 2 deletions

View file

@ -56,9 +56,13 @@ const ZIG_SCOPE_QUERY = `
(container_field
name: (identifier) @declaration.name) @declaration.field
;; Declarations — const/var bindings (import/container groups filtered in TS)
;; Declarations — const/var bindings (import/container groups filtered in TS).
;; The \`.\` anchor pins the FIRST named child: without it the pattern also
;; matched the initializer of \`const first = target;\`, minting a phantom
;; local named \`target\` that shadowed the real callee for every later
;; reference in the block.
(variable_declaration
(identifier) @declaration.name) @declaration.variable
. (identifier) @declaration.name) @declaration.variable
;; Imports — const x = @import("...")
(variable_declaration

View file

@ -72,3 +72,28 @@ describe('Zig basic resolution', () => {
expect(edgeSet(calls)).toContain('main → tick');
});
});
describe('Zig scope captures — variable bindings', () => {
it('binds only the declared name, never the initializer identifier', async () => {
// `(variable_declaration (identifier) @declaration.name)` without a
// first-child anchor ALSO matches the RHS identifier of `const h = helper;`
// and mints a phantom local named `helper` in the enclosing block. That
// phantom shadows the real function for every later reference in the
// block, so `helper()` below silently lost its CALLS edge — and the
// callable-value-flow seed for `h` had nothing to resolve against.
const { emitZigScopeCaptures } =
await import('../../../src/core/ingestion/languages/zig/captures.js');
const source = [
'fn helper() void {}',
'pub fn main() void {',
' const h = helper;',
' helper();',
'}',
'',
].join('\n');
const variableNames = emitZigScopeCaptures(source, 'main.zig')
.filter((m) => m['@declaration.variable'] !== undefined)
.map((m) => m['@declaration.name']?.text);
expect(variableNames).toEqual(['h']);
});
});