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