Summary
rust_crate_roots derives Rust crate:: and unqualified import candidates from only one predicate: whether the source path begins with src.
That conflates a Cargo package with a Rust crate. A package can contain many distinct crates—library, default binary, src/bin binaries, examples, benches, and integration tests—whose crate:: roots are different source files and directories.
Consequently, imports inside non-default targets can be unresolved or, worse, attached to a same-named module in another crate in the package.
Affected revision: 85dd1b1b918e863f5535333cf435a5ac6163c30e.
Rust/Cargo contract
Cargo documents that:
- every file under
tests/ is compiled as a separate crate;
- examples and explicitly declared targets are independent targets with their own
path;
- files under
src/bin/ are separate binary crates;
- a crate is one compilation unit with its own module tree and crate root.
References:
Therefore crate::x is relative to the current target's crate root, not automatically to the package's src/ directory.
Relevant implementation
src/indexer/import_resolution.rs routes crate:: and ordinary Rust paths through:
pub(super) fn rust_crate_roots(source: &Path) -> Vec<PathBuf> {
if source.starts_with("src") {
vec![PathBuf::from("src"), PathBuf::new()]
} else {
vec![PathBuf::new(), PathBuf::from("src")]
}
}
The function has no target manifest, target path, or owning crate-root input.
Counterexample 1: wrong edge from a src/bin crate
Repository:
Cargo.toml
src/lib.rs
src/util.rs # library module
src/bin/admin.rs # separate binary crate root
src/bin/util.rs # binary-local module
src/bin/admin.rs:
mod util;
use crate::util::Config;
Rust resolves crate::util in the admin binary crate to:
LeanToken computes:
rust_crate_roots("src/bin/admin.rs") = ["src", ""]
and therefore generates, in preferred order, candidates including:
src/util.rs
src/util/mod.rs
util.rs
util/mod.rs
It never generates src/bin/util.rs.
Because src/util.rs exists, resolve_import_candidates returns that first existing candidate. LeanToken therefore records a dependency from the binary to the library's unrelated module rather than to the module Rust compiles.
Formally:
RustResolve(admin, crate::util) = src/bin/util.rs
LeanTokenResolve(admin, crate::util) = src/util.rs
with both paths present. This is a deterministic false edge, not only missing recall.
Counterexample 2: integration-test crate
Repository:
src/common.rs
tests/api.rs
tests/common.rs
tests/api.rs:
mod common;
use crate::common::fixture;
Cargo compiles tests/api.rs as a separate crate, and its sibling module is tests/common.rs (or an owned tests/api/common.rs layout depending on the declaration structure).
LeanToken uses roots "" and "src", so it checks common.rs and src/common.rs, never tests/common.rs. If src/common.rs exists, the integration-test import is falsely attached to product code that the test crate's crate::common does not denote.
Counterexample 3: explicit target path
Cargo permits:
[[bin]]
name = "worker"
path = "tools/worker/main.rs"
For tools/worker/main.rs, crate::protocol can be declared by tools/worker/protocol.rs. LeanToken sees a non-src path and tries only repository root and src, so the configured target root is invisible.
The same issue applies to [[example]], [[test]], and [[bench]] paths.
Why #471 does not cover this defect
#471 concerns nested workspace package roots such as crates/core/src/lib.rs, where the nearest package-local src directory should own crate::.
This issue concerns multiple crate targets inside one Cargo package. Even after recognizing crates/core/src as a package root, crates/core/src/bin/tool.rs, crates/core/tests/api.rs, and explicit target paths remain separate crates with distinct roots.
Impact
Incorrect import projections affect:
- reverse-importer discovery during targeted reconciliation;
- graph-based context expansion and ranking;
- owner/test recommendations;
- changed-file impact analysis;
- symbol/reference neighborhood evidence;
- retrieval evaluations that assume import edges approximate compiled module ownership.
The wrong-edge form is especially harmful: a plausible existing file is returned, so the result looks complete while identifying code from a different crate.
Required invariant
For every indexed Rust source file s, LeanToken should first determine its owning Cargo target T and crate root r(T). Candidate construction for crate::p must satisfy:
RustCompilerModulePath(T, p) = x
=> x ∈ LeanTokenCandidates(s, crate::p, T)
and a path belonging only to a different target must not outrank x.
Design direction
Parse Cargo target ownership once into a narrow domain model, for example:
CargoPackage
└── CargoTarget { kind, name, crate_root, module_root }
Then assign each Rust file to an owning target/module tree and pass the typed target root into import resolution. Sources not provably owned by a target should remain explicit/ambiguous rather than silently falling back to another crate.
Target discovery must include:
Acceptance criteria
Non-goals
- Do not run build scripts or procedural macros to discover modules.
- Do not claim full rustc name resolution or macro expansion.
- Do not merge all package sources into one synthetic crate graph; that is the root cause of this issue.
Summary
rust_crate_rootsderives Rustcrate::and unqualified import candidates from only one predicate: whether the source path begins withsrc.That conflates a Cargo package with a Rust crate. A package can contain many distinct crates—library, default binary,
src/binbinaries, examples, benches, and integration tests—whosecrate::roots are different source files and directories.Consequently, imports inside non-default targets can be unresolved or, worse, attached to a same-named module in another crate in the package.
Affected revision:
85dd1b1b918e863f5535333cf435a5ac6163c30e.Rust/Cargo contract
Cargo documents that:
tests/is compiled as a separate crate;path;src/bin/are separate binary crates;References:
Therefore
crate::xis relative to the current target's crate root, not automatically to the package'ssrc/directory.Relevant implementation
src/indexer/import_resolution.rsroutescrate::and ordinary Rust paths through:The function has no target manifest, target path, or owning crate-root input.
Counterexample 1: wrong edge from a
src/bincrateRepository:
src/bin/admin.rs:Rust resolves
crate::utilin theadminbinary crate to:LeanToken computes:
and therefore generates, in preferred order, candidates including:
It never generates
src/bin/util.rs.Because
src/util.rsexists,resolve_import_candidatesreturns that first existing candidate. LeanToken therefore records a dependency from the binary to the library's unrelated module rather than to the module Rust compiles.Formally:
with both paths present. This is a deterministic false edge, not only missing recall.
Counterexample 2: integration-test crate
Repository:
tests/api.rs:Cargo compiles
tests/api.rsas a separate crate, and its sibling module istests/common.rs(or an ownedtests/api/common.rslayout depending on the declaration structure).LeanToken uses roots
""and"src", so it checkscommon.rsandsrc/common.rs, nevertests/common.rs. Ifsrc/common.rsexists, the integration-test import is falsely attached to product code that the test crate'scrate::commondoes not denote.Counterexample 3: explicit target path
Cargo permits:
For
tools/worker/main.rs,crate::protocolcan be declared bytools/worker/protocol.rs. LeanToken sees a non-srcpath and tries only repository root andsrc, so the configured target root is invisible.The same issue applies to
[[example]],[[test]], and[[bench]]paths.Why #471 does not cover this defect
#471 concerns nested workspace package roots such as
crates/core/src/lib.rs, where the nearest package-localsrcdirectory should owncrate::.This issue concerns multiple crate targets inside one Cargo package. Even after recognizing
crates/core/srcas a package root,crates/core/src/bin/tool.rs,crates/core/tests/api.rs, and explicit target paths remain separate crates with distinct roots.Impact
Incorrect import projections affect:
The wrong-edge form is especially harmful: a plausible existing file is returned, so the result looks complete while identifying code from a different crate.
Required invariant
For every indexed Rust source file
s, LeanToken should first determine its owning Cargo targetTand crate rootr(T). Candidate construction forcrate::pmust satisfy:and a path belonging only to a different target must not outrank
x.Design direction
Parse Cargo target ownership once into a narrow domain model, for example:
Then assign each Rust file to an owning target/module tree and pass the typed target root into import resolution. Sources not provably owned by a target should remain explicit/ambiguous rather than silently falling back to another crate.
Target discovery must include:
[lib]and defaultsrc/lib.rs;src/main.rs;src/bin/*.rsandsrc/bin/*/main.rs;tests,examples, andbenchestarget conventions;[[bin]],[[test]],[[example]], and[[bench]]paths;Acceptance criteria
crate::utilinsrc/bin/admin.rsresolves tosrc/bin/util.rs, notsrc/util.rs.crate::commonimport in an integration-test target resolves within that test crate and cannot attach to the package library's same-named module.Cargo.tomlare honored.Non-goals