nandi/rustnimpublic Fork
⑂ 4d9142c
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

Namespace a file's own modules, macro arguments included

`mod` is a namespace in Rust and rustnim flattens it, so two modules that each
declare `SIGNIFICAND_SIZE` -- which is what a `macro_rules!` invoked once per
module produces -- became one declaration twice. `almost` does exactly that
for `f32` and `f64`.

Names that collide between a file's modules are prefixed with the module that
declares them; a name declared once keeps it. This is the same shape as the
cross-crate renaming, one level down.

The part that took the finding: a macro invocation's arguments are a token
stream, not an AST, so `VisitMut` does not reach them -- and rustnim parses
them later, after this pass. `format!("{}", MASK)` and `a::f(1)` inside a
`println!` were both untouched while everything around them was renamed, which
looked like the rename silently not working. Both renamers now rewrite macro
tokens: bare names within the module that declares them, and `module::name`
anywhere in the file.

`Renamer` also renames `const`, `static`, `fn` and `trait` declarations now.
It handled only type declarations, which is why the *uses* moved and the
declarations did not.

tests/cases/072.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-20T12:33:58-07:00 Browse files
4d9142c parent: e34bf8b
modified src/lower.rs +234 -0
@@ -604,6 +604,12 @@ impl Lowerer {
604604 // crate that declares it, and the uses are rewritten to match: inside
605605 // that crate, and inside any crate that `use`s it from there.
606606 let mut files = files.to_vec();
607+ // Within a file first: `mod` is a namespace Rust keeps and rustnim
608+ // flattens, so two modules declaring the same name collide before any
609+ // cross-crate question arises.
610+ for (_, f) in files.iter_mut() {
611+ namespace_modules(f);
612+ }
607613 namespace_types(&mut files);
608614
609615 // Pass -1: give every function-local type a name of its own.
@@ -10209,6 +10215,35 @@ impl Renamer<'_> {
1020910215 }
1021010216
1021110217 impl syn::visit_mut::VisitMut for Renamer<'_> {
10218+ /// A macro's arguments are tokens, not an AST, and rustnim parses them
10219+ /// after this pass. `format!("{}", MASK)` is why the rename has to reach
10220+ /// them.
10221+ fn visit_macro_mut(&mut self, m: &mut syn::Macro) {
10222+ fn walk(
10223+ ts: proc_macro2::TokenStream,
10224+ r: &HashMap<String, String>,
10225+ ) -> proc_macro2::TokenStream {
10226+ use proc_macro2::{Group, TokenTree};
10227+ ts.into_iter()
10228+ .map(|t| match t {
10229+ TokenTree::Ident(i) => match r.get(&i.to_string()) {
10230+ Some(to) => {
10231+ TokenTree::Ident(proc_macro2::Ident::new(to, i.span()))
10232+ }
10233+ None => TokenTree::Ident(i),
10234+ },
10235+ TokenTree::Group(g) => {
10236+ let mut ng = Group::new(g.delimiter(), walk(g.stream(), r));
10237+ ng.set_span(g.span());
10238+ TokenTree::Group(ng)
10239+ }
10240+ other => other,
10241+ })
10242+ .collect()
10243+ }
10244+ m.tokens = walk(m.tokens.clone(), self.renames);
10245+ }
10246+
1021210247 fn visit_item_struct_mut(&mut self, i: &mut syn::ItemStruct) {
1021310248 self.rename(&mut i.ident);
1021410249 syn::visit_mut::visit_item_struct_mut(self, i);
@@ -10225,6 +10260,24 @@ impl syn::visit_mut::VisitMut for Renamer<'_> {
1022510260 self.rename(&mut i.ident);
1022610261 syn::visit_mut::visit_item_union_mut(self, i);
1022710262 }
10263+ // Values collide between modules exactly as types do: a `const` declared
10264+ // in two of them is one declaration twice once `mod` is flattened.
10265+ fn visit_item_const_mut(&mut self, i: &mut syn::ItemConst) {
10266+ self.rename(&mut i.ident);
10267+ syn::visit_mut::visit_item_const_mut(self, i);
10268+ }
10269+ fn visit_item_static_mut(&mut self, i: &mut syn::ItemStatic) {
10270+ self.rename(&mut i.ident);
10271+ syn::visit_mut::visit_item_static_mut(self, i);
10272+ }
10273+ fn visit_item_fn_mut(&mut self, i: &mut syn::ItemFn) {
10274+ self.rename(&mut i.sig.ident);
10275+ syn::visit_mut::visit_item_fn_mut(self, i);
10276+ }
10277+ fn visit_item_trait_mut(&mut self, i: &mut syn::ItemTrait) {
10278+ self.rename(&mut i.ident);
10279+ syn::visit_mut::visit_item_trait_mut(self, i);
10280+ }
1022810281 fn visit_path_mut(&mut self, p: &mut syn::Path) {
1022910282 self.rename_path(p);
1023010283 syn::visit_mut::visit_path_mut(self, p);
@@ -10278,3 +10331,184 @@ fn is_bare_literal(e: &Expr) -> bool {
1027810331 _ => false,
1027910332 }
1028010333 }
10334+
10335+// ------------------------------------------------------ module namespacing
10336+
10337+/// The names an item declares, for collision purposes. Values and types
10338+/// alike: a `const` in two modules collides exactly as a `struct` would.
10339+fn declared_names(item: &Item) -> Vec<String> {
10340+ match item {
10341+ Item::Fn(f) => vec![f.sig.ident.to_string()],
10342+ Item::Const(c) => vec![c.ident.to_string()],
10343+ Item::Static(s) => vec![s.ident.to_string()],
10344+ Item::Struct(s) => vec![s.ident.to_string()],
10345+ Item::Enum(e) => vec![e.ident.to_string()],
10346+ Item::Type(t) => vec![t.ident.to_string()],
10347+ Item::Union(u) => vec![u.ident.to_string()],
10348+ Item::Trait(t) => vec![t.ident.to_string()],
10349+ _ => Vec::new(),
10350+ }
10351+}
10352+
10353+/// Rename items that collide between a file's own inline modules.
10354+///
10355+/// `mod` is a namespace in Rust and rustnim flattens it, so two modules that
10356+/// each declare `SIGNIFICAND_SIZE` -- which is what a `macro_rules!` invoked
10357+/// once per module produces -- become one declaration twice. Rust keeps them
10358+/// apart by module, so the module name is what tells them apart here.
10359+///
10360+/// Only names that actually collide are touched, and only within the module
10361+/// that declares them, plus any `module::name` reference elsewhere in the
10362+/// file. A name declared once keeps it.
10363+fn namespace_modules(f: &mut syn::File) {
10364+ // Which modules declare each name. The file's own top level counts as a
10365+ // module with no name, and never gets renamed -- it has nothing to be
10366+ // qualified by.
10367+ let mut owners: HashMap<String, Vec<String>> = HashMap::new();
10368+ fn scan(items: &[Item], path: &str, owners: &mut HashMap<String, Vec<String>>) {
10369+ for item in items {
10370+ for n in declared_names(item) {
10371+ let e = owners.entry(n).or_default();
10372+ if !e.contains(&path.to_string()) {
10373+ e.push(path.to_string());
10374+ }
10375+ }
10376+ if let Item::Mod(m) = item {
10377+ if let Some((_, inner)) = &m.content {
10378+ scan(inner, &m.ident.to_string(), owners);
10379+ }
10380+ }
10381+ }
10382+ }
10383+ scan(&f.items, "", &mut owners);
10384+
10385+ let clashing: Vec<(String, Vec<String>)> = owners
10386+ .into_iter()
10387+ .filter(|(_, mods)| mods.len() > 1)
10388+ .collect();
10389+ if clashing.is_empty() {
10390+ return;
10391+ }
10392+
10393+ // Every rename, as (module, old name, new name).
10394+ let mut renames: Vec<(String, String, String)> = Vec::new();
10395+ for (name, mods) in clashing {
10396+ for m in mods {
10397+ if m.is_empty() {
10398+ continue; // the top level keeps the bare name
10399+ }
10400+ renames.push((m.clone(), name.clone(), format!("{}_{}", crate_prefix(&m), name)));
10401+ }
10402+ }
10403+
10404+ // Inside each module, the bare name; everywhere, the qualified one.
10405+ fn walk(items: &mut Vec<Item>, path: &str, renames: &[(String, String, String)]) {
10406+ let here: HashMap<String, String> = renames
10407+ .iter()
10408+ .filter(|(m, _, _)| m == path)
10409+ .map(|(_, from, to)| (from.clone(), to.clone()))
10410+ .collect();
10411+ if !here.is_empty() {
10412+ for item in items.iter_mut() {
10413+ syn::visit_mut::VisitMut::visit_item_mut(&mut Renamer { renames: &here }, item);
10414+ }
10415+ }
10416+ for item in items.iter_mut() {
10417+ if let Item::Mod(m) = item {
10418+ let name = m.ident.to_string();
10419+ if let Some((_, inner)) = &mut m.content {
10420+ walk(inner, &name, renames);
10421+ }
10422+ }
10423+ }
10424+ }
10425+ walk(&mut f.items, "", &renames);
10426+
10427+ // A `module::name` reference anywhere in the file now names the renamed
10428+ // item; the qualifier is dropped by the flattening, so the last segment
10429+ // has to carry it.
10430+ let qual: Vec<(String, String, String)> = renames;
10431+ syn::visit_mut::VisitMut::visit_file_mut(&mut QualRenamer { renames: &qual }, f);
10432+}
10433+
10434+/// Rewrite `module::name` to the flattened name it now has.
10435+struct QualRenamer<'a> {
10436+ renames: &'a [(String, String, String)],
10437+}
10438+
10439+impl QualRenamer<'_> {
10440+ /// Rewrite `module :: name` inside a macro's tokens.
10441+ ///
10442+ /// A macro invocation's arguments are a token stream, not an AST, so
10443+ /// `VisitMut` does not reach them -- and rustnim parses them later, after
10444+ /// this pass has run. `println!("{}", a::f(1))` is the common case and it
10445+ /// is the whole of why the rename has to happen here too.
10446+ fn tokens(&self, ts: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
10447+ use proc_macro2::{Group, TokenTree};
10448+ let src: Vec<TokenTree> = ts.into_iter().collect();
10449+ let mut out: Vec<TokenTree> = Vec::new();
10450+ let mut i = 0;
10451+ while i < src.len() {
10452+ // `Ident :: Ident`, with the two colons syn gives as separate
10453+ // punctuation.
10454+ if i + 3 < src.len() {
10455+ if let (
10456+ TokenTree::Ident(q),
10457+ TokenTree::Punct(c1),
10458+ TokenTree::Punct(c2),
10459+ TokenTree::Ident(n),
10460+ ) = (&src[i], &src[i + 1], &src[i + 2], &src[i + 3])
10461+ {
10462+ if c1.as_char() == ':' && c2.as_char() == ':' {
10463+ let (qs, ns) = (q.to_string(), n.to_string());
10464+ if let Some((_, _, to)) =
10465+ self.renames.iter().find(|(m, f, _)| *m == qs && *f == ns)
10466+ {
10467+ out.push(src[i].clone());
10468+ out.push(src[i + 1].clone());
10469+ out.push(src[i + 2].clone());
10470+ out.push(TokenTree::Ident(proc_macro2::Ident::new(
10471+ to,
10472+ n.span(),
10473+ )));
10474+ i += 4;
10475+ continue;
10476+ }
10477+ }
10478+ }
10479+ }
10480+ match &src[i] {
10481+ TokenTree::Group(g) => {
10482+ let mut ng = Group::new(g.delimiter(), self.tokens(g.stream()));
10483+ ng.set_span(g.span());
10484+ out.push(TokenTree::Group(ng));
10485+ }
10486+ other => out.push(other.clone()),
10487+ }
10488+ i += 1;
10489+ }
10490+ out.into_iter().collect()
10491+ }
10492+}
10493+
10494+impl syn::visit_mut::VisitMut for QualRenamer<'_> {
10495+ fn visit_macro_mut(&mut self, m: &mut syn::Macro) {
10496+ m.tokens = self.tokens(m.tokens.clone());
10497+ }
10498+ fn visit_path_mut(&mut self, p: &mut syn::Path) {
10499+ let n = p.segments.len();
10500+ if n >= 2 {
10501+ let q = p.segments[n - 2].ident.to_string();
10502+ let last = p.segments[n - 1].ident.to_string();
10503+ if let Some((_, _, to)) = self
10504+ .renames
10505+ .iter()
10506+ .find(|(m, from, _)| *m == q && *from == last)
10507+ {
10508+ p.segments[n - 1].ident =
10509+ proc_macro2::Ident::new(to, p.segments[n - 1].ident.span());
10510+ }
10511+ }
10512+ syn::visit_mut::visit_path_mut(self, p);
10513+ }
10514+}
@@ -604,6 +604,12 @@ impl Lowerer {
604 // crate that declares it, and the uses are rewritten to match: inside604 // crate that declares it, and the uses are rewritten to match: inside
605 // that crate, and inside any crate that `use`s it from there.605 // that crate, and inside any crate that `use`s it from there.
606 let mut files = files.to_vec();606 let mut files = files.to_vec();
607+ // Within a file first: `mod` is a namespace Rust keeps and rustnim
608+ // flattens, so two modules declaring the same name collide before any
609+ // cross-crate question arises.
610+ for (_, f) in files.iter_mut() {
611+ namespace_modules(f);
612+ }
607 namespace_types(&mut files);613 namespace_types(&mut files);
608 614
609 // Pass -1: give every function-local type a name of its own.615 // Pass -1: give every function-local type a name of its own.
@@ -10209,6 +10215,35 @@ impl Renamer<'_> {
10209 }10215 }
10210 10216
10211 impl syn::visit_mut::VisitMut for Renamer<'_> {10217 impl syn::visit_mut::VisitMut for Renamer<'_> {
10218+ /// A macro's arguments are tokens, not an AST, and rustnim parses them
10219+ /// after this pass. `format!("{}", MASK)` is why the rename has to reach
10220+ /// them.
10221+ fn visit_macro_mut(&mut self, m: &mut syn::Macro) {
10222+ fn walk(
10223+ ts: proc_macro2::TokenStream,
10224+ r: &HashMap<String, String>,
10225+ ) -> proc_macro2::TokenStream {
10226+ use proc_macro2::{Group, TokenTree};
10227+ ts.into_iter()
10228+ .map(|t| match t {
10229+ TokenTree::Ident(i) => match r.get(&i.to_string()) {
10230+ Some(to) => {
10231+ TokenTree::Ident(proc_macro2::Ident::new(to, i.span()))
10232+ }
10233+ None => TokenTree::Ident(i),
10234+ },
10235+ TokenTree::Group(g) => {
10236+ let mut ng = Group::new(g.delimiter(), walk(g.stream(), r));
10237+ ng.set_span(g.span());
10238+ TokenTree::Group(ng)
10239+ }
10240+ other => other,
10241+ })
10242+ .collect()
10243+ }
10244+ m.tokens = walk(m.tokens.clone(), self.renames);
10245+ }
10246+
10212 fn visit_item_struct_mut(&mut self, i: &mut syn::ItemStruct) {10247 fn visit_item_struct_mut(&mut self, i: &mut syn::ItemStruct) {
10213 self.rename(&mut i.ident);10248 self.rename(&mut i.ident);
10214 syn::visit_mut::visit_item_struct_mut(self, i);10249 syn::visit_mut::visit_item_struct_mut(self, i);
@@ -10225,6 +10260,24 @@ impl syn::visit_mut::VisitMut for Renamer<'_> {
10225 self.rename(&mut i.ident);10260 self.rename(&mut i.ident);
10226 syn::visit_mut::visit_item_union_mut(self, i);10261 syn::visit_mut::visit_item_union_mut(self, i);
10227 }10262 }
10263+ // Values collide between modules exactly as types do: a `const` declared
10264+ // in two of them is one declaration twice once `mod` is flattened.
10265+ fn visit_item_const_mut(&mut self, i: &mut syn::ItemConst) {
10266+ self.rename(&mut i.ident);
10267+ syn::visit_mut::visit_item_const_mut(self, i);
10268+ }
10269+ fn visit_item_static_mut(&mut self, i: &mut syn::ItemStatic) {
10270+ self.rename(&mut i.ident);
10271+ syn::visit_mut::visit_item_static_mut(self, i);
10272+ }
10273+ fn visit_item_fn_mut(&mut self, i: &mut syn::ItemFn) {
10274+ self.rename(&mut i.sig.ident);
10275+ syn::visit_mut::visit_item_fn_mut(self, i);
10276+ }
10277+ fn visit_item_trait_mut(&mut self, i: &mut syn::ItemTrait) {
10278+ self.rename(&mut i.ident);
10279+ syn::visit_mut::visit_item_trait_mut(self, i);
10280+ }
10228 fn visit_path_mut(&mut self, p: &mut syn::Path) {10281 fn visit_path_mut(&mut self, p: &mut syn::Path) {
10229 self.rename_path(p);10282 self.rename_path(p);
10230 syn::visit_mut::visit_path_mut(self, p);10283 syn::visit_mut::visit_path_mut(self, p);
@@ -10278,3 +10331,184 @@ fn is_bare_literal(e: &Expr) -> bool {
10278 _ => false,10331 _ => false,
10279 }10332 }
10280 }10333 }
10334+
10335+// ------------------------------------------------------ module namespacing
10336+
10337+/// The names an item declares, for collision purposes. Values and types
10338+/// alike: a `const` in two modules collides exactly as a `struct` would.
10339+fn declared_names(item: &Item) -> Vec<String> {
10340+ match item {
10341+ Item::Fn(f) => vec![f.sig.ident.to_string()],
10342+ Item::Const(c) => vec![c.ident.to_string()],
10343+ Item::Static(s) => vec![s.ident.to_string()],
10344+ Item::Struct(s) => vec![s.ident.to_string()],
10345+ Item::Enum(e) => vec![e.ident.to_string()],
10346+ Item::Type(t) => vec![t.ident.to_string()],
10347+ Item::Union(u) => vec![u.ident.to_string()],
10348+ Item::Trait(t) => vec![t.ident.to_string()],
10349+ _ => Vec::new(),
10350+ }
10351+}
10352+
10353+/// Rename items that collide between a file's own inline modules.
10354+///
10355+/// `mod` is a namespace in Rust and rustnim flattens it, so two modules that
10356+/// each declare `SIGNIFICAND_SIZE` -- which is what a `macro_rules!` invoked
10357+/// once per module produces -- become one declaration twice. Rust keeps them
10358+/// apart by module, so the module name is what tells them apart here.
10359+///
10360+/// Only names that actually collide are touched, and only within the module
10361+/// that declares them, plus any `module::name` reference elsewhere in the
10362+/// file. A name declared once keeps it.
10363+fn namespace_modules(f: &mut syn::File) {
10364+ // Which modules declare each name. The file's own top level counts as a
10365+ // module with no name, and never gets renamed -- it has nothing to be
10366+ // qualified by.
10367+ let mut owners: HashMap<String, Vec<String>> = HashMap::new();
10368+ fn scan(items: &[Item], path: &str, owners: &mut HashMap<String, Vec<String>>) {
10369+ for item in items {
10370+ for n in declared_names(item) {
10371+ let e = owners.entry(n).or_default();
10372+ if !e.contains(&path.to_string()) {
10373+ e.push(path.to_string());
10374+ }
10375+ }
10376+ if let Item::Mod(m) = item {
10377+ if let Some((_, inner)) = &m.content {
10378+ scan(inner, &m.ident.to_string(), owners);
10379+ }
10380+ }
10381+ }
10382+ }
10383+ scan(&f.items, "", &mut owners);
10384+
10385+ let clashing: Vec<(String, Vec<String>)> = owners
10386+ .into_iter()
10387+ .filter(|(_, mods)| mods.len() > 1)
10388+ .collect();
10389+ if clashing.is_empty() {
10390+ return;
10391+ }
10392+
10393+ // Every rename, as (module, old name, new name).
10394+ let mut renames: Vec<(String, String, String)> = Vec::new();
10395+ for (name, mods) in clashing {
10396+ for m in mods {
10397+ if m.is_empty() {
10398+ continue; // the top level keeps the bare name
10399+ }
10400+ renames.push((m.clone(), name.clone(), format!("{}_{}", crate_prefix(&m), name)));
10401+ }
10402+ }
10403+
10404+ // Inside each module, the bare name; everywhere, the qualified one.
10405+ fn walk(items: &mut Vec<Item>, path: &str, renames: &[(String, String, String)]) {
10406+ let here: HashMap<String, String> = renames
10407+ .iter()
10408+ .filter(|(m, _, _)| m == path)
10409+ .map(|(_, from, to)| (from.clone(), to.clone()))
10410+ .collect();
10411+ if !here.is_empty() {
10412+ for item in items.iter_mut() {
10413+ syn::visit_mut::VisitMut::visit_item_mut(&mut Renamer { renames: &here }, item);
10414+ }
10415+ }
10416+ for item in items.iter_mut() {
10417+ if let Item::Mod(m) = item {
10418+ let name = m.ident.to_string();
10419+ if let Some((_, inner)) = &mut m.content {
10420+ walk(inner, &name, renames);
10421+ }
10422+ }
10423+ }
10424+ }
10425+ walk(&mut f.items, "", &renames);
10426+
10427+ // A `module::name` reference anywhere in the file now names the renamed
10428+ // item; the qualifier is dropped by the flattening, so the last segment
10429+ // has to carry it.
10430+ let qual: Vec<(String, String, String)> = renames;
10431+ syn::visit_mut::VisitMut::visit_file_mut(&mut QualRenamer { renames: &qual }, f);
10432+}
10433+
10434+/// Rewrite `module::name` to the flattened name it now has.
10435+struct QualRenamer<'a> {
10436+ renames: &'a [(String, String, String)],
10437+}
10438+
10439+impl QualRenamer<'_> {
10440+ /// Rewrite `module :: name` inside a macro's tokens.
10441+ ///
10442+ /// A macro invocation's arguments are a token stream, not an AST, so
10443+ /// `VisitMut` does not reach them -- and rustnim parses them later, after
10444+ /// this pass has run. `println!("{}", a::f(1))` is the common case and it
10445+ /// is the whole of why the rename has to happen here too.
10446+ fn tokens(&self, ts: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
10447+ use proc_macro2::{Group, TokenTree};
10448+ let src: Vec<TokenTree> = ts.into_iter().collect();
10449+ let mut out: Vec<TokenTree> = Vec::new();
10450+ let mut i = 0;
10451+ while i < src.len() {
10452+ // `Ident :: Ident`, with the two colons syn gives as separate
10453+ // punctuation.
10454+ if i + 3 < src.len() {
10455+ if let (
10456+ TokenTree::Ident(q),
10457+ TokenTree::Punct(c1),
10458+ TokenTree::Punct(c2),
10459+ TokenTree::Ident(n),
10460+ ) = (&src[i], &src[i + 1], &src[i + 2], &src[i + 3])
10461+ {
10462+ if c1.as_char() == ':' && c2.as_char() == ':' {
10463+ let (qs, ns) = (q.to_string(), n.to_string());
10464+ if let Some((_, _, to)) =
10465+ self.renames.iter().find(|(m, f, _)| *m == qs && *f == ns)
10466+ {
10467+ out.push(src[i].clone());
10468+ out.push(src[i + 1].clone());
10469+ out.push(src[i + 2].clone());
10470+ out.push(TokenTree::Ident(proc_macro2::Ident::new(
10471+ to,
10472+ n.span(),
10473+ )));
10474+ i += 4;
10475+ continue;
10476+ }
10477+ }
10478+ }
10479+ }
10480+ match &src[i] {
10481+ TokenTree::Group(g) => {
10482+ let mut ng = Group::new(g.delimiter(), self.tokens(g.stream()));
10483+ ng.set_span(g.span());
10484+ out.push(TokenTree::Group(ng));
10485+ }
10486+ other => out.push(other.clone()),
10487+ }
10488+ i += 1;
10489+ }
10490+ out.into_iter().collect()
10491+ }
10492+}
10493+
10494+impl syn::visit_mut::VisitMut for QualRenamer<'_> {
10495+ fn visit_macro_mut(&mut self, m: &mut syn::Macro) {
10496+ m.tokens = self.tokens(m.tokens.clone());
10497+ }
10498+ fn visit_path_mut(&mut self, p: &mut syn::Path) {
10499+ let n = p.segments.len();
10500+ if n >= 2 {
10501+ let q = p.segments[n - 2].ident.to_string();
10502+ let last = p.segments[n - 1].ident.to_string();
10503+ if let Some((_, _, to)) = self
10504+ .renames
10505+ .iter()
10506+ .find(|(m, from, _)| *m == q && *from == last)
10507+ {
10508+ p.segments[n - 1].ident =
10509+ proc_macro2::Ident::new(to, p.segments[n - 1].ident.span());
10510+ }
10511+ }
10512+ syn::visit_mut::visit_path_mut(self, p);
10513+ }
10514+}
added tests/cases/072-module-collision/main.rs +32 -0
new file mode 100644
@@ -0,0 +1,32 @@
1+// `mod` is a namespace in Rust and rustnim flattens it, so two modules that
2+// each declare `WIDTH` — which is what a macro invoked once per module
3+// produces — would become one declaration twice. The module name is what
4+// tells them apart.
5+mod narrow {
6+ pub const WIDTH: u32 = 8;
7+ pub const MASK: u32 = (1 << WIDTH) - 1;
8+
9+ pub fn describe(v: u32) -> String {
10+ format!("narrow {} of {}", v & MASK, WIDTH)
11+ }
12+}
13+
14+mod wide {
15+ pub const WIDTH: u32 = 16;
16+ pub const MASK: u32 = (1 << WIDTH) - 1;
17+
18+ pub fn describe(v: u32) -> String {
19+ format!("wide {} of {}", v & MASK, WIDTH)
20+ }
21+}
22+
23+// A name declared only once keeps it.
24+const SHARED: u32 = 99;
25+
26+fn main() {
27+ println!("{}", narrow::describe(300));
28+ println!("{}", wide::describe(300));
29+ println!("{} {}", narrow::WIDTH, wide::WIDTH);
30+ println!("{} {}", narrow::MASK, wide::MASK);
31+ println!("{}", SHARED);
32+}
new file mode 100644
@@ -0,0 +1,32 @@
1+// `mod` is a namespace in Rust and rustnim flattens it, so two modules that
2+// each declare `WIDTH` — which is what a macro invoked once per module
3+// produces — would become one declaration twice. The module name is what
4+// tells them apart.
5+mod narrow {
6+ pub const WIDTH: u32 = 8;
7+ pub const MASK: u32 = (1 << WIDTH) - 1;
8+
9+ pub fn describe(v: u32) -> String {
10+ format!("narrow {} of {}", v & MASK, WIDTH)
11+ }
12+}
13+
14+mod wide {
15+ pub const WIDTH: u32 = 16;
16+ pub const MASK: u32 = (1 << WIDTH) - 1;
17+
18+ pub fn describe(v: u32) -> String {
19+ format!("wide {} of {}", v & MASK, WIDTH)
20+ }
21+}
22+
23+// A name declared only once keeps it.
24+const SHARED: u32 = 99;
25+
26+fn main() {
27+ println!("{}", narrow::describe(300));
28+ println!("{}", wide::describe(300));
29+ println!("{} {}", narrow::WIDTH, wide::WIDTH);
30+ println!("{} {}", narrow::MASK, wide::MASK);
31+ println!("{}", SHARED);
32+}