Skip to content

Fix Python type reference stubs across imports (#3252) - #3254

Open
hopstreax wants to merge 1 commit into
Graphify-Labs:v8from
hopstreax:investigate/3252-type-reference-rewire
Open

Fix Python type reference stubs across imports (#3252)#3254
hopstreax wants to merge 1 commit into
Graphify-Labs:v8from
hopstreax:investigate/3252-type-reference-rewire

Conversation

@hopstreax

Copy link
Copy Markdown
Contributor

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 multiple StateFrame definitions 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 uses edges and did not update the original AST references / inherits edges.

As a result, the graph could contain ghost StateFrame nodes with AST edges still pointing at them.

Changes

  • Run Python cross-file import resolution before generic stub rewiring.
  • Use the existing file-scoped import_targets mapping to identify the exact imported definition.
  • Repoint sourceless references, inherits, implements, and extends edges to the canonical definition.
  • Preserve existing edge metadata when repointing.
  • Prune sourceless stubs once they no longer have any incident edges.
  • Keep generic _rewire_unique_stub_nodes() as the fallback for unimported symbols.
  • Preserve existing behavior for ambiguous unimported references rather than guessing.

Covered Cases

Regression tests cover:

  • Multiple packages defining the same type
  • Aliased imports
  • Imported inheritance
  • Relative imports
  • Ambiguous unimported types
  • Python/TypeScript definitions sharing the same name
  • Aliased inheritance
  • Edge metadata preservation

Testing

  • uv run pytest tests/test_extract.py -k "3252" -v
  • uv run pytest tests/test_extract.py -k "3252 or stateframe or rewire or cross_file_import" -v
  • uv run pytest tests/test_extract.py -v
  • uv run pytest tests/test_languages.py -v
  • uv run pytest -q

The focused and extraction/language test suites pass, with the full suite run as final verification.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Class/type references across files never rewire to canonical definition — #1781 fix excluded "Types" by design

1 participant