make_fields 0.1.0

Tiny derive macro to work with fields inspired by lens's makeFields
Documentation
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
            }
        }
    }
}