Repository navigation
Python: .get()/.update() on a typed project-class instance get no edge (dropped as builtins) #2583
Description
Summary
In Python, a call to a project method named like a builtin collection method (get, update, items, pop, …) is dropped as a builtin unless the receiver variable's name, capitalized, is the name of a project class. This happens even when the receiver's type is known: reg = SystemPackRegistry(); reg.get("y") gets no edge, while the same code with the methods renamed, or with the variable named systemPackRegistry, resolves at 0.9.
Reproduction (verified)
On main at 8fe3156, built from source, Linux (WSL2), Node 22.23.2, WASM extractor (CODEGRAPH_KERNEL=0). Two files:
# loader.py
class SystemPackRegistry:
def get(self, pack_id):
return pack_id
def update(self):
return None
def load_registry() -> SystemPackRegistry:
return SystemPackRegistry()# test_packs.py
from loader import SystemPackRegistry, load_registry
def test_pack():
registry = load_registry()
return registry.get("x") # line 6
def test_ctor():
reg = SystemPackRegistry()
reg.update() # line 11
return reg.get("y") # line 12codegraph init -i, then the calls / imports / instantiates edges:
load_registry -instantiates-> SystemPackRegistry [loader.py:1] @10 exact-match/0.9
test_packs.py -imports-> SystemPackRegistry [loader.py:1] @1 import/0.9
test_packs.py -imports-> load_registry [loader.py:9] @1 import/0.9
test_pack -calls-> load_registry [loader.py:9] @5 import/0.9
test_ctor -instantiates-> SystemPackRegistry [loader.py:1] @10 import/0.9
There is no edge for lines 6, 11 or 12.
Expected: test_ctor -calls-> SystemPackRegistry::update (line 11) and -> SystemPackRegistry::get (line 12), and test_pack -calls-> SystemPackRegistry::get (line 6), as in the controls below.
Controls, same build:
| Change | Line 6 | Line 11 | Line 12 |
|---|---|---|---|
| as above | none | none | none |
methods renamed get→lookup, update→refresh |
SystemPackRegistry::lookup instance-method/0.7 |
SystemPackRegistry::refresh instance-method/0.9 |
SystemPackRegistry::lookup instance-method/0.9 |
reg renamed systemPackRegistry |
none | SystemPackRegistry::update instance-method/0.9 |
SystemPackRegistry::get instance-method/0.9 |
So the resolver can type both receivers, but the builtin filter runs first and decides from the variable's name alone.
Likely cause (inferred from reading the code)
isBuiltInOrExternal runs before any resolution strategy. For a dotted Python ref whose method is in PYTHON_BUILT_IN_METHODS (get, update, items, pop, read, write, close, …), it keeps the ref only if the capitalized receiver (reg → Reg) is a project class name, or the receiver is an imported project module (#1681). Otherwise it returns true, and the ref is discarded before matchMethodCall can infer the receiver's type from reg = SystemPackRegistry() or -> SystemPackRegistry.
One option is to leave these refs to matchMethodCall when the receiver has a local declaration that the existing receiver-type inference can type, and drop them only when it can't.
Related
- Python: a top-level function named like a collection method (append/update/get) gets ZERO real callers and fabricated ones — uncovered shapes left by #715 and #1317, live in 1.6.0 #1681 / fix(resolution): resolve module-qualified calls colliding with builtin methods (#1681) #1749: the same filter dropped
module.append()on an imported project module. The fix added the project-module exception, not a typed-receiver one. - Python: module-scope dict member calls can resolve to unrelated project methods #1652 / fix(resolution): decline Python module-scope collection method guesses (#1652) #1776: the filter's module-scope collection check (
d = {}thend.get(...)). That case should still be dropped. - [python] matching built-ins #1230: the original Python builtin-name false positives this filter guards against.
- fix(resolution): library-method calls on untyped receivers need receiver evidence (Rust, Go, Python, Kotlin, Scala, C#, Java) #1947 (open PR): the opposite direction, library-method names on untyped receivers binding to project methods. This issue is about typed receivers losing real edges.
A build of the inth3shadows fork that carries open PRs #1704 and #1926 gives the same output.
Not checked
- Other names in the set (
items,pop,read,close, …); onlygetandupdatewere run. self.<attr>.get()and parameters annotated with the class (def f(reg: SystemPackRegistry)).- The native kernel extractor (only
CODEGRAPH_KERNEL=0was run). - Windows and macOS.
Activity
inth3shadows commentedon Oct 15, 2026
I tried the fix suggested above, on
mainat c29caf2. The fix kept a builtin-named call when the receiver's type can be read and that class has the method. It does link the constructor, annotated-parameter and pytest-fixture cases: on one real project it added 124 correct edges, such aslog = ReceiptLog(…); log.append(…). But the receiver typing it relies on is not safe for names likeget,update,closeandappend, so I'm not opening a PR. These are the shapes that got a new wrong edge. Each was verified by indexing on the branch and onmain, andmaingives no edge for all of them:from queue import Queue; q = Queue(); q.get()links a project class namedQueue. The same happens withfrom requests import Session,from collections import Counterandfrom io import StringIO. It also happens with two project classes of the same name:from b import Handlerreacheda.py'sHandler. An inherited method is found by its bare base-class name too.reg = Store()thenreg = {}(or= json.load(f), anif/else,for reg in …,with open(p) as reg, a comprehension variable) is still typedStore.x = Foo().dataorx = Foo().build(), and a fixturereturn Config().settings, are typedFooorConfig.reg.get()picks up areg = Store()written inside an earlier function.reg: Store | dictwithisinstance(reg, dict)is typedStore.In the shapes above, every one already links wrongly on
mainwhen the method has a non-builtin name. The builtin-method filter is what hides it forget/update/…. So lifting the filter for typed receivers needs receiver typing that respects imports and rebinding first.Not fixed by the attempt either:
registry = load_registry()withdef load_registry() -> SystemPackRegistry(line 6 in the report). The resolver doesn't read Python return annotations today. The renamed-method control linked through the unique-method-name fallback, not through typing.colbymchenry commentedon Oct 23, 2026
Thanks for the careful report, and for trying a fix first: the false-positive shapes from your follow-up became the negative tests.
#2821 is merged to
main. It links builtin-named calls likereg.get()andreg.update()when the function says what the receiver holds:registry = load_registry()with-> SystemPackRegistrylinks too;The receiver is typed only when every binding of the name in its function agrees, and the class is the one the file's imports name. So
from queue import Queue, a laterreg = {}orjson.load(...),Foo().data(), a union, and module-level code still link nothing.It ships in the next release.
fix(python): calls like reg.get() on a typed local or parameter reach…