bevy_reflect: Contextual serialization error messages (#13888)

# Objective

Reflection serialization can be difficult to debug. A lot of times a
type fails to be serialized and the user is left wondering where that
type came from.

This is most often encountered with Bevy's scenes. Attempting to
serialize all resources in the world will fail because some resources
can't be serialized.

For example, users will often get complaints about `bevy_utils::Instant`
not registering `ReflectSerialize`. Well, `Instant` can't be serialized,
so the only other option is to exclude the resource that contains it.
But what resource contains it? This is where reflection serialization
can get a little tricky (it's `Time<Real>` btw).

## Solution

Add the `debug_stack` feature to `bevy_reflect`. When enabled, the
reflection serializers and deserializers will keep track of the current
type stack. And this stack will be used in error messages to help with
debugging.

Now, if we unknowingly try to serialize `Time<Real>`, we'll get the
following error:

```
type `bevy_utils::Instant` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data` (stack: `bevy_time::time::Time<bevy_time::real::Real>` -> `bevy_time::real::Real` -> `bevy_utils::Instant`)
```

### Implementation

This makes use of `thread_local!` to manage an internal `TypeInfoStack`
which holds a stack of `&'static TypeInfo`. We push to the stack before
a type is (de)serialized and pop from the stack afterwards.

Using a thread-local should be fine since we know two (de)serializers
can't be running at the same time (and if they're running on separate
threads, then we're still good).

The only potential issue would be if a user went through one of the
sub-serializers, like `StructSerializer`. However, I don't think many
users are going through these types (I don't even know if we necessarily
want to keep those public either, but we'll save that for a different
PR). Additionally, this is just a debug feature that only affects error
messages, so it wouldn't have any drastically negative effect. It would
just result in the stack not being cleared properly if there were any
errors.

Lastly, this is not the most performant implementation since we now
fetch the `TypeInfo` an extra time. But I figured that for a debug tool,
it wouldn't matter too much.

### Feature

This also adds a `debug` feature, which enables the `debug_stack`
feature.

I added it because I think we may want to potentially add more debug
tools in the future, and this gives us a good framework for adding
those. Users who want all debug features, present and future, can just
set `debug`. If they only want this feature, then they can just use
`debug_stack`.

I also made the `debug` feature default to help capture the widest
audience (i.e. the users who want this feature but don't know they do).
However, if we think it's better as a non-default feature, I can change
it!

And if there's any bikeshedding around the name `debug_stack`, let me
know!

## Testing

Run the following command:

```
cargo test --package bevy_reflect --features debug_stack
```

---

## Changelog

- Added the `debug` and `debug_stack` features to `bevy_reflect`
- Updated the error messages returned by the reflection serializers and
deserializers to include more contextual information when the
`debug_stack` or `debug` feature is enabled
This commit is contained in:
Gino Valente 2024-09-09 10:52:40 -07:00 committed by GitHub
parent a5c4606a98
commit 90bb1adeb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 482 additions and 171 deletions

View File

@ -10,13 +10,17 @@ keywords = ["bevy"]
rust-version = "1.76.0" rust-version = "1.76.0"
[features] [features]
default = ["smallvec"] default = ["smallvec", "debug"]
# When enabled, provides Bevy-related reflection implementations # When enabled, provides Bevy-related reflection implementations
bevy = ["smallvec", "smol_str"] bevy = ["smallvec", "smol_str"]
glam = ["dep:glam"] glam = ["dep:glam"]
petgraph = ["dep:petgraph"] petgraph = ["dep:petgraph"]
smallvec = ["dep:smallvec"] smallvec = ["dep:smallvec"]
uuid = ["dep:uuid"] uuid = ["dep:uuid"]
# Enables features useful for debugging reflection
debug = ["debug_stack"]
# When enabled, keeps track of the current serialization/deserialization context for better error messages
debug_stack = []
# When enabled, allows documentation comments to be accessed via reflection # When enabled, allows documentation comments to be accessed via reflection
documentation = ["bevy_reflect_derive/documentation"] documentation = ["bevy_reflect_derive/documentation"]
# Enables function reflection # Enables function reflection

View File

@ -473,7 +473,7 @@
//! //!
//! | Default | Dependencies | //! | Default | Dependencies |
//! | :-----: | :---------------------------------------: | //! | :-----: | :---------------------------------------: |
//! | ❌ | [`bevy_math`], [`glam`], [`smallvec`] | //! | ❌ | [`bevy_math`], [`glam`], [`smallvec`] |
//! //!
//! This feature makes it so that the appropriate reflection traits are implemented on all the types //! This feature makes it so that the appropriate reflection traits are implemented on all the types
//! necessary for the [Bevy] game engine. //! necessary for the [Bevy] game engine.
@ -493,6 +493,18 @@
//! This can be useful for generating documentation for scripting language interop or //! This can be useful for generating documentation for scripting language interop or
//! for displaying tooltips in an editor. //! for displaying tooltips in an editor.
//! //!
//! ## `debug`
//!
//! | Default | Dependencies |
//! | :-----: | :-------------------------------------------: |
//! | ✅ | `debug_stack` |
//!
//! This feature enables useful debug features for reflection.
//!
//! This includes the `debug_stack` feature,
//! which enables capturing the type stack when serializing or deserializing a type
//! and displaying it in error messages.
//!
//! [Reflection]: https://en.wikipedia.org/wiki/Reflective_programming //! [Reflection]: https://en.wikipedia.org/wiki/Reflective_programming
//! [Bevy]: https://bevyengine.org/ //! [Bevy]: https://bevyengine.org/
//! [limitations]: #limitations //! [limitations]: #limitations
@ -562,6 +574,8 @@ pub mod attributes;
mod enums; mod enums;
pub mod serde; pub mod serde;
pub mod std_traits; pub mod std_traits;
#[cfg(feature = "debug_stack")]
mod type_info_stack;
pub mod utility; pub mod utility;
/// The reflect prelude. /// The reflect prelude.

View File

@ -35,9 +35,10 @@ impl<'a, 'de> Visitor<'de> for ArrayVisitor<'a> {
{ {
let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or_default()); let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or_default());
let registration = try_get_registration(self.array_info.item_ty(), self.registry)?; let registration = try_get_registration(self.array_info.item_ty(), self.registry)?;
while let Some(value) = while let Some(value) = seq.next_element_seed(TypedReflectDeserializer::new_internal(
seq.next_element_seed(TypedReflectDeserializer::new(registration, self.registry))? registration,
{ self.registry,
))? {
vec.push(value); vec.push(value);
} }

View File

@ -1,5 +1,8 @@
use crate::serde::de::arrays::ArrayVisitor; use crate::serde::de::arrays::ArrayVisitor;
use crate::serde::de::enums::EnumVisitor; use crate::serde::de::enums::EnumVisitor;
use crate::serde::de::error_utils::make_custom_error;
#[cfg(feature = "debug_stack")]
use crate::serde::de::error_utils::TYPE_INFO_STACK;
use crate::serde::de::lists::ListVisitor; use crate::serde::de::lists::ListVisitor;
use crate::serde::de::maps::MapVisitor; use crate::serde::de::maps::MapVisitor;
use crate::serde::de::options::OptionVisitor; use crate::serde::de::options::OptionVisitor;
@ -231,6 +234,20 @@ pub struct TypedReflectDeserializer<'a> {
impl<'a> TypedReflectDeserializer<'a> { impl<'a> TypedReflectDeserializer<'a> {
pub fn new(registration: &'a TypeRegistration, registry: &'a TypeRegistry) -> Self { pub fn new(registration: &'a TypeRegistration, registry: &'a TypeRegistry) -> Self {
#[cfg(feature = "debug_stack")]
TYPE_INFO_STACK.set(crate::type_info_stack::TypeInfoStack::new());
Self {
registration,
registry,
}
}
/// An internal constructor for creating a deserializer without resetting the type info stack.
pub(super) fn new_internal(
registration: &'a TypeRegistration,
registry: &'a TypeRegistry,
) -> Self {
Self { Self {
registration, registration,
registry, registry,
@ -245,89 +262,106 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> {
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
let type_path = self.registration.type_info().type_path(); let deserialize_internal = || -> Result<Self::Value, D::Error> {
let type_path = self.registration.type_info().type_path();
// Handle both Value case and types that have a custom `ReflectDeserialize` // Handle both Value case and types that have a custom `ReflectDeserialize`
if let Some(deserialize_reflect) = self.registration.data::<ReflectDeserialize>() { if let Some(deserialize_reflect) = self.registration.data::<ReflectDeserialize>() {
let value = deserialize_reflect.deserialize(deserializer)?; let value = deserialize_reflect.deserialize(deserializer)?;
return Ok(value.into_partial_reflect()); return Ok(value.into_partial_reflect());
} }
match self.registration.type_info() { match self.registration.type_info() {
TypeInfo::Struct(struct_info) => { TypeInfo::Struct(struct_info) => {
let mut dynamic_struct = deserializer.deserialize_struct( let mut dynamic_struct = deserializer.deserialize_struct(
struct_info.type_path_table().ident().unwrap(), struct_info.type_path_table().ident().unwrap(),
struct_info.field_names(), struct_info.field_names(),
StructVisitor::new(struct_info, self.registration, self.registry), StructVisitor::new(struct_info, self.registration, self.registry),
)?; )?;
dynamic_struct.set_represented_type(Some(self.registration.type_info())); dynamic_struct.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_struct)) Ok(Box::new(dynamic_struct))
}
TypeInfo::TupleStruct(tuple_struct_info) => {
let mut dynamic_tuple_struct = deserializer.deserialize_tuple_struct(
tuple_struct_info.type_path_table().ident().unwrap(),
tuple_struct_info.field_len(),
TupleStructVisitor::new(
tuple_struct_info,
self.registration,
self.registry,
),
)?;
dynamic_tuple_struct.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_tuple_struct))
}
TypeInfo::List(list_info) => {
let mut dynamic_list =
deserializer.deserialize_seq(ListVisitor::new(list_info, self.registry))?;
dynamic_list.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_list))
}
TypeInfo::Array(array_info) => {
let mut dynamic_array = deserializer.deserialize_tuple(
array_info.capacity(),
ArrayVisitor::new(array_info, self.registry),
)?;
dynamic_array.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_array))
}
TypeInfo::Map(map_info) => {
let mut dynamic_map =
deserializer.deserialize_map(MapVisitor::new(map_info, self.registry))?;
dynamic_map.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_map))
}
TypeInfo::Set(set_info) => {
let mut dynamic_set =
deserializer.deserialize_seq(SetVisitor::new(set_info, self.registry))?;
dynamic_set.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_set))
}
TypeInfo::Tuple(tuple_info) => {
let mut dynamic_tuple = deserializer.deserialize_tuple(
tuple_info.field_len(),
TupleVisitor::new(tuple_info, self.registration, self.registry),
)?;
dynamic_tuple.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_tuple))
}
TypeInfo::Enum(enum_info) => {
let mut dynamic_enum = if enum_info.type_path_table().module_path()
== Some("core::option")
&& enum_info.type_path_table().ident() == Some("Option")
{
deserializer
.deserialize_option(OptionVisitor::new(enum_info, self.registry))?
} else {
deserializer.deserialize_enum(
enum_info.type_path_table().ident().unwrap(),
enum_info.variant_names(),
EnumVisitor::new(enum_info, self.registration, self.registry),
)?
};
dynamic_enum.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_enum))
}
TypeInfo::Value(_) => {
// This case should already be handled
Err(make_custom_error(format_args!(
"type `{type_path}` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`",
)))
}
} }
TypeInfo::TupleStruct(tuple_struct_info) => { };
let mut dynamic_tuple_struct = deserializer.deserialize_tuple_struct(
tuple_struct_info.type_path_table().ident().unwrap(), #[cfg(feature = "debug_stack")]
tuple_struct_info.field_len(), TYPE_INFO_STACK.with_borrow_mut(|stack| stack.push(self.registration.type_info()));
TupleStructVisitor::new(tuple_struct_info, self.registration, self.registry),
)?; let output = deserialize_internal();
dynamic_tuple_struct.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_tuple_struct)) #[cfg(feature = "debug_stack")]
} TYPE_INFO_STACK.with_borrow_mut(crate::type_info_stack::TypeInfoStack::pop);
TypeInfo::List(list_info) => {
let mut dynamic_list = output
deserializer.deserialize_seq(ListVisitor::new(list_info, self.registry))?;
dynamic_list.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_list))
}
TypeInfo::Array(array_info) => {
let mut dynamic_array = deserializer.deserialize_tuple(
array_info.capacity(),
ArrayVisitor::new(array_info, self.registry),
)?;
dynamic_array.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_array))
}
TypeInfo::Map(map_info) => {
let mut dynamic_map =
deserializer.deserialize_map(MapVisitor::new(map_info, self.registry))?;
dynamic_map.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_map))
}
TypeInfo::Set(set_info) => {
let mut dynamic_set =
deserializer.deserialize_seq(SetVisitor::new(set_info, self.registry))?;
dynamic_set.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_set))
}
TypeInfo::Tuple(tuple_info) => {
let mut dynamic_tuple = deserializer.deserialize_tuple(
tuple_info.field_len(),
TupleVisitor::new(tuple_info, self.registration, self.registry),
)?;
dynamic_tuple.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_tuple))
}
TypeInfo::Enum(enum_info) => {
let mut dynamic_enum = if enum_info.type_path_table().module_path()
== Some("core::option")
&& enum_info.type_path_table().ident() == Some("Option")
{
deserializer.deserialize_option(OptionVisitor::new(enum_info, self.registry))?
} else {
deserializer.deserialize_enum(
enum_info.type_path_table().ident().unwrap(),
enum_info.variant_names(),
EnumVisitor::new(enum_info, self.registration, self.registry),
)?
};
dynamic_enum.set_represented_type(Some(self.registration.type_info()));
Ok(Box::new(dynamic_enum))
}
TypeInfo::Value(_) => {
// This case should already be handled
Err(Error::custom(format_args!(
"Type `{type_path}` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`",
)))
}
}
} }
} }

