From 2de104150ec6791b6cadfd5b19a84f4e5f559285 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Tue, 21 Apr 2026 20:47:08 +0100 Subject: [PATCH] =?UTF-8?q?feat(csharp-scope):=20parity=20Unit=206a=20?= =?UTF-8?q?=E2=80=94=20class-like=20owner=20extension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes 1 parity failure (10 → 9). Extends `populateClassOwnedMembers` to recognize Interface / Struct / Record / Enum / Trait as class-like owners, not just Class. The C# scope query collapses interface_declaration / struct_declaration / record_declaration / enum_declaration to @scope.class (they share body-scope semantics), but the declaration-side tags produce defs of type Interface / Struct / Record / Enum. `populateClassOwnedMembers` previously only looked for Class-typed defs in class scopes, so interface members (including C# 8+ default methods) never got ownerIds — making them invisible to `findOwnedMember` via `memberByOwner`. With this fix, `user.Validate()` on a variable typed as `IValidator` resolves correctly: receiver-bound-calls Case 4 finds IValidator via findClassBindingInScope (which already accepted Interface), walks the chain, and findOwnedMember locates Validate now that the interface default has a proper ownerId. Legacy C# 175/175 green; Python parity 204/204 on both flag paths; 9 C# parity failures remain. --- .../scope-resolution/scope/walkers.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/gitnexus/src/core/ingestion/scope-resolution/scope/walkers.ts b/gitnexus/src/core/ingestion/scope-resolution/scope/walkers.ts index 3f4dc8c39..95cdcabc9 100644 --- a/gitnexus/src/core/ingestion/scope-resolution/scope/walkers.ts +++ b/gitnexus/src/core/ingestion/scope-resolution/scope/walkers.ts @@ -183,13 +183,25 @@ export function populateClassOwnedMembers(parsed: ParsedFile): void { // on `class U: def save(self): def helper(): ...` — helper.ownerId will // remain undefined. The theoretical concern is real only if the // extractor ever stops creating scopes for inner defs. + // Class-like def types: Class scope covers C#'s interface/struct/ + // record/enum too (they all collapse to @scope.class per the query + // contract). Interface default methods land as children of the + // Interface def here. + const isClassLike = (t: string): boolean => + t === 'Class' || + t === 'Interface' || + t === 'Struct' || + t === 'Record' || + t === 'Enum' || + t === 'Trait'; + for (const scope of parsed.scopes) { // Methods: function scope whose parent is a Class scope. Owner is - // the parent's Class def. + // the parent's class-like def. if (scope.parent !== null) { const parentScope = scopesById.get(scope.parent); if (parentScope !== undefined && parentScope.kind === 'Class') { - const classDef = parentScope.ownedDefs.find((d) => d.type === 'Class'); + const classDef = parentScope.ownedDefs.find((d) => isClassLike(d.type)); if (classDef !== undefined) { for (const def of scope.ownedDefs) { (def as { ownerId?: string }).ownerId = classDef.nodeId; @@ -199,9 +211,9 @@ export function populateClassOwnedMembers(parsed: ParsedFile): void { } } // Class-body fields: defs directly owned by a Class scope (the - // class def itself excluded). + // class-like def itself excluded). if (scope.kind === 'Class') { - const classDef = scope.ownedDefs.find((d) => d.type === 'Class'); + const classDef = scope.ownedDefs.find((d) => isClassLike(d.type)); if (classDef !== undefined) { for (const def of scope.ownedDefs) { if (def === classDef) continue;