mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
bevy_reflect: Split #[reflect(where)]
(#11597)
# Objective Revert the changes to type parameter bounds introduced in #9046, improves the `#[reflect(where)]` attribute (also from #9046), and adds the ability to opt out of field bounds. This is based on suggestions by @soqb and discussion on [Discord](https://discord.com/channels/691052431525675048/1002362493634629796/1201227833826103427). ## Solution Reverts the changes to type parameter bounds when deriving `Reflect`, introduced in #9046. This was originally done as a means of fixing a recursion issue (#8965). However, as @soqb pointed out, we could achieve the same result by simply making an opt-out attribute instead of messing with the type parameter bounds. This PR has four main changes: 1. Reverts the type parameter bounds from #9046 2. Includes `TypePath` as a default bound for active fields 3. Changes `#reflect(where)]` to be strictly additive 4. Adds `#reflect(no_field_bounds)]` to opt out of field bounds Change 1 means that, like before, type parameters only receive at most the `TypePath` bound (if `#[reflect(type_path = false)]` is not present) and active fields receive the `Reflect` or `FromReflect` bound. And with Change 2, they will also receive `TypePath` (since it's indirectly required by `Typed` to construct `NamedField` and `UnnamedField` instances). Change 3 was made to make room for Change 4. By splitting out the responsibility of `#reflect(where)]`, we can use it with or without `#reflect(no_field_bounds)]` for various use cases. For example, if we hadn't done this, the following would have failed: ```rust // Since we're not using `#reflect(no_field_bounds)]`, // `T::Assoc` is automatically given the required bounds // of `FromReflect + TypePath` #[derive(Reflect)] #[reflect(where T::Assoc: OtherTrait)] struct Foo<T: MyTrait> { value: T::Assoc, } ``` This provides more flexibility to the user while still letting them add or remove most trait bounds. And to solve the original recursion issue, we can do: ```rust #[derive(Reflect)] #[reflect(no_field_bounds)] // <-- Added struct Foo { foo: Vec<Foo> } ``` #### Bounds All in all, we now have four sets of trait bounds: - `Self` gets the bounds `Any + Send + Sync` - Type parameters get the bound `TypePath`. This can be opted out of with `#[reflect(type_path = false)]` - Active fields get the bounds `TypePath` and `FromReflect`/`Reflect` bounds. This can be opted out of with `#reflect(no_field_bounds)]` - Custom bounds can be added with `#[reflect(where)]` --- ## Changelog - Revert some changes #9046 - `#reflect(where)]` is now strictly additive - Added `#reflect(no_field_bounds)]` attribute to opt out of automatic field trait bounds when deriving `Reflect` - Made the `TypePath` requirement on fields when deriving `Reflect` more explicit ## Migration Guide > [!important] > This PR shouldn't be a breaking change relative to the current version of Bevy (v0.12). And since it removes the breaking parts of #9046, that PR also won't need a migration guide.
This commit is contained in:
parent
ba2fffef5a
commit
379b9e5cb6
10 changed files with 242 additions and 143 deletions
|
@ -122,7 +122,6 @@ impl std::fmt::Debug for StrongHandle {
|
|||
/// [`Handle::Strong`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists).
|
||||
#[derive(Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
#[reflect(where A: Asset)]
|
||||
pub enum Handle<A: Asset> {
|
||||
/// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
|
||||
/// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata.
|
||||
|
|
|
@ -17,7 +17,6 @@ use thiserror::Error;
|
|||
///
|
||||
/// For an "untyped" / "generic-less" id, see [`UntypedAssetId`].
|
||||
#[derive(Reflect)]
|
||||
#[reflect(where A: Asset)]
|
||||
pub enum AssetId<A: Asset> {
|
||||
/// A small / efficient runtime identifier that can be used to efficiently look up an asset stored in [`Assets`]. This is
|
||||
/// the "default" identifier used for assets. The alternative(s) (ex: [`AssetId::Uuid`]) will only be used if assets are
|
||||
|
|
|
@ -25,6 +25,9 @@ const HASH_ATTR: &str = "Hash";
|
|||
// but useful to know exist nonetheless
|
||||
pub(crate) const REFLECT_DEFAULT: &str = "ReflectDefault";
|
||||
|
||||
// Attributes for `Reflect` implementation
|
||||
const NO_FIELD_BOUNDS_ATTR: &str = "no_field_bounds";
|
||||
|
||||
// Attributes for `FromReflect` implementation
|
||||
const FROM_REFLECT_ATTR: &str = "from_reflect";
|
||||
|
||||
|
@ -212,6 +215,7 @@ pub(crate) struct ReflectTraits {
|
|||
from_reflect_attrs: FromReflectAttrs,
|
||||
type_path_attrs: TypePathAttrs,
|
||||
custom_where: Option<WhereClause>,
|
||||
no_field_bounds: bool,
|
||||
idents: Vec<Ident>,
|
||||
}
|
||||
|
||||
|
@ -260,6 +264,9 @@ impl ReflectTraits {
|
|||
HASH_ATTR => {
|
||||
traits.hash.merge(TraitImpl::Implemented(span))?;
|
||||
}
|
||||
NO_FIELD_BOUNDS_ATTR => {
|
||||
traits.no_field_bounds = true;
|
||||
}
|
||||
// We only track reflected idents for traits not considered special
|
||||
_ => {
|
||||
// Create the reflect ident
|
||||
|
@ -422,6 +429,10 @@ impl ReflectTraits {
|
|||
self.custom_where.as_ref()
|
||||
}
|
||||
|
||||
pub fn no_field_bounds(&self) -> bool {
|
||||
self.no_field_bounds
|
||||
}
|
||||
|
||||
/// Merges the trait implementations of this [`ReflectTraits`] with another one.
|
||||
///
|
||||
/// An error is returned if the two [`ReflectTraits`] have conflicting implementations.
|
||||
|
@ -434,6 +445,8 @@ impl ReflectTraits {
|
|||
|
||||
self.merge_custom_where(other.custom_where);
|
||||
|
||||
self.no_field_bounds |= other.no_field_bounds;
|
||||
|
||||
for ident in other.idents {
|
||||
add_unique_ident(&mut self.idents, ident)?;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::container_attributes::{FromReflectAttrs, ReflectTraits};
|
||||
use crate::container_attributes::{FromReflectAttrs, ReflectTraits, TypePathAttrs};
|
||||
use crate::field_attributes::{parse_field_attrs, ReflectFieldAttr};
|
||||
use crate::type_path::parse_path_no_leading_colon;
|
||||
use crate::utility::{StringExpr, WhereClauseOptions};
|
||||
|
@ -402,6 +402,11 @@ impl<'a> ReflectMeta<'a> {
|
|||
self.traits.from_reflect_attrs()
|
||||
}
|
||||
|
||||
/// The `TypePath` attributes on this type.
|
||||
pub fn type_path_attrs(&self) -> &TypePathAttrs {
|
||||
self.traits.type_path_attrs()
|
||||
}
|
||||
|
||||
/// The path to this type.
|
||||
pub fn type_path(&self) -> &ReflectTypePath<'a> {
|
||||
&self.type_path
|
||||
|
@ -480,7 +485,7 @@ impl<'a> ReflectStruct<'a> {
|
|||
}
|
||||
|
||||
pub fn where_clause_options(&self) -> WhereClauseOptions {
|
||||
WhereClauseOptions::new(self.meta())
|
||||
WhereClauseOptions::new_with_fields(self.meta(), self.active_types().into_boxed_slice())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -503,28 +508,33 @@ impl<'a> ReflectEnum<'a> {
|
|||
&self.variants
|
||||
}
|
||||
|
||||
/// Get a collection of types which are exposed to the reflection API
|
||||
pub fn active_types(&self) -> Vec<Type> {
|
||||
self.active_fields()
|
||||
.map(|field| field.data.ty.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 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())
|
||||
}
|
||||
|
||||
pub fn where_clause_options(&self) -> WhereClauseOptions {
|
||||
WhereClauseOptions::new(self.meta())
|
||||
WhereClauseOptions::new_with_fields(self.meta(), self.active_types().into_boxed_slice())
|
||||
}
|
||||
}
|
||||
|
||||
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(|field| field.attrs.ignore.is_active())
|
||||
}
|
||||
|
||||
/// 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(|field| field.attrs.ignore.is_ignored())
|
||||
}
|
||||
|
||||
/// The complete set of fields in this variant.
|
||||
#[allow(dead_code)]
|
||||
pub fn fields(&self) -> &[StructField<'a>] {
|
||||
|
|
|
@ -23,8 +23,7 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream {
|
|||
let type_path = meta.type_path();
|
||||
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 =
|
||||
WhereClauseOptions::new_type_path(meta).extend_where_clause(where_clause);
|
||||
let where_from_reflect_clause = WhereClauseOptions::new(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<Self> {
|
||||
|
@ -50,8 +49,9 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
|
|||
let (impl_generics, ty_generics, where_clause) = enum_path.generics().split_for_impl();
|
||||
|
||||
// Add FromReflect bound for each active field
|
||||
let where_from_reflect_clause =
|
||||
WhereClauseOptions::new(reflect_enum.meta()).extend_where_clause(where_clause);
|
||||
let where_from_reflect_clause = reflect_enum
|
||||
.where_clause_options()
|
||||
.extend_where_clause(where_clause);
|
||||
|
||||
quote! {
|
||||
impl #impl_generics #bevy_reflect_path::FromReflect for #enum_path #ty_generics #where_from_reflect_clause {
|
||||
|
@ -130,8 +130,9 @@ fn impl_struct_internal(
|
|||
.split_for_impl();
|
||||
|
||||
// Add FromReflect bound for each active field
|
||||
let where_from_reflect_clause =
|
||||
WhereClauseOptions::new(reflect_struct.meta()).extend_where_clause(where_clause);
|
||||
let where_from_reflect_clause = reflect_struct
|
||||
.where_clause_options()
|
||||
.extend_where_clause(where_clause);
|
||||
|
||||
quote! {
|
||||
impl #impl_generics #bevy_reflect_path::FromReflect for #struct_path #ty_generics #where_from_reflect_clause {
|
||||
|
|
|
@ -49,7 +49,7 @@ pub(crate) enum TypedProperty {
|
|||
}
|
||||
|
||||
pub(crate) fn impl_type_path(meta: &ReflectMeta) -> proc_macro2::TokenStream {
|
||||
let where_clause_options = WhereClauseOptions::new_type_path(meta);
|
||||
let where_clause_options = WhereClauseOptions::new(meta);
|
||||
|
||||
if !meta.traits().type_path_attrs().should_auto_derive() {
|
||||
return proc_macro2::TokenStream::new();
|
||||
|
|
|
@ -21,7 +21,7 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream {
|
|||
#[cfg(not(feature = "documentation"))]
|
||||
let with_docs: Option<proc_macro2::TokenStream> = None;
|
||||
|
||||
let where_clause_options = WhereClauseOptions::new_type_path(meta);
|
||||
let where_clause_options = WhereClauseOptions::new(meta);
|
||||
let typed_impl = impl_typed(
|
||||
meta,
|
||||
&where_clause_options,
|
||||
|
|
|
@ -131,50 +131,85 @@ pub(crate) static TYPE_NAME_ATTRIBUTE_NAME: &str = "type_name";
|
|||
/// This is useful for when a type can't or shouldn't implement `TypePath`,
|
||||
/// or if a manual implementation is desired.
|
||||
///
|
||||
/// ## `#[reflect(where T: Trait, U::Assoc: Trait, ...)]`
|
||||
/// ## `#[reflect(no_field_bounds)]`
|
||||
///
|
||||
/// By default, the derive macro will automatically add certain trait bounds to all generic type parameters
|
||||
/// in order to make them compatible with reflection without the user needing to add them manually.
|
||||
/// This includes traits like `Reflect` and `FromReflect`.
|
||||
/// However, this may not always be desired, and some type paramaters can't or shouldn't require those bounds
|
||||
/// (i.e. their usages in fields are ignored or they're only used for their associated types).
|
||||
/// This attribute will opt-out of the default trait bounds added to all field types
|
||||
/// for the generated reflection trait impls.
|
||||
///
|
||||
/// With this attribute, you can specify a custom `where` clause to be used instead of the default.
|
||||
/// If this attribute is present, none of the type parameters will receive the default bounds.
|
||||
/// Only the bounds specified by the type itself and by this attribute will be used.
|
||||
/// The only exceptions to this are the `Any`, `Send`, `Sync`, and `TypePath` bounds,
|
||||
/// which will always be added regardless of this attribute due to their necessity for reflection
|
||||
/// in general.
|
||||
///
|
||||
/// This means that if you want to opt-out of the default bounds for _all_ type parameters,
|
||||
/// you can add `#[reflect(where)]` to the container item to indicate
|
||||
/// that an empty `where` clause should be used.
|
||||
/// Normally, all fields will have the bounds `TypePath`, and either `FromReflect` or `Reflect`
|
||||
/// depending on if `#[reflect(from_reflect = false)]` is used.
|
||||
/// However, this might not always be desirable, and so this attribute may be used to remove those bounds.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// If a type is recursive the default bounds will cause an overflow error when building:
|
||||
///
|
||||
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||
/// #[derive(Reflect)] // ERROR: overflow evaluating the requirement `Foo: FromReflect`
|
||||
/// struct Foo {
|
||||
/// foo: Vec<Foo>,
|
||||
/// }
|
||||
///
|
||||
/// // Generates a where clause like:
|
||||
/// // impl bevy_reflect::Reflect for Foo
|
||||
/// // where
|
||||
/// // Self: Any + Send + Sync,
|
||||
/// // Vec<Foo>: FromReflect + TypePath,
|
||||
/// ```
|
||||
///
|
||||
/// In this case, `Foo` is given the bounds `Vec<Foo>: FromReflect + TypePath`,
|
||||
/// which requires that `Foo` implements `FromReflect`,
|
||||
/// which requires that `Vec<Foo>` implements `FromReflect`,
|
||||
/// and so on, resulting in the error.
|
||||
///
|
||||
/// To fix this, we can add `#[reflect(no_field_bounds)]` to `Foo` to remove the bounds on `Vec<Foo>`:
|
||||
///
|
||||
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||
/// #[derive(Reflect)]
|
||||
/// #[reflect(no_field_bounds)]
|
||||
/// struct Foo {
|
||||
/// foo: Vec<Foo>,
|
||||
/// }
|
||||
///
|
||||
/// // Generates a where clause like:
|
||||
/// // impl bevy_reflect::Reflect for Foo
|
||||
/// // where
|
||||
/// // Self: Any + Send + Sync,
|
||||
/// ```
|
||||
///
|
||||
/// ## `#[reflect(where T: Trait, U::Assoc: Trait, ...)]`
|
||||
///
|
||||
/// This attribute can be used to add additional bounds to the generated reflection trait impls.
|
||||
///
|
||||
/// This is useful for when a type needs certain bounds only applied to the reflection impls
|
||||
/// that are not otherwise automatically added by the derive macro.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// In the example below, we want to enforce that `T::Assoc: List` is required in order for
|
||||
/// `Foo<T>` to be reflectable, but we don't want it to prevent `Foo<T>` from being used
|
||||
/// in places where `T::Assoc: List` is not required.
|
||||
///
|
||||
/// ```ignore
|
||||
/// trait Trait {
|
||||
/// type Assoc;
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Reflect)]
|
||||
/// #[reflect(where T::Assoc: FromReflect)]
|
||||
/// #[reflect(where T::Assoc: List)]
|
||||
/// struct Foo<T: Trait> where T::Assoc: Default {
|
||||
/// value: T::Assoc,
|
||||
/// }
|
||||
///
|
||||
/// // Generates a where clause like the following
|
||||
/// // (notice that `T` does not have any `Reflect` or `FromReflect` bounds):
|
||||
/// // Generates a where clause like:
|
||||
/// //
|
||||
/// // impl<T: Trait> bevy_reflect::Reflect for Foo<T>
|
||||
/// // where
|
||||
/// // Self: 'static,
|
||||
/// // Self: Any + Send + Sync,
|
||||
/// // T::Assoc: Default,
|
||||
/// // T: bevy_reflect::TypePath
|
||||
/// // + ::core::any::Any
|
||||
/// // + ::core::marker::Send
|
||||
/// // + ::core::marker::Sync,
|
||||
/// // T::Assoc: FromReflect,
|
||||
/// // T: TypePath,
|
||||
/// // T::Assoc: FromReflect + TypePath,
|
||||
/// // T::Assoc: List,
|
||||
/// // {/* ... */}
|
||||
/// ```
|
||||
///
|
||||
|
@ -191,10 +226,6 @@ pub(crate) static TYPE_NAME_ATTRIBUTE_NAME: &str = "type_name";
|
|||
/// which may be useful for maintaining invariants, keeping certain data private,
|
||||
/// or allowing the use of types that do not implement `Reflect` within the container.
|
||||
///
|
||||
/// If the field contains a generic type parameter, you will likely need to add a
|
||||
/// [`#[reflect(where)]`](#reflectwheret-trait-uassoc-trait-)
|
||||
/// attribute to the container in order to avoid the default bounds being applied to the type parameter.
|
||||
///
|
||||
/// ## `#[reflect(skip_serializing)]`
|
||||
///
|
||||
/// This works similar to `#[reflect(ignore)]`, but rather than opting out of _all_ of reflection,
|
||||
|
|
|
@ -5,9 +5,10 @@ use bevy_macro_utils::{
|
|||
fq_std::{FQAny, FQOption, FQSend, FQSync},
|
||||
BevyManifest,
|
||||
};
|
||||
use proc_macro2::{Ident, Span};
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{spanned::Spanned, LitStr, Member, Path, WhereClause};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{spanned::Spanned, LitStr, Member, Path, Token, Type, WhereClause};
|
||||
|
||||
/// Returns the correct path for `bevy_reflect`.
|
||||
pub(crate) fn get_bevy_reflect_path() -> Path {
|
||||
|
@ -69,64 +70,36 @@ pub(crate) fn ident_or_index(ident: Option<&Ident>, index: usize) -> Member {
|
|||
/// 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,
|
||||
active_fields: Box<[Type]>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
||||
/// Create [`WhereClauseOptions`] for a reflected struct or enum type.
|
||||
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() {
|
||||
quote!(#bevy_reflect_path::FromReflect)
|
||||
} else {
|
||||
quote!(#bevy_reflect_path::Reflect)
|
||||
};
|
||||
|
||||
let type_path_bound = if meta.traits().type_path_attrs().should_auto_derive() {
|
||||
Some(quote!(#bevy_reflect_path::TypePath +))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Self {
|
||||
meta,
|
||||
additional_bounds: quote!(#type_path_bound #active_bound),
|
||||
required_bounds: quote!(#type_path_bound #FQAny + #FQSend + #FQSync),
|
||||
active_fields: Box::new([]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create [`WhereClauseOptions`] with the minimum bounds needed to fulfill `TypePath`.
|
||||
pub fn new_type_path(meta: &'a ReflectMeta<'b>) -> Self {
|
||||
let bevy_reflect_path = meta.bevy_reflect_path();
|
||||
|
||||
pub fn new_with_fields(meta: &'a ReflectMeta<'b>, active_fields: Box<[Type]>) -> Self {
|
||||
Self {
|
||||
meta,
|
||||
additional_bounds: quote!(#bevy_reflect_path::TypePath),
|
||||
required_bounds: quote!(#bevy_reflect_path::TypePath + #FQAny + #FQSend + #FQSync),
|
||||
active_fields,
|
||||
}
|
||||
}
|
||||
|
||||
/// Extends the `where` clause in reflection with additional bounds needed for reflection.
|
||||
/// Extends the `where` clause for a type with additional bounds needed for the reflection impls.
|
||||
///
|
||||
/// This will only add bounds for generic type parameters.
|
||||
/// The default bounds added are as follows:
|
||||
/// - `Self` has the bounds `Any + Send + Sync`
|
||||
/// - Type parameters have the bound `TypePath` unless `#[reflect(type_path = false)]` is present
|
||||
/// - Active fields have the bounds `TypePath` and either `Reflect` if `#[reflect(from_reflect = false)]` is present
|
||||
/// or `FromReflect` otherwise (or no bounds at all if `#[reflect(no_field_bounds)]` is present)
|
||||
///
|
||||
/// If the container has a `#[reflect(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`.
|
||||
/// When the derive is used with `#[reflect(where)]`, the bounds specified in the attribute are added as well.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Take the following struct:
|
||||
///
|
||||
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||
/// #[derive(Reflect)]
|
||||
/// struct Foo<T, U> {
|
||||
|
@ -136,82 +109,145 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
|||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// It has type parameters `T` and `U`.
|
||||
///
|
||||
/// Since there is no `#[reflect(where)]` attribute, this method will extend the type parameters
|
||||
/// with the additional bounds:
|
||||
/// Generates the following where clause:
|
||||
///
|
||||
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||
/// where
|
||||
/// T: FromReflect + TypePath, // additional bounds
|
||||
/// U: FromReflect + TypePath, // additional bounds
|
||||
/// // `Self` bounds:
|
||||
/// Self: Any + Send + Sync,
|
||||
/// // Type parameter bounds:
|
||||
/// T: TypePath,
|
||||
/// U: TypePath,
|
||||
/// // Field bounds
|
||||
/// T: FromReflect + TypePath,
|
||||
/// ```
|
||||
///
|
||||
/// If we had this struct:
|
||||
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||
/// #[derive(Reflect)]
|
||||
/// #[reflect(where T: FromReflect + Default)]
|
||||
/// struct Foo<T, U> {
|
||||
/// a: T,
|
||||
/// #[reflect(ignore)]
|
||||
/// b: U
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Since there is a `#[reflect(where)]` attribute, this method will extend the type parameters
|
||||
/// with _just_ the required bounds along with the predicates specified in the attribute:
|
||||
/// If we had added `#[reflect(where T: MyTrait)]` to the type, it would instead generate:
|
||||
///
|
||||
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||
/// where
|
||||
/// T: FromReflect + Default, // predicates from attribute
|
||||
/// T: TypePath + Any + Send + Sync, // required bounds
|
||||
/// U: TypePath + Any + Send + Sync, // required bounds
|
||||
/// // `Self` bounds:
|
||||
/// Self: Any + Send + Sync,
|
||||
/// // Type parameter bounds:
|
||||
/// T: TypePath,
|
||||
/// U: TypePath,
|
||||
/// // Field bounds
|
||||
/// T: FromReflect + TypePath,
|
||||
/// // Custom bounds
|
||||
/// T: MyTrait,
|
||||
/// ```
|
||||
///
|
||||
/// And if we also added `#[reflect(no_field_bounds)]` to the type, it would instead generate:
|
||||
///
|
||||
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||
/// where
|
||||
/// // `Self` bounds:
|
||||
/// Self: Any + Send + Sync,
|
||||
/// // Type parameter bounds:
|
||||
/// T: TypePath,
|
||||
/// U: TypePath,
|
||||
/// // Custom bounds
|
||||
/// T: MyTrait,
|
||||
/// ```
|
||||
pub fn extend_where_clause(
|
||||
&self,
|
||||
where_clause: Option<&WhereClause>,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let required_bounds = self.required_bounds();
|
||||
// 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,)*}
|
||||
quote! {where Self: #required_bounds, #(#predicates,)*}
|
||||
} else {
|
||||
quote!(where Self: 'static,)
|
||||
quote!(where Self: #required_bounds,)
|
||||
};
|
||||
|
||||
// Add additional reflection trait bounds
|
||||
let types = self.type_param_idents();
|
||||
let custom_where = self
|
||||
.meta
|
||||
.traits()
|
||||
.custom_where()
|
||||
.map(|clause| &clause.predicates);
|
||||
let trait_bounds = self.trait_bounds();
|
||||
|
||||
let predicates = self.predicates();
|
||||
generic_where_clause.extend(quote! {
|
||||
#(#types: #trait_bounds,)*
|
||||
#custom_where
|
||||
#predicates
|
||||
});
|
||||
|
||||
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
|
||||
/// Returns an iterator the where clause predicates to extended the where clause with.
|
||||
fn predicates(&self) -> Punctuated<TokenStream, Token![,]> {
|
||||
let mut predicates = Punctuated::new();
|
||||
|
||||
if let Some(type_param_predicates) = self.type_param_predicates() {
|
||||
predicates.extend(type_param_predicates);
|
||||
}
|
||||
|
||||
if let Some(field_predicates) = self.active_field_predicates() {
|
||||
predicates.extend(field_predicates);
|
||||
}
|
||||
|
||||
if let Some(custom_where) = self.meta.traits().custom_where() {
|
||||
predicates.push(custom_where.predicates.to_token_stream());
|
||||
}
|
||||
|
||||
predicates
|
||||
}
|
||||
|
||||
/// Returns an iterator over the where clause predicates for the type parameters
|
||||
/// if they require one.
|
||||
fn type_param_predicates(&self) -> Option<impl Iterator<Item = TokenStream> + '_> {
|
||||
self.type_path_bound().map(|type_path_bound| {
|
||||
self.meta
|
||||
.type_path()
|
||||
.generics()
|
||||
.type_params()
|
||||
.map(move |param| {
|
||||
let ident = ¶m.ident;
|
||||
|
||||
quote!(#ident : #type_path_bound)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an iterator over the where clause predicates for the active fields.
|
||||
fn active_field_predicates(&self) -> Option<impl Iterator<Item = TokenStream> + '_> {
|
||||
if self.meta.traits().no_field_bounds() {
|
||||
None
|
||||
} else {
|
||||
&self.additional_bounds
|
||||
let bevy_reflect_path = self.meta.bevy_reflect_path();
|
||||
let reflect_bound = self.reflect_bound();
|
||||
|
||||
// `TypePath` is always required for active fields since they are used to
|
||||
// construct `NamedField` and `UnnamedField` instances for the `Typed` impl.
|
||||
Some(
|
||||
self.active_fields
|
||||
.iter()
|
||||
.map(move |ty| quote!(#ty : #reflect_bound + #bevy_reflect_path::TypePath)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator of the type parameter idents for the reflected type.
|
||||
fn type_param_idents(&self) -> impl Iterator<Item = &Ident> {
|
||||
self.meta
|
||||
.type_path()
|
||||
.generics()
|
||||
.type_params()
|
||||
.map(|param| ¶m.ident)
|
||||
/// The `Reflect` or `FromReflect` bound to use based on `#[reflect(from_reflect = false)]`.
|
||||
fn reflect_bound(&self) -> TokenStream {
|
||||
let bevy_reflect_path = self.meta.bevy_reflect_path();
|
||||
|
||||
if self.meta.from_reflect().should_auto_derive() {
|
||||
quote!(#bevy_reflect_path::FromReflect)
|
||||
} else {
|
||||
quote!(#bevy_reflect_path::Reflect)
|
||||
}
|
||||
}
|
||||
|
||||
/// The `TypePath` bounds to use based on `#[reflect(type_path = false)]`.
|
||||
fn type_path_bound(&self) -> Option<TokenStream> {
|
||||
if self.meta.type_path_attrs().should_auto_derive() {
|
||||
let bevy_reflect_path = self.meta.bevy_reflect_path();
|
||||
Some(quote!(#bevy_reflect_path::TypePath))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// The minimum required bounds for a type to be reflected.
|
||||
fn required_bounds(&self) -> proc_macro2::TokenStream {
|
||||
quote!(#FQAny + #FQSend + #FQSync)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1899,8 +1899,8 @@ bevy_reflect::tests::Test {
|
|||
#[test]
|
||||
fn should_allow_multiple_custom_where() {
|
||||
#[derive(Reflect)]
|
||||
#[reflect(where T: Default + FromReflect)]
|
||||
#[reflect(where U: std::ops::Add<T> + FromReflect)]
|
||||
#[reflect(where T: Default)]
|
||||
#[reflect(where U: std::ops::Add<T>)]
|
||||
struct Foo<T, U>(T, U);
|
||||
|
||||
#[derive(Reflect)]
|
||||
|
@ -1916,12 +1916,12 @@ bevy_reflect::tests::Test {
|
|||
#[test]
|
||||
fn should_allow_custom_where_wtih_assoc_type() {
|
||||
trait Trait {
|
||||
type Assoc: FromReflect + TypePath;
|
||||
type Assoc;
|
||||
}
|
||||
|
||||
// We don't need `T` to be `Reflect` since we only care about `T::Assoc`
|
||||
#[derive(Reflect)]
|
||||
#[reflect(where T::Assoc: FromReflect)]
|
||||
#[reflect(where T::Assoc: core::fmt::Display)]
|
||||
struct Foo<T: Trait>(T::Assoc);
|
||||
|
||||
#[derive(TypePath)]
|
||||
|
@ -1931,7 +1931,15 @@ bevy_reflect::tests::Test {
|
|||
type Assoc = usize;
|
||||
}
|
||||
|
||||
#[derive(TypePath)]
|
||||
struct Baz;
|
||||
|
||||
impl Trait for Baz {
|
||||
type Assoc = (f32, f32);
|
||||
}
|
||||
|
||||
assert_impl_all!(Foo<Bar>: Reflect);
|
||||
assert_not_impl_all!(Foo<Baz>: Reflect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1943,6 +1951,7 @@ bevy_reflect::tests::Test {
|
|||
let _ = <Recurse<Recurse<()>> as TypePath>::type_path();
|
||||
|
||||
#[derive(Reflect)]
|
||||
#[reflect(no_field_bounds)]
|
||||
struct SelfRecurse {
|
||||
recurse: Vec<SelfRecurse>,
|
||||
}
|
||||
|
@ -1951,11 +1960,13 @@ bevy_reflect::tests::Test {
|
|||
let _ = <SelfRecurse as TypePath>::type_path();
|
||||
|
||||
#[derive(Reflect)]
|
||||
#[reflect(no_field_bounds)]
|
||||
enum RecurseA {
|
||||
Recurse(RecurseB),
|
||||
}
|
||||
|
||||
#[derive(Reflect)]
|
||||
// `#[reflect(no_field_bounds)]` not needed since already added to `RecurseA`
|
||||
struct RecurseB {
|
||||
vector: Vec<RecurseA>,
|
||||
}
|
||||
|
@ -1970,7 +1981,6 @@ bevy_reflect::tests::Test {
|
|||
fn can_opt_out_type_path() {
|
||||
#[derive(Reflect)]
|
||||
#[reflect(type_path = false)]
|
||||
#[reflect(where)]
|
||||
struct Foo<T> {
|
||||
#[reflect(ignore)]
|
||||
_marker: PhantomData<T>,
|
||||
|
|
Loading…
Reference in a new issue