use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::{
parse::ParseStream, parse_macro_input, parse_quote, spanned::Spanned, Attribute, Data,
DataEnum, DataStruct, DeriveInput, Fields, GenericParam, Lifetime, LifetimeDef, TypeParamBound,
};
pub fn derive_trace(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mut input = parse_macro_input!(input as DeriveInput);
let tick_v = GenericParam::Lifetime(LifetimeDef::new(Lifetime::new("'v", Span::call_site())));
let bound: TypeParamBound = parse_quote!(starlark::values::Trace<'v>);
let mut has_tick_v = false;
for param in &mut input.generics.params {
if let GenericParam::Type(type_param) = param {
type_param.bounds.push(bound.clone());
}
if let GenericParam::Lifetime(t) = param {
if t.lifetime.ident == "v" {
has_tick_v = true;
}
}
}
let mut generics2 = input.generics.clone();
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
if !has_tick_v {
generics2.params.insert(0, tick_v);
}
let (impl_generics, _, _) = generics2.split_for_impl();
let name = &input.ident;
let body = trace_impl(&input.data);
let gen = quote! {
unsafe impl #impl_generics starlark::values::Trace<'v> for #name #ty_generics #where_clause {
fn trace(&mut self, tracer: &starlark::values::Tracer<'v>) {
#body
}
}
};
gen.into()
}
fn is_ignore(attrs: &[Attribute]) -> bool {
syn::custom_keyword!(unsafe_ignore);
attrs.iter().any(|a| {
a.path.is_ident("trace")
&& a.parse_args_with(|input: ParseStream| {
let ignore = input.parse::<Option<unsafe_ignore>>()?.is_some();
Ok(ignore)
})
.unwrap()
})
}
fn trace_struct(data: &DataStruct) -> TokenStream {
match data.fields {
Fields::Named(ref fields) => {
let xs: Vec<_> = fields
.named
.iter()
.filter_map(|f| {
if !is_ignore(&f.attrs) {
let name = &f.ident;
Some(quote_spanned! {f.span() =>
starlark::values::Trace::trace(&mut self.#name, tracer);
})
} else {
None
}
})
.collect();
quote! {
#(#xs)*
}
}
Fields::Unnamed(ref fields) => {
let xs: Vec<_> = fields
.unnamed
.iter()
.enumerate()
.filter_map(|(i, f)| {
if !is_ignore(&f.attrs) {
let i = syn::Index::from(i);
Some(quote_spanned! {f.span() => starlark::values::Trace::trace(&mut self.#i, tracer);})
} else {
None
}
})
.collect();
quote! {
#(#xs)*
}
}
Fields::Unit => {
quote!()
}
}
}
fn trace_enum(data: &DataEnum) -> TokenStream {
for variant in &data.variants {
if let Fields::Unit = variant.fields {
continue;
}
match &variant.fields {
Fields::Named(fields) => {
for field in &fields.named {
if is_ignore(&field.attrs) {
continue;
}
unimplemented!("Can't derive Trace for enums");
}
}
Fields::Unnamed(fields) => {
for field in &fields.unnamed {
if is_ignore(&field.attrs) {
continue;
}
unimplemented!("Can't derive Trace for enums");
}
}
Fields::Unit => {}
}
}
quote!()
}
fn trace_impl(data: &Data) -> TokenStream {
match data {
Data::Struct(data) => trace_struct(data),
Data::Enum(data) => trace_enum(data),
Data::Union(_) => unimplemented!("Can't derive Trace for unions"),
}
}