Skip to main content

rustc_type_ir_macros/
lib.rs

1use std::ops::ControlFlow;
2
3use indexmap::IndexSet;
4use quote::{ToTokens, quote};
5use syn::parse::Parse;
6use syn::visit_mut::VisitMut;
7use syn::{Attribute, parse_quote};
8use synstructure::decl_derive;
9
10decl_derive!(
11    [TypeVisitable_Generic, attributes(type_visitable)] => type_visitable_derive
12);
13decl_derive!(
14    [TypeFoldable_Generic, attributes(type_foldable)] => type_foldable_derive
15);
16decl_derive!(
17    [Lift_Generic, attributes(lift)] => lift_derive
18);
19decl_derive!(
20    [ GenericTypeVisitable, attributes(generic_type_visitable)] =>
21        /// By default, `#[derive(GenericTypeVisitable)]` will add `GenericTypeVisitable`
22        /// bounds to every field of the item. However, this results in infinite recursion
23        /// for types whose fields mention `Self`, such as:
24        ///
25        /// ```
26        /// struct List {
27        ///     next: Option<Box<Self>>
28        /// }
29        /// ```
30        ///
31        /// The `#[generic_type_visitable(bounds(...))]` attribute provides an escape
32        /// hatch: it allows you to override the list of trait bounds added to the field's type.
33        /// Namely, it should contain `GenericTypeVisitable` bounds for all the non-`Self`
34        /// types present in the field.
35        ///
36        /// For the example above, that list will be empty:
37        /// ```ignore (would need to import GenericTypeVisitable to get this to compile)
38        /// #[derive(GenericTypeVisitable)]
39        /// struct List {
40        ///     #[generic_type_visitable(bounds())]
41        ///     next: Option<Box<Self>>
42        /// }
43        /// ```
44        ///
45        /// For a more complicated type:
46        /// ```ignore (would need to import GenericTypeVisitable to get this to compile)
47        /// #[derive(GenericTypeVisitable)]
48        /// struct Foo {
49        ///     #[generic_type_visitable(bounds())]
50        ///     just_self: Box<Self>,
51        ///     #[generic_type_visitable(bounds(Bar: GenericTypeVisitable))]
52        ///     contains_self: (Box<Self>, Bar),
53        /// }
54        /// struct Bar;
55        /// ```
56        customizable_type_visitable_derive
57);
58
59struct TransformedTy {
60    ty: syn::Type,
61    generic_parameter_bounds: IndexSet<syn::Ident>,
62}
63
64enum TypeParameterPath {
65    Interner,
66    GenericParameter(syn::Ident),
67}
68
69type TypeParameterVisitor =
70    fn(TypeParameterPath, &mut syn::TypePath, &mut IndexSet<syn::Ident>) -> ControlFlow<()>;
71
72fn has_ignore_attr(attrs: &[Attribute], name: &'static str, meta: &'static str) -> bool {
73    let mut ignored = false;
74    attrs.iter().for_each(|attr| {
75        if !attr.path().is_ident(name) {
76            return;
77        }
78        let _ = attr.parse_nested_meta(|nested| {
79            if nested.path.is_ident(meta) {
80                ignored = true;
81            }
82            Ok(())
83        });
84    });
85
86    ignored
87}
88
89fn type_visitable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
90    if let syn::Data::Union(_) = s.ast().data {
91        panic!("cannot derive on union")
92    }
93
94    if !s.ast().generics.type_params().any(|ty| ty.ident == "I") {
95        s.add_impl_generic(parse_quote! { I });
96    }
97
98    s.filter(|bi| !has_ignore_attr(&bi.ast().attrs, "type_visitable", "ignore"));
99
100    s.add_where_predicate(parse_quote! { I: Interner });
101    s.add_bounds(synstructure::AddBounds::Fields);
102    let body_visit = s.each(|bind| {
103        quote! {
104            match ::rustc_type_ir::VisitorResult::branch(
105                ::rustc_type_ir::TypeVisitable::visit_with(#bind, __visitor)
106            ) {
107                ::core::ops::ControlFlow::Continue(()) => {},
108                ::core::ops::ControlFlow::Break(r) => {
109                    return ::rustc_type_ir::VisitorResult::from_residual(r);
110                },
111            }
112        }
113    });
114    s.bind_with(|_| synstructure::BindStyle::Move);
115
116    s.bound_impl(
117        quote!(::rustc_type_ir::TypeVisitable<I>),
118        quote! {
119            fn visit_with<__V: ::rustc_type_ir::TypeVisitor<I>>(
120                &self,
121                __visitor: &mut __V
122            ) -> __V::Result {
123                match *self { #body_visit }
124                <__V::Result as ::rustc_type_ir::VisitorResult>::output()
125            }
126        },
127    )
128}
129
130fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
131    if let syn::Data::Union(_) = s.ast().data {
132        panic!("cannot derive on union")
133    }
134
135    if !s.ast().generics.type_params().any(|ty| ty.ident == "I") {
136        s.add_impl_generic(parse_quote! { I });
137    }
138
139    s.add_where_predicate(parse_quote! { I: Interner });
140    s.add_bounds(synstructure::AddBounds::Fields);
141    let generic_parameters =
142        s.ast().generics.type_params().map(|ty| ty.ident.clone()).collect::<Vec<_>>();
143    let mut generic_parameter_bounds = IndexSet::new();
144    s.bind_with(|_| synstructure::BindStyle::Move);
145    let body_try_fold = s.each_variant(|vi| {
146        let bindings = vi.bindings();
147        vi.construct(|_, index| {
148            let bind = &bindings[index];
149
150            // retain value of fields with #[type_foldable(identity)]
151            if has_ignore_attr(&bind.ast().attrs, "type_foldable", "identity") {
152                bind.to_token_stream()
153            } else {
154                for param in
155                    type_foldable_generic_parameters(bind.ast().ty.clone(), &generic_parameters)
156                {
157                    generic_parameter_bounds.insert(param);
158                }
159
160                quote! {
161                    ::rustc_type_ir::TypeFoldable::try_fold_with(#bind, __folder)?
162                }
163            }
164        })
165    });
166
167    let body_fold = s.each_variant(|vi| {
168        let bindings = vi.bindings();
169        vi.construct(|_, index| {
170            let bind = &bindings[index];
171
172            // retain value of fields with #[type_foldable(identity)]
173            if has_ignore_attr(&bind.ast().attrs, "type_foldable", "identity") {
174                bind.to_token_stream()
175            } else {
176                quote! {
177                    ::rustc_type_ir::TypeFoldable::fold_with(#bind, __folder)
178                }
179            }
180        })
181    });
182
183    // We filter fields which get ignored and don't require them to implement
184    // `TypeFoldable`. We do so after generating `body_fold` as we still need
185    // to generate code for them.
186    s.filter(|bi| !has_ignore_attr(&bi.ast().attrs, "type_foldable", "identity"));
187    s.add_bounds(synstructure::AddBounds::Fields);
188    for param in generic_parameter_bounds {
189        s.add_where_predicate(parse_quote! { #param: ::rustc_type_ir::TypeFoldable<I> });
190    }
191    s.bound_impl(
192        quote!(::rustc_type_ir::TypeFoldable<I>),
193        quote! {
194            fn try_fold_with<__F: ::rustc_type_ir::FallibleTypeFolder<I>>(
195                self,
196                __folder: &mut __F
197            ) -> Result<Self, __F::Error> {
198                Ok(match self { #body_try_fold })
199            }
200
201            fn fold_with<__F: ::rustc_type_ir::TypeFolder<I>>(
202                self,
203                __folder: &mut __F
204            ) -> Self {
205                match self { #body_fold }
206            }
207        },
208    )
209}
210
211fn type_foldable_generic_parameters(
212    ty: syn::Type,
213    generic_parameters: &[syn::Ident],
214) -> IndexSet<syn::Ident> {
215    transform_type_parameters(ty, generic_parameters, |path, _, generic_parameter_bounds| {
216        if let TypeParameterPath::GenericParameter(param) = path {
217            generic_parameter_bounds.insert(param);
218        }
219        ControlFlow::Continue(())
220    })
221    .generic_parameter_bounds
222}
223
224/// `Lift_Generic` is specialised for structs/enums parameterised by an interner
225/// `I: Interner`. It derives `Lift<J>` by rewriting interner associated types
226/// from `I::Assoc` to `J::Assoc`. The required associated type lift bounds are
227/// supplied by `I: LiftInto<J>`.
228///
229/// Ordinary generic parameters still get explicit `Lift<J>` bounds. Interner
230/// independent fields must either implement `Lift` manually or use
231/// `#[lift(identity)]`.
232///
233/// `PhantomData` is a special case that occurs enough in the code base to be
234/// handled here directly. We collect any generic bounds from the type then
235/// produce another `PhantomData`.
236fn lift_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
237    if let syn::Data::Union(_) = s.ast().data {
238        panic!("cannot derive on union")
239    }
240
241    if !s.ast().generics.type_params().any(|ty| ty.ident == "I") {
242        s.add_impl_generic(parse_quote! { I });
243    }
244
245    s.add_bounds(synstructure::AddBounds::None);
246    s.add_impl_generic(parse_quote! { J });
247    s.add_where_predicate(parse_quote! { J: Interner });
248    s.add_where_predicate(parse_quote! { I: ::rustc_type_ir::LiftInto<J> });
249
250    let generic_parameters =
251        s.ast().generics.type_params().map(|ty| ty.ident.clone()).collect::<Vec<_>>();
252
253    let mut wc = vec![];
254    s.bind_with(|_| synstructure::BindStyle::Move);
255    let body_fold = s.each_variant(|vi| {
256        let bindings = vi.bindings();
257        vi.construct(|field, index| {
258            let ty = field.ty.clone();
259            let bind = &bindings[index];
260            // Allow field to be ignored from lift
261            if has_ignore_attr(&field.attrs, "lift", "identity") {
262                return bind.to_token_stream();
263            }
264
265            let lifted = lift(ty.clone(), &generic_parameters);
266
267            // Field types involving ordinary generic parameters still need
268            // explicit bounds for those parameters, e.g. `Binder<I, T>` needs
269            // `T: Lift<J>` so its own derived `Lift` impl applies. Interner
270            // associated types are covered by `I: LiftInto<J>`.
271            for param in lifted.generic_parameter_bounds {
272                wc.push(parse_quote! { #param: ::rustc_type_ir::lift::Lift<J> });
273            }
274
275            if is_type_phantom(&ty) {
276                return quote! {
277                    PhantomData
278                };
279            }
280
281            quote! {
282                #bind.lift_to_interner(interner)
283            }
284        })
285    });
286    for wc in wc {
287        s.add_where_predicate(wc);
288    }
289
290    let (_, ty_generics, _) = s.ast().generics.split_for_impl();
291    let name = s.ast().ident.clone();
292    let self_ty: syn::Type = parse_quote! { #name #ty_generics };
293    let lifted = lift(self_ty, &generic_parameters);
294    let lifted_ty = lifted.ty;
295
296    s.bound_impl(
297        quote!(::rustc_type_ir::lift::Lift<J>),
298        quote! {
299            type Lifted = #lifted_ty;
300
301            fn lift_to_interner(
302                self,
303                interner: J,
304            ) -> Self::Lifted {
305                match self { #body_fold }
306            }
307        },
308    )
309}
310
311fn get_first_path_segment(ty: &syn::Type) -> Option<&syn::PathSegment> {
312    if let syn::Type::Path(ty) = ty
313        && ty.path.segments.len() == 1
314    {
315        ty.path.segments.first()
316    } else {
317        None
318    }
319}
320
321/// Return if the type is `PhantomData`
322fn is_type_phantom(ty: &syn::Type) -> bool {
323    get_first_path_segment(ty).is_some_and(|segment| segment.ident == "PhantomData")
324}
325
326fn lift(ty: syn::Type, generic_parameters: &[syn::Ident]) -> TransformedTy {
327    transform_type_parameters(ty, generic_parameters, |path, ty, generic_parameter_bounds| {
328        match path {
329            TypeParameterPath::Interner => {
330                *ty.path.segments.first_mut().unwrap() = parse_quote! { J };
331                ControlFlow::Continue(())
332            }
333            TypeParameterPath::GenericParameter(param) => {
334                generic_parameter_bounds.insert(param.clone());
335                *ty = parse_quote! { <#param as ::rustc_type_ir::lift::Lift<J>>::Lifted };
336                ControlFlow::Break(())
337            }
338        }
339    })
340}
341
342fn transform_type_parameters(
343    mut ty: syn::Type,
344    generic_parameters: &[syn::Ident],
345    visit: TypeParameterVisitor,
346) -> TransformedTy {
347    struct TypeParameterTransformer<'a> {
348        generic_parameters: &'a [syn::Ident],
349        generic_parameter_bounds: IndexSet<syn::Ident>,
350        visit: TypeParameterVisitor,
351    }
352
353    impl VisitMut for TypeParameterTransformer<'_> {
354        fn visit_type_path_mut(&mut self, i: &mut syn::TypePath) {
355            let path = if i.qself.is_none() {
356                let segments_len = i.path.segments.len();
357                i.path.segments.first().and_then(|first| {
358                    if first.ident == "I" {
359                        Some(TypeParameterPath::Interner)
360                    } else if segments_len == 1
361                        && matches!(first.arguments, syn::PathArguments::None)
362                        && self.generic_parameters.contains(&first.ident)
363                    {
364                        Some(TypeParameterPath::GenericParameter(first.ident.clone()))
365                    } else {
366                        None
367                    }
368                })
369            } else {
370                None
371            };
372
373            if let Some(path) = path {
374                if (self.visit)(path, i, &mut self.generic_parameter_bounds).is_break() {
375                    return;
376                }
377            }
378
379            syn::visit_mut::visit_type_path_mut(self, i);
380        }
381    }
382
383    let mut visitor = TypeParameterTransformer {
384        generic_parameters,
385        generic_parameter_bounds: IndexSet::new(),
386        visit,
387    };
388    visitor.visit_type_mut(&mut ty);
389    TransformedTy { ty, generic_parameter_bounds: visitor.generic_parameter_bounds }
390}
391
392fn customizable_type_visitable_derive(
393    mut s: synstructure::Structure<'_>,
394) -> proc_macro2::TokenStream {
395    if let syn::Data::Union(_) = s.ast().data {
396        panic!("cannot derive on union")
397    }
398
399    s.add_impl_generic(parse_quote!(__V));
400    s.add_bounds(synstructure::AddBounds::None);
401
402    let mut wc = vec![];
403    let body_visit = s.each(|bind| {
404        let field = bind.ast();
405        let ty = field.ty.clone();
406
407        match field_generic_type_visitable_bound(field) {
408            Ok(Some(bounds)) => wc.extend(bounds),
409            Ok(None) => {
410                // no overridden bounds, add the default one
411                wc.push(parse_quote! { #ty: ::rustc_type_ir::GenericTypeVisitable::<__V> });
412            }
413            Err(err) => return err.into_compile_error(),
414        }
415
416        quote! {
417            ::rustc_type_ir::GenericTypeVisitable::<__V>::generic_visit_with(#bind, __visitor);
418        }
419    });
420    s.bind_with(|_| synstructure::BindStyle::Move);
421    for wc in wc {
422        s.add_where_predicate(wc);
423    }
424
425    s.unsafe_bound_impl(
426        quote!(::rustc_type_ir::GenericTypeVisitable<__V>),
427        quote! {
428            fn generic_visit_with(
429                &self,
430                __visitor: &mut __V
431            ) {
432                match *self { #body_visit }
433            }
434        },
435    )
436}
437
438fn field_generic_type_visitable_bound(
439    field: &syn::Field,
440) -> syn::Result<Option<impl Iterator<Item = syn::WherePredicate>>> {
441    let mut attrs =
442        field.attrs.iter().filter(|attr| attr.path().is_ident("generic_type_visitable"));
443    let Some(attr) = attrs.next() else {
444        return Ok(None);
445    };
446
447    if attrs.next().is_some() {
448        return Err(syn::Error::new_spanned(
449            field,
450            "multiple `generic_type_visitable` attributes on field",
451        ));
452    }
453
454    parse_generic_type_visitable_bound(attr).map(Some)
455}
456
457mod kw {
458    syn::custom_keyword!(bounds);
459}
460
461/// Parses a bound like:
462///
463/// ```ignore (would need to import GenericTypeVisitable to get this to compile)
464/// #[generic_type_visitable(bounds(Foo: GenericTypeVisitable, Bar: GenericTypeVisitable))]
465/// ```
466fn parse_generic_type_visitable_bound(
467    attr: &Attribute,
468) -> syn::Result<impl Iterator<Item = syn::WherePredicate>> {
469    attr.parse_args_with(|input: syn::parse::ParseStream<'_>| {
470        input.parse::<kw::bounds>()?;
471        let predicates;
472        syn::parenthesized!(predicates in input);
473
474        let proof =
475            predicates.parse_terminated(syn::WherePredicate::parse, syn::Token![,])?.into_iter();
476
477        if input.is_empty() { Ok(proof) } else { Err(input.error("unexpected token")) }
478    })
479}