1use proc_macro2::{Ident, Span};
8use quote::{format_ident, quote, quote_spanned};
9use syn::spanned::Spanned as _;
10use syn::{Block, Data, DeriveInput, Error, Fields, Generics, ItemImpl, Result, parse_quote, parse2};
11
12use crate::attribute::ParseAttribute as _;
13use crate::attribute::trace::TraceAttribute;
14use crate::utils::{add_trait_bounds, wrap_in_fields_group};
15
16pub(super) fn impl_trace(mut input: DeriveInput) -> Result<ItemImpl> {
17 add_trait_bounds(&mut input.generics, &parse_quote!(::mozjs::gc::Traceable));
18 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
19 let impl_generics: Generics = parse2(quote_spanned!(impl_generics.span() => #impl_generics))?;
20
21 let name = &input.ident;
22 let body = impl_body(input.span(), &mut input.data)?;
23
24 parse2(quote_spanned!(input.span() =>
25 #[automatically_derived]
26 unsafe impl #impl_generics ::mozjs::gc::Traceable for #name #ty_generics #where_clause {
27 unsafe fn trace(&self, __ion_tracer: *mut ::mozjs::jsapi::JSTracer) {
28 #[allow(unused_unsafe)]
29 unsafe #body
30 }
31 }
32 ))
33}
34
35fn impl_body(span: Span, data: &mut Data) -> Result<Box<Block>> {
36 let mut variants = Vec::new();
37 let mut variant_idents = Vec::new();
38 let mut variant_traced = Vec::new();
39 let mut fields = Vec::new();
40
41 match data {
42 Data::Struct(r#struct) => {
43 let (idents, traced) = field_idents(&mut r#struct.fields)?;
44 variants.push(quote!(Self));
45 variant_idents.push(idents);
46 variant_traced.push(traced);
47 fields.push(r#struct.fields.clone());
48 }
49 Data::Enum(r#enum) => {
50 for variant in &mut r#enum.variants {
51 let ident = &variant.ident;
52 let (idents, traced) = field_idents(&mut variant.fields)?;
53 variants.push(quote!(Self::#ident));
54 variant_idents.push(idents);
55 variant_traced.push(traced);
56 fields.push(variant.fields.clone());
57 }
58 }
59 Data::Union(_) => {
60 return Err(Error::new(
61 span,
62 "#[derive(Traceable)] is not implemented for union types.",
63 ));
64 }
65 }
66
67 let wrapped = variant_idents.into_iter().enumerate().map(|(index, idents)| {
68 if matches!(&fields[index], Fields::Named(_) | Fields::Unnamed(_)) {
69 wrap_in_fields_group(idents, &fields[index])
70 } else {
71 quote!()
72 }
73 });
74
75 parse2(quote_spanned!(span => {
76 match self {
77 #(#variants #wrapped => {
78 #(::mozjs::gc::Traceable::trace(#variant_traced, __ion_tracer));*
79 },)*
80 }
81 }))
82}
83
84fn field_idents(fields: &mut Fields) -> Result<(Vec<Ident>, Vec<Ident>)> {
85 let mut idents = Vec::with_capacity(fields.len());
86 let mut traced = Vec::with_capacity(fields.len());
87 for (index, field) in fields.iter_mut().enumerate() {
88 let attribute = TraceAttribute::from_attributes("trace", &mut field.attrs, ())?;
89 let ident = match &field.ident {
90 Some(ident) => ident.clone(),
91 None => format_ident!("_self_{}", index),
92 };
93 idents.push(ident.clone());
94 if !attribute.no_trace {
95 traced.push(ident);
96 }
97 }
98 Ok((idents, traced))
99}