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 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 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 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 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
228fn 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 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 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
325fn 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 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
465fn 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}