fix(rust): resolve module-qualified calls via qualifier-verified cross-file resolution#2135
Open
okinoxis wants to merge 1 commit into
Open
fix(rust): resolve module-qualified calls via qualifier-verified cross-file resolution#2135okinoxis wants to merge 1 commit into
okinoxis wants to merge 1 commit into
Conversation
…s-file resolution Unresolved scoped calls (module::function()) were dropped at extraction, making the dominant idiomatic intra-crate call form invisible to the graph (a real production caller showed 0 dependents while grep found it). Keep the #908 bare-name guard, but stop discarding the qualifier — use it as the verification key: enqueue scoped calls with their qualifier path, then resolve strictly against it in the shared cross-file pass (defining file must BE the named module: <seg>.rs or <seg>/mod.rs; exactly one survivor or bail). Uppercase qualifiers (Type::method / Enum::Variant) and crate/super/self keep their existing skipped behavior. Emitted as EXTRACTED (confidence 1.0) — the module is named explicitly in source, same reasoning as the qualified-class-method passes (Graphify-Labs#1446/Graphify-Labs#1533). Defense-in-depth: the same branch in resolve_cross_file_raw_calls so scoped entries never reach bare-name resolution. Real-world Rust crate (~200 files, 14k edges): +138 EXTRACTED, -388 INFERRED (the #908 class shrinking), no regressions on bare-name-called control symbols.
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.
Problem
For Rust, scoped calls (
module::function(...)) are dropped at extraction when the callee is not defined in the same file (extractors/rust.py). Since the module-qualified call is the DOMINANT idiomatic intra-crate call form, this makes the call graph blind to most cross-file Rust edges: a real production caller ofscan_substrate::accumulate_observations(...)showed 0 dependents whilegrepfound the call site immediately. Every downstream consumer (blast-radius queries, dependents listings) silently under-reports, which pushes users back to grep — defeating graph-first navigation exactly where it matters.The drop was intentional: resolving scoped calls by BARE last-segment lookup across crate boundaries produced spurious INFERRED edges (#908). The guard is right; the lost signal is not.
Solution
Keep the #908 guard, but stop discarding the qualifier — use it as the verification key:
raw_callswithis_scoped_call: Trueand itsscope_qualifier(thepathfield text of thescoped_identifier), instead of being dropped.is_scoped_callBEFORE bare-name resolution and never falls through to it:snake_case, typesUpperCamelCase— rustc warns otherwise) cheaply separatesmodule::func()fromType::method()/Enum::Variant; uppercase qualifiers keep their existing behavior (skipped).crate/super/selfqualifiers require real module-tree resolution and are conservatively skipped.<seg>.rsor<seg>/mod.rs); require exactly one survivor or bail (god-node guard preserved).EXTRACTED(confidence 1.0): the module is named explicitly in source — the same reasoning as the existing qualified-class-method passes (Python qualified class-method calls miss EXTRACTED edges #1446/Swift: static method calls (MemberAccessExpr) don't create caller→type edges + overloaded methods collapsed into single node #1533).resolve_cross_file_raw_calls, so scoped entries can never reach that path's bare-name resolution either (defense in depth; pair-dedup makes double emission impossible).Results (real-world Rust crate, ~200 source files, 14k-edge graph)
scan_substrate::accumulate_observations→ its caller,EXTRACTEDat the exact source line.Suggested tests
a.rs/b.rs,b::helper()called froma→ one EXTRACTED edge tob.rs'shelper, none to a same-namedhelperinc.rs.mod.rsform: callutil::helper()withutil/mod.rsdefininghelper→ resolves.helperdefined in BOTHb.rsandb/mod.rs(pathological) → no edge (two survivors).Type::method()/Enum::Variant(uppercase qualifier) → unchanged behavior (no new edge).crate::x::helper()/super::helper()/self::helper()→ skipped (no spurious edge).m::<T>-adjacent shapes → qualifier normalization strips generics.Known limitations (documented, out of scope here)
crate::/super::-qualified paths still unresolved (needs module-tree walking).usealias resolution (use a::b as c; c::f()) not attempted.