mlua_bridge/
lib.rs

1extern crate proc_macro;
2
3use darling::{ast::NestedMeta, FromMeta};
4use ident_case::RenameRule;
5use proc_macro::TokenStream;
6use quote::{quote, ToTokens};
7use syn::{ImplItem, ItemImpl, PatType, ReturnType, Signature, Type, Visibility};
8
9enum MluaReturnType {
10    Void,
11    Primitive,
12    Result,
13}
14
15enum TakesSelf {
16    No,
17    Yes,
18    Mut,
19}
20
21struct ExportedFn {
22    ret: MluaReturnType,
23    takes_self: TakesSelf,
24    is_field: FieldType,
25    sig: Signature,
26}
27
28#[derive(Debug, FromMeta, Default)]
29struct ImplMeta {
30    #[darling(default)]
31    rename_funcs: RenameRule,
32    #[darling(default)]
33    rename_fields: RenameRule,
34    pub_only: Option<()>,
35    no_auto_fields: Option<()>,
36}
37
38fn split_appdata_args(sig: &Signature) -> (Vec<PatType>, Vec<PatType>) {
39    sig.inputs
40        .iter()
41        .filter_map(|x| {
42            if let syn::FnArg::Typed(t) = x {
43                Some(t)
44            } else {
45                None
46            }
47        })
48        .cloned()
49        .filter(|p| p.pat.to_token_stream().to_string() != "lua")
50        .partition(|a| match a.ty.as_ref() {
51            Type::Reference(_) => true,
52            _ => false,
53        })
54}
55
56#[derive(PartialEq)]
57enum FieldType {
58    None,
59    Get,
60    Set,
61}
62
63#[proc_macro_attribute]
64pub fn mlua_bridge(attr: TokenStream, item: TokenStream) -> TokenStream {
65    //TODO: if function returns mlua result: map it to lua
66    //      How to check for correct return type?
67
68    // if function argument is of AppDataRef(Mut) type: fetch it from lua appdata before calling function
69    // if function is `get_` or `set_` and takes no arguments: map to field
70    //TODO: collect type information along the way to generate luals definitions
71
72    //TODO: Write out proper darling error as they're more detailed
73    let impl_meta = NestedMeta::parse_meta_list(attr.into()).expect("Failed to parse attribute");
74    let ImplMeta {
75        pub_only,
76        rename_funcs,
77        rename_fields,
78        no_auto_fields,
79    } = ImplMeta::from_list(&impl_meta).expect("Failed to parse attribute");
80    let pub_only = pub_only.is_some();
81
82    let impl_item = item.clone();
83    let impl_item = syn::parse_macro_input!(impl_item as ItemImpl);
84
85    let mut fns = vec![];
86    let mut consts = vec![];
87
88    for ele in impl_item.items {
89        match ele {
90            ImplItem::Const(c) => consts.push(c),
91            ImplItem::Fn(f) => fns.push(f),
92            _ => continue,
93        }
94    }
95
96    let mut exported_fns = vec![];
97
98    for ele in fns.into_iter() {
99        if pub_only && !matches!(ele.vis, Visibility::Public(_)) {
100            continue;
101        }
102
103        let sig = ele.sig;
104
105        let ret = match &sig.output {
106            ReturnType::Default => MluaReturnType::Void,
107            ReturnType::Type(_, t) => match t.as_ref() {
108                Type::Path(r) => {
109                    if r.path.segments.last().is_some_and(|r| r.ident == "Result") {
110                        MluaReturnType::Result
111                    } else {
112                        MluaReturnType::Primitive
113                    }
114                }
115                _ => MluaReturnType::Primitive,
116            },
117        };
118
119        let takes_self = sig
120            .inputs
121            .first()
122            .map(|a| match a {
123                syn::FnArg::Receiver(r) => {
124                    if r.mutability.is_some() {
125                        TakesSelf::Mut
126                    } else {
127                        TakesSelf::Yes
128                    }
129                }
130                syn::FnArg::Typed(_) => TakesSelf::No,
131            })
132            .unwrap_or(TakesSelf::No);
133
134        let field_incompat_args = sig
135            .inputs
136            .iter()
137            .filter(|x| match x {
138                syn::FnArg::Receiver(_) => false,
139                syn::FnArg::Typed(pat_type) => match pat_type.ty.as_ref() {
140                    Type::Reference(_) => false,
141                    _ => true,
142                },
143            })
144            .count();
145        let fn_name = sig.ident.to_string();
146        let is_field = no_auto_fields.is_none()
147            && fn_name.len() > 4
148            && matches!(&fn_name[..4], "get_" | "set_");
149        let is_field = if is_field {
150            match &fn_name[..4] {
151                "get_" if field_incompat_args == 0 => FieldType::Get,
152                "set_" if field_incompat_args == 1 => FieldType::Set,
153                _ => FieldType::None,
154            }
155        } else {
156            FieldType::None
157        };
158
159        exported_fns.push(ExportedFn {
160            ret,
161            takes_self,
162            is_field,
163            sig,
164        });
165    }
166
167    let (fields, funcs): (Vec<_>, Vec<_>) = exported_fns
168        .into_iter()
169        .partition(|x| x.is_field != FieldType::None);
170
171    let mut funcs_impl = quote! {};
172    let mut fields_impl = quote! {};
173
174    for f in funcs {
175        let name = f.sig.ident.to_token_stream();
176
177        let name = rename_funcs
178            .apply_to_field(name.to_string())
179            .to_token_stream();
180
181        let (app_data, lua_args) = split_appdata_args(&f.sig);
182        let self_name = f.sig.ident;
183        let name = quote! {#name};
184        let rust_args: Vec<PatType> = f
185            .sig
186            .inputs
187            .iter()
188            .cloned()
189            .filter_map(|x| match x {
190                syn::FnArg::Receiver(_) => None,
191                syn::FnArg::Typed(pat_type) => Some(pat_type),
192            })
193            .collect();
194
195        let args_tup = lua_args.iter().map(|x| x.pat.clone());
196        let args_typ = lua_args.iter().map(|x| x.ty.clone());
197        let args_tup = quote! {(#(#args_tup),*)};
198        let args_typ = quote! { (#(#args_typ),*)};
199        let args = quote! {#args_tup: #args_typ};
200
201        let rust_args = rust_args.iter().map(|x| x.pat.clone());
202        let rust_args = quote! {(#(#rust_args),*)};
203        let app_data = app_data.iter().map(|x| {
204            let Type::Reference(ref_type) = x.ty.as_ref() else {
205                unreachable!()
206            };
207
208            let name = x.pat.to_token_stream();
209            let t = ref_type.elem.to_token_stream();
210
211            if ref_type.mutability.is_some() {
212                quote! {let #name = &mut *lua.app_data_mut::<#t>().ok_or(mlua::Error::external("AppData not set"))?; }
213            }
214            else {
215                quote! {let #name = &*lua.app_data_ref::<#t>().ok_or(mlua::Error::external("AppData not set"))?; }
216            }
217        });
218
219        let question = match f.ret {
220            MluaReturnType::Void | MluaReturnType::Primitive => quote! {},
221            MluaReturnType::Result => quote! {?},
222        };
223
224        let method = match &f.takes_self {
225            TakesSelf::No => quote! {add_function_mut},
226            TakesSelf::Yes => quote! {add_method},
227            TakesSelf::Mut => quote! {add_method_mut},
228        };
229
230        let self_ident = match &f.takes_self {
231            TakesSelf::No => quote! {Self::},
232            TakesSelf::Yes | TakesSelf::Mut => quote! {s.},
233        };
234
235        let closure_def = match &f.takes_self {
236            TakesSelf::No => quote! { |lua, #args| },
237            TakesSelf::Yes | TakesSelf::Mut => quote! { |lua, s, #args| },
238        };
239
240        let t = quote! {
241            methods.#method(#name, #closure_def {
242                #(#app_data)*
243
244                 Ok(#self_ident #self_name #rust_args #question)
245                });
246        };
247
248        t.to_tokens(&mut funcs_impl);
249    }
250
251    for f in fields {
252        let name = f.sig.ident.clone();
253        let name = rename_fields.apply_to_field(format!("{}", &name.to_string()[4..]));
254        let self_name = f.sig.ident.clone();
255        let name = quote! {#name};
256
257        let (app_data, lua_args) = split_appdata_args(&f.sig);
258        if lua_args.len() > 1 {
259            panic!("Incalid field signature")
260        }
261
262        let value_name = lua_args
263            .get(0)
264            .map(|x| x.pat.clone().to_token_stream())
265            .unwrap_or_default();
266
267        let rust_args: Vec<PatType> = f
268            .sig
269            .inputs
270            .iter()
271            .cloned()
272            .filter_map(|x| match x {
273                syn::FnArg::Receiver(_) => None,
274                syn::FnArg::Typed(pat_type) => Some(pat_type),
275            })
276            .collect();
277        let rust_args = rust_args.iter().map(|x| x.pat.clone());
278        let rust_args = quote! {(#(#rust_args),*)};
279
280        let app_data = app_data.iter().map(|x| {
281            let Type::Reference(ref_type) = x.ty.as_ref() else {
282                unreachable!()
283            };
284            let name = x.pat.to_token_stream();
285            let t = ref_type.elem.to_token_stream();
286
287            if ref_type.mutability.is_some() {
288                quote! {let #name = &mut *lua.app_data_mut::<#t>().ok_or(mlua::Error::external("AppData not set"))?; }
289            }
290            else {
291                quote! {let #name = &*lua.app_data_ref::<#t>().ok_or(mlua::Error::external("AppData not set"))?; }
292            }
293        });
294
295        let question = match &f.ret {
296            MluaReturnType::Void | MluaReturnType::Primitive => quote! {},
297            MluaReturnType::Result => quote! {?},
298        };
299
300        let self_ident = match &f.takes_self {
301            TakesSelf::No => quote! {Self::},
302            TakesSelf::Yes | TakesSelf::Mut => quote! {s.},
303        };
304
305        let t = match (&f.takes_self, &f.sig.ident.to_string().starts_with("set")) {
306            (TakesSelf::No, true) => quote! {
307                fields.add_field_function_set(#name, |lua, _, #value_name| {
308                    #(#app_data)*
309
310                    Ok(#self_ident #self_name #rust_args #question)});
311            },
312            (TakesSelf::No, false) => quote! {
313                fields.add_field_function_get(#name, |lua, _| {
314                    #(#app_data)*
315
316                    Ok(#self_ident #self_name #rust_args #question)});
317            },
318            (_, true) => quote! {
319                fields.add_field_method_set(#name, |lua, s, #value_name| {
320                    #(#app_data)*
321
322                    Ok(#self_ident #self_name #rust_args #question)});
323
324            },
325            (_, false) => quote! {
326                fields.add_field_method_get(#name, |lua, s| {
327                    #(#app_data)*
328
329                    Ok(#self_ident #self_name #rust_args #question)});
330            },
331        };
332
333        t.to_tokens(&mut fields_impl);
334    }
335
336    for c in consts {
337        let name = c.ident;
338        quote! {
339            fields.add_field_function_get(stringify!(#name)     , |lua, _| Ok(Self::#name));
340        }
341        .to_tokens(&mut fields_impl);
342    }
343
344    let item_ident = impl_item.self_ty.into_token_stream();
345
346    let trait_impl = quote! {
347        impl ::mlua::UserData for #item_ident {
348            fn add_methods<M: ::mlua::UserDataMethods<Self>>(methods: &mut M) {
349                #funcs_impl
350            }
351
352            fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
353                #fields_impl
354            }
355        }
356    };
357    let mut item = proc_macro2::TokenStream::from(item);
358    trait_impl.to_tokens(&mut item);
359
360    item.into()
361}