Repository navigation
[Bug]: Rust: a use rooted at a child module (pub use item::X;) still mints a stub that makes affected/explain ambiguous #4564
Description
Pre-flight checks
- I have checked the Troubleshooting section in the README
What happened?
Follow-up to #4439, which ef1d256 and 5faa05c fixed in 0.9.91 for use paths rooted at crate, self or super. A use whose path starts at a child module declared beside it, such as pub use item::Invoice; next to mod item;, is still not named for the stub rewire. When nothing else lends the rewire a node under the bare id, the imports_from edge dangles, build mints the external stub invoice, and affected and explain tie on it exactly as #4439 described.
Expected: the edge lands on Invoice in src/model/item.rs, no stub is minted, and both commands resolve the name.
Steps to reproduce
# Cargo.toml
[package]
name = "repro"
version = "0.1.0"
edition = "2021"// src/lib.rs
pub mod model;
// src/model.rs
mod item;
pub use item::Invoice;
// src/model/item.rs
pub struct Invoice(pub u64);graphify extract . --code-only --cargo --force
graphify cluster-only . --no-viz --no-label
graphify affected Invoice
graphify explain InvoiceSpelling the re-export pub use self::item::Invoice; makes both commands resolve.
Error output or graph output
| graphify | external stub | imports_from target |
affected Invoice |
explain Invoice |
|---|---|---|---|---|
| 0.9.62 | none | edge dropped | resolves | resolves |
| 0.9.91, 0.9.92 | id: invoice, label: invoice, type: external, source_file: "" |
invoice, the stub |
No unique node match for Invoice, exit 0 |
Ambiguous: 'Invoice' matches 2 nodes in different files., exit 3 |
Graphify version
0.9.91 and 0.9.92, the latest. 0.9.63 to 0.9.90 mint the same stub through #4439 itself, and 0.9.62 is not affected.
Operating System
macOS 26.5.2 (arm64)
Python Version
3.15.0
Installation Method
uv: uv tool run --from 'graphifyy==<version>' graphify …
Additional Environment Details
No provider environment variables are set, every run uses --code-only, and the repro is a clean directory holding only the four files above.
Additional context
From the 0.9.92 source:
graphify/extractors/rust.py,use_declarationbranch:name_usesets_rust_use_nameonly when the path's first segment iscrate,selforsuper, soitem::Invoicegets none andextract.pylends the rewire no node for it.- A larger repro can hide this. When another file imports the same item from
crate::and that edge is still on the bare id at the rewire, its lent node rescues the re-export's edge too. On the private ~440-file Cargo workspace from [Bug]: Rust:useedges target a bare name, and since 0.9.63 the stub they leave makesaffected/explainambiguous #4439, those imports reach_rewire_unique_stub_nodeson per-file placeholder ids (<file>_rs_<name>) instead, so the re-export's edge stays alone on the bare id. - On that workspace, 90 names that 0.9.62 resolves still tie with an external stub on 0.9.92, down from 444 on 0.9.89. 83 of them are left on the stub only by child-module paths: 103 edges, all in a
mod.rs,lib.rsormain.rs, 93 of thempub usere-exports.
A rule that should not reopen the serde::Serialize concern behind 5faa05c: also name a use whose first segment is a mod declared in the same scope, unless the path starts with ::. Rust resolves such a segment to the local module before a crate of that name. With serde as a dependency and mod serde { pub struct Local; } declared, use serde::Serialize; fails with E0432 on rustc 1.97.1, while use ::serde::Serialize; reaches the crate. Two details for the check: name_use strips a leading :: before it reads the first segment, and an inline mod block is a scope of its own, so a use serde::… inside mod tests { … } still names the crate.
Related: @liam-mcelhaney122's comment on #4439 covers the same re-export from the importing side (pub use item::Stack in core/src/lib.rs). #4134 would mask this for affected but not for explain.
Activity
github-actions commentedon Oct 19, 2026
Thanks for opening this issue, @almothafar-maf. A maintainer will take a look soon.
If you would like to discuss it in real time, come say hi on our Discord server. For longer-form questions and ideas there is also GitHub Discussions.
To help us triage, please make sure the report includes what you expected, what actually happened, and the steps (and a small sample) to reproduce it.
Deepjyoti-Sarmah commentedon Oct 19, 2026
I reproduced this on v8 (86c57be) with your four files, and got the same result as your table. The edge is src_model -> invoice, the stub is invoice (external, empty source_file), affected Invoice exits 0 with "No unique node match", and explain Invoice exits 3. Spelling it self::item::Invoice resolves both.
I have a fix on a branch, following the rule you proposed. In extractors/rust.py, name_use also sets _rust_use_name when the path's first segment is a mod declared beside the use in the same scope. A path that starts with :: is never named. The scope is the use node's siblings, so pub use item::Invoice; before mod item; works, and a use serde::… inside mod tests { … } doesn't see a top-level mod serde. _AST_CACHE_SCHEMA goes from 17 to 18.
I checked what Rust itself does with cargo check on rustc 1.95.0, using a stand-in serde crate:
A separate session that didn't write the fix ran 20 cases against v8 and the branch:
Limits:
useedges target a bare name, and since 0.9.63 the stub they leave makesaffected/explainambiguous #4439's included, because the manifest doesn't record the extractor version or schema.New tests are in tests/test_rust_use_trees.py: 10 fail and 5 pass on v8, and all 15 pass on the branch. The 5 that pass on v8 guard the serde cases, the second-import case and the two-types case. The full suite passes: 8407 passed, 14 skipped. I'll open the PR unless you'd rather go a different way.
fix: a Rust use rooted at a child mod reaches its declaration (#4564)
test: a Rust use rooted at a child mod reaches its declaration (#4564)
safishamsi commentedon Oct 20, 2026
Fixed in 0.9.93 by #4567. A Rust
userooted at a siblingmodnow reaches its declaration. Thanks @almothafar-maf for the report. Live on PyPI: https://pypi.org/project/graphifyy/0.9.93/