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>
b867486 parent: 026a641 modified
src/lower.rs +110 -0 | @@ -444,6 +444,22 @@ impl Lowerer { | ||
| 444 | 444 | // ---------------------------------------------------------------- file |
| 445 | 445 | |
| 446 | 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 | 464 | // Pass 0: type aliases. A signature in one file may use an alias |
| 449 | 465 | // 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 | 7182 | .collect(); |
| 7167 | 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 | +} | |
| @@ -444,6 +444,22 @@ impl Lowerer { | |||
| 444 | // ---------------------------------------------------------------- file | 444 | // ---------------------------------------------------------------- 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 alias | 464 | // Pass 0: type aliases. A signature in one file may use an alias |
| 449 | // declared in another, and inputs are given in whatever order suits | 465 | // 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 | +} | ||