From 594bcf32822b03be58d89f87958ebde7ccd205cf Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Tue, 21 Apr 2026 18:42:36 +0100 Subject: [PATCH] =?UTF-8?q?feat(csharp-scope):=20parity=20Unit=202a=20?= =?UTF-8?q?=E2=80=94=20foreach=20+=20pattern=20+=20field=20captures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes 11 parity failures (48 → 37). Partial Unit 2 progress. Adds type-binding captures for every shape the parity suite exercises whose resolution path is in-file: - Typed foreach `foreach (User u in xs)` — @type-binding.annotation with bindingName `u` and type `User`. - Var foreach `foreach (var u in xs)` — @type-binding.alias so the generic-stripper unwraps `List` / `Dictionary.Values` to the element type at chain-follow time. Matches Python's for-loop alias pattern. - `is` pattern `if (obj is User u)` — @type-binding.annotation with scope narrowing simplified to function scope (matches Python's match-case treatment since we don't emit @scope.block). - `switch_section > declaration_pattern` (`case User u:`) — no case_pattern_switch_label wrapper in tree-sitter-c-sharp. - `recursive_pattern` (`is User { Age: 1 } u` / `case User { ... } u:`) — named binding via type+name fields on the pattern node. - Field declaration `private City _city;` — @type-binding.annotation attached to the class scope for `this._city.X` resolution. - Property declaration `public User Owner { get; set; }` — same. - Assignment rebind `alias = Factory()` / `alias = new User()` — @type-binding.alias / @type-binding.constructor so reassignment propagates type info to later receiver-typed resolution. Closed tests: foreach (3), var foreach Tier 1c (2), is-pattern (1), switch pattern (2), recursive_pattern (3). Remaining 37 include tests that need cross-file same-namespace visibility (field chains, assignment chain, cross-file return-type propagation) — deferred to Unit 5 where the IMPORTS/cross-file work lives. 74/74 scope-resolution unit tests pass; legacy path 175/175 green. --- .../core/ingestion/languages/csharp/query.ts | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/gitnexus/src/core/ingestion/languages/csharp/query.ts b/gitnexus/src/core/ingestion/languages/csharp/query.ts index 39501e702..6dbe5e6b2 100644 --- a/gitnexus/src/core/ingestion/languages/csharp/query.ts +++ b/gitnexus/src/core/ingestion/languages/csharp/query.ts @@ -195,6 +195,149 @@ const CSHARP_SCOPE_QUERY = ` (invocation_expression function: (identifier) @type-binding.type)))) @type-binding.alias +;; Type bindings — field declaration: \`private City _city;\`. Attaches +;; to the enclosing class scope via positionIndex, so \`this._city.X\` +;; can look up _city's type on the class. +(field_declaration + (variable_declaration + type: (identifier) @type-binding.type + (variable_declarator + name: (identifier) @type-binding.name))) @type-binding.annotation + +(field_declaration + (variable_declaration + type: (generic_name) @type-binding.type + (variable_declarator + name: (identifier) @type-binding.name))) @type-binding.annotation + +(field_declaration + (variable_declaration + type: (qualified_name) @type-binding.type + (variable_declarator + name: (identifier) @type-binding.name))) @type-binding.annotation + +;; Type bindings — property declaration: \`public User Owner { get; set; }\`. +(property_declaration + type: (identifier) @type-binding.type + name: (identifier) @type-binding.name) @type-binding.annotation + +(property_declaration + type: (generic_name) @type-binding.type + name: (identifier) @type-binding.name) @type-binding.annotation + +(property_declaration + type: (qualified_name) @type-binding.type + name: (identifier) @type-binding.name) @type-binding.annotation + +(property_declaration + type: (nullable_type) @type-binding.type + name: (identifier) @type-binding.name) @type-binding.annotation + +;; Type bindings — assignment rebind: \`alias = Factory();\` where +;; \`alias\` was previously declared. Same alias shape as \`var x = F();\`. +(assignment_expression + left: (identifier) @type-binding.name + right: (invocation_expression + function: (identifier) @type-binding.type)) @type-binding.alias + +;; Type bindings — assignment with constructor: \`alias = new User();\`. +(assignment_expression + left: (identifier) @type-binding.name + right: (object_creation_expression + type: (identifier) @type-binding.type)) @type-binding.constructor + +(assignment_expression + left: (identifier) @type-binding.name + right: (object_creation_expression + type: (generic_name) @type-binding.type)) @type-binding.constructor + +;; Type bindings — \`is\` pattern: \`if (obj is User u) { u.Save(); }\`. +;; The declaration_pattern carries both the matched type and the +;; binding name; scope narrowing to the guarded branch is simplified +;; to function-scope (matches Python's match-case treatment) since we +;; don't emit @scope.block. +(is_pattern_expression + pattern: (declaration_pattern + type: (identifier) @type-binding.type + name: (identifier) @type-binding.name)) @type-binding.annotation + +(is_pattern_expression + pattern: (declaration_pattern + type: (generic_name) @type-binding.type + name: (identifier) @type-binding.name)) @type-binding.annotation + +(is_pattern_expression + pattern: (declaration_pattern + type: (qualified_name) @type-binding.type + name: (identifier) @type-binding.name)) @type-binding.annotation + +;; Type bindings — \`case User u:\` inside a switch section. +;; tree-sitter-c-sharp's switch_section directly contains the +;; declaration_pattern / recursive_pattern (no case_pattern_switch_label +;; wrapper as in other C# grammars). +(switch_section + (declaration_pattern + type: (identifier) @type-binding.type + name: (identifier) @type-binding.name)) @type-binding.annotation + +(switch_section + (declaration_pattern + type: (generic_name) @type-binding.type + name: (identifier) @type-binding.name)) @type-binding.annotation + +;; Type bindings — recursive_pattern with named binding: +;; \`x is User { Age: 1 } u\` / \`case User { Age: 1 } u:\`. type + name +;; are named fields on recursive_pattern; inner property/positional +;; clauses don't affect the binding. +(is_pattern_expression + pattern: (recursive_pattern + type: (identifier) @type-binding.type + name: (identifier) @type-binding.name)) @type-binding.annotation + +(switch_section + (recursive_pattern + type: (identifier) @type-binding.type + name: (identifier) @type-binding.name)) @type-binding.annotation + +;; Type bindings — typed foreach: \`foreach (User u in xs)\`. +;; Shape parity with \`User u = …;\` — left binds to the declared type. +(foreach_statement + type: (identifier) @type-binding.type + left: (identifier) @type-binding.name) @type-binding.annotation + +(foreach_statement + type: (generic_name) @type-binding.type + left: (identifier) @type-binding.name) @type-binding.annotation + +(foreach_statement + type: (qualified_name) @type-binding.type + left: (identifier) @type-binding.name) @type-binding.annotation + +(foreach_statement + type: (nullable_type) @type-binding.type + left: (identifier) @type-binding.name) @type-binding.annotation + +;; Type bindings — \`var\` foreach: \`foreach (var u in xs)\`. Alias to +;; the iterable's identifier / chain so chain-follow unwraps +;; \`List\` / \`Dictionary.Values\` to the element type via +;; the generic-stripper in interpret.ts. Mirrors Python's for-loop +;; alias patterns. +(foreach_statement + type: (implicit_type) + left: (identifier) @type-binding.name + right: (identifier) @type-binding.type) @type-binding.alias + +(foreach_statement + type: (implicit_type) + left: (identifier) @type-binding.name + right: (member_access_expression) @type-binding.type) @type-binding.alias + +(foreach_statement + type: (implicit_type) + left: (identifier) @type-binding.name + right: (invocation_expression + function: (identifier) @type-binding.type)) @type-binding.alias + ;; Return-type captures on method_declaration / property_declaration / ;; field_declaration are deferred — tree-sitter-c-sharp does not expose ;; the return/field type under a simple named field that pattern-matches