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