diff --git a/gitnexus/src/core/ingestion/languages/csharp/namespace-siblings.ts b/gitnexus/src/core/ingestion/languages/csharp/namespace-siblings.ts index 376c2fcf2..c99b68ae9 100644 --- a/gitnexus/src/core/ingestion/languages/csharp/namespace-siblings.ts +++ b/gitnexus/src/core/ingestion/languages/csharp/namespace-siblings.ts @@ -73,7 +73,10 @@ export function populateCsharpNamespaceSiblings( NAMESPACE_RE.lastIndex = 0; let m: RegExpExecArray | null; while ((m = NAMESPACE_RE.exec(content)) !== null) names.push(m[1]!); - if (names.length === 0) continue; + // Files with no `namespace X;` declaration still share visibility + // — they all live in the default (global) namespace. Use the + // empty-string bucket key for this case. + if (names.length === 0) names.push(''); const namespaceScopes = parsed.scopes.filter((s) => s.kind === 'Namespace'); // With file-scoped namespaces (`namespace X;`), the Namespace 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 fceaae8d2..688d770d3 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 @@ -287,6 +287,36 @@ export function emitReceiverBoundCalls( emitted++; handledSites.add(siteKey); } + continue; + } + } + } + + // ── Case 5: class-as-receiver (static call / type member) ──── + // `Animal.Classify()` — receiver name resolves to a Class + // binding, not a typeBinding. Look up the member on the class's + // MRO chain. Python syntactically collapses this with free + // calls; C# (and other statically-typed languages) distinguish + // via the member_access_expression shape. + if (typeRef === undefined) { + const classDef = findClassBindingInScope(site.inScope, receiverName, scopes); + if (classDef !== undefined) { + const chain = [classDef.nodeId, ...scopes.methodDispatch.mroFor(classDef.nodeId)]; + let memberDef: SymbolDefinition | undefined; + for (const ownerId of chain) { + memberDef = findOwnedMember(ownerId, memberName, index); + if (memberDef !== undefined) break; + } + if (memberDef !== undefined) { + const reason = + site.kind === 'write' || site.kind === 'read' + ? site.kind + : 'scope-resolution: class-receiver'; + const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, reason, seen); + if (ok) { + emitted++; + handledSites.add(siteKey); + } } } }