Skip to main content

haste_reflect_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Attribute, Data, DeriveInput, Expr, Field, Lit, Meta, parse_macro_input};
4
5fn get_attribute_rename(attrs: &[Attribute]) -> Option<String> {
6    attrs.iter().find_map(|attr| match &attr.meta {
7        Meta::NameValue(name_value) => {
8            if name_value.path.is_ident("rename_field") {
9                match &name_value.value {
10                    Expr::Lit(lit) => match &lit.lit {
11                        Lit::Str(lit) => Some(lit.value()),
12                        _ => panic!("Expected a string literal"),
13                    },
14                    _ => panic!("Expected a string literal"),
15                }
16            } else {
17                None
18            }
19        }
20        _ => None,
21    })
22}
23
24fn is_optional(field: &Field) -> bool {
25    if let syn::Type::Path(type_path) = &field.ty
26        && let Some(segment) = type_path.path.segments.first()
27    {
28        return segment.ident == "Option";
29    }
30    false
31}
32
33fn get_attribute_fhir_type(attrs: &[Attribute]) -> Option<String> {
34    attrs.iter().find_map(|attr| match &attr.meta {
35        Meta::NameValue(name_value) => {
36            if name_value.path.is_ident("fhir_type") {
37                match &name_value.value {
38                    Expr::Lit(lit) => match &lit.lit {
39                        Lit::Str(lit) => Some(lit.value()),
40                        _ => panic!("Expected a string literal"),
41                    },
42                    _ => panic!("Expected a string literal"),
43                }
44            } else {
45                None
46            }
47        }
48        _ => None,
49    })
50}
51
52fn expand_struct(input: &DeriveInput, data: &syn::DataStruct) -> TokenStream {
53    let fhir_type = get_attribute_fhir_type(&input.attrs);
54    let all_fields = data
55        .fields
56        .iter()
57        .map(|field| field.ident.as_ref().unwrap().to_string());
58
59    let name = &input.ident;
60
61    let accessors = data.fields.iter().map(|field| {
62        let renamed = get_attribute_rename(&field.attrs);
63        let name = renamed.unwrap_or_else(|| field.ident.as_ref().unwrap().to_string());
64
65        let accessor = field.ident.clone().unwrap();
66
67        if is_optional(field) {
68            quote! {
69                #name => if let Some(v) = self.#accessor.as_ref() {
70                    Some(v)
71                } else {
72                    None
73                }
74            }
75        } else {
76            quote! {
77                #name => Some(&self.#accessor)
78            }
79        }
80    });
81
82    let mutable_accessor = data.fields.iter().map(|field| {
83        let renamed = get_attribute_rename(&field.attrs);
84        let name = renamed.unwrap_or_else(|| field.ident.as_ref().unwrap().to_string());
85
86        let accessor = field.ident.clone().unwrap();
87
88        quote! {
89            #name => Some(&mut self.#accessor)
90        }
91    });
92
93    quote! {
94        impl haste_reflect::MetaValue for #name {
95            fn fields(&self) -> Vec<&'static str> {
96                vec![#(#all_fields),*]
97            }
98
99            fn get_field<'a>(&'a self, field: &str) -> Option<&'a dyn MetaValue> {
100                match field {
101                    #(#accessors),*,
102                    _ => None,
103                }
104            }
105
106            fn get_field_mut<'a>(&'a mut self, field: &str) -> Option<&'a mut dyn MetaValue> {
107                match field {
108                    #(#mutable_accessor),*,
109                    _ => None,
110                }
111            }
112
113            fn get_index_mut<'a>(&'a mut self, _index: usize) -> Option<&'a mut dyn MetaValue> {
114                None
115            }
116
117            fn get_index<'a>(&'a self, _index: usize) -> Option<&'a dyn MetaValue> {
118                None
119            }
120
121            fn fhir_type(&self) -> &'static str {
122                #fhir_type
123            }
124
125            fn as_any(&self) -> &dyn std::any::Any {
126                self
127            }
128
129            fn flatten(&self) -> Vec<&dyn MetaValue> {
130                vec![self]
131            }
132
133            fn is_many(&self) -> bool {
134                false
135            }
136        }
137    }
138    .into()
139}
140
141fn expand_enum(input: &DeriveInput, data: &syn::DataEnum) -> TokenStream {
142    let enum_name = &input.ident;
143
144    let variants_fields = data.variants.iter().map(|variant| {
145        let name = variant.ident.clone();
146        quote! { Self::#name(k) => k.fields() }
147    });
148
149    let variants_get_field = data.variants.iter().map(|variant| {
150        let name = variant.ident.clone();
151        quote! { Self::#name(k) => k.get_field(field) }
152    });
153
154    let variants_get_index = data.variants.iter().map(|variant| {
155        let name = variant.ident.clone();
156        quote! { Self::#name(k) => k.get_index(field) }
157    });
158
159    let variants_get_field_mut = data.variants.iter().map(|variant| {
160        let name = variant.ident.clone();
161        quote! { Self::#name(k) => k.get_field_mut(field) }
162    });
163
164    let variants_get_index_mut = data.variants.iter().map(|variant| {
165        let name = variant.ident.clone();
166        quote! { Self::#name(k) => k.get_index_mut(index) }
167    });
168
169    let variants_as_any = data.variants.iter().map(|variant| {
170        let name = variant.ident.clone();
171        quote! { Self::#name(k) => k.as_any() }
172    });
173
174    let variants_flatten = data.variants.iter().map(|variant| {
175        let name = variant.ident.clone();
176        quote! { Self::#name(k) => k.flatten() }
177    });
178
179    let variants_fhir_type = data.variants.iter().map(|variant| {
180        let name = variant.ident.clone();
181        quote! { Self::#name(k) => k.fhir_type() }
182    });
183
184    quote! {
185        impl haste_reflect::MetaValue for #enum_name {
186            fn fields(&self) -> Vec<&'static str> {
187                match self {
188                    #(#variants_fields),*
189                }
190            }
191
192            fn get_field<'a>(&'a self, field: &str) -> Option<&'a dyn MetaValue> {
193                match self {
194                    #(#variants_get_field),*
195                }
196            }
197
198            fn get_index<'a>(&'a self, field: usize) -> Option<&'a dyn MetaValue> {
199                match self {
200                    #(#variants_get_index),*
201                }
202            }
203
204            fn get_field_mut<'a>(&'a mut self, field: &str) -> Option<&'a mut dyn MetaValue> {
205                match self {
206                    #(#variants_get_field_mut),*
207                }
208            }
209
210            fn get_index_mut<'a>(&'a mut self, index: usize) -> Option<&'a mut dyn MetaValue> {
211                match self {
212                    #(#variants_get_index_mut),*
213                }
214            }
215
216            fn fhir_type(&self) -> &'static str {
217                match self {
218                    #(#variants_fhir_type),*
219                }
220            }
221
222            fn as_any(&self) -> &dyn std::any::Any {
223                match self {
224                    #(#variants_as_any),*
225                }
226            }
227
228            fn flatten(&self) -> Vec<&dyn MetaValue> {
229                match self {
230                    #(#variants_flatten),*
231                }
232            }
233
234            fn is_many(&self) -> bool {
235                false
236            }
237        }
238    }
239    .into()
240}
241
242#[proc_macro_derive(Reflect, attributes(rename_field, fhir_type))]
243pub fn haste_reflect(input: TokenStream) -> TokenStream {
244    let input = parse_macro_input!(input as DeriveInput);
245
246    match input.data {
247        Data::Struct(ref data) => expand_struct(&input, data),
248        Data::Enum(ref data) => expand_enum(&input, data),
249        Data::Union(_) => todo!("Union not supported"),
250    }
251}