From e7f494530f0771a8ee6e5446e6ff7e938b9cd478 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 28 Jun 2022 18:28:40 +0100 Subject: [PATCH] Add Lua::create_proxy for easy access to UserData static fields and functions Closes #178 --- src/lua.rs | 44 +++++++++++++++++++++++++++++++++++++++++++- src/userdata_impl.rs | 5 +++++ tests/userdata.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/lua.rs b/src/lua.rs index 6af616a..9aa654b 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -29,7 +29,7 @@ use crate::types::{ Number, RegistryKey, }; use crate::userdata::{AnyUserData, UserData, UserDataCell}; -use crate::userdata_impl::{StaticUserDataFields, StaticUserDataMethods}; +use crate::userdata_impl::{StaticUserDataFields, StaticUserDataMethods, UserDataProxy}; use crate::util::{ self, assert_stack, callback_error, check_stack, get_destructed_userdata_metatable, get_gc_metatable, get_gc_userdata, get_main_state, get_userdata, init_error_registry, @@ -1721,6 +1721,7 @@ impl Lua { } /// Create a Lua userdata object from a custom userdata type. + #[inline] pub fn create_userdata(&self, data: T) -> Result where T: 'static + MaybeSend + UserData, @@ -1733,6 +1734,7 @@ impl Lua { /// Requires `feature = "serialize"` #[cfg(feature = "serialize")] #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] + #[inline] pub fn create_ser_userdata(&self, data: T) -> Result where T: 'static + MaybeSend + UserData + Serialize, @@ -1740,6 +1742,46 @@ impl Lua { unsafe { self.make_userdata(UserDataCell::new_ser(data)) } } + /// Create a Lua userdata "proxy" object from a custom userdata type. + /// + /// Proxy object is an empty userdata object that has `T` metatable attached. + /// The main purpose of this object is to provide access to static fields and functions + /// without creating an instance of type `T`. + /// + /// You can get or set uservalues on this object but you cannot borrow any Rust type. + /// + /// # Examples + /// + /// ``` + /// # use mlua::{Lua, Result, UserData, UserDataFields, UserDataMethods}; + /// # fn main() -> Result<()> { + /// # let lua = Lua::new(); + /// struct MyUserData(i32); + /// + /// impl UserData for MyUserData { + /// fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { + /// fields.add_field_method_get("val", |_, this| Ok(this.0)); + /// } + /// + /// fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + /// methods.add_function("new", |_, value: i32| Ok(MyUserData(value))); + /// } + /// } + /// + /// lua.globals().set("MyUserData", lua.create_proxy::()?)?; + /// + /// lua.load("assert(MyUserData.new(321).val == 321)").exec()?; + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn create_proxy(&self) -> Result + where + T: 'static + UserData, + { + unsafe { self.make_userdata(UserDataCell::new(UserDataProxy::(PhantomData))) } + } + /// Returns a handle to the global environment. pub fn globals(&self) -> Table { unsafe { diff --git a/src/userdata_impl.rs b/src/userdata_impl.rs index 913bd73..944e20d 100644 --- a/src/userdata_impl.rs +++ b/src/userdata_impl.rs @@ -622,3 +622,8 @@ lua_userdata_impl!(Arc>); lua_userdata_impl!(Arc>); #[cfg(feature = "parking_lot")] lua_userdata_impl!(Arc>); + +// A special proxy object for UserData +pub(crate) struct UserDataProxy(pub(crate) PhantomData); + +lua_userdata_impl!(UserDataProxy); diff --git a/tests/userdata.rs b/tests/userdata.rs index ce9d9d0..91996b7 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -656,3 +656,44 @@ fn test_userdata_wrapped() -> Result<()> { Ok(()) } + +#[test] +fn test_userdata_proxy() -> Result<()> { + struct MyUserData(i64); + + impl UserData for MyUserData { + fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_function_get("static_field", |_, _| Ok(123)); + fields.add_field_method_get("n", |_, this| Ok(this.0)); + } + + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_function("new", |_, n| Ok(Self(n))); + + methods.add_method("plus", |_, this, n: i64| Ok(this.0 + n)); + } + } + + let lua = Lua::new(); + let globals = lua.globals(); + globals.set("MyUserData", lua.create_proxy::()?)?; + + lua.load( + r#" + assert(MyUserData.static_field == 123) + local data = MyUserData.new(321) + assert(data.static_field == 123) + assert(data.n == 321) + assert(data:plus(1) == 322) + + -- Error when accessing the proxy object fields and methods that require instance + + local ok = pcall(function() return MyUserData.n end) + assert(not ok) + + ok = pcall(function() return MyUserData:plus(1) end) + assert(not ok) + "#, + ) + .exec() +}