diff --git a/src/userdata.rs b/src/userdata.rs index 4719533..9a507d7 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -711,7 +711,7 @@ pub trait UserData: Sized { /// /// [`is`]: crate::AnyUserData::is /// [`borrow`]: crate::AnyUserData::borrow -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, PartialEq)] pub struct AnyUserData(pub(crate) ValueRef); impl AnyUserData { @@ -1081,6 +1081,45 @@ impl AnyUserData { }; is_serializable().unwrap_or(false) } + + unsafe fn invoke_tostring_dbg(&self) -> Result> { + let lua = self.0.lua.lock(); + let state = lua.state(); + let _guard = StackGuard::new(state); + check_stack(state, 3)?; + + lua.push_ref(&self.0); + protect_lua!(state, 1, 1, fn(state) { + // Try `__todebugstring` metamethod first, then `__tostring` + if ffi::luaL_callmeta(state, -1, cstr!("__todebugstring")) == 0 { + if ffi::luaL_callmeta(state, -1, cstr!("__tostring")) == 0 { + ffi::lua_pushnil(state); + } + } + })?; + Ok(lua.pop_value().as_string().map(|s| s.to_string_lossy())) + } + + pub(crate) fn fmt_pretty(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + // Try converting to a (debug) string first, with fallback to `__name/__type` + match unsafe { self.invoke_tostring_dbg() } { + Ok(Some(s)) => write!(fmt, "{s}"), + _ => { + let name = self.type_name().ok().flatten(); + let name = name.as_deref().unwrap_or("userdata"); + write!(fmt, "{name}: {:?}", self.to_pointer()) + } + } + } +} + +impl fmt::Debug for AnyUserData { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + if fmt.alternate() { + return self.fmt_pretty(fmt); + } + fmt.debug_tuple("AnyUserData").field(&self.0).finish() + } } /// Handle to a [`AnyUserData`] metatable. diff --git a/src/value.rs b/src/value.rs index 623fe00..8d178e0 100644 --- a/src/value.rs +++ b/src/value.rs @@ -545,24 +545,6 @@ impl Value { ident: usize, visited: &mut HashSet<*const c_void>, ) -> fmt::Result { - unsafe fn invoke_tostring_dbg(vref: &ValueRef) -> Result> { - let lua = vref.lua.lock(); - let state = lua.state(); - let _guard = StackGuard::new(state); - check_stack(state, 3)?; - - lua.push_ref(vref); - protect_lua!(state, 1, 1, fn(state) { - // Try `__todebugstring` metamethod first, then `__tostring` - if ffi::luaL_callmeta(state, -1, cstr!("__todebugstring")) == 0 { - if ffi::luaL_callmeta(state, -1, cstr!("__tostring")) == 0 { - ffi::lua_pushnil(state); - } - } - })?; - Ok(lua.pop_value().as_string().map(|s| s.to_string_lossy())) - } - match self { Value::Nil => write!(fmt, "nil"), Value::Boolean(b) => write!(fmt, "{b}"), @@ -579,17 +561,7 @@ impl Value { t @ Value::Table(_) => write!(fmt, "table: {:?}", t.to_pointer()), f @ Value::Function(_) => write!(fmt, "function: {:?}", f.to_pointer()), t @ Value::Thread(_) => write!(fmt, "thread: {:?}", t.to_pointer()), - u @ Value::UserData(ud) => unsafe { - // Try converting to a (debug) string first, with fallback to `__name/__type` - match invoke_tostring_dbg(&ud.0) { - Ok(Some(s)) => write!(fmt, "{s}"), - _ => { - let name = ud.type_name().ok().flatten(); - let name = name.as_deref().unwrap_or("userdata"); - write!(fmt, "{name}: {:?}", u.to_pointer()) - } - } - }, + Value::UserData(ud) => ud.fmt_pretty(fmt), #[cfg(feature = "luau")] buf @ Value::Buffer(_) => write!(fmt, "buffer: {:?}", buf.to_pointer()), Value::Error(e) if recursive => write!(fmt, "{e:?}"), diff --git a/tests/value.rs b/tests/value.rs index 31bc4e9..506aaa0 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -3,7 +3,8 @@ use std::os::raw::c_void; use std::ptr; use mlua::{ - Error, LightUserData, Lua, MultiValue, Result, UserData, UserDataMethods, UserDataRegistry, Value, + AnyUserData, Error, LightUserData, Lua, MultiValue, Result, UserData, UserDataMethods, UserDataRegistry, + Value, }; #[test] @@ -234,11 +235,16 @@ fn test_debug_format() -> Result<()> { struct ToStringUserData; impl UserData for ToStringUserData { fn register(registry: &mut UserDataRegistry) { - registry.add_meta_method("__tostring", |_, _, ()| Ok("to-string-only")); + registry.add_meta_method("__tostring", |_, _, ()| Ok("regular-string")); } } let tostring_only_ud = Value::UserData(lua.create_userdata(ToStringUserData)?); - assert_eq!(format!("{tostring_only_ud:#?}"), "tostring-only"); + assert_eq!(format!("{tostring_only_ud:#?}"), "regular-string"); + + // Check that `AnyUsedata` pretty debug format is same as for `Value::UserData` + let any_ud: AnyUserData = lua.create_userdata(ToDebugUserData)?; + let value_ud = Value::UserData(any_ud.clone()); + assert_eq!(format!("{any_ud:#?}"), format!("{value_ud:#?}")); Ok(()) }