rbxm_proc/
lib.rs

1use proc_macro::{Span, TokenStream};
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput, Ident, LitStr};
4
5fn match_path(path: &syn::Path, ident: &str) -> bool {
6    path.is_ident(&Ident::new(ident, Span::call_site().into()))
7}
8
9fn to_pascal_case(ident: &Ident) -> LitStr {
10    let ident = ident
11        .to_string()
12        .split('_')
13        .into_iter()
14        .map(|segment| {
15            let mut v = segment.chars().collect::<Vec<_>>();
16            v[0] = v[0].to_uppercase().nth(0).unwrap();
17            v.into_iter().collect::<String>()
18        })
19        .collect::<String>();
20    LitStr::new(&ident, Span::call_site().into())
21}
22
23fn has_attr(attrs: &Vec<syn::Attribute>, name: &str) -> bool {
24    attrs.iter().any(|attr| match_path(&attr.path, name))
25}
26
27#[proc_macro_derive(Inherits)]
28pub fn inherits(item: TokenStream) -> TokenStream {
29    let item = parse_macro_input!(item as DeriveInput);
30    let item_name = &item.ident;
31
32    let fields = match &item.data {
33        syn::Data::Struct(data) => &data.fields,
34        _ => panic!("Inherits not supported on non-structs"),
35    };
36    let named_fields = match fields {
37        syn::Fields::Named(fields) => fields,
38        _ => panic!("Inherits requires named fields"),
39    };
40
41    let field = named_fields.named.first().unwrap();
42
43    let (target_ty, target_name) = (&field.ty, field.ident.as_ref().unwrap());
44
45    let expanded = quote!(
46        impl core::ops::Deref for #item_name {
47            type Target = #target_ty;
48
49            fn deref(&self) -> &Self::Target {
50                &self.#target_name
51            }
52        }
53
54        impl core::ops::DerefMut for #item_name {
55            fn deref_mut(&mut self) -> &mut Self::Target {
56                &mut self.#target_name
57            }
58        }
59    );
60
61    TokenStream::from(expanded)
62}
63
64#[proc_macro_derive(PropertyConvert, attributes(shared, propname))]
65pub fn property_convert(item: TokenStream) -> TokenStream {
66    let item = parse_macro_input!(item as DeriveInput);
67    let item_name = &item.ident;
68
69    let fields = match &item.data {
70        syn::Data::Struct(data) => &data.fields,
71        _ => panic!("PropertyConvert not supported on non-structs"),
72    };
73    let named_fields = match fields {
74        syn::Fields::Named(fields) => fields,
75        _ => panic!("PropertyConvert requires named fields"),
76    };
77
78    let (constructor, destructor): (Vec<_>, Vec<_>) = named_fields
79        .named
80        .iter()
81        .map(|field| {
82            let field_name = &field.ident;
83            let shared = has_attr(&field.attrs, "shared");
84            let prop_name = field.attrs.iter().find(|attr| match_path(&attr.path, "propname")).map(|attr| {
85                let meta = if let syn::Meta::NameValue(value) = attr.parse_meta().unwrap() {
86                    value
87                } else {
88                    panic!()
89                };
90                if let syn::Lit::Str(lit) = meta.lit {
91                    lit
92                } else {
93                    panic!()
94                }
95            }).unwrap_or(to_pascal_case(&field.ident.as_ref().unwrap()));
96
97            let (getter, setter) = (
98                quote!(
99                    crate::serde::internal::FieldFromProperties::from_properties(
100                        crate::serde::internal::FieldAttrs { field_name: stringify!(#field_name), prop_name: #prop_name, shared: #shared },
101                        properties,
102                    )?
103                ),
104                quote!(
105                    crate::serde::internal::FieldToProperties::to_properties(
106                        self.#field_name.clone(),
107                        crate::serde::internal::FieldAttrs { field_name: stringify!(#field_name), prop_name: #prop_name, shared: #shared },
108                        properties,
109                    );
110                ),
111            );
112
113            (quote!(#field_name: #getter), quote!(#setter))
114        })
115        .unzip();
116
117    let expanded = quote! {
118        impl FromProperties for #item_name {
119            fn from_properties(properties: &mut alloc::collections::BTreeMap<String, Property>) -> core::result::Result<Self, crate::SerdeError> {
120                Ok(Self {
121                    #(#constructor),*
122                })
123            }
124        }
125
126        impl ToProperties for #item_name {
127            fn to_properties(&self, properties: &mut alloc::collections::BTreeMap<String, Property>) {
128                #(#destructor;)*
129            }
130        }
131    };
132
133    TokenStream::from(expanded)
134}
135
136#[proc_macro_derive(EnumConvert)]
137pub fn enum_convert(item: TokenStream) -> TokenStream {
138    let item = parse_macro_input!(item as DeriveInput);
139    let item_name = &item.ident;
140
141    let variants = match &item.data {
142        syn::Data::Enum(data) => &data.variants,
143        _ => panic!("TryFrom not supported on non-enums"),
144    };
145
146    let mut last_discrim = 0;
147
148    let variant_match = variants
149        .iter()
150        .map(|var| {
151            let var_name = &var.ident;
152            let discrim: i32 = var
153                .discriminant
154                .as_ref()
155                .map(|(_, expr)| match expr {
156                    syn::Expr::Lit(expr_lit) => {
157                        if let syn::Lit::Int(val) = &expr_lit.lit {
158                            val.base10_parse().unwrap()
159                        } else {
160                            panic!("Discriminant wasn't an integer literal")
161                        }
162                    }
163                    _ => panic!("Discriminant wasn't a literal value"),
164                })
165                .unwrap_or_else(|| last_discrim + 1);
166
167            last_discrim = discrim;
168
169            quote!( #discrim => Ok(Self::#var_name) )
170        })
171        .collect::<Vec<_>>();
172
173    let expanded = quote! {
174        impl core::convert::TryFrom<i32> for #item_name {
175            type Error = ();
176
177            fn try_from(val: i32) -> core::result::Result<Self, ()> {
178                match val {
179                    #(#variant_match,)*
180                    _ => Err(()),
181                }
182            }
183        }
184
185        impl core::convert::From<#item_name> for i32 {
186            fn from(i: #item_name) -> Self {
187                i as i32
188            }
189        }
190
191        impl crate::serde::internal::FieldFromProperties for #item_name {
192            fn from_properties(
193                attrs: crate::serde::internal::FieldAttrs,
194                properties: &mut alloc::collections::BTreeMap<alloc::string::String, crate::model::Property>
195            ) -> crate::serde::error::Result<Self> {
196                match properties.remove(attrs.prop_name) {
197                    Some(crate::model::Property::Enum(val)) => Self::try_from(val)
198                        .map_err(|_| crate::SerdeError::unknown_variant(val)),
199                    Some(prop) => Err(crate::SerdeError::wrong_property_type(
200                        alloc::string::String::from(attrs.prop_name),
201                        Some((crate::model::property::PropertyType::Enum, prop.kind())),
202                    )),
203                    None => Err(crate::SerdeError::missing_property(alloc::string::String::from(attrs.prop_name))),
204                }
205            }
206        }
207
208        impl crate::serde::internal::FieldToProperties for #item_name {
209            fn to_properties(
210                self,
211                attrs: crate::serde::internal::FieldAttrs,
212                properties: &mut alloc::collections::BTreeMap<alloc::string::String, crate::model::Property>
213            ) {
214                properties.insert(alloc::string::String::from(attrs.prop_name), crate::model::Property::Enum(self.into()));
215            }
216        }
217    };
218
219    TokenStream::from(expanded)
220}
221
222struct InstanceResult {
223    class_name: proc_macro2::TokenStream,
224    name: proc_macro2::TokenStream,
225    from_props: proc_macro2::TokenStream,
226    to_props: proc_macro2::TokenStream,
227}
228
229impl InstanceResult {
230    fn unzip(
231        results: Vec<InstanceResult>,
232    ) -> (
233        Vec<proc_macro2::TokenStream>,
234        Vec<proc_macro2::TokenStream>,
235        Vec<proc_macro2::TokenStream>,
236        Vec<proc_macro2::TokenStream>,
237    ) {
238        results.into_iter().fold(
239            (Vec::new(), Vec::new(), Vec::new(), Vec::new()),
240            |(mut classes, mut names, mut from_props, mut to_props), this| {
241                classes.push(this.class_name);
242                names.push(this.name);
243                from_props.push(this.from_props);
244                to_props.push(this.to_props);
245                (classes, names, from_props, to_props)
246            },
247        )
248    }
249}
250
251#[proc_macro_derive(InstanceExtra)]
252pub fn instance_extra(item: TokenStream) -> TokenStream {
253    let item = parse_macro_input!(item as DeriveInput);
254    let item_name = &item.ident;
255
256    let variants = match &item.data {
257        syn::Data::Enum(data) => &data.variants,
258        _ => panic!("InstanceExtra not supported on non-enums"),
259    };
260
261    let results = variants
262        .iter()
263        .map(|variant| {
264            let variant_name = &variant.ident;
265
266            // Other is assumed to be the last variant in the enum
267            if &variant_name.to_string() == "Other" {
268                return InstanceResult {
269                    class_name: quote!(#item_name::Other(class_name, _) => return class_name.clone()),
270                    name: quote!(#item_name::Other(_, attrs) => {
271                        if let Property::TextString(name) = attrs.get("Name").expect("Instance didn't have a name") {
272                            name
273                        } else {
274                            panic!("Instance didn't have a Name")
275                        }
276                    }),
277                    from_props: quote!(_ => {
278                        // eprintln!("Other ty: {}", kind);
279                        // eprintln!("    Properties: {:?}", properties);
280                        let kind = Instance::Other(
281                            String::from(kind),
282                            properties
283                                .iter()
284                                .map(|(key, val)| (key.clone(), val.clone()))
285                                .collect(),
286                        );
287                        properties.clear();
288                        kind
289                    }),
290                    to_props: quote!(#item_name::Other(_, attrs) => properties.extend(attrs.clone())),
291                };
292            }
293
294            let fields = if let syn::Fields::Unnamed(fields) = &variant.fields {
295                fields
296            } else {
297                panic!("Expected unnamed enum fields");
298            };
299
300            let class_name_str = variant_name.to_string();
301            let is_boxed = match &fields.unnamed[0].ty {
302                syn::Type::Path(path) => path.path.segments[0].ident.to_string() == "Box",
303                _ => panic!("Unexpected type in Variant"),
304            };
305
306            let class_name = quote!(#item_name::#variant_name(..) => #class_name_str);
307            let name = quote!(#item_name::#variant_name(data) => &data.name);
308            let from_props = if is_boxed {
309                quote!(#class_name_str => #item_name::#variant_name(alloc::boxed::Box::new(#variant_name::from_properties(&mut properties)?)))
310            } else {
311                quote!(#class_name_str => #item_name::#variant_name(#variant_name::from_properties(&mut properties)?))
312            };
313            let to_props = quote!(#item_name::#variant_name(data) => data.to_properties(&mut properties));
314
315            InstanceResult {
316                class_name,
317                name,
318                from_props,
319                to_props
320            }
321        })
322        .collect();
323
324    let (class_names, names, from_props, to_props) = InstanceResult::unzip(results);
325
326    let expanded = quote! {
327        impl #item_name {
328            /// Get the name of the class for this kind
329            #[must_use]
330            pub fn class_name(&self) -> String {
331                String::from(match self {
332                    #(#class_names),*
333                })
334            }
335
336            /// Get the name of this kind
337            ///
338            /// # Panics
339            ///
340            /// If the instance is of an unrecognized type which doesn't have a name.
341            #[must_use]
342            pub fn name(&self) -> &str {
343                match self {
344                    #(#names),*
345                }
346            }
347
348            pub(crate) fn make_instance(kind: &str, mut properties: BTreeMap<String, Property>) -> Result<Instance, crate::SerdeError> {
349                let out = match kind {
350                    #(#from_props),*
351                };
352
353                if properties.is_empty() {
354                    Ok(out)
355                } else {
356                    Err(crate::SerdeError::unconsumed_properties(
357                        out.class_name(),
358                        properties.into_iter().map(|(keys, _)| keys).collect(),
359                    ))
360                }
361            }
362
363            pub(crate) fn break_instance(&self) -> BTreeMap<String, Property> {
364                let mut properties = BTreeMap::new();
365                match self {
366                    #(#to_props),*
367                }
368                properties
369            }
370        }
371    };
372
373    TokenStream::from(expanded)
374}