Repository navigation
fix(python): calls like reg.get() on a typed local or parameter reach the class's method - #2821
Merged
Merged
Conversation
… the class's method In Python, a call of a method named like a builtin dict, list, string or file method (get, update, items, pop, close and the rest of the builtin-method list) was dropped by the builtin filter before any strategy could read its receiver. Only a receiver whose capitalized name is a project class, or an imported project module, got past it. So `reg = SystemPackRegistry(); reg.get(k)`, `registry = load_registry()` with `def load_registry() -> SystemPackRegistry`, and a parameter `reg: SystemPackRegistry` gave no edge, while `self.reg.get()` linked through the attribute reader of PR #1926. A call the filter drops now gets one way back, by evidence only: matchPythonTypedLocalCall. It reads the def that binds the receiver where the call reads it (the calling def, or one it is nested in). It types the name only when every binding there gives it the same project class: - a parameter gives what its annotation names ("T", Optional[T], T | None); - an assignment gives its own annotation, or what its whole value makes: a constructor, or a factory typed by its return annotation or by its returns (the attribute reader's pythonValueEvidence and pythonFactoryType); - `x = None` gives nothing either way; - each class is the one the file's imports name (pythonClassNamed), so a class imported from outside the project is none of the project's; - the method must be the class's own or a declared base's. Everything else stays dropped: any other binding of the name (a loop, with, except or comprehension target, a tuple or augmented assignment, a walrus, an import, del, a nested def or class of the name, a nonlocal write from a nested def), a value that names no class ({}, json.load(p), Foo().data(), a union), a rebound callee, a module-level name. These are the shapes the reporter's follow-up found making wrong edges with a looser reader. Like an attribute call, the edge records the other files its answer read (typeFrom), so a sync that edits the factory or a base re-opens it. Validation: - New __tests__/python-typed-builtin-method-calls.test.ts, 20 tests: - the issue's three calls; annotated, quoted and Optional parameters; items, pop and close; a module-qualified constructor; typed, untyped and awaited factories; a None placeholder; a local annotation; a closure; typeFrom; two same-named classes; an inherited method; - the reporter's trap shapes, which stay unlinked; - three sync cases, each matching a fresh index. With main's source the 13 positive tests fail and the 7 trap tests pass. - A/B against main 3f58e34. Both arms were built from their own sources with the kernel. Rebased onto e32021f (PR #2820, outside Python), the fix arm's graphs are byte-identical to before the rebase: - requests: +15 edges; - sqlalchemy: +52 edges; - django's 2,938 Python files: +73 edges; - 0 removed and no other change in any of them; - express and gin (controls): byte-identical. All 140 added edges were read at their source: each is a constructed local, a factory-typed local or an annotated parameter calling its own class's method, or one a declared base has. The issue's fixtures link all three calls, and the verification kit's nine trap call sites stay unresolved, kernel and wasm alike. - Cost: a CPU profile puts the new reader at about 1.0 s on django and 1.2 s on sqlalchemy, around 1% of index CPU. Index CPU medians of 3 interleaved runs, main vs fix: sqlalchemy 98.6 s vs 98.5 s, django 97.9 s vs 99.1 s (single runs vary by several seconds on this shared machine). - kernel-parity.mjs: 295/295 Python files at parity. Fixes #2583 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 23, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
In Python, a call of your own class's method named like a builtin dict, list, string or file method —
get,update,items,pop,closeand the rest of the builtin-method list — now links to that method when the function says what the receiver holds:All three calls from the issue link now. The shapes from the reporter's follow-up comment, where a looser receiver typing made wrong edges, still link nothing.
Root cause
isBuiltInOrExternal(src/resolution/index.ts) drops a dotted Python call whose method is inPYTHON_BUILT_IN_METHODSbefore any strategy runs. It keeps the call only when the capitalized receiver name is a project class (systemPackRegistry→SystemPackRegistry) or the receiver is an imported project module (#1681). A typed local or parameter with any other name never reachedmatchMethodCall.self.reg.get()already linked, because the attribute reader from #1926 runs on the attribute chain.Fix
A call the filter drops gets one way back, by evidence only.
matchPythonTypedLocalCall(src/resolution/name-matcher.ts) reads the def that binds the receiver where the call reads it: the calling def, or one it is nested in. It types the name only when every binding there gives it the same project class:"T",Optional[T],T | None);-> Tor by its returns. These are the attribute reader'spythonValueEvidenceandpythonFactoryType, soFoo().data()is no evidence ofFoo;x = Nonesays nothing either way;pythonClassNamed).from queue import Queuenames no project class, even when the project has aQueue, andfrom b import Handleris b'sHandler, not a's;Everything else leaves the receiver unknown and the call dropped, as before:
for,with,exceptor comprehension target, a tuple or augmented assignment, a walrus, an import,del, a nesteddeforclassof the name, or anonlocalwrite from a nested def;{},json.load(p),Foo().data()or a union;Store = load(); reg = Store());These are exactly the shapes in the reporter's 2026-10-15 comment. The edge records the other files its answer was read from (
typeFrom), as an attribute call's does, so a sync that edits the factory or a base re-opens it.Only calls the filter used to drop can change, and only Python ones.
Validation
Tests. New
__tests__/python-typed-builtin-method-calls.test.ts, 20 tests:Optionalparameters;items/pop/close; a module-qualified constructor; typed, untyped and awaited factories; aNoneplaceholder; a local annotation; a closure;typeFrom; two same-named classes; an inherited method;Queue/Session/Counter/StringIO, rebinding to{}andjson.load, branches, loops,with, comprehensions,Foo().data()/Foo().build(), a union, an unannotated parameter, a rebound class name, a rebound global, module-level code, and an outside base;Against main's source the 13 positive tests fail and the 7 trap tests pass; with the fix all 20 pass. The 29 Python-related suite files (572 tests) pass.
A/B against main
3f58e343. Both arms were built from their own sources, kernel included, indexed fresh with the default engine, dumped withscripts/dump-graph.mjsand diffed by edge site. After the rebase ontoe32021f6(#2820, Java/Kotlin/C#/Scala only), the fix arm's dumps are byte-identical to before the rebase.611c616269e49c9c.pyfiles only8e4e95da0dee2c520f09c3a9I read the source behind every added edge. Each one is a constructed local, a factory-typed local or an annotated parameter calling its own class's method, or one a declared base has. Examples:
jar = requests.cookies.RequestsCookieJar(); jar.get(…);record: ConnectionPoolEntry→record.close(), andsess = object_session(…)→Session.flush(through-> Optional[Session]);clause = WhereNode(…); clause.add(…)→tree.Node.add(inherited), andstream = LimitedStream(…); stream.readline().Django was indexed as a copy of its Python files, under the sweep's RAM rule for big repos; its other files take no part in Python resolution.
The issue's fixtures link all three calls on both engines. The verification kit's nine trap call sites stay unresolved on both, identical to main.
Cost. Index CPU, median of 3 interleaved runs, main vs fix: sqlalchemy 98.6 s vs 98.5 s, django 97.9 s vs 99.1 s. Single runs vary by several seconds on this shared machine, so I also took a CPU profile: it puts the new reader at about 1.0 s on django and 1.2 s on sqlalchemy, around 1% of index CPU.
Kernel parity.
scripts/kernel-parity.mjs --lang pythonover requests and sqlalchemy/lib: 295/295 files at parity. The change is resolution-only.Full suite on the pushed commit
0008dc90(CODEGRAPH_KERNEL_EXPECT=1, kernel built): 738 files (732 passed, 6 skipped), 8,852 tests: 8,770 passed, 0 failed, 82 skipped. No reruns were needed.Fixes #2583
Related
isBuiltInOrExternal.Left out
__init__.py, likefrom django.template import Context(defined indjango/template/context.py). The class lookup is the attribute reader'spythonClassNamed, which doesn't follow re-exports, soself.ctx.update()misses the same class today. Fixing it for both readers together is a separate change.reg: Optional[Registry] = None, is not read yet. The same annotation on a parameter is._svc = Registry()at module scope, then_svc.get()) stays dropped for builtin names; Python:_svc.delete()on a module-level instance gets no edge unless the method name is unique #2582 types those for other method names. Reusing its reader here added 6 correct sqlalchemy edges but about 2 s per index, because it re-reads whole class bodies on every call, so I left it out.getin Django), sync doesn't retry the call, and it links at the next full index. That is the existing per-name retry ceiling. In smaller projects the sync cases above converge.🤖 Generated with Claude Code