Skip to content

Commit 1d4a756

Browse files
committed
mlua_derive: Support async userdata methods in macro
1 parent 6e7d6c7 commit 1d4a756

13 files changed

Lines changed: 418 additions & 25 deletions

mlua_derive/src/userdata/mod.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ use syn::{Attribute, Data, DeriveInput, Error, Fields, FieldsNamed, Meta, parse_
77

88
use self::attr::LuaAttr;
99

10+
/// Wrap registration tokens with any `#[cfg]`/`#[cfg_attr]` attributes from the original item.
11+
pub(crate) fn with_cfg(tokens: proc_macro2::TokenStream, attrs: &[Attribute]) -> proc_macro2::TokenStream {
12+
let cfgs: Vec<_> = (attrs.iter())
13+
.filter(|attr| attr.path().is_ident("cfg") || attr.path().is_ident("cfg_attr"))
14+
.collect();
15+
if cfgs.is_empty() {
16+
return tokens;
17+
}
18+
quote! {
19+
#(#cfgs)*
20+
#tokens
21+
}
22+
}
23+
1024
/// Parse all `#[lua(...)]` attributes on a field, merging them into one `LuaAttr`.
1125
fn parse_field_lua_attr(attrs: &[Attribute]) -> syn::Result<LuaAttr> {
1226
let mut lua_attr = LuaAttr::default();
@@ -85,17 +99,19 @@ pub fn userdata_type(attr: TokenStream, item: TokenStream) -> TokenStream {
8599
};
86100

87101
if has_get {
88-
field_registrations.push(quote! {
102+
let tokens = quote! {
89103
registry.add_field_method_get(#lua_name, |_lua, this| Ok(this.#field_name.clone()));
90-
});
104+
};
105+
field_registrations.push(with_cfg(tokens, &field.attrs));
91106
}
92107
if has_set {
93-
field_registrations.push(quote! {
108+
let tokens = quote! {
94109
registry.add_field_method_set(#lua_name, |_lua, this, val| {
95110
this.#field_name = val;
96111
Ok(())
97112
});
98-
});
113+
};
114+
field_registrations.push(with_cfg(tokens, &field.attrs));
99115
}
100116
}
101117

mlua_derive/src/userdata/userdata_impl.rs

Lines changed: 159 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use syn::{
88
};
99

1010
use super::attr::LuaAttr;
11+
use super::with_cfg;
1112

1213
/// `&T` reference types that mlua provides as wrapper types via `FromLua`.
1314
static BORROW_WRAPPERS: &[(&str, &str)] = &[
@@ -235,13 +236,15 @@ pub fn userdata_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
235236
let const_name = &const_item.ident;
236237
let lua_name = lua_attr.name(const_name);
237238
if lua_attr.meta {
238-
registration_calls.push(quote! {
239+
let tokens = quote! {
239240
registry.add_meta_field(#lua_name, #type_path::#const_name);
240-
});
241+
};
242+
registration_calls.push(with_cfg(tokens, &const_item.attrs));
241243
} else {
242-
registration_calls.push(quote! {
244+
let tokens = quote! {
243245
registry.add_field(#lua_name, #type_path::#const_name);
244-
});
246+
};
247+
registration_calls.push(with_cfg(tokens, &const_item.attrs));
245248
}
246249
}
247250
ImplItem::Fn(method) => {
@@ -273,8 +276,14 @@ pub fn userdata_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
273276

274277
let fn_name = &method.sig.ident;
275278
let info = try_compile!(analyze_self_and_args(&method.sig));
279+
let is_async = method.sig.asyncness.is_some();
276280

277281
if lua_attr.getter {
282+
if is_async {
283+
return syn::Error::new_spanned(&method.sig, "async field getter is not supported")
284+
.to_compile_error()
285+
.into();
286+
}
278287
if !matches!(info.self_kind, SelfKind::Ref(RefKind::Ref)) {
279288
return syn::Error::new_spanned(&method.sig, "field getter must take `&self`")
280289
.to_compile_error()
@@ -288,10 +297,16 @@ pub fn userdata_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
288297
.to_compile_error()
289298
.into();
290299
}
291-
registration_calls.push(gen_field_getter(type_path, fn_name, &lua_attr, &info));
300+
let tokens = gen_field_getter(type_path, fn_name, &lua_attr, &info);
301+
registration_calls.push(with_cfg(tokens, &method.attrs));
292302
continue;
293303
}
294304
if lua_attr.setter {
305+
if is_async {
306+
return syn::Error::new_spanned(&method.sig, "async field setter is not supported")
307+
.to_compile_error()
308+
.into();
309+
}
295310
if !matches!(info.self_kind, SelfKind::Ref(_)) {
296311
return syn::Error::new_spanned(&method.sig, "field setter must take `&[mut] self`")
297312
.to_compile_error()
@@ -305,24 +320,32 @@ pub fn userdata_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
305320
.to_compile_error()
306321
.into();
307322
}
308-
registration_calls.push(gen_field_setter(type_path, fn_name, &lua_attr, &info));
323+
let tokens = gen_field_setter(type_path, fn_name, &lua_attr, &info);
324+
registration_calls.push(with_cfg(tokens, &method.attrs));
309325
continue;
310326
}
311327
if lua_attr.field {
328+
if is_async {
329+
return syn::Error::new_spanned(&method.sig, "async field function is not supported")
330+
.to_compile_error()
331+
.into();
332+
}
312333
if !matches!(info.self_kind, SelfKind::None) {
313334
return syn::Error::new_spanned(&method.sig, "field function must not take `self`")
314335
.to_compile_error()
315336
.into();
316337
}
317338
let lua_name = lua_attr.name(fn_name);
318339
if lua_attr.meta {
319-
registration_calls.push(quote! {
320-
registry.add_meta_field(#lua_name, #type_path::#fn_name());
321-
});
340+
let tokens = quote! {
341+
registry.add_meta_field(#lua_name, #type_path::#fn_name);
342+
};
343+
registration_calls.push(with_cfg(tokens, &method.attrs));
322344
} else {
323-
registration_calls.push(quote! {
345+
let tokens = quote! {
324346
registry.add_field(#lua_name, #type_path::#fn_name());
325-
});
347+
};
348+
registration_calls.push(with_cfg(tokens, &method.attrs));
326349
}
327350
continue;
328351
}
@@ -336,11 +359,23 @@ pub fn userdata_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
336359
.to_compile_error()
337360
.into();
338361
}
339-
registration_calls.push(gen_meta(type_path, fn_name, &lua_attr, &info));
362+
if is_async {
363+
let tokens = gen_async_meta(type_path, fn_name, &lua_attr, &info);
364+
registration_calls.push(with_cfg(tokens, &method.attrs));
365+
} else {
366+
let tokens = gen_meta(type_path, fn_name, &lua_attr, &info);
367+
registration_calls.push(with_cfg(tokens, &method.attrs));
368+
}
340369
continue;
341370
}
342371

343-
registration_calls.push(gen_regular_method(type_path, fn_name, &lua_attr, &info));
372+
if is_async {
373+
let tokens = gen_async_regular_method(type_path, fn_name, &lua_attr, &info);
374+
registration_calls.push(with_cfg(tokens, &method.attrs));
375+
} else {
376+
let tokens = gen_regular_method(type_path, fn_name, &lua_attr, &info);
377+
registration_calls.push(with_cfg(tokens, &method.attrs));
378+
}
344379
}
345380
_ => {}
346381
}
@@ -417,6 +452,33 @@ fn gen_call_args(info: &MethodInfo) -> TokenStream2 {
417452
quote! { #(#call_args),* }
418453
}
419454

455+
/// Generate call arguments for invoking the original async method.
456+
fn gen_async_call_args(info: &MethodInfo) -> TokenStream2 {
457+
let mut call_args: Vec<TokenStream2> = Vec::new();
458+
459+
match info.self_kind {
460+
SelfKind::None => {}
461+
SelfKind::Ref(RefKind::Ref) => call_args.push(quote! { &this }),
462+
SelfKind::Ref(RefKind::Mut) => call_args.push(quote! { &mut this }),
463+
SelfKind::Owned => call_args.push(quote! { this }),
464+
}
465+
466+
if info.has_lua {
467+
call_args.push(quote! { lua });
468+
}
469+
470+
for arg in &info.args {
471+
let ident = &arg.ident;
472+
match arg.userdata_ref {
473+
Some(RefKind::Ref) => call_args.push(quote! { &*#ident }),
474+
Some(RefKind::Mut) => call_args.push(quote! { &mut *#ident }),
475+
None => call_args.push(quote! { #ident }),
476+
}
477+
}
478+
479+
quote! { #(#call_args),* }
480+
}
481+
420482
/// Generate the closure params for the registration callback.
421483
fn gen_closure_params(info: &MethodInfo) -> TokenStream2 {
422484
let destructure = gen_closure_destructure(info);
@@ -426,6 +488,16 @@ fn gen_closure_params(info: &MethodInfo) -> TokenStream2 {
426488
}
427489
}
428490

491+
/// Generate the closure params for an async registration callback.
492+
fn gen_async_closure_params(info: &MethodInfo) -> TokenStream2 {
493+
let destructure = gen_closure_destructure(info);
494+
match info.self_kind {
495+
SelfKind::None => quote! { |lua, #destructure| },
496+
SelfKind::Ref(RefKind::Mut) => quote! { |lua, mut this, #destructure| },
497+
_ => quote! { |lua, this, #destructure| },
498+
}
499+
}
500+
429501
fn gen_field_getter(
430502
type_path: &syn::Path,
431503
fn_name: &Ident,
@@ -549,3 +621,77 @@ fn gen_regular_method(
549621
},
550622
}
551623
}
624+
625+
fn gen_async_regular_method(
626+
type_path: &syn::Path,
627+
fn_name: &Ident,
628+
lua_attr: &LuaAttr,
629+
info: &MethodInfo,
630+
) -> TokenStream2 {
631+
let fn_path = quote! { #type_path::#fn_name };
632+
let closure_params = gen_async_closure_params(info);
633+
let call_args = gen_async_call_args(info);
634+
let lua_name = lua_attr.name(fn_name);
635+
636+
let body = if lua_attr.infallible {
637+
quote! { async move { Ok(#fn_path(#call_args).await) } }
638+
} else {
639+
quote! { async move { #fn_path(#call_args).await } }
640+
};
641+
match info.self_kind {
642+
SelfKind::Ref(RefKind::Ref) => quote! {
643+
registry.add_async_method(#lua_name, #closure_params #body);
644+
},
645+
SelfKind::Ref(RefKind::Mut) => quote! {
646+
registry.add_async_method_mut(#lua_name, #closure_params #body);
647+
},
648+
SelfKind::Owned => quote! {
649+
registry.add_async_method_once(#lua_name, #closure_params #body);
650+
},
651+
SelfKind::None => quote! {
652+
registry.add_async_function(#lua_name, #closure_params #body);
653+
},
654+
}
655+
}
656+
657+
fn gen_async_meta(
658+
type_path: &syn::Path,
659+
fn_name: &Ident,
660+
lua_attr: &LuaAttr,
661+
info: &MethodInfo,
662+
) -> TokenStream2 {
663+
let meta_name = match lua_attr.effective_meta_name(fn_name) {
664+
Ok(name) => name,
665+
Err(err) => return err.to_compile_error(),
666+
};
667+
let closure_params = if matches!(info.self_kind, SelfKind::None) {
668+
if info.args.is_empty() {
669+
quote! { |lua, _this: ::mlua::AnyUserData| }
670+
} else {
671+
let idents: Vec<_> = info.args.iter().map(|a| &a.ident).collect();
672+
let types: Vec<_> = info.args.iter().map(|a| &a.callback_type).collect();
673+
quote! { |lua, (_this, #(#idents),*): (::mlua::AnyUserData, #(#types),*) | }
674+
}
675+
} else {
676+
gen_async_closure_params(info)
677+
};
678+
let call_args = gen_async_call_args(info);
679+
let fn_path = quote! { #type_path::#fn_name };
680+
681+
let body = if lua_attr.infallible {
682+
quote! { async move { Ok(#fn_path(#call_args).await) } }
683+
} else {
684+
quote! { async move { #fn_path(#call_args).await } }
685+
};
686+
match info.self_kind {
687+
SelfKind::None => quote! {
688+
registry.add_async_meta_function(#meta_name, #closure_params #body);
689+
},
690+
SelfKind::Ref(RefKind::Mut) => quote! {
691+
registry.add_async_meta_method_mut(#meta_name, #closure_params #body);
692+
},
693+
_ => quote! {
694+
registry.add_async_meta_method(#meta_name, #closure_params #body);
695+
},
696+
}
697+
}

tests/compile.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,11 @@ fn test_compilation() {
3636
t.compile_fail("tests/compile/userdata_meta_owned_self.rs");
3737
t.compile_fail("tests/compile/userdata_const_getter.rs");
3838
}
39+
40+
#[cfg(all(feature = "macros", feature = "async"))]
41+
{
42+
t.compile_fail("tests/compile/userdata_getter_async.rs");
43+
t.compile_fail("tests/compile/userdata_setter_async.rs");
44+
t.compile_fail("tests/compile/userdata_field_async.rs");
45+
}
3946
}

tests/compile/async_any_userdata_method.stderr

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
error[E0596]: cannot borrow `s` as mutable, as it is a captured variable in a `Fn` closure
22
--> tests/compile/async_any_userdata_method.rs:9:49
33
|
4-
8 | let mut s = &s;
5-
| ----- `s` declared here, outside the closure
6-
9 | reg.add_async_method("t", |_, this, ()| async {
7-
| ------------- ^^^^^ cannot borrow as mutable
8-
| |
9-
| in this closure
10-
10 | s = &*this;
11-
| - mutable borrow occurs due to use of `s` in closure
4+
8 | let mut s = &s;
5+
| ----- `s` declared here, outside the closure
6+
9 | reg.add_async_method("t", |_, this, ()| async {
7+
| - ------------- ^^^^^ cannot borrow as mutable
8+
| | |
9+
| _____________| in this closure
10+
| |
11+
10 | | s = &*this;
12+
| | - mutable borrow occurs due to use of `s` in closure
13+
11 | | Ok(())
14+
12 | | });
15+
| |__________- expects `Fn` instead of `FnMut`
1216

1317
error[E0373]: async block may outlive the current function, but it borrows `this`, which is owned by the current function
1418
--> tests/compile/async_any_userdata_method.rs:9:49

tests/compile/lua_norefunwindsafe.stderr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ error[E0277]: the type `UnsafeCell<state::extra::ExtraData>` may contain interio
5050
|
5151
= help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell<state::extra::ExtraData>`
5252
= note: required for `Rc<UnsafeCell<state::extra::ExtraData>>` to implement `RefUnwindSafe`
53+
note: required because it appears within the type `MaybeDangling<Rc<UnsafeCell<state::extra::ExtraData>>>`
54+
--> $RUST/core/src/mem/maybe_dangling.rs
55+
|
56+
| pub struct MaybeDangling<P: ?Sized>(P);
57+
| ^^^^^^^^^^^^^
58+
note: required because it appears within the type `ManuallyDrop<Rc<UnsafeCell<state::extra::ExtraData>>>`
59+
--> $RUST/core/src/mem/manually_drop.rs
60+
|
61+
| pub struct ManuallyDrop<T: ?Sized> {
62+
| ^^^^^^^^^^^^
5363
note: required because it appears within the type `mlua::state::RawLua`
5464
--> src/state/raw.rs
5565
|

tests/compile/ref_nounwindsafe.stderr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,16 @@ error[E0277]: the type `UnsafeCell<state::extra::ExtraData>` may contain interio
120120
|
121121
= help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell<state::extra::ExtraData>`
122122
= note: required for `Rc<UnsafeCell<state::extra::ExtraData>>` to implement `RefUnwindSafe`
123+
note: required because it appears within the type `MaybeDangling<Rc<UnsafeCell<state::extra::ExtraData>>>`
124+
--> $RUST/core/src/mem/maybe_dangling.rs
125+
|
126+
| pub struct MaybeDangling<P: ?Sized>(P);
127+
| ^^^^^^^^^^^^^
128+
note: required because it appears within the type `ManuallyDrop<Rc<UnsafeCell<state::extra::ExtraData>>>`
129+
--> $RUST/core/src/mem/manually_drop.rs
130+
|
131+
| pub struct ManuallyDrop<T: ?Sized> {
132+
| ^^^^^^^^^^^^
123133
note: required because it appears within the type `mlua::state::RawLua`
124134
--> src/state/raw.rs
125135
|
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use mlua::Result;
2+
3+
#[derive(Clone, Debug)]
4+
#[mlua::userdata]
5+
struct Foo;
6+
7+
#[mlua::userdata_impl]
8+
impl Foo {
9+
#[lua(field)]
10+
async fn description() -> Result<String> {
11+
Ok("foo".into())
12+
}
13+
}
14+
15+
fn main() {}

0 commit comments

Comments
 (0)