schnauzer_derive/
lib.rs

1use proc_macro::{self, TokenStream};
2use quote::quote;
3use syn::{parse_macro_input, token::Comma};
4
5#[proc_macro_derive(AutoEnumFields)]
6pub fn derive(input: TokenStream) -> TokenStream {
7    let ast: syn::DeriveInput = parse_macro_input!(input);
8
9    impl_auto_enum_fields(&ast).into()
10}
11
12fn impl_auto_enum_fields(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
13    let id = &ast.ident;
14    let fields = match &ast.data {
15        syn::Data::Struct(data) => match &data.fields {
16            syn::Fields::Named(f) => fields_token_stream(&f.named),
17            syn::Fields::Unnamed(f) => fields_token_stream(&f.unnamed),
18            syn::Fields::Unit => panic!("Unit structs is not supported by derive"),
19        },
20        syn::Data::Enum(data) => enum_cases_token_stream(id, data),
21        _ => panic!("Fields enumeration derive can only implemented for structs and enums"),
22    };
23
24    let ident = &ast.ident;
25
26    let output = quote! {
27        impl AutoEnumFields for #ident {
28            fn all_fields(&self) -> Vec<Field> {
29                let mut v: Vec<Field> = Vec::new();
30                #fields;
31                v
32            }
33        }
34    };
35
36    output.into()
37}
38
39fn enum_cases_token_stream(
40    ident: &syn::Ident,
41    data_enum: &syn::DataEnum,
42) -> proc_macro2::TokenStream {
43    let matches: Vec<_> = data_enum
44        .variants
45        .iter()
46        .map(|var| arm_tokens(var, ident))
47        .collect();
48
49    quote! {
50        match self {
51            #(#matches)*
52        };
53    }
54}
55
56fn arm_tokens(var: &syn::Variant, enum_id: &syn::Ident) -> proc_macro2::TokenStream {
57    let var_id = &var.ident;
58    let fields_tokens = match &var.fields {
59        syn::Fields::Named(f) => case_fields_tokens(&f.named),
60        syn::Fields::Unnamed(f) => case_fields_tokens(&f.unnamed),
61        syn::Fields::Unit => Vec::new(),
62    };
63
64    if fields_tokens.len() == 0 {
65        return quote! {
66            #enum_id::#var_id => {
67
68            }
69        };
70    }
71
72    let inserts_tokens: Vec<_> = fields_tokens
73        .iter()
74        .map(|field| {
75            quote! {
76                {
77                    let mut tmp = #field.all_fields();
78                    v.append(&mut tmp);
79                }
80            }
81        })
82        .collect();
83
84    quote! {
85        #enum_id::#var_id(#(#fields_tokens),*) => {
86            #(#inserts_tokens)*
87        }
88    }
89}
90
91fn case_fields_tokens(
92    fields: &syn::punctuated::Punctuated<syn::Field, Comma>,
93) -> Vec<proc_macro2::TokenStream> {
94    fields
95        .iter()
96        .enumerate()
97        .map(|(idx, field)| {
98            let ident = &field.ident.as_ref().map(|i| quote! {#i}).unwrap_or({
99                let new_name = format!("field{idx}");
100                let new_id = proc_macro2::Ident::new(&new_name, proc_macro2::Span::call_site());
101                quote! {#new_id}
102            });
103            quote!(#ident)
104        })
105        .collect()
106}
107
108fn fields_token_stream(
109    fields: &syn::punctuated::Punctuated<syn::Field, Comma>,
110) -> proc_macro2::TokenStream {
111    let v: Vec<_> = fields
112        .iter()
113        .filter(|field| match &field.vis {
114            syn::Visibility::Public(_) => true,
115            _ => false,
116        })
117        .enumerate()
118        .map(|(idx, field)| {
119            let ident = &field.ident.as_ref().map(|i| quote! {#i}).unwrap_or({
120                let t = proc_macro2::Literal::usize_unsuffixed(idx);
121                quote! {#t}
122            });
123            let value = quote! {
124                format!("{:?}", self.#ident)
125            };
126            quote! {
127                v.push(Field::new(stringify!(#ident).to_string(), #value));
128            }
129        })
130        .collect();
131
132    quote! {
133        #(#v)*;
134    }
135}