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}