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