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}