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>4d9142c parent: e34bf8b modified
src/lower.rs +234 -0 | @@ -604,6 +604,12 @@ impl Lowerer { | ||
| 604 | 604 | // crate that declares it, and the uses are rewritten to match: inside |
| 605 | 605 | // that crate, and inside any crate that `use`s it from there. |
| 606 | 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 | 613 | namespace_types(&mut files); |
| 608 | 614 | |
| 609 | 615 | // Pass -1: give every function-local type a name of its own. |
| @@ -10209,6 +10215,35 @@ impl Renamer<'_> { | ||
| 10209 | 10215 | } |
| 10210 | 10216 | |
| 10211 | 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 | 10247 | fn visit_item_struct_mut(&mut self, i: &mut syn::ItemStruct) { |
| 10213 | 10248 | self.rename(&mut i.ident); |
| 10214 | 10249 | syn::visit_mut::visit_item_struct_mut(self, i); |
| @@ -10225,6 +10260,24 @@ impl syn::visit_mut::VisitMut for Renamer<'_> { | ||
| 10225 | 10260 | self.rename(&mut i.ident); |
| 10226 | 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 | 10281 | fn visit_path_mut(&mut self, p: &mut syn::Path) { |
| 10229 | 10282 | self.rename_path(p); |
| 10230 | 10283 | syn::visit_mut::visit_path_mut(self, p); |
| @@ -10278,3 +10331,184 @@ fn is_bare_literal(e: &Expr) -> bool { | ||
| 10278 | 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 | +} | |
| @@ -604,6 +604,12 @@ impl Lowerer { | |||
| 604 | // crate that declares it, and the uses are rewritten to match: inside | 604 | // 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 | +} | ||