ion_proc/
trace.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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}