Namespace each crate's types, and expand aliases wherever they sit
Every input file's items end up in one Nim module, which is what makes cross-file calls work without modelling Rust's module system -- and what makes two crates' `Color` one type. Of 14,714 distinct type names across libcosmic's 356 dependencies, 1,575 are declared in more than one place. A name declared by more than one crate is now prefixed with the crate that declares it, and the uses are rewritten to match: inside that crate, and inside any crate that `use`s it from there. Names declared once are left alone, which is most of them. Done as a token rewrite at ingestion, like the function-local renaming, rather than by re-keying the lowering's tables. Writing the test for it found a bug that had nothing to do with namespacing: `expand` only looked at the outermost node of a type, so an alias inside `&Color`, `Vec<Color>` or `(Color, u8)` was never expanded and the name reached the output undeclared. It walks the whole type now. That in turn broke `036-extern-c`, correctly: the case declares `type c_char = i8` as a stand-in for libc, and expanding it produced `const signed char *` where C's `strlen` wants `const char *` -- distinct types in C, whatever their representation. A C type name is what carries the ABI meaning, so it is never expanded, whatever the input aliases it to. tests/cases/058, which is two crates declaring `Color` and meaning different things by it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
406427b parent: d553b29 modified
src/lower.rs +242 -2 | @@ -504,6 +504,21 @@ impl Lowerer { | ||
| 504 | 504 | // ---------------------------------------------------------------- file |
| 505 | 505 | |
| 506 | 506 | pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> { |
| 507 | + // Pass -2: give each crate's types a namespace. | |
| 508 | + // | |
| 509 | + // Every input file's items end up in one Nim module, which is what | |
| 510 | + // makes cross-file calls work without modelling Rust's module system. | |
| 511 | + // It also means two crates that each declare a `Color` declare the | |
| 512 | + // same one. In a dependency graph that is not an edge case: of 14,714 | |
| 513 | + // distinct type names across libcosmic's 356 dependencies, 1,575 are | |
| 514 | + // declared in more than one place. | |
| 515 | + // | |
| 516 | + // So a name declared by more than one crate is prefixed with the | |
| 517 | + // crate that declares it, and the uses are rewritten to match: inside | |
| 518 | + // that crate, and inside any crate that `use`s it from there. | |
| 519 | + let mut files = files.to_vec(); | |
| 520 | + namespace_types(&mut files); | |
| 521 | + | |
| 507 | 522 | // Pass -1: give every function-local type a name of its own. |
| 508 | 523 | // |
| 509 | 524 | // Rust scopes a `struct`/`enum` declared in a function body to that |
| @@ -511,7 +526,6 @@ impl Lowerer { | ||
| 511 | 526 | // things -- every `serde` `deserialize` impl does exactly that. Nim has |
| 512 | 527 | // no such scope. Renaming the clashing ones inside their own body is an |
| 513 | 528 | // exact rewrite, because the body is the whole of the name's scope. |
| 514 | - let mut files = files.to_vec(); | |
| 515 | 529 | let mut seen: HashMap<String, String> = HashMap::new(); |
| 516 | 530 | let mut n = 0usize; |
| 517 | 531 | for (_, f) in files.iter_mut() { |
| @@ -1406,6 +1420,45 @@ impl Lowerer { | ||
| 1406 | 1420 | if depth > 16 { |
| 1407 | 1421 | return Err("type alias expansion did not terminate; is it cyclic?".into()); |
| 1408 | 1422 | } |
| 1423 | + // An alias can sit anywhere inside a type -- `&Color`, `Vec<Color>`, | |
| 1424 | + // `(Color, u8)` -- so the whole structure is walked, not just its | |
| 1425 | + // outermost node. | |
| 1426 | + use syn::Type; | |
| 1427 | + let mut nested = t.clone(); | |
| 1428 | + let rec = |me: &Self, e: &syn::Type| me.expand(e, depth + 1); | |
| 1429 | + match &mut nested { | |
| 1430 | + Type::Reference(r) => { | |
| 1431 | + *r.elem = rec(self, &r.elem)?; | |
| 1432 | + return Ok(nested); | |
| 1433 | + } | |
| 1434 | + Type::Slice(sl) => { | |
| 1435 | + *sl.elem = rec(self, &sl.elem)?; | |
| 1436 | + return Ok(nested); | |
| 1437 | + } | |
| 1438 | + Type::Array(a) => { | |
| 1439 | + *a.elem = rec(self, &a.elem)?; | |
| 1440 | + return Ok(nested); | |
| 1441 | + } | |
| 1442 | + Type::Ptr(pt) => { | |
| 1443 | + *pt.elem = rec(self, &pt.elem)?; | |
| 1444 | + return Ok(nested); | |
| 1445 | + } | |
| 1446 | + Type::Paren(pr) => { | |
| 1447 | + *pr.elem = rec(self, &pr.elem)?; | |
| 1448 | + return Ok(nested); | |
| 1449 | + } | |
| 1450 | + Type::Group(g) => { | |
| 1451 | + *g.elem = rec(self, &g.elem)?; | |
| 1452 | + return Ok(nested); | |
| 1453 | + } | |
| 1454 | + Type::Tuple(tu) => { | |
| 1455 | + for e in tu.elems.iter_mut() { | |
| 1456 | + *e = rec(self, e)?; | |
| 1457 | + } | |
| 1458 | + return Ok(nested); | |
| 1459 | + } | |
| 1460 | + _ => {} | |
| 1461 | + } | |
| 1409 | 1462 | let syn::Type::Path(p) = t else { return Ok(t.clone()) }; |
| 1410 | 1463 | // Only an unqualified name can be one of this file's aliases. |
| 1411 | 1464 | // `fmt::Result` and `core::result::Result` are different types that |
| @@ -1414,8 +1467,27 @@ impl Lowerer { | ||
| 1414 | 1467 | return Ok(t.clone()); |
| 1415 | 1468 | } |
| 1416 | 1469 | let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) }; |
| 1417 | - let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else { | |
| 1470 | + // A C type name is never expanded, even when the input declares it as | |
| 1471 | + // an alias. `type c_char = i8` is a stand-in for what libc declares, | |
| 1472 | + // and it is `c_char` that carries the ABI meaning: C's `char` and | |
| 1473 | + // `signed char` are distinct types, and a prototype written with the | |
| 1474 | + // wrong one does not match the real `strlen`. | |
| 1475 | + if ty::c_type(&seg.ident.to_string()).is_some() { | |
| 1418 | 1476 | return Ok(t.clone()); |
| 1477 | + } | |
| 1478 | + let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else { | |
| 1479 | + // Not an alias itself, but `Vec<Color>` still has one inside. | |
| 1480 | + let mut out = p.clone(); | |
| 1481 | + for seg in out.path.segments.iter_mut() { | |
| 1482 | + if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments { | |
| 1483 | + for g in a.args.iter_mut() { | |
| 1484 | + if let GenericArgument::Type(at) = g { | |
| 1485 | + *at = self.expand(at, depth + 1)?; | |
| 1486 | + } | |
| 1487 | + } | |
| 1488 | + } | |
| 1489 | + } | |
| 1490 | + return Ok(syn::Type::Path(out)); | |
| 1419 | 1491 | }; |
| 1420 | 1492 | let args: Vec<syn::Type> = match &seg.arguments { |
| 1421 | 1493 | syn::PathArguments::AngleBracketed(a) => a |
| @@ -7932,3 +8004,171 @@ fn contains_impl_trait(t: &syn::Type) -> bool { | ||
| 7932 | 8004 | _ => false, |
| 7933 | 8005 | } |
| 7934 | 8006 | } |
| 8007 | + | |
| 8008 | +// --------------------------------------------------------- crate namespacing | |
| 8009 | + | |
| 8010 | +/// The types a file declares, by name, including inside inline modules and | |
| 8011 | +/// the `const _: () = { .. }` blocks derive macros emit. | |
| 8012 | +fn declared_types(items: &[Item], out: &mut std::collections::HashSet<String>) { | |
| 8013 | + for item in items { | |
| 8014 | + match item { | |
| 8015 | + Item::Struct(s) => { | |
| 8016 | + out.insert(s.ident.to_string()); | |
| 8017 | + } | |
| 8018 | + Item::Enum(e) => { | |
| 8019 | + out.insert(e.ident.to_string()); | |
| 8020 | + } | |
| 8021 | + Item::Type(t) => { | |
| 8022 | + out.insert(t.ident.to_string()); | |
| 8023 | + } | |
| 8024 | + Item::Union(u) => { | |
| 8025 | + out.insert(u.ident.to_string()); | |
| 8026 | + } | |
| 8027 | + Item::Mod(m) => { | |
| 8028 | + if let Some((_, items)) = &m.content { | |
| 8029 | + declared_types(items, out); | |
| 8030 | + } | |
| 8031 | + } | |
| 8032 | + Item::Const(c) => { | |
| 8033 | + if let Some(items) = derive_item_block(c) { | |
| 8034 | + declared_types(&items, out); | |
| 8035 | + } | |
| 8036 | + } | |
| 8037 | + _ => {} | |
| 8038 | + } | |
| 8039 | + } | |
| 8040 | +} | |
| 8041 | + | |
| 8042 | +/// The crate each `use` brings a type in from: `use anstyle::Color;` says a | |
| 8043 | +/// bare `Color` in this file means `anstyle`'s. | |
| 8044 | +fn use_sources(items: &[Item], out: &mut HashMap<String, String>) { | |
| 8045 | + fn walk(prefix: &str, t: &syn::UseTree, out: &mut HashMap<String, String>) { | |
| 8046 | + match t { | |
| 8047 | + syn::UseTree::Path(p) => { | |
| 8048 | + // The first segment is the crate; deeper ones are its modules, | |
| 8049 | + // which do not change which crate the name comes from. | |
| 8050 | + let root = if prefix.is_empty() { p.ident.to_string() } else { prefix.to_string() }; | |
| 8051 | + walk(&root, &p.tree, out); | |
| 8052 | + } | |
| 8053 | + syn::UseTree::Name(n) => { | |
| 8054 | + let name = n.ident.to_string(); | |
| 8055 | + if !prefix.is_empty() && name.starts_with(char::is_uppercase) { | |
| 8056 | + out.insert(name, prefix.to_string()); | |
| 8057 | + } | |
| 8058 | + } | |
| 8059 | + syn::UseTree::Rename(r) => { | |
| 8060 | + let name = r.rename.to_string(); | |
| 8061 | + if !prefix.is_empty() && name.starts_with(char::is_uppercase) { | |
| 8062 | + out.insert(name, prefix.to_string()); | |
| 8063 | + } | |
| 8064 | + } | |
| 8065 | + syn::UseTree::Group(g) => { | |
| 8066 | + for t in &g.items { | |
| 8067 | + walk(prefix, t, out); | |
| 8068 | + } | |
| 8069 | + } | |
| 8070 | + syn::UseTree::Glob(_) => {} | |
| 8071 | + } | |
| 8072 | + } | |
| 8073 | + for item in items { | |
| 8074 | + match item { | |
| 8075 | + Item::Use(u) => walk("", &u.tree, out), | |
| 8076 | + Item::Mod(m) => { | |
| 8077 | + if let Some((_, items)) = &m.content { | |
| 8078 | + use_sources(items, out); | |
| 8079 | + } | |
| 8080 | + } | |
| 8081 | + _ => {} | |
| 8082 | + } | |
| 8083 | + } | |
| 8084 | +} | |
| 8085 | + | |
| 8086 | +/// A crate name as a Nim identifier prefix. | |
| 8087 | +fn crate_prefix(module: &str) -> String { | |
| 8088 | + let cleaned: String = module | |
| 8089 | + .chars() | |
| 8090 | + .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }) | |
| 8091 | + .collect(); | |
| 8092 | + cleaned.trim_matches('_').replace("__", "_") | |
| 8093 | +} | |
| 8094 | + | |
| 8095 | +/// Prefix every type name that more than one crate declares with the crate | |
| 8096 | +/// that declares it, and rewrite the uses to match. | |
| 8097 | +/// | |
| 8098 | +/// A name declared by exactly one crate is left alone: most names are, and an | |
| 8099 | +/// untouched name keeps the output readable and the diffs small. | |
| 8100 | +fn namespace_types(files: &mut [(String, syn::File)]) { | |
| 8101 | + let mut declared: Vec<std::collections::HashSet<String>> = Vec::new(); | |
| 8102 | + for (_, f) in files.iter() { | |
| 8103 | + let mut set = std::collections::HashSet::new(); | |
| 8104 | + declared_types(&f.items, &mut set); | |
| 8105 | + declared.push(set); | |
| 8106 | + } | |
| 8107 | + | |
| 8108 | + // Which crate(s) declare each name, and so which names clash. | |
| 8109 | + let mut owners: HashMap<&str, Vec<usize>> = HashMap::new(); | |
| 8110 | + for (i, set) in declared.iter().enumerate() { | |
| 8111 | + for n in set { | |
| 8112 | + owners.entry(n.as_str()).or_default().push(i); | |
| 8113 | + } | |
| 8114 | + } | |
| 8115 | + let clashing: std::collections::HashSet<String> = owners | |
| 8116 | + .iter() | |
| 8117 | + .filter(|(_, v)| v.len() > 1) | |
| 8118 | + .map(|(k, _)| (*k).to_string()) | |
| 8119 | + .collect(); | |
| 8120 | + if clashing.is_empty() { | |
| 8121 | + return; | |
| 8122 | + } | |
| 8123 | + | |
| 8124 | + // The first file is the root module and has no name of its own, so its | |
| 8125 | + // declarations keep theirs; everything else is prefixed. | |
| 8126 | + let prefixes: Vec<String> = files | |
| 8127 | + .iter() | |
| 8128 | + .map(|(m, _)| crate_prefix(m)) | |
| 8129 | + .collect(); | |
| 8130 | + let by_prefix: HashMap<String, usize> = prefixes | |
| 8131 | + .iter() | |
| 8132 | + .enumerate() | |
| 8133 | + .map(|(i, p)| (p.clone(), i)) | |
| 8134 | + .collect(); | |
| 8135 | + | |
| 8136 | + for (i, (_, f)) in files.iter_mut().enumerate() { | |
| 8137 | + if prefixes[i].is_empty() { | |
| 8138 | + continue; | |
| 8139 | + } | |
| 8140 | + let mut renames: HashMap<String, String> = HashMap::new(); | |
| 8141 | + for n in &declared[i] { | |
| 8142 | + if clashing.contains(n) { | |
| 8143 | + renames.insert(n.clone(), format!("{}_{}", prefixes[i], n)); | |
| 8144 | + } | |
| 8145 | + } | |
| 8146 | + // Names this file does not declare but brings in from a crate that | |
| 8147 | + // does -- those have been renamed over there. | |
| 8148 | + let mut uses = HashMap::new(); | |
| 8149 | + use_sources(&f.items, &mut uses); | |
| 8150 | + for (name, from) in uses { | |
| 8151 | + if !clashing.contains(&name) || declared[i].contains(&name) { | |
| 8152 | + continue; | |
| 8153 | + } | |
| 8154 | + let from = crate_prefix(&from); | |
| 8155 | + match by_prefix.get(&from) { | |
| 8156 | + Some(&j) if declared[j].contains(&name) && !prefixes[j].is_empty() => { | |
| 8157 | + let to = format!("{}_{}", prefixes[j], name); | |
| 8158 | + renames.insert(name, to); | |
| 8159 | + } | |
| 8160 | + _ => {} | |
| 8161 | + } | |
| 8162 | + } | |
| 8163 | + if renames.is_empty() { | |
| 8164 | + continue; | |
| 8165 | + } | |
| 8166 | + let mut ts = quote::quote!(#f); | |
| 8167 | + for (from, to) in &renames { | |
| 8168 | + ts = rename_ident(ts, from, to); | |
| 8169 | + } | |
| 8170 | + if let Ok(parsed) = syn::parse2::<syn::File>(ts) { | |
| 8171 | + *f = parsed; | |
| 8172 | + } | |
| 8173 | + } | |
| 8174 | +} | |
| @@ -504,6 +504,21 @@ impl Lowerer { | |||
| 504 | // ---------------------------------------------------------------- file | 504 | // ---------------------------------------------------------------- file |
| 505 | 505 | ||
| 506 | pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> { | 506 | pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> { |
| 507 | + // Pass -2: give each crate's types a namespace. | ||
| 508 | + // | ||
| 509 | + // Every input file's items end up in one Nim module, which is what | ||
| 510 | + // makes cross-file calls work without modelling Rust's module system. | ||
| 511 | + // It also means two crates that each declare a `Color` declare the | ||
| 512 | + // same one. In a dependency graph that is not an edge case: of 14,714 | ||
| 513 | + // distinct type names across libcosmic's 356 dependencies, 1,575 are | ||
| 514 | + // declared in more than one place. | ||
| 515 | + // | ||
| 516 | + // So a name declared by more than one crate is prefixed with the | ||
| 517 | + // crate that declares it, and the uses are rewritten to match: inside | ||
| 518 | + // that crate, and inside any crate that `use`s it from there. | ||
| 519 | + let mut files = files.to_vec(); | ||
| 520 | + namespace_types(&mut files); | ||
| 521 | + | ||
| 507 | // Pass -1: give every function-local type a name of its own. | 522 | // Pass -1: give every function-local type a name of its own. |
| 508 | // | 523 | // |
| 509 | // Rust scopes a `struct`/`enum` declared in a function body to that | 524 | // Rust scopes a `struct`/`enum` declared in a function body to that |
| @@ -511,7 +526,6 @@ impl Lowerer { | |||
| 511 | // things -- every `serde` `deserialize` impl does exactly that. Nim has | 526 | // things -- every `serde` `deserialize` impl does exactly that. Nim has |
| 512 | // no such scope. Renaming the clashing ones inside their own body is an | 527 | // no such scope. Renaming the clashing ones inside their own body is an |
| 513 | // exact rewrite, because the body is the whole of the name's scope. | 528 | // exact rewrite, because the body is the whole of the name's scope. |
| 514 | - let mut files = files.to_vec(); | ||
| 515 | let mut seen: HashMap<String, String> = HashMap::new(); | 529 | let mut seen: HashMap<String, String> = HashMap::new(); |
| 516 | let mut n = 0usize; | 530 | let mut n = 0usize; |
| 517 | for (_, f) in files.iter_mut() { | 531 | for (_, f) in files.iter_mut() { |
| @@ -1406,6 +1420,45 @@ impl Lowerer { | |||
| 1406 | if depth > 16 { | 1420 | if depth > 16 { |
| 1407 | return Err("type alias expansion did not terminate; is it cyclic?".into()); | 1421 | return Err("type alias expansion did not terminate; is it cyclic?".into()); |
| 1408 | } | 1422 | } |
| 1423 | + // An alias can sit anywhere inside a type -- `&Color`, `Vec<Color>`, | ||
| 1424 | + // `(Color, u8)` -- so the whole structure is walked, not just its | ||
| 1425 | + // outermost node. | ||
| 1426 | + use syn::Type; | ||
| 1427 | + let mut nested = t.clone(); | ||
| 1428 | + let rec = |me: &Self, e: &syn::Type| me.expand(e, depth + 1); | ||
| 1429 | + match &mut nested { | ||
| 1430 | + Type::Reference(r) => { | ||
| 1431 | + *r.elem = rec(self, &r.elem)?; | ||
| 1432 | + return Ok(nested); | ||
| 1433 | + } | ||
| 1434 | + Type::Slice(sl) => { | ||
| 1435 | + *sl.elem = rec(self, &sl.elem)?; | ||
| 1436 | + return Ok(nested); | ||
| 1437 | + } | ||
| 1438 | + Type::Array(a) => { | ||
| 1439 | + *a.elem = rec(self, &a.elem)?; | ||
| 1440 | + return Ok(nested); | ||
| 1441 | + } | ||
| 1442 | + Type::Ptr(pt) => { | ||
| 1443 | + *pt.elem = rec(self, &pt.elem)?; | ||
| 1444 | + return Ok(nested); | ||
| 1445 | + } | ||
| 1446 | + Type::Paren(pr) => { | ||
| 1447 | + *pr.elem = rec(self, &pr.elem)?; | ||
| 1448 | + return Ok(nested); | ||
| 1449 | + } | ||
| 1450 | + Type::Group(g) => { | ||
| 1451 | + *g.elem = rec(self, &g.elem)?; | ||
| 1452 | + return Ok(nested); | ||
| 1453 | + } | ||
| 1454 | + Type::Tuple(tu) => { | ||
| 1455 | + for e in tu.elems.iter_mut() { | ||
| 1456 | + *e = rec(self, e)?; | ||
| 1457 | + } | ||
| 1458 | + return Ok(nested); | ||
| 1459 | + } | ||
| 1460 | + _ => {} | ||
| 1461 | + } | ||
| 1409 | let syn::Type::Path(p) = t else { return Ok(t.clone()) }; | 1462 | let syn::Type::Path(p) = t else { return Ok(t.clone()) }; |
| 1410 | // Only an unqualified name can be one of this file's aliases. | 1463 | // Only an unqualified name can be one of this file's aliases. |
| 1411 | // `fmt::Result` and `core::result::Result` are different types that | 1464 | // `fmt::Result` and `core::result::Result` are different types that |
| @@ -1414,8 +1467,27 @@ impl Lowerer { | |||
| 1414 | return Ok(t.clone()); | 1467 | return Ok(t.clone()); |
| 1415 | } | 1468 | } |
| 1416 | let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) }; | 1469 | let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) }; |
| 1417 | - let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else { | 1470 | + // A C type name is never expanded, even when the input declares it as |
| 1471 | + // an alias. `type c_char = i8` is a stand-in for what libc declares, | ||
| 1472 | + // and it is `c_char` that carries the ABI meaning: C's `char` and | ||
| 1473 | + // `signed char` are distinct types, and a prototype written with the | ||
| 1474 | + // wrong one does not match the real `strlen`. | ||
| 1475 | + if ty::c_type(&seg.ident.to_string()).is_some() { | ||
| 1418 | return Ok(t.clone()); | 1476 | return Ok(t.clone()); |
| 1477 | + } | ||
| 1478 | + let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else { | ||
| 1479 | + // Not an alias itself, but `Vec<Color>` still has one inside. | ||
| 1480 | + let mut out = p.clone(); | ||
| 1481 | + for seg in out.path.segments.iter_mut() { | ||
| 1482 | + if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments { | ||
| 1483 | + for g in a.args.iter_mut() { | ||
| 1484 | + if let GenericArgument::Type(at) = g { | ||
| 1485 | + *at = self.expand(at, depth + 1)?; | ||
| 1486 | + } | ||
| 1487 | + } | ||
| 1488 | + } | ||
| 1489 | + } | ||
| 1490 | + return Ok(syn::Type::Path(out)); | ||
| 1419 | }; | 1491 | }; |
| 1420 | let args: Vec<syn::Type> = match &seg.arguments { | 1492 | let args: Vec<syn::Type> = match &seg.arguments { |
| 1421 | syn::PathArguments::AngleBracketed(a) => a | 1493 | syn::PathArguments::AngleBracketed(a) => a |
| @@ -7932,3 +8004,171 @@ fn contains_impl_trait(t: &syn::Type) -> bool { | |||
| 7932 | _ => false, | 8004 | _ => false, |
| 7933 | } | 8005 | } |
| 7934 | } | 8006 | } |
| 8007 | + | ||
| 8008 | +// --------------------------------------------------------- crate namespacing | ||
| 8009 | + | ||
| 8010 | +/// The types a file declares, by name, including inside inline modules and | ||
| 8011 | +/// the `const _: () = { .. }` blocks derive macros emit. | ||
| 8012 | +fn declared_types(items: &[Item], out: &mut std::collections::HashSet<String>) { | ||
| 8013 | + for item in items { | ||
| 8014 | + match item { | ||
| 8015 | + Item::Struct(s) => { | ||
| 8016 | + out.insert(s.ident.to_string()); | ||
| 8017 | + } | ||
| 8018 | + Item::Enum(e) => { | ||
| 8019 | + out.insert(e.ident.to_string()); | ||
| 8020 | + } | ||
| 8021 | + Item::Type(t) => { | ||
| 8022 | + out.insert(t.ident.to_string()); | ||
| 8023 | + } | ||
| 8024 | + Item::Union(u) => { | ||
| 8025 | + out.insert(u.ident.to_string()); | ||
| 8026 | + } | ||
| 8027 | + Item::Mod(m) => { | ||
| 8028 | + if let Some((_, items)) = &m.content { | ||
| 8029 | + declared_types(items, out); | ||
| 8030 | + } | ||
| 8031 | + } | ||
| 8032 | + Item::Const(c) => { | ||
| 8033 | + if let Some(items) = derive_item_block(c) { | ||
| 8034 | + declared_types(&items, out); | ||
| 8035 | + } | ||
| 8036 | + } | ||
| 8037 | + _ => {} | ||
| 8038 | + } | ||
| 8039 | + } | ||
| 8040 | +} | ||
| 8041 | + | ||
| 8042 | +/// The crate each `use` brings a type in from: `use anstyle::Color;` says a | ||
| 8043 | +/// bare `Color` in this file means `anstyle`'s. | ||
| 8044 | +fn use_sources(items: &[Item], out: &mut HashMap<String, String>) { | ||
| 8045 | + fn walk(prefix: &str, t: &syn::UseTree, out: &mut HashMap<String, String>) { | ||
| 8046 | + match t { | ||
| 8047 | + syn::UseTree::Path(p) => { | ||
| 8048 | + // The first segment is the crate; deeper ones are its modules, | ||
| 8049 | + // which do not change which crate the name comes from. | ||
| 8050 | + let root = if prefix.is_empty() { p.ident.to_string() } else { prefix.to_string() }; | ||
| 8051 | + walk(&root, &p.tree, out); | ||
| 8052 | + } | ||
| 8053 | + syn::UseTree::Name(n) => { | ||
| 8054 | + let name = n.ident.to_string(); | ||
| 8055 | + if !prefix.is_empty() && name.starts_with(char::is_uppercase) { | ||
| 8056 | + out.insert(name, prefix.to_string()); | ||
| 8057 | + } | ||
| 8058 | + } | ||
| 8059 | + syn::UseTree::Rename(r) => { | ||
| 8060 | + let name = r.rename.to_string(); | ||
| 8061 | + if !prefix.is_empty() && name.starts_with(char::is_uppercase) { | ||
| 8062 | + out.insert(name, prefix.to_string()); | ||
| 8063 | + } | ||
| 8064 | + } | ||
| 8065 | + syn::UseTree::Group(g) => { | ||
| 8066 | + for t in &g.items { | ||
| 8067 | + walk(prefix, t, out); | ||
| 8068 | + } | ||
| 8069 | + } | ||
| 8070 | + syn::UseTree::Glob(_) => {} | ||
| 8071 | + } | ||
| 8072 | + } | ||
| 8073 | + for item in items { | ||
| 8074 | + match item { | ||
| 8075 | + Item::Use(u) => walk("", &u.tree, out), | ||
| 8076 | + Item::Mod(m) => { | ||
| 8077 | + if let Some((_, items)) = &m.content { | ||
| 8078 | + use_sources(items, out); | ||
| 8079 | + } | ||
| 8080 | + } | ||
| 8081 | + _ => {} | ||
| 8082 | + } | ||
| 8083 | + } | ||
| 8084 | +} | ||
| 8085 | + | ||
| 8086 | +/// A crate name as a Nim identifier prefix. | ||
| 8087 | +fn crate_prefix(module: &str) -> String { | ||
| 8088 | + let cleaned: String = module | ||
| 8089 | + .chars() | ||
| 8090 | + .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }) | ||
| 8091 | + .collect(); | ||
| 8092 | + cleaned.trim_matches('_').replace("__", "_") | ||
| 8093 | +} | ||
| 8094 | + | ||
| 8095 | +/// Prefix every type name that more than one crate declares with the crate | ||
| 8096 | +/// that declares it, and rewrite the uses to match. | ||
| 8097 | +/// | ||
| 8098 | +/// A name declared by exactly one crate is left alone: most names are, and an | ||
| 8099 | +/// untouched name keeps the output readable and the diffs small. | ||
| 8100 | +fn namespace_types(files: &mut [(String, syn::File)]) { | ||
| 8101 | + let mut declared: Vec<std::collections::HashSet<String>> = Vec::new(); | ||
| 8102 | + for (_, f) in files.iter() { | ||
| 8103 | + let mut set = std::collections::HashSet::new(); | ||
| 8104 | + declared_types(&f.items, &mut set); | ||
| 8105 | + declared.push(set); | ||
| 8106 | + } | ||
| 8107 | + | ||
| 8108 | + // Which crate(s) declare each name, and so which names clash. | ||
| 8109 | + let mut owners: HashMap<&str, Vec<usize>> = HashMap::new(); | ||
| 8110 | + for (i, set) in declared.iter().enumerate() { | ||
| 8111 | + for n in set { | ||
| 8112 | + owners.entry(n.as_str()).or_default().push(i); | ||
| 8113 | + } | ||
| 8114 | + } | ||
| 8115 | + let clashing: std::collections::HashSet<String> = owners | ||
| 8116 | + .iter() | ||
| 8117 | + .filter(|(_, v)| v.len() > 1) | ||
| 8118 | + .map(|(k, _)| (*k).to_string()) | ||
| 8119 | + .collect(); | ||
| 8120 | + if clashing.is_empty() { | ||
| 8121 | + return; | ||
| 8122 | + } | ||
| 8123 | + | ||
| 8124 | + // The first file is the root module and has no name of its own, so its | ||
| 8125 | + // declarations keep theirs; everything else is prefixed. | ||
| 8126 | + let prefixes: Vec<String> = files | ||
| 8127 | + .iter() | ||
| 8128 | + .map(|(m, _)| crate_prefix(m)) | ||
| 8129 | + .collect(); | ||
| 8130 | + let by_prefix: HashMap<String, usize> = prefixes | ||
| 8131 | + .iter() | ||
| 8132 | + .enumerate() | ||
| 8133 | + .map(|(i, p)| (p.clone(), i)) | ||
| 8134 | + .collect(); | ||
| 8135 | + | ||
| 8136 | + for (i, (_, f)) in files.iter_mut().enumerate() { | ||
| 8137 | + if prefixes[i].is_empty() { | ||
| 8138 | + continue; | ||
| 8139 | + } | ||
| 8140 | + let mut renames: HashMap<String, String> = HashMap::new(); | ||
| 8141 | + for n in &declared[i] { | ||
| 8142 | + if clashing.contains(n) { | ||
| 8143 | + renames.insert(n.clone(), format!("{}_{}", prefixes[i], n)); | ||
| 8144 | + } | ||
| 8145 | + } | ||
| 8146 | + // Names this file does not declare but brings in from a crate that | ||
| 8147 | + // does -- those have been renamed over there. | ||
| 8148 | + let mut uses = HashMap::new(); | ||
| 8149 | + use_sources(&f.items, &mut uses); | ||
| 8150 | + for (name, from) in uses { | ||
| 8151 | + if !clashing.contains(&name) || declared[i].contains(&name) { | ||
| 8152 | + continue; | ||
| 8153 | + } | ||
| 8154 | + let from = crate_prefix(&from); | ||
| 8155 | + match by_prefix.get(&from) { | ||
| 8156 | + Some(&j) if declared[j].contains(&name) && !prefixes[j].is_empty() => { | ||
| 8157 | + let to = format!("{}_{}", prefixes[j], name); | ||
| 8158 | + renames.insert(name, to); | ||
| 8159 | + } | ||
| 8160 | + _ => {} | ||
| 8161 | + } | ||
| 8162 | + } | ||
| 8163 | + if renames.is_empty() { | ||
| 8164 | + continue; | ||
| 8165 | + } | ||
| 8166 | + let mut ts = quote::quote!(#f); | ||
| 8167 | + for (from, to) in &renames { | ||
| 8168 | + ts = rename_ident(ts, from, to); | ||
| 8169 | + } | ||
| 8170 | + if let Ok(parsed) = syn::parse2::<syn::File>(ts) { | ||
| 8171 | + *f = parsed; | ||
| 8172 | + } | ||
| 8173 | + } | ||
| 8174 | +} | ||
added
tests/cases/058-crate-namespacing/main.rs +13 -0 | new file mode 100644 | ||
| @@ -0,0 +1,13 @@ | ||
| 1 | +// Two dependency crates each declaring `Color`. In Rust they are | |
| 2 | +// `palette::Color` and `quantize::Color`; flattened into one Nim module they | |
| 3 | +// would be the same name, and one would silently replace the other. | |
| 4 | +mod palette; | |
| 5 | +mod quantize; | |
| 6 | + | |
| 7 | +use palette::Color; | |
| 8 | + | |
| 9 | +fn main() { | |
| 10 | + println!("{}", palette::name(&Color::Red)); | |
| 11 | + println!("{}", palette::name(&Color::Blue)); | |
| 12 | + println!("{}", quantize::weight(&quantize::Quad { v: 7 })); | |
| 13 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | +// Two dependency crates each declaring `Color`. In Rust they are | ||
| 2 | +// `palette::Color` and `quantize::Color`; flattened into one Nim module they | ||
| 3 | +// would be the same name, and one would silently replace the other. | ||
| 4 | +mod palette; | ||
| 5 | +mod quantize; | ||
| 6 | + | ||
| 7 | +use palette::Color; | ||
| 8 | + | ||
| 9 | +fn main() { | ||
| 10 | + println!("{}", palette::name(&Color::Red)); | ||
| 11 | + println!("{}", palette::name(&Color::Blue)); | ||
| 12 | + println!("{}", quantize::weight(&quantize::Quad { v: 7 })); | ||
| 13 | +} | ||
added
tests/cases/058-crate-namespacing/palette.rs +12 -0 | new file mode 100644 | ||
| @@ -0,0 +1,12 @@ | ||
| 1 | +// One crate's `Color`. | |
| 2 | +pub enum Color { | |
| 3 | + Red, | |
| 4 | + Blue, | |
| 5 | +} | |
| 6 | + | |
| 7 | +pub fn name(c: &Color) -> String { | |
| 8 | + match c { | |
| 9 | + Color::Red => String::from("red"), | |
| 10 | + Color::Blue => String::from("blue"), | |
| 11 | + } | |
| 12 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | +// One crate's `Color`. | ||
| 2 | +pub enum Color { | ||
| 3 | + Red, | ||
| 4 | + Blue, | ||
| 5 | +} | ||
| 6 | + | ||
| 7 | +pub fn name(c: &Color) -> String { | ||
| 8 | + match c { | ||
| 9 | + Color::Red => String::from("red"), | ||
| 10 | + Color::Blue => String::from("blue"), | ||
| 11 | + } | ||
| 12 | +} | ||
added
tests/cases/058-crate-namespacing/quantize.rs +10 -0 | new file mode 100644 | ||
| @@ -0,0 +1,10 @@ | ||
| 1 | +// A different crate's `Color`, same name, different thing entirely. | |
| 2 | +pub struct Quad { | |
| 3 | + pub v: i32, | |
| 4 | +} | |
| 5 | + | |
| 6 | +pub type Color = Quad; | |
| 7 | + | |
| 8 | +pub fn weight(c: &Color) -> i32 { | |
| 9 | + c.v * 3 | |
| 10 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | +// A different crate's `Color`, same name, different thing entirely. | ||
| 2 | +pub struct Quad { | ||
| 3 | + pub v: i32, | ||
| 4 | +} | ||
| 5 | + | ||
| 6 | +pub type Color = Quad; | ||
| 7 | + | ||
| 8 | +pub fn weight(c: &Color) -> i32 { | ||
| 9 | + c.v * 3 | ||
| 10 | +} | ||