Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Enable deriving Reflect on structs with generic types #7364

6 changes: 4 additions & 2 deletions crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use quote::quote;
use crate::{utility, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{Data, DeriveInput, Field, Fields, Generics, Ident, Meta, Path, Token, Variant};
use syn::{Data, DeriveInput, Field, Fields, Generics, Ident, Meta, Path, Token, Type, Variant};

pub(crate) enum ReflectDerive<'a> {
Struct(ReflectStruct<'a>),
Expand Down Expand Up @@ -322,6 +322,7 @@ impl<'a> ReflectMeta<'a> {
&self.bevy_reflect_path,
self.traits.idents(),
self.generics,
&Vec::default(),
None,
)
}
Expand Down Expand Up @@ -350,14 +351,15 @@ impl<'a> ReflectStruct<'a> {
/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
///
/// Returns a specific implementation for structs and this method should be preffered over the generic [`get_type_registration`](crate::ReflectMeta) method
pub fn get_type_registration(&self) -> proc_macro2::TokenStream {
pub fn get_type_registration(&self, fields: &Vec<Type>) -> proc_macro2::TokenStream {
let reflect_path = self.meta.bevy_reflect_path();

crate::registration::impl_get_type_registration(
self.meta.type_name(),
reflect_path,
self.meta.traits().idents(),
self.meta.generics(),
fields,
Some(&self.serialization_denylist),
)
}
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
let typed_impl = impl_typed(
enum_name,
reflect_enum.meta().generics(),
&Vec::default(),
quote! {
let variants = [#(#variant_info),*];
let info = #info_generator;
Expand Down
25 changes: 22 additions & 3 deletions crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream {
let typed_impl = impl_typed(
struct_name,
reflect_struct.meta().generics(),
&field_types,
quote! {
let fields = [#field_generator];
let info = #info_generator;
Expand All @@ -99,16 +100,34 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream {
bevy_reflect_path,
);

let get_type_registration_impl = reflect_struct.get_type_registration();
let get_type_registration_impl = reflect_struct.get_type_registration(&field_types);
let (impl_generics, ty_generics, where_clause) =
reflect_struct.meta().generics().split_for_impl();

// Add Reflect bound for each active field
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
let mut where_reflect_clause = if where_clause.is_some() {
quote! {#where_clause}
} else if !field_types.is_empty() {
quote! {where}
} else {
quote! {}
};
where_reflect_clause.extend(quote! {
// TODO: had to add #bevy_reflect_path::Typed to get the test to compile,
// presumably because of this:
// #[inline]
// fn get_type_info(&self) -> &'static bevy_reflect::TypeInfo {
// <Self as bevy_reflect::Typed>::type_info()
// }
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
#(#field_types: #bevy_reflect_path::Reflect + #bevy_reflect_path::Typed,)*
});

TokenStream::from(quote! {
#get_type_registration_impl

#typed_impl

impl #impl_generics #bevy_reflect_path::Struct for #struct_name #ty_generics #where_clause {
impl #impl_generics #bevy_reflect_path::Struct for #struct_name #ty_generics #where_reflect_clause {
fn field(&self, name: &str) -> #FQOption<&dyn #bevy_reflect_path::Reflect> {
match name {
#(#field_names => #fqoption::Some(&self.#field_idents),)*
Expand Down Expand Up @@ -160,7 +179,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream {
}
}

impl #impl_generics #bevy_reflect_path::Reflect for #struct_name #ty_generics #where_clause {
impl #impl_generics #bevy_reflect_path::Reflect for #struct_name #ty_generics #where_reflect_clause {
#[inline]
fn type_name(&self) -> &str {
::core::any::type_name::<Self>()
Expand Down
26 changes: 23 additions & 3 deletions crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream {

let bevy_reflect_path = reflect_struct.meta().bevy_reflect_path();
let struct_name = reflect_struct.meta().type_name();
let get_type_registration_impl = reflect_struct.get_type_registration();

let field_idents = reflect_struct
.active_fields()
Expand All @@ -21,6 +20,8 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream {
let field_count = field_idents.len();
let field_indices = (0..field_count).collect::<Vec<usize>>();

let get_type_registration_impl = reflect_struct.get_type_registration(&field_types);

let hash_fn = reflect_struct
.meta()
.traits()
Expand Down Expand Up @@ -75,6 +76,7 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream {
let typed_impl = impl_typed(
struct_name,
reflect_struct.meta().generics(),
&field_types,
quote! {
let fields = [#field_generator];
let info = #info_generator;
Expand All @@ -86,12 +88,30 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream {
let (impl_generics, ty_generics, where_clause) =
reflect_struct.meta().generics().split_for_impl();

// Add Reflect bound for each active field
let mut where_reflect_clause = if where_clause.is_some() {
quote! {#where_clause}
} else if !field_types.is_empty() {
quote! {where}
} else {
quote! {}
};
where_reflect_clause.extend(quote! {
// TODO: had to add #bevy_reflect_path::Typed to get the test to compile,
// presumably because of this:
// #[inline]
// fn get_type_info(&self) -> &'static bevy_reflect::TypeInfo {
// <Self as bevy_reflect::Typed>::type_info()
// }
#(#field_types: #bevy_reflect_path::Reflect + #bevy_reflect_path::Typed,)*
});

TokenStream::from(quote! {
#get_type_registration_impl

#typed_impl

impl #impl_generics #bevy_reflect_path::TupleStruct for #struct_name #ty_generics #where_clause {
impl #impl_generics #bevy_reflect_path::TupleStruct for #struct_name #ty_generics #where_reflect_clause {
fn field(&self, index: usize) -> #FQOption<&dyn #bevy_reflect_path::Reflect> {
match index {
#(#field_indices => #fqoption::Some(&self.#field_idents),)*
Expand Down Expand Up @@ -122,7 +142,7 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream {
}
}

impl #impl_generics #bevy_reflect_path::Reflect for #struct_name #ty_generics #where_clause {
impl #impl_generics #bevy_reflect_path::Reflect for #struct_name #ty_generics #where_reflect_clause {
#[inline]
fn type_name(&self) -> &str {
::core::any::type_name::<Self>()
Expand Down
17 changes: 15 additions & 2 deletions crates/bevy_reflect/bevy_reflect_derive/src/impls/typed.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use proc_macro2::Ident;
use quote::quote;
use syn::{Generics, Path};
use syn::{Generics, Path, Type};

pub(crate) fn impl_typed(
type_name: &Ident,
generics: &Generics,
field_types: &Vec<Type>,
generator: proc_macro2::TokenStream,
bevy_reflect_path: &Path,
) -> proc_macro2::TokenStream {
Expand All @@ -28,8 +29,20 @@ pub(crate) fn impl_typed(

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

// Add Typed bound for each active field
let mut where_typed_clause = if where_clause.is_some() {
quote! {#where_clause}
} else if !field_types.is_empty() {
quote! {where}
} else {
quote! {}
};
where_typed_clause.extend(quote! {
#(#field_types: #bevy_reflect_path::Typed,)*
});

quote! {
impl #impl_generics #bevy_reflect_path::Typed for #type_name #ty_generics #where_clause {
impl #impl_generics #bevy_reflect_path::Typed for #type_name #ty_generics #where_typed_clause {
fn type_info() -> &'static #bevy_reflect_path::TypeInfo {
#static_generator
}
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> TokenStream {
#[cfg(not(feature = "documentation"))]
let with_docs: Option<proc_macro2::TokenStream> = None;

let field_types = Vec::default();
let typed_impl = impl_typed(
type_name,
meta.generics(),
&field_types,
quote! {
let info = #bevy_reflect_path::ValueInfo::new::<Self>() #with_docs;
#bevy_reflect_path::TypeInfo::Value(info)
Expand Down
17 changes: 15 additions & 2 deletions crates/bevy_reflect/bevy_reflect_derive/src/registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
use bit_set::BitSet;
use proc_macro2::Ident;
use quote::quote;
use syn::{Generics, Path};
use syn::{Generics, Path, Type};

/// Creates the `GetTypeRegistration` impl for the given type data.
pub(crate) fn impl_get_type_registration(
type_name: &Ident,
bevy_reflect_path: &Path,
registration_data: &[Ident],
generics: &Generics,
field_types: &Vec<Type>,
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
serialization_denylist: Option<&BitSet<u32>>,
) -> proc_macro2::TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
Expand All @@ -22,9 +23,21 @@ pub(crate) fn impl_get_type_registration(
}
});

// Add Typed bound for each active field
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
let mut where_reflect_typed_clause = if where_clause.is_some() {
quote! {#where_clause}
} else if !field_types.is_empty() {
quote! {where}
} else {
quote! {}
};
where_reflect_typed_clause.extend(quote! {
#(#field_types: #bevy_reflect_path::Reflect + #bevy_reflect_path::Typed,)*
});
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved

quote! {
#[allow(unused_mut)]
impl #impl_generics #bevy_reflect_path::GetTypeRegistration for #type_name #ty_generics #where_clause {
impl #impl_generics #bevy_reflect_path::GetTypeRegistration for #type_name #ty_generics #where_reflect_typed_clause {
fn get_type_registration() -> #bevy_reflect_path::TypeRegistration {
let mut registration = #bevy_reflect_path::TypeRegistration::of::<#type_name #ty_generics>();
registration.insert::<#bevy_reflect_path::ReflectFromPtr>(#bevy_reflect_path::FromType::<#type_name #ty_generics>::from_type());
Expand Down
24 changes: 24 additions & 0 deletions crates/bevy_reflect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,30 @@ mod tests {
assert_eq!(foo.a, 123);
}

#[test]
fn reflect_struct_generics() {
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Reflect)]
struct Foo<T> {
a: T,
}

let mut foo = Foo::<u32> { a: 42 };

let a = *foo.get_field::<u32>("a").unwrap();
assert_eq!(a, 42);

*foo.get_field_mut::<u32>("a").unwrap() += 1;
assert_eq!(foo.a, 43);

// patch Foo with a dynamic struct
let mut dynamic_struct = DynamicStruct::default();
dynamic_struct.insert("a", 123u32);
dynamic_struct.insert("should_be_ignored", 456);

foo.apply(&dynamic_struct);
assert_eq!(foo.a, 123);
}

#[test]
fn reflect_map() {
#[derive(Reflect, Hash)]
Expand Down