From 6d220cfc85a39f2ad948a9e6ddab0145d6d32b3c Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Mon, 20 Apr 2026 10:25:32 +0100 Subject: [PATCH] feat(python-scope): for-loop call-iterable typeBinding Adds `(for_statement left: (identifier) right: (call function: (identifier)))` to the typeBinding capture set. Combined with Unit 3's return-type capture and the cross-file return-type propagation pass, this makes `for u in get_users(): u.save()` resolve to `User.save` even when `get_users` is imported from another module. Captured as `@type-binding.alias` (rawName = function identifier, without parens) so the existing chain-follow walks the alias to the function's return-type binding without any new code path. Verification: - Flag-off: 191/191 (identical baseline). - Flag-on: 12 fail / 179 pass (was 16/175; +4 for-loop call-iterable tests across get_users / get_repos fixtures). - tsc --noEmit clean. --- gitnexus/src/core/ingestion/languages/python/query.ts | 11 +++++++++++ .../src/core/ingestion/languages/python/scopes.scm | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/gitnexus/src/core/ingestion/languages/python/query.ts b/gitnexus/src/core/ingestion/languages/python/query.ts index abfd43766..4732340cc 100644 --- a/gitnexus/src/core/ingestion/languages/python/query.ts +++ b/gitnexus/src/core/ingestion/languages/python/query.ts @@ -108,6 +108,17 @@ export const PYTHON_SCOPE_QUERY = ` left: (identifier) @type-binding.name right: (identifier) @type-binding.type) @type-binding.alias +;; For-loop iterable of a free-call result: +;; def get_users() -> list[User]: ... +;; for u in get_users(): # u: get_users → User via chain follow +;; u.save() +;; Captures the call's function identifier as the rawName. With +;; \`propagateImportedReturnTypes\`, this works cross-file too. +(for_statement + left: (identifier) @type-binding.name + right: (call + function: (identifier) @type-binding.type)) @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 c92d0ee67..69ca6c59c 100644 --- a/gitnexus/src/core/ingestion/languages/python/scopes.scm +++ b/gitnexus/src/core/ingestion/languages/python/scopes.scm @@ -125,6 +125,15 @@ left: (identifier) @type-binding.name type: (type) @type-binding.type) @type-binding.annotation +; For-loop iterable of a free-call result: `for u in get_users()` — +; binds `u → get_users` so the chain post-pass follows it through +; `get_users`'s return-type annotation (cross-file via +; `propagateImportedReturnTypes`). +(for_statement + left: (identifier) @type-binding.name + right: (call + function: (identifier) @type-binding.type)) @type-binding.alias + ; ─── Type bindings: function return-type annotations ───────────────────── ; ; `def get_user() -> User:` — binds the function's NAME to its return