use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, GenericParam, WhereClause, WherePredicate};
#[proc_macro_derive(LightClone)]
pub fn derive_light_clone(input: TokenStream) -> TokenStream {
derive_light_clone_impl(input)
}
fn collect_field_types(data: &Data) -> Vec<syn::Type> {
let mut types = Vec::new();
match data {
Data::Struct(data_struct) => {
for field in &data_struct.fields {
types.push(field.ty.clone());
}
}
Data::Enum(data_enum) => {
for variant in &data_enum.variants {
for field in &variant.fields {
types.push(field.ty.clone());
}
}
}
Data::Union(_) => {
}
}
types
}
fn build_where_clause(
existing: Option<&WhereClause>,
field_types: &[syn::Type],
generics: &syn::Generics,
) -> TokenStream2 {
let mut predicates: Vec<WherePredicate> = existing
.map(|w| w.predicates.iter().cloned().collect())
.unwrap_or_default();
let type_params: Vec<_> = generics
.params
.iter()
.filter_map(|param| {
if let GenericParam::Type(type_param) = param {
Some(type_param.ident.clone())
} else {
None
}
})
.collect();
for type_param in &type_params {
let predicate: WherePredicate = syn::parse_quote!(#type_param: light_clone::LightClone);
predicates.push(predicate);
}
for ty in field_types {
let predicate: WherePredicate = syn::parse_quote!(#ty: light_clone::LightClone);
predicates.push(predicate);
}
if predicates.is_empty() {
quote! {}
} else {
quote! { where #(#predicates),* }
}
}
fn derive_light_clone_impl(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let generics = &input.generics;
let (impl_generics, ty_generics, _) = generics.split_for_impl();
if let Data::Union(_) = &input.data {
return syn::Error::new_spanned(
&input.ident,
"LightClone derive is not supported for unions.",
)
.to_compile_error()
.into();
}
let field_types = collect_field_types(&input.data);
let where_clause =
build_where_clause(input.generics.where_clause.as_ref(), &field_types, generics);
let light_clone_impl = quote! {
impl #impl_generics light_clone::LightClone for #name #ty_generics #where_clause {}
};
light_clone_impl.into()
}