diff --git a/crates/bevy_reflect/derive/src/lib.rs b/crates/bevy_reflect/derive/src/lib.rs index 2d9dfca681..7ee7ad83e7 100644 --- a/crates/bevy_reflect/derive/src/lib.rs +++ b/crates/bevy_reflect/derive/src/lib.rs @@ -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: FromReflect + TypePath, +/// // Foo: Any + Send + Sync, +/// // Vec: FromReflect + TypePath + MaybeTyped + RegisterForReflection, /// ``` /// -/// In this case, `Foo` is given the bounds `Vec: FromReflect + TypePath`, +/// In this case, `Foo` is given the bounds `Vec: FromReflect + ...`, /// which requires that `Foo` implements `FromReflect`, /// which requires that `Vec` implements `FromReflect`, /// and so on, resulting in the error. @@ -283,10 +283,10 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre /// // /// // impl bevy_reflect::Reflect for Foo /// // where -/// // Self: Any + Send + Sync, +/// // Foo: Any + Send + Sync, /// // T::Assoc: Default, /// // T: TypePath, -/// // T::Assoc: FromReflect + TypePath, +/// // T::Assoc: FromReflect + TypePath + MaybeTyped + RegisterForReflection, /// // T::Assoc: List, /// // {/* ... */} /// ``` diff --git a/crates/bevy_reflect/derive/src/where_clause_options.rs b/crates/bevy_reflect/derive/src/where_clause_options.rs index e63dbe599a..d2d3b15a44 100644 --- a/crates/bevy_reflect/derive/src/where_clause_options.rs +++ b/crates/bevy_reflect/derive/src/where_clause_options.rs @@ -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: 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 + /// 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: 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 this = self.meta.type_path().true_type(); + let mut generic_where_clause = quote! { where }; - 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. - 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| { - quote!( - #ty : #reflect_bound - + #bevy_reflect_path::TypePath - // Needed for `Typed` impls - + #bevy_reflect_path::MaybeTyped - // Needed for `GetTypeRegistration` impls - + #bevy_reflect_path::__macro_exports::RegisterForReflection - ) + // 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::>(); + + // 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 + // 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 } } - - /// The minimum required bounds for a type to be reflected. - fn required_bounds(&self) -> TokenStream { - quote!(#FQAny + #FQSend + #FQSync) - } } diff --git a/crates/bevy_reflect/src/impls/core/sync.rs b/crates/bevy_reflect/src/impls/core/sync.rs index b4fe8977d5..06b930e8a9 100644 --- a/crates/bevy_reflect/src/impls/core/sync.rs +++ b/crates/bevy_reflect/src/impls/core/sync.rs @@ -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::(); registration.insert::(FromType::::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(::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 { 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); }; } diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index eaf601ef0d..99a0a1f7b5 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -2615,9 +2615,11 @@ bevy_reflect::tests::Test { #[reflect(where T: Default)] struct Foo(String, #[reflect(ignore)] PhantomData); + #[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(String, #[reflect(ignore)] PhantomData); + #[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::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;