use convert_case::{Case, Casing};
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Type};
mod attrs;
use attrs::Attrs;
#[proc_macro_derive(HasFields, attributes(skip_class))]
pub fn make_fields(annotated_item: TokenStream) -> TokenStream {
let input = parse_macro_input!(annotated_item as DeriveInput);
let syn::Data::Struct(data) = input.data else {
panic!("make_fields should be used with a struct")
};
let struct_name = input.ident;
let ts = data.fields.iter().map(|f| {
let name = f
.ident
.clone()
.expect("make_fields should be used on a struct with named fields");
let field_name = name.clone();
let pascal_field_name = name.to_string().to_case(Case::Pascal);
let class_name = Ident::new(&format!("Has{}", pascal_field_name), Span::call_site());
let output_type_name = Ident::new(&pascal_field_name, Span::call_site());
let ty = &f.ty;
let attrs = Attrs::from(f.clone());
if attrs.skip_class {
make_instance(
&class_name,
&struct_name,
&field_name,
&output_type_name,
ty,
)
} else {
make_class(
&class_name,
&struct_name,
&field_name,
&output_type_name,
ty,
)
}
});
quote! {
#(#ts)*
}
.into()
}
fn make_class(
class_name: &Ident,
struct_name: &Ident,
field_name: &Ident,
output_type_name: &Ident,
ty: &Type,
) -> proc_macro2::TokenStream {
let instance = make_instance(
&class_name,
&struct_name,
&field_name,
&output_type_name,
ty,
);
quote! {
trait #class_name {
type #output_type_name;
fn #field_name(&self) -> &Self::#output_type_name;
}
#instance
}
}
fn make_instance(
class_name: &Ident,
struct_name: &Ident,
field_name: &Ident,
output_type_name: &Ident,
ty: &Type,
) -> proc_macro2::TokenStream {
quote! {
impl #class_name for #struct_name {
type #output_type_name = #ty;
fn #field_name(&self) -> &Self::#output_type_name {
&self.#field_name
}
}
}
}