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

33 changes: 31 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 @@ -81,6 +81,18 @@ pub(crate) struct ReflectEnum<'a> {
variants: Vec<EnumVariant<'a>>,
}

impl<'a> ReflectEnum<'a> {
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
/// Get a collection of types which are exposed to the reflection API
pub fn active_types(&self) -> Vec<syn::Type> {
Vec::default()
}

/// Get a collection of types which are ignored by the reflection API
pub fn ignored_types(&self) -> Vec<syn::Type> {
Vec::default()
}
}
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved

/// Represents a field on a struct or tuple struct.
pub(crate) struct StructField<'a> {
/// The raw field.
Expand Down Expand Up @@ -322,6 +334,8 @@ impl<'a> ReflectMeta<'a> {
&self.bevy_reflect_path,
self.traits.idents(),
self.generics,
&Vec::default(),
&Vec::default(),
None,
)
}
Expand Down Expand Up @@ -350,14 +364,20 @@ 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,
field_types: &Vec<Type>,
active_types: &Vec<Type>,
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
) -> 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(),
field_types,
active_types,
Some(&self.serialization_denylist),
)
}
Expand All @@ -378,6 +398,15 @@ impl<'a> ReflectStruct<'a> {
.filter(move |field| field.attrs.ignore.is_active())
}

/// Get a collection of types which are ignored by the reflection API
pub fn ignored_types(&self) -> Vec<syn::Type> {
self.fields
.iter()
.filter(move |field| field.attrs.ignore.is_ignored())
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
.map(|field| field.data.ty.clone())
.collect::<Vec<_>>()
}

/// Get an iterator of fields which are ignored by the reflection API
pub fn ignored_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.fields
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
let ref_index = Ident::new("__index_param", Span::call_site());
let ref_value = Ident::new("__value_param", Span::call_site());

let ignored_types = reflect_enum.ignored_types();
let field_types = reflect_enum.active_types();

