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
This commit is contained in:
Alex Orlenko
2023-07-26 21:41:28 +01:00
parent 9f5325ef2f
commit 4daa631178
5 changed files with 44 additions and 16 deletions
+3 -3
View File
@@ -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'
}
}
};
+3 -3
View File
@@ -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::<T>();
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);
+23 -3
View File
@@ -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<MetaMethod> 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<Option<StdString>> {
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())),
+1 -1
View File
@@ -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()))
+14 -6
View File
@@ -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::<String>("__name")
metatable.get::<String>(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::<Result<Vec<_>>>()?;
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::<String>("__name")?.to_str()?, "CustomName");
assert_eq!(
metatable.get::<String>(MetaMethod::Type)?.to_str()?,
"CustomName"
);
Ok(())
}