From 4daa631178071ce37cd396776aa947dd508cab66 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 26 Jul 2023 21:41:28 +0100 Subject: [PATCH] Set `__type` metatable field for Luau instead of `__name`. Other Lua versions continue to use `__name` field. Also make `MetaMethod` enum non-exhaustive (and add `Type` variant) Closes #295 --- mlua-sys/src/luau/compat.rs | 6 +++--- src/lua.rs | 6 +++--- src/userdata.rs | 26 +++++++++++++++++++++++--- src/value.rs | 2 +- tests/userdata.rs | 20 ++++++++++++++------ 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index 36a6e97..98fa9c9 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -323,7 +323,7 @@ pub unsafe fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) pub unsafe fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_int { if luaL_newmetatable_(L, tname) != 0 { lua_pushstring(L, tname); - lua_setfield(L, -2, cstr!("__name")); + lua_setfield(L, -2, cstr!("__type")); 1 } else { 0 @@ -454,7 +454,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) } } t => { - let tt = luaL_getmetafield(L, idx, cstr!("__name")); + let tt = luaL_getmetafield(L, idx, cstr!("__type")); let name = if tt == LUA_TSTRING { lua_tostring(L, -1) } else { @@ -462,7 +462,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) }; lua_pushfstring(L, cstr!("%s: %p"), name, lua_topointer(L, idx)); if tt != LUA_TNIL { - lua_replace(L, -2); // remove '__name' + lua_replace(L, -2); // remove '__type' } } }; diff --git a/src/lua.rs b/src/lua.rs index cf09112..d2120da 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -2531,15 +2531,15 @@ impl Lua { } let mut has_name = false; for (k, f) in registry.meta_fields { - has_name = has_name || k == "__name"; + has_name = has_name || k == MetaMethod::Type; self.push_value(f(self, MultiValue::new())?.pop_front().unwrap())?; rawset_field(state, -2, MetaMethod::validate(&k)?)?; } - // Set `__name` if not provided + // Set `__name/__type` if not provided if !has_name { let type_name = short_type_name::(); push_string(state, type_name.as_bytes(), !self.unlikely_memory_error())?; - rawset_field(state, -2, "__name")?; + rawset_field(state, -2, MetaMethod::Type.name())?; } let metatable_index = ffi::lua_absindex(state, -1); diff --git a/src/userdata.rs b/src/userdata.rs index 26c03da..a5ccd36 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1,5 +1,6 @@ use std::any::{type_name, TypeId}; use std::cell::{Ref, RefCell, RefMut}; +use std::ffi::CStr; use std::fmt; use std::hash::Hash; use std::mem; @@ -36,6 +37,7 @@ pub(crate) const USER_VALUE_MAXSLOT: usize = 8; /// /// [`UserData`]: crate::UserData #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] pub enum MetaMethod { /// The `+` operator. Add, @@ -141,6 +143,11 @@ pub enum MetaMethod { #[cfg(feature = "lua54")] #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] Close, + /// The `__name`/`__type` metafield. + /// + /// This is not a function, but it's value can be used by `tostring` and `typeof` built-in functions. + #[doc(hidden)] + Type, } impl PartialEq for &str { @@ -212,6 +219,19 @@ impl MetaMethod { #[cfg(feature = "lua54")] MetaMethod::Close => "__close", + + #[rustfmt::skip] + MetaMethod::Type => if cfg!(feature = "luau") { "__type" } else { "__name" }, + } + } + + pub(crate) const fn as_cstr(self) -> &'static CStr { + match self { + #[rustfmt::skip] + MetaMethod::Type => unsafe { + CStr::from_bytes_with_nul_unchecked(if cfg!(feature = "luau") { b"__type\0" } else { b"__name\0" }) + }, + _ => unreachable!(), } } @@ -1089,7 +1109,7 @@ impl<'lua> AnyUserData<'lua> { unsafe { self.0.lua.get_userdata_type_id(&self.0) } } - /// Returns a type name of this `UserData` (from `__name` metatable field). + /// Returns a type name of this `UserData` (from a metatable field). pub(crate) fn type_name(&self) -> Result> { let lua = self.0.lua; let state = lua.state(); @@ -1101,10 +1121,10 @@ impl<'lua> AnyUserData<'lua> { let protect = !lua.unlikely_memory_error(); let name_type = if protect { protect_lua!(state, 1, 1, |state| { - ffi::luaL_getmetafield(state, -1, cstr!("__name")) + ffi::luaL_getmetafield(state, -1, MetaMethod::Type.as_cstr().as_ptr()) })? } else { - ffi::luaL_getmetafield(state, -1, cstr!("__name")) + ffi::luaL_getmetafield(state, -1, MetaMethod::Type.as_cstr().as_ptr()) }; match name_type { ffi::LUA_TSTRING => Ok(Some(String(lua.pop_ref()).to_str()?.to_owned())), diff --git a/src/value.rs b/src/value.rs index 82f12d9..eaaf26f 100644 --- a/src/value.rs +++ b/src/value.rs @@ -223,7 +223,7 @@ impl<'lua> Value<'lua> { f @ Value::Function(_) => write!(fmt, "function: {:?}", f.to_pointer()), t @ Value::Thread(_) => write!(fmt, "thread: {:?}", t.to_pointer()), u @ Value::UserData(ud) => { - // Try `__name` first then `__tostring` + // Try `__name/__type` first then `__tostring` let name = ud.type_name().ok().flatten(); let s = name .map(|name| format!("{name}: {:?}", u.to_pointer())) diff --git a/tests/userdata.rs b/tests/userdata.rs index 8bdd46b..365ea4f 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -543,7 +543,7 @@ fn test_metatable() -> Result<()> { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { methods.add_function("my_type_name", |_, data: AnyUserData| { let metatable = data.get_metatable()?; - metatable.get::("__name") + metatable.get::(MetaMethod::Type) }); } } @@ -551,8 +551,13 @@ fn test_metatable() -> Result<()> { let lua = Lua::new(); let globals = lua.globals(); globals.set("ud", MyUserData)?; - lua.load(r#"assert(ud:my_type_name() == "MyUserData")"#) - .exec()?; + lua.load( + r#" + assert(ud:my_type_name() == "MyUserData") + assert(tostring(ud):sub(1, 14) == "MyUserData: 0x") + "#, + ) + .exec()?; let ud: AnyUserData = globals.get("ud")?; let metatable = ud.get_metatable()?; @@ -574,7 +579,7 @@ fn test_metatable() -> Result<()> { .map(|kv: Result<(_, Value)>| Ok(kv?.0)) .collect::>>()?; methods.sort(); - assert_eq!(methods, vec!["__index", "__name"]); + assert_eq!(methods, vec!["__index", MetaMethod::Type.name()]); #[derive(Copy, Clone)] struct MyUserData2; @@ -596,13 +601,16 @@ fn test_metatable() -> Result<()> { impl UserData for MyUserData3 { fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { - fields.add_meta_field_with("__name", |_| Ok("CustomName")); + fields.add_meta_field_with(MetaMethod::Type, |_| Ok("CustomName")); } } let ud = lua.create_userdata(MyUserData3)?; let metatable = ud.get_metatable()?; - assert_eq!(metatable.get::("__name")?.to_str()?, "CustomName"); + assert_eq!( + metatable.get::(MetaMethod::Type)?.to_str()?, + "CustomName" + ); Ok(()) }