nandi/rustnimpublic Fork
⑂ 406427b
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 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>
nandithebull committed 2026-09-19T17:58:43-07:00 Browse files
406427b parent: d553b29
modified src/lower.rs +242 -2
@@ -504,6 +504,21 @@ impl Lowerer {
504504 // ---------------------------------------------------------------- file
505505
506506 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+
507522 // Pass -1: give every function-local type a name of its own.
508523 //
509524 // Rust scopes a `struct`/`enum` declared in a function body to that
@@ -511,7 +526,6 @@ impl Lowerer {
511526 // things -- every `serde` `deserialize` impl does exactly that. Nim has
512527 // no such scope. Renaming the clashing ones inside their own body is an
513528 // exact rewrite, because the body is the whole of the name's scope.
514- let mut files = files.to_vec();
515529 let mut seen: HashMap<String, String> = HashMap::new();
516530 let mut n = 0usize;
517531 for (_, f) in files.iter_mut() {
@@ -1406,6 +1420,45 @@ impl Lowerer {
14061420 if depth > 16 {
14071421 return Err("type alias expansion did not terminate; is it cyclic?".into());
14081422 }
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+ }
14091462 let syn::Type::Path(p) = t else { return Ok(t.clone()) };
14101463 // Only an unqualified name can be one of this file's aliases.
14111464 // `fmt::Result` and `core::result::Result` are different types that
@@ -1414,8 +1467,27 @@ impl Lowerer {
14141467 return Ok(t.clone());
14151468 }
14161469 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() {
14181476 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));
14191491 };
14201492 let args: Vec<syn::Type> = match &seg.arguments {
14211493 syn::PathArguments::AngleBracketed(a) => a
@@ -7932,3 +8004,171 @@ fn contains_impl_trait(t: &syn::Type) -> bool {
79328004 _ => false,
79338005 }
79348006 }
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 // ---------------------------------------------------------------- file504 // ---------------------------------------------------------------- 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 that524 // 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 has526 // things -- every `serde` `deserialize` impl does exactly that. Nim has
512 // no such scope. Renaming the clashing ones inside their own body is an527 // 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 that1464 // `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) => a1493 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+}