make_fields/
lib.rs

1use convert_case::{Case, Casing};
2use proc_macro::TokenStream;
3use proc_macro2::{Ident, Span};
4use quote::quote;
5use syn::{parse_macro_input, DeriveInput, Type};
6
7mod attrs;
8use attrs::Attrs;
9
10#[proc_macro_derive(HasFields, attributes(skip_class))]
11pub fn make_fields(annotated_item: TokenStream) -> TokenStream {
12    let input = parse_macro_input!(annotated_item as DeriveInput);
13    let syn::Data::Struct(data) = input.data else {
14        panic!("make_fields should be used with a struct")
15    };
16
17    let struct_name = input.ident;
18
19    let ts = data.fields.iter().map(|f| {
20        let name = f
21            .ident
22            .clone()
23            .expect("make_fields should be used on a struct with named fields");
24        let field_name = name.clone();
25        let pascal_field_name = name.to_string().to_case(Case::Pascal);
26        let class_name = Ident::new(&format!("Has{}", pascal_field_name), Span::call_site());
27        let output_type_name = Ident::new(&pascal_field_name, Span::call_site());
28        let ty = &f.ty;
29        let attrs = Attrs::from(f.clone());
30
31        if attrs.skip_class {
32            make_instance(
33                &class_name,
34                &struct_name,
35                &field_name,
36                &output_type_name,
37                ty,
38            )
39        } else {
40            make_class(
41                &class_name,
42                &struct_name,
43                &field_name,
44                &output_type_name,
45                ty,
46            )
47        }
48    });
49
50    quote! {
51        #(#ts)*
52    }
53    .into()
54}
55
56fn make_class(
57    class_name: &Ident,
58    struct_name: &Ident,
59    field_name: &Ident,
60    output_type_name: &Ident,
61    ty: &Type,
62) -> proc_macro2::TokenStream {
63    let instance = make_instance(
64        &class_name,
65        &struct_name,
66        &field_name,
67        &output_type_name,
68        ty,
69    );
70
71    quote! {
72        trait #class_name {
73            type #output_type_name;
74
75            fn #field_name(&self) -> &Self::#output_type_name;
76        }
77
78        #instance
79    }
80}
81
82fn make_instance(
83    class_name: &Ident,
84    struct_name: &Ident,
85    field_name: &Ident,
86    output_type_name: &Ident,
87    ty: &Type,
88) -> proc_macro2::TokenStream {
89    quote! {
90        impl #class_name for #struct_name {
91            type #output_type_name = #ty;
92
93            fn #field_name(&self) -> &Self::#output_type_name {
94                &self.#field_name
95            }
96        }
97    }
98}