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}