feat(python-scope): enumerate(X) for-loop tuple destructuring

Adds two new typeBinding capture patterns for the canonical enumerate
pattern:

  for (i, u) in enumerate(users): ...   ; tuple_pattern
  for  i, u  in enumerate(users): ...   ; pattern_list

Both bind the second tuple element (u) to the iterable identifier
(users). The chain-follow then unwraps users → its element type via
the existing generic-strip in interpret.ts (List[User] → User).

The #eq? predicate scopes the pattern to enumerate specifically;
generic tuple destructuring of arbitrary callables is left to a
future iteration once we have a richer signal for "what does this
call yield".

Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 7 fail / 184 pass (was 8/183; +1 — `parenthesized tuple:
  for (i, u) in enumerate(users)` now passes).
- tsc --noEmit clean.
This commit is contained in:
Gergo Magyar 2026-04-20 12:01:03 +01:00
parent f4f51910dd
commit a04cc1732c
2 changed files with 49 additions and 0 deletions

View file

@ -119,6 +119,31 @@ export const PYTHON_SCOPE_QUERY = `
right: (call
function: (identifier) @type-binding.type)) @type-binding.alias
;; for (i, u) in enumerate(X) paren-tuple, bind last element to X's
;; element type. \`enumerate(X)\` yields (int, X-element); the second
;; pattern var takes X (which the chain-follow then unwraps to its
;; element type via generic-strip in interpret.ts).
(for_statement
left: (tuple_pattern
(identifier)
(identifier) @type-binding.name)
right: (call
function: (identifier) @_enum
arguments: (argument_list
(identifier) @type-binding.type))
(#eq? @_enum "enumerate")) @type-binding.alias
;; for i, u in enumerate(X) pattern_list (no parens) variant.
(for_statement
left: (pattern_list
(identifier)
(identifier) @type-binding.name)
right: (call
function: (identifier) @_enum
arguments: (argument_list
(identifier) @type-binding.type))
(#eq? @_enum "enumerate")) @type-binding.alias
;; Type bindings (variable annotations: \`u: User\` / \`u: User = x\`)
(assignment
left: (identifier) @type-binding.name

View file

@ -134,6 +134,30 @@
right: (call
function: (identifier) @type-binding.type)) @type-binding.alias
; for (i, u) in enumerate(X) and for i, u in enumerate(X) — bind the
; second tuple element to X. enumerate yields (int, X-element); the
; chain-follow unwraps X via generic-strip when X is an annotated
; collection.
(for_statement
left: (tuple_pattern
(identifier)
(identifier) @type-binding.name)
right: (call
function: (identifier) @_enum
arguments: (argument_list
(identifier) @type-binding.type))
(#eq? @_enum "enumerate")) @type-binding.alias
(for_statement
left: (pattern_list
(identifier)
(identifier) @type-binding.name)
right: (call
function: (identifier) @_enum
arguments: (argument_list
(identifier) @type-binding.type))
(#eq? @_enum "enumerate")) @type-binding.alias
; ─── Type bindings: function return-type annotations ─────────────────────
;
; `def get_user() -> User:` — binds the function's NAME to its return