From 13086508e55048cd1ca63dd1f58820c93e3c0457 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Tue, 21 Apr 2026 20:39:05 +0100 Subject: [PATCH] =?UTF-8?q?feat(csharp-scope):=20parity=20Unit=205d=20?= =?UTF-8?q?=E2=80=94=20cross-file=20typeBinding=20mirror?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes 3 parity failures (15 → 12). `languages/csharp/namespace-siblings.ts`: extend the pass to mirror method return-type bindings from accessible sibling files' Module scopes into the importer's Module scope. "Accessible" = same-namespace siblings + `using namespace X;` targets. Without this mirror, `var u = svc.GetUser()` in App.cs couldn't chain-follow to User even after Unit 5b's module-scope hoist: `GetUser → User` lived on User.cs's Module scope, which isn't on the ancestor chain of App.cs's function scope, and `propagateImportedReturnTypes` only mirrors across explicit ImportEdge targets (not same-namespace implicit visibility). Closes: var-invocation return type, async/await u.Save (ambient namespace), cross-file return-type propagation (via u.Save / u.GetName in Program.cs). Python parity 204/204 on both flag paths; legacy C# 175/175; 12 C# parity failures remain. --- .../languages/csharp/namespace-siblings.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/gitnexus/src/core/ingestion/languages/csharp/namespace-siblings.ts b/gitnexus/src/core/ingestion/languages/csharp/namespace-siblings.ts index c99b68ae9..d434594fd 100644 --- a/gitnexus/src/core/ingestion/languages/csharp/namespace-siblings.ts +++ b/gitnexus/src/core/ingestion/languages/csharp/namespace-siblings.ts @@ -130,6 +130,50 @@ export function populateCsharpNamespaceSiblings( // for module-scope typeBindings). const finalized = indexes.bindings as Map>; + // Cross-namespace type-binding propagation: for each file, mirror + // method return-type bindings from same-namespace sibling files and + // from files in namespaces the importer `using`s, into the + // importer's Module scope typeBindings. This enables + // chain-follow from `var u = svc.GetUser()` → `GetUser → User` + // even across files — without it the chain stalls at `GetUser` + // because the return binding lives in the defining file's Module + // scope, which isn't an ancestor of the importer's scope chain. + for (const parsed of parsedFiles) { + const moduleScope = parsed.scopes.find((s) => s.kind === 'Module'); + if (moduleScope === undefined) continue; + const moduleTypeBindings = moduleScope.typeBindings as Map< + string, + import('gitnexus-shared').TypeRef + >; + + // Accessible namespaces = this file's own namespaces + every + // `using namespace X;` target. + const accessibleNamespaces = new Set(); + const fileContent = inputs.fileContents.get(parsed.filePath) ?? ''; + NAMESPACE_RE.lastIndex = 0; + let nm: RegExpExecArray | null; + while ((nm = NAMESPACE_RE.exec(fileContent)) !== null) accessibleNamespaces.add(nm[1]!); + if (accessibleNamespaces.size === 0) accessibleNamespaces.add(''); + for (const imp of parsed.parsedImports) { + if (imp.kind === 'namespace' && imp.targetRaw !== null) { + accessibleNamespaces.add(imp.targetRaw); + } + } + + for (const nsName of accessibleNamespaces) { + const bucket = buckets.get(nsName); + if (bucket === undefined) continue; + for (const scopeInfo of bucket.scopes) { + if (scopeInfo.filePath === parsed.filePath) continue; + if (scopeInfo.scope.kind !== 'Module') continue; + for (const [boundName, typeRef] of scopeInfo.scope.typeBindings) { + if (moduleTypeBindings.has(boundName)) continue; + moduleTypeBindings.set(boundName, typeRef); + } + } + } + } + // Cross-namespace imports: for each file's `using X;` directive, // if `X` matches a known namespace bucket, inject that bucket's // classes into the importer's module scope. This is what makes