Implement pretty debug format for AnyUserData similar to Value::UserData.

This commit is contained in:
Alex Orlenko
2026-02-20 20:31:29 +00:00
parent 7f3ec63ab5
commit 151adc0e87
3 changed files with 50 additions and 33 deletions
+40 -1
View File
@@ -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<Option<String>> {
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.
+1 -29
View File
@@ -545,24 +545,6 @@ impl Value {
ident: usize,
visited: &mut HashSet<*const c_void>,
) -> fmt::Result {
unsafe fn invoke_tostring_dbg(vref: &ValueRef) -> Result<Option<String>> {
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:?}"),
+9 -3
View File
@@ -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<Self>) {
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(())
}