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:
|
||||
/// // impl bevy_reflect::Reflect for Foo
|
||||
/// // where
|
||||
/// // Self: Any + Send + Sync,
|
||||
/// // Vec<Foo>: FromReflect + TypePath,
|
||||
/// // Foo: Any + Send + Sync,
|
||||
/// // 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 `Vec<Foo>` implements `FromReflect`,
|
||||
/// 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>
|
||||
/// // where
|
||||
/// // Self: Any + Send + Sync,
|
||||
/// // Foo<T>: Any + Send + Sync,
|
||||
/// // T::Assoc: Default,
|
||||
/// // T: TypePath,
|
||||
/// // T::Assoc: FromReflect + TypePath,
|
||||
/// // T::Assoc: FromReflect + TypePath + MaybeTyped + RegisterForReflection,
|
||||
/// // T::Assoc: List,
|
||||
/// // {/* ... */}
|
||||
/// ```
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::derive_data::ReflectMeta;
|
||||
use bevy_macro_utils::fq_std::{FQAny, FQSend, FQSync};
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro2::{TokenStream, TokenTree};
|
||||
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.
|
||||
pub(crate) struct WhereClauseOptions<'a, 'b> {
|
||||
@ -29,15 +29,24 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
||||
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:
|
||||
/// - `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 `PartialReflect` if `#[reflect(from_reflect = false)]` is present
|
||||
/// or `FromReflect` otherwise (or no bounds at all if `#[reflect(no_field_bounds)]` is present)
|
||||
/// - `Self` has:
|
||||
/// - `Any + Send + Sync` bounds, if generic over types
|
||||
/// - An `Any` bound, if generic over lifetimes but not types
|
||||
/// - 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
|
||||
///
|
||||
@ -55,57 +64,69 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
||||
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||
/// where
|
||||
/// // `Self` bounds:
|
||||
/// Self: Any + Send + Sync,
|
||||
/// Foo<T, U>: Any + Send + Sync,
|
||||
/// // Type parameter bounds:
|
||||
/// T: TypePath,
|
||||
/// U: TypePath,
|
||||
/// // Field bounds
|
||||
/// T: FromReflect + TypePath,
|
||||
/// // Active non-generic field bounds
|
||||
/// 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)
|
||||
/// where
|
||||
/// // `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,
|
||||
/// Foo<T, U>: Any + Send + Sync,
|
||||
/// // Given bounds:
|
||||
/// T: Clone,
|
||||
/// // Type parameter bounds:
|
||||
/// T: TypePath,
|
||||
/// U: TypePath,
|
||||
/// // No active non-generic field bounds
|
||||
/// // Custom bounds
|
||||
/// T: MyTrait,
|
||||
/// ```
|
||||
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
|
||||
// and trait impls for a type's reference (e.g. `impl FromArg for &MyType`)
|
||||
let mut generic_where_clause = quote! { where };
|
||||
|
||||
// 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, });
|
||||
}
|
||||
|
||||
let required_bounds = self.required_bounds();
|
||||
|
||||
// Maintain existing where clause, if any.
|
||||
let mut generic_where_clause = if let Some(where_clause) = where_clause {
|
||||
// Maintain existing where clause bounds, if any.
|
||||
if let Some(where_clause) = where_clause {
|
||||
let predicates = where_clause.predicates.iter();
|
||||
quote! {where #this: #required_bounds, #(#predicates,)*}
|
||||
} else {
|
||||
quote!(where #this: #required_bounds,)
|
||||
};
|
||||
generic_where_clause.extend(quote! { #(#predicates,)* });
|
||||
}
|
||||
|
||||
// Add additional reflection trait bounds
|
||||
// Add additional reflection trait bounds.
|
||||
let predicates = self.predicates();
|
||||
generic_where_clause.extend(quote! {
|
||||
#predicates
|
||||
@ -157,19 +178,57 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
||||
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.
|
||||
// Likewise, `GetTypeRegistration` is always required for active fields since
|
||||
// they are used to register the type's dependencies.
|
||||
Some(self.active_fields.iter().map(move |ty| {
|
||||
// Get the identifiers of all type parameters.
|
||||
let type_param_idents = self
|
||||
.meta
|
||||
.type_path()
|
||||
.generics()
|
||||
.type_params()
|
||||
.map(|type_param| type_param.ident.clone())
|
||||
.collect::<Vec<Ident>>();
|
||||
|
||||
// Do any of the identifiers in `idents` appear in `token_stream`?
|
||||
fn is_any_ident_in_token_stream(idents: &[Ident], token_stream: TokenStream) -> bool {
|
||||
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
|
||||
#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 `GetTypeRegistration` impls
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
/// 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_reflect_derive::impl_type_path;
|
||||
use core::any::Any;
|
||||
use core::fmt;
|
||||
|
||||
macro_rules! impl_reflect_for_atomic {
|
||||
@ -21,10 +20,7 @@ macro_rules! impl_reflect_for_atomic {
|
||||
#[cfg(feature = "functions")]
|
||||
crate::func::macros::impl_function_traits!($ty);
|
||||
|
||||
impl GetTypeRegistration for $ty
|
||||
where
|
||||
$ty: Any + Send + Sync,
|
||||
{
|
||||
impl GetTypeRegistration for $ty {
|
||||
fn get_type_registration() -> TypeRegistration {
|
||||
let mut registration = TypeRegistration::of::<Self>();
|
||||
registration.insert::<ReflectFromPtr>(FromType::<Self>::from_type());
|
||||
@ -42,10 +38,7 @@ macro_rules! impl_reflect_for_atomic {
|
||||
}
|
||||
}
|
||||
|
||||
impl Typed for $ty
|
||||
where
|
||||
$ty: Any + Send + Sync,
|
||||
{
|
||||
impl Typed for $ty {
|
||||
fn type_info() -> &'static TypeInfo {
|
||||
static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
|
||||
CELL.get_or_set(|| {
|
||||
@ -55,10 +48,7 @@ macro_rules! impl_reflect_for_atomic {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialReflect for $ty
|
||||
where
|
||||
$ty: Any + Send + Sync,
|
||||
{
|
||||
impl PartialReflect for $ty {
|
||||
#[inline]
|
||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||
Some(<Self as Typed>::type_info())
|
||||
@ -128,10 +118,7 @@ macro_rules! impl_reflect_for_atomic {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromReflect for $ty
|
||||
where
|
||||
$ty: Any + Send + Sync,
|
||||
{
|
||||
impl FromReflect for $ty {
|
||||
fn from_reflect(reflect: &dyn PartialReflect) -> Option<Self> {
|
||||
Some(<$ty>::new(
|
||||
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)]
|
||||
struct Foo<T>(String, #[reflect(ignore)] PhantomData<T>);
|
||||
|
||||
#[expect(dead_code, reason = "Bar is never constructed")]
|
||||
#[derive(Default, TypePath)]
|
||||
struct Bar;
|
||||
|
||||
#[expect(dead_code, reason = "Baz is never constructed")]
|
||||
#[derive(TypePath)]
|
||||
struct Baz;
|
||||
|
||||
@ -2631,6 +2633,7 @@ bevy_reflect::tests::Test {
|
||||
#[reflect(where)]
|
||||
struct Foo<T>(String, #[reflect(ignore)] PhantomData<T>);
|
||||
|
||||
#[expect(dead_code, reason = "Bar is never constructed")]
|
||||
#[derive(TypePath)]
|
||||
struct Bar;
|
||||
|
||||
@ -2665,6 +2668,7 @@ bevy_reflect::tests::Test {
|
||||
#[reflect(where T::Assoc: core::fmt::Display)]
|
||||
struct Foo<T: Trait>(T::Assoc);
|
||||
|
||||
#[expect(dead_code, reason = "Bar is never constructed")]
|
||||
#[derive(TypePath)]
|
||||
struct Bar;
|
||||
|
||||
@ -2672,6 +2676,7 @@ bevy_reflect::tests::Test {
|
||||
type Assoc = usize;
|
||||
}
|
||||
|
||||
#[expect(dead_code, reason = "Baz is never constructed")]
|
||||
#[derive(TypePath)]
|
||||
struct Baz;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user