From aa241672e19f2c259f0ec7ff811669e0d9f8cb4f Mon Sep 17 00:00:00 2001 From: Gino Valente <49806985+MrGVSV@users.noreply.github.com> Date: Sun, 14 Jul 2024 17:40:07 -0700 Subject: [PATCH] bevy_reflect: Nested `TypeInfo` getters (#13321) # Objective Right now, `TypeInfo` can be accessed directly from a type using either `Typed::type_info` or `Reflect::get_represented_type_info`. However, once that `TypeInfo` is accessed, any nested types must be accessed via the `TypeRegistry`. ```rust #[derive(Reflect)] struct Foo { bar: usize } let registry = TypeRegistry::default(); let TypeInfo::Struct(type_info) = Foo::type_info() else { panic!("expected struct info"); }; let field = type_info.field("bar").unwrap(); let field_info = registry.get_type_info(field.type_id()).unwrap(); assert!(field_info.is::());; ``` ## Solution Enable nested types within a `TypeInfo` to be retrieved directly. ```rust #[derive(Reflect)] struct Foo { bar: usize } let TypeInfo::Struct(type_info) = Foo::type_info() else { panic!("expected struct info"); }; let field = type_info.field("bar").unwrap(); let field_info = field.type_info().unwrap(); assert!(field_info.is::());; ``` The particular implementation was chosen for two reasons. Firstly, we can't just store `TypeInfo` inside another `TypeInfo` directly. This is because some types are recursive and would result in a deadlock when trying to create the `TypeInfo` (i.e. it has to create the `TypeInfo` before it can use it, but it also needs the `TypeInfo` before it can create it). Therefore, we must instead store the function so it can be retrieved lazily. I had considered also using a `OnceLock` or something to lazily cache the info, but I figured we can look into optimizations later. The API should remain the same with or without the `OnceLock`. Secondly, a new wrapper trait had to be introduced: `MaybeTyped`. Like `RegisterForReflection`, this trait is `#[doc(hidden)]` and only exists so that we can properly handle dynamic type fields without requiring them to implement `Typed`. We don't want dynamic types to implement `Typed` due to the fact that it would make the return type `Option<&'static TypeInfo>` for all types even though only the dynamic types ever need to return `None` (see #6971 for details). Users should never have to interact with this trait as it has a blanket impl for all `Typed` types. And `Typed` is automatically implemented when deriving `Reflect` (as it is required). The one downside is we do need to return `Option<&'static TypeInfo>` from all these new methods so that we can handle the dynamic cases. If we didn't have to, we'd be able to get rid of the `Option` entirely. But I think that's an okay tradeoff for this one part of the API, and keeps the other APIs intact. ## Testing This PR contains tests to verify everything works as expected. You can test locally by running: ``` cargo test --package bevy_reflect ``` --- ## Changelog ### Public Changes - Added `ArrayInfo::item_info` method - Added `NamedField::type_info` method - Added `UnnamedField::type_info` method - Added `ListInfo::item_info` method - Added `MapInfo::key_info` method - Added `MapInfo::value_info` method - All active fields now have a `Typed` bound (remember that this is automatically satisfied for all types that derive `Reflect`) ### Internal Changes - Added `MaybeTyped` trait ## Migration Guide All active fields for reflected types (including lists, maps, tuples, etc.), must implement `Typed`. For the majority of users this won't have any visible impact. However, users implementing `Reflect` manually may need to update their types to implement `Typed` if they weren't already. Additionally, custom dynamic types will need to implement the new hidden `MaybeTyped` trait. --- .../tests/reflect_derive/generics_fail.rs | 1 + crates/bevy_reflect/derive/src/utility.rs | 3 + crates/bevy_reflect/src/array.rs | 18 ++- crates/bevy_reflect/src/enums/mod.rs | 18 +++ crates/bevy_reflect/src/fields.rs | 28 ++++- crates/bevy_reflect/src/impls/smallvec.rs | 16 +-- crates/bevy_reflect/src/impls/std.rs | 106 ++++++++++-------- crates/bevy_reflect/src/lib.rs | 41 ++++++- crates/bevy_reflect/src/list.rs | 16 ++- crates/bevy_reflect/src/map.rs | 31 ++++- crates/bevy_reflect/src/tuple.rs | 18 +-- crates/bevy_reflect/src/type_info.rs | 42 ++++++- crates/bevy_reflect/src/utility.rs | 4 +- 13 files changed, 262 insertions(+), 80 deletions(-) diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_derive/generics_fail.rs b/crates/bevy_reflect/compile_fail/tests/reflect_derive/generics_fail.rs index ba463bf4dd..b555b53318 100644 --- a/crates/bevy_reflect/compile_fail/tests/reflect_derive/generics_fail.rs +++ b/crates/bevy_reflect/compile_fail/tests/reflect_derive/generics_fail.rs @@ -13,6 +13,7 @@ struct NoReflect(f32); fn main() { let mut foo: Box = Box::new(Foo:: { a: NoReflect(42.0) }); //~^ ERROR: `NoReflect` does not provide type registration information + //~| ERROR: `NoReflect` can not provide type information through reflection // foo doesn't implement Reflect because NoReflect doesn't implement Reflect foo.get_field::("a").unwrap(); diff --git a/crates/bevy_reflect/derive/src/utility.rs b/crates/bevy_reflect/derive/src/utility.rs index a12a0a08d5..843b278672 100644 --- a/crates/bevy_reflect/derive/src/utility.rs +++ b/crates/bevy_reflect/derive/src/utility.rs @@ -227,6 +227,9 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> { quote!( #ty : #reflect_bound + #bevy_reflect_path::TypePath + // Needed for `Typed` impls + + #bevy_reflect_path::MaybeTyped + // Needed for `GetTypeRegistration` impls + #bevy_reflect_path::__macro_exports::RegisterForReflection ) })) diff --git a/crates/bevy_reflect/src/array.rs b/crates/bevy_reflect/src/array.rs index 0d957ccdf4..b0b6a0ade5 100644 --- a/crates/bevy_reflect/src/array.rs +++ b/crates/bevy_reflect/src/array.rs @@ -1,6 +1,6 @@ use crate::{ - self as bevy_reflect, utility::reflect_hasher, ApplyError, Reflect, ReflectKind, ReflectMut, - ReflectOwned, ReflectRef, TypeInfo, TypePath, TypePathTable, + self as bevy_reflect, utility::reflect_hasher, ApplyError, MaybeTyped, Reflect, ReflectKind, + ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, TypePathTable, }; use bevy_reflect_derive::impl_type_path; use std::{ @@ -79,6 +79,7 @@ pub trait Array: Reflect { pub struct ArrayInfo { type_path: TypePathTable, type_id: TypeId, + item_info: fn() -> Option<&'static TypeInfo>, item_type_path: TypePathTable, item_type_id: TypeId, capacity: usize, @@ -93,10 +94,13 @@ impl ArrayInfo { /// /// * `capacity`: The maximum capacity of the underlying array. /// - pub fn new(capacity: usize) -> Self { + pub fn new( + capacity: usize, + ) -> Self { Self { type_path: TypePathTable::of::(), type_id: TypeId::of::(), + item_info: TItem::maybe_type_info, item_type_path: TypePathTable::of::(), item_type_id: TypeId::of::(), capacity, @@ -143,6 +147,14 @@ impl ArrayInfo { TypeId::of::() == self.type_id } + /// The [`TypeInfo`] of the array item. + /// + /// Returns `None` if the array item does not contain static type information, + /// such as for dynamic types. + pub fn item_info(&self) -> Option<&'static TypeInfo> { + (self.item_info)() + } + /// A representation of the type path of the array item. /// /// Provides dynamic access to all methods on [`TypePath`]. diff --git a/crates/bevy_reflect/src/enums/mod.rs b/crates/bevy_reflect/src/enums/mod.rs index 2cb77af571..9f15bd6319 100644 --- a/crates/bevy_reflect/src/enums/mod.rs +++ b/crates/bevy_reflect/src/enums/mod.rs @@ -50,6 +50,18 @@ mod tests { if let VariantInfo::Tuple(variant) = info.variant("B").unwrap() { assert!(variant.field_at(0).unwrap().is::()); assert!(variant.field_at(1).unwrap().is::()); + assert!(variant + .field_at(0) + .unwrap() + .type_info() + .unwrap() + .is::()); + assert!(variant + .field_at(1) + .unwrap() + .type_info() + .unwrap() + .is::()); } else { panic!("Expected `VariantInfo::Tuple`"); } @@ -60,6 +72,12 @@ mod tests { if let VariantInfo::Struct(variant) = info.variant("C").unwrap() { assert!(variant.field_at(0).unwrap().is::()); assert!(variant.field("foo").unwrap().is::()); + assert!(variant + .field("foo") + .unwrap() + .type_info() + .unwrap() + .is::()); } else { panic!("Expected `VariantInfo::Struct`"); } diff --git a/crates/bevy_reflect/src/fields.rs b/crates/bevy_reflect/src/fields.rs index 31855aeb78..4a58cd24ad 100644 --- a/crates/bevy_reflect/src/fields.rs +++ b/crates/bevy_reflect/src/fields.rs @@ -1,5 +1,5 @@ use crate::attributes::{impl_custom_attribute_methods, CustomAttributes}; -use crate::{Reflect, TypePath, TypePathTable}; +use crate::{MaybeTyped, Reflect, TypeInfo, TypePath, TypePathTable}; use std::any::{Any, TypeId}; use std::sync::Arc; @@ -7,6 +7,7 @@ use std::sync::Arc; #[derive(Clone, Debug)] pub struct NamedField { name: &'static str, + type_info: fn() -> Option<&'static TypeInfo>, type_path: TypePathTable, type_id: TypeId, custom_attributes: Arc, @@ -16,9 +17,10 @@ pub struct NamedField { impl NamedField { /// Create a new [`NamedField`]. - pub fn new(name: &'static str) -> Self { + pub fn new(name: &'static str) -> Self { Self { name, + type_info: T::maybe_type_info, type_path: TypePathTable::of::(), type_id: TypeId::of::(), custom_attributes: Arc::new(CustomAttributes::default()), @@ -46,6 +48,15 @@ impl NamedField { self.name } + /// The [`TypeInfo`] of the field. + /// + /// + /// Returns `None` if the field does not contain static type information, + /// such as for dynamic types. + pub fn type_info(&self) -> Option<&'static TypeInfo> { + (self.type_info)() + } + /// A representation of the type path of the field. /// /// Provides dynamic access to all methods on [`TypePath`]. @@ -86,6 +97,7 @@ impl NamedField { #[derive(Clone, Debug)] pub struct UnnamedField { index: usize, + type_info: fn() -> Option<&'static TypeInfo>, type_path: TypePathTable, type_id: TypeId, custom_attributes: Arc, @@ -94,9 +106,10 @@ pub struct UnnamedField { } impl UnnamedField { - pub fn new(index: usize) -> Self { + pub fn new(index: usize) -> Self { Self { index, + type_info: T::maybe_type_info, type_path: TypePathTable::of::(), type_id: TypeId::of::(), custom_attributes: Arc::new(CustomAttributes::default()), @@ -124,6 +137,15 @@ impl UnnamedField { self.index } + /// The [`TypeInfo`] of the field. + /// + /// + /// Returns `None` if the field does not contain static type information, + /// such as for dynamic types. + pub fn type_info(&self) -> Option<&'static TypeInfo> { + (self.type_info)() + } + /// A representation of the type path of the field. /// /// Provides dynamic access to all methods on [`TypePath`]. diff --git a/crates/bevy_reflect/src/impls/smallvec.rs b/crates/bevy_reflect/src/impls/smallvec.rs index a0f1f38b09..ece76288fa 100644 --- a/crates/bevy_reflect/src/impls/smallvec.rs +++ b/crates/bevy_reflect/src/impls/smallvec.rs @@ -6,13 +6,13 @@ use std::any::Any; use crate::utility::GenericTypeInfoCell; use crate::{ self as bevy_reflect, ApplyError, FromReflect, FromType, GetTypeRegistration, List, ListInfo, - ListIter, Reflect, ReflectFromPtr, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, - TypePath, TypeRegistration, Typed, + ListIter, MaybeTyped, Reflect, ReflectFromPtr, ReflectKind, ReflectMut, ReflectOwned, + ReflectRef, TypeInfo, TypePath, TypeRegistration, Typed, }; impl List for SmallVec where - T::Item: FromReflect + TypePath, + T::Item: FromReflect + MaybeTyped + TypePath, { fn get(&self, index: usize) -> Option<&dyn Reflect> { if index < SmallVec::len(self) { @@ -79,7 +79,7 @@ where impl Reflect for SmallVec where - T::Item: FromReflect + TypePath, + T::Item: FromReflect + MaybeTyped + TypePath, { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) @@ -149,7 +149,7 @@ where impl Typed for SmallVec where - T::Item: FromReflect + TypePath, + T::Item: FromReflect + MaybeTyped + TypePath, { fn type_info() -> &'static TypeInfo { static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); @@ -161,7 +161,7 @@ impl_type_path!(::smallvec::SmallVec); impl FromReflect for SmallVec where - T::Item: FromReflect + TypePath, + T::Item: FromReflect + MaybeTyped + TypePath, { fn from_reflect(reflect: &dyn Reflect) -> Option { if let ReflectRef::List(ref_list) = reflect.reflect_ref() { @@ -178,7 +178,7 @@ where impl GetTypeRegistration for SmallVec where - T::Item: FromReflect + TypePath, + T::Item: FromReflect + MaybeTyped + TypePath, { fn get_type_registration() -> TypeRegistration { let mut registration = TypeRegistration::of::>(); @@ -188,4 +188,4 @@ where } #[cfg(feature = "functions")] -crate::func::macros::impl_function_traits!(SmallVec; where T::Item: FromReflect + TypePath); +crate::func::macros::impl_function_traits!(SmallVec; where T::Item: FromReflect + MaybeTyped + TypePath); diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index cef07d75a4..aff3f0d130 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -5,7 +5,7 @@ use crate::utility::{ use crate::{ self as bevy_reflect, impl_type_path, map_apply, map_partial_eq, map_try_apply, ApplyError, Array, ArrayInfo, ArrayIter, DynamicMap, DynamicTypePath, FromReflect, FromType, - GetTypeRegistration, List, ListInfo, ListIter, Map, MapInfo, MapIter, Reflect, + GetTypeRegistration, List, ListInfo, ListIter, Map, MapInfo, MapIter, MaybeTyped, Reflect, ReflectDeserialize, ReflectFromPtr, ReflectFromReflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, ReflectSerialize, TypeInfo, TypePath, TypeRegistration, TypeRegistry, Typed, ValueInfo, @@ -222,7 +222,7 @@ impl_type_path!(::bevy_utils::FixedState); macro_rules! impl_reflect_for_veclike { ($ty:path, $insert:expr, $remove:expr, $push:expr, $pop:expr, $sub:ty) => { - impl List for $ty { + impl List for $ty { #[inline] fn get(&self, index: usize) -> Option<&dyn Reflect> { <$sub>::get(self, index).map(|value| value as &dyn Reflect) @@ -281,7 +281,7 @@ macro_rules! impl_reflect_for_veclike { } } - impl Reflect for $ty { + impl Reflect for $ty { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) } @@ -352,7 +352,7 @@ macro_rules! impl_reflect_for_veclike { } } - impl Typed for $ty { + impl Typed for $ty { fn type_info() -> &'static TypeInfo { static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); CELL.get_or_insert::(|| TypeInfo::List(ListInfo::new::())) @@ -361,7 +361,9 @@ macro_rules! impl_reflect_for_veclike { impl_type_path!($ty); - impl GetTypeRegistration for $ty { + impl GetTypeRegistration + for $ty + { fn get_type_registration() -> TypeRegistration { let mut registration = TypeRegistration::of::<$ty>(); registration.insert::(FromType::<$ty>::from_type()); @@ -373,7 +375,7 @@ macro_rules! impl_reflect_for_veclike { } } - impl FromReflect for $ty { + impl FromReflect for $ty { fn from_reflect(reflect: &dyn Reflect) -> Option { if let ReflectRef::List(ref_list) = reflect.reflect_ref() { let mut new_list = Self::with_capacity(ref_list.len()); @@ -398,7 +400,7 @@ impl_reflect_for_veclike!( [T] ); #[cfg(feature = "functions")] -crate::func::macros::impl_function_traits!(Vec; ); +crate::func::macros::impl_function_traits!(Vec; ); impl_reflect_for_veclike!( ::alloc::collections::VecDeque, @@ -409,14 +411,14 @@ impl_reflect_for_veclike!( VecDeque:: ); #[cfg(feature = "functions")] -crate::func::macros::impl_function_traits!(VecDeque; ); +crate::func::macros::impl_function_traits!(VecDeque; ); macro_rules! impl_reflect_for_hashmap { ($ty:path) => { impl Map for $ty where - K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + TypePath + GetTypeRegistration, + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, S: TypePath + BuildHasher + Send + Sync, { fn get(&self, key: &dyn Reflect) -> Option<&dyn Reflect> { @@ -512,8 +514,8 @@ macro_rules! impl_reflect_for_hashmap { impl Reflect for $ty where - K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + TypePath + GetTypeRegistration, + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, S: TypePath + BuildHasher + Send + Sync, { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { @@ -585,8 +587,8 @@ macro_rules! impl_reflect_for_hashmap { impl Typed for $ty where - K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + TypePath + GetTypeRegistration, + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, S: TypePath + BuildHasher + Send + Sync, { fn type_info() -> &'static TypeInfo { @@ -597,8 +599,8 @@ macro_rules! impl_reflect_for_hashmap { impl GetTypeRegistration for $ty where - K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + TypePath + GetTypeRegistration, + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, S: TypePath + BuildHasher + Send + Sync, { fn get_type_registration() -> TypeRegistration { @@ -615,8 +617,8 @@ macro_rules! impl_reflect_for_hashmap { impl FromReflect for $ty where - K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + TypePath + GetTypeRegistration, + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, S: TypePath + BuildHasher + Default + Send + Sync, { fn from_reflect(reflect: &dyn Reflect) -> Option { @@ -642,8 +644,8 @@ impl_type_path!(::std::collections::HashMap); #[cfg(feature = "functions")] crate::func::macros::impl_function_traits!(::std::collections::HashMap; < - K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + TypePath + GetTypeRegistration, + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, S: TypePath + BuildHasher + Default + Send + Sync > ); @@ -654,16 +656,16 @@ impl_type_path!(::bevy_utils::hashbrown::HashMap); #[cfg(feature = "functions")] crate::func::macros::impl_function_traits!(::bevy_utils::hashbrown::HashMap; < - K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + TypePath + GetTypeRegistration, + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, S: TypePath + BuildHasher + Default + Send + Sync > ); impl Map for ::std::collections::BTreeMap where - K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord, - V: FromReflect + TypePath + GetTypeRegistration, + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, { fn get(&self, key: &dyn Reflect) -> Option<&dyn Reflect> { key.downcast_ref::() @@ -758,8 +760,8 @@ where impl Reflect for ::std::collections::BTreeMap where - K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord, - V: FromReflect + TypePath + GetTypeRegistration, + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) @@ -830,8 +832,8 @@ where impl Typed for ::std::collections::BTreeMap where - K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord, - V: FromReflect + TypePath + GetTypeRegistration, + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, { fn type_info() -> &'static TypeInfo { static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); @@ -841,8 +843,8 @@ where impl GetTypeRegistration for ::std::collections::BTreeMap where - K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord, - V: FromReflect + TypePath + GetTypeRegistration, + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, { fn get_type_registration() -> TypeRegistration { let mut registration = TypeRegistration::of::(); @@ -853,8 +855,8 @@ where impl FromReflect for ::std::collections::BTreeMap where - K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord, - V: FromReflect + TypePath + GetTypeRegistration, + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, { fn from_reflect(reflect: &dyn Reflect) -> Option { if let ReflectRef::Map(ref_map) = reflect.reflect_ref() { @@ -875,12 +877,12 @@ impl_type_path!(::std::collections::BTreeMap); #[cfg(feature = "functions")] crate::func::macros::impl_function_traits!(::std::collections::BTreeMap; < - K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord, - V: FromReflect + TypePath + GetTypeRegistration + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration > ); -impl Array for [T; N] { +impl Array for [T; N] { #[inline] fn get(&self, index: usize) -> Option<&dyn Reflect> { <[T]>::get(self, index).map(|value| value as &dyn Reflect) @@ -909,7 +911,7 @@ impl Array for [T; } } -impl Reflect for [T; N] { +impl Reflect for [T; N] { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) } @@ -996,7 +998,9 @@ impl Reflect for [T } } -impl FromReflect for [T; N] { +impl FromReflect + for [T; N] +{ fn from_reflect(reflect: &dyn Reflect) -> Option { if let ReflectRef::Array(ref_array) = reflect.reflect_ref() { let mut temp_vec = Vec::with_capacity(ref_array.len()); @@ -1010,7 +1014,7 @@ impl FromReflec } } -impl Typed for [T; N] { +impl Typed for [T; N] { fn type_info() -> &'static TypeInfo { static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); CELL.get_or_insert::(|| TypeInfo::Array(ArrayInfo::new::(N))) @@ -1029,7 +1033,9 @@ impl TypePath for [T; N] { } } -impl GetTypeRegistration for [T; N] { +impl GetTypeRegistration + for [T; N] +{ fn get_type_registration() -> TypeRegistration { TypeRegistration::of::<[T; N]>() } @@ -1040,7 +1046,7 @@ impl GetTypeRegistr } #[cfg(feature = "functions")] -crate::func::macros::impl_function_traits!([T; N]; [const N: usize]); +crate::func::macros::impl_function_traits!([T; N]; [const N: usize]); impl_reflect! { #[type_path = "core::option"] @@ -1217,7 +1223,9 @@ where } } -impl List for Cow<'static, [T]> { +impl List + for Cow<'static, [T]> +{ fn get(&self, index: usize) -> Option<&dyn Reflect> { self.as_ref().get(index).map(|x| x as &dyn Reflect) } @@ -1276,7 +1284,9 @@ impl List for Cow<'stat } } -impl Reflect for Cow<'static, [T]> { +impl Reflect + for Cow<'static, [T]> +{ fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) } @@ -1347,14 +1357,16 @@ impl Reflect for Cow<'s } } -impl Typed for Cow<'static, [T]> { +impl Typed + for Cow<'static, [T]> +{ fn type_info() -> &'static TypeInfo { static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); CELL.get_or_insert::(|| TypeInfo::List(ListInfo::new::())) } } -impl GetTypeRegistration +impl GetTypeRegistration for Cow<'static, [T]> { fn get_type_registration() -> TypeRegistration { @@ -1366,7 +1378,9 @@ impl GetTypeRegistratio } } -impl FromReflect for Cow<'static, [T]> { +impl FromReflect + for Cow<'static, [T]> +{ fn from_reflect(reflect: &dyn Reflect) -> Option { if let ReflectRef::List(ref_list) = reflect.reflect_ref() { let mut temp_vec = Vec::with_capacity(ref_list.len()); @@ -1381,7 +1395,7 @@ impl FromReflect for Co } #[cfg(feature = "functions")] -crate::func::macros::impl_function_traits!(Cow<'static, [T]>; ); +crate::func::macros::impl_function_traits!(Cow<'static, [T]>; ); impl Reflect for &'static str { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 1f8dbad27c..fc7c14259b 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -1555,6 +1555,7 @@ mod tests { assert_eq!(MyStruct::type_path(), info.type_path()); assert_eq!(i32::type_path(), info.field("foo").unwrap().type_path()); assert_eq!(TypeId::of::(), info.field("foo").unwrap().type_id()); + assert!(info.field("foo").unwrap().type_info().unwrap().is::()); assert!(info.field("foo").unwrap().is::()); assert_eq!("foo", info.field("foo").unwrap().name()); assert_eq!(usize::type_path(), info.field_at(1).unwrap().type_path()); @@ -1571,11 +1572,11 @@ mod tests { } let info = >::type_info().as_struct().unwrap(); - assert!(info.is::>()); assert_eq!(MyGenericStruct::::type_path(), info.type_path()); assert_eq!(i32::type_path(), info.field("foo").unwrap().type_path()); assert_eq!("foo", info.field("foo").unwrap().name()); + assert!(info.field("foo").unwrap().type_info().unwrap().is::()); assert_eq!(usize::type_path(), info.field_at(1).unwrap().type_path()); let value: &dyn Reflect = &MyGenericStruct { @@ -1585,6 +1586,36 @@ mod tests { let info = value.get_represented_type_info().unwrap(); assert!(info.is::>()); + // Struct (dynamic field) + #[derive(Reflect)] + #[reflect(from_reflect = false)] + struct MyDynamicStruct { + foo: DynamicStruct, + bar: usize, + } + + let info = MyDynamicStruct::type_info(); + if let TypeInfo::Struct(info) = info { + assert!(info.is::()); + assert_eq!(MyDynamicStruct::type_path(), info.type_path()); + assert_eq!( + DynamicStruct::type_path(), + info.field("foo").unwrap().type_path() + ); + assert_eq!("foo", info.field("foo").unwrap().name()); + assert!(info.field("foo").unwrap().type_info().is_none()); + assert_eq!(usize::type_path(), info.field_at(1).unwrap().type_path()); + } else { + panic!("Expected `TypeInfo::Struct`"); + } + + let value: &dyn Reflect = &MyDynamicStruct { + foo: DynamicStruct::default(), + bar: 321, + }; + let info = value.get_represented_type_info().unwrap(); + assert!(info.is::()); + // Tuple Struct #[derive(Reflect)] struct MyTupleStruct(usize, i32, MyStruct); @@ -1594,6 +1625,7 @@ mod tests { assert!(info.is::()); assert_eq!(MyTupleStruct::type_path(), info.type_path()); assert_eq!(i32::type_path(), info.field_at(1).unwrap().type_path()); + assert!(info.field_at(1).unwrap().type_info().unwrap().is::()); assert!(info.field_at(1).unwrap().is::()); // Tuple @@ -1604,6 +1636,7 @@ mod tests { assert!(info.is::()); assert_eq!(MyTuple::type_path(), info.type_path()); assert_eq!(f32::type_path(), info.field_at(1).unwrap().type_path()); + 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(); @@ -1616,6 +1649,7 @@ mod tests { assert!(info.is::()); assert!(info.item_is::()); + assert!(info.item_info().unwrap().is::()); assert_eq!(MyList::type_path(), info.type_path()); assert_eq!(usize::type_path(), info.item_type_path_table().path()); @@ -1631,6 +1665,7 @@ mod tests { let info = MySmallVec::type_info().as_list().unwrap(); assert!(info.is::()); assert!(info.item_is::()); + assert!(info.item_info().unwrap().is::()); assert_eq!(MySmallVec::type_path(), info.type_path()); assert_eq!(String::type_path(), info.item_type_path_table().path()); @@ -1646,6 +1681,7 @@ mod tests { let info = MyArray::type_info().as_array().unwrap(); assert!(info.is::()); assert!(info.item_is::()); + assert!(info.item_info().unwrap().is::()); assert_eq!(MyArray::type_path(), info.type_path()); assert_eq!(usize::type_path(), info.item_type_path_table().path()); assert_eq!(3, info.capacity()); @@ -1673,6 +1709,7 @@ mod tests { assert!(info.is::()); assert!(info.item_is::()); + assert!(info.item_info().unwrap().is::()); assert_eq!(std::any::type_name::(), info.type_path()); assert_eq!( std::any::type_name::(), @@ -1691,6 +1728,8 @@ mod tests { assert!(info.is::()); assert!(info.key_is::()); assert!(info.value_is::()); + assert!(info.key_info().unwrap().is::()); + assert!(info.value_info().unwrap().is::()); assert_eq!(MyMap::type_path(), info.type_path()); assert_eq!(usize::type_path(), info.key_type_path_table().path()); assert_eq!(f32::type_path(), info.value_type_path_table().path()); diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs index 68921d4817..60f07eaf6e 100644 --- a/crates/bevy_reflect/src/list.rs +++ b/crates/bevy_reflect/src/list.rs @@ -6,8 +6,8 @@ use bevy_reflect_derive::impl_type_path; use crate::utility::reflect_hasher; use crate::{ - self as bevy_reflect, ApplyError, FromReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, - ReflectRef, TypeInfo, TypePath, TypePathTable, + self as bevy_reflect, ApplyError, FromReflect, MaybeTyped, Reflect, ReflectKind, ReflectMut, + ReflectOwned, ReflectRef, TypeInfo, TypePath, TypePathTable, }; /// A trait used to power [list-like] operations via [reflection]. @@ -110,6 +110,7 @@ pub trait List: Reflect { pub struct ListInfo { type_path: TypePathTable, type_id: TypeId, + item_info: fn() -> Option<&'static TypeInfo>, item_type_path: TypePathTable, item_type_id: TypeId, #[cfg(feature = "documentation")] @@ -118,10 +119,11 @@ pub struct ListInfo { impl ListInfo { /// Create a new [`ListInfo`]. - pub fn new() -> Self { + pub fn new() -> Self { Self { type_path: TypePathTable::of::(), type_id: TypeId::of::(), + item_info: TItem::maybe_type_info, item_type_path: TypePathTable::of::(), item_type_id: TypeId::of::(), #[cfg(feature = "documentation")] @@ -162,6 +164,14 @@ impl ListInfo { TypeId::of::() == self.type_id } + /// The [`TypeInfo`] of the list item. + /// + /// Returns `None` if the list item does not contain static type information, + /// such as for dynamic types. + pub fn item_info(&self) -> Option<&'static TypeInfo> { + (self.item_info)() + } + /// A representation of the type path of the list item. /// /// Provides dynamic access to all methods on [`TypePath`]. diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index 6c94b284e4..a034d864f8 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -5,8 +5,8 @@ use bevy_reflect_derive::impl_type_path; use bevy_utils::{Entry, HashMap}; use crate::{ - self as bevy_reflect, ApplyError, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, - TypeInfo, TypePath, TypePathTable, + self as bevy_reflect, ApplyError, MaybeTyped, Reflect, ReflectKind, ReflectMut, ReflectOwned, + ReflectRef, TypeInfo, TypePath, TypePathTable, }; /// A trait used to power [map-like] operations via [reflection]. @@ -96,8 +96,10 @@ pub trait Map: Reflect { pub struct MapInfo { type_path: TypePathTable, type_id: TypeId, + key_info: fn() -> Option<&'static TypeInfo>, key_type_path: TypePathTable, key_type_id: TypeId, + value_info: fn() -> Option<&'static TypeInfo>, value_type_path: TypePathTable, value_type_id: TypeId, #[cfg(feature = "documentation")] @@ -106,13 +108,18 @@ pub struct MapInfo { impl MapInfo { /// Create a new [`MapInfo`]. - pub fn new() -> Self - { + pub fn new< + TMap: Map + TypePath, + TKey: Reflect + MaybeTyped + TypePath, + TValue: Reflect + MaybeTyped + TypePath, + >() -> Self { Self { type_path: TypePathTable::of::(), type_id: TypeId::of::(), + key_info: TKey::maybe_type_info, key_type_path: TypePathTable::of::(), key_type_id: TypeId::of::(), + value_info: TValue::maybe_type_info, value_type_path: TypePathTable::of::(), value_type_id: TypeId::of::(), #[cfg(feature = "documentation")] @@ -153,6 +160,14 @@ impl MapInfo { TypeId::of::() == self.type_id } + /// The [`TypeInfo`] of the key type. + /// + /// Returns `None` if the key type does not contain static type information, + /// such as for dynamic types. + pub fn key_info(&self) -> Option<&'static TypeInfo> { + (self.key_info)() + } + /// A representation of the type path of the key type. /// /// Provides dynamic access to all methods on [`TypePath`]. @@ -170,6 +185,14 @@ impl MapInfo { TypeId::of::() == self.key_type_id } + /// The [`TypeInfo`] of the value type. + /// + /// Returns `None` if the value type does not contain static type information, + /// such as for dynamic types. + pub fn value_info(&self) -> Option<&'static TypeInfo> { + (self.value_info)() + } + /// A representation of the type path of the value type. /// /// Provides dynamic access to all methods on [`TypePath`]. diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index c151921ef6..1af8e93057 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -3,8 +3,8 @@ use bevy_utils::all_tuples; use crate::{ self as bevy_reflect, utility::GenericTypePathCell, ApplyError, FromReflect, - GetTypeRegistration, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, - TypeRegistration, TypeRegistry, Typed, UnnamedField, + GetTypeRegistration, MaybeTyped, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, + TypePath, TypeRegistration, TypeRegistry, Typed, UnnamedField, }; use crate::{ReflectKind, TypePathTable}; use std::any::{Any, TypeId}; @@ -483,7 +483,7 @@ pub fn tuple_debug(dyn_tuple: &dyn Tuple, f: &mut Formatter<'_>) -> std::fmt::Re macro_rules! impl_reflect_tuple { {$($index:tt : $name:tt),*} => { - impl<$($name: Reflect + TypePath + GetTypeRegistration),*> Tuple for ($($name,)*) { + impl<$($name: Reflect + MaybeTyped + TypePath + GetTypeRegistration),*> Tuple for ($($name,)*) { #[inline] fn field(&self, index: usize) -> Option<&dyn Reflect> { match index { @@ -534,7 +534,7 @@ macro_rules! impl_reflect_tuple { } } - impl<$($name: Reflect + TypePath + GetTypeRegistration),*> Reflect for ($($name,)*) { + impl<$($name: Reflect + MaybeTyped + TypePath + GetTypeRegistration),*> Reflect for ($($name,)*) { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) } @@ -601,7 +601,7 @@ macro_rules! impl_reflect_tuple { } } - impl <$($name: Reflect + TypePath + GetTypeRegistration),*> Typed for ($($name,)*) { + impl <$($name: Reflect + MaybeTyped + TypePath + GetTypeRegistration),*> Typed for ($($name,)*) { fn type_info() -> &'static TypeInfo { static CELL: $crate::utility::GenericTypeInfoCell = $crate::utility::GenericTypeInfoCell::new(); CELL.get_or_insert::(|| { @@ -614,7 +614,7 @@ macro_rules! impl_reflect_tuple { } } - impl<$($name: Reflect + TypePath + GetTypeRegistration),*> GetTypeRegistration for ($($name,)*) { + impl<$($name: Reflect + MaybeTyped + TypePath + GetTypeRegistration),*> GetTypeRegistration for ($($name,)*) { fn get_type_registration() -> TypeRegistration { TypeRegistration::of::<($($name,)*)>() } @@ -624,7 +624,7 @@ macro_rules! impl_reflect_tuple { } } - impl<$($name: FromReflect + TypePath + GetTypeRegistration),*> FromReflect for ($($name,)*) + impl<$($name: FromReflect + MaybeTyped + TypePath + GetTypeRegistration),*> FromReflect for ($($name,)*) { fn from_reflect(reflect: &dyn Reflect) -> Option { if let ReflectRef::Tuple(_ref_tuple) = reflect.reflect_ref() { @@ -722,7 +722,7 @@ const _: () = { macro_rules! impl_from_arg_tuple { ($($name: ident),*) => { - $crate::func::args::impl_from_arg!(($($name,)*); <$($name: FromReflect + TypePath + GetTypeRegistration),*>); + $crate::func::args::impl_from_arg!(($($name,)*); <$($name: FromReflect + MaybeTyped + TypePath + GetTypeRegistration),*>); }; } @@ -730,7 +730,7 @@ const _: () = { macro_rules! impl_into_return_tuple { ($($name: ident),+) => { - $crate::func::impl_into_return!(($($name,)*); <$($name: FromReflect + TypePath + GetTypeRegistration),*>); + $crate::func::impl_into_return!(($($name,)*); <$($name: FromReflect + MaybeTyped + TypePath + GetTypeRegistration),*>); }; } diff --git a/crates/bevy_reflect/src/type_info.rs b/crates/bevy_reflect/src/type_info.rs index 47d69a63e3..1de6e754a1 100644 --- a/crates/bevy_reflect/src/type_info.rs +++ b/crates/bevy_reflect/src/type_info.rs @@ -1,5 +1,6 @@ use crate::{ - ArrayInfo, EnumInfo, ListInfo, MapInfo, Reflect, ReflectKind, StructInfo, TupleInfo, + ArrayInfo, DynamicArray, DynamicEnum, DynamicList, DynamicMap, DynamicStruct, DynamicTuple, + DynamicTupleStruct, EnumInfo, ListInfo, MapInfo, Reflect, ReflectKind, StructInfo, TupleInfo, TupleStructInfo, TypePath, TypePathTable, }; use std::any::{Any, TypeId}; @@ -82,6 +83,45 @@ pub trait Typed: Reflect + TypePath { fn type_info() -> &'static TypeInfo; } +/// A wrapper trait around [`Typed`]. +/// +/// This trait is used to provide a way to get compile-time type information for types that +/// do implement `Typed` while also allowing for types that do not implement `Typed` to be used. +/// It's used instead of `Typed` directly to avoid making dynamic types also +/// implement `Typed` in order to be used as active fields. +/// +/// This trait has a blanket implementation for all types that implement `Typed` +/// and manual implementations for all dynamic types (which simply return `None`). +#[doc(hidden)] +pub trait MaybeTyped: Reflect { + /// Returns the compile-time [info] for the underlying type, if it exists. + /// + /// [info]: TypeInfo + fn maybe_type_info() -> Option<&'static TypeInfo> { + None + } +} + +impl MaybeTyped for T { + fn maybe_type_info() -> Option<&'static TypeInfo> { + Some(T::type_info()) + } +} + +impl MaybeTyped for DynamicEnum {} + +impl MaybeTyped for DynamicTupleStruct {} + +impl MaybeTyped for DynamicStruct {} + +impl MaybeTyped for DynamicMap {} + +impl MaybeTyped for DynamicList {} + +impl MaybeTyped for DynamicArray {} + +impl MaybeTyped for DynamicTuple {} + /// 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 86dcbbc175..cb29c13f86 100644 --- a/crates/bevy_reflect/src/utility.rs +++ b/crates/bevy_reflect/src/utility.rs @@ -135,7 +135,7 @@ impl Default for NonGenericTypeCell { /// /// struct Foo(T); /// -/// impl Typed for Foo { +/// impl Typed for Foo { /// fn type_info() -> &'static TypeInfo { /// static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); /// CELL.get_or_insert::(|| { @@ -149,7 +149,7 @@ impl Default for NonGenericTypeCell { /// # fn type_path() -> &'static str { todo!() } /// # fn short_type_path() -> &'static str { todo!() } /// # } -/// # impl Reflect for Foo { +/// # impl Reflect for Foo { /// # fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { todo!() } /// # fn into_any(self: Box) -> Box { todo!() } /// # fn as_any(&self) -> &dyn Any { todo!() }