Skip to content

Commit 7b9ec57

Browse files
committed
mlua_derive: Support wildcard params and reject invalid impls in userdata_impl
1 parent 466b327 commit 7b9ec57

7 files changed

Lines changed: 151 additions & 39 deletions

File tree

mlua_derive/src/userdata/userdata_impl.rs

Lines changed: 70 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -168,51 +168,64 @@ fn analyze_self_and_args(sig: &Signature) -> syn::Result<MethodInfo> {
168168
}
169169
}
170170

171-
if let syn::Pat::Ident(pat_ident) = &*typed.pat {
172-
let arg_type = &*typed.ty;
173-
let mut option_inner = None;
174-
let ref_kind = match arg_type {
175-
Type::Reference(r) if r.mutability.is_some() => Some(RefKind::Mut),
176-
Type::Reference(_) => Some(RefKind::Ref),
177-
_ => {
178-
// Check if it's `Option<&T>` or `Option<&mut T>`
179-
option_inner = try_unwrap_option(arg_type);
180-
option_inner.and_then(|inner| match inner {
181-
Type::Reference(r) if r.mutability.is_some() => Some(RefKind::OptionMut),
182-
Type::Reference(_) => Some(RefKind::OptionRef),
183-
_ => None,
184-
})
185-
}
186-
};
187-
let callback_type = match &ref_kind {
188-
Some(RefKind::OptionRef | RefKind::OptionMut) => {
189-
match classify_ref_type(option_inner.unwrap()) {
190-
Some(ty) => parse_quote! { Option<#ty> },
191-
None => {
192-
return Err(syn::Error::new_spanned(
193-
arg_type,
194-
"this reference type is not supported as a callback parameter",
195-
));
196-
}
197-
}
198-
}
199-
Some(_) => match classify_ref_type(arg_type) {
200-
Some(ty) => ty,
171+
let ident = match &*typed.pat {
172+
syn::Pat::Ident(pat_ident) => pat_ident.ident.clone(),
173+
syn::Pat::Wild(_) => {
174+
// For wildcards we generate a unique identifier to avoid collisions with other
175+
// parameters.
176+
Ident::new(&format!("__mlua_arg_{}", args.len()), Span2::mixed_site())
177+
}
178+
_ => {
179+
return Err(syn::Error::new_spanned(
180+
&typed.pat,
181+
"`#[mlua::userdata_impl]` requires a named parameter or `_`; destructuring patterns are not supported",
182+
));
183+
}
184+
};
185+
186+
let arg_type = &*typed.ty;
187+
let mut option_inner = None;
188+
let ref_kind = match arg_type {
189+
Type::Reference(r) if r.mutability.is_some() => Some(RefKind::Mut),
190+
Type::Reference(_) => Some(RefKind::Ref),
191+
_ => {
192+
// Check if it's `Option<&T>` or `Option<&mut T>`
193+
option_inner = try_unwrap_option(arg_type);
194+
option_inner.and_then(|inner| match inner {
195+
Type::Reference(r) if r.mutability.is_some() => Some(RefKind::OptionMut),
196+
Type::Reference(_) => Some(RefKind::OptionRef),
197+
_ => None,
198+
})
199+
}
200+
};
201+
let callback_type = match &ref_kind {
202+
Some(RefKind::OptionRef | RefKind::OptionMut) => {
203+
match classify_ref_type(option_inner.unwrap()) {
204+
Some(ty) => parse_quote! { Option<#ty> },
201205
None => {
202206
return Err(syn::Error::new_spanned(
203207
arg_type,
204208
"this reference type is not supported as a callback parameter",
205209
));
206210
}
207-
},
208-
None => arg_type.clone(),
209-
};
210-
args.push(ArgInfo {
211-
ident: pat_ident.ident.clone(),
212-
userdata_ref: ref_kind,
213-
callback_type,
214-
});
215-
}
211+
}
212+
}
213+
Some(_) => match classify_ref_type(arg_type) {
214+
Some(ty) => ty,
215+
None => {
216+
return Err(syn::Error::new_spanned(
217+
arg_type,
218+
"this reference type is not supported as a callback parameter",
219+
));
220+
}
221+
},
222+
None => arg_type.clone(),
223+
};
224+
args.push(ArgInfo {
225+
ident,
226+
userdata_ref: ref_kind,
227+
callback_type,
228+
});
216229
}
217230
}
218231
}
@@ -275,6 +288,24 @@ pub fn userdata_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
275288

