rusty_value_derive/
lib.rs1use 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 = ¶m.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}