rusty_value_derive/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::{
5    parse_macro_input, parse_quote, DataEnum, DataStruct, DeriveInput, FieldsNamed, FieldsUnnamed,
6    Generics, Variant, WhereClause, WherePredicate,
7};
8
9#[proc_macro_derive(RustyValue)]
10pub fn derive_value(input: TokenStream) -> TokenStream {
11    derive(parse_macro_input!(input as DeriveInput))
12}
13
14fn derive(input: DeriveInput) -> TokenStream {
15    match &input.data {
16        syn::Data::Struct(s) => derive_struct(&input, s),
17        syn::Data::Enum(e) => derive_enum(&input, e),
18        syn::Data::Union(_) => panic!("unions are currently unsupported"),
19    }
20}
21
22fn derive_struct(input: &DeriveInput, struct_data: &DataStruct) -> TokenStream {
23    let ident = &input.ident;
24    let name = ident.to_string();
25    let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
26    let where_clause = add_rusty_bound(&input.generics);
27
28    match &struct_data.fields {
29        syn::Fields::Named(FieldsNamed { named, .. }) => {
30            let field_idents = named.iter().map(|f| f.ident.as_ref()).collect::<Vec<_>>();
31            let field_names = named
32                .iter()
33                .map(|f| f.ident.as_ref().unwrap().to_string())
34                .collect::<Vec<_>>();
35            let field_count = named.len();
36
37            TokenStream::from(quote! {
38                impl #impl_generics RustyValue for #ident #ty_generics #where_clause {
39                    fn into_rusty_value(self) -> Value {
40                        let mut values = std::collections::HashMap::with_capacity(#field_count);
41
42                        #(
43                            values.insert(#field_names.to_string(), self.#field_idents.into_rusty_value());
44                        )*
45
46                        Value::Struct(Struct{
47                            name: #name.to_string(),
48                            fields: Fields::Named(values),
49                        })
50                    }
51                }
52            })
53        }
54        syn::Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
55            let field_indices = unnamed
56                .iter()
57                .enumerate()
58                .map(|(i, _)| syn::Index::from(i))
59                .collect::<Vec<_>>();
60            let field_count = unnamed.len();
61
62            TokenStream::from(quote! {
63                impl #impl_generics RustyValue for #ident #ty_generics #where_clause {
64                    fn into_rusty_value(self) -> Value {
65                        let mut values = Vec::with_capacity(#field_count);
66
67                        #(
68                            values.push(self.#field_indices.into_rusty_value());
69                        )*
70
71                        Value::Struct(Struct{
72                            name: #name.to_string(),
73                            fields: Fields::Unnamed(values),
74                        })
75                    }
76                }
77            })
78        }
79        syn::Fields::Unit => TokenStream::from(quote! {
80                impl #impl_generics RustyValue for #ident #ty_generics #where_clause {
81                    fn into_rusty_value(self) -> Value {
82                        Value::Struct(Struct{
83                            name: #name.to_string(),
84                            fields: Fields::Unit,
85                        })
86                    }
87                }
88        }),
89    }
90}
91
92fn derive_enum(input: &DeriveInput, enum_data: &DataEnum) -> TokenStream {
93    let ident = &input.ident;
94    let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
95    let where_clause = add_rusty_bound(&input.generics);
96    let variant_matchers = enum_data
97        .variants
98        .iter()
99        .map(|v| create_enum_value_match(ident, v))
100        .collect::<Vec<_>>();
101
102    TokenStream::from(quote! {
103        impl #impl_generics RustyValue for #ident #ty_generics #where_clause {
104            fn into_rusty_value(self) -> Value {
105                let enum_val = match self {
106                    #( #variant_matchers )*
107                };
108                Value::Enum(enum_val)
109            }
110        }
111    })
112}
113
114fn create_enum_value_match(ident: &syn::Ident, variant: &Variant) -> proc_macro2::TokenStream {
115    let enum_name = ident.to_string();
116    let variant_ident = &variant.ident;
117    let variant_name = variant_ident.to_string();
118
119    match &variant.fields {
120        syn::Fields::Named(FieldsNamed { named, .. }) => {
121            let field_idents = named.iter().map(|f| &f.ident).collect::<Vec<_>>();
122            let field_names = named
123                .iter()
124                .map(|f| f.ident.as_ref().unwrap().to_string())
125                .collect::<Vec<_>>();
126            let field_count = named.len();
127
128            quote! {
129                #ident::#variant_ident { #( #field_idents, )* } => {
130                    let mut fields = std::collections::HashMap::with_capacity(#field_count);
131                    #(
132                        fields.insert(#field_names.to_string(), #field_idents.into_rusty_value());
133                    )*
134                    Enum {
135                        name: #enum_name.to_string(),
136                        variant: #variant_name.to_string(),
137                        fields: Fields::Named(fields)
138                    }
139                }
140            }
141        }
142        syn::Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
143            let field_names = unnamed
144                .iter()
145                .enumerate()
146                .map(|(i, _)| syn::Ident::new(&format!("f{i}"), Span::call_site()))
147                .collect::<Vec<_>>();
148            let field_count = unnamed.len();
149
150            quote! {
151                #ident::#variant_ident ( #( #field_names, )* ) => {
152                    let mut fields = Vec::with_capacity(#field_count);
153                    #(
154                        fields.push(#field_names.into_rusty_value());
155                    )*
156                    Enum {
157                        name: #enum_name.to_string(),
158                        variant: #variant_name.to_string(),
159                        fields: Fields::Unnamed(fields)
160                    }
161                }
162            }
163        }
164        syn::Fields::Unit => quote! {
165            #ident::#variant_ident => {
166                Enum {
167                    name: #enum_name.to_string(),
168                    variant: #variant_name.to_string(),
169                    fields: Fields::Unit
170                }
171            }
172        },
173    }
174}
175
176fn add_rusty_bound(generics: &Generics) -> WhereClause {
177    let trait_bound: proc_macro2::TokenStream = parse_quote!(rusty_value::RustyValue);
178
179    let new_predicates = generics.type_params().map::<WherePredicate, _>(|param| {
180        let param = &param.ident;
181        parse_quote!(#param : #trait_bound)
182    });
183
184    let mut generics = generics.clone();
185    generics
186        .make_where_clause()
187        .predicates
188        .extend(new_predicates);
189    generics.where_clause.unwrap()
190}