diff --git a/gitnexus/src/core/ingestion/languages/zig/query.ts b/gitnexus/src/core/ingestion/languages/zig/query.ts index de8c46dbc..778d7a4ed 100644 --- a/gitnexus/src/core/ingestion/languages/zig/query.ts +++ b/gitnexus/src/core/ingestion/languages/zig/query.ts @@ -225,9 +225,18 @@ const ZIG_SCOPE_QUERY = ` name: (identifier) @type-binding.name type: (_) @type-binding.type) @type-binding.parameter -;; Type bindings — constructor inference: const p = T{ ... } +;; Type bindings — constructor inference: const p = T{ ... }. Keyword-gated +;; like every binding rule: a keyword-less \`p = T{ ... };\` is a +;; re-assignment (same node type in tree-sitter-zig 1.1.2), and Zig's static +;; typing means \`p\` already carries its type from its declaration +;; (annotation, constructor or inferred value) — the assignment declares +;; nothing, and \`_ = T{ ... };\` must not bind \`_\`. (variable_declaration - (identifier) @type-binding.name + "const" . (identifier) @type-binding.name + (struct_initializer + (identifier) @type-binding.type)) @type-binding.constructor +(variable_declaration + "var" . (identifier) @type-binding.name (struct_initializer (identifier) @type-binding.type)) @type-binding.constructor @@ -236,7 +245,11 @@ const ZIG_SCOPE_QUERY = ` ;; receiver dispatch resolves the namespace prefix through the import ;; binding (emitReceiverBoundCalls Case 3). (variable_declaration - (identifier) @type-binding.name + "const" . (identifier) @type-binding.name + (struct_initializer + (field_expression) @type-binding.type)) @type-binding.constructor +(variable_declaration + "var" . (identifier) @type-binding.name (struct_initializer (field_expression) @type-binding.type)) @type-binding.constructor @@ -244,7 +257,11 @@ const ZIG_SCOPE_QUERY = ` ;; The callee is the type constructor; \`normalizeZigTypeName\` drops the ;; comptime argument list so \`List(u8)\` looks up \`List\`. (variable_declaration - (identifier) @type-binding.name + "const" . (identifier) @type-binding.name + (struct_initializer + (call_expression) @type-binding.type)) @type-binding.constructor +(variable_declaration + "var" . (identifier) @type-binding.name (struct_initializer (call_expression) @type-binding.type)) @type-binding.constructor diff --git a/gitnexus/test/unit/zig-extractors.test.ts b/gitnexus/test/unit/zig-extractors.test.ts index b9c501eff..cb139e976 100644 --- a/gitnexus/test/unit/zig-extractors.test.ts +++ b/gitnexus/test/unit/zig-extractors.test.ts @@ -1390,6 +1390,36 @@ fn f() !void { ]); }); + it('a keyword-less re-assignment `p = T{…};` / `_ = T{…};` binds no constructor type (only declarations do)', () => { + // tree-sitter-zig 1.1.2 parses assignments as `variable_declaration` too; + // the constructor rules used to match them and mint a binding for `p` (and + // for `_`) in the assignment's block. `p` already carries its type from its + // declaration, so the assignment adds nothing — the rules are keyword-gated + // like the import and call-return rules. + const src = ` +fn f() void { + var p = Thing{ .a = 1 }; + const q = mod.Thing{ .a = 1 }; + const l = List(u8){}; + p = Thing{ .a = 2 }; + q = mod.Thing{ .a = 2 }; + l = List(u8){}; + _ = Thing{ .a = 3 }; +} +`; + const ctor = (boundName: string, rawTypeName: string) => ({ + boundName, + rawTypeName, + source: 'constructor-inferred', + kind: '@type-binding.constructor', + }); + expect(bindingsOf(src)).toEqual([ + ctor('p', 'Thing'), + ctor('q', 'mod.Thing'), + ctor('l', 'List'), + ]); + }); + it('a wrapped struct literal (`try Thing{…}`) is a constructor binding', () => { expect(bindingsOf('fn f() !void { const t = try Thing{ .a = 1 }; }')).toEqual([ {