mlua_proc_macro/
lib.rs

1use quote::quote;
2use syn::{spanned::Spanned, Fields, ItemStruct};
3
4#[proc_macro_derive(ToTable, attributes(table))]
5pub fn convert_to_table(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
6    convert_to_table_impl(syn::parse_macro_input!(input))
7}
8
9fn convert_to_table_impl(input: ItemStruct) -> proc_macro::TokenStream {
10    let Fields::Named(fields) = &input.fields else {
11        return quote!(compile_error!("expected struct with named fields");).into();
12    };
13
14    let mut statements = vec![];
15
16    'field_gen: for field in &fields.named {
17        //If BOTH are true then that means that there is invalid use of the attributes, and we should create a compiler error
18
19        //This is set to true by the closure if we want to skip this entry
20        let mut should_skip = false;
21        //This is set to true by the closure if we want to save this entry
22        let mut should_save = false;
23        //This is set to true if the user has skipped the serde serialization, this means that we would skip this table entry, but if the user manually saves it by ```#[table(save)]``` we save that entry
24        let mut should_skip_serde = false;
25
26        //If there was the skip attr present we continue, so that we dont generate code for this field
27        for attr in &field.attrs {
28            //We can manually skip an entry or if there is a serde skip ettribute that means that it cant be turned into a lua table entry anyway
29            if attr.path().is_ident("table") || attr.path().is_ident("serde") {
30                //Check if there is a skip attribute in the table or serde attribute
31                match attr.parse_nested_meta(|meta| {
32                    if meta.path.is_ident("save") {
33                        //If the user wants to save then we let them xd
34                        should_save = true;
35
36                        if should_save && should_skip {
37                            return Err(syn::Error::new(
38                                attr.span(),
39                                "You can only save or skip a field.",
40                            ));
41                        }
42                    }
43
44                    if meta.path.is_ident("skip") {
45                        if attr.path().is_ident("serde") {
46                            should_skip_serde = true;
47                        }
48                        //If attr.path().is_ident("table")
49                        else {
50                            //If the user wants to skip then we set the bool
51                            should_skip = true;
52
53                            if should_save && should_skip {
54                                return Err(syn::Error::new(
55                                    attr.span(),
56                                    "You can only save or skip a field.",
57                                ));
58                            }
59                        }
60                    }
61
62                    Ok(())
63                }) {
64                    Ok(_) => {}
65                    Err(_) => {
66                        return quote!(compile_error!("You can only save or skip a field.");).into()
67                    }
68                };
69            }
70        }
71
72        //We should only check for this after we have iterated over the attributes of said field
73        //If we should skip this entry
74        if should_skip || (should_skip_serde && !should_save) {
75            continue 'field_gen;
76        }
77
78        //Name of the entry
79        let ident = field.ident.as_ref().unwrap();
80
81        //Name of the entry as string
82        let string = ident.to_string();
83
84        //Check if the field supports serde
85        quote! {
86            match serde_json::to_string(&self.#ident) {
87                Ok(_) => {TokenStream::new()},
88                Err(err) => {syn::Error::into_compiler_error},
89            }
90        };
91
92        //Statement, this is the code representation of the automaticly added line
93        let statement = quote! {
94            fields.add_field_method_get(#string, |_, this| { Ok(serde_json::to_string(&this.#ident).unwrap()) });
95        };
96
97        //Back up all the statements
98        statements.push(statement);
99    }
100
101    let name = &input.ident;
102    let name_as_string = name.to_string().to_lowercase();
103    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
104
105    //Create function
106    quote! {
107        impl mlua::UserData for #impl_generics #name #ty_generics #where_clause {
108            fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) {
109                #(#statements)*
110            }
111        }
112
113        impl #impl_generics #name #ty_generics #where_clause {
114            pub fn set_lua_table_function(self, lua: &Lua) {
115                lua.globals().set(#name_as_string, self).unwrap();
116            }
117        }
118    }
119    .into()
120}