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

109 changes: 100 additions & 9 deletions crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ use crate::container_attributes::ReflectTraits;
use crate::field_attributes::{parse_field_attrs, ReflectFieldAttr};
use crate::utility::members_to_serialization_denylist;
use bit_set::BitSet;
use proc_macro2::TokenStream;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this import and just fully qualify the usages in code? It mainly helps to differentiate between proc_macro::TokenStream and proc_macro2::TokenStream without having to check the imports.

We may revisit and decide this practice is not really needed, but just to help keep things consistent for now, we should change it.

Copy link
Contributor Author

@cBournhonesque cBournhonesque Jan 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can; but in general I think the common practice is to use proc_macro2::TokenStream everywhere (since it's the one that is compatible with syn and quote) and only at the very last stage convert into a proc_macro::TokenStream.

See for example: https://github.com/serde-rs/serde/tree/master/serde_derive/src

I think most crates have this model; where proc_macro::TokenStream is the only one that is explicitly spelled out and not imported (and is normally only used in lib.rs when the final macro is put together)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, I think that use proc_macro2::TokenStream should be used everywhere by default (since it's the one we apply ops on), and then we just call proc_macro::TokenStream when needed. But this can be left to another PR, ot unblock this one

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree, it makes more sense to flip it. But just for consistency until a cleanup PR comes along to handle it, we should try to keep it consistent

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 @@ -316,12 +317,27 @@ impl<'a> ReflectMeta<'a> {
}

/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
pub fn get_type_registration(&self) -> proc_macro2::TokenStream {
///
/// * `active_types`: types corresponding to active fields in the object (used to add specific trait bounds)
/// * `ignored_types`: types corresponding to ignored fields in the object (used to add specific trait bounds)
/// * `active_trait_bounds`: trait bounds to provide for the active types
/// * `ignored_trait_bounds`: trait bounds to provide for the ignored types
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
pub fn get_type_registration(
&self,
active_types: &[Type],
ignored_types: &[Type],
active_trait_bounds: &TokenStream,
ignored_trait_bounds: &TokenStream,
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
) -> proc_macro2::TokenStream {
crate::registration::impl_get_type_registration(
self.type_name,
&self.bevy_reflect_path,
self.traits.idents(),
self.generics,
active_types,
ignored_types,
active_trait_bounds,
ignored_trait_bounds,
None,
)
}
Expand Down Expand Up @@ -350,39 +366,59 @@ impl<'a> ReflectStruct<'a> {
/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
///
/// Returns a specific implementation for structs and this method should be preferred over the generic [`get_type_registration`](crate::ReflectMeta) method
pub fn get_type_registration(&self) -> proc_macro2::TokenStream {
///
/// * `active_types`: types corresponding to active fields in the struct (used to add specific trait bounds)
/// * `ignored_types`: types corresponding to ignored fields in the struct (used to add specific trait bounds)
/// * `active_trait_bounds`: trait bounds to provide for the active types
/// * `ignored_trait_bounds`: trait bounds to provide for the ignored types
pub fn get_type_registration(
&self,
active_types: &[Type],
ignored_types: &[Type],
active_trait_bounds: &TokenStream,
ignored_trait_bounds: &TokenStream,
) -> 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(),
active_types,
ignored_types,
active_trait_bounds,
ignored_trait_bounds,
Some(&self.serialization_denylist),
)
}

/// Get a collection of types which are exposed to the reflection API
pub fn active_types(&self) -> Vec<syn::Type> {
self.fields
.iter()
.filter(move |field| field.attrs.ignore.is_active())
self.active_fields()
.map(|field| field.data.ty.clone())
.collect::<Vec<_>>()
.collect()
}

/// Get an iterator of fields which are exposed to the reflection API
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.fields
.iter()
.filter(move |field| field.attrs.ignore.is_active())
.filter(|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.ignored_fields()
.map(|field| field.data.ty.clone())
.collect()
}

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

/// The complete set of fields in this struct.
Expand Down Expand Up @@ -410,4 +446,59 @@ impl<'a> ReflectEnum<'a> {
pub fn variants(&self) -> &[EnumVariant<'a>] {
&self.variants
}

/// Get an iterator of fields which are exposed to the reflection API
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.variants()
.iter()
.flat_map(|variant| variant.active_fields())
}

/// Get a collection of types which are exposed to the reflection API
pub fn active_types(&self) -> Vec<syn::Type> {
self.active_fields()
.map(|field| field.data.ty.clone())
.collect()
}

/// Get an iterator of fields which are ignored by the reflection API
pub fn ignored_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.variants()
.iter()
.flat_map(|variant| variant.ignored_fields())
}

/// Get a collection of types which are ignored to the reflection API
pub fn ignored_types(&self) -> Vec<syn::Type> {
self.ignored_fields()
.map(|field| field.data.ty.clone())
.collect()
}
}

impl<'a> EnumVariant<'a> {
/// Get an iterator of fields which are exposed to the reflection API
#[allow(dead_code)]
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.fields()
.iter()
.filter(move |field| field.attrs.ignore.is_active())
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
}

/// Get an iterator of fields which are ignored by the reflection API
#[allow(dead_code)]
pub fn ignored_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.fields()
.iter()
.filter(move |field| field.attrs.ignore.is_ignored())
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved
}

