Fix Python type reference stubs across imports (#3252) - #3254
Conversation
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Repoints Python type-reference and inheritance edges (references, inherits, implements, extends) from sourceless stubs to the exact imported definition, so an ambiguous same-named type resolves to the package it was actually imported from rather than a global guess. Runs cross-file import resolution before _rewire_unique_stub_nodes and passes it the full node/edge lists, and prunes stub nodes that repointing leaves unreferenced. Extends relative-import handling to walk multi-dot prefixes and dotted subpackages, and prefers exact/suffix-matched module stems over bare-name lookup when resolving import targets.
No blocking issues surfaced. 6 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2197 functions depend on the 738 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 528 callers, 43 callees - new:
_rebuild_code()— 113 callers, 50 callees - new:
_extract_generic()— 18 callers, 25 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
dispatch_command()— 2 callers, 123 callees - new:
extract_objc()— 27 callers, 9 callees - new:
_resolve_js_module_path()— 27 callers, 6 callees - …and 41 more — each is listed as a finding
Verification — 2197 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: 2032 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify extract.
The verifier did not have enough to check extract, 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: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_resolve\_cross\_file\_imports.
The verifier did not have enough to check \_resolve\_cross\_file\_imports, 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: not verifiable: the input domain has 81 values but only 9 distinct were tested — a small finite domain must be EXHAUSTED, not sampled (an untested input could invert the result)
· 49 more finding(s) on lines outside this diff (see the check run).
Summary
Fixes #3252 by correctly rewiring Python type-reference and inheritance edges from sourceless AST stubs to their exact imported definitions.
Previously, when multiple definitions shared the same type name, Graphify's generic stub rewiring could not disambiguate them. This left per-file sourceless stubs behind even though the Python import resolver already knew the correct target.
Root Cause
Python type annotations and inheritance expressions can create temporary sourceless stub nodes such as
StateFrame._rewire_unique_stub_nodes()relies on global bare-name uniqueness, so when multipleStateFramedefinitions exist across packages, tests, or languages, it intentionally refuses to guess.The Python cross-file import resolver, however, already has the exact import context needed to resolve these references. It previously only added inferred
usesedges and did not update the original ASTreferences/inheritsedges.As a result, the graph could contain ghost
StateFramenodes with AST edges still pointing at them.Changes
import_targetsmapping to identify the exact imported definition.references,inherits,implements, andextendsedges to the canonical definition._rewire_unique_stub_nodes()as the fallback for unimported symbols.Covered Cases
Regression tests cover:
Testing
uv run pytest tests/test_extract.py -k "3252" -vuv run pytest tests/test_extract.py -k "3252 or stateframe or rewire or cross_file_import" -vuv run pytest tests/test_extract.py -vuv run pytest tests/test_languages.py -vuv run pytest -qThe focused and extraction/language test suites pass, with the full suite run as final verification.