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
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 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 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 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
224fn 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 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 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
321fn 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 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
461fn 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}