use crate::error::Error;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use syn::{
Data, DeriveInput, Error as SynError, Field, Fields, FieldsNamed, FieldsUnnamed, Ident, Index, Path,
spanned::Spanned,
};
pub struct Basic;
impl Basic {
fn field_assignment(field: &Field, trait_path: &Path, method: &Ident) -> Result<TokenStream2, SynError> {
let Some(field_name) = &field.ident else {
return Err(Error::c_struct_field_missing_name(field.span()));
};
let field_assignment =
quote::quote! { #field_name: #trait_path::#method(&self.#field_name, &other.#field_name) };
Ok(field_assignment)
}
fn value_for_c_struct(
fields_named: &FieldsNamed,
trait_path: &Path,
method: &Ident,
) -> Result<TokenStream2, SynError> {
let field_assignments = fields_named
.named
.iter()
.map(|field| Self::field_assignment(field, trait_path, method))
.collect::<Result<Vec<TokenStream2>, SynError>>()?;
let value = quote::quote! { Self { #(#field_assignments),* } };
Ok(value)
}
fn value_for_tuple_struct(fields_unnamed: &FieldsUnnamed, trait_path: &Path, method: &Ident) -> TokenStream2 {
let num_fields = fields_unnamed.unnamed.len();
let field_values = (0..num_fields)
.map(Index::from)
.map(|index| quote::quote! { #trait_path::#method(&self.#index, &other.#index) });
quote::quote! { Self(#(#field_values),*) }
}
fn value_for_unit_struct() -> TokenStream2 {
quote::quote! { Self }
}
fn value(input: &DeriveInput, trait_path: &Path, method: &Ident) -> Result<TokenStream2, SynError> {
let Data::Struct(data_struct) = &input.data else {
return Err(Error::unsupported_item_type(input.span()));
};
let value = match &data_struct.fields {
Fields::Named(fields_named) => Self::value_for_c_struct(fields_named, trait_path, method)?,
Fields::Unnamed(fields_unnamed) => Self::value_for_tuple_struct(fields_unnamed, trait_path, method),
Fields::Unit => Self::value_for_unit_struct(),
};
Ok(value)
}
fn derive_impl(input: &DeriveInput, trait_path: &str, method: &str) -> Result<TokenStream2, SynError> {
let input_ident = &input.ident;
let (impl_generics, input_generics, input_where_clause) = input.generics.split_for_impl();
let trait_path = syn::parse_str::<Path>(trait_path)?;
let method = syn::parse_str::<Ident>(method)?;
let value = Self::value(input, &trait_path, &method)?;
let impl_block_token_stream = quote::quote! {
impl #impl_generics #trait_path for #input_ident #input_generics #input_where_clause {
fn #method(&self, other: &Self) -> Self {
#value
}
}
};
Ok(impl_block_token_stream)
}
pub fn derive(input_token_stream: TokenStream, trait_path: &str, method: &str) -> TokenStream {
let input = syn::parse_macro_input!(input_token_stream);
Self::derive_impl(&input, trait_path, method)
.unwrap_or_else(SynError::into_compile_error)
.into()
}
}