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 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}