From a04cc1732c4b73da13e8a7ffd1042262a0577e8a Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Mon, 20 Apr 2026 12:01:03 +0100 Subject: [PATCH] feat(python-scope): enumerate(X) for-loop tuple destructuring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../core/ingestion/languages/python/query.ts | 25 +++++++++++++++++++ .../ingestion/languages/python/scopes.scm | 24 ++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/gitnexus/src/core/ingestion/languages/python/query.ts b/gitnexus/src/core/ingestion/languages/python/query.ts index 4732340cc..87f2eee39 100644 --- a/gitnexus/src/core/ingestion/languages/python/query.ts +++ b/gitnexus/src/core/ingestion/languages/python/query.ts @@ -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 diff --git a/gitnexus/src/core/ingestion/languages/python/scopes.scm b/gitnexus/src/core/ingestion/languages/python/scopes.scm index 69ca6c59c..944cab3d6 100644 --- a/gitnexus/src/core/ingestion/languages/python/scopes.scm +++ b/gitnexus/src/core/ingestion/languages/python/scopes.scm @@ -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