Skip to content

Commit cbb4c26

Browse files
Enable deriving Reflect on structs with generic types (#7364)
# Objective I recently had an issue, where I have a struct: ``` struct Property { inner: T } ``` that I use as a wrapper for internal purposes. I don't want to update my struct definition to ``` struct Property<T: Reflect>{ inner: T } ``` because I still want to be able to build `Property<T>` for types `T` that are not `Reflect`. (and also because I don't want to update my whole code base with `<T: Reflect>` bounds) I still wanted to have reflection on it (for `bevy_inspector_egui`), but adding `derive(Reflect)` fails with the error: `T cannot be sent between threads safely. T needs to implement Sync.` I believe that `bevy_reflect` should adopt the model of other derives in the case of generics, which is to add the `Reflect` implementation only if the generics also implement `Reflect`. (That is the behaviour of other macros such as `derive(Clone)` or `derive(Debug)`. It's also the current behavior of `derive(FromReflect)`. Basically doing something like: ``` impl<T> Reflect for Foo<T> where T: Reflect ``` ## Solution - I updated the derive macros for `Structs` and `TupleStructs` to add extra `where` bounds. - Every type that is reflected will need a `T: Reflect` bound - Ignored types will need a `T: 'static + Send + Sync` bound. Here's the reason. For cases like this: ``` #[derive(Reflect)] struct Foo<T, U>{ a: T #[reflect(ignore)] b: U } ``` I had to add the bound `'static + Send + Sync` to ignored generics like `U`. The reason is that we want `Foo<T, U>` to be `Reflect: 'static + Send + Sync`, so `Foo<T, U>` must be able to implement those auto-traits. `Foo<T, U>` will only implement those auto-traits if every generic type implements them, including ignored types. This means that the previously compile-fail case now compiles: ``` #[derive(Reflect)] struct Foo<'a> { #[reflect(ignore)] value: &'a str, } ``` But `Foo<'a>` will only be useable in the cases where `'a: 'static` and panic if we don't have `'a: 'static`, which is what we want (nice bonus from this PR ;) ) --- ## Changelog > This section is optional. If this was a trivial fix, or has no externally-visible impact, you can delete this section. ### Added Possibility to add `derive(Reflect)` to structs and enums that contain generic types, like so: ``` #[derive(Reflect)] struct Foo<T>{ a: T } ``` Reflection will only be available if the generic type T also implements `Reflect`. (previously, this would just return a compiler error)
1 parent 9ffba9b commit cbb4c26

File tree

14 files changed

+312
-44
lines changed

14 files changed

+312
-44
lines changed

crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs

+98-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::container_attributes::ReflectTraits;
22
use crate::field_attributes::{parse_field_attrs, ReflectFieldAttr};
3-
use crate::utility::members_to_serialization_denylist;
3+
use crate::fq_std::{FQAny, FQDefault, FQSend, FQSync};
4+
use crate::utility::{members_to_serialization_denylist, WhereClauseOptions};
45
use bit_set::BitSet;
56
use quote::quote;
67

@@ -316,12 +317,16 @@ impl<'a> ReflectMeta<'a> {
316317
}
317318

318319
/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
319-
pub fn get_type_registration(&self) -> proc_macro2::TokenStream {
320+
pub fn get_type_registration(
321+
&self,
322+
where_clause_options: &WhereClauseOptions,
323+
) -> proc_macro2::TokenStream {
320324
crate::registration::impl_get_type_registration(
321325
self.type_name,
322326
&self.bevy_reflect_path,
323327
self.traits.idents(),
324328
self.generics,
329+
where_clause_options,
325330
None,
326331
)
327332
}
@@ -350,46 +355,65 @@ impl<'a> ReflectStruct<'a> {
350355
/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
351356
///
352357
/// Returns a specific implementation for structs and this method should be preferred over the generic [`get_type_registration`](crate::ReflectMeta) method
353-
pub fn get_type_registration(&self) -> proc_macro2::TokenStream {
358+
pub fn get_type_registration(
359+
&self,
360+
where_clause_options: &WhereClauseOptions,
361+
) -> proc_macro2::TokenStream {
354362
let reflect_path = self.meta.bevy_reflect_path();
355363

356364
crate::registration::impl_get_type_registration(
357365
self.meta.type_name(),
358366
reflect_path,
359367
self.meta.traits().idents(),
360368
self.meta.generics(),
369+
where_clause_options,
361370
Some(&self.serialization_denylist),
362371
)
363372
}
364373

365374
/// Get a collection of types which are exposed to the reflection API
366375
pub fn active_types(&self) -> Vec<syn::Type> {
367-
self.fields
368-
.iter()
369-
.filter(move |field| field.attrs.ignore.is_active())
376+
self.active_fields()
370377
.map(|field| field.data.ty.clone())
371-
.collect::<Vec<_>>()
378+
.collect()
372379
}
373380

374381
/// Get an iterator of fields which are exposed to the reflection API
375382
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
376383
self.fields
377384
.iter()
378-
.filter(move |field| field.attrs.ignore.is_active())
385+
.filter(|field| field.attrs.ignore.is_active())
386+
}
387+
388+
/// Get a collection of types which are ignored by the reflection API
389+
pub fn ignored_types(&self) -> Vec<syn::Type> {
390+
self.ignored_fields()
391+
.map(|field| field.data.ty.clone())
392+
.collect()
379393
}
380394

381395
/// Get an iterator of fields which are ignored by the reflection API
382396
pub fn ignored_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
383397
self.fields
384398
.iter()
385-
.filter(move |field| field.attrs.ignore.is_ignored())
399+
.filter(|field| field.attrs.ignore.is_ignored())
386400
}
387401

388402
/// The complete set of fields in this struct.
389403
#[allow(dead_code)]
390404
pub fn fields(&self) -> &[StructField<'a>] {
391405
&self.fields
392406
}
407+
408+
pub fn where_clause_options(&self) -> WhereClauseOptions {
409+
let bevy_reflect_path = &self.meta().bevy_reflect_path;
410+
WhereClauseOptions {
411+
active_types: self.active_types().into(),
412+
active_trait_bounds: quote! { #bevy_reflect_path::Reflect },
413+
ignored_types: self.ignored_types().into(),
414+
ignored_trait_bounds: quote! { #FQAny + #FQSend + #FQSync },
415+
}
416+
}
393417
}
394418

395419
impl<'a> ReflectEnum<'a> {
@@ -410,4 +434,69 @@ impl<'a> ReflectEnum<'a> {
410434
pub fn variants(&self) -> &[EnumVariant<'a>] {
411435
&self.variants
412436
}
437+
438+
/// Get an iterator of fields which are exposed to the reflection API
439+
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
440+
self.variants()
441+
.iter()
442+
.flat_map(|variant| variant.active_fields())
443+
}
444+
445+
/// Get a collection of types which are exposed to the reflection API
446+
pub fn active_types(&self) -> Vec<syn::Type> {
447+
self.active_fields()
448+
.map(|field| field.data.ty.clone())
449+
.collect()
450+
}
451+
452+
/// Get an iterator of fields which are ignored by the reflection API
453+
pub fn ignored_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
454+
self.variants()
455+
.iter()
456+
.flat_map(|variant| variant.ignored_fields())
457+
}
458+
459+
/// Get a collection of types which are ignored to the reflection API
460+
pub fn ignored_types(&self) -> Vec<syn::Type> {
461+
self.ignored_fields()
462+
.map(|field| field.data.ty.clone())
463+
.collect()
464+
}
465+
466+
pub fn where_clause_options(&self) -> WhereClauseOptions {
467+
let bevy_reflect_path = &self.meta().bevy_reflect_path;
468+
WhereClauseOptions {
469+
active_types: self.active_types().into(),
470+
active_trait_bounds: quote! { #bevy_reflect_path::FromReflect },
471+
ignored_types: self.ignored_types().into(),
472+
ignored_trait_bounds: quote! { #FQAny + #FQSend + #FQSync + #FQDefault },
473+
}
474+
}
475+
}
476+
477+
impl<'a> EnumVariant<'a> {
478+
/// Get an iterator of fields which are exposed to the reflection API
479+
#[allow(dead_code)]
480+
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
481+
self.fields()
482+
.iter()
483+
.filter(|field| field.attrs.ignore.is_active())
484+
}
485+
486+
/// Get an iterator of fields which are ignored by the reflection API
487+
#[allow(dead_code)]
488+
pub fn ignored_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
489+
self.fields()
490+
.iter()
491+
.filter(|field| field.attrs.ignore.is_ignored())
492+
}
493+
494+
/// The complete set of fields in this variant.
495+
#[allow(dead_code)]
496+
pub fn fields(&self) -> &[StructField<'a>] {
497+
match &self.fields {
498+
EnumVariantFields::Named(fields) | EnumVariantFields::Unnamed(fields) => fields,
499+
EnumVariantFields::Unit => &[],
500+
}
501+
}
413502
}

crates/bevy_reflect/bevy_reflect_derive/src/fq_std.rs

+14
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ pub(crate) struct FQClone;
3939
pub(crate) struct FQDefault;
4040
pub(crate) struct FQOption;
4141
pub(crate) struct FQResult;
42+
pub(crate) struct FQSend;
43+
pub(crate) struct FQSync;
4244

4345
impl ToTokens for FQAny {
4446
fn to_tokens(&self, tokens: &mut TokenStream) {
@@ -75,3 +77,15 @@ impl ToTokens for FQResult {
7577
quote!(::core::result::Result).to_tokens(tokens);
7678
}
7779
}
80+
81+
impl ToTokens for FQSend {
82+
fn to_tokens(&self, tokens: &mut TokenStream) {
83+
quote!(::core::marker::Send).to_tokens(tokens);
84+
}
85+
}
86+
87+
impl ToTokens for FQSync {
88+
fn to_tokens(&self, tokens: &mut TokenStream) {
89+
quote!(::core::marker::Sync).to_tokens(tokens);
90+
}
91+
}

crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::derive_data::{EnumVariant, EnumVariantFields, ReflectEnum, StructFiel
22
use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors};
33
use crate::fq_std::{FQAny, FQBox, FQOption, FQResult};
44
use crate::impls::impl_typed;
5+
use crate::utility::extend_where_clause;
56
use proc_macro::TokenStream;
67
use proc_macro2::{Ident, Span};
78
use quote::quote;
@@ -15,6 +16,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
1516
let ref_index = Ident::new("__index_param", Span::call_site());
1617
let ref_value = Ident::new("__value_param", Span::call_site());
1718

19+
let where_clause_options = reflect_enum.where_clause_options();
20+
1821
let EnumImpls {
1922
variant_info,
2023
enum_field,
@@ -76,6 +79,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
7679
let typed_impl = impl_typed(
7780
enum_name,
7881
reflect_enum.meta().generics(),
82+
&where_clause_options,
7983
quote! {
8084
let variants = [#(#variant_info),*];
8185
let info = #info_generator;
@@ -84,16 +88,20 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
8488
bevy_reflect_path,
8589
);
8690

87-
let get_type_registration_impl = reflect_enum.meta().get_type_registration();
91+
let get_type_registration_impl = reflect_enum
92+
.meta()
93+
.get_type_registration(&where_clause_options);
8894
let (impl_generics, ty_generics, where_clause) =
8995
reflect_enum.meta().generics().split_for_impl();
9096

97+
let where_reflect_clause = extend_where_clause(where_clause, &where_clause_options);
98+
9199
TokenStream::from(quote! {
92100
#get_type_registration_impl
93101

94102
#typed_impl
95103

96-
impl #impl_generics #bevy_reflect_path::Enum for #enum_name #ty_generics #where_clause {
104+
impl #impl_generics #bevy_reflect_path::Enum for #enum_name #ty_generics #where_reflect_clause {
97105
fn field(&self, #ref_name: &str) -> #FQOption<&dyn #bevy_reflect_path::Reflect> {
98106
match self {
99107
#(#enum_field,)*
@@ -177,7 +185,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
177185
}
178186
}
179187

180-
impl #impl_generics #bevy_reflect_path::Reflect for #enum_name #ty_generics #where_clause {
188+
impl #impl_generics #bevy_reflect_path::Reflect for #enum_name #ty_generics #where_reflect_clause {
181189
#[inline]
182190
fn type_name(&self) -> &str {
183191
::core::any::type_name::<Self>()

crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult};
22
use crate::impls::impl_typed;
3+
use crate::utility::extend_where_clause;
34
use crate::ReflectStruct;
45
use proc_macro::TokenStream;
56
use quote::{quote, ToTokens};
@@ -88,9 +89,11 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream {
8889
}
8990
};
9091

92+
let where_clause_options = reflect_struct.where_clause_options();
9193
let typed_impl = impl_typed(
9294
struct_name,
9395
reflect_struct.meta().generics(),
96+
&where_clause_options,
9497
quote! {
9598
let fields = [#field_generator];
9699
let info = #info_generator;
@@ -99,16 +102,18 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream {
99102
bevy_reflect_path,
100103
);
101104

102-
let get_type_registration_impl = reflect_struct.get_type_registration();
105+
let get_type_registration_impl = reflect_struct.get_type_registration(&where_clause_options);
103106
let (impl_generics, ty_generics, where_clause) =
104107
reflect_struct.meta().generics().split_for_impl();
105108

109+
let where_reflect_clause = extend_where_clause(where_clause, &where_clause_options);
110+
106111
TokenStream::from(quote! {
107112
#get_type_registration_impl
108113

109114
#typed_impl
110115

111-
impl #impl_generics #bevy_reflect_path::Struct for #struct_name #ty_generics #where_clause {
116+
impl #impl_generics #bevy_reflect_path::Struct for #struct_name #ty_generics #where_reflect_clause {
112117
fn field(&self, name: &str) -> #FQOption<&dyn #bevy_reflect_path::Reflect> {
113118
match name {
114119
#(#field_names => #fqoption::Some(&self.#field_idents),)*
@@ -160,7 +165,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream {
160165
}
161166
}
162167

163-
impl #impl_generics #bevy_reflect_path::Reflect for #struct_name #ty_generics #where_clause {
168+
impl #impl_generics #bevy_reflect_path::Reflect for #struct_name #ty_generics #where_reflect_clause {
164169
#[inline]
165170
fn type_name(&self) -> &str {
166171
::core::any::type_name::<Self>()

crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult};
22
use crate::impls::impl_typed;
3+
use crate::utility::extend_where_clause;
34
use crate::ReflectStruct;
45
use proc_macro::TokenStream;
56
use quote::{quote, ToTokens};
@@ -11,7 +12,6 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream {
1112

1213
let bevy_reflect_path = reflect_struct.meta().bevy_reflect_path();
1314
let struct_name = reflect_struct.meta().type_name();
14-
let get_type_registration_impl = reflect_struct.get_type_registration();
1515

1616
let field_idents = reflect_struct
1717
.active_fields()
@@ -21,6 +21,9 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream {
2121
let field_count = field_idents.len();
2222
let field_indices = (0..field_count).collect::<Vec<usize>>();
2323

24+
let where_clause_options = reflect_struct.where_clause_options();
25+
let get_type_registration_impl = reflect_struct.get_type_registration(&where_clause_options);
26+
2427
let hash_fn = reflect_struct
2528
.meta()
2629
.traits()
@@ -75,6 +78,7 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream {
7578
let typed_impl = impl_typed(
7679
struct_name,
7780
reflect_struct.meta().generics(),
81+
&where_clause_options,
7882
quote! {
7983
let fields = [#field_generator];
8084
let info = #info_generator;
@@ -86,12 +90,14 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream {
8690
let (impl_generics, ty_generics, where_clause) =
8791
reflect_struct.meta().generics().split_for_impl();
8892

93+
let where_reflect_clause = extend_where_clause(where_clause, &where_clause_options);
94+
8995
TokenStream::from(quote! {
9096
#get_type_registration_impl
9197

9298
#typed_impl
9399

94-
impl #impl_generics #bevy_reflect_path::TupleStruct for #struct_name #ty_generics #where_clause {
100+
impl #impl_generics #bevy_reflect_path::TupleStruct for #struct_name #ty_generics #where_reflect_clause {
95101
fn field(&self, index: usize) -> #FQOption<&dyn #bevy_reflect_path::Reflect> {
96102
match index {
97103
#(#field_indices => #fqoption::Some(&self.#field_idents),)*
@@ -122,7 +128,7 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream {
122128
}
123129
}
124130

125-
impl #impl_generics #bevy_reflect_path::Reflect for #struct_name #ty_generics #where_clause {
131+
impl #impl_generics #bevy_reflect_path::Reflect for #struct_name #ty_generics #where_reflect_clause {
126132
#[inline]
127133
fn type_name(&self) -> &str {
128134
::core::any::type_name::<Self>()

crates/bevy_reflect/bevy_reflect_derive/src/impls/typed.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
use crate::utility::{extend_where_clause, WhereClauseOptions};
12
use proc_macro2::Ident;
23
use quote::quote;
34
use syn::{Generics, Path};
45

6+
#[allow(clippy::too_many_arguments)]
57
pub(crate) fn impl_typed(
68
type_name: &Ident,
79
generics: &Generics,
10+
where_clause_options: &WhereClauseOptions,
811
generator: proc_macro2::TokenStream,
912
bevy_reflect_path: &Path,
1013
) -> proc_macro2::TokenStream {
@@ -28,8 +31,11 @@ pub(crate) fn impl_typed(
2831

2932
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
3033

34+
// Add Typed bound for each active field
35+
let where_reflect_clause = extend_where_clause(where_clause, where_clause_options);
36+
3137
quote! {
32-
impl #impl_generics #bevy_reflect_path::Typed for #type_name #ty_generics #where_clause {
38+
impl #impl_generics #bevy_reflect_path::Typed for #type_name #ty_generics #where_reflect_clause {
3339
fn type_info() -> &'static #bevy_reflect_path::TypeInfo {
3440
#static_generator
3541
}

0 commit comments

Comments
 (0)