From 4ec68c5f82c16308f552a61bb41550d7c50eeb44 Mon Sep 17 00:00:00 2001 From: Navid EMAD Date: Tue, 18 Aug 2026 15:00:22 +0200 Subject: [PATCH] fix(zig): give function-local and anonymous containers an identity (F8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `const R = struct {…}` declared inside a fn (Lightpanda's reflection.zig has ~20, one per builder) all collapsed onto one `Struct::R` with one `R.get`; anonymous containers (`std.sort.pdq(…, struct { fn lessThan … } .lessThan)`, `const byte_size = struct { fn it … }.it;`, `?struct { min, max }` field types) had no identity at all, so their fns were OWNERLESS Methods (`Method::lessThan#3`) that collided across a file. `zigContainerName` now yields the graph IDENTITY on both phases: - function-local named: `$` — `Reflect.string$R` (Java local-class `$` chain; `populateClassOwnedMembers` leaves it whole); - anonymous: `$` — `build$1`, `Outer$1`, `Page$1` (javac's `Outer$1` numbering per host, in source order); - a `test` host is keyed `test@L` (its string does not survive the class extractor's qualified-name normalization). `zigContainerBindingName` keeps the spelling code writes (`R`) for scope bindings and `@This()` alias rewrites (`@declaration.binding-name`). Structure phase: bare `(struct|enum|union|opaque_declaration)` rules mint the local/anonymous nodes via the class extractor; `shouldSkipDefinitionCapture` keeps exactly one rule per container (`zigContainerAnchor`); a new grammar-neutral `resolveContainerTypeOwner` provider hook lets the shared owner walk name a container from context, so `Method::Reflect.string$R .get#0` and its HAS_METHOD source agree by construction. Scope phase: the wrapper group splits name/binding-name for locals and anonymous containers get synthesized `@declaration.` defs (`is-synthetic`). Lightpanda: ownerless Methods 14 → 0, fns without a node 55 → 0, ownerless Properties 276 → 5, HAS_METHOD 8003 → 8074, HAS_PROPERTY 7275 → 7403, CALLS 15838 → 15857, Struct 1905 → 2199; no resolution bucket dropped. --- .../ingestion/class-extractors/configs/zig.ts | 22 +- .../src/core/ingestion/language-provider.ts | 20 ++ gitnexus/src/core/ingestion/languages/zig.ts | 34 +- .../core/ingestion/languages/zig/captures.ts | 290 +++++++++++++++++- .../src/core/ingestion/languages/zig/index.ts | 5 + .../src/core/ingestion/tree-sitter-queries.ts | 15 + .../src/core/ingestion/utils/ast-helpers.ts | 25 ++ .../core/ingestion/workers/parse-worker.ts | 7 + .../zig-filestruct/src/Sorter.zig | 50 +++ .../zig-filestruct/src/reflect.zig | 62 ++++ .../test/integration/resolvers/zig.test.ts | 131 ++++++++ gitnexus/test/unit/zig-extractors.test.ts | 195 +++++++++++- 12 files changed, 833 insertions(+), 23 deletions(-) create mode 100644 gitnexus/test/fixtures/lang-resolution/zig-filestruct/src/Sorter.zig create mode 100644 gitnexus/test/fixtures/lang-resolution/zig-filestruct/src/reflect.zig diff --git a/gitnexus/src/core/ingestion/class-extractors/configs/zig.ts b/gitnexus/src/core/ingestion/class-extractors/configs/zig.ts index c33a40095..690a9ff56 100644 --- a/gitnexus/src/core/ingestion/class-extractors/configs/zig.ts +++ b/gitnexus/src/core/ingestion/class-extractors/configs/zig.ts @@ -4,6 +4,7 @@ import type { SyntaxNode } from '../../utils/ast-helpers.js'; import { isZigFileStruct, ZIG_CONTAINER_TYPES, + zigContainerLabel, zigContainerName, } from '../../languages/zig/captures.js'; @@ -12,11 +13,16 @@ import { * * const Point = struct { ... }; * pub fn List(comptime T: type) type { return struct { ... }; } + * fn build() void { const R = struct { ... }; sort(struct { fn lt … }.lt); } * - * The binding name is the first identifier child of the parent - * variable_declaration, or the generic type constructor's name — - * `zigContainerName` is the single source shared with the field/method - * extractors so owner ids and node ids agree by construction. + * The identity is the binding name (first identifier of the parent + * variable_declaration), the generic type constructor's name, or — for a + * FUNCTION-LOCAL or ANONYMOUS container — a synthesized `host$Name` / + * `host$N` (F8). `zigContainerName` is the single source shared with the + * field/method extractors and the owner walk, so owner ids and node ids + * agree by construction. Which of the (up to three) ZIG_QUERIES rules that + * match one container gets to mint it is decided by the provider's + * `shouldSkipDefinitionCapture` (`isZigRedundantContainerCapture`). */ const extractZigContainerName = (node: SyntaxNode, filePath?: string): string | undefined => zigContainerName(node, filePath); @@ -25,13 +31,7 @@ const extractZigContainerType = (node: SyntaxNode): ClassLikeNodeLabel | undefin // The file itself, when it declares top-level fields (file-struct); a // namespace-only file is not a type — `extract` then yields no symbol. if (node.type === 'source_file') return isZigFileStruct(node) ? 'Struct' : undefined; - if (node.type === 'struct_declaration') return 'Struct'; - if (node.type === 'enum_declaration') return 'Enum'; - if (node.type === 'union_declaration') return 'Union'; - // `opaque {}` is a fieldless container that may own methods — Struct is the - // closest class-like label (rationale in ZIG_QUERIES). - if (node.type === 'opaque_declaration') return 'Struct'; - return undefined; + return zigContainerLabel(node); }; export const zigClassConfig: ClassExtractionConfig = { diff --git a/gitnexus/src/core/ingestion/language-provider.ts b/gitnexus/src/core/ingestion/language-provider.ts index 6344a622e..90e42f9e8 100644 --- a/gitnexus/src/core/ingestion/language-provider.ts +++ b/gitnexus/src/core/ingestion/language-provider.ts @@ -174,6 +174,26 @@ interface LanguageProviderConfig { filePath: string, ) => { readonly name: string; readonly label: NodeLabel } | null; + /** + * The type a CONTAINER node declares, when the language names it from its + * context rather than from a name child of the node — a binding wrapper, + * an enclosing callable, an ordinal among anonymous siblings (Zig: + * `const T = struct {…}` is `T`; a function-local `const R = struct {…}` + * inside `fn string` is `string$R`; `struct { fn lessThan … }.lessThan` + * passed to a sort is `$1`). + * + * Consulted by the enclosing-owner walk for every `CLASS_CONTAINER_TYPES` + * node it meets (after `resolveEnclosingOwner` remapping), BEFORE the + * generic name-child derivation; return `null` to fall back to it. The name + * must be the one the class-like node is minted under + * (`