bevy_reflect: Avoid trait bounds on non-generic types (#19929)
# Objective All the derived reflection methods currently have multiple trait bounds on non-generic field types, which serve no purpose. The are emitted because "emit bounds on all fields" is easier than "emit bounds on fields that need them". But improving things isn't too hard. Similarly, lots of useless `Any + Send + Sync` bounds exist on non-generic types. Helps a lot with #19873. ## Solution Remove the unnecessary bounds by only emitting them if the relevant type is generic. ## Testing I used `cargo expand` to confirm the unnecessary bounds are no longer produced. `-Zmacro-stats` output tells me this reduces the size of the `Reflect` code produced for `bevy_ui` by 21.2%.
This commit is contained in:
parent
f09c3a1a56
commit
f1df61e458
@ -231,11 +231,11 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre
|
|||||||
/// // Generates a where clause like:
|
/// // Generates a where clause like:
|
||||||
/// // impl bevy_reflect::Reflect for Foo
|
/// // impl bevy_reflect::Reflect for Foo
|
||||||
/// // where
|
/// // where
|
||||||
/// // Self: Any + Send + Sync,
|
/// // Foo: Any + Send + Sync,
|
||||||
/// // Vec<Foo>: FromReflect + TypePath,
|
/// // Vec<Foo>: FromReflect + TypePath + MaybeTyped + RegisterForReflection,
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// In this case, `Foo` is given the bounds `Vec<Foo>: FromReflect + TypePath`,
|
/// In this case, `Foo` is given the bounds `Vec<Foo>: FromReflect + ...`,
|
||||||
/// which requires that `Foo` implements `FromReflect`,
|
/// which requires that `Foo` implements `FromReflect`,
|
||||||
/// which requires that `Vec<Foo>` implements `FromReflect`,
|
/// which requires that `Vec<Foo>` implements `FromReflect`,
|
||||||
/// and so on, resulting in the error.
|
/// and so on, resulting in the error.
|
||||||
@ -283,10 +283,10 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre
|
|||||||
/// //
|
/// //
|
||||||
/// // impl<T: Trait> bevy_reflect::Reflect for Foo<T>
|
/// // impl<T: Trait> bevy_reflect::Reflect for Foo<T>
|
||||||
/// // where
|
/// // where
|
||||||
/// // Self: Any + Send + Sync,
|
/// // Foo<T>: Any + Send + Sync,
|
||||||
/// // T::Assoc: Default,
|
/// // T::Assoc: Default,
|
||||||
/// // T: TypePath,
|
/// // T: TypePath,
|
||||||
/// // T::Assoc: FromReflect + TypePath,
|
/// // T::Assoc: FromReflect + TypePath + MaybeTyped + RegisterForReflection,
|
||||||
/// // T::Assoc: List,
|
/// // T::Assoc: List,
|
||||||
/// // {/* ... */}
|
/// // {/* ... */}
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::derive_data::ReflectMeta;
|
use crate::derive_data::ReflectMeta;
|
||||||
use bevy_macro_utils::fq_std::{FQAny, FQSend, FQSync};
|
use bevy_macro_utils::fq_std::{FQAny, FQSend, FQSync};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::{TokenStream, TokenTree};
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use syn::{punctuated::Punctuated, Token, Type, WhereClause};
|
use syn::{punctuated::Punctuated, Ident, Token, Type, WhereClause};
|
||||||
|
|
||||||
/// Options defining how to extend the `where` clause for reflection.
|
/// Options defining how to extend the `where` clause for reflection.
|
||||||
pub(crate) struct WhereClauseOptions<'a, 'b> {
|
pub(crate) struct WhereClauseOptions<'a, 'b> {
|
||||||
@ -29,15 +29,24 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
|||||||
self.meta
|
self.meta
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extends the `where` clause for a type with additional bounds needed for the reflection impls.
|
/// Extends the `where` clause for a type with additional bounds needed for the reflection
|
||||||
|
/// impls.
|
||||||
///
|
///
|
||||||
/// The default bounds added are as follows:
|
/// The default bounds added are as follows:
|
||||||
/// - `Self` has the bounds `Any + Send + Sync`
|
/// - `Self` has:
|
||||||
/// - Type parameters have the bound `TypePath` unless `#[reflect(type_path = false)]` is present
|
/// - `Any + Send + Sync` bounds, if generic over types
|
||||||
/// - Active fields have the bounds `TypePath` and either `PartialReflect` if `#[reflect(from_reflect = false)]` is present
|
/// - An `Any` bound, if generic over lifetimes but not types
|
||||||
/// or `FromReflect` otherwise (or no bounds at all if `#[reflect(no_field_bounds)]` is present)
|
/// - No bounds, if generic over neither types nor lifetimes
|
||||||
|
/// - Any given bounds in a `where` clause on the type
|
||||||
|
/// - Type parameters have the bound `TypePath` unless `#[reflect(type_path = false)]` is
|
||||||
|
/// present
|
||||||
|
/// - Active fields with non-generic types have the bounds `TypePath`, either `PartialReflect`
|
||||||
|
/// if `#[reflect(from_reflect = false)]` is present or `FromReflect` otherwise,
|
||||||
|
/// `MaybeTyped`, and `RegisterForReflection` (or no bounds at all if
|
||||||
|
/// `#[reflect(no_field_bounds)]` is present)
|
||||||
///
|
///
|
||||||
/// When the derive is used with `#[reflect(where)]`, the bounds specified in the attribute are added as well.
|
/// When the derive is used with `#[reflect(where)]`, the bounds specified in the attribute are
|
||||||
|
/// added as well.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -55,57 +64,69 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
|||||||
/// ```ignore (bevy_reflect is not accessible from this crate)
|
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||||
/// where
|
/// where
|
||||||
/// // `Self` bounds:
|
/// // `Self` bounds:
|
||||||
/// Self: Any + Send + Sync,
|
/// Foo<T, U>: Any + Send + Sync,
|
||||||
/// // Type parameter bounds:
|
/// // Type parameter bounds:
|
||||||
/// T: TypePath,
|
/// T: TypePath,
|
||||||
/// U: TypePath,
|
/// U: TypePath,
|
||||||
/// // Field bounds
|
/// // Active non-generic field bounds
|
||||||
/// T: FromReflect + TypePath,
|
/// T: FromReflect + TypePath + MaybeTyped + RegisterForReflection,
|
||||||
|
///
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// If we had added `#[reflect(where T: MyTrait)]` to the type, it would instead generate:
|
/// If we add various things to the type:
|
||||||
|
///
|
||||||
|
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||||
|
/// #[derive(Reflect)]
|
||||||
|
/// #[reflect(where T: MyTrait)]
|
||||||
|
/// #[reflect(no_field_bounds)]
|
||||||
|
/// struct Foo<T, U>
|
||||||
|
/// where T: Clone
|
||||||
|
/// {
|
||||||
|
/// a: T,
|
||||||
|
/// #[reflect(ignore)]
|
||||||
|
/// b: U
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// It will instead generate the following where clause:
|
||||||
///
|
///
|
||||||
/// ```ignore (bevy_reflect is not accessible from this crate)
|
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||||
/// where
|
/// where
|
||||||
/// // `Self` bounds:
|
/// // `Self` bounds:
|
||||||
/// Self: Any + Send + Sync,
|
/// Foo<T, U>: Any + Send + Sync,
|
||||||
/// // Type parameter bounds:
|
/// // Given bounds:
|
||||||
/// T: TypePath,
|
/// T: Clone,
|
||||||
/// 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:
|
/// // Type parameter bounds:
|
||||||
/// T: TypePath,
|
/// T: TypePath,
|
||||||
/// U: TypePath,
|
/// U: TypePath,
|
||||||
|
/// // No active non-generic field bounds
|
||||||
/// // Custom bounds
|
/// // Custom bounds
|
||||||
/// T: MyTrait,
|
/// T: MyTrait,
|
||||||
/// ```
|
/// ```
|
||||||
pub fn extend_where_clause(&self, where_clause: Option<&WhereClause>) -> TokenStream {
|
pub fn extend_where_clause(&self, where_clause: Option<&WhereClause>) -> TokenStream {
|
||||||
// We would normally just use `Self`, but that won't work for generating things like assertion functions
|
let mut generic_where_clause = quote! { where };
|
||||||
// and trait impls for a type's reference (e.g. `impl FromArg for &MyType`)
|
|
||||||
let this = self.meta.type_path().true_type();
|
|
||||||
|
|
||||||
let required_bounds = self.required_bounds();
|
// Bounds on `Self`. We would normally just use `Self`, but that won't work for generating
|
||||||
|
// things like assertion functions and trait impls for a type's reference (e.g. `impl
|
||||||
|
// FromArg for &MyType`).
|
||||||
|
let generics = self.meta.type_path().generics();
|
||||||
|
if generics.type_params().next().is_some() {
|
||||||
|
// Generic over types? We need `Any + Send + Sync`.
|
||||||
|
let this = self.meta.type_path().true_type();
|
||||||
|
generic_where_clause.extend(quote! { #this: #FQAny + #FQSend + #FQSync, });
|
||||||
|
} else if generics.lifetimes().next().is_some() {
|
||||||
|
// Generic only over lifetimes? We need `'static`.
|
||||||
|
let this = self.meta.type_path().true_type();
|
||||||
|
generic_where_clause.extend(quote! { #this: 'static, });
|
||||||
|
}
|
||||||
|
|
||||||
// Maintain existing where clause, if any.
|
// Maintain existing where clause bounds, if any.
|
||||||
let mut generic_where_clause = if let Some(where_clause) = where_clause {
|
if let Some(where_clause) = where_clause {
|
||||||
let predicates = where_clause.predicates.iter();
|
let predicates = where_clause.predicates.iter();
|
||||||
quote! {where #this: #required_bounds, #(#predicates,)*}
|
generic_where_clause.extend(quote! { #(#predicates,)* });
|
||||||
} else {
|
}
|
||||||
quote!(where #this: #required_bounds,)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add additional reflection trait bounds
|
// Add additional reflection trait bounds.
|
||||||
let predicates = self.predicates();
|
let predicates = self.predicates();
|
||||||
generic_where_clause.extend(quote! {
|
generic_where_clause.extend(quote! {
|
||||||
#predicates
|
#predicates
|
||||||
@ -157,19 +178,57 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
|||||||
let bevy_reflect_path = self.meta.bevy_reflect_path();
|
let bevy_reflect_path = self.meta.bevy_reflect_path();
|
||||||
let reflect_bound = self.reflect_bound();
|
let reflect_bound = self.reflect_bound();
|
||||||
|
|
||||||
// `TypePath` is always required for active fields since they are used to
|
// Get the identifiers of all type parameters.
|
||||||
// construct `NamedField` and `UnnamedField` instances for the `Typed` impl.
|
let type_param_idents = self
|
||||||
// Likewise, `GetTypeRegistration` is always required for active fields since
|
.meta
|
||||||
// they are used to register the type's dependencies.
|
.type_path()
|
||||||
Some(self.active_fields.iter().map(move |ty| {
|
.generics()
|
||||||
quote!(
|
.type_params()
|
||||||
#ty : #reflect_bound
|
.map(|type_param| type_param.ident.clone())
|
||||||
+ #bevy_reflect_path::TypePath
|
.collect::<Vec<Ident>>();
|
||||||
// Needed for `Typed` impls
|
|
||||||
+ #bevy_reflect_path::MaybeTyped
|
// Do any of the identifiers in `idents` appear in `token_stream`?
|
||||||
// Needed for `GetTypeRegistration` impls
|
fn is_any_ident_in_token_stream(idents: &[Ident], token_stream: TokenStream) -> bool {
|
||||||
+ #bevy_reflect_path::__macro_exports::RegisterForReflection
|
for token_tree in token_stream {
|
||||||
)
|
match token_tree {
|
||||||
|
TokenTree::Ident(ident) => {
|
||||||
|
if idents.contains(&ident) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TokenTree::Group(group) => {
|
||||||
|
if is_any_ident_in_token_stream(idents, group.stream()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TokenTree::Punct(_) | TokenTree::Literal(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(self.active_fields.iter().filter_map(move |ty| {
|
||||||
|
// Field type bounds are only required if `ty` is generic. How to determine that?
|
||||||
|
// Search `ty`s token stream for identifiers that match the identifiers from the
|
||||||
|
// function's type params. E.g. if `T` and `U` are the type param identifiers and
|
||||||
|
// `ty` is `Vec<[T; 4]>` then the `T` identifiers match. This is a bit hacky, but
|
||||||
|
// it works.
|
||||||
|
let is_generic =
|
||||||
|
is_any_ident_in_token_stream(&type_param_idents, ty.to_token_stream());
|
||||||
|
|
||||||
|
is_generic.then(|| {
|
||||||
|
quote!(
|
||||||
|
#ty: #reflect_bound
|
||||||
|
// Needed to construct `NamedField` and `UnnamedField` instances for
|
||||||
|
// the `Typed` impl.
|
||||||
|
+ #bevy_reflect_path::TypePath
|
||||||
|
// Needed for `Typed` impls
|
||||||
|
+ #bevy_reflect_path::MaybeTyped
|
||||||
|
// Needed for registering type dependencies in the
|
||||||
|
// `GetTypeRegistration` impl.
|
||||||
|
+ #bevy_reflect_path::__macro_exports::RegisterForReflection
|
||||||
|
)
|
||||||
|
})
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,9 +253,4 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The minimum required bounds for a type to be reflected.
|
|
||||||
fn required_bounds(&self) -> TokenStream {
|
|
||||||
quote!(#FQAny + #FQSend + #FQSync)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use bevy_platform::prelude::*;
|
use bevy_platform::prelude::*;
|
||||||
use bevy_reflect_derive::impl_type_path;
|
use bevy_reflect_derive::impl_type_path;
|
||||||
use core::any::Any;
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
macro_rules! impl_reflect_for_atomic {
|
macro_rules! impl_reflect_for_atomic {
|
||||||
@ -21,10 +20,7 @@ macro_rules! impl_reflect_for_atomic {
|
|||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
crate::func::macros::impl_function_traits!($ty);
|
crate::func::macros::impl_function_traits!($ty);
|
||||||
|
|
||||||
impl GetTypeRegistration for $ty
|
impl GetTypeRegistration for $ty {
|
||||||
where
|
|
||||||
$ty: Any + Send + Sync,
|
|
||||||
{
|
|
||||||
fn get_type_registration() -> TypeRegistration {
|
fn get_type_registration() -> TypeRegistration {
|
||||||
let mut registration = TypeRegistration::of::<Self>();
|
let mut registration = TypeRegistration::of::<Self>();
|
||||||
registration.insert::<ReflectFromPtr>(FromType::<Self>::from_type());
|
registration.insert::<ReflectFromPtr>(FromType::<Self>::from_type());
|
||||||
@ -42,10 +38,7 @@ macro_rules! impl_reflect_for_atomic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Typed for $ty
|
impl Typed for $ty {
|
||||||
where
|
|
||||||
$ty: Any + Send + Sync,
|
|
||||||
{
|
|
||||||
fn type_info() -> &'static TypeInfo {
|
fn type_info() -> &'static TypeInfo {
|
||||||
static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
|
static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
|
||||||
CELL.get_or_set(|| {
|
CELL.get_or_set(|| {
|
||||||
@ -55,10 +48,7 @@ macro_rules! impl_reflect_for_atomic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialReflect for $ty
|
impl PartialReflect for $ty {
|
||||||
where
|
|
||||||
$ty: Any + Send + Sync,
|
|
||||||
{
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||||
Some(<Self as Typed>::type_info())
|
Some(<Self as Typed>::type_info())
|
||||||
@ -128,10 +118,7 @@ macro_rules! impl_reflect_for_atomic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromReflect for $ty
|
impl FromReflect for $ty {
|
||||||
where
|
|
||||||
$ty: Any + Send + Sync,
|
|
||||||
{
|
|
||||||
fn from_reflect(reflect: &dyn PartialReflect) -> Option<Self> {
|
fn from_reflect(reflect: &dyn PartialReflect) -> Option<Self> {
|
||||||
Some(<$ty>::new(
|
Some(<$ty>::new(
|
||||||
reflect.try_downcast_ref::<$ty>()?.load($ordering),
|
reflect.try_downcast_ref::<$ty>()?.load($ordering),
|
||||||
@ -140,7 +127,7 @@ macro_rules! impl_reflect_for_atomic {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
impl_full_reflect!(for $ty where $ty: Any + Send + Sync);
|
impl_full_reflect!(for $ty);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2615,9 +2615,11 @@ bevy_reflect::tests::Test {
|
|||||||
#[reflect(where T: Default)]
|
#[reflect(where T: Default)]
|
||||||
struct Foo<T>(String, #[reflect(ignore)] PhantomData<T>);
|
struct Foo<T>(String, #[reflect(ignore)] PhantomData<T>);
|
||||||
|
|
||||||
|
#[expect(dead_code, reason = "Bar is never constructed")]
|
||||||
#[derive(Default, TypePath)]
|
#[derive(Default, TypePath)]
|
||||||
struct Bar;
|
struct Bar;
|
||||||
|
|
||||||
|
#[expect(dead_code, reason = "Baz is never constructed")]
|
||||||
#[derive(TypePath)]
|
#[derive(TypePath)]
|
||||||
struct Baz;
|
struct Baz;
|
||||||
|
|
||||||
@ -2631,6 +2633,7 @@ bevy_reflect::tests::Test {
|
|||||||
#[reflect(where)]
|
#[reflect(where)]
|
||||||
struct Foo<T>(String, #[reflect(ignore)] PhantomData<T>);
|
struct Foo<T>(String, #[reflect(ignore)] PhantomData<T>);
|
||||||
|
|
||||||
|
#[expect(dead_code, reason = "Bar is never constructed")]
|
||||||
#[derive(TypePath)]
|
#[derive(TypePath)]
|
||||||
struct Bar;
|
struct Bar;
|
||||||
|
|
||||||
@ -2665,6 +2668,7 @@ bevy_reflect::tests::Test {
|
|||||||
#[reflect(where T::Assoc: core::fmt::Display)]
|
#[reflect(where T::Assoc: core::fmt::Display)]
|
||||||
struct Foo<T: Trait>(T::Assoc);
|
struct Foo<T: Trait>(T::Assoc);
|
||||||
|
|
||||||
|
#[expect(dead_code, reason = "Bar is never constructed")]
|
||||||
#[derive(TypePath)]
|
#[derive(TypePath)]
|
||||||
struct Bar;
|
struct Bar;
|
||||||
|
|
||||||
@ -2672,6 +2676,7 @@ bevy_reflect::tests::Test {
|
|||||||
type Assoc = usize;
|
type Assoc = usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(dead_code, reason = "Baz is never constructed")]
|
||||||
#[derive(TypePath)]
|
#[derive(TypePath)]
|
||||||
struct Baz;
|
struct Baz;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user