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

Rename types through the AST, not the token stream

The token-level rename was too blunt, and the full dependency run showed
exactly how: `Iterator<Item = T>` became `Iterator<swash_Item = T>`,
`Deref<Target = X>` became `Deref<zbus_Target = X>`, and `A::Adapter` became
`A::wgpu_Adapter`. `Item`, `Target`, `Output` and `Error` are all names crates
declare *and* names of associated types -- different things that happen to be
spelled alike, which is a distinction an AST has and a token stream does not.

A `VisitMut` now renames only where a type name of this crate can appear: the
declarations themselves, and paths whose type segment is not qualified by
another type. An associated type's name, an `impl` block's `type Output`, and
a field's name are left alone; only their values are visited.

Token renaming stays where it is still right -- function-local types, where
the body is the entire scope and every spelling of the name inside it does
mean that type.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-19T18:45:19-07:00 Browse files
8204755 parent: 113d857
modified Cargo.toml +1 -1
@@ -6,7 +6,7 @@ edition = "2024"
66 [dependencies]
77 quote = "1"
88 proc-macro2 = "1"
9-syn = { version = "3.0.5", features = ["full", "extra-traits", "visit"] }
9+syn = { version = "3.0.5", features = ["full", "extra-traits", "visit", "visit-mut"] }
1010
1111 # Used only by the test corpus: a case carrying `//@ extern: bitflags` is
1212 # compiled by the oracle against the real crate, so the `bitflags!` lowering is
@@ -6,7 +6,7 @@ edition = "2024"
6 [dependencies]6 [dependencies]
7 quote = "1"7 quote = "1"
8 proc-macro2 = "1"8 proc-macro2 = "1"
9-syn = { version = "3.0.5", features = ["full", "extra-traits", "visit"] }9+syn = { version = "3.0.5", features = ["full", "extra-traits", "visit", "visit-mut"] }
10 10
11 # Used only by the test corpus: a case carrying `//@ extern: bitflags` is11 # Used only by the test corpus: a case carrying `//@ extern: bitflags` is
12 # compiled by the oracle against the real crate, so the `bitflags!` lowering is12 # compiled by the oracle against the real crate, so the `bitflags!` lowering is
modified src/lower.rs +97 -5
@@ -8184,12 +8184,104 @@ fn namespace_types(files: &mut [(String, syn::File)]) {
81848184 if renames.is_empty() {
81858185 continue;
81868186 }
8187- let mut ts = quote::quote!(#f);
8188- for (from, to) in &renames {
8189- ts = rename_ident(ts, from, to);
8187+ syn::visit_mut::VisitMut::visit_file_mut(&mut Renamer { renames: &renames }, f);
8188+ }
8189+}
8190+
8191+/// Rename type names across a crate, in the places a *type* name can appear.
8192+///
8193+/// Deliberately not a token substitution. `Item`, `Target`, `Output` and
8194+/// `Error` are all names crates declare and also names of associated types, so
8195+/// rewriting every token spelled `Item` turns `Iterator<Item = T>` into
8196+/// `Iterator<swash_Item = T>` and `A::Adapter` into `A::wgpu_Adapter`. Those
8197+/// are different things that happen to be spelled alike, which is exactly the
8198+/// distinction an AST has and a token stream does not.
8199+struct Renamer<'a> {
8200+ renames: &'a HashMap<String, String>,
8201+}
8202+
8203+impl Renamer<'_> {
8204+ fn rename(&self, id: &mut proc_macro2::Ident) {
8205+ if let Some(to) = self.renames.get(&id.to_string()) {
8206+ *id = proc_macro2::Ident::new(to, id.span());
81908207 }
8191- if let Ok(parsed) = syn::parse2::<syn::File>(ts) {
8192- *f = parsed;
8208+ }
8209+
8210+ /// The first segment of a path, or the last when the path is qualified by
8211+ /// a module. Anything after a segment that is not the crate root is an
8212+ /// associated item and belongs to whatever the qualifier resolves to, not
8213+ /// to this crate's namespace.
8214+ fn rename_path(&self, p: &mut syn::Path) {
8215+ let n = p.segments.len();
8216+ if n == 0 {
8217+ return;
8218+ }
8219+ if n == 1 {
8220+ self.rename(&mut p.segments[0].ident);
8221+ return;
8222+ }
8223+ // `module::Type` and `module::Type::CONST` -- rename the segment that
8224+ // names a type, never one that follows it.
8225+ for i in 0..n {
8226+ let seg = &p.segments[i];
8227+ let is_type = seg.ident.to_string().starts_with(char::is_uppercase);
8228+ if is_type {
8229+ // Only if what precedes it is lowercase (a module path). A
8230+ // preceding uppercase segment means this is an associated
8231+ // item: `A::Adapter`, `Self::Output`.
8232+ let qualified_by_type = i > 0
8233+ && p.segments[i - 1]
8234+ .ident
8235+ .to_string()
8236+ .starts_with(char::is_uppercase);
8237+ if !qualified_by_type {
8238+ let mut id = seg.ident.clone();
8239+ self.rename(&mut id);
8240+ p.segments[i].ident = id;
8241+ }
8242+ break;
8243+ }
81938244 }
81948245 }
81958246 }
8247+
8248+impl syn::visit_mut::VisitMut for Renamer<'_> {
8249+ fn visit_item_struct_mut(&mut self, i: &mut syn::ItemStruct) {
8250+ self.rename(&mut i.ident);
8251+ syn::visit_mut::visit_item_struct_mut(self, i);
8252+ }
8253+ fn visit_item_enum_mut(&mut self, i: &mut syn::ItemEnum) {
8254+ self.rename(&mut i.ident);
8255+ syn::visit_mut::visit_item_enum_mut(self, i);
8256+ }
8257+ fn visit_item_type_mut(&mut self, i: &mut syn::ItemType) {
8258+ self.rename(&mut i.ident);
8259+ syn::visit_mut::visit_item_type_mut(self, i);
8260+ }
8261+ fn visit_item_union_mut(&mut self, i: &mut syn::ItemUnion) {
8262+ self.rename(&mut i.ident);
8263+ syn::visit_mut::visit_item_union_mut(self, i);
8264+ }
8265+ fn visit_path_mut(&mut self, p: &mut syn::Path) {
8266+ self.rename_path(p);
8267+ syn::visit_mut::visit_path_mut(self, p);
8268+ }
8269+ /// `Iterator<Item = T>`: `Item` names an associated type of `Iterator`,
8270+ /// not a type in this crate. Only the value is visited.
8271+ fn visit_assoc_type_mut(&mut self, a: &mut syn::AssocType) {
8272+ syn::visit_mut::visit_type_mut(self, &mut a.ty);
8273+ }
8274+ fn visit_assoc_const_mut(&mut self, a: &mut syn::AssocConst) {
8275+ syn::visit_mut::visit_expr_mut(self, &mut a.value);
8276+ }
8277+ /// An `impl` block's own associated items keep their names: `type Output`
8278+ /// in `impl Add for X` is the trait's name for it.
8279+ fn visit_impl_item_type_mut(&mut self, i: &mut syn::ImplItemType) {
8280+ syn::visit_mut::visit_type_mut(self, &mut i.ty);
8281+ }
8282+ fn visit_trait_item_type_mut(&mut self, _: &mut syn::TraitItemType) {}
8283+ /// A field or variant named like a type is still a field or a variant.
8284+ fn visit_field_mut(&mut self, f: &mut syn::Field) {
8285+ syn::visit_mut::visit_type_mut(self, &mut f.ty);
8286+ }
8287+}
@@ -8184,12 +8184,104 @@ fn namespace_types(files: &mut [(String, syn::File)]) {
8184 if renames.is_empty() {8184 if renames.is_empty() {
8185 continue;8185 continue;
8186 }8186 }
8187- let mut ts = quote::quote!(#f);8187+ syn::visit_mut::VisitMut::visit_file_mut(&mut Renamer { renames: &renames }, f);
8188- for (from, to) in &renames {8188+ }
8189- ts = rename_ident(ts, from, to);8189+}
8190+
8191+/// Rename type names across a crate, in the places a *type* name can appear.
8192+///
8193+/// Deliberately not a token substitution. `Item`, `Target`, `Output` and
8194+/// `Error` are all names crates declare and also names of associated types, so
8195+/// rewriting every token spelled `Item` turns `Iterator<Item = T>` into
8196+/// `Iterator<swash_Item = T>` and `A::Adapter` into `A::wgpu_Adapter`. Those
8197+/// are different things that happen to be spelled alike, which is exactly the
8198+/// distinction an AST has and a token stream does not.
8199+struct Renamer<'a> {
8200+ renames: &'a HashMap<String, String>,
8201+}
8202+
8203+impl Renamer<'_> {
8204+ fn rename(&self, id: &mut proc_macro2::Ident) {
8205+ if let Some(to) = self.renames.get(&id.to_string()) {
8206+ *id = proc_macro2::Ident::new(to, id.span());
8190 }8207 }
8191- if let Ok(parsed) = syn::parse2::<syn::File>(ts) {8208+ }
8192- *f = parsed;8209+
8210+ /// The first segment of a path, or the last when the path is qualified by
8211+ /// a module. Anything after a segment that is not the crate root is an
8212+ /// associated item and belongs to whatever the qualifier resolves to, not
8213+ /// to this crate's namespace.
8214+ fn rename_path(&self, p: &mut syn::Path) {
8215+ let n = p.segments.len();
8216+ if n == 0 {
8217+ return;
8218+ }
8219+ if n == 1 {
8220+ self.rename(&mut p.segments[0].ident);
8221+ return;
8222+ }
8223+ // `module::Type` and `module::Type::CONST` -- rename the segment that
8224+ // names a type, never one that follows it.
8225+ for i in 0..n {
8226+ let seg = &p.segments[i];
8227+ let is_type = seg.ident.to_string().starts_with(char::is_uppercase);
8228+ if is_type {
8229+ // Only if what precedes it is lowercase (a module path). A
8230+ // preceding uppercase segment means this is an associated
8231+ // item: `A::Adapter`, `Self::Output`.
8232+ let qualified_by_type = i > 0
8233+ && p.segments[i - 1]
8234+ .ident
8235+ .to_string()
8236+ .starts_with(char::is_uppercase);
8237+ if !qualified_by_type {
8238+ let mut id = seg.ident.clone();
8239+ self.rename(&mut id);
8240+ p.segments[i].ident = id;
8241+ }
8242+ break;
8243+ }
8193 }8244 }
8194 }8245 }
8195 }8246 }
8247+
8248+impl syn::visit_mut::VisitMut for Renamer<'_> {
8249+ fn visit_item_struct_mut(&mut self, i: &mut syn::ItemStruct) {
8250+ self.rename(&mut i.ident);
8251+ syn::visit_mut::visit_item_struct_mut(self, i);
8252+ }
8253+ fn visit_item_enum_mut(&mut self, i: &mut syn::ItemEnum) {
8254+ self.rename(&mut i.ident);
8255+ syn::visit_mut::visit_item_enum_mut(self, i);
8256+ }
8257+ fn visit_item_type_mut(&mut self, i: &mut syn::ItemType) {
8258+ self.rename(&mut i.ident);
8259+ syn::visit_mut::visit_item_type_mut(self, i);
8260+ }
8261+ fn visit_item_union_mut(&mut self, i: &mut syn::ItemUnion) {
8262+ self.rename(&mut i.ident);
8263+ syn::visit_mut::visit_item_union_mut(self, i);
8264+ }
8265+ fn visit_path_mut(&mut self, p: &mut syn::Path) {
8266+ self.rename_path(p);
8267+ syn::visit_mut::visit_path_mut(self, p);
8268+ }
8269+ /// `Iterator<Item = T>`: `Item` names an associated type of `Iterator`,
8270+ /// not a type in this crate. Only the value is visited.
8271+ fn visit_assoc_type_mut(&mut self, a: &mut syn::AssocType) {
8272+ syn::visit_mut::visit_type_mut(self, &mut a.ty);
8273+ }
8274+ fn visit_assoc_const_mut(&mut self, a: &mut syn::AssocConst) {
8275+ syn::visit_mut::visit_expr_mut(self, &mut a.value);
8276+ }
8277+ /// An `impl` block's own associated items keep their names: `type Output`
8278+ /// in `impl Add for X` is the trait's name for it.
8279+ fn visit_impl_item_type_mut(&mut self, i: &mut syn::ImplItemType) {
8280+ syn::visit_mut::visit_type_mut(self, &mut i.ty);
8281+ }
8282+ fn visit_trait_item_type_mut(&mut self, _: &mut syn::TraitItemType) {}
8283+ /// A field or variant named like a type is still a field or a variant.
8284+ fn visit_field_mut(&mut self, f: &mut syn::Field) {
8285+ syn::visit_mut::visit_type_mut(self, &mut f.ty);
8286+ }
8287+}