let EnumImpls {
variant_info,
enum_field,
Expand Down Expand Up @@ -76,6 +79,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
let typed_impl = impl_typed(
enum_name,
reflect_enum.meta().generics(),
&field_types,
&ignored_types,
quote! {
let variants = [#(#variant_info),*];
let info = #info_generator;
Expand Down
20 changes: 17 additions & 3 deletions crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult};
use crate::impls::impl_typed;
use crate::utility::generic_where_clause;
use crate::ReflectStruct;
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
Expand Down Expand Up @@ -34,6 +35,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream {
.unwrap_or_else(|| Member::Unnamed(Index::from(field.index)))
})
.collect::<Vec<_>>();
let ignored_types = reflect_struct.ignored_types();
let field_types = reflect_struct.active_types();
let field_count = field_idents.len();
let field_indices = (0..field_count).collect::<Vec<usize>>();
Expand Down Expand Up @@ -91,6 +93,8 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream {
let typed_impl = impl_typed(
struct_name,
reflect_struct.meta().generics(),
&field_types,
&ignored_types,
quote! {
let fields = [#field_generator];
let info = #info_generator;
Expand All @@ -99,16 +103,26 @@ 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, &ignored_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 where_reflect_clause = generic_where_clause(
where_clause,
&field_types,
quote! { #bevy_reflect_path::Reflect },
&ignored_types,
quote! { 'static + std::marker::Send + std::marker::Sync },
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
);

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 +174,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
21 changes: 18 additions & 3 deletions crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult};
use crate::impls::impl_typed;
use crate::utility::generic_where_clause;
use crate::ReflectStruct;
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
Expand All @@ -11,16 +12,19 @@ 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()
.map(|field| Member::Unnamed(Index::from(field.index)))
.collect::<Vec<_>>();
let ignored_types = reflect_struct.ignored_types();
let field_types = reflect_struct.active_types();
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, &ignored_types);

let hash_fn = reflect_struct
.meta()
.traits()
Expand Down Expand Up @@ -75,6 +79,8 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream {
let typed_impl = impl_typed(
struct_name,
reflect_struct.meta().generics(),
&field_types,
&ignored_types,
quote! {
let fields = [#field_generator];
let info = #info_generator;
Expand All @@ -86,12 +92,21 @@ 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 where_reflect_clause = generic_where_clause(
where_clause,
&field_types,
quote! { #bevy_reflect_path::Reflect + #bevy_reflect_path::Typed },
&ignored_types,
quote! { 'static + std::marker::Send + std::marker::Sync },
);

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 +137,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
16 changes: 14 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,13 @@
use crate::utility::generic_where_clause;
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>,
ignored_types: &Vec<Type>,
generator: proc_macro2::TokenStream,
bevy_reflect_path: &Path,
) -> proc_macro2::TokenStream {
Expand All @@ -28,8 +31,17 @@ pub(crate) fn impl_typed(

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

// Add Typed bound for each active field
let where_reflect_clause = generic_where_clause(
where_clause,
field_types,
quote! { #bevy_reflect_path::Reflect },
ignored_types,
quote! { 'static + std::marker::Send + std::marker::Sync },
);

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_reflect_clause {
fn type_info() -> &'static #bevy_reflect_path::TypeInfo {
#static_generator
}
Expand Down
4 changes: 4 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,13 @@ 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 ignored_types = Vec::default();
let typed_impl = impl_typed(
type_name,
meta.generics(),
&field_types,
&ignored_types,
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
quote! {
let info = #bevy_reflect_path::ValueInfo::new::<Self>() #with_docs;
#bevy_reflect_path::TypeInfo::Value(info)
Expand Down
16 changes: 14 additions & 2 deletions crates/bevy_reflect/bevy_reflect_derive/src/registration.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
//! Contains code related specifically to Bevy's type registration.

use crate::utility::generic_where_clause;
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
ignored_types: &Vec<Type>,
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 +25,18 @@ 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 where_reflect_clause = generic_where_clause(
where_clause,
field_types,
quote! { #bevy_reflect_path::Reflect },
ignored_types,
quote! { 'static + std::marker::Send + std::marker::Sync },
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
);
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_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
28 changes: 26 additions & 2 deletions crates/bevy_reflect/bevy_reflect_derive/src/utility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
use crate::field_attributes::ReflectIgnoreBehavior;
use bevy_macro_utils::BevyManifest;
use bit_set::BitSet;
use proc_macro2::{Ident, Span};
use syn::{Member, Path};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{Member, Path, Type, WhereClause};

/// Returns the correct path for `bevy_reflect`.
pub(crate) fn get_bevy_reflect_path() -> Path {
Expand Down Expand Up @@ -59,6 +60,29 @@ pub(crate) fn ident_or_index(ident: Option<&Ident>, index: usize) -> Member {
)
}

pub(crate) fn generic_where_clause(
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
where_clause: Option<&WhereClause>,
active_types: &Vec<Type>,
active_trait_bounds: TokenStream,
ignored_types: &Vec<Type>,
ignored_trait_bounds: TokenStream,
) -> TokenStream {
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
let mut generic_where_clause = if where_clause.is_some() {
quote! {#where_clause}
} else if !(active_types.is_empty() && ignored_types.is_empty()) {
quote! {where}
} else {
quote! {}
};
generic_where_clause.extend(quote! {
#(#active_types: #active_trait_bounds,)*
});
generic_where_clause.extend(quote! {
#(#ignored_types: #ignored_trait_bounds,)*
});
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
generic_where_clause
}

impl<T> Default for ResultSifter<T> {
fn default() -> Self {
Self {
Expand Down
32 changes: 32 additions & 0 deletions crates/bevy_reflect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,38 @@ 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, U> {
a: T,
#[reflect(ignore)]
_b: U,
}

// Type that doesn't implement Reflect
struct NoReflect(f32);

let mut foo = Foo::<u32, NoReflect> {
a: 42,
_b: NoReflect(1.0),
};

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
Loading