From 47ecf3521406c43703119e46c8b1b9c5543d1875 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 5 Aug 2026 12:07:02 +0200 Subject: [PATCH 1/5] misc: replace `TypeParameterTransform` with a standard type --- compiler/rustc_type_ir_macros/src/lib.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_type_ir_macros/src/lib.rs b/compiler/rustc_type_ir_macros/src/lib.rs index bafd8d72dc437..f7f0a5e9efd00 100644 --- a/compiler/rustc_type_ir_macros/src/lib.rs +++ b/compiler/rustc_type_ir_macros/src/lib.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use indexmap::IndexSet; use quote::{ToTokens, quote}; use syn::visit_mut::VisitMut; @@ -28,13 +30,8 @@ enum TypeParameterPath { GenericParameter(syn::Ident), } -enum TypeParameterTransform { - Continue, - Stop, -} - type TypeParameterVisitor = - fn(TypeParameterPath, &mut syn::TypePath, &mut IndexSet) -> TypeParameterTransform; + fn(TypeParameterPath, &mut syn::TypePath, &mut IndexSet) -> ControlFlow<()>; fn has_ignore_attr(attrs: &[Attribute], name: &'static str, meta: &'static str) -> bool { let mut ignored = false; @@ -183,7 +180,7 @@ fn type_foldable_generic_parameters( if let TypeParameterPath::GenericParameter(param) = path { generic_parameter_bounds.insert(param); } - TypeParameterTransform::Continue + ControlFlow::Continue(()) }) .generic_parameter_bounds } @@ -295,12 +292,12 @@ fn lift(ty: syn::Type, generic_parameters: &[syn::Ident]) -> TransformedTy { match path { TypeParameterPath::Interner => { *ty.path.segments.first_mut().unwrap() = parse_quote! { J }; - TypeParameterTransform::Continue + ControlFlow::Continue(()) } TypeParameterPath::GenericParameter(param) => { generic_parameter_bounds.insert(param.clone()); *ty = parse_quote! { <#param as ::rustc_type_ir::lift::Lift>::Lifted }; - TypeParameterTransform::Stop + ControlFlow::Break(()) } } }) @@ -338,9 +335,7 @@ fn transform_type_parameters( }; if let Some(path) = path { - if let TypeParameterTransform::Stop = - (self.visit)(path, i, &mut self.generic_parameter_bounds) - { + if (self.visit)(path, i, &mut self.generic_parameter_bounds).is_break() { return; } } From 490a5dee1f387f4be2536348ff9cd2f0c455ae9f Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 31 Jul 2026 12:12:16 +0200 Subject: [PATCH 2/5] make `#[derive(GenericTypeVisitable)]` no longer a no-op in rustc This is motivated by https://github.com/rust-lang/rust/pull/160164, which added the derive which wouldn't actually work, due to recusrive trait bounds (more on this in a later commit). This change will make it so that these errors are caught in rustc CI. --- compiler/rustc_type_ir/src/lib.rs | 2 -- compiler/rustc_type_ir_macros/src/lib.rs | 8 -------- 2 files changed, 10 deletions(-) diff --git a/compiler/rustc_type_ir/src/lib.rs b/compiler/rustc_type_ir/src/lib.rs index 9bd7698b852e1..b7fa0c77e44ee 100644 --- a/compiler/rustc_type_ir/src/lib.rs +++ b/compiler/rustc_type_ir/src/lib.rs @@ -66,7 +66,6 @@ mod const_kind; mod flags; mod fold; mod generic_arg; -#[cfg(not(feature = "nightly"))] mod generic_visit; mod infer_ctxt; mod interner; @@ -97,7 +96,6 @@ pub use const_kind::*; pub use flags::*; pub use fold::*; pub use generic_arg::*; -#[cfg(not(feature = "nightly"))] pub use generic_visit::*; pub use infer_ctxt::*; pub use interner::*; diff --git a/compiler/rustc_type_ir_macros/src/lib.rs b/compiler/rustc_type_ir_macros/src/lib.rs index f7f0a5e9efd00..ee1a0701654a1 100644 --- a/compiler/rustc_type_ir_macros/src/lib.rs +++ b/compiler/rustc_type_ir_macros/src/lib.rs @@ -15,7 +15,6 @@ decl_derive!( decl_derive!( [Lift_Generic, attributes(lift)] => lift_derive ); -#[cfg(not(feature = "nightly"))] decl_derive!( [GenericTypeVisitable] => customizable_type_visitable_derive ); @@ -353,7 +352,6 @@ fn transform_type_parameters( TransformedTy { ty, generic_parameter_bounds: visitor.generic_parameter_bounds } } -#[cfg(not(feature = "nightly"))] fn customizable_type_visitable_derive( mut s: synstructure::Structure<'_>, ) -> proc_macro2::TokenStream { @@ -382,9 +380,3 @@ fn customizable_type_visitable_derive( }, ) } - -#[cfg(feature = "nightly")] -#[proc_macro_derive(GenericTypeVisitable)] -pub fn customizable_type_visitable_derive(_: proc_macro::TokenStream) -> proc_macro::TokenStream { - proc_macro::TokenStream::new() -} From cc31decd690b21b8421bac8985f0dc78be068fdd Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 31 Jul 2026 11:58:43 +0200 Subject: [PATCH 3/5] make GenericTypeVisitable an unsafe trait Becuase its implementations must uphold a soundness-critical invariant. --- compiler/rustc_type_ir/src/generic_visit.rs | 55 ++++++++++++--------- compiler/rustc_type_ir_macros/src/lib.rs | 2 +- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_type_ir/src/generic_visit.rs b/compiler/rustc_type_ir/src/generic_visit.rs index 6f4b461575075..2444990749411 100644 --- a/compiler/rustc_type_ir/src/generic_visit.rs +++ b/compiler/rustc_type_ir/src/generic_visit.rs @@ -20,29 +20,35 @@ use crate::Interner; /// This trait is implemented for every type that can be visited, /// providing the skeleton of the traversal. /// -/// To implement this conveniently, use the derive macro located in -/// `rustc_macros`. -pub trait GenericTypeVisitable { +/// ## Safety +/// +/// A manual implementation **must visit** every field. +/// +/// Therefore, it is advised to instead derive this using the derive +/// macro located in `rustc_macros`. +pub unsafe trait GenericTypeVisitable { fn generic_visit_with(&self, visitor: &mut V); } /////////////////////////////////////////////////////////////////////////// // Traversal implementations. -impl> GenericTypeVisitable for &T { +unsafe impl> GenericTypeVisitable for &T { fn generic_visit_with(&self, visitor: &mut V) { T::generic_visit_with(*self, visitor) } } -impl, U: GenericTypeVisitable> GenericTypeVisitable for (T, U) { +unsafe impl, U: GenericTypeVisitable> GenericTypeVisitable + for (T, U) +{ fn generic_visit_with(&self, visitor: &mut V) { self.0.generic_visit_with(visitor); self.1.generic_visit_with(visitor); } } -impl, B: GenericTypeVisitable, C: GenericTypeVisitable> +unsafe impl, B: GenericTypeVisitable, C: GenericTypeVisitable> GenericTypeVisitable for (A, B, C) { fn generic_visit_with(&self, visitor: &mut V) { @@ -52,7 +58,7 @@ impl, B: GenericTypeVisitable, C: GenericTypeVi } } -impl> GenericTypeVisitable for Option { +unsafe impl> GenericTypeVisitable for Option { fn generic_visit_with(&self, visitor: &mut V) { match self { Some(v) => v.generic_visit_with(visitor), @@ -61,7 +67,7 @@ impl> GenericTypeVisitable for Option { } } -impl, E: GenericTypeVisitable> GenericTypeVisitable +unsafe impl, E: GenericTypeVisitable> GenericTypeVisitable for Result { fn generic_visit_with(&self, visitor: &mut V) { @@ -72,54 +78,56 @@ impl, E: GenericTypeVisitable> GenericTypeVisit } } -impl> GenericTypeVisitable for Arc { +unsafe impl> GenericTypeVisitable for Arc { fn generic_visit_with(&self, visitor: &mut V) { (**self).generic_visit_with(visitor) } } -impl> GenericTypeVisitable for Box { +unsafe impl> GenericTypeVisitable for Box { fn generic_visit_with(&self, visitor: &mut V) { (**self).generic_visit_with(visitor) } } -impl> GenericTypeVisitable for Vec { +unsafe impl> GenericTypeVisitable for Vec { fn generic_visit_with(&self, visitor: &mut V) { self.iter().for_each(|it| it.generic_visit_with(visitor)); } } -impl> GenericTypeVisitable for ThinVec { +unsafe impl> GenericTypeVisitable for ThinVec { fn generic_visit_with(&self, visitor: &mut V) { self.iter().for_each(|it| it.generic_visit_with(visitor)); } } -impl, const N: usize> GenericTypeVisitable for SmallVec<[T; N]> { +unsafe impl, const N: usize> GenericTypeVisitable + for SmallVec<[T; N]> +{ fn generic_visit_with(&self, visitor: &mut V) { self.iter().for_each(|it| it.generic_visit_with(visitor)); } } -impl> GenericTypeVisitable for [T] { +unsafe impl> GenericTypeVisitable for [T] { fn generic_visit_with(&self, visitor: &mut V) { self.iter().for_each(|it| it.generic_visit_with(visitor)); } } -impl, Ix: Idx> GenericTypeVisitable for IndexVec { +unsafe impl, Ix: Idx> GenericTypeVisitable for IndexVec { fn generic_visit_with(&self, visitor: &mut V) { self.iter().for_each(|it| it.generic_visit_with(visitor)); } } -impl GenericTypeVisitable for std::hash::BuildHasherDefault { +unsafe impl GenericTypeVisitable for std::hash::BuildHasherDefault { fn generic_visit_with(&self, _visitor: &mut V) {} } #[expect(rustc::default_hash_types, rustc::potential_query_instability)] -impl< +unsafe impl< Visitor, Key: GenericTypeVisitable, Value: GenericTypeVisitable, @@ -133,7 +141,7 @@ impl< } #[expect(rustc::default_hash_types, rustc::potential_query_instability)] -impl, S: GenericTypeVisitable> GenericTypeVisitable +unsafe impl, S: GenericTypeVisitable> GenericTypeVisitable for std::collections::HashSet { fn generic_visit_with(&self, visitor: &mut V) { @@ -142,7 +150,7 @@ impl, S: GenericTypeVisitable> GenericTypeVisit } } -impl< +unsafe impl< Visitor, Key: GenericTypeVisitable, Value: GenericTypeVisitable, @@ -155,7 +163,7 @@ impl< } } -impl, S: GenericTypeVisitable> GenericTypeVisitable +unsafe impl, S: GenericTypeVisitable> GenericTypeVisitable for indexmap::IndexSet { fn generic_visit_with(&self, visitor: &mut V) { @@ -167,7 +175,7 @@ impl, S: GenericTypeVisitable> GenericTypeVisit macro_rules! trivial_impls { ( $($ty:ty),* $(,)? ) => { $( - impl + unsafe impl GenericTypeVisitable for $ty { fn generic_visit_with(&self, _visitor: &mut V) {} @@ -176,7 +184,7 @@ macro_rules! trivial_impls { }; } -impl GenericTypeVisitable for std::marker::PhantomData { +unsafe impl GenericTypeVisitable for std::marker::PhantomData { fn generic_visit_with(&self, _visitor: &mut V) {} } @@ -215,6 +223,7 @@ trivial_impls!( rustc_abi::ExternAbi, ); -impl GenericTypeVisitable for crate::FnSigKind { +// SAFETY: `FnSigKind` is a packed representation, therefore visiting its fields doesn't make sense +unsafe impl GenericTypeVisitable for crate::FnSigKind { fn generic_visit_with(&self, _visitor: &mut V) {} } diff --git a/compiler/rustc_type_ir_macros/src/lib.rs b/compiler/rustc_type_ir_macros/src/lib.rs index ee1a0701654a1..5bce3ae841b4e 100644 --- a/compiler/rustc_type_ir_macros/src/lib.rs +++ b/compiler/rustc_type_ir_macros/src/lib.rs @@ -368,7 +368,7 @@ fn customizable_type_visitable_derive( }); s.bind_with(|_| synstructure::BindStyle::Move); - s.bound_impl( + s.unsafe_bound_impl( quote!(::rustc_type_ir::GenericTypeVisitable<__V>), quote! { fn generic_visit_with( From ce920059542c6d277943017899baf2b61756e40a Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 5 Aug 2026 11:05:20 +0200 Subject: [PATCH 4/5] implement `#[generic_type_visitable(bounds(...))]` --- compiler/rustc_type_ir_macros/src/lib.rs | 101 ++++++++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_type_ir_macros/src/lib.rs b/compiler/rustc_type_ir_macros/src/lib.rs index 5bce3ae841b4e..e1d2b53366066 100644 --- a/compiler/rustc_type_ir_macros/src/lib.rs +++ b/compiler/rustc_type_ir_macros/src/lib.rs @@ -2,6 +2,7 @@ use std::ops::ControlFlow; use indexmap::IndexSet; use quote::{ToTokens, quote}; +use syn::parse::Parse; use syn::visit_mut::VisitMut; use syn::{Attribute, parse_quote}; use synstructure::decl_derive; @@ -16,7 +17,43 @@ decl_derive!( [Lift_Generic, attributes(lift)] => lift_derive ); decl_derive!( - [GenericTypeVisitable] => customizable_type_visitable_derive + [ GenericTypeVisitable, attributes(generic_type_visitable)] => + /// By default, `#[derive(GenericTypeVisitable)]` will add `GenericTypeVisitable` + /// bounds to every field of the item. However, this results in infinite recursion + /// for types whose fields mention `Self`, such as: + /// + /// ``` + /// struct List { + /// next: Option> + /// } + /// ``` + /// + /// The `#[generic_type_visitable(bounds(...))]` attribute provides an escape + /// hatch: it allows you to override the list of trait bounds added to the field's type. + /// Namely, it should contain `GenericTypeVisitable` bounds for all the non-`Self` + /// types present in the field. + /// + /// For the example above, that list will be empty: + /// ```ignore (would need to import GenericTypeVisitable to get this to compile) + /// #[derive(GenericTypeVisitable)] + /// struct List { + /// #[generic_type_visitable(bounds())] + /// next: Option> + /// } + /// ``` + /// + /// For a more complicated type: + /// ```ignore (would need to import GenericTypeVisitable to get this to compile) + /// #[derive(GenericTypeVisitable)] + /// struct Foo { + /// #[generic_type_visitable(bounds())] + /// just_self: Box, + /// #[generic_type_visitable(bounds(Bar: GenericTypeVisitable))] + /// contains_self: (Box, Bar), + /// } + /// struct Bar; + /// ``` + customizable_type_visitable_derive ); struct TransformedTy { @@ -360,13 +397,30 @@ fn customizable_type_visitable_derive( } s.add_impl_generic(parse_quote!(__V)); - s.add_bounds(synstructure::AddBounds::Fields); + s.add_bounds(synstructure::AddBounds::None); + + let mut wc = vec![]; let body_visit = s.each(|bind| { + let field = bind.ast(); + let ty = field.ty.clone(); + + match field_generic_type_visitable_bound(field) { + Ok(Some(bounds)) => wc.extend(bounds), + Ok(None) => { + // no overridden bounds, add the default one + wc.push(parse_quote! { #ty: ::rustc_type_ir::GenericTypeVisitable::<__V> }); + } + Err(err) => return err.into_compile_error(), + } + quote! { ::rustc_type_ir::GenericTypeVisitable::<__V>::generic_visit_with(#bind, __visitor); } }); s.bind_with(|_| synstructure::BindStyle::Move); + for wc in wc { + s.add_where_predicate(wc); + } s.unsafe_bound_impl( quote!(::rustc_type_ir::GenericTypeVisitable<__V>), @@ -380,3 +434,46 @@ fn customizable_type_visitable_derive( }, ) } + +fn field_generic_type_visitable_bound( + field: &syn::Field, +) -> syn::Result>> { + let mut attrs = + field.attrs.iter().filter(|attr| attr.path().is_ident("generic_type_visitable")); + let Some(attr) = attrs.next() else { + return Ok(None); + }; + + if attrs.next().is_some() { + return Err(syn::Error::new_spanned( + field, + "multiple `generic_type_visitable` attributes on field", + )); + } + + parse_generic_type_visitable_bound(attr).map(Some) +} + +mod kw { + syn::custom_keyword!(bounds); +} + +/// Parses a bound like: +/// +/// ```ignore (would need to import GenericTypeVisitable to get this to compile) +/// #[generic_type_visitable(bounds(Foo: GenericTypeVisitable, Bar: GenericTypeVisitable))] +/// ``` +fn parse_generic_type_visitable_bound( + attr: &Attribute, +) -> syn::Result> { + attr.parse_args_with(|input: syn::parse::ParseStream<'_>| { + input.parse::()?; + let predicates; + syn::parenthesized!(predicates in input); + + let proof = + predicates.parse_terminated(syn::WherePredicate::parse, syn::Token![,])?.into_iter(); + + if input.is_empty() { Ok(proof) } else { Err(input.error("unexpected token")) } + }) +} From d75b165d27a48eddfdb738662a7e01f49ae4c10f Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 5 Aug 2026 13:07:32 +0200 Subject: [PATCH 5/5] fix `GenericTypeVisitable` derive for `RegionConstraint` --- compiler/rustc_type_ir/src/region_constraint.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 2592c0579c741..2923709432ee5 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -117,8 +117,8 @@ pub enum RegionConstraint { /// and there may wind up being assumptions we can use to prove this when we're in a smaller universe. PlaceholderTyOutlives(I::Ty, Region, S), - And(Box<[RegionConstraint]>), - Or(Box<[RegionConstraint]>), + And(#[generic_type_visitable(bounds())] Box<[RegionConstraint]>), + Or(#[generic_type_visitable(bounds())] Box<[RegionConstraint]>), } /// A solver region constraint together with the span that caused each leaf constraint.