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:
Gino Valente 2024-01-29 10:54:17 -07:00 committed by GitHub
parent ba2fffef5a
commit 379b9e5cb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 242 additions and 143 deletions

View file

@ -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.

View file

@ -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

View file

@ -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)?;
}

View file

@ -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>] {

View file

@ -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 {

View file

@ -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();

View file

@ -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,

View file

@ -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,

View file

@ -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 = &param.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| &param.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)
}
}

View file

@ -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>,