Skip to content

Commit 5c1a731

Browse files
committed
mlua_derive: Fix static metamethods shifting arguments in userdata_impl
1 parent ec26d3d commit 5c1a731

3 files changed

Lines changed: 85 additions & 25 deletions

File tree

docs/UserData.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,29 @@ impl MyType {
115115
fn __add(&self, other: &Self) -> Self { ... }
116116
117117
#[lua(meta, name = "__call", infallible)]
118-
fn construct(lua: &Lua, value: u32) -> Self { ... }
118+
fn construct(this: mlua::AnyUserData, value: u32) -> Self { ... }
119+
}
120+
```
121+
122+
A metamethod with a `self` receiver is registered via `add_meta_method`
123+
and behaves like a regular method. A metamethod without a receiver is
124+
registered via `add_meta_function` and receives exactly the values Lua passes.
125+
Declare every argument Lua provides, in order:
126+
127+
- Binary metamethods (`__add`, `__sub`, `__eq`, `__concat`, ...) receive both
128+
operands, so both must be declared. This is also the way to support reversed
129+
operands (e.g. `2 + obj`), where the userdata is the second argument.
130+
- Method-style metamethods (`__call`, `__index`, `__newindex`, ...) receive the
131+
object as their first argument. When registered without a `self` receiver
132+
(for example a constructor invoked as `MyType(value)`), declare that leading
133+
argument explicitly (typically `mlua::AnyUserData`), even if it is ignored.
134+
135+
```rust,ignore
136+
#[mlua::userdata_impl]
137+
impl Vec2 {
138+
// No receiver: both operands are declared and passed directly by Lua.
139+
#[lua(meta, infallible, name = "__add")]
140+
fn add(a: &Vec2, b: &Vec2) -> Vec2 { ... }
119141
}
120142
```
121143

mlua_derive/src/userdata/userdata_impl.rs

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -654,18 +654,7 @@ fn gen_meta(type_path: &syn::Path, fn_name: &Ident, lua_attr: &LuaAttr, info: &M
654654
Ok(name) => name,
655655
Err(err) => return err.to_compile_error(),
656656
};
657-
let closure_params = if matches!(info.self_kind, SelfKind::None) {
658-
// Lua always passes `self` to the stack arg, just ignore it.
659-
if info.args.is_empty() {
660-
quote! { |lua, _this: ::mlua::AnyUserData| }
661-
} else {
662-
let idents: Vec<_> = info.args.iter().map(|a| &a.ident).collect();
663-
let types: Vec<_> = info.args.iter().map(|a| &a.callback_type).collect();
664-
quote! { |lua, (_this, #(#idents),*): (::mlua::AnyUserData, #(#types),*) | }
665-
}
666-
} else {
667-
gen_closure_params(info)
668-
};
657+
let closure_params = gen_closure_params(info);
669658
let call_args = gen_call_args(info);
670659
let fn_path = quote! { #type_path::#fn_name };
671660

@@ -761,17 +750,7 @@ fn gen_async_meta(
761750
Ok(name) => name,
762751
Err(err) => return err.to_compile_error(),
763752
};
764-
let closure_params = if matches!(info.self_kind, SelfKind::None) {
765-
if info.args.is_empty() {
766-
quote! { |lua, _this: ::mlua::AnyUserData| }
767-
} else {
768-
let idents: Vec<_> = info.args.iter().map(|a| &a.ident).collect();
769-
let types: Vec<_> = info.args.iter().map(|a| &a.callback_type).collect();
770-
quote! { |lua, (_this, #(#idents),*): (::mlua::AnyUserData, #(#types),*) | }
771-
}
772-
} else {
773-
gen_async_closure_params(info)
774-
};
753+
let closure_params = gen_async_closure_params(info);
775754
let call_args = gen_async_call_args(info);
776755
let fn_path = quote! { #type_path::#fn_name };
777756

tests/userdata_macro.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![cfg(feature = "macros")]
22

3-
use mlua::{Lua, Result, UserData};
3+
use mlua::{AnyUserData, Lua, Result, UserData};
44

55
#[derive(Default, Clone, Debug, UserData)]
66
struct Rectangle {
@@ -412,6 +412,65 @@ fn test_known_borrow_wrappers() -> Result<()> {
412412
Ok(())
413413
}
414414

415+
#[derive(Clone, Copy, Debug, PartialEq, UserData)]
416+
struct Vec2 {
417+
x: i32,
418+
y: i32,
419+
}
420+
421+
#[mlua::userdata_impl]
422+
impl Vec2 {
423+
#[lua(infallible)]
424+
fn new(x: i32, y: i32) -> Self {
425+
Vec2 { x, y }
426+
}
427+
428+
#[lua(meta, infallible, name = "__add")]
429+
fn add(this: &Vec2, other: &Vec2) -> Vec2 {
430+
Vec2 {
431+
x: this.x + other.x,
432+
y: this.y + other.y,
433+
}
434+
}
435+
436+
#[lua(meta, infallible, name = "__eq")]
437+
fn eq(this: &Vec2, other: &Vec2) -> bool {
438+
this == other
439+
}
440+
441+
#[lua(meta, infallible, name = "__call")]
442+
fn call(_lua: &Lua, _proxy: AnyUserData, x: i32, y: i32) -> Vec2 {
443+
Self::new(x, y)
444+
}
445+
}
446+
447+
#[test]
448+
fn test_static_metamethods() {
449+
let lua = Lua::new();
450+
lua.globals()
451+
.set("Vec2", lua.create_proxy::<Vec2>().unwrap())
452+
.unwrap();
453+
lua.load(
454+
r#"
455+
local a = Vec2.new(1, 2)
456+
local b = Vec2.new(3, 4)
457+
458+
local c = a + b
459+
assert(c.x == 4, "__add x should be 1 + 3 = 4, got " .. tostring(c.x))
460+
assert(c.y == 6, "__add y should be 2 + 4 = 6, got " .. tostring(c.y))
461+
462+
assert(a == Vec2.new(1, 2), "__eq should report vectors equal")
463+
assert(a ~= b, "__eq should report vectors unequal")
464+
465+
-- `__call` on the proxy
466+
local d = Vec2(7, 14)
467+
assert(d.x == 7 and d.y == 14, "__call should build Vec2(7, 14)")
468+
"#,
469+
)
470+
.exec()
471+
.unwrap();
472+
}
473+
415474
#[cfg(feature = "async")]
416475
mod async_tests {
417476
use mlua::{Lua, Result, UserData};

0 commit comments

Comments
 (0)