diff --git a/gitnexus/src/core/ingestion/import-resolvers/zig.ts b/gitnexus/src/core/ingestion/import-resolvers/zig.ts index 418d849ea..b45da6fde 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/zig.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/zig.ts @@ -113,7 +113,11 @@ export function resolveZigImportInternal( // `@import("/abs.zig")` as an import outside the module path). Splitting // would drop the empty leading component and read `/foo.zig` as an // importer-relative `foo.zig`, fabricating an in-repo edge. - if (trimmed.startsWith('/')) return null; + // A drive-qualified spelling (`C:/foo.zig`, normalized from `C:\foo.zig`) + // is absolute too: it carries a `/`, so without this guard it would take + // the importer-relative branch and probe `src/C:/foo.zig`. Same test as + // `normalizeZigDepPath`. + if (trimmed.startsWith('/') || /^[A-Za-z]:\//.test(trimmed)) return null; // Path-bearing import: resolve relative to the current file's directory. // Zig allows both "./foo.zig" and "foo.zig" — both are filesystem-relative. diff --git a/gitnexus/src/core/ingestion/scope-resolution/contract/scope-resolver.ts b/gitnexus/src/core/ingestion/scope-resolution/contract/scope-resolver.ts index aa82da251..9ac2423f5 100644 --- a/gitnexus/src/core/ingestion/scope-resolution/contract/scope-resolver.ts +++ b/gitnexus/src/core/ingestion/scope-resolution/contract/scope-resolver.ts @@ -897,8 +897,11 @@ export interface ScopeResolver { * The marker rides in `reason` because relationships carry no arbitrary * properties (adding one moves SCHEMA_FINGERPRINT — the IMPLEMENTS * `-pointer` precedent in `pipeline/run.ts`). Applies to the free-call - * fallback and the reference bridge; receiver-qualified construction sites - * are not tagged `constructor` by any provider today. + * fallback, the reference bridge and the receiver-bound paths — a + * namespace-qualified literal (`mod.T{…}`, Case 1), a type nested in the + * receiver's class (`A.Item{}`, Case 2) and a dotted type binding (Case 3) + * all go through `constructionSiteReason` — so an opted-in provider sees + * one vocabulary whichever path resolved the site. */ readonly markConstructionSites?: boolean; diff --git a/gitnexus/src/core/ingestion/scope-resolution/passes/compound-receiver.ts b/gitnexus/src/core/ingestion/scope-resolution/passes/compound-receiver.ts index a057baff4..c39c02a9c 100644 --- a/gitnexus/src/core/ingestion/scope-resolution/passes/compound-receiver.ts +++ b/gitnexus/src/core/ingestion/scope-resolution/passes/compound-receiver.ts @@ -1204,10 +1204,12 @@ export function resolveCompoundReceiverClass( // itself, so a variant / static member hop is read off the class scope), // and the remaining segments are walked as members. Longest first: the // prefix is a class, not a value, and `a.B.C` must seed at `C`, not stop - // at `B` and read `C` as a member of it. + // at `B` and read `C` as a member of it. The whole receiver may be the + // class (`opmod.Op` — no member segment left), exactly as a bare class + // name head resolves to the class constant above. let firstHop = 1; if (currentClass === undefined && headType === undefined && options.resolveQualifiedClass) { - for (let k = parts.length - 1; k >= 2; k--) { + for (let k = parts.length; k >= 2; k--) { const prefix = parts.slice(0, k).join('.'); if (prefix.includes('(')) continue; const seeded = options.resolveQualifiedClass(prefix, inScope); diff --git a/gitnexus/src/core/ingestion/scope-resolution/passes/receiver-bound-calls.ts b/gitnexus/src/core/ingestion/scope-resolution/passes/receiver-bound-calls.ts index 241793e1b..ea0168070 100644 --- a/gitnexus/src/core/ingestion/scope-resolution/passes/receiver-bound-calls.ts +++ b/gitnexus/src/core/ingestion/scope-resolution/passes/receiver-bound-calls.ts @@ -1771,7 +1771,15 @@ export function emitReceiverBoundCalls( nodeLookup, site, memberDef, - memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', + // Same marker rule as Case 1 / Case 2: a constructor-form site + // reached through a dotted type binding keeps its + // ` (constructor)` suffix when the provider opted in; for + // every other provider the string is unchanged. + constructionSiteReason( + memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', + site, + provider.markConstructionSites, + ), seen, // Explicit defaults so the trailing capture ctx (#2227 U2) can // be threaded without changing dedup/confidence behavior. diff --git a/gitnexus/test/unit/zig-import-resolver.test.ts b/gitnexus/test/unit/zig-import-resolver.test.ts index fd479d563..29c438699 100644 --- a/gitnexus/test/unit/zig-import-resolver.test.ts +++ b/gitnexus/test/unit/zig-import-resolver.test.ts @@ -64,6 +64,11 @@ describe('resolveZigImportInternal', () => { expect(resolveZigImportInternal('src/main.zig', '/src/foo.zig', files)).toBeNull(); // Backslash-spelled absolute paths normalize to the same rejection. expect(resolveZigImportInternal('src/main.zig', '\\foo.zig', files)).toBeNull(); + // A drive-qualified spelling carries a `/` after normalization and used to + // take the importer-relative branch (probing `src/C:/foo.zig`). + const drive = new Set([...files, 'src/C:/foo.zig']); + expect(resolveZigImportInternal('src/main.zig', 'C:\\foo.zig', drive)).toBeNull(); + expect(resolveZigImportInternal('src/main.zig', 'C:/foo.zig', drive)).toBeNull(); // The relative spelling next to it still resolves. expect(resolveZigImportInternal('src/main.zig', 'foo.zig', files)).toBe('src/foo.zig'); });