View File

@ -1,3 +1,4 @@
use crate::serde::de::error_utils::make_custom_error;
use crate::serde::de::helpers::ExpectedValues; use crate::serde::de::helpers::ExpectedValues;
use crate::serde::de::registration_utils::try_get_registration; use crate::serde::de::registration_utils::try_get_registration;
use crate::serde::de::struct_utils::{visit_struct, visit_struct_seq}; use crate::serde::de::struct_utils::{visit_struct, visit_struct_seq};
@ -67,10 +68,9 @@ impl<'a, 'de> Visitor<'de> for EnumVisitor<'a> {
*TupleLikeInfo::field_at(tuple_info, 0)?.ty(), *TupleLikeInfo::field_at(tuple_info, 0)?.ty(),
self.registry, self.registry,
)?; )?;
let value = variant.newtype_variant_seed(TypedReflectDeserializer::new( let value = variant.newtype_variant_seed(
registration, TypedReflectDeserializer::new_internal(registration, self.registry),
self.registry, )?;
))?;
let mut dynamic_tuple = DynamicTuple::default(); let mut dynamic_tuple = DynamicTuple::default();
dynamic_tuple.insert_boxed(value); dynamic_tuple.insert_boxed(value);
dynamic_tuple.into() dynamic_tuple.into()
@ -121,7 +121,7 @@ impl<'de> DeserializeSeed<'de> for VariantDeserializer {
E: Error, E: Error,
{ {
self.0.variant_at(variant_index as usize).ok_or_else(|| { self.0.variant_at(variant_index as usize).ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"no variant found at index `{}` on enum `{}`", "no variant found at index `{}` on enum `{}`",
variant_index, variant_index,
self.0.type_path() self.0.type_path()
@ -135,7 +135,7 @@ impl<'de> DeserializeSeed<'de> for VariantDeserializer {
{ {
self.0.variant(variant_name).ok_or_else(|| { self.0.variant(variant_name).ok_or_else(|| {
let names = self.0.iter().map(VariantInfo::name); let names = self.0.iter().map(VariantInfo::name);
Error::custom(format_args!( make_custom_error(format_args!(
"unknown variant `{}`, expected one of {:?}", "unknown variant `{}`, expected one of {:?}",
variant_name, variant_name,
ExpectedValues::from_iter(names) ExpectedValues::from_iter(names)

View File

@ -0,0 +1,26 @@
use core::fmt::Display;
use serde::de::Error;
#[cfg(feature = "debug_stack")]
thread_local! {
/// The thread-local [`TypeInfoStack`] used for debugging.
///
/// [`TypeInfoStack`]: crate::type_info_stack::TypeInfoStack
pub(super) static TYPE_INFO_STACK: std::cell::RefCell<crate::type_info_stack::TypeInfoStack> = const { std::cell::RefCell::new(
crate::type_info_stack::TypeInfoStack::new()
) };
}
/// A helper function for generating a custom deserialization error message.
///
/// This function should be preferred over [`Error::custom`] as it will include
/// other useful information, such as the [type info stack].
///
/// [type info stack]: crate::type_info_stack::TypeInfoStack
pub(super) fn make_custom_error<E: Error>(msg: impl Display) -> E {
#[cfg(feature = "debug_stack")]
return TYPE_INFO_STACK
.with_borrow(|stack| E::custom(format_args!("{} (stack: {:?})", msg, stack)));
#[cfg(not(feature = "debug_stack"))]
return E::custom(msg);
}

View File

@ -35,9 +35,10 @@ impl<'a, 'de> Visitor<'de> for ListVisitor<'a> {
{ {
let mut list = DynamicList::default(); let mut list = DynamicList::default();
let registration = try_get_registration(self.list_info.item_ty(), self.registry)?; let registration = try_get_registration(self.list_info.item_ty(), self.registry)?;
while let Some(value) = while let Some(value) = seq.next_element_seed(TypedReflectDeserializer::new_internal(
seq.next_element_seed(TypedReflectDeserializer::new(registration, self.registry))? registration,
{ self.registry,
))? {
list.push_box(value); list.push_box(value);
} }
Ok(list) Ok(list)

View File

@ -33,11 +33,11 @@ impl<'a, 'de> Visitor<'de> for MapVisitor<'a> {
let mut dynamic_map = DynamicMap::default(); let mut dynamic_map = DynamicMap::default();
let key_registration = try_get_registration(self.map_info.key_ty(), self.registry)?; let key_registration = try_get_registration(self.map_info.key_ty(), self.registry)?;
let value_registration = try_get_registration(self.map_info.value_ty(), self.registry)?; let value_registration = try_get_registration(self.map_info.value_ty(), self.registry)?;
while let Some(key) = map.next_key_seed(TypedReflectDeserializer::new( while let Some(key) = map.next_key_seed(TypedReflectDeserializer::new_internal(
key_registration, key_registration,
self.registry, self.registry,
))? { ))? {
let value = map.next_value_seed(TypedReflectDeserializer::new( let value = map.next_value_seed(TypedReflectDeserializer::new_internal(
value_registration, value_registration,
self.registry, self.registry,
))?; ))?;

View File

@ -4,6 +4,7 @@ pub use registrations::*;
mod arrays; mod arrays;
mod deserializer; mod deserializer;
mod enums; mod enums;
mod error_utils;
mod helpers; mod helpers;
mod lists; mod lists;
mod maps; mod maps;
@ -275,7 +276,7 @@ mod tests {
let mut registry = get_registry(); let mut registry = get_registry();
registry.register::<Foo>(); registry.register::<Foo>();
let registration = registry.get(TypeId::of::<Foo>()).unwrap(); let registration = registry.get(TypeId::of::<Foo>()).unwrap();
let reflect_deserializer = TypedReflectDeserializer::new(registration, &registry); let reflect_deserializer = TypedReflectDeserializer::new_internal(registration, &registry);
let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap();
let dynamic_output = reflect_deserializer let dynamic_output = reflect_deserializer
.deserialize(&mut ron_deserializer) .deserialize(&mut ron_deserializer)
@ -510,6 +511,52 @@ mod tests {
let error = reflect_deserializer let error = reflect_deserializer
.deserialize(&mut deserializer) .deserialize(&mut deserializer)
.unwrap_err(); .unwrap_err();
assert_eq!(error, ron::Error::Message("Type `core::ops::RangeInclusive<f32>` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string())); #[cfg(feature = "debug_stack")]
assert_eq!(error, ron::Error::Message("type `core::ops::RangeInclusive<f32>` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data` (stack: `core::ops::RangeInclusive<f32>`)".to_string()));
#[cfg(not(feature = "debug_stack"))]
assert_eq!(error, ron::Error::Message("type `core::ops::RangeInclusive<f32>` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string()));
}
#[cfg(feature = "debug_stack")]
mod debug_stack {
use super::*;
#[test]
fn should_report_context_in_errors() {
#[derive(Reflect)]
struct Foo {
bar: Bar,
}
#[derive(Reflect)]
struct Bar {
some_other_field: Option<u32>,
baz: Baz,
}
#[derive(Reflect)]
struct Baz {
value: Vec<RangeInclusive<f32>>,
}
let mut registry = TypeRegistry::new();
registry.register::<Foo>();
registry.register::<Bar>();
registry.register::<Baz>();
registry.register::<RangeInclusive<f32>>();
let input = r#"{"bevy_reflect::serde::de::tests::debug_stack::Foo":(bar:(some_other_field:Some(123),baz:(value:[(start:0.0,end:1.0)])))}"#;
let mut deserializer = ron::de::Deserializer::from_str(input).unwrap();
let reflect_deserializer = ReflectDeserializer::new(&registry);
let error = reflect_deserializer
.deserialize(&mut deserializer)
.unwrap_err();
assert_eq!(
error,
ron::Error::Message(
"type `core::ops::RangeInclusive<f32>` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data` (stack: `bevy_reflect::serde::de::tests::debug_stack::Foo` -> `bevy_reflect::serde::de::tests::debug_stack::Bar` -> `bevy_reflect::serde::de::tests::debug_stack::Baz` -> `alloc::vec::Vec<core::ops::RangeInclusive<f32>>` -> `core::ops::RangeInclusive<f32>`)".to_string()
)
);
}
} }
} }

View File

@ -1,3 +1,4 @@
use crate::serde::de::error_utils::make_custom_error;
use crate::serde::de::registration_utils::try_get_registration; use crate::serde::de::registration_utils::try_get_registration;
use crate::serde::TypedReflectDeserializer; use crate::serde::TypedReflectDeserializer;
use crate::{DynamicEnum, DynamicTuple, EnumInfo, TypeRegistry, VariantInfo}; use crate::{DynamicEnum, DynamicTuple, EnumInfo, TypeRegistry, VariantInfo};
@ -46,14 +47,14 @@ impl<'a, 'de> Visitor<'de> for OptionVisitor<'a> {
VariantInfo::Tuple(tuple_info) if tuple_info.field_len() == 1 => { VariantInfo::Tuple(tuple_info) if tuple_info.field_len() == 1 => {
let field = tuple_info.field_at(0).unwrap(); let field = tuple_info.field_at(0).unwrap();
let registration = try_get_registration(*field.ty(), self.registry)?; let registration = try_get_registration(*field.ty(), self.registry)?;
let de = TypedReflectDeserializer::new(registration, self.registry); let de = TypedReflectDeserializer::new_internal(registration, self.registry);
let mut value = DynamicTuple::default(); let mut value = DynamicTuple::default();
value.insert_boxed(de.deserialize(deserializer)?); value.insert_boxed(de.deserialize(deserializer)?);
let mut option = DynamicEnum::default(); let mut option = DynamicEnum::default();
option.set_variant("Some", value); option.set_variant("Some", value);
Ok(option) Ok(option)
} }
info => Err(Error::custom(format_args!( info => Err(make_custom_error(format_args!(
"invalid variant, expected `Some` but got `{}`", "invalid variant, expected `Some` but got `{}`",
info.name() info.name()
))), ))),

View File

@ -1,3 +1,4 @@
use crate::serde::de::error_utils::make_custom_error;
use crate::{Type, TypeRegistration, TypeRegistry}; use crate::{Type, TypeRegistration, TypeRegistry};
use serde::de::Error; use serde::de::Error;
@ -8,8 +9,8 @@ pub(super) fn try_get_registration<E: Error>(
ty: Type, ty: Type,
registry: &TypeRegistry, registry: &TypeRegistry,
) -> Result<&TypeRegistration, E> { ) -> Result<&TypeRegistration, E> {
let registration = registry let registration = registry.get(ty.id()).ok_or_else(|| {
.get(ty.id()) make_custom_error(format_args!("no registration found for type `{ty:?}`"))
.ok_or_else(|| Error::custom(format_args!("no registration found for type `{ty:?}`")))?; })?;
Ok(registration) Ok(registration)
} }

View File

@ -1,3 +1,4 @@
use crate::serde::de::error_utils::make_custom_error;
use crate::{TypeRegistration, TypeRegistry}; use crate::{TypeRegistration, TypeRegistry};
use core::fmt::Formatter; use core::fmt::Formatter;
use serde::de::{DeserializeSeed, Error, Visitor}; use serde::de::{DeserializeSeed, Error, Visitor};
@ -42,7 +43,7 @@ impl<'a, 'de> DeserializeSeed<'de> for TypeRegistrationDeserializer<'a> {
E: Error, E: Error,
{ {
self.0.get_with_type_path(type_path).ok_or_else(|| { self.0.get_with_type_path(type_path).ok_or_else(|| {
Error::custom(format_args!("No registration found for `{type_path}`")) make_custom_error(format_args!("no registration found for `{type_path}`"))
}) })
} }
} }

View File

@ -32,7 +32,7 @@ impl<'a, 'de> Visitor<'de> for SetVisitor<'a> {
{ {
let mut dynamic_set = DynamicSet::default(); let mut dynamic_set = DynamicSet::default();
let value_registration = try_get_registration(self.set_info.value_ty(), self.registry)?; let value_registration = try_get_registration(self.set_info.value_ty(), self.registry)?;
while let Some(value) = set.next_element_seed(TypedReflectDeserializer::new( while let Some(value) = set.next_element_seed(TypedReflectDeserializer::new_internal(
value_registration, value_registration,
self.registry, self.registry,
))? { ))? {

View File

@ -1,3 +1,4 @@
use crate::serde::de::error_utils::make_custom_error;
use crate::serde::de::helpers::{ExpectedValues, Ident}; use crate::serde::de::helpers::{ExpectedValues, Ident};
use crate::serde::de::registration_utils::try_get_registration; use crate::serde::de::registration_utils::try_get_registration;
use crate::serde::{SerializationData, TypedReflectDeserializer}; use crate::serde::{SerializationData, TypedReflectDeserializer};
@ -18,8 +19,8 @@ pub(super) trait StructLikeInfo {
impl StructLikeInfo for StructInfo { impl StructLikeInfo for StructInfo {
fn field<E: Error>(&self, name: &str) -> Result<&NamedField, E> { fn field<E: Error>(&self, name: &str) -> Result<&NamedField, E> {
Self::field(self, name).ok_or_else(|| { Self::field(self, name).ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"no field named {} on struct {}", "no field named `{}` on struct `{}`",
name, name,
self.type_path(), self.type_path(),
)) ))
@ -28,8 +29,8 @@ impl StructLikeInfo for StructInfo {
fn field_at<E: Error>(&self, index: usize) -> Result<&NamedField, E> { fn field_at<E: Error>(&self, index: usize) -> Result<&NamedField, E> {
Self::field_at(self, index).ok_or_else(|| { Self::field_at(self, index).ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"no field at index {} on struct {}", "no field at index `{}` on struct `{}`",
index, index,
self.type_path(), self.type_path(),
)) ))
@ -48,8 +49,8 @@ impl StructLikeInfo for StructInfo {
impl StructLikeInfo for StructVariantInfo { impl StructLikeInfo for StructVariantInfo {
fn field<E: Error>(&self, name: &str) -> Result<&NamedField, E> { fn field<E: Error>(&self, name: &str) -> Result<&NamedField, E> {
Self::field(self, name).ok_or_else(|| { Self::field(self, name).ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"no field named {} on variant {}", "no field named `{}` on variant `{}`",
name, name,
self.name(), self.name(),
)) ))
@ -58,8 +59,8 @@ impl StructLikeInfo for StructVariantInfo {
fn field_at<E: Error>(&self, index: usize) -> Result<&NamedField, E> { fn field_at<E: Error>(&self, index: usize) -> Result<&NamedField, E> {
Self::field_at(self, index).ok_or_else(|| { Self::field_at(self, index).ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"no field at index {} on variant {}", "no field at index `{}` on variant `{}`",
index, index,
self.name(), self.name(),
)) ))
@ -92,14 +93,17 @@ where
while let Some(Ident(key)) = map.next_key::<Ident>()? { while let Some(Ident(key)) = map.next_key::<Ident>()? {
let field = info.field::<V::Error>(&key).map_err(|_| { let field = info.field::<V::Error>(&key).map_err(|_| {
let fields = info.iter_fields().map(NamedField::name); let fields = info.iter_fields().map(NamedField::name);
Error::custom(format_args!( make_custom_error(format_args!(
"unknown field `{}`, expected one of {:?}", "unknown field `{}`, expected one of {:?}",
key, key,
ExpectedValues::from_iter(fields) ExpectedValues::from_iter(fields)
)) ))
})?; })?;
let registration = try_get_registration(*field.ty(), registry)?; let registration = try_get_registration(*field.ty(), registry)?;
let value = map.next_value_seed(TypedReflectDeserializer::new(registration, registry))?; let value = map.next_value_seed(TypedReflectDeserializer::new_internal(
registration,
registry,
))?;
dynamic_struct.insert_boxed(&key, value); dynamic_struct.insert_boxed(&key, value);
} }
@ -156,7 +160,7 @@ where
} }
let value = seq let value = seq
.next_element_seed(TypedReflectDeserializer::new( .next_element_seed(TypedReflectDeserializer::new_internal(
try_get_registration(*info.field_at(index)?.ty(), registry)?, try_get_registration(*info.field_at(index)?.ty(), registry)?,
registry, registry,
))? ))?

View File

@ -1,3 +1,4 @@
use crate::serde::de::error_utils::make_custom_error;
use crate::serde::de::registration_utils::try_get_registration; use crate::serde::de::registration_utils::try_get_registration;
use crate::serde::{SerializationData, TypedReflectDeserializer}; use crate::serde::{SerializationData, TypedReflectDeserializer};
use crate::{ use crate::{
@ -18,8 +19,8 @@ impl TupleLikeInfo for TupleInfo {
fn field_at<E: Error>(&self, index: usize) -> Result<&UnnamedField, E> { fn field_at<E: Error>(&self, index: usize) -> Result<&UnnamedField, E> {
Self::field_at(self, index).ok_or_else(|| { Self::field_at(self, index).ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"no field at index {} on tuple {}", "no field at index `{}` on tuple `{}`",
index, index,
self.type_path(), self.type_path(),
)) ))
@ -34,8 +35,8 @@ impl TupleLikeInfo for TupleStructInfo {
fn field_at<E: Error>(&self, index: usize) -> Result<&UnnamedField, E> { fn field_at<E: Error>(&self, index: usize) -> Result<&UnnamedField, E> {
Self::field_at(self, index).ok_or_else(|| { Self::field_at(self, index).ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"no field at index {} on tuple struct {}", "no field at index `{}` on tuple struct `{}`",
index, index,
self.type_path(), self.type_path(),
)) ))
@ -50,8 +51,8 @@ impl TupleLikeInfo for TupleVariantInfo {
fn field_at<E: Error>(&self, index: usize) -> Result<&UnnamedField, E> { fn field_at<E: Error>(&self, index: usize) -> Result<&UnnamedField, E> {
Self::field_at(self, index).ok_or_else(|| { Self::field_at(self, index).ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"no field at index {} on tuple variant {}", "no field at index `{}` on tuple variant `{}`",
index, index,
self.name(), self.name(),
)) ))
@ -90,7 +91,7 @@ where
} }
let value = seq let value = seq
.next_element_seed(TypedReflectDeserializer::new( .next_element_seed(TypedReflectDeserializer::new_internal(
try_get_registration(*info.field_at(index)?.ty(), registry)?, try_get_registration(*info.field_at(index)?.ty(), registry)?,
registry, registry,
))? ))?

View File

@ -142,7 +142,7 @@ mod tests {
#[test] #[test]
#[should_panic( #[should_panic(
expected = "cannot serialize dynamic value without represented type: bevy_reflect::DynamicStruct" expected = "cannot serialize dynamic value without represented type: `bevy_reflect::DynamicStruct`"
)] )]
fn should_not_serialize_unproxied_dynamic() { fn should_not_serialize_unproxied_dynamic() {
let registry = TypeRegistry::default(); let registry = TypeRegistry::default();

View File

@ -22,7 +22,7 @@ impl<'a> Serialize for ArraySerializer<'a> {
{ {
let mut state = serializer.serialize_tuple(self.array.len())?; let mut state = serializer.serialize_tuple(self.array.len())?;
for value in self.array.iter() { for value in self.array.iter() {
state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; state.serialize_element(&TypedReflectSerializer::new_internal(value, self.registry))?;
} }
state.end() state.end()
} }

View File

@ -1,6 +1,7 @@
use crate::serde::ser::error_utils::make_custom_error;
use crate::serde::TypedReflectSerializer; use crate::serde::TypedReflectSerializer;
use crate::{Enum, TypeInfo, TypeRegistry, VariantInfo, VariantType}; use crate::{Enum, TypeInfo, TypeRegistry, VariantInfo, VariantType};
use serde::ser::{Error, SerializeStructVariant, SerializeTupleVariant}; use serde::ser::{SerializeStructVariant, SerializeTupleVariant};
use serde::Serialize; use serde::Serialize;
/// A serializer for [`Enum`] values. /// A serializer for [`Enum`] values.
@ -24,8 +25,8 @@ impl<'a> Serialize for EnumSerializer<'a> {
S: serde::Serializer, S: serde::Serializer,
{ {
let type_info = self.enum_value.get_represented_type_info().ok_or_else(|| { let type_info = self.enum_value.get_represented_type_info().ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"cannot get type info for {}", "cannot get type info for `{}`",
self.enum_value.reflect_type_path() self.enum_value.reflect_type_path()
)) ))
})?; })?;
@ -33,7 +34,7 @@ impl<'a> Serialize for EnumSerializer<'a> {
let enum_info = match type_info { let enum_info = match type_info {
TypeInfo::Enum(enum_info) => enum_info, TypeInfo::Enum(enum_info) => enum_info,
info => { info => {
return Err(Error::custom(format_args!( return Err(make_custom_error(format_args!(
"expected enum type but received {info:?}" "expected enum type but received {info:?}"
))); )));
} }
@ -44,7 +45,7 @@ impl<'a> Serialize for EnumSerializer<'a> {
let variant_info = enum_info let variant_info = enum_info
.variant_at(variant_index as usize) .variant_at(variant_index as usize)
.ok_or_else(|| { .ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"variant at index `{variant_index}` does not exist", "variant at index `{variant_index}` does not exist",
)) ))
})?; })?;
@ -66,7 +67,7 @@ impl<'a> Serialize for EnumSerializer<'a> {
let struct_info = match variant_info { let struct_info = match variant_info {
VariantInfo::Struct(struct_info) => struct_info, VariantInfo::Struct(struct_info) => struct_info,
info => { info => {
return Err(Error::custom(format_args!( return Err(make_custom_error(format_args!(
"expected struct variant type but received {info:?}", "expected struct variant type but received {info:?}",
))); )));
} }
@ -82,7 +83,7 @@ impl<'a> Serialize for EnumSerializer<'a> {
let field_info = struct_info.field_at(index).unwrap(); let field_info = struct_info.field_at(index).unwrap();
state.serialize_field( state.serialize_field(
field_info.name(), field_info.name(),
&TypedReflectSerializer::new(field.value(), self.registry), &TypedReflectSerializer::new_internal(field.value(), self.registry),
)?; )?;
} }
state.end() state.end()
@ -93,13 +94,14 @@ impl<'a> Serialize for EnumSerializer<'a> {
if type_info.type_path_table().module_path() == Some("core::option") if type_info.type_path_table().module_path() == Some("core::option")
&& type_info.type_path_table().ident() == Some("Option") && type_info.type_path_table().ident() == Some("Option")
{ {
serializer.serialize_some(&TypedReflectSerializer::new(field, self.registry)) serializer
.serialize_some(&TypedReflectSerializer::new_internal(field, self.registry))
} else { } else {
serializer.serialize_newtype_variant( serializer.serialize_newtype_variant(
enum_name, enum_name,
variant_index, variant_index,
variant_name, variant_name,
&TypedReflectSerializer::new(field, self.registry), &TypedReflectSerializer::new_internal(field, self.registry),
) )
} }
} }
@ -111,7 +113,7 @@ impl<'a> Serialize for EnumSerializer<'a> {
field_len, field_len,
)?; )?;
for field in self.enum_value.iter_fields() { for field in self.enum_value.iter_fields() {
state.serialize_field(&TypedReflectSerializer::new( state.serialize_field(&TypedReflectSerializer::new_internal(
field.value(), field.value(),
self.registry, self.registry,
))?; ))?;

View File

@ -0,0 +1,26 @@
use core::fmt::Display;
use serde::ser::Error;
#[cfg(feature = "debug_stack")]
thread_local! {
/// The thread-local [`TypeInfoStack`] used for debugging.
///
/// [`TypeInfoStack`]: crate::type_info_stack::TypeInfoStack
pub(super) static TYPE_INFO_STACK: std::cell::RefCell<crate::type_info_stack::TypeInfoStack> = const { std::cell::RefCell::new(
crate::type_info_stack::TypeInfoStack::new()
) };
}
/// A helper function for generating a custom serialization error message.
///
/// This function should be preferred over [`Error::custom`] as it will include
/// other useful information, such as the [type info stack].
///
/// [type info stack]: crate::type_info_stack::TypeInfoStack
pub(super) fn make_custom_error<E: Error>(msg: impl Display) -> E {
#[cfg(feature = "debug_stack")]
return TYPE_INFO_STACK
.with_borrow(|stack| E::custom(format_args!("{} (stack: {:?})", msg, stack)));
#[cfg(not(feature = "debug_stack"))]
return E::custom(msg);
}

View File

@ -22,7 +22,7 @@ impl<'a> Serialize for ListSerializer<'a> {
{ {
let mut state = serializer.serialize_seq(Some(self.list.len()))?; let mut state = serializer.serialize_seq(Some(self.list.len()))?;
for value in self.list.iter() { for value in self.list.iter() {
state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; state.serialize_element(&TypedReflectSerializer::new_internal(value, self.registry))?;
} }
state.end() state.end()
} }

View File

@ -23,8 +23,8 @@ impl<'a> Serialize for MapSerializer<'a> {
let mut state = serializer.serialize_map(Some(self.map.len()))?; let mut state = serializer.serialize_map(Some(self.map.len()))?;
for (key, value) in self.map.iter() { for (key, value) in self.map.iter() {
state.serialize_entry( state.serialize_entry(
&TypedReflectSerializer::new(key, self.registry), &TypedReflectSerializer::new_internal(key, self.registry),
&TypedReflectSerializer::new(value, self.registry), &TypedReflectSerializer::new_internal(value, self.registry),
)?; )?;
} }
state.end() state.end()

View File

@ -3,6 +3,7 @@ pub use serializer::*;
mod arrays; mod arrays;
mod enums; mod enums;
mod error_utils;
mod lists; mod lists;
mod maps; mod maps;
mod serializable; mod serializable;
@ -429,11 +430,20 @@ mod tests {
let serializer = ReflectSerializer::new(&value, &registry); let serializer = ReflectSerializer::new(&value, &registry);
let error = ron::ser::to_string(&serializer).unwrap_err(); let error = ron::ser::to_string(&serializer).unwrap_err();
#[cfg(feature = "debug_stack")]
assert_eq!( assert_eq!(
error, error,
ron::Error::Message( ron::Error::Message(
"Type `core::ops::RangeInclusive<f32>` is not registered in the type registry" "type `core::ops::RangeInclusive<f32>` is not registered in the type registry (stack: `core::ops::RangeInclusive<f32>`)"
.to_string() .to_string(),
)
);
#[cfg(not(feature = "debug_stack"))]
assert_eq!(
error,
ron::Error::Message(
"type `core::ops::RangeInclusive<f32>` is not registered in the type registry"
.to_string(),
) )
); );
} }
@ -446,11 +456,63 @@ mod tests {
let serializer = ReflectSerializer::new(&value, &registry); let serializer = ReflectSerializer::new(&value, &registry);
let error = ron::ser::to_string(&serializer).unwrap_err(); let error = ron::ser::to_string(&serializer).unwrap_err();
#[cfg(feature = "debug_stack")]
assert_eq!( assert_eq!(
error, error,
ron::Error::Message( ron::Error::Message(
"Type `core::ops::RangeInclusive<f32>` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string() "type `core::ops::RangeInclusive<f32>` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data` (stack: `core::ops::RangeInclusive<f32>`)".to_string()
)
);
#[cfg(not(feature = "debug_stack"))]
assert_eq!(
error,
ron::Error::Message(
"type `core::ops::RangeInclusive<f32>` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string()
) )
); );
} }
#[cfg(feature = "debug_stack")]
mod debug_stack {
use super::*;
#[test]
fn should_report_context_in_errors() {
#[derive(Reflect)]
struct Foo {
bar: Bar,
}
#[derive(Reflect)]
struct Bar {
some_other_field: Option<u32>,
baz: Baz,
}
#[derive(Reflect)]
struct Baz {
value: Vec<RangeInclusive<f32>>,
}
let value = Foo {
bar: Bar {
some_other_field: Some(123),
baz: Baz {
value: vec![0.0..=1.0],
},
},
};
let registry = TypeRegistry::new();
let serializer = ReflectSerializer::new(&value, &registry);
let error = ron::ser::to_string(&serializer).unwrap_err();
assert_eq!(
error,
ron::Error::Message(
"type `core::ops::RangeInclusive<f32>` is not registered in the type registry (stack: `bevy_reflect::serde::ser::tests::debug_stack::Foo` -> `bevy_reflect::serde::ser::tests::debug_stack::Bar` -> `bevy_reflect::serde::ser::tests::debug_stack::Baz` -> `alloc::vec::Vec<core::ops::RangeInclusive<f32>>` -> `core::ops::RangeInclusive<f32>`)".to_string()
)
);
}
}
} }

View File

@ -1,3 +1,4 @@
use crate::serde::ser::error_utils::make_custom_error;
use crate::{PartialReflect, ReflectSerialize, TypeRegistry}; use crate::{PartialReflect, ReflectSerialize, TypeRegistry};
use serde::ser::Error; use serde::ser::Error;
use std::ops::Deref; use std::ops::Deref;
@ -23,29 +24,29 @@ impl<'a> Serializable<'a> {
type_registry: &TypeRegistry, type_registry: &TypeRegistry,
) -> Result<Serializable<'a>, E> { ) -> Result<Serializable<'a>, E> {
let value = value.try_as_reflect().ok_or_else(|| { let value = value.try_as_reflect().ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"Type '{}' does not implement `Reflect`", "type `{}` does not implement `Reflect`",
value.reflect_type_path() value.reflect_type_path()
)) ))
})?; })?;
let info = value.get_represented_type_info().ok_or_else(|| { let info = value.get_represented_type_info().ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"Type '{}' does not represent any type", "type `{}` does not represent any type",
value.reflect_type_path(), value.reflect_type_path(),
)) ))
})?; })?;
let registration = type_registry.get(info.type_id()).ok_or_else(|| { let registration = type_registry.get(info.type_id()).ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"Type `{}` is not registered in the type registry", "type `{}` is not registered in the type registry",
info.type_path(), info.type_path(),
)) ))
})?; })?;
let reflect_serialize = registration.data::<ReflectSerialize>().ok_or_else(|| { let reflect_serialize = registration.data::<ReflectSerialize>().ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"Type `{}` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`", "type `{}` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`",
info.type_path(), info.type_path(),
)) ))
})?; })?;

View File

@ -1,5 +1,8 @@
use crate::serde::ser::arrays::ArraySerializer; use crate::serde::ser::arrays::ArraySerializer;
use crate::serde::ser::enums::EnumSerializer; use crate::serde::ser::enums::EnumSerializer;
use crate::serde::ser::error_utils::make_custom_error;
#[cfg(feature = "debug_stack")]
use crate::serde::ser::error_utils::TYPE_INFO_STACK;
use crate::serde::ser::lists::ListSerializer; use crate::serde::ser::lists::ListSerializer;
use crate::serde::ser::maps::MapSerializer; use crate::serde::ser::maps::MapSerializer;
use crate::serde::ser::sets::SetSerializer; use crate::serde::ser::sets::SetSerializer;
@ -8,7 +11,7 @@ use crate::serde::ser::tuple_structs::TupleStructSerializer;
use crate::serde::ser::tuples::TupleSerializer; use crate::serde::ser::tuples::TupleSerializer;
use crate::serde::Serializable; use crate::serde::Serializable;
use crate::{PartialReflect, ReflectRef, TypeRegistry}; use crate::{PartialReflect, ReflectRef, TypeRegistry};
use serde::ser::{Error, SerializeMap}; use serde::ser::SerializeMap;
use serde::Serialize; use serde::Serialize;
/// A general purpose serializer for reflected types. /// A general purpose serializer for reflected types.
@ -54,7 +57,7 @@ pub struct ReflectSerializer<'a> {
impl<'a> ReflectSerializer<'a> { impl<'a> ReflectSerializer<'a> {
pub fn new(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self { pub fn new(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self {
ReflectSerializer { value, registry } Self { value, registry }
} }
} }
@ -69,13 +72,13 @@ impl<'a> Serialize for ReflectSerializer<'a> {
.get_represented_type_info() .get_represented_type_info()
.ok_or_else(|| { .ok_or_else(|| {
if self.value.is_dynamic() { if self.value.is_dynamic() {
Error::custom(format_args!( make_custom_error(format_args!(
"cannot serialize dynamic value without represented type: {}", "cannot serialize dynamic value without represented type: `{}`",
self.value.reflect_type_path() self.value.reflect_type_path()
)) ))
} else { } else {
Error::custom(format_args!( make_custom_error(format_args!(
"cannot get type info for {}", "cannot get type info for `{}`",
self.value.reflect_type_path() self.value.reflect_type_path()
)) ))
} }
@ -132,7 +135,15 @@ pub struct TypedReflectSerializer<'a> {
impl<'a> TypedReflectSerializer<'a> { impl<'a> TypedReflectSerializer<'a> {
pub fn new(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self { pub fn new(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self {
TypedReflectSerializer { value, registry } #[cfg(feature = "debug_stack")]
TYPE_INFO_STACK.set(crate::type_info_stack::TypeInfoStack::new());
Self { value, registry }
}
/// An internal constructor for creating a serializer without resetting the type info stack.
pub(super) fn new_internal(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self {
Self { value, registry }
} }
} }
@ -141,14 +152,29 @@ impl<'a> Serialize for TypedReflectSerializer<'a> {
where where
S: serde::Serializer, S: serde::Serializer,
{ {
#[cfg(feature = "debug_stack")]
{
let info = self.value.get_represented_type_info().ok_or_else(|| {
make_custom_error(format_args!(
"type `{}` does not represent any type",
self.value.reflect_type_path(),
))
})?;
TYPE_INFO_STACK.with_borrow_mut(|stack| stack.push(info));
}
// Handle both Value case and types that have a custom `Serialize` // Handle both Value case and types that have a custom `Serialize`
let serializable = let serializable =
Serializable::try_from_reflect_value::<S::Error>(self.value, self.registry); Serializable::try_from_reflect_value::<S::Error>(self.value, self.registry);
if let Ok(serializable) = serializable { if let Ok(serializable) = serializable {
#[cfg(feature = "debug_stack")]
TYPE_INFO_STACK.with_borrow_mut(crate::type_info_stack::TypeInfoStack::pop);
return serializable.serialize(serializer); return serializable.serialize(serializer);
} }
match self.value.reflect_ref() { let output = match self.value.reflect_ref() {
ReflectRef::Struct(value) => { ReflectRef::Struct(value) => {
StructSerializer::new(value, self.registry).serialize(serializer) StructSerializer::new(value, self.registry).serialize(serializer)
} }
@ -174,6 +200,11 @@ impl<'a> Serialize for TypedReflectSerializer<'a> {
EnumSerializer::new(value, self.registry).serialize(serializer) EnumSerializer::new(value, self.registry).serialize(serializer)
} }
ReflectRef::Value(_) => Err(serializable.err().unwrap()), ReflectRef::Value(_) => Err(serializable.err().unwrap()),
} };
#[cfg(feature = "debug_stack")]
TYPE_INFO_STACK.with_borrow_mut(crate::type_info_stack::TypeInfoStack::pop);
output
} }
} }

View File

@ -22,7 +22,7 @@ impl<'a> Serialize for SetSerializer<'a> {
{ {
let mut state = serializer.serialize_seq(Some(self.set.len()))?; let mut state = serializer.serialize_seq(Some(self.set.len()))?;
for value in self.set.iter() { for value in self.set.iter() {
state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; state.serialize_element(&TypedReflectSerializer::new_internal(value, self.registry))?;
} }
state.end() state.end()
} }

View File

@ -1,6 +1,7 @@
use crate::serde::ser::error_utils::make_custom_error;
use crate::serde::{SerializationData, TypedReflectSerializer}; use crate::serde::{SerializationData, TypedReflectSerializer};
use crate::{Struct, TypeInfo, TypeRegistry}; use crate::{Struct, TypeInfo, TypeRegistry};
use serde::ser::{Error, SerializeStruct}; use serde::ser::SerializeStruct;
use serde::Serialize; use serde::Serialize;
/// A serializer for [`Struct`] values. /// A serializer for [`Struct`] values.
@ -27,8 +28,8 @@ impl<'a> Serialize for StructSerializer<'a> {
.struct_value .struct_value
.get_represented_type_info() .get_represented_type_info()
.ok_or_else(|| { .ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"cannot get type info for {}", "cannot get type info for `{}`",
self.struct_value.reflect_type_path() self.struct_value.reflect_type_path()
)) ))
})?; })?;
@ -36,7 +37,7 @@ impl<'a> Serialize for StructSerializer<'a> {
let struct_info = match type_info { let struct_info = match type_info {
TypeInfo::Struct(struct_info) => struct_info, TypeInfo::Struct(struct_info) => struct_info,
info => { info => {
return Err(Error::custom(format_args!( return Err(make_custom_error(format_args!(
"expected struct type but received {info:?}" "expected struct type but received {info:?}"
))); )));
} }
@ -60,7 +61,10 @@ impl<'a> Serialize for StructSerializer<'a> {
continue; continue;
} }
let key = struct_info.field_at(index).unwrap().name(); let key = struct_info.field_at(index).unwrap().name();
state.serialize_field(key, &TypedReflectSerializer::new(value, self.registry))?; state.serialize_field(
key,
&TypedReflectSerializer::new_internal(value, self.registry),
)?;
} }
state.end() state.end()
} }

View File

@ -1,6 +1,7 @@
use crate::serde::ser::error_utils::make_custom_error;
use crate::serde::{SerializationData, TypedReflectSerializer}; use crate::serde::{SerializationData, TypedReflectSerializer};
use crate::{TupleStruct, TypeInfo, TypeRegistry}; use crate::{TupleStruct, TypeInfo, TypeRegistry};
use serde::ser::{Error, SerializeTupleStruct}; use serde::ser::SerializeTupleStruct;
use serde::Serialize; use serde::Serialize;
/// A serializer for [`TupleStruct`] values. /// A serializer for [`TupleStruct`] values.
@ -27,8 +28,8 @@ impl<'a> Serialize for TupleStructSerializer<'a> {
.tuple_struct .tuple_struct
.get_represented_type_info() .get_represented_type_info()
.ok_or_else(|| { .ok_or_else(|| {
Error::custom(format_args!( make_custom_error(format_args!(
"cannot get type info for {}", "cannot get type info for `{}`",
self.tuple_struct.reflect_type_path() self.tuple_struct.reflect_type_path()
)) ))
})?; })?;
@ -36,7 +37,7 @@ impl<'a> Serialize for TupleStructSerializer<'a> {
let tuple_struct_info = match type_info { let tuple_struct_info = match type_info {
TypeInfo::TupleStruct(tuple_struct_info) => tuple_struct_info, TypeInfo::TupleStruct(tuple_struct_info) => tuple_struct_info,
info => { info => {
return Err(Error::custom(format_args!( return Err(make_custom_error(format_args!(
"expected tuple struct type but received {info:?}" "expected tuple struct type but received {info:?}"
))); )));
} }
@ -59,7 +60,7 @@ impl<'a> Serialize for TupleStructSerializer<'a> {
{ {
continue; continue;
} }
state.serialize_field(&TypedReflectSerializer::new(value, self.registry))?; state.serialize_field(&TypedReflectSerializer::new_internal(value, self.registry))?;
} }
state.end() state.end()
} }

View File

@ -23,7 +23,7 @@ impl<'a> Serialize for TupleSerializer<'a> {
let mut state = serializer.serialize_tuple(self.tuple.field_len())?; let mut state = serializer.serialize_tuple(self.tuple.field_len())?;
for value in self.tuple.iter_fields() { for value in self.tuple.iter_fields() {
state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; state.serialize_element(&TypedReflectSerializer::new_internal(value, self.registry))?;
} }
state.end() state.end()
} }

View File

@ -0,0 +1,49 @@
use crate::TypeInfo;
use core::fmt::{Debug, Formatter};
use core::slice::Iter;
/// Helper struct for managing a stack of [`TypeInfo`] instances.
///
/// This is useful for tracking the type hierarchy when serializing and deserializing types.
#[derive(Default, Clone)]
pub(crate) struct TypeInfoStack {
stack: Vec<&'static TypeInfo>,
}
impl TypeInfoStack {
/// Create a new empty [`TypeInfoStack`].
pub const fn new() -> Self {
Self { stack: Vec::new() }
}
/// Push a new [`TypeInfo`] onto the stack.
pub fn push(&mut self, type_info: &'static TypeInfo) {
self.stack.push(type_info);
}
/// Pop the last [`TypeInfo`] off the stack.
pub fn pop(&mut self) {
self.stack.pop();
}
/// Get an iterator over the stack in the order they were pushed.
pub fn iter(&self) -> Iter<&'static TypeInfo> {
self.stack.iter()
}
}
impl Debug for TypeInfoStack {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let mut iter = self.iter();
if let Some(first) = iter.next() {
write!(f, "`{}`", first.type_path())?;
}
for info in iter {
write!(f, " -> `{}`", info.type_path())?;
}
Ok(())
}
}