diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs b/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs index bd319d78d5a8a..c55267d3741b9 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs @@ -2,7 +2,7 @@ use crate::container_attributes::REFLECT_DEFAULT; use crate::derive_data::ReflectEnum; use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors}; use crate::field_attributes::DefaultBehavior; -use crate::utility::{extend_where_clause, ident_or_index, WhereClauseOptions}; +use crate::utility::{ident_or_index, WhereClauseOptions}; use crate::{ReflectMeta, ReflectStruct}; use bevy_macro_utils::fq_std::{FQAny, FQClone, FQDefault, FQOption}; use proc_macro2::Span; @@ -24,7 +24,7 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream { let bevy_reflect_path = meta.bevy_reflect_path(); let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl(); let where_from_reflect_clause = - extend_where_clause(where_clause, &WhereClauseOptions::new_type_path(meta)); + WhereClauseOptions::new_type_path(meta).extend_where_clause(where_clause); quote! { impl #impl_generics #bevy_reflect_path::FromReflect for #type_path #ty_generics #where_from_reflect_clause { fn from_reflect(reflect: &dyn #bevy_reflect_path::Reflect) -> #FQOption { @@ -51,7 +51,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream // Add FromReflect bound for each active field let where_from_reflect_clause = - extend_where_clause(where_clause, &WhereClauseOptions::new(reflect_enum.meta())); + WhereClauseOptions::new(reflect_enum.meta()).extend_where_clause(where_clause); quote! { impl #impl_generics #bevy_reflect_path::FromReflect for #enum_path #ty_generics #where_from_reflect_clause { @@ -130,10 +130,8 @@ fn impl_struct_internal( .split_for_impl(); // Add FromReflect bound for each active field - let where_from_reflect_clause = extend_where_clause( - where_clause, - &WhereClauseOptions::new(reflect_struct.meta()), - ); + let where_from_reflect_clause = + WhereClauseOptions::new(reflect_struct.meta()).extend_where_clause(where_clause); quote! { impl #impl_generics #bevy_reflect_path::FromReflect for #struct_path #ty_generics #where_from_reflect_clause { diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs index a733ec2e262bf..559052dd64445 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs @@ -1,7 +1,6 @@ use crate::derive_data::{EnumVariant, EnumVariantFields, ReflectEnum, StructField}; use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors}; use crate::impls::{impl_type_path, impl_typed}; -use crate::utility::extend_where_clause; use bevy_macro_utils::fq_std::{FQAny, FQBox, FQOption, FQResult}; use proc_macro2::{Ident, Span}; use quote::quote; @@ -92,7 +91,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream let (impl_generics, ty_generics, where_clause) = reflect_enum.meta().type_path().generics().split_for_impl(); - let where_reflect_clause = extend_where_clause(where_clause, &where_clause_options); + let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); quote! { #get_type_registration_impl diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs index 1bf46968cebdc..efbaef665c734 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs @@ -1,5 +1,5 @@ use crate::impls::{impl_type_path, impl_typed}; -use crate::utility::{extend_where_clause, ident_or_index}; +use crate::utility::ident_or_index; use crate::ReflectStruct; use bevy_macro_utils::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult}; use quote::{quote, ToTokens}; @@ -99,7 +99,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS .generics() .split_for_impl(); - let where_reflect_clause = extend_where_clause(where_clause, &where_clause_options); + let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); quote! { #get_type_registration_impl diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs index e05226d7a52b6..2f936552e6459 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs @@ -1,5 +1,4 @@ use crate::impls::{impl_type_path, impl_typed}; -use crate::utility::extend_where_clause; use crate::ReflectStruct; use bevy_macro_utils::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult}; use quote::{quote, ToTokens}; @@ -90,7 +89,7 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2:: .generics() .split_for_impl(); - let where_reflect_clause = extend_where_clause(where_clause, &where_clause_options); + let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); quote! { #get_type_registration_impl diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/typed.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/typed.rs index 5894ef82e5540..66b6c1374b949 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/typed.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/typed.rs @@ -1,4 +1,4 @@ -use crate::utility::{extend_where_clause, StringExpr, WhereClauseOptions}; +use crate::utility::{StringExpr, WhereClauseOptions}; use quote::{quote, ToTokens}; use crate::{ @@ -101,7 +101,7 @@ pub(crate) fn impl_type_path( let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl(); // Add Typed bound for each active field - let where_reflect_clause = extend_where_clause(where_clause, where_clause_options); + let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); quote! { #primitive_assert @@ -142,7 +142,7 @@ pub(crate) fn impl_typed( let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl(); - let where_reflect_clause = extend_where_clause(where_clause, where_clause_options); + let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); quote! { impl #impl_generics #bevy_reflect_path::Typed for #type_path #ty_generics #where_reflect_clause { diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs index 51baf43a92373..e6171e3fb4c86 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs @@ -1,5 +1,5 @@ use crate::impls::{impl_type_path, impl_typed}; -use crate::utility::{extend_where_clause, WhereClauseOptions}; +use crate::utility::WhereClauseOptions; use crate::ReflectMeta; use bevy_macro_utils::fq_std::{FQAny, FQBox, FQClone, FQOption, FQResult}; use quote::quote; @@ -34,7 +34,7 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream { let type_path_impl = impl_type_path(meta, &where_clause_options); let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl(); - let where_reflect_clause = extend_where_clause(where_clause, &where_clause_options); + let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); let get_type_registration_impl = meta.get_type_registration(&where_clause_options); quote! { diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs b/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs index 115274ad46ae1..45d9731c18c0e 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs @@ -2,7 +2,7 @@ use crate::derive_data::ReflectMeta; use crate::serialization::SerializationDataDef; -use crate::utility::{extend_where_clause, WhereClauseOptions}; +use crate::utility::WhereClauseOptions; use quote::quote; /// Creates the `GetTypeRegistration` impl for the given type data. @@ -16,7 +16,7 @@ pub(crate) fn impl_get_type_registration( let bevy_reflect_path = meta.bevy_reflect_path(); let registration_data = meta.traits().idents(); let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl(); - let where_reflect_clause = extend_where_clause(where_clause, where_clause_options); + let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); let from_reflect_data = if meta.from_reflect().should_auto_derive() { Some(quote! { diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs b/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs index 0bed9def61b39..0cd6520ff183e 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs @@ -7,8 +7,7 @@ use bevy_macro_utils::{ }; use proc_macro2::{Ident, Span}; use quote::{quote, ToTokens}; -use syn::punctuated::Punctuated; -use syn::{spanned::Spanned, LitStr, Member, Path, Token, TypeParam, WhereClause, WherePredicate}; +use syn::{spanned::Spanned, LitStr, Member, Path, WhereClause}; /// Returns the correct path for `bevy_reflect`. pub(crate) fn get_bevy_reflect_path() -> Path { @@ -61,35 +60,16 @@ pub(crate) fn ident_or_index(ident: Option<&Ident>, index: usize) -> Member { ) } -/// Options defining how to extend the `where` clause in reflection with any additional bounds needed. -pub(crate) struct WhereClauseOptions { - /// Any types that will be reflected and need an extra trait bound - active_types: Vec, - /// Trait bounds to add to the active types - active_trait_bounds: Vec, - /// Any types that won't be reflected and need an extra trait bound - ignored_types: Vec, - /// Trait bounds to add to the ignored types - ignored_trait_bounds: Vec, - custom_where: Option>, +/// Options defining how to extend the `where` clause for reflection. +pub(crate) struct WhereClauseOptions<'a, 'b> { + meta: &'a ReflectMeta<'b>, + additional_bounds: proc_macro2::TokenStream, + required_bounds: proc_macro2::TokenStream, } -impl Default for WhereClauseOptions { - /// By default, don't add any additional bounds to the `where` clause - fn default() -> Self { - Self { - active_types: Vec::new(), - ignored_types: Vec::new(), - active_trait_bounds: Vec::new(), - ignored_trait_bounds: Vec::new(), - custom_where: None, - } - } -} - -impl WhereClauseOptions { +impl<'a, 'b> WhereClauseOptions<'a, 'b> { /// Create [`WhereClauseOptions`] for a reflected struct or enum type. - pub fn new(meta: &ReflectMeta) -> Self { + pub fn new(meta: &'a ReflectMeta<'b>) -> Self { let bevy_reflect_path = meta.bevy_reflect_path(); let active_bound = if meta.from_reflect().should_auto_derive() { @@ -104,113 +84,125 @@ impl WhereClauseOptions { None }; - Self::new_with_bounds( + Self { meta, - |_| Some(quote!(#type_path_bound #active_bound)), - |_| Some(quote!(#type_path_bound #FQAny + #FQSend + #FQSync)), - ) + additional_bounds: quote!(#type_path_bound #active_bound), + required_bounds: quote!(#type_path_bound #FQAny + #FQSend + #FQSync), + } } /// Create [`WhereClauseOptions`] with the minimum bounds needed to fulfill `TypePath`. - pub fn new_type_path(meta: &ReflectMeta) -> Self { + pub fn new_type_path(meta: &'a ReflectMeta<'b>) -> Self { let bevy_reflect_path = meta.bevy_reflect_path(); - Self::new_with_bounds( + Self { meta, - |_| Some(quote!(#bevy_reflect_path::TypePath)), - |_| Some(quote!(#bevy_reflect_path::TypePath + #FQAny + #FQSend + #FQSync)), - ) + additional_bounds: quote!(#bevy_reflect_path::TypePath), + required_bounds: quote!(#bevy_reflect_path::TypePath + #FQAny + #FQSend + #FQSync), + } } - /// Create [`WhereClauseOptions`] for a struct or enum type. + /// Extends the `where` clause in reflection with additional bounds needed for reflection. /// - /// Compared to [`WhereClauseOptions::new`], this version allows you to specify - /// custom trait bounds for each field. - fn new_with_bounds( - meta: &ReflectMeta, - active_bounds: impl Fn(&TypeParam) -> Option, - ignored_bounds: impl Fn(&TypeParam) -> Option, - ) -> Self { - let mut options = WhereClauseOptions::default(); - - for param in meta.type_path().generics().type_params() { - let ident = param.ident.clone(); - let override_bounds = meta.traits().custom_where().is_some(); - - if override_bounds { - let bounds = ignored_bounds(param).unwrap_or_default(); - - options.ignored_types.push(ident); - options.ignored_trait_bounds.push(bounds); - } else { - let bounds = active_bounds(param).unwrap_or_default(); - - options.active_types.push(ident); - options.active_trait_bounds.push(bounds); - } - } + /// This will only add bounds for generic type parameters. + /// + /// If the container has a `#[reflect(custom_where(...))]` attribute, + /// this method will extend the type parameters with the _required_ bounds. + /// If the attribute is not present, it will extend the type parameters with the _additional_ bounds. + /// + /// The required bounds are the minimum bounds needed for a type to be reflected. + /// These include `TypePath`, `Any`, `Send`, and `Sync`. + /// + /// The additional bounds are added bounds used to enforce that a generic type parameter + /// is itself reflectable. + /// These include `Reflect` and `FromReflect`, as well as `TypePath`. + /// + /// # Example + /// + /// Take the following struct: + /// + /// ```ignore + /// #[derive(Reflect)] + /// struct Foo { + /// a: T, + /// #[reflect(ignore)] + /// b: U + /// } + /// ``` + /// + /// It has type parameters `T` and `U`. + /// + /// Since there is no `#[reflect(custom_where(...))]` attribute, this method will extend the type parameters + /// with the additional bounds: + /// + /// ```ignore + /// where + /// T: FromReflect + TypePath, // additional bounds + /// U: FromReflect + TypePath, // additional bounds + /// ``` + /// + /// If we had this struct: + /// ```ignore + /// #[derive(Reflect)] + /// #[reflect(custom_where(T: FromReflect + Default))] + /// struct Foo { + /// a: T, + /// #[reflect(ignore)] + /// b: U + /// } + /// ``` + /// + /// Since there is a `#[reflect(custom_where(...))]` attribute, this method will extend the type parameters + /// with _just_ the required bounds along with the predicates specified in the attribute: + /// + /// ```ignore + /// where + /// T: FromReflect + Default, // predicates from attribute + /// T: TypePath + Any + Send + Sync, // required bounds + /// U: TypePath + Any + Send + Sync, // required bounds + /// ``` + pub fn extend_where_clause( + &self, + where_clause: Option<&WhereClause>, + ) -> proc_macro2::TokenStream { + // Maintain existing where clause, if any. + let mut generic_where_clause = if let Some(where_clause) = where_clause { + let predicates = where_clause.predicates.iter(); + quote! {where Self: 'static, #(#predicates,)*} + } else { + quote!(where Self: 'static,) + }; + + // Add additional reflection trait bounds + let types = self.type_param_idents(); + let custom_where = self.meta.traits().custom_where(); + let trait_bounds = self.trait_bounds(); - options.custom_where = meta.traits().custom_where().cloned(); + generic_where_clause.extend(quote! { + #(#types: #trait_bounds,)* + #custom_where + }); - options + generic_where_clause } -} -/// Extends the `where` clause in reflection with any additional bounds needed. -/// -/// This is mostly used to add additional bounds to reflected objects with generic types. -/// For reflection purposes, we usually have: -/// * `active_trait_bounds`: `Reflect + TypePath` or `FromReflect + TypePath` -/// * `ignored_trait_bounds`: `TypePath + Any + Send + Sync` -/// -/// # Arguments -/// -/// * `where_clause`: existing `where` clause present on the object to be derived -/// * `where_clause_options`: additional parameters defining which trait bounds to add to the `where` clause -/// -/// # Example -/// -/// The struct: -/// ```ignore -/// #[derive(Reflect)] -/// struct Foo { -/// a: T, -/// #[reflect(ignore)] -/// b: U -/// } -/// ``` -/// will have active types: `[T]` and ignored types: `[U]` -/// -/// The `extend_where_clause` function will yield the following `where` clause: -/// ```ignore -/// where -/// T: Reflect + TypePath, // active_trait_bounds -/// U: TypePath + Any + Send + Sync, // ignored_trait_bounds -/// ``` -pub(crate) fn extend_where_clause( - where_clause: Option<&WhereClause>, - where_clause_options: &WhereClauseOptions, -) -> proc_macro2::TokenStream { - let active_types = &where_clause_options.active_types; - let ignored_types = &where_clause_options.ignored_types; - let active_trait_bounds = &where_clause_options.active_trait_bounds; - let ignored_trait_bounds = &where_clause_options.ignored_trait_bounds; - - let mut generic_where_clause = if let Some(where_clause) = where_clause { - let predicates = where_clause.predicates.iter(); - quote! {where Self: 'static, #(#predicates,)*} - } else { - quote!(where Self: 'static,) - }; - - let custom_where = &where_clause_options.custom_where; - - generic_where_clause.extend(quote! { - #(#active_types: #active_trait_bounds,)* - #(#ignored_types: #ignored_trait_bounds,)* - #custom_where - }); - generic_where_clause + /// Returns the trait bounds to use for all type parameters. + fn trait_bounds(&self) -> &proc_macro2::TokenStream { + if self.meta.traits().custom_where().is_some() { + &self.required_bounds + } else { + &self.additional_bounds + } + } + + /// Returns an iterator of the type parameter idents for the reflected type. + fn type_param_idents(&self) -> impl Iterator { + self.meta + .type_path() + .generics() + .type_params() + .map(|param| ¶m.ident) + } } impl Default for ResultSifter { diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 931bd562345fe..431a2e1093d93 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -2012,7 +2012,7 @@ bevy_reflect::tests::Test { fn can_opt_out_type_path() { #[derive(Reflect)] #[reflect(type_path = false)] - #[reflect(custom_where(T: 'static))] + #[reflect(custom_where())] struct Foo { #[reflect(ignore)] _marker: PhantomData,