From 37443e0f3fa816ed4f4fc3132c014f0bd507d014 Mon Sep 17 00:00:00 2001 From: Gino Valente <49806985+MrGVSV@users.noreply.github.com> Date: Fri, 13 Sep 2024 10:17:10 -0700 Subject: [PATCH] bevy_reflect: Add `DynamicTyped` trait (#15108) # Objective Thanks to #7207, we now have a way to validate at the type-level that a reflected value is actually the type it says it is and not just a dynamic representation of that type. `dyn PartialReflect` values _might_ be a dynamic type, but `dyn Reflect` values are guaranteed to _not_ be a dynamic type. Therefore, we can start to add methods to `Reflect` that weren't really possible before. For example, we should now be able to always get a `&'static TypeInfo`, and not just an `Option<&'static TypeInfo>`. ## Solution Add the `DynamicTyped` trait. This trait is similar to `DynamicTypePath` in that it provides a way to use the non-object-safe `Typed` trait in an object-safe way. And since all types that derive `Reflect` will also derive `Typed`, we can safely add `DynamicTyped` as a supertrait of `Reflect`. This allows us to use it when just given a `dyn Reflect` trait object. ## Testing You can test locally by running: ``` cargo test --package bevy_reflect ``` --- ## Showcase `Reflect` now has a supertrait of `DynamicTyped`, allowing `TypeInfo` to be retrieved from a `dyn Reflect` trait object without having to unwrap anything! ```rust let value: Box = Box::new(String::from("Hello!")); // BEFORE let info: &'static TypeInfo = value.get_represented_type_info().unwrap(); // AFTER let info: &'static TypeInfo = value.reflect_type_info(); ``` ## Migration Guide `Reflect` now has a supertrait of `DynamicTyped`. If you were manually implementing `Reflect` and did not implement `Typed`, you will now need to do so. --- crates/bevy_reflect/src/lib.rs | 24 +++++++++---------- crates/bevy_reflect/src/reflect.rs | 6 ++--- .../src/serde/ser/serializable.rs | 7 +----- crates/bevy_reflect/src/type_info.rs | 21 ++++++++++++++++ crates/bevy_reflect/src/utility.rs | 2 +- examples/reflection/type_data.rs | 4 ++-- 6 files changed, 40 insertions(+), 24 deletions(-) diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index dc820caaaa03e..40f602ac35038 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -1632,7 +1632,7 @@ mod tests { // TypeInfo (instance) let value: &dyn Reflect = &123_i32; - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // Struct @@ -1653,7 +1653,7 @@ mod tests { assert_eq!(usize::type_path(), info.field_at(1).unwrap().type_path()); let value: &dyn Reflect = &MyStruct { foo: 123, bar: 321 }; - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // Struct (generic) @@ -1675,7 +1675,7 @@ mod tests { foo: String::from("Hello!"), bar: 321, }; - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::>()); // Struct (dynamic field) @@ -1705,7 +1705,7 @@ mod tests { foo: DynamicStruct::default(), bar: 321, }; - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // Tuple Struct @@ -1731,7 +1731,7 @@ mod tests { assert!(info.field_at(1).unwrap().type_info().unwrap().is::()); let value: &dyn Reflect = &(123_u32, 1.23_f32, String::from("Hello!")); - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // List @@ -1746,7 +1746,7 @@ mod tests { assert_eq!(usize::type_path(), info.item_ty().path()); let value: &dyn Reflect = &vec![123_usize]; - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // List (SmallVec) @@ -1763,7 +1763,7 @@ mod tests { let value: MySmallVec = smallvec::smallvec![String::default(); 2]; let value: &dyn Reflect = &value; - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); } @@ -1779,7 +1779,7 @@ mod tests { assert_eq!(3, info.capacity()); let value: &dyn Reflect = &[1usize, 2usize, 3usize]; - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // Cow<'static, str> @@ -1791,7 +1791,7 @@ mod tests { assert_eq!(std::any::type_name::(), info.type_path()); let value: &dyn Reflect = &Cow::<'static, str>::Owned("Hello!".to_string()); - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // Cow<'static, [u8]> @@ -1806,7 +1806,7 @@ mod tests { assert_eq!(std::any::type_name::(), info.item_ty().path()); let value: &dyn Reflect = &Cow::<'static, [u8]>::Owned(vec![0, 1, 2, 3]); - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // Map @@ -1824,7 +1824,7 @@ mod tests { assert_eq!(f32::type_path(), info.value_ty().path()); let value: &dyn Reflect = &MyMap::new(); - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // Value @@ -1836,7 +1836,7 @@ mod tests { assert_eq!(MyValue::type_path(), info.type_path()); let value: &dyn Reflect = &String::from("Hello!"); - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); } diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 68d5cb0aa345a..9efa868e6cfc0 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -1,7 +1,7 @@ use crate::{ array_debug, enum_debug, list_debug, map_debug, serde::Serializable, struct_debug, tuple_debug, - tuple_struct_debug, Array, DynamicTypePath, Enum, List, Map, Set, Struct, Tuple, TupleStruct, - TypeInfo, TypePath, Typed, ValueInfo, + tuple_struct_debug, Array, DynamicTypePath, DynamicTyped, Enum, List, Map, Set, Struct, Tuple, + TupleStruct, TypeInfo, TypePath, Typed, ValueInfo, }; use std::{ any::{Any, TypeId}, @@ -408,7 +408,7 @@ where message = "`{Self}` does not implement `Reflect` so cannot be fully reflected", note = "consider annotating `{Self}` with `#[derive(Reflect)]`" )] -pub trait Reflect: PartialReflect + Any { +pub trait Reflect: PartialReflect + DynamicTyped + Any { /// Returns the value as a [`Box`][std::any::Any]. /// /// For remote wrapper types, this will return the remote type instead. diff --git a/crates/bevy_reflect/src/serde/ser/serializable.rs b/crates/bevy_reflect/src/serde/ser/serializable.rs index 48d3b2968368d..9cf96ee77a26e 100644 --- a/crates/bevy_reflect/src/serde/ser/serializable.rs +++ b/crates/bevy_reflect/src/serde/ser/serializable.rs @@ -30,12 +30,7 @@ impl<'a> Serializable<'a> { )) })?; - let info = value.get_represented_type_info().ok_or_else(|| { - make_custom_error(format_args!( - "type `{}` does not represent any type", - value.reflect_type_path(), - )) - })?; + let info = value.reflect_type_info(); let registration = type_registry.get(info.type_id()).ok_or_else(|| { make_custom_error(format_args!( diff --git a/crates/bevy_reflect/src/type_info.rs b/crates/bevy_reflect/src/type_info.rs index ca9963f3803aa..1d98b14b0ea0d 100644 --- a/crates/bevy_reflect/src/type_info.rs +++ b/crates/bevy_reflect/src/type_info.rs @@ -136,6 +136,27 @@ impl MaybeTyped for DynamicArray {} impl MaybeTyped for DynamicTuple {} +/// Dynamic dispatch for [`Typed`]. +/// +/// Since this is a supertrait of [`Reflect`] its methods can be called on a `dyn Reflect`. +/// +/// [`Reflect`]: crate::Reflect +#[diagnostic::on_unimplemented( + message = "`{Self}` can not provide dynamic type information through reflection", + note = "consider annotating `{Self}` with `#[derive(Reflect)]`" +)] +pub trait DynamicTyped { + /// See [`Typed::type_info`]. + fn reflect_type_info(&self) -> &'static TypeInfo; +} + +impl DynamicTyped for T { + #[inline] + fn reflect_type_info(&self) -> &'static TypeInfo { + Self::type_info() + } +} + /// A [`TypeInfo`]-specific error. #[derive(Debug, Error)] pub enum TypeInfoError { diff --git a/crates/bevy_reflect/src/utility.rs b/crates/bevy_reflect/src/utility.rs index a9f33711ae39f..6eafc3e7c4287 100644 --- a/crates/bevy_reflect/src/utility.rs +++ b/crates/bevy_reflect/src/utility.rs @@ -171,7 +171,7 @@ impl Default for NonGenericTypeCell { /// # fn reflect_owned(self: Box) -> ReflectOwned { todo!() } /// # fn clone_value(&self) -> Box { todo!() } /// # } -/// # impl Reflect for Foo { +/// # impl Reflect for Foo { /// # fn into_any(self: Box) -> Box { todo!() } /// # fn as_any(&self) -> &dyn Any { todo!() } /// # fn as_any_mut(&mut self) -> &mut dyn Any { todo!() } diff --git a/examples/reflection/type_data.rs b/examples/reflection/type_data.rs index f1f9022b233b7..10c21fce861b5 100644 --- a/examples/reflection/type_data.rs +++ b/examples/reflection/type_data.rs @@ -80,7 +80,7 @@ fn main() { registry.register_type_data::(); // Then at any point we can retrieve the type data from the registry: - let type_id = value.get_represented_type_info().unwrap().type_id(); + let type_id = value.reflect_type_info().type_id(); let reflect_damageable = registry .get_type_data::(type_id) .unwrap(); @@ -133,7 +133,7 @@ fn main() { // Now we can use `ReflectHealth` to convert `dyn Reflect` into `dyn Health`: let value: Box = Box::new(Skeleton { health: 50 }); - let type_id = value.get_represented_type_info().unwrap().type_id(); + let type_id = value.reflect_type_info().type_id(); let reflect_health = registry.get_type_data::(type_id).unwrap(); // Type data generated by `#[reflect_trait]` comes with a `get`, `get_mut`, and `get_boxed` method,