nandi/rustnimpublic Fork
⑂ b867486
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

Give colliding function-local types a name of their own

Feeding 357 expanded dependency crates at once turned the guard added in the
previous commit into a real failure: many crates' `serde` impls each declare
`enum __Field` in a `deserialize` body, and they are different enums.

Rust scopes a function-local type to its body, so renaming one inside its own
body is an exact rewrite -- the body *is* the whole of the name's scope, so
nothing outside can refer to it and nothing inside can mean anything else by
it. Done as a token-level substitution over the body before any collection
happens, which is why it can be exact rather than best-effort.

tests/cases/053 has two bodies declaring a different `enum Field` each, plus a
`struct Field` in a third.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-19T09:45:46-07:00 Browse files
b867486 parent: 026a641
modified src/lower.rs +110 -0
@@ -444,6 +444,22 @@ impl Lowerer {
444444 // ---------------------------------------------------------------- file
445445
446446 pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> {
447+ // Pass -1: give every function-local type a name of its own.
448+ //
449+ // Rust scopes a `struct`/`enum` declared in a function body to that
450+ // body, so two bodies may each declare `__Field` and mean different
451+ // things -- every `serde` `deserialize` impl does exactly that. Nim has
452+ // no such scope. Renaming the clashing ones inside their own body is an
453+ // exact rewrite, because the body is the whole of the name's scope.
454+ let mut files = files.to_vec();
455+ let mut seen: HashMap<String, String> = HashMap::new();
456+ let mut n = 0usize;
457+ for (_, f) in files.iter_mut() {
458+ for item in f.items.iter_mut() {
459+ uniquify_local_types(item, &mut seen, &mut n);
460+ }
461+ }
462+ let files = &files[..];
447463
448464 // Pass 0: type aliases. A signature in one file may use an alias
449465 // declared in another, and inputs are given in whatever order suits
@@ -7166,3 +7182,97 @@ fn derive_item_block(c: &syn::ItemConst) -> Option<Vec<Item>> {
71667182 .collect();
71677183 if items.is_empty() { None } else { Some(items) }
71687184 }
7185+
7186+/// Rename function-local `struct`/`enum` declarations that would collide at
7187+/// module level, rewriting each inside its own body.
7188+///
7189+/// The rename is a token-level substitution of the identifier within the
7190+/// body, which is exact here and only here: the body *is* the whole scope of
7191+/// a function-local type name in Rust, so nothing outside can be referring to
7192+/// it, and nothing inside can mean anything else by it.
7193+fn uniquify_local_types(item: &mut Item, seen: &mut HashMap<String, String>, n: &mut usize) {
7194+ match item {
7195+ Item::Fn(f) => uniquify_block(&mut f.block, seen, n),
7196+ Item::Impl(im) => {
7197+ for it in im.items.iter_mut() {
7198+ if let syn::ImplItem::Fn(m) = it {
7199+ uniquify_block(&mut m.block, seen, n);
7200+ }
7201+ }
7202+ }
7203+ Item::Mod(m) => {
7204+ if let Some((_, items)) = m.content.as_mut() {
7205+ for i in items.iter_mut() {
7206+ uniquify_local_types(i, seen, n);
7207+ }
7208+ }
7209+ }
7210+ Item::Const(c) => {
7211+ // A derive macro's `const _: () = { .. }` wrapper, whose impls are
7212+ // unwrapped to module level later.
7213+ if derive_item_block(c).is_some() {
7214+ if let Expr::Block(b) = &mut *c.expr {
7215+ for st in b.block.stmts.iter_mut() {
7216+ if let Stmt::Item(i) = st {
7217+ uniquify_local_types(i, seen, n);
7218+ }
7219+ }
7220+ }
7221+ }
7222+ }
7223+ _ => {}
7224+ }
7225+}
7226+
7227+fn uniquify_block(block: &mut syn::Block, seen: &mut HashMap<String, String>, n: &mut usize) {
7228+ let mut renames: Vec<(String, String)> = Vec::new();
7229+ for st in &block.stmts {
7230+ let Stmt::Item(i) = st else { continue };
7231+ let name = match i {
7232+ Item::Struct(s) => s.ident.to_string(),
7233+ Item::Enum(e) => e.ident.to_string(),
7234+ _ => continue,
7235+ };
7236+ let text = quote::quote!(#i).to_string();
7237+ match seen.get(&name) {
7238+ Some(prev) if prev == &text => {}
7239+ Some(_) => {
7240+ *n += 1;
7241+ let fresh = format!("{name}Local{n}");
7242+ seen.insert(fresh.clone(), text);
7243+ renames.push((name, fresh));
7244+ }
7245+ None => {
7246+ seen.insert(name, text);
7247+ }
7248+ }
7249+ }
7250+ if renames.is_empty() {
7251+ return;
7252+ }
7253+ let mut ts = quote::quote!(#block);
7254+ for (from, to) in &renames {
7255+ ts = rename_ident(ts, from, to);
7256+ }
7257+ if let Ok(b) = syn::parse2::<syn::Block>(ts) {
7258+ *block = b;
7259+ }
7260+}
7261+
7262+fn rename_ident(ts: proc_macro2::TokenStream, from: &str, to: &str) -> proc_macro2::TokenStream {
7263+ use proc_macro2::{Group, TokenTree};
7264+ ts.into_iter()
7265+ .map(|t| match t {
7266+ TokenTree::Ident(i) if i == from => {
7267+ TokenTree::Ident(proc_macro2::Ident::new(to, i.span()))
7268+ }
7269+ TokenTree::Group(g) => {
7270+ let inner = rename_ident(g.stream(), from, to);
7271+ let mut ng = Group::new(g.delimiter(), inner);
7272+ ng.set_span(g.span());
7273+ TokenTree::Group(ng)
7274+ }
7275+ other => other,
7276+ })
7277+ .collect()
7278+}
@@ -444,6 +444,22 @@ impl Lowerer {
444 // ---------------------------------------------------------------- file444 // ---------------------------------------------------------------- file
445 445
446 pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> {446 pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> {
447+ // Pass -1: give every function-local type a name of its own.
448+ //
449+ // Rust scopes a `struct`/`enum` declared in a function body to that
450+ // body, so two bodies may each declare `__Field` and mean different
451+ // things -- every `serde` `deserialize` impl does exactly that. Nim has
452+ // no such scope. Renaming the clashing ones inside their own body is an
453+ // exact rewrite, because the body is the whole of the name's scope.
454+ let mut files = files.to_vec();
455+ let mut seen: HashMap<String, String> = HashMap::new();
456+ let mut n = 0usize;
457+ for (_, f) in files.iter_mut() {
458+ for item in f.items.iter_mut() {
459+ uniquify_local_types(item, &mut seen, &mut n);
460+ }
461+ }
462+ let files = &files[..];
447 463
448 // Pass 0: type aliases. A signature in one file may use an alias464 // Pass 0: type aliases. A signature in one file may use an alias
449 // declared in another, and inputs are given in whatever order suits465 // declared in another, and inputs are given in whatever order suits
@@ -7166,3 +7182,97 @@ fn derive_item_block(c: &syn::ItemConst) -> Option<Vec<Item>> {
7166 .collect();7182 .collect();
7167 if items.is_empty() { None } else { Some(items) }7183 if items.is_empty() { None } else { Some(items) }
7168 }7184 }
7185+
7186+/// Rename function-local `struct`/`enum` declarations that would collide at
7187+/// module level, rewriting each inside its own body.
7188+///
7189+/// The rename is a token-level substitution of the identifier within the
7190+/// body, which is exact here and only here: the body *is* the whole scope of
7191+/// a function-local type name in Rust, so nothing outside can be referring to
7192+/// it, and nothing inside can mean anything else by it.
7193+fn uniquify_local_types(item: &mut Item, seen: &mut HashMap<String, String>, n: &mut usize) {
7194+ match item {
7195+ Item::Fn(f) => uniquify_block(&mut f.block, seen, n),
7196+ Item::Impl(im) => {
7197+ for it in im.items.iter_mut() {
7198+ if let syn::ImplItem::Fn(m) = it {
7199+ uniquify_block(&mut m.block, seen, n);
7200+ }
7201+ }
7202+ }
7203+ Item::Mod(m) => {
7204+ if let Some((_, items)) = m.content.as_mut() {
7205+ for i in items.iter_mut() {
7206+ uniquify_local_types(i, seen, n);
7207+ }
7208+ }
7209+ }
7210+ Item::Const(c) => {
7211+ // A derive macro's `const _: () = { .. }` wrapper, whose impls are
7212+ // unwrapped to module level later.
7213+ if derive_item_block(c).is_some() {
7214+ if let Expr::Block(b) = &mut *c.expr {
7215+ for st in b.block.stmts.iter_mut() {
7216+ if let Stmt::Item(i) = st {
7217+ uniquify_local_types(i, seen, n);
7218+ }
7219+ }
7220+ }
7221+ }
7222+ }
7223+ _ => {}
7224+ }
7225+}
7226+
7227+fn uniquify_block(block: &mut syn::Block, seen: &mut HashMap<String, String>, n: &mut usize) {
7228+ let mut renames: Vec<(String, String)> = Vec::new();
7229+ for st in &block.stmts {
7230+ let Stmt::Item(i) = st else { continue };
7231+ let name = match i {
7232+ Item::Struct(s) => s.ident.to_string(),
7233+ Item::Enum(e) => e.ident.to_string(),
7234+ _ => continue,
7235+ };
7236+ let text = quote::quote!(#i).to_string();
7237+ match seen.get(&name) {
7238+ Some(prev) if prev == &text => {}
7239+ Some(_) => {
7240+ *n += 1;
7241+ let fresh = format!("{name}Local{n}");
7242+ seen.insert(fresh.clone(), text);
7243+ renames.push((name, fresh));
7244+ }
7245+ None => {
7246+ seen.insert(name, text);
7247+ }
7248+ }
7249+ }
7250+ if renames.is_empty() {
7251+ return;
7252+ }
7253+ let mut ts = quote::quote!(#block);
7254+ for (from, to) in &renames {
7255+ ts = rename_ident(ts, from, to);
7256+ }
7257+ if let Ok(b) = syn::parse2::<syn::Block>(ts) {
7258+ *block = b;
7259+ }
7260+}
7261+
7262+fn rename_ident(ts: proc_macro2::TokenStream, from: &str, to: &str) -> proc_macro2::TokenStream {
7263+ use proc_macro2::{Group, TokenTree};
7264+ ts.into_iter()
7265+ .map(|t| match t {
7266+ TokenTree::Ident(i) if i == from => {
7267+ TokenTree::Ident(proc_macro2::Ident::new(to, i.span()))
7268+ }
7269+ TokenTree::Group(g) => {
7270+ let inner = rename_ident(g.stream(), from, to);
7271+ let mut ng = Group::new(g.delimiter(), inner);
7272+ ng.set_span(g.span());
7273+ TokenTree::Group(ng)
7274+ }
7275+ other => other,
7276+ })
7277+ .collect()
7278+}
added tests/cases/053-local-type-collision.rs +46 -0
new file mode 100644
@@ -0,0 +1,46 @@
1+// Two function bodies each declaring a type of the same name and a different
2+// shape. Rust scopes each to its own body; every `serde` `deserialize` impl
3+// declares an `enum __Field` this way. Nim has no such scope, so one of them
4+// is renamed inside its own body — which is exact, because the body is the
5+// whole of the name's scope.
6+fn left(n: u32) -> String {
7+ #[derive(Debug)]
8+ enum Field {
9+ A,
10+ B,
11+ }
12+ let f: Field = if n > 0 { Field::A } else { Field::B };
13+ format!("left {:?}", f)
14+}
15+
16+fn right(n: u32) -> String {
17+ #[derive(Debug)]
18+ enum Field {
19+ X,
20+ Y,
21+ Z,
22+ }
23+ let f: Field = match n {
24+ 0 => Field::X,
25+ 1 => Field::Y,
26+ _ => Field::Z,
27+ };
28+ format!("right {:?}", f)
29+}
30+
31+fn boxed(n: u32) -> String {
32+ struct Field {
33+ v: u32,
34+ }
35+ let f = Field { v: n * 3 };
36+ format!("boxed {}", f.v)
37+}
38+
39+fn main() {
40+ println!("{}", left(1));
41+ println!("{}", left(0));
42+ println!("{}", right(0));
43+ println!("{}", right(1));
44+ println!("{}", right(5));
45+ println!("{}", boxed(4));
46+}
new file mode 100644
@@ -0,0 +1,46 @@
1+// Two function bodies each declaring a type of the same name and a different
2+// shape. Rust scopes each to its own body; every `serde` `deserialize` impl
3+// declares an `enum __Field` this way. Nim has no such scope, so one of them
4+// is renamed inside its own body — which is exact, because the body is the
5+// whole of the name's scope.
6+fn left(n: u32) -> String {
7+ #[derive(Debug)]
8+ enum Field {
9+ A,
10+ B,
11+ }
12+ let f: Field = if n > 0 { Field::A } else { Field::B };
13+ format!("left {:?}", f)
14+}
15+
16+fn right(n: u32) -> String {
17+ #[derive(Debug)]
18+ enum Field {
19+ X,
20+ Y,
21+ Z,
22+ }
23+ let f: Field = match n {
24+ 0 => Field::X,
25+ 1 => Field::Y,
26+ _ => Field::Z,
27+ };
28+ format!("right {:?}", f)
29+}
30+
31+fn boxed(n: u32) -> String {
32+ struct Field {
33+ v: u32,
34+ }
35+ let f = Field { v: n * 3 };
36+ format!("boxed {}", f.v)
37+}
38+
39+fn main() {
40+ println!("{}", left(1));
41+ println!("{}", left(0));
42+ println!("{}", right(0));
43+ println!("{}", right(1));
44+ println!("{}", right(5));
45+ println!("{}", boxed(4));
46+}