276289
let mut input = parse_macro_input!(item as ItemImpl);
277290

291+
// Generic impl blocks are not supported
292+
if !input.generics.params.is_empty() {
293+
return syn::Error::new_spanned(
294+
&input.generics,
295+
"`#[mlua::userdata_impl]` does not support generic impl blocks.",
296+
)
297+
.to_compile_error()
298+
.into();
299+
}
300+
if let Some(where_clause) = &input.generics.where_clause {
301+
return syn::Error::new_spanned(
302+
where_clause,
303+
"`#[mlua::userdata_impl]` does not support `where` clauses on the impl block.",
304+
)
305+
.to_compile_error()
306+
.into();
307+
}
308+
278309
let type_path = match &*input.self_ty {
279310
Type::Path(type_path) => &type_path.path,
280311
_ => {

tests/compile.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ fn test_compilation() {
3434
t.compile_fail("tests/compile/userdata_setter_no_value.rs");
3535
t.compile_fail("tests/compile/userdata_static_with_self.rs");
3636
t.compile_fail("tests/compile/userdata_meta_owned_self.rs");
37+
t.compile_fail("tests/compile/userdata_destructuring_arg.rs");
38+
t.compile_fail("tests/compile/userdata_generic_impl.rs");
3739
t.compile_fail("tests/compile/userdata_const_getter.rs");
3840
t.compile_fail("tests/compile/userdata_field_with_args.rs");
3941
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#[derive(mlua::UserData)]
2+
struct Foo {
3+
x: u32,
4+
}
5+
6+
#[mlua::userdata_impl]
7+
impl Foo {
8+
#[lua(infallible)]
9+
fn takes_pattern(&self, (a, b): (u32, u32)) -> u32 {
10+
self.x + a + b
11+
}
12+
}
13+
14+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: `#[mlua::userdata_impl]` requires a named parameter or `_`; destructuring patterns are not supported
2+
--> tests/compile/userdata_destructuring_arg.rs:9:29
3+
|
4+
9 | fn takes_pattern(&self, (a, b): (u32, u32)) -> u32 {
5+
| ^^^^^^
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#[derive(mlua::UserData)]
2+
struct Foo {
3+
x: u32,
4+
}
5+
6+
#[mlua::userdata_impl]
7+
impl<T> Foo<T> {
8+
#[lua(infallible)]
9+
fn get(&self) -> u32 {
10+
self.x
11+
}
12+
}
13+
14+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: `#[mlua::userdata_impl]` does not support generic impl blocks.
2+
--> tests/compile/userdata_generic_impl.rs:7:5
3+
|
4+
7 | impl<T> Foo<T> {
5+
| ^^^

tests/userdata_macro.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,47 @@ fn test_param_name_hygiene() {
528528
.unwrap();
529529
}
530530

531+
#[derive(Clone, Debug, UserData)]
532+
struct Wild {
533+
n: i32,
534+
}
535+
536+
#[mlua::userdata_impl]
537+
impl Wild {
538+
#[lua(infallible)]
539+
fn new(n: i32) -> Self {
540+
Wild { n }
541+
}
542+
543+
#[lua(infallible)]
544+
fn ignore_one(&self, _: i32) -> i32 {
545+
self.n
546+
}
547+
548+
#[lua(infallible)]
549+
fn add_second(&self, _: i32, other: &Wild) -> i32 {
550+
self.n + other.n
551+
}
552+
}
553+
554+
#[test]
555+
fn test_wildcard_params() {
556+
let lua = Lua::new();
557+
lua.globals()
558+
.set("Wild", lua.create_proxy::<Wild>().unwrap())
559+
.unwrap();
560+
lua.load(
561+
r#"
562+
local w = Wild.new(7)
563+
assert(w:ignore_one(99) == 7, "single wildcard arg should be ignored")
564+
assert(w:add_second(1, Wild.new(5)) == 12, "named arg after a wildcard should work")
565+
assert(not pcall(function() return w:ignore_one({}) end), "wildcard arg is still type-checked")
566+
"#,
567+
)
568+
.exec()
569+
.unwrap();
570+
}
571+
531572
#[cfg(feature = "async")]
532573
mod async_tests {
533574
use mlua::{Lua, Result, UserData};

0 commit comments

Comments
 (0)