Skip to content

Commit

Permalink
add Reflect and Typed bounds when reflecting on a struct that has gen…
Browse files Browse the repository at this point in the history
…eric types
  • Loading branch information
cbournhonesque-sc committed Jan 25, 2023
1 parent c3a4682 commit 1e3ecec
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 12 deletions.
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
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::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>,
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
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,)*
});

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
26 changes: 26 additions & 0 deletions crates/bevy_reflect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,32 @@ mod tests {
assert_eq!(foo.a, 123);
}

#[test]
fn reflect_struct_generics() {
#[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

0 comments on commit 1e3ecec

Please sign in to comment.