Repository navigation
Conversation
self.m(), cls.m() and super().m() are exempt from the receiver-typed defer because the receiver is the caller's own instance, but the lookup they fell through to was the file-wide name map, which keeps only the last definition of each name. Two classes in one file that both define m() sent every self-call to whichever was declared last, as EXTRACTED. On httpx that wires the sync Client's request/send chain into AsyncClient, and every super().__init__() in _exceptions.py into RequestNotRead.__init__. Resolve these against the enclosing class, then its in-file bases nearest first (super skips the class itself), the same order the ObjC resolver uses for self. A tie between two bases on one level binds nothing, and a method of a class outside the chain is never the target. A module-level callable stored on the instance still binds as before.
this.save() in one JS/TS class was resolved through the file-wide name map, which keeps only the last definition of each name, so a class that calls this.save() was wired to the save() of whichever class the file declared last, as EXTRACTED. In zod's v3 types.ts every ZodString, ZodNumber and ZodBigInt check method landed on ZodDate._addCheck. Route this.m() and super.m() through the self-call lookup the Python fix added, renamed _self_call_target. It binds to the caller's own class when that class defines m(). JS/TS extends edges are only emitted by the later symbol pass, so the base chain is unknown at this point; anything the own class does not define keeps the plain lookup, which leaves inherited this.m() edges exactly as they were.
self.save(), a bare save() (implicit self) and super.ping() in a Swift method went through the file-wide name map, which keeps only the last definition of each name, so they landed on whichever class the file declares last, as EXTRACTED. In Alamofire, DataRequest.init calling super.init() was recorded as DataRequest.init calling itself, and URLEncodedFormEncoder's keyed container calls went to the unkeyed one. Route them through _self_call_target with the in-file inherits walk, which Swift supports because its inherits edges exist at this point. Free functions, constructors and calls from an extension of the same type resolve exactly as before.
|
Thanks for the pull request, @Cintu07. A maintainer will review it soon. Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions. A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic. |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Formal verification. PR-changed functions: 0/1 verified (0 proven, 0 may-equivalent, 0 distinguished) · 1 not verified (1 unsupported).
Not verified on this run: \_extract\_generic (unsupported).
Graphify review — findings
Fixes self.m() / cls.m() / super().m() in Python, this.m() / super.m() in JS/TS, and Swift's explicit and implicit self calls so they bind to the caller's own class instead of whichever class in the file declared m last. _self_call_target walks in-file bases nearest-first, skipping the caller's own class for super, and binds nothing on a multiple-inheritance tie or when m exists only on an unrelated class. Module-level callables stored on the instance still resolve through the file-wide lookup, and JS/TS stop after the caller's own class and keep the old lookup, because extends edges aren't known yet at this point.
Worth a look
- Python super().method() is not routed through super resolution —
graphify/extractors/engine.py:6838· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1140 functions depend on the 700 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_extract_generic()— 18 callers, 30 callees - new:
extract_js()— 87 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
extract_objc()— 27 callers, 9 callees - new:
extract_julia()— 19 callers, 7 callees - new:
extract_cpp()— 31 callers, 3 callees - new:
extract_vue()— 10 callers, 7 callees - new:
walk()— 1 callers, 67 callees - …and 9 more — each is listed as a finding
Verification — 1140 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 1077 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
25 of 318 test file(s) selected (8%) via static blast radius.
tests/test_astro_extraction.py— impacttests/test_build.py— impacttests/test_cjs_module_extension.py— impacttests/test_cpp_nested_and_cli.py— impacttests/test_dotnet.py— impacttests/test_extract.py— impact, changed-testtests/test_extract_php_closures.py— impacttests/test_import_extension_resolution.py— impacttests/test_indirect_call_block_scoped_shadow.py— impacttests/test_indirect_dispatch.py— impacttests/test_indirect_dispatch_assign_return.py— impacttests/test_indirect_dispatch_getattr.py— impacttests/test_js_exported_scalar_bindings.py— impacttests/test_languages.py— impacttests/test_multilang.py— impacttests/test_python_underscore_resolution.py— impacttests/test_rationale.py— impacttests/test_ruby_resolution.py— impacttests/test_scala_self_type.py— impacttests/test_swift_computed_properties.py— impacttests/test_swift_protocol_requirements.py— impacttests/test_trailing_newline_not_a_syntax_error.py— impacttests/test_ts_new_expression_calls.py— impacttests/test_typescript_module_extensions.py— impacttests/test_vue_extraction.py— impact
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `config` is annotated `LanguageConfig` — outside the synthesizable primitive/collection set
· 17 more finding(s) on lines outside this diff (see the check run).
|
Shipped in v0.9.75 (live on PyPI as Closing as shipped. |
what does this pr do?
stacked on #4011 (python) and #4012 (js), it reuses their _self_call_target. only the last commit is new, happy to rebase once those land.
self.save(), a bare save() (implicit self) and super.ping() in a swift method go through the file-wide label_to_nid, which keeps only the last definition of each name, so they land on whichever class the file declares last, as EXTRACTED.
on Alamofire at bda9ed5 (Source/, 44 files, 665 calls edges) 46 calls edges changed: 33 wrong ones moved to the right method and 13 wrong ones dropped. DataRequest.init, UploadRequest.reset, WebSocketRequest.cancel and friends calling super were recorded as calling themselves. URLEncodedFormEncoder's keyed container calls went to the unkeyed container, and DataRequest.serializingResponse went to DownloadTask.response.
swift inherits edges exist at bind time, so this uses the same in-file chain walk as python: own class first, then bases nearest first, super skips the class itself. a method of a class outside the chain is never the target. free functions, constructors and calls from an extension of the same type resolve exactly as before.
one gap that was already there: an extension of a nested type (extension URLEncodedFormEncoder.KeyedContainer) gets a different node id than the nested class, so those calls now get no in-file edge instead of the wrong one.
type of change
verification & invariants
a self, super or implicit-self call never binds to a method of a class outside the callers own class chain. calls that resolve to something that is not a method of another class keep their old target.
how was this tested?
windows 11 arm64, python 3.14, venv from uv sync --all-extras.
graphify-specific checklist
uv run python -m tools.skillgen --bless) when changing their source fragments.