feat(csharp-scope): parity Unit 2a — foreach + pattern + field captures

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<User>` / `Dictionary<K,V>.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.
This commit is contained in:
Gergo Magyar 2026-04-21 18:42:36 +01:00
parent 908798af0b
commit 594bcf3282

View file

@ -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<User>\` / \`Dictionary<K,V>.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