/// The complete set of fields in this variant.
#[allow(dead_code)]
pub fn fields(&self) -> &[StructField<'a>] {
match &self.fields {
EnumVariantFields::Named(fields) | EnumVariantFields::Unnamed(fields) => fields,
EnumVariantFields::Unit => &[],
}
}
}
14 changes: 14 additions & 0 deletions crates/bevy_reflect/bevy_reflect_derive/src/fq_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub(crate) struct FQClone;
pub(crate) struct FQDefault;
pub(crate) struct FQOption;
pub(crate) struct FQResult;
pub(crate) struct FQSend;
pub(crate) struct FQSync;

impl ToTokens for FQAny {
fn to_tokens(&self, tokens: &mut TokenStream) {
Expand Down Expand Up @@ -75,3 +77,15 @@ impl ToTokens for FQResult {
quote!(::core::result::Result).to_tokens(tokens);
}
}

impl ToTokens for FQSend {
fn to_tokens(&self, tokens: &mut TokenStream) {
quote!(::core::marker::Send).to_tokens(tokens);
}
}

impl ToTokens for FQSync {
fn to_tokens(&self, tokens: &mut TokenStream) {
quote!(::core::marker::Sync).to_tokens(tokens);
}
}
31 changes: 27 additions & 4 deletions crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::derive_data::{EnumVariant, EnumVariantFields, ReflectEnum, StructField};
use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors};
use crate::fq_std::{FQAny, FQBox, FQOption, FQResult};
use crate::fq_std::{FQAny, FQBox, FQOption, FQResult, FQSend, FQSync};
use crate::impls::impl_typed;
use crate::utility::extend_where_clause;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
Expand All @@ -15,6 +16,11 @@ 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 field_types = reflect_enum.active_types();
let ignored_types = reflect_enum.ignored_types();
let active_trait_bounds = quote! { #bevy_reflect_path::FromReflect };
let ignored_trait_bounds = quote! { #FQAny + #FQSend + #FQSync + Default };
cBournhonesque marked this conversation as resolved.
Show resolved Hide resolved

let EnumImpls {
variant_info,
enum_field,
Expand Down Expand Up @@ -76,6 +82,10 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
let typed_impl = impl_typed(
enum_name,
reflect_enum.meta().generics(),
&field_types,
&ignored_types,
&active_trait_bounds,
&ignored_trait_bounds,
quote! {
let variants = [#(#variant_info),*];
let info = #info_generator;
Expand All @@ -84,16 +94,29 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
bevy_reflect_path,
);

let get_type_registration_impl = reflect_enum.meta().get_type_registration();
let get_type_registration_impl = reflect_enum.meta().get_type_registration(
&field_types,
&ignored_types,
&active_trait_bounds,
&ignored_trait_bounds,
);
let (impl_generics, ty_generics, where_clause) =
reflect_enum.meta().generics().split_for_impl();

let where_reflect_clause = extend_where_clause(
where_clause,
&field_types,
&active_trait_bounds,
&ignored_types,
&ignored_trait_bounds,
);

TokenStream::from(quote! {
#get_type_registration_impl

#typed_impl

impl #impl_generics #bevy_reflect_path::Enum for #enum_name #ty_generics #where_clause {
impl #impl_generics #bevy_reflect_path::Enum for #enum_name #ty_generics #where_reflect_clause {
fn field(&self, #ref_name: &str) -> #FQOption<&dyn #bevy_reflect_path::Reflect> {
match self {
#(#enum_field,)*
Expand Down Expand Up @@ -177,7 +200,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
}
}

impl #impl_generics #bevy_reflect_path::Reflect for #enum_name #ty_generics #where_clause {
impl #impl_generics #bevy_reflect_path::Reflect for #enum_name #ty_generics #where_reflect_clause {
#[inline]
fn type_name(&self) -> &str {
::core::any::type_name::<Self>()
Expand Down
29 changes: 25 additions & 4 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::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult, FQSend, FQSync};
use crate::impls::impl_typed;
use crate::utility::extend_where_clause;
use crate::ReflectStruct;
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
Expand Down Expand Up @@ -35,6 +36,9 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream {
})
.collect::<Vec<_>>();
let field_types = reflect_struct.active_types();
let ignored_types = reflect_struct.ignored_types();
let active_trait_bounds = quote! { #bevy_reflect_path::Reflect };
let ignored_trait_bounds = quote! { #FQAny + #FQSend + #FQSync };
let field_count = field_idents.len();
let field_indices = (0..field_count).collect::<Vec<usize>>();

Expand Down Expand Up @@ -91,6 +95,10 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream {
let typed_impl = impl_typed(
struct_name,
reflect_struct.meta().generics(),
&field_types,
&ignored_types,
&active_trait_bounds,
&ignored_trait_bounds,
quote! {
let fields = [#field_generator];
let info = #info_generator;
Expand All @@ -99,16 +107,29 @@ 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,
&active_trait_bounds,
&ignored_trait_bounds,
);
let (impl_generics, ty_generics, where_clause) =
reflect_struct.meta().generics().split_for_impl();

let where_reflect_clause = extend_where_clause(
where_clause,
&field_types,
&active_trait_bounds,
&ignored_types,
&ignored_trait_bounds,
);

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 +181,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
Loading