From 97cc77a5fa20dc2f8bbd9eaf99404f58e9a42aa9 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 30 May 2022 16:41:31 +0000 Subject: [PATCH] bevy_reflect: Improve debug formatting for reflected types (#4218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective Debugging reflected types can be somewhat frustrating since all `dyn Reflect` trait objects return something like `Reflect(core::option::Option)`. It would be much nicer to be able to see the actual value— or even use a custom `Debug` implementation. ## Solution Added `Reflect::debug` which allows users to customize the debug output. It sets defaults for all `ReflectRef` subtraits and falls back to `Reflect(type_name)` if no `Debug` implementation was registered. To register a custom `Debug` impl, users can add `#[reflect(Debug)]` like they can with other traits. ### Example Using the following structs: ```rust #[derive(Reflect)] pub struct Foo { a: usize, nested: Bar, #[reflect(ignore)] _ignored: NonReflectedValue, } #[derive(Reflect)] pub struct Bar { value: Vec2, tuple_value: (i32, String), list_value: Vec, // We can't determine debug formatting for Option yet unknown_value: Option, custom_debug: CustomDebug } #[derive(Reflect)] #[reflect(Debug)] struct CustomDebug; impl Debug for CustomDebug { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "This is a custom debug!") } } pub struct NonReflectedValue { _a: usize, } ``` We can do: ```rust let value = Foo { a: 1, _ignored: NonReflectedValue { _a: 10 }, nested: Bar { value: Vec2::new(1.23, 3.21), tuple_value: (123, String::from("Hello")), list_value: vec![1, 2, 3], unknown_value: Some(String::from("World")), custom_debug: CustomDebug }, }; let reflected_value: &dyn Reflect = &value; println!("{:#?}", reflected_value) ``` Which results in: ```rust Foo { a: 2, nested: Bar { value: Vec2( 1.23, 3.21, ), tuple_value: ( 123, "Hello", ), list_value: [ 1, 2, 3, ], unknown_value: Reflect(core::option::Option), custom_debug: This is a custom debug!, }, } ``` Notice that neither `Foo` nor `Bar` implement `Debug`, yet we can still deduce it. This might be a concern if we're worried about leaking internal values. If it is, we might want to consider a way to exclude fields (possibly with a `#[reflect(hide)]` macro) or make it purely opt in (as opposed to the default implementation automatically handled by ReflectRef subtraits). Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com> --- .../src/container_attributes.rs | 24 ++++++ .../bevy_reflect_derive/src/impls.rs | 9 ++ crates/bevy_reflect/src/array.rs | 27 ++++++ crates/bevy_reflect/src/impls/glam.rs | 38 ++++----- crates/bevy_reflect/src/impls/std.rs | 36 ++++---- crates/bevy_reflect/src/lib.rs | 82 +++++++++++++++++++ crates/bevy_reflect/src/list.rs | 39 +++++++++ crates/bevy_reflect/src/map.rs | 39 +++++++++ crates/bevy_reflect/src/reflect.rs | 27 +++++- crates/bevy_reflect/src/struct_trait.rs | 45 ++++++++++ crates/bevy_reflect/src/tuple.rs | 33 ++++++++ crates/bevy_reflect/src/tuple_struct.rs | 42 ++++++++++ 12 files changed, 402 insertions(+), 39 deletions(-) diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs b/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs index 39cc946b570e9e..d75a987a046d1c 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs @@ -15,6 +15,7 @@ use syn::{Meta, NestedMeta, Path}; // The "special" trait idents that are used internally for reflection. // Received via attributes like `#[reflect(PartialEq, Hash, ...)]` +const DEBUG_ATTR: &str = "Debug"; const PARTIAL_EQ_ATTR: &str = "PartialEq"; const HASH_ATTR: &str = "Hash"; const SERIALIZE_ATTR: &str = "Serialize"; @@ -46,6 +47,7 @@ impl Default for TraitImpl { /// `Reflect` derive macro using the helper attribute: `#[reflect(...)]`. /// /// The list of special traits are as follows: +/// * `Debug` /// * `Hash` /// * `PartialEq` /// * `Serialize` @@ -101,6 +103,7 @@ impl Default for TraitImpl { /// #[derive(Default)] pub(crate) struct ReflectTraits { + debug: TraitImpl, hash: TraitImpl, partial_eq: TraitImpl, serialize: TraitImpl, @@ -123,6 +126,7 @@ impl ReflectTraits { }; match ident.as_str() { + DEBUG_ATTR => traits.debug = TraitImpl::Implemented, PARTIAL_EQ_ATTR => traits.partial_eq = TraitImpl::Implemented, HASH_ATTR => traits.hash = TraitImpl::Implemented, SERIALIZE_ATTR => traits.serialize = TraitImpl::Implemented, @@ -145,6 +149,7 @@ impl ReflectTraits { // This should be the ident of the custom function let trait_func_ident = TraitImpl::Custom(segment.ident.clone()); match ident.as_str() { + DEBUG_ATTR => traits.debug = trait_func_ident, PARTIAL_EQ_ATTR => traits.partial_eq = trait_func_ident, HASH_ATTR => traits.hash = trait_func_ident, SERIALIZE_ATTR => traits.serialize = trait_func_ident, @@ -239,6 +244,25 @@ impl ReflectTraits { TraitImpl::NotImplemented => None, } } + + /// Returns the implementation of `Reflect::debug` as a `TokenStream`. + /// + /// If `Debug` was not registered, returns `None`. + pub fn get_debug_impl(&self) -> Option { + match &self.debug { + TraitImpl::Implemented => Some(quote! { + fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } + }), + TraitImpl::Custom(impl_fn) => Some(quote! { + fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #impl_fn(self, f) + } + }), + TraitImpl::NotImplemented => None, + } + } } impl Parse for ReflectTraits { diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls.rs index ecea8a62655eb7..abec509425a64b 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls.rs @@ -47,6 +47,7 @@ pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream { } } }); + let debug_fn = derive_data.traits().get_debug_impl(); let get_type_registration_impl = derive_data.get_type_registration(); let (impl_generics, ty_generics, where_clause) = derive_data.generics().split_for_impl(); @@ -166,6 +167,8 @@ pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream { #partial_eq_fn + #debug_fn + #serialize_fn } }) @@ -196,6 +199,7 @@ pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream } } }); + let debug_fn = derive_data.traits().get_debug_impl(); let (impl_generics, ty_generics, where_clause) = derive_data.generics().split_for_impl(); TokenStream::from(quote! { @@ -291,6 +295,8 @@ pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream #partial_eq_fn + #debug_fn + #serialize_fn } }) @@ -307,6 +313,7 @@ pub(crate) fn impl_value( let hash_fn = reflect_traits.get_hash_impl(bevy_reflect_path); let serialize_fn = reflect_traits.get_serialize_impl(bevy_reflect_path); let partial_eq_fn = reflect_traits.get_partial_eq_impl(bevy_reflect_path); + let debug_fn = reflect_traits.get_debug_impl(); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); TokenStream::from(quote! { @@ -372,6 +379,8 @@ pub(crate) fn impl_value( #partial_eq_fn + #debug_fn + #serialize_fn } }) diff --git a/crates/bevy_reflect/src/array.rs b/crates/bevy_reflect/src/array.rs index 0bbcf271272af2..74e0da561d353c 100644 --- a/crates/bevy_reflect/src/array.rs +++ b/crates/bevy_reflect/src/array.rs @@ -1,5 +1,6 @@ use crate::{serde::Serializable, Reflect, ReflectMut, ReflectRef}; use serde::ser::SerializeSeq; +use std::fmt::Debug; use std::{ any::Any, hash::{Hash, Hasher}, @@ -298,3 +299,29 @@ pub fn array_partial_eq(array: &A, reflect: &dyn Reflect) -> Option) -> std::fmt::Result { + let mut debug = f.debug_list(); + for item in dyn_array.iter() { + debug.entry(&item as &dyn Debug); + } + debug.finish() +} diff --git a/crates/bevy_reflect/src/impls/glam.rs b/crates/bevy_reflect/src/impls/glam.rs index 93f803cc0cf94d..101f5e6597d072 100644 --- a/crates/bevy_reflect/src/impls/glam.rs +++ b/crates/bevy_reflect/src/impls/glam.rs @@ -6,14 +6,14 @@ use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_struct, impl_ref use glam::*; impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct IVec2 { x: i32, y: i32, } ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct IVec3 { x: i32, y: i32, @@ -21,7 +21,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct IVec4 { x: i32, y: i32, @@ -31,14 +31,14 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct UVec2 { x: u32, y: u32, } ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct UVec3 { x: u32, y: u32, @@ -46,7 +46,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct UVec4 { x: u32, y: u32, @@ -56,14 +56,14 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct Vec2 { x: f32, y: f32, } ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct Vec3 { x: f32, y: f32, @@ -71,7 +71,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct Vec3A { x: f32, y: f32, @@ -79,7 +79,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct Vec4 { x: f32, y: f32, @@ -89,14 +89,14 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct DVec2 { x: f64, y: f64, } ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct DVec3 { x: f64, y: f64, @@ -104,7 +104,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct DVec4 { x: f64, y: f64, @@ -114,7 +114,7 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct Mat3 { x_axis: Vec3, y_axis: Vec3, @@ -122,7 +122,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct Mat4 { x_axis: Vec4, y_axis: Vec4, @@ -132,7 +132,7 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct DMat3 { x_axis: DVec3, y_axis: DVec3, @@ -140,7 +140,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] struct DMat4 { x_axis: DVec4, y_axis: DVec4, @@ -153,8 +153,8 @@ impl_reflect_struct!( // mechanisms for read-only fields. I doubt those mechanisms would be added, // so for now quaternions will remain as values. They are represented identically // to Vec4 and DVec4, so you may use those instead and convert between. -impl_reflect_value!(Quat(PartialEq, Serialize, Deserialize, Default)); -impl_reflect_value!(DQuat(PartialEq, Serialize, Deserialize, Default)); +impl_reflect_value!(Quat(Debug, PartialEq, Serialize, Deserialize, Default)); +impl_reflect_value!(DQuat(Debug, PartialEq, Serialize, Deserialize, Default)); impl_from_reflect_value!(Quat); impl_from_reflect_value!(DQuat); diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index 7c0b987ef47dd8..f8614cdb0078cc 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -15,27 +15,27 @@ use std::{ ops::Range, }; -impl_reflect_value!(bool(Hash, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(char(Hash, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(u8(Hash, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(u16(Hash, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(u32(Hash, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(u64(Hash, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(u128(Hash, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(usize(Hash, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(i8(Hash, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(i16(Hash, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(i32(Hash, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(i64(Hash, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(i128(Hash, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(isize(Hash, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(f32(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(f64(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(String(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(bool(Debug, Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(char(Debug, Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(u8(Debug, Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(u16(Debug, Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(u32(Debug, Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(u64(Debug, Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(u128(Debug, Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(usize(Debug, Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(i8(Debug, Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(i16(Debug, Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(i32(Debug, Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(i64(Debug, Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(i128(Debug, Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(isize(Debug, Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(f32(Debug, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(f64(Debug, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(String(Debug, Hash, PartialEq, Serialize, Deserialize)); impl_reflect_value!(Option Deserialize<'de> + Reflect + 'static>(Serialize, Deserialize)); impl_reflect_value!(HashSet Deserialize<'de> + Send + Sync + 'static>(Serialize, Deserialize)); impl_reflect_value!(Range Deserialize<'de> + Send + Sync + 'static>(Serialize, Deserialize)); -impl_reflect_value!(Duration(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(Duration(Debug, Hash, PartialEq, Serialize, Deserialize)); impl_from_reflect_value!(bool); impl_from_reflect_value!(char); diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index b484e47738cfd9..0319b144a24618 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -92,6 +92,7 @@ mod tests { ser::{to_string_pretty, PrettyConfig}, Deserializer, }; + use std::fmt::{Debug, Formatter}; use super::*; use crate as bevy_reflect; @@ -489,6 +490,87 @@ mod tests { let _ = trait_object.as_reflect(); } + #[test] + fn should_reflect_debug() { + #[derive(Reflect)] + struct Test { + value: usize, + list: Vec, + array: [f32; 3], + map: HashMap, + a_struct: SomeStruct, + a_tuple_struct: SomeTupleStruct, + custom: CustomDebug, + unknown: Option, + #[reflect(ignore)] + #[allow(dead_code)] + ignored: isize, + } + + #[derive(Reflect)] + struct SomeStruct { + foo: String, + } + + #[derive(Reflect)] + struct SomeTupleStruct(String); + + #[derive(Reflect)] + #[reflect(Debug)] + struct CustomDebug; + impl Debug for CustomDebug { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str("Cool debug!") + } + } + + let mut map = HashMap::new(); + map.insert(123, 1.23); + + let test = Test { + value: 123, + list: vec![String::from("A"), String::from("B"), String::from("C")], + array: [1.0, 2.0, 3.0], + map, + a_struct: SomeStruct { + foo: String::from("A Struct!"), + }, + a_tuple_struct: SomeTupleStruct(String::from("A Tuple Struct!")), + custom: CustomDebug, + unknown: Some(String::from("Enums aren't supported yet :(")), + ignored: 321, + }; + + let reflected: &dyn Reflect = &test; + let expected = r#" +bevy_reflect::tests::should_reflect_debug::Test { + value: 123, + list: [ + "A", + "B", + "C", + ], + array: [ + 1.0, + 2.0, + 3.0, + ], + map: { + 123: 1.23, + }, + a_struct: bevy_reflect::tests::should_reflect_debug::SomeStruct { + foo: "A Struct!", + }, + a_tuple_struct: bevy_reflect::tests::should_reflect_debug::SomeTupleStruct( + "A Tuple Struct!", + ), + custom: Cool debug!, + unknown: Reflect(core::option::Option), +}"#; + + assert_eq!(expected, format!("\n{:#?}", reflected)); + } + #[cfg(feature = "glam")] mod glam { use super::*; diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs index f2927556eadd34..a3fabcc5edf30a 100644 --- a/crates/bevy_reflect/src/list.rs +++ b/crates/bevy_reflect/src/list.rs @@ -1,4 +1,5 @@ use std::any::Any; +use std::fmt::{Debug, Formatter}; use crate::{serde::Serializable, Array, ArrayIter, DynamicArray, Reflect, ReflectMut, ReflectRef}; @@ -167,6 +168,18 @@ unsafe impl Reflect for DynamicList { fn serializable(&self) -> Option { Some(Serializable::Borrowed(self)) } + + fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "DynamicList(")?; + list_debug(self, f)?; + write!(f, ")") + } +} + +impl Debug for DynamicList { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.debug(f) + } } impl serde::Serialize for DynamicList { @@ -239,6 +252,32 @@ pub fn list_partial_eq(a: &L, b: &dyn Reflect) -> Option { Some(true) } +/// The default debug formatter for [`List`] types. +/// +/// # Example +/// ``` +/// use bevy_reflect::Reflect; +/// +/// let my_list: &dyn Reflect = &vec![1, 2, 3]; +/// println!("{:#?}", my_list); +/// +/// // Output: +/// +/// // [ +/// // 1, +/// // 2, +/// // 3, +/// // ] +/// ``` +#[inline] +pub fn list_debug(dyn_list: &dyn List, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug = f.debug_list(); + for item in dyn_list.iter() { + debug.entry(&item as &dyn Debug); + } + debug.finish() +} + #[cfg(test)] mod tests { use super::DynamicList; diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index ec6a748839a72f..988e723ef24e61 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -1,4 +1,5 @@ use std::any::Any; +use std::fmt::{Debug, Formatter}; use bevy_utils::{Entry, HashMap}; @@ -189,6 +190,18 @@ unsafe impl Reflect for DynamicMap { fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { map_partial_eq(self, value) } + + fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "DynamicMap(")?; + map_debug(self, f)?; + write!(f, ")") + } +} + +impl Debug for DynamicMap { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.debug(f) + } } /// An iterator over the key-value pairs of a [`Map`]. @@ -255,6 +268,32 @@ pub fn map_partial_eq(a: &M, b: &dyn Reflect) -> Option { Some(true) } +/// The default debug formatter for [`Map`] types. +/// +/// # Example +/// ``` +/// # use bevy_utils::HashMap; +/// use bevy_reflect::Reflect; +/// +/// let mut my_map = HashMap::new(); +/// my_map.insert(123, String::from("Hello")); +/// println!("{:#?}", &my_map as &dyn Reflect); +/// +/// // Output: +/// +/// // { +/// // 123: "Hello", +/// // } +/// ``` +#[inline] +pub fn map_debug(dyn_map: &dyn Map, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug = f.debug_map(); + for (key, value) in dyn_map.iter() { + debug.entry(&key as &dyn Debug, &value as &dyn Debug); + } + debug.finish() +} + #[cfg(test)] mod tests { use super::DynamicMap; diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 4dd2797e630a9f..1a7bb63973fe27 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -1,4 +1,8 @@ -use crate::{serde::Serializable, Array, List, Map, Struct, Tuple, TupleStruct}; +use crate::{ + array_debug, list_debug, map_debug, serde::Serializable, struct_debug, tuple_debug, + tuple_struct_debug, Array, List, Map, Struct, Tuple, TupleStruct, +}; + use std::{any::Any, fmt::Debug}; pub use bevy_utils::AHasher as ReflectHasher; @@ -141,6 +145,25 @@ pub unsafe trait Reflect: Any + Send + Sync { None } + /// Debug formatter for the value. + /// + /// Any value that is not an implementor of other `Reflect` subtraits + /// (e.g. [`List`], [`Map`]), will default to the format: `"Reflect(type_name)"`, + /// where `type_name` is the [type name] of the underlying type. + /// + /// [type name]: Self::type_name + fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.reflect_ref() { + ReflectRef::Struct(dyn_struct) => struct_debug(dyn_struct, f), + ReflectRef::TupleStruct(dyn_tuple_struct) => tuple_struct_debug(dyn_tuple_struct, f), + ReflectRef::Tuple(dyn_tuple) => tuple_debug(dyn_tuple, f), + ReflectRef::List(dyn_list) => list_debug(dyn_list, f), + ReflectRef::Array(dyn_array) => array_debug(dyn_array, f), + ReflectRef::Map(dyn_map) => map_debug(dyn_map, f), + _ => write!(f, "Reflect({})", self.type_name()), + } + } + /// Returns a serializable version of the value. /// /// If the underlying type does not support serialization, returns `None`. @@ -166,7 +189,7 @@ pub trait FromReflect: Reflect + Sized { impl Debug for dyn Reflect { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Reflect({})", self.type_name()) + self.debug(f) } } diff --git a/crates/bevy_reflect/src/struct_trait.rs b/crates/bevy_reflect/src/struct_trait.rs index b68a6ab7d4e1ff..74415d09c28571 100644 --- a/crates/bevy_reflect/src/struct_trait.rs +++ b/crates/bevy_reflect/src/struct_trait.rs @@ -1,5 +1,6 @@ use crate::{Reflect, ReflectMut, ReflectRef}; use bevy_utils::{Entry, HashMap}; +use std::fmt::{Debug, Formatter}; use std::{any::Any, borrow::Cow}; /// A reflected Rust regular struct type. @@ -315,6 +316,18 @@ unsafe impl Reflect for DynamicStruct { fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { struct_partial_eq(self, value) } + + fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "DynamicStruct(")?; + struct_debug(self, f)?; + write!(f, ")") + } +} + +impl Debug for DynamicStruct { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.debug(f) + } } /// Compares a [`Struct`] with a [`Reflect`] value. @@ -349,3 +362,35 @@ pub fn struct_partial_eq(a: &S, b: &dyn Reflect) -> Option { Some(true) } + +/// The default debug formatter for [`Struct`] types. +/// +/// # Example +/// ``` +/// use bevy_reflect::Reflect; +/// #[derive(Reflect)] +/// struct MyStruct { +/// foo: usize +/// } +/// +/// let my_struct: &dyn Reflect = &MyStruct { foo: 123 }; +/// println!("{:#?}", my_struct); +/// +/// // Output: +/// +/// // MyStruct { +/// // foo: 123, +/// // } +/// ``` +#[inline] +pub fn struct_debug(dyn_struct: &dyn Struct, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug = f.debug_struct(dyn_struct.type_name()); + for field_index in 0..dyn_struct.field_len() { + let field = dyn_struct.field_at(field_index).unwrap(); + debug.field( + dyn_struct.name_at(field_index).unwrap(), + &field as &dyn Debug, + ); + } + debug.finish() +} diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index 9803e88dd064df..3d5439171461ee 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -4,6 +4,7 @@ use crate::{ }; use serde::Deserialize; use std::any::Any; +use std::fmt::{Debug, Formatter}; /// A reflected Rust tuple. /// @@ -262,6 +263,12 @@ unsafe impl Reflect for DynamicTuple { fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { tuple_partial_eq(self, value) } + + fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "DynamicTuple(")?; + tuple_debug(self, f)?; + write!(f, ")") + } } /// Applies the elements of `b` to the corresponding elements of `a`. @@ -310,6 +317,32 @@ pub fn tuple_partial_eq(a: &T, b: &dyn Reflect) -> Option { Some(true) } +/// The default debug formatter for [`Tuple`] types. +/// +/// # Example +/// ``` +/// use bevy_reflect::Reflect; +/// +/// let my_tuple: &dyn Reflect = &(1, 2, 3); +/// println!("{:#?}", my_tuple); +/// +/// // Output: +/// +/// // ( +/// // 1, +/// // 2, +/// // 3, +/// // ) +/// ``` +#[inline] +pub fn tuple_debug(dyn_tuple: &dyn Tuple, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug = f.debug_tuple(""); + for field in dyn_tuple.iter_fields() { + debug.field(&field as &dyn Debug); + } + debug.finish() +} + macro_rules! impl_reflect_tuple { {$($index:tt : $name:tt),*} => { impl<$($name: Reflect),*> Tuple for ($($name,)*) { diff --git a/crates/bevy_reflect/src/tuple_struct.rs b/crates/bevy_reflect/src/tuple_struct.rs index 92a9d7bb641626..27e60b12d67bfe 100644 --- a/crates/bevy_reflect/src/tuple_struct.rs +++ b/crates/bevy_reflect/src/tuple_struct.rs @@ -1,5 +1,6 @@ use crate::{Reflect, ReflectMut, ReflectRef}; use std::any::Any; +use std::fmt::{Debug, Formatter}; /// A reflected Rust tuple struct. /// @@ -254,6 +255,18 @@ unsafe impl Reflect for DynamicTupleStruct { fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { tuple_struct_partial_eq(self, value) } + + fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "DynamicTupleStruct(")?; + tuple_struct_debug(self, f)?; + write!(f, ")") + } +} + +impl Debug for DynamicTupleStruct { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.debug(f) + } } /// Compares a [`TupleStruct`] with a [`Reflect`] value. @@ -286,3 +299,32 @@ pub fn tuple_struct_partial_eq(a: &S, b: &dyn Reflect) -> Option Some(true) } + +/// The default debug formatter for [`TupleStruct`] types. +/// +/// # Example +/// ``` +/// use bevy_reflect::Reflect; +/// #[derive(Reflect)] +/// struct MyTupleStruct(usize); +/// +/// let my_tuple_struct: &dyn Reflect = &MyTupleStruct(123); +/// println!("{:#?}", my_tuple_struct); +/// +/// // Output: +/// +/// // MyTupleStruct ( +/// // 123, +/// // ) +/// ``` +#[inline] +pub fn tuple_struct_debug( + dyn_tuple_struct: &dyn TupleStruct, + f: &mut std::fmt::Formatter<'_>, +) -> std::fmt::Result { + let mut debug = f.debug_tuple(dyn_tuple_struct.type_name()); + for field in dyn_tuple_struct.iter_fields() { + debug.field(&field as &dyn Debug); + } + debug.finish() +}