Summary
While fixing #1876 (receiver-typed calls + trait dispatch through local vars), I traced why cross-file return-type propagation (Phase 8.2 — returnTypeMap/callAssignments, the same generic mechanism the JS/TS extractor already uses) does not resolve let service = build_service(); in main.rs even though build_service is declared in service.rs and imported via use crate::service::build_service;.
Root cause: resolveImportPathJS (src/domain/graph/resolve.ts) has no handling for Rust's crate::/self::/super::-prefixed module paths. For any import source that doesn't start with . (which covers every Rust use path — Rust has no relative-path import syntax), it falls through to the workspace/exports resolution branches (npm-package semantics) and, finding no match, returns the literal unresolved import-source string (e.g. "crate::service::build_service") as if it were a resolved file path.
This means buildImportedNamesMap's importedNames map for a Rust file maps each imported local name to this bogus non-file string, not to the actual .rs file that declares it.
Why this was hidden until now
Rust's actual "module-function" call edges (e.g. UserService.add_user -> create_user via use crate::models::create_user;) still resolve today — but not through real import-aware resolution. They fall through resolveCallTargets's import-aware branch (which fails silently, since lookup.byNameAndFile(name, "crate::models::create_user") never matches a real file), all the way to resolveExactGlobalMatch's bare global-name fallback, which happens to succeed because the callee names in the fixture are globally unique. This is coincidental, not by design — any Rust project with two same-named functions in different modules would silently produce zero or wrong edges for this pattern, and it also means Rust import edges/confidence scoring never get the importedFrom === targetFile proximity bonus real import resolution provides.
It also blocks legitimate cross-file features that depend on knowing the real target file, such as the return-type propagation added in #1876 — main -> UserService.add_user/get_user/remove_user (typing service from build_service()'s cross-file return type) remains unresolved specifically because of this gap, not because of anything specific to receiver typing.
Fix direction
Add a Rust-aware branch to resolveImportPathJS (and the native Rust mirror in resolve_import.rs/equivalent) that maps crate::a::b::name / self::a::name / super::a::name paths to real files by walking the project's module tree (mod declarations → file or dir/mod.rs/dir.rs conventions), analogous to how Python's dotted-module imports or Go's package-path imports are resolved elsewhere in this file. This is a non-trivial addition (Rust's module system has its own visibility/path rules) — scoping it as its own issue rather than folding it into #1876's receiver-typing fix.
Evidence
Fixture: tests/benchmarks/resolution/fixtures/rust/. After #1876's fix, resolution-benchmark.test.ts's rust suite sits at 100% precision / 83.3% recall (20/24), with the remaining 4 false negatives all being main -> UserService.*/User.display_name edges that depend on this import-path gap (see expected-edges.json, mode: "receiver-typed" entries citing "local var typed by return value").
Summary
While fixing #1876 (receiver-typed calls + trait dispatch through local vars), I traced why cross-file return-type propagation (Phase 8.2 —
returnTypeMap/callAssignments, the same generic mechanism the JS/TS extractor already uses) does not resolvelet service = build_service();inmain.rseven thoughbuild_serviceis declared inservice.rsand imported viause crate::service::build_service;.Root cause:
resolveImportPathJS(src/domain/graph/resolve.ts) has no handling for Rust'scrate::/self::/super::-prefixed module paths. For any import source that doesn't start with.(which covers every Rustusepath — Rust has no relative-path import syntax), it falls through to the workspace/exportsresolution branches (npm-package semantics) and, finding no match, returns the literal unresolved import-source string (e.g."crate::service::build_service") as if it were a resolved file path.This means
buildImportedNamesMap'simportedNamesmap for a Rust file maps each imported local name to this bogus non-file string, not to the actual.rsfile that declares it.Why this was hidden until now
Rust's actual "module-function" call edges (e.g.
UserService.add_user -> create_userviause crate::models::create_user;) still resolve today — but not through real import-aware resolution. They fall throughresolveCallTargets's import-aware branch (which fails silently, sincelookup.byNameAndFile(name, "crate::models::create_user")never matches a real file), all the way toresolveExactGlobalMatch's bare global-name fallback, which happens to succeed because the callee names in the fixture are globally unique. This is coincidental, not by design — any Rust project with two same-named functions in different modules would silently produce zero or wrong edges for this pattern, and it also means Rust import edges/confidence scoring never get theimportedFrom === targetFileproximity bonus real import resolution provides.It also blocks legitimate cross-file features that depend on knowing the real target file, such as the return-type propagation added in #1876 —
main -> UserService.add_user/get_user/remove_user(typingservicefrombuild_service()'s cross-file return type) remains unresolved specifically because of this gap, not because of anything specific to receiver typing.Fix direction
Add a Rust-aware branch to
resolveImportPathJS(and the native Rust mirror inresolve_import.rs/equivalent) that mapscrate::a::b::name/self::a::name/super::a::namepaths to real files by walking the project's module tree (moddeclarations → file ordir/mod.rs/dir.rsconventions), analogous to how Python's dotted-module imports or Go's package-path imports are resolved elsewhere in this file. This is a non-trivial addition (Rust's module system has its own visibility/path rules) — scoping it as its own issue rather than folding it into #1876's receiver-typing fix.Evidence
Fixture:
tests/benchmarks/resolution/fixtures/rust/. After #1876's fix,resolution-benchmark.test.ts's rust suite sits at 100% precision / 83.3% recall (20/24), with the remaining 4 false negatives all beingmain -> UserService.*/User.display_nameedges that depend on this import-path gap (seeexpected-edges.json,mode: "receiver-typed"entries citing "local var typed by return value").