Skip to content

Commit aa24167

Browse files
authored
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::<usize>());; ``` ## 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::<usize>());; ``` 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.
1 parent 8e67aef commit aa24167

File tree

13 files changed

+262
-80
lines changed

13 files changed

+262
-80
lines changed

crates/bevy_reflect/compile_fail/tests/reflect_derive/generics_fail.rs

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ struct NoReflect(f32);
1313
fn main() {
1414
let mut foo: Box<dyn Struct> = Box::new(Foo::<NoReflect> { a: NoReflect(42.0) });
1515
//~^ ERROR: `NoReflect` does not provide type registration information
16+
//~| ERROR: `NoReflect` can not provide type information through reflection
1617

1718
// foo doesn't implement Reflect because NoReflect doesn't implement Reflect
1819
foo.get_field::<NoReflect>("a").unwrap();

crates/bevy_reflect/derive/src/utility.rs

+3
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
227227
quote!(
228228
#ty : #reflect_bound
229229
+ #bevy_reflect_path::TypePath
230+
// Needed for `Typed` impls
231+
+ #bevy_reflect_path::MaybeTyped
232+
// Needed for `GetTypeRegistration` impls
230233
+ #bevy_reflect_path::__macro_exports::RegisterForReflection
231234
)
232235
}))

crates/bevy_reflect/src/array.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
2-
self as bevy_reflect, utility::reflect_hasher, ApplyError, Reflect, ReflectKind, ReflectMut,
3-
ReflectOwned, ReflectRef, TypeInfo, TypePath, TypePathTable,
2+
self as bevy_reflect, utility::reflect_hasher, ApplyError, MaybeTyped, Reflect, ReflectKind,
3+
ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, TypePathTable,
44
};
55
use bevy_reflect_derive::impl_type_path;
66
use std::{
@@ -79,6 +79,7 @@ pub trait Array: Reflect {
7979
pub struct ArrayInfo {
8080
type_path: TypePathTable,
8181
type_id: TypeId,
82+
item_info: fn() -> Option<&'static TypeInfo>,
8283
item_type_path: TypePathTable,
8384
item_type_id: TypeId,
8485
capacity: usize,
@@ -93,10 +94,13 @@ impl ArrayInfo {
9394
///
9495
/// * `capacity`: The maximum capacity of the underlying array.
9596
///
96-
pub fn new<TArray: Array + TypePath, TItem: Reflect + TypePath>(capacity: usize) -> Self {
97+
pub fn new<TArray: Array + TypePath, TItem: Reflect + MaybeTyped + TypePath>(
98+
capacity: usize,
99+
) -> Self {
97100
Self {
98101
type_path: TypePathTable::of::<TArray>(),
99102
type_id: TypeId::of::<TArray>(),
103+
item_info: TItem::maybe_type_info,
100104
item_type_path: TypePathTable::of::<TItem>(),
101105
item_type_id: TypeId::of::<TItem>(),
102106
capacity,
@@ -143,6 +147,14 @@ impl ArrayInfo {
143147
TypeId::of::<T>() == self.type_id
144148
}
145149

150+
/// The [`TypeInfo`] of the array item.
151+
///
152+
/// Returns `None` if the array item does not contain static type information,
153+
/// such as for dynamic types.
154+
pub fn item_info(&self) -> Option<&'static TypeInfo> {
155+
(self.item_info)()
156+
}
157+
146158
/// A representation of the type path of the array item.
147159
///
148160
/// Provides dynamic access to all methods on [`TypePath`].

crates/bevy_reflect/src/enums/mod.rs

+18
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ mod tests {
5050
if let VariantInfo::Tuple(variant) = info.variant("B").unwrap() {
5151
assert!(variant.field_at(0).unwrap().is::<usize>());
5252
assert!(variant.field_at(1).unwrap().is::<i32>());
53+
assert!(variant
54+
.field_at(0)
55+
.unwrap()
56+
.type_info()
57+
.unwrap()
58+
.is::<usize>());
59+
assert!(variant
60+
.field_at(1)
61+
.unwrap()
62+
.type_info()
63+
.unwrap()
64+
.is::<i32>());
5365
} else {
5466
panic!("Expected `VariantInfo::Tuple`");
5567
}
@@ -60,6 +72,12 @@ mod tests {
6072
if let VariantInfo::Struct(variant) = info.variant("C").unwrap() {
6173
assert!(variant.field_at(0).unwrap().is::<f32>());
6274
assert!(variant.field("foo").unwrap().is::<f32>());
75+
assert!(variant
76+
.field("foo")
77+
.unwrap()
78+
.type_info()
79+
.unwrap()
80+
.is::<f32>());
6381
} else {
6482
panic!("Expected `VariantInfo::Struct`");
6583
}

crates/bevy_reflect/src/fields.rs

+25-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use crate::attributes::{impl_custom_attribute_methods, CustomAttributes};
2-
use crate::{Reflect, TypePath, TypePathTable};
2+
use crate::{MaybeTyped, Reflect, TypeInfo, TypePath, TypePathTable};
33
use std::any::{Any, TypeId};
44
use std::sync::Arc;
55

66
/// The named field of a reflected struct.
77
#[derive(Clone, Debug)]
88
pub struct NamedField {
99
name: &'static str,
10+
type_info: fn() -> Option<&'static TypeInfo>,
1011
type_path: TypePathTable,
1112
type_id: TypeId,
1213
custom_attributes: Arc<CustomAttributes>,
@@ -16,9 +17,10 @@ pub struct NamedField {
1617

1718
impl NamedField {
1819
/// Create a new [`NamedField`].
19-
pub fn new<T: Reflect + TypePath>(name: &'static str) -> Self {
20+
pub fn new<T: Reflect + MaybeTyped + TypePath>(name: &'static str) -> Self {
2021
Self {
2122
name,
23+
type_info: T::maybe_type_info,
2224
type_path: TypePathTable::of::<T>(),
2325
type_id: TypeId::of::<T>(),
2426
custom_attributes: Arc::new(CustomAttributes::default()),
@@ -46,6 +48,15 @@ impl NamedField {
4648
self.name
4749
}
4850

51+
/// The [`TypeInfo`] of the field.
52+
///
53+
///
54+
/// Returns `None` if the field does not contain static type information,
55+
/// such as for dynamic types.
56+
pub fn type_info(&self) -> Option<&'static TypeInfo> {
57+
(self.type_info)()
58+
}
59+
4960
/// A representation of the type path of the field.
5061
///
5162
/// Provides dynamic access to all methods on [`TypePath`].
@@ -86,6 +97,7 @@ impl NamedField {
8697
#[derive(Clone, Debug)]
8798
pub struct UnnamedField {
8899
index: usize,
100+
type_info: fn() -> Option<&'static TypeInfo>,
89101
type_path: TypePathTable,
90102
type_id: TypeId,
91103
custom_attributes: Arc<CustomAttributes>,
@@ -94,9 +106,10 @@ pub struct UnnamedField {
94106
}
95107

96108
impl UnnamedField {
97-
pub fn new<T: Reflect + TypePath>(index: usize) -> Self {
109+
pub fn new<T: Reflect + MaybeTyped + TypePath>(index: usize) -> Self {
98110
Self {
99111
index,
112+
type_info: T::maybe_type_info,
100113
type_path: TypePathTable::of::<T>(),
101114
type_id: TypeId::of::<T>(),
102115
custom_attributes: Arc::new(CustomAttributes::default()),
@@ -124,6 +137,15 @@ impl UnnamedField {
124137
self.index
125138
}
126139

140+
/// The [`TypeInfo`] of the field.
141+
///
142+
///
143+
/// Returns `None` if the field does not contain static type information,
144+
/// such as for dynamic types.
145+
pub fn type_info(&self) -> Option<&'static TypeInfo> {
146+
(self.type_info)()
147+
}
148+
127149
/// A representation of the type path of the field.
128150
///
129151
/// Provides dynamic access to all methods on [`TypePath`].

crates/bevy_reflect/src/impls/smallvec.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ use std::any::Any;
66
use crate::utility::GenericTypeInfoCell;
77
use crate::{
88
self as bevy_reflect, ApplyError, FromReflect, FromType, GetTypeRegistration, List, ListInfo,
9-
ListIter, Reflect, ReflectFromPtr, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo,
10-
TypePath, TypeRegistration, Typed,
9+
ListIter, MaybeTyped, Reflect, ReflectFromPtr, ReflectKind, ReflectMut, ReflectOwned,
10+
ReflectRef, TypeInfo, TypePath, TypeRegistration, Typed,
1111
};
1212

1313
impl<T: SmallArray + TypePath + Send + Sync> List for SmallVec<T>
1414
where
15-
T::Item: FromReflect + TypePath,
15+
T::Item: FromReflect + MaybeTyped + TypePath,
1616
{
1717
fn get(&self, index: usize) -> Option<&dyn Reflect> {
1818
if index < SmallVec::len(self) {
@@ -79,7 +79,7 @@ where
7979

8080
impl<T: SmallArray + TypePath + Send + Sync> Reflect for SmallVec<T>
8181
where
82-
T::Item: FromReflect + TypePath,
82+
T::Item: FromReflect + MaybeTyped + TypePath,
8383
{
8484
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
8585
Some(<Self as Typed>::type_info())
@@ -149,7 +149,7 @@ where
149149

150150
impl<T: SmallArray + TypePath + Send + Sync + 'static> Typed for SmallVec<T>
151151
where
152-
T::Item: FromReflect + TypePath,
152+
T::Item: FromReflect + MaybeTyped + TypePath,
153153
{
154154
fn type_info() -> &'static TypeInfo {
155155
static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new();
@@ -161,7 +161,7 @@ impl_type_path!(::smallvec::SmallVec<T: SmallArray>);
161161

162162
impl<T: SmallArray + TypePath + Send + Sync> FromReflect for SmallVec<T>
163163
where
164-
T::Item: FromReflect + TypePath,
164+
T::Item: FromReflect + MaybeTyped + TypePath,
165165
{
166166
fn from_reflect(reflect: &dyn Reflect) -> Option<Self> {
167167
if let ReflectRef::List(ref_list) = reflect.reflect_ref() {
@@ -178,7 +178,7 @@ where
178178

179179
impl<T: SmallArray + TypePath + Send + Sync> GetTypeRegistration for SmallVec<T>
180180
where
181-
T::Item: FromReflect + TypePath,
181+
T::Item: FromReflect + MaybeTyped + TypePath,
182182
{
183183
fn get_type_registration() -> TypeRegistration {
184184
let mut registration = TypeRegistration::of::<SmallVec<T>>();
@@ -188,4 +188,4 @@ where
188188
}
189189

190190
#[cfg(feature = "functions")]
191-
crate::func::macros::impl_function_traits!(SmallVec<T>; <T: SmallArray + TypePath + Send + Sync> where T::Item: FromReflect + TypePath);
191+
crate::func::macros::impl_function_traits!(SmallVec<T>; <T: SmallArray + TypePath + Send + Sync> where T::Item: FromReflect + MaybeTyped + TypePath);

0 commit comments

Comments
 (0)