From abdc01e30705ac45b75715ad8b117f8997876f9e Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 11 May 2024 18:36:44 -0700 Subject: [PATCH 1/9] Add #[reflect(Clone)] container attribute --- .../derive/src/container_attributes.rs | 48 +++++++++++++++++-- crates/bevy_reflect/derive/src/impls/enums.rs | 6 +++ .../bevy_reflect/derive/src/impls/structs.rs | 6 +++ .../derive/src/impls/tuple_structs.rs | 6 +++ .../bevy_reflect/derive/src/impls/values.rs | 5 ++ crates/bevy_reflect/derive/src/lib.rs | 5 ++ crates/bevy_reflect/src/lib.rs | 33 +++++++++++++ crates/bevy_reflect/src/reflect.rs | 10 ++++ 8 files changed, 116 insertions(+), 3 deletions(-) diff --git a/crates/bevy_reflect/derive/src/container_attributes.rs b/crates/bevy_reflect/derive/src/container_attributes.rs index 7b7a12752de11..f93559da384ae 100644 --- a/crates/bevy_reflect/derive/src/container_attributes.rs +++ b/crates/bevy_reflect/derive/src/container_attributes.rs @@ -9,7 +9,7 @@ use crate::custom_attributes::CustomAttributes; use crate::derive_data::ReflectTraitToImpl; use crate::utility; use crate::utility::terminated_parser; -use bevy_macro_utils::fq_std::{FQAny, FQOption}; +use bevy_macro_utils::fq_std::{FQAny, FQBox, FQClone, FQOption}; use proc_macro2::{Ident, Span}; use quote::quote_spanned; use syn::ext::IdentExt; @@ -23,6 +23,7 @@ mod kw { syn::custom_keyword!(Debug); syn::custom_keyword!(PartialEq); syn::custom_keyword!(Hash); + syn::custom_keyword!(Clone); syn::custom_keyword!(no_field_bounds); } @@ -181,6 +182,7 @@ impl TypePathAttrs { /// #[derive(Default, Clone)] pub(crate) struct ContainerAttributes { + clone: TraitImpl, debug: TraitImpl, hash: TraitImpl, partial_eq: TraitImpl, @@ -239,12 +241,14 @@ impl ContainerAttributes { self.parse_type_path(input, trait_) } else if lookahead.peek(kw::no_field_bounds) { self.parse_no_field_bounds(input) + } else if lookahead.peek(kw::Clone) { + self.parse_clone(input) } else if lookahead.peek(kw::Debug) { self.parse_debug(input) - } else if lookahead.peek(kw::PartialEq) { - self.parse_partial_eq(input) } else if lookahead.peek(kw::Hash) { self.parse_hash(input) + } else if lookahead.peek(kw::PartialEq) { + self.parse_partial_eq(input) } else if lookahead.peek(Ident::peek_any) { self.parse_ident(input) } else { @@ -277,6 +281,26 @@ impl ContainerAttributes { Ok(()) } + /// Parse `clone` attribute. + /// + /// Examples: + /// - `#[reflect(Clone)]` + /// - `#[reflect(Clone(custom_clone_fn))]` + fn parse_clone(&mut self, input: ParseStream) -> syn::Result<()> { + let ident = input.parse::()?; + + if input.peek(token::Paren) { + let content; + parenthesized!(content in input); + let path = content.parse::()?; + self.clone.merge(TraitImpl::Custom(path, ident.span))?; + } else { + self.clone = TraitImpl::Implemented(ident.span); + } + + Ok(()) + } + /// Parse special `Debug` registration. /// /// Examples: @@ -513,6 +537,24 @@ impl ContainerAttributes { } } + pub fn get_clone_impl(&self, bevy_reflect_path: &Path) -> Option { + match &self.clone { + &TraitImpl::Implemented(span) => Some(quote_spanned! {span=> + #[inline] + fn reflect_clone(&self) -> #FQOption<#FQBox> { + #FQOption::Some(#FQBox::new(#FQClone::clone(self))) + } + }), + &TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=> + #[inline] + fn reflect_clone(&self) -> #FQOption<#FQBox> { + #FQOption::Some(#FQBox::new(#impl_fn(self))) + } + }), + TraitImpl::NotImplemented => None, + } + } + pub fn custom_attributes(&self) -> &CustomAttributes { &self.custom_attributes } diff --git a/crates/bevy_reflect/derive/src/impls/enums.rs b/crates/bevy_reflect/derive/src/impls/enums.rs index 42162717a4670..b9f156f0a9e50 100644 --- a/crates/bevy_reflect/derive/src/impls/enums.rs +++ b/crates/bevy_reflect/derive/src/impls/enums.rs @@ -33,6 +33,10 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream .. } = TryApplyVariantBuilder::new(reflect_enum).build(&ref_value); + let clone_fn = reflect_enum + .meta() + .attrs() + .get_clone_impl(bevy_reflect_path); let hash_fn = reflect_enum .meta() .attrs() @@ -276,6 +280,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream #bevy_reflect_path::ReflectOwned::Enum(self) } + #clone_fn + #hash_fn #partial_eq_fn diff --git a/crates/bevy_reflect/derive/src/impls/structs.rs b/crates/bevy_reflect/derive/src/impls/structs.rs index 249ac22745b08..fb3b79a4609df 100644 --- a/crates/bevy_reflect/derive/src/impls/structs.rs +++ b/crates/bevy_reflect/derive/src/impls/structs.rs @@ -29,6 +29,10 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS let field_count = field_idents.len(); let field_indices = (0..field_count).collect::>(); + let clone_fn = reflect_struct + .meta() + .attrs() + .get_clone_impl(bevy_reflect_path); let hash_fn = reflect_struct .meta() .attrs() @@ -206,6 +210,8 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS #bevy_reflect_path::ReflectOwned::Struct(self) } + #clone_fn + #hash_fn #partial_eq_fn diff --git a/crates/bevy_reflect/derive/src/impls/tuple_structs.rs b/crates/bevy_reflect/derive/src/impls/tuple_structs.rs index cf43e5fe9874d..ee03af81c63d8 100644 --- a/crates/bevy_reflect/derive/src/impls/tuple_structs.rs +++ b/crates/bevy_reflect/derive/src/impls/tuple_structs.rs @@ -21,6 +21,10 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2:: let where_clause_options = reflect_struct.where_clause_options(); let get_type_registration_impl = reflect_struct.get_type_registration(&where_clause_options); + let clone_fn = reflect_struct + .meta() + .attrs() + .get_clone_impl(bevy_reflect_path); let hash_fn = reflect_struct .meta() .attrs() @@ -174,6 +178,8 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2:: #bevy_reflect_path::ReflectOwned::TupleStruct(self) } + #clone_fn + #hash_fn #partial_eq_fn diff --git a/crates/bevy_reflect/derive/src/impls/values.rs b/crates/bevy_reflect/derive/src/impls/values.rs index c0e7b2d4fee44..ab18cfc585853 100644 --- a/crates/bevy_reflect/derive/src/impls/values.rs +++ b/crates/bevy_reflect/derive/src/impls/values.rs @@ -85,6 +85,11 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream { #FQBox::new(#FQClone::clone(self)) } + #[inline] + fn reflect_clone(&self) -> #FQOption<#FQBox> { + #FQOption::Some(#FQBox::new(#FQClone::clone(self))) + } + #[inline] fn try_apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) -> #FQResult<(), #bevy_reflect_path::ApplyError> { let any = #bevy_reflect_path::Reflect::as_any(value); diff --git a/crates/bevy_reflect/derive/src/lib.rs b/crates/bevy_reflect/derive/src/lib.rs index 86e0069a887fb..3ce9ccc058ae6 100644 --- a/crates/bevy_reflect/derive/src/lib.rs +++ b/crates/bevy_reflect/derive/src/lib.rs @@ -144,6 +144,11 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre /// /// There are a few "special" identifiers that work a bit differently: /// +/// * `#[reflect(Clone)]` will force the implementation of `Reflect::reflect_clone` to rely on +/// the type's [`Clone`] implementation. +/// A custom implementation may be provided using `#[reflect(Clone(my_clone_func))]` where +/// `my_clone_func` is the path to a function matching the signature: +/// `(&self) -> Self`. /// * `#[reflect(Debug)]` will force the implementation of `Reflect::reflect_debug` to rely on /// the type's [`Debug`] implementation. /// A custom implementation may be provided using `#[reflect(Debug(my_debug_func))]` where diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 1ba4d1a3ec3ac..061355d1458ee 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -797,6 +797,39 @@ mod tests { assert_eq!(values, vec![1]); } + #[test] + fn should_reflect_clone() { + #[derive(Reflect, Clone, Debug, PartialEq)] + #[reflect(Clone)] + struct Foo(usize); + + let foo = Foo(123); + let clone = foo.reflect_clone().unwrap(); + assert_eq!(foo, clone.take::().unwrap()); + + #[derive(Reflect, Clone, Debug, PartialEq)] + struct Bar(usize); + + let bar = Bar(123); + let clone = bar.reflect_clone(); + assert!(clone.is_none()); + } + + #[test] + fn should_custom_reflect_clone() { + #[derive(Reflect, Debug, PartialEq)] + #[reflect(Clone(clone_foo))] + struct Foo(usize); + + fn clone_foo(foo: &Foo) -> Foo { + Foo(foo.0 + 198) + } + + let foo = Foo(123); + let clone = foo.reflect_clone().unwrap(); + assert_eq!(Foo(321), clone.take::().unwrap()); + } + #[test] fn should_call_from_reflect_dynamically() { #[derive(Reflect)] diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 43108e61a4278..93bc2807e5d28 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -307,6 +307,16 @@ pub trait Reflect: DynamicTypePath + Any + Send + Sync { /// use those subtraits' respective `clone_dynamic` methods. fn clone_value(&self) -> Box; + /// Attempts to clone `Self` using reflection. + /// + /// Unlike [`Reflect::clone_value`], which often returns a dynamic representation of `Self`, + /// this method attempts create a clone of `Self` directly, if possible. + /// + /// If the clone cannot be performed, `None` is returned. + fn reflect_clone(&self) -> Option> { + None + } + /// Returns a hash of the value (which includes the type). /// /// If the underlying type does not support hashing, returns `None`. From f72ed38351b6f4df129aa74177b1c032dffdf59b Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 11 May 2024 18:49:26 -0700 Subject: [PATCH 2/9] Add `Clone` attribute to reflect impls --- crates/bevy_reflect/src/impls/glam.rs | 76 ++++---- .../bevy_reflect/src/impls/math/direction.rs | 24 ++- .../src/impls/math/primitives2d.rs | 24 +-- .../src/impls/math/primitives3d.rs | 28 +-- crates/bevy_reflect/src/impls/math/rect.rs | 6 +- .../bevy_reflect/src/impls/math/rotation2d.rs | 2 +- crates/bevy_reflect/src/impls/petgraph.rs | 3 +- crates/bevy_reflect/src/impls/smol_str.rs | 2 +- crates/bevy_reflect/src/impls/std.rs | 172 +++++++++++++++--- crates/bevy_reflect/src/impls/uuid.rs | 1 + 10 files changed, 239 insertions(+), 99 deletions(-) diff --git a/crates/bevy_reflect/src/impls/glam.rs b/crates/bevy_reflect/src/impls/glam.rs index 06823374b0c08..3b0d9565d107f 100644 --- a/crates/bevy_reflect/src/impls/glam.rs +++ b/crates/bevy_reflect/src/impls/glam.rs @@ -4,7 +4,7 @@ use bevy_reflect_derive::{impl_reflect, impl_reflect_value}; use glam::*; impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Clone, Debug, Hash, PartialEq, Default)] #[type_path = "glam"] struct IVec2 { x: i32, @@ -12,7 +12,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Clone, Debug, Hash, PartialEq, Default)] #[type_path = "glam"] struct IVec3 { x: i32, @@ -21,7 +21,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Clone, Debug, Hash, PartialEq, Default)] #[type_path = "glam"] struct IVec4 { x: i32, @@ -32,7 +32,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Clone, Debug, Hash, PartialEq, Default)] #[type_path = "glam"] struct I64Vec2 { x: i64, @@ -41,7 +41,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Clone, Debug, Hash, PartialEq, Default)] #[type_path = "glam"] struct I64Vec3 { x: i64, @@ -51,7 +51,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Clone, Debug, Hash, PartialEq, Default)] #[type_path = "glam"] struct I64Vec4 { x: i64, @@ -62,7 +62,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Clone, Debug, Hash, PartialEq, Default)] #[type_path = "glam"] struct UVec2 { x: u32, @@ -70,7 +70,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Clone, Debug, Hash, PartialEq, Default)] #[type_path = "glam"] struct UVec3 { x: u32, @@ -79,7 +79,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Clone, Debug, Hash, PartialEq, Default)] #[type_path = "glam"] struct UVec4 { x: u32, @@ -90,7 +90,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Clone, Debug, Hash, PartialEq, Default)] #[type_path = "glam"] struct U64Vec2 { x: u64, @@ -98,7 +98,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Clone, Debug, Hash, PartialEq, Default)] #[type_path = "glam"] struct U64Vec3 { x: u64, @@ -107,7 +107,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Clone, Debug, Hash, PartialEq, Default)] #[type_path = "glam"] struct U64Vec4 { x: u64, @@ -118,7 +118,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct Vec2 { x: f32, @@ -126,7 +126,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct Vec3 { x: f32, @@ -135,7 +135,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct Vec3A { x: f32, @@ -144,7 +144,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct Vec4 { x: f32, @@ -155,7 +155,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct BVec2 { x: bool, @@ -163,7 +163,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct BVec3 { x: bool, @@ -172,7 +172,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct BVec4 { x: bool, @@ -183,7 +183,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct DVec2 { x: f64, @@ -191,7 +191,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct DVec3 { x: f64, @@ -200,7 +200,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct DVec4 { x: f64, @@ -211,7 +211,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct Mat2 { x_axis: Vec2, @@ -219,7 +219,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct Mat3 { x_axis: Vec3, @@ -228,7 +228,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct Mat3A { x_axis: Vec3A, @@ -237,7 +237,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct Mat4 { x_axis: Vec4, @@ -248,7 +248,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct DMat2 { x_axis: DVec2, @@ -256,7 +256,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct DMat3 { x_axis: DVec3, @@ -265,7 +265,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct DMat4 { x_axis: DVec4, @@ -276,7 +276,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct Affine2 { matrix2: Mat2, @@ -284,7 +284,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct Affine3A { matrix3: Mat3A, @@ -293,7 +293,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct DAffine2 { matrix2: DMat2, @@ -301,7 +301,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct DAffine3 { matrix3: DMat3, @@ -310,7 +310,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct Quat { x: f32, @@ -320,7 +320,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Clone, Debug, PartialEq, Default)] #[type_path = "glam"] struct DQuat { x: f64, @@ -330,6 +330,6 @@ impl_reflect!( } ); -impl_reflect_value!(::glam::EulerRot(Debug, Default)); -impl_reflect_value!(::glam::BVec3A(Debug, Default)); -impl_reflect_value!(::glam::BVec4A(Debug, Default)); +impl_reflect_value!(::glam::EulerRot(Clone, Debug, PartialEq, Default)); +impl_reflect_value!(::glam::BVec3A(Clone, Debug, PartialEq, Default)); +impl_reflect_value!(::glam::BVec4A(Clone, Debug, PartialEq, Default)); diff --git a/crates/bevy_reflect/src/impls/math/direction.rs b/crates/bevy_reflect/src/impls/math/direction.rs index abfd9ff0f8394..307564b067a58 100644 --- a/crates/bevy_reflect/src/impls/math/direction.rs +++ b/crates/bevy_reflect/src/impls/math/direction.rs @@ -2,6 +2,24 @@ use crate as bevy_reflect; use crate::{ReflectDeserialize, ReflectSerialize}; use bevy_reflect_derive::impl_reflect_value; -impl_reflect_value!(::bevy_math::Dir2(Debug, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(::bevy_math::Dir3(Debug, PartialEq, Serialize, Deserialize)); -impl_reflect_value!(::bevy_math::Dir3A(Debug, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(::bevy_math::Dir2( + Clone, + Debug, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_value!(::bevy_math::Dir3( + Clone, + Debug, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_value!(::bevy_math::Dir3A( + Clone, + Debug, + PartialEq, + Serialize, + Deserialize +)); diff --git a/crates/bevy_reflect/src/impls/math/primitives2d.rs b/crates/bevy_reflect/src/impls/math/primitives2d.rs index c9d9a5b50dc09..830d58a80f749 100644 --- a/crates/bevy_reflect/src/impls/math/primitives2d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives2d.rs @@ -4,7 +4,7 @@ use bevy_math::{primitives::*, Dir2, Vec2}; use bevy_reflect_derive::impl_reflect; impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Circle { radius: f32, @@ -12,7 +12,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Ellipse { half_size: Vec2, @@ -20,7 +20,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Annulus { inner_circle: Circle, @@ -29,7 +29,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Plane2d { normal: Dir2, @@ -37,7 +37,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Line2d { direction: Dir2, @@ -45,7 +45,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Segment2d { direction: Dir2, @@ -54,7 +54,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq)] + #[reflect(Clone, Debug, PartialEq)] #[type_path = "bevy_math::primitives"] struct Polyline2d { vertices: [Vec2; N], @@ -62,7 +62,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Triangle2d { vertices: [Vec2; 3], @@ -70,7 +70,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Rectangle { half_size: Vec2, @@ -78,7 +78,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq)] + #[reflect(Clone, Debug, PartialEq)] #[type_path = "bevy_math::primitives"] struct Polygon { vertices: [Vec2; N], @@ -86,7 +86,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct RegularPolygon { circumcircle: Circle, @@ -95,7 +95,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Capsule2d { radius: f32, diff --git a/crates/bevy_reflect/src/impls/math/primitives3d.rs b/crates/bevy_reflect/src/impls/math/primitives3d.rs index 47d9ecbb60d0c..a068915fb2791 100644 --- a/crates/bevy_reflect/src/impls/math/primitives3d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives3d.rs @@ -4,7 +4,7 @@ use bevy_math::{primitives::*, Dir3, Vec2, Vec3}; use bevy_reflect_derive::impl_reflect; impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Sphere { radius: f32, @@ -12,7 +12,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Plane3d { normal: Dir3, @@ -21,7 +21,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct InfinitePlane3d { normal: Dir3, @@ -29,7 +29,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Line3d { direction: Dir3, @@ -37,7 +37,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Segment3d { direction: Dir3, @@ -46,7 +46,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq)] + #[reflect(Clone, Debug, PartialEq)] #[type_path = "bevy_math::primitives"] struct Polyline3d { vertices: [Vec3; N], @@ -54,7 +54,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Triangle3d { vertices: [Vec3; 3], @@ -62,7 +62,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Cuboid { half_size: Vec3, @@ -70,7 +70,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Cylinder { radius: f32, @@ -79,7 +79,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Capsule3d { radius: f32, @@ -88,7 +88,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Cone { radius: f32, @@ -97,7 +97,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct ConicalFrustum { radius_top: f32, @@ -107,7 +107,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Torus { minor_radius: f32, @@ -116,7 +116,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Tetrahedron { vertices: [Vec3; 4], diff --git a/crates/bevy_reflect/src/impls/math/rect.rs b/crates/bevy_reflect/src/impls/math/rect.rs index 4389e0be1920f..7d5600f06655d 100644 --- a/crates/bevy_reflect/src/impls/math/rect.rs +++ b/crates/bevy_reflect/src/impls/math/rect.rs @@ -5,7 +5,7 @@ use bevy_math::{IRect, IVec2, Rect, URect, UVec2, Vec2}; use bevy_reflect_derive::impl_reflect; impl_reflect!( - #[reflect(Debug, PartialEq, Hash, Serialize, Deserialize, Default)] + #[reflect(Clone, Debug, PartialEq, Hash, Serialize, Deserialize, Default)] #[type_path = "bevy_math"] struct IRect { min: IVec2, @@ -14,7 +14,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] #[type_path = "bevy_math"] struct Rect { min: Vec2, @@ -23,7 +23,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Hash, Serialize, Deserialize, Default)] + #[reflect(Clone, Debug, PartialEq, Hash, Serialize, Deserialize, Default)] #[type_path = "bevy_math"] struct URect { min: UVec2, diff --git a/crates/bevy_reflect/src/impls/math/rotation2d.rs b/crates/bevy_reflect/src/impls/math/rotation2d.rs index 4082e1bff5dee..6524d84593b81 100644 --- a/crates/bevy_reflect/src/impls/math/rotation2d.rs +++ b/crates/bevy_reflect/src/impls/math/rotation2d.rs @@ -4,7 +4,7 @@ use bevy_math::Rotation2d; use bevy_reflect_derive::impl_reflect; impl_reflect!( - #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Clone, Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math"] struct Rotation2d { cos: f32, diff --git a/crates/bevy_reflect/src/impls/petgraph.rs b/crates/bevy_reflect/src/impls/petgraph.rs index a4d02b4c87e74..9cbcc19893b6a 100644 --- a/crates/bevy_reflect/src/impls/petgraph.rs +++ b/crates/bevy_reflect/src/impls/petgraph.rs @@ -4,6 +4,7 @@ use crate::{ }; impl_reflect_value!(::petgraph::graph::NodeIndex( + Clone, Default, Serialize, Deserialize @@ -12,4 +13,4 @@ impl_reflect_value!(::petgraph::graph::DiGraph< N: ::std::clone::Clone, E: ::std::clone::Clone, Ix: ::petgraph::graph::IndexType ->()); +>(Clone)); diff --git a/crates/bevy_reflect/src/impls/smol_str.rs b/crates/bevy_reflect/src/impls/smol_str.rs index 0375c1544f238..8c1d13e8b8ae6 100644 --- a/crates/bevy_reflect/src/impls/smol_str.rs +++ b/crates/bevy_reflect/src/impls/smol_str.rs @@ -2,7 +2,7 @@ use crate::std_traits::ReflectDefault; use crate::{self as bevy_reflect}; use bevy_reflect_derive::impl_reflect_value; -impl_reflect_value!(::smol_str::SmolStr(Debug, Hash, PartialEq, Default)); +impl_reflect_value!(::smol_str::SmolStr(Clone, Debug, Hash, PartialEq, Default)); #[cfg(test)] mod tests { diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index bcee1b51ee6e8..8327ee920de89 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -21,6 +21,7 @@ use std::{ }; impl_reflect_value!(bool( + Clone, Debug, Hash, PartialEq, @@ -29,6 +30,43 @@ impl_reflect_value!(bool( Default )); impl_reflect_value!(char( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_value!(u8( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_value!(u16( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_value!(u32( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_value!(u64( + Clone, Debug, Hash, PartialEq, @@ -36,11 +74,8 @@ impl_reflect_value!(char( Deserialize, Default )); -impl_reflect_value!(u8(Debug, Hash, PartialEq, Serialize, Deserialize, Default)); -impl_reflect_value!(u16(Debug, Hash, PartialEq, Serialize, Deserialize, Default)); -impl_reflect_value!(u32(Debug, Hash, PartialEq, Serialize, Deserialize, Default)); -impl_reflect_value!(u64(Debug, Hash, PartialEq, Serialize, Deserialize, Default)); impl_reflect_value!(u128( + Clone, Debug, Hash, PartialEq, @@ -49,6 +84,43 @@ impl_reflect_value!(u128( Default )); impl_reflect_value!(usize( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_value!(i8( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_value!(i16( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_value!(i32( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_value!(i64( + Clone, Debug, Hash, PartialEq, @@ -56,11 +128,8 @@ impl_reflect_value!(usize( Deserialize, Default )); -impl_reflect_value!(i8(Debug, Hash, PartialEq, Serialize, Deserialize, Default)); -impl_reflect_value!(i16(Debug, Hash, PartialEq, Serialize, Deserialize, Default)); -impl_reflect_value!(i32(Debug, Hash, PartialEq, Serialize, Deserialize, Default)); -impl_reflect_value!(i64(Debug, Hash, PartialEq, Serialize, Deserialize, Default)); impl_reflect_value!(i128( + Clone, Debug, Hash, PartialEq, @@ -69,6 +138,7 @@ impl_reflect_value!(i128( Default )); impl_reflect_value!(isize( + Clone, Debug, Hash, PartialEq, @@ -76,10 +146,25 @@ impl_reflect_value!(isize( Deserialize, Default )); -impl_reflect_value!(f32(Debug, PartialEq, Serialize, Deserialize, Default)); -impl_reflect_value!(f64(Debug, PartialEq, Serialize, Deserialize, Default)); +impl_reflect_value!(f32( + Clone, + Debug, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_value!(f64( + Clone, + Debug, + PartialEq, + Serialize, + Deserialize, + Default +)); impl_type_path!(str); impl_reflect_value!(::alloc::string::String( + Clone, Debug, Hash, PartialEq, @@ -88,6 +173,7 @@ impl_reflect_value!(::alloc::string::String( Default )); impl_reflect_value!(::std::path::PathBuf( + Clone, Debug, Hash, PartialEq, @@ -95,17 +181,18 @@ impl_reflect_value!(::std::path::PathBuf( Deserialize, Default )); -impl_reflect_value!(::std::any::TypeId(Debug, Hash, PartialEq,)); -impl_reflect_value!(::std::collections::BTreeSet()); -impl_reflect_value!(::std::collections::HashSet()); -impl_reflect_value!(::bevy_utils::hashbrown::HashSet()); -impl_reflect_value!(::core::ops::Range()); -impl_reflect_value!(::core::ops::RangeInclusive()); -impl_reflect_value!(::core::ops::RangeFrom()); -impl_reflect_value!(::core::ops::RangeTo()); -impl_reflect_value!(::core::ops::RangeToInclusive()); -impl_reflect_value!(::core::ops::RangeFull()); +impl_reflect_value!(::std::any::TypeId(Clone, Debug, Hash, PartialEq,)); +impl_reflect_value!(::std::collections::BTreeSet(Clone)); +impl_reflect_value!(::std::collections::HashSet(Clone)); +impl_reflect_value!(::bevy_utils::hashbrown::HashSet(Clone)); +impl_reflect_value!(::core::ops::Range(Clone)); +impl_reflect_value!(::core::ops::RangeInclusive(Clone)); +impl_reflect_value!(::core::ops::RangeFrom(Clone)); +impl_reflect_value!(::core::ops::RangeTo(Clone)); +impl_reflect_value!(::core::ops::RangeToInclusive(Clone)); +impl_reflect_value!(::core::ops::RangeFull(Clone)); impl_reflect_value!(::bevy_utils::Duration( + Clone, Debug, Hash, PartialEq, @@ -113,8 +200,9 @@ impl_reflect_value!(::bevy_utils::Duration( Deserialize, Default )); -impl_reflect_value!(::bevy_utils::Instant(Debug, Hash, PartialEq)); +impl_reflect_value!(::bevy_utils::Instant(Clone, Debug, Hash, PartialEq)); impl_reflect_value!(::core::num::NonZeroI128( + Clone, Debug, Hash, PartialEq, @@ -122,6 +210,7 @@ impl_reflect_value!(::core::num::NonZeroI128( Deserialize )); impl_reflect_value!(::core::num::NonZeroU128( + Clone, Debug, Hash, PartialEq, @@ -129,6 +218,7 @@ impl_reflect_value!(::core::num::NonZeroU128( Deserialize )); impl_reflect_value!(::core::num::NonZeroIsize( + Clone, Debug, Hash, PartialEq, @@ -136,6 +226,7 @@ impl_reflect_value!(::core::num::NonZeroIsize( Deserialize )); impl_reflect_value!(::core::num::NonZeroUsize( + Clone, Debug, Hash, PartialEq, @@ -143,6 +234,7 @@ impl_reflect_value!(::core::num::NonZeroUsize( Deserialize )); impl_reflect_value!(::core::num::NonZeroI64( + Clone, Debug, Hash, PartialEq, @@ -150,6 +242,7 @@ impl_reflect_value!(::core::num::NonZeroI64( Deserialize )); impl_reflect_value!(::core::num::NonZeroU64( + Clone, Debug, Hash, PartialEq, @@ -157,6 +250,7 @@ impl_reflect_value!(::core::num::NonZeroU64( Deserialize )); impl_reflect_value!(::core::num::NonZeroU32( + Clone, Debug, Hash, PartialEq, @@ -164,6 +258,7 @@ impl_reflect_value!(::core::num::NonZeroU32( Deserialize )); impl_reflect_value!(::core::num::NonZeroI32( + Clone, Debug, Hash, PartialEq, @@ -171,6 +266,7 @@ impl_reflect_value!(::core::num::NonZeroI32( Deserialize )); impl_reflect_value!(::core::num::NonZeroI16( + Clone, Debug, Hash, PartialEq, @@ -178,6 +274,7 @@ impl_reflect_value!(::core::num::NonZeroI16( Deserialize )); impl_reflect_value!(::core::num::NonZeroU16( + Clone, Debug, Hash, PartialEq, @@ -185,6 +282,7 @@ impl_reflect_value!(::core::num::NonZeroU16( Deserialize )); impl_reflect_value!(::core::num::NonZeroU8( + Clone, Debug, Hash, PartialEq, @@ -192,20 +290,22 @@ impl_reflect_value!(::core::num::NonZeroU8( Deserialize )); impl_reflect_value!(::core::num::NonZeroI8( + Clone, Debug, Hash, PartialEq, Serialize, Deserialize )); -impl_reflect_value!(::core::num::Wrapping()); -impl_reflect_value!(::core::num::Saturating()); -impl_reflect_value!(::std::sync::Arc); +impl_reflect_value!(::core::num::Wrapping(Clone)); +impl_reflect_value!(::core::num::Saturating(Clone)); +impl_reflect_value!(::std::sync::Arc(Clone)); // `Serialize` and `Deserialize` only for platforms supported by serde: // https://github.com/serde-rs/serde/blob/3ffb86fc70efd3d329519e2dddfa306cc04f167c/serde/src/de/impls.rs#L1732 #[cfg(any(unix, windows))] impl_reflect_value!(::std::ffi::OsString( + Clone, Debug, Hash, PartialEq, @@ -213,8 +313,8 @@ impl_reflect_value!(::std::ffi::OsString( Deserialize )); #[cfg(not(any(unix, windows)))] -impl_reflect_value!(::std::ffi::OsString(Debug, Hash, PartialEq)); -impl_reflect_value!(::alloc::collections::BinaryHeap); +impl_reflect_value!(::std::ffi::OsString(Clone, Debug, Hash, PartialEq)); +impl_reflect_value!(::alloc::collections::BinaryHeap(Clone)); impl_type_path!(::bevy_utils::NoOpHash); impl_type_path!(::bevy_utils::EntityHash); @@ -1119,6 +1219,10 @@ impl Reflect for Cow<'static, str> { Box::new(self.clone()) } + fn reflect_clone(&self) -> Option> { + Some(Box::new(self.clone())) + } + fn reflect_hash(&self) -> Option { let mut hasher = reflect_hasher(); Hash::hash(&std::any::Any::type_id(self), &mut hasher); @@ -1304,6 +1408,10 @@ impl Reflect for Cow<'s Box::new(List::clone_dynamic(self)) } + fn reflect_clone(&self) -> Option> { + Some(Box::new(self.clone())) + } + fn reflect_hash(&self) -> Option { crate::list_hash(self) } @@ -1409,6 +1517,10 @@ impl Reflect for &'static str { Box::new(*self) } + fn reflect_clone(&self) -> Option> { + Some(Box::new(*self)) + } + fn reflect_hash(&self) -> Option { let mut hasher = reflect_hasher(); Hash::hash(&std::any::Any::type_id(self), &mut hasher); @@ -1519,6 +1631,10 @@ impl Reflect for &'static Path { Box::new(*self) } + fn reflect_clone(&self) -> Option> { + Some(Box::new(*self)) + } + fn reflect_hash(&self) -> Option { let mut hasher = reflect_hasher(); Hash::hash(&std::any::Any::type_id(self), &mut hasher); @@ -1624,6 +1740,10 @@ impl Reflect for Cow<'static, Path> { Box::new(self.clone()) } + fn reflect_clone(&self) -> Option> { + Some(Box::new(self.clone())) + } + fn reflect_hash(&self) -> Option { let mut hasher = reflect_hasher(); Hash::hash(&std::any::Any::type_id(self), &mut hasher); diff --git a/crates/bevy_reflect/src/impls/uuid.rs b/crates/bevy_reflect/src/impls/uuid.rs index f845dda798c15..0b0242a859af7 100644 --- a/crates/bevy_reflect/src/impls/uuid.rs +++ b/crates/bevy_reflect/src/impls/uuid.rs @@ -7,6 +7,7 @@ impl_reflect_value!(::uuid::Uuid( Serialize, Deserialize, Default, + Clone, Debug, PartialEq, Hash From 07a7401bde33449bb318091b009afd98aad9ee74 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 18 May 2024 16:24:28 -0700 Subject: [PATCH 3/9] Support #[reflect(clone)] on fields --- crates/bevy_reflect/derive/src/derive_data.rs | 76 ++++++- .../bevy_reflect/derive/src/enum_utility.rs | 193 ++++++++++++++---- .../derive/src/field_attributes.rs | 38 ++++ crates/bevy_reflect/derive/src/impls/enums.rs | 5 +- .../bevy_reflect/derive/src/impls/structs.rs | 5 +- .../derive/src/impls/tuple_structs.rs | 5 +- crates/bevy_reflect/derive/src/lib.rs | 20 +- crates/bevy_reflect/src/lib.rs | 126 +++++++++++- 8 files changed, 400 insertions(+), 68 deletions(-) diff --git a/crates/bevy_reflect/derive/src/derive_data.rs b/crates/bevy_reflect/derive/src/derive_data.rs index 77b33396519c3..ef75d03ab689d 100644 --- a/crates/bevy_reflect/derive/src/derive_data.rs +++ b/crates/bevy_reflect/derive/src/derive_data.rs @@ -2,17 +2,19 @@ use core::fmt; use proc_macro2::Span; use crate::container_attributes::{ContainerAttributes, FromReflectAttrs, TypePathAttrs}; -use crate::field_attributes::FieldAttributes; +use crate::field_attributes::{CloneBehavior, FieldAttributes}; use crate::type_path::parse_path_no_leading_colon; -use crate::utility::{StringExpr, WhereClauseOptions}; +use crate::utility::{ident_or_index, StringExpr, WhereClauseOptions}; use quote::{quote, ToTokens}; use syn::token::Comma; +use crate::enum_utility::{EnumVariantOutputData, ReflectCloneVariantBuilder, VariantBuilder}; use crate::serialization::SerializationDataDef; use crate::{ utility, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME, TYPE_NAME_ATTRIBUTE_NAME, TYPE_PATH_ATTRIBUTE_NAME, }; +use bevy_macro_utils::fq_std::{FQBox, FQClone, FQOption}; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::{ @@ -622,6 +624,51 @@ impl<'a> ReflectStruct<'a> { #bevy_reflect_path::TypeInfo::#info_variant(#info) } } + /// Returns the `Reflect::reflect_clone` impl, if any, as a `TokenStream`. + pub fn get_clone_impl(&self) -> Option { + let bevy_reflect_path = self.meta().bevy_reflect_path(); + + if let container_clone @ Some(_) = self.meta().attrs().get_clone_impl(bevy_reflect_path) { + return container_clone; + } + + let mut tokens = proc_macro2::TokenStream::new(); + + for field in self.fields() { + let member = ident_or_index(field.data.ident.as_ref(), field.declaration_index); + + match &field.attrs.clone { + CloneBehavior::Default => { + if field.attrs.ignore.is_ignored() { + return None; + } + + tokens.extend(quote! { + #member: #bevy_reflect_path::Reflect::reflect_clone(&self.#member)?.take().ok()?, + }); + } + CloneBehavior::Trait => { + tokens.extend(quote! { + #member: #FQClone::clone(&self.#member), + }); + } + CloneBehavior::Func(clone_fn) => { + tokens.extend(quote! { + #member: #clone_fn(&self.#member), + }); + } + } + } + + Some(quote! { + #[inline] + fn reflect_clone(&self) -> #FQOption<#FQBox> { + #FQOption::Some(#FQBox::new(Self { + #tokens + })) + } + }) + } } impl<'a> ReflectEnum<'a> { @@ -711,6 +758,31 @@ impl<'a> ReflectEnum<'a> { #bevy_reflect_path::TypeInfo::Enum(#info) } } + + /// Returns the `Reflect::reflect_clone` impl, if any, as a `TokenStream`. + pub fn get_clone_impl(&self) -> Option { + let bevy_reflect_path = self.meta().bevy_reflect_path(); + + if let container_clone @ Some(_) = self.meta().attrs().get_clone_impl(bevy_reflect_path) { + return container_clone; + } + + let this = Ident::new("self", Span::call_site()); + let EnumVariantOutputData { + variant_patterns, + variant_constructors, + .. + } = ReflectCloneVariantBuilder::new(self).build(&this)?; + + Some(quote! { + #[inline] + fn reflect_clone(&#this) -> #FQOption<#FQBox> { + #FQOption::Some(#FQBox::new(match #this { + #(#variant_patterns => #variant_constructors),* + })) + } + }) + } } impl<'a> EnumVariant<'a> { diff --git a/crates/bevy_reflect/derive/src/enum_utility.rs b/crates/bevy_reflect/derive/src/enum_utility.rs index 9f7a12aa70df7..571a52f0b1147 100644 --- a/crates/bevy_reflect/derive/src/enum_utility.rs +++ b/crates/bevy_reflect/derive/src/enum_utility.rs @@ -1,7 +1,7 @@ use crate::derive_data::StructField; -use crate::field_attributes::DefaultBehavior; +use crate::field_attributes::{CloneBehavior, DefaultBehavior}; use crate::{derive_data::ReflectEnum, utility::ident_or_index}; -use bevy_macro_utils::fq_std::{FQDefault, FQOption}; +use bevy_macro_utils::fq_std::{FQClone, FQDefault, FQOption}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; @@ -10,6 +10,10 @@ pub(crate) struct EnumVariantOutputData { /// /// For example, `Some` and `None` for the `Option` enum. pub variant_names: Vec, + /// The pattern matching portion of each variant. + /// + /// For example, `Option::Some { 0: _0 }` and `Option::None {}` for the `Option` enum. + pub variant_patterns: Vec, /// The constructor portion of each variant. /// /// For example, `Option::Some { 0: value }` and `Option::None {}` for the `Option` enum. @@ -30,6 +34,9 @@ pub(crate) struct VariantField<'a, 'b> { /// Trait used to control how enum variants are built. pub(crate) trait VariantBuilder: Sized { + /// The variant output data. + type Output; + /// Returns the enum data. fn reflect_enum(&self) -> &ReflectEnum; @@ -114,67 +121,83 @@ pub(crate) trait VariantBuilder: Sized { /// Returns a token stream that constructs an instance of an ignored field. /// + /// If the ignored field cannot be adequately handled, `None` should be returned. + /// /// # Parameters /// * `field`: The field to access - fn on_ignored_field(&self, field: VariantField) -> TokenStream { - match &field.field.attrs.default { + fn on_ignored_field(&self, field: VariantField) -> Option { + Some(match &field.field.attrs.default { DefaultBehavior::Func(path) => quote! { #path() }, _ => quote! { #FQDefault::default() }, - } + }) } /// Builds the enum variant output data. - fn build(&self, this: &Ident) -> EnumVariantOutputData { - let variants = self.reflect_enum().variants(); - - let mut variant_names = Vec::with_capacity(variants.len()); - let mut variant_constructors = Vec::with_capacity(variants.len()); + fn build(&self, this: &Ident) -> Self::Output; +} - for variant in variants { - let variant_ident = &variant.data.ident; - let variant_name = variant_ident.to_string(); - let variant_path = self.reflect_enum().get_unit(variant_ident); +fn build(builder: &V, this: &Ident) -> Option { + let variants = builder.reflect_enum().variants(); - let fields = variant.fields(); + let mut variant_names = Vec::with_capacity(variants.len()); + let mut variant_patterns = Vec::with_capacity(variants.len()); + let mut variant_constructors = Vec::with_capacity(variants.len()); - let field_constructors = fields.iter().map(|field| { - let member = ident_or_index(field.data.ident.as_ref(), field.declaration_index); - let alias = format_ident!("_{}", member); + for variant in variants { + let variant_ident = &variant.data.ident; + let variant_name = variant_ident.to_string(); + let variant_path = builder.reflect_enum().get_unit(variant_ident); - let variant_field = VariantField { - alias: &alias, - variant_name: &variant_name, - field, - }; + let fields = variant.fields(); - let value = if field.attrs.ignore.is_ignored() { - self.on_ignored_field(variant_field) - } else { - self.on_active_field(this, variant_field) - }; + let mut field_patterns = Vec::with_capacity(fields.len()); + let mut field_constructors = Vec::with_capacity(fields.len()); - let constructor = quote! { - #member: #value - }; + for field in fields { + let member = ident_or_index(field.data.ident.as_ref(), field.declaration_index); + let alias = format_ident!("_{}", member); - constructor - }); + let variant_field = VariantField { + alias: &alias, + variant_name: &variant_name, + field, + }; - let constructor = quote! { - #variant_path { - #( #field_constructors ),* - } + let value = if field.attrs.ignore.is_ignored() { + builder.on_ignored_field(variant_field)? + } else { + builder.on_active_field(this, variant_field) }; - variant_names.push(variant_name); - variant_constructors.push(constructor); - } + field_patterns.push(quote! { + #member: #alias + }); - EnumVariantOutputData { - variant_names, - variant_constructors, + field_constructors.push(quote! { + #member: #value + }); } + + let pattern = quote! { + #variant_path { #( #field_patterns ),* } + }; + + let constructor = quote! { + #variant_path { + #( #field_constructors ),* + } + }; + + variant_names.push(variant_name); + variant_patterns.push(pattern); + variant_constructors.push(constructor); } + + Some(EnumVariantOutputData { + variant_names, + variant_patterns, + variant_constructors, + }) } /// Generates the enum variant output data needed to build the `FromReflect::from_reflect` implementation. @@ -189,6 +212,8 @@ impl<'a> FromReflectVariantBuilder<'a> { } impl<'a> VariantBuilder for FromReflectVariantBuilder<'a> { + type Output = EnumVariantOutputData; + fn reflect_enum(&self) -> &ReflectEnum { self.reflect_enum } @@ -207,6 +232,10 @@ impl<'a> VariantBuilder for FromReflectVariantBuilder<'a> { <#field_ty as #bevy_reflect_path::FromReflect>::from_reflect(#alias)? } } + + fn build(&self, this: &Ident) -> Self::Output { + build(self, this).expect("internal bevy_reflect error: ignored fields should be handled") + } } /// Generates the enum variant output data needed to build the `Reflect::try_apply` implementation. @@ -221,6 +250,8 @@ impl<'a> TryApplyVariantBuilder<'a> { } impl<'a> VariantBuilder for TryApplyVariantBuilder<'a> { + type Output = EnumVariantOutputData; + fn reflect_enum(&self) -> &ReflectEnum { self.reflect_enum } @@ -263,4 +294,80 @@ impl<'a> VariantBuilder for TryApplyVariantBuilder<'a> { })? } } + + fn build(&self, this: &Ident) -> Self::Output { + build(self, this).expect("internal bevy_reflect error: ignored fields should be handled") + } +} + +/// Generates the enum variant output data needed to build the `Reflect::reflect_clone` implementation. +pub(crate) struct ReflectCloneVariantBuilder<'a> { + reflect_enum: &'a ReflectEnum<'a>, +} + +impl<'a> ReflectCloneVariantBuilder<'a> { + pub fn new(reflect_enum: &'a ReflectEnum) -> Self { + Self { reflect_enum } + } +} + +impl<'a> VariantBuilder for ReflectCloneVariantBuilder<'a> { + type Output = Option; + fn reflect_enum(&self) -> &ReflectEnum { + self.reflect_enum + } + + fn access_field(&self, _ident: &Ident, field: VariantField) -> TokenStream { + let alias = field.alias; + quote!(#FQOption::Some(#alias)) + } + + fn unwrap_field(&self, field: VariantField) -> TokenStream { + let alias = field.alias; + quote!(#alias.unwrap()) + } + + fn construct_field(&self, field: VariantField) -> TokenStream { + let bevy_reflect_path = self.reflect_enum.meta().bevy_reflect_path(); + + let alias = field.alias; + + match &field.field.attrs.clone { + CloneBehavior::Default => { + quote! { + #bevy_reflect_path::Reflect::reflect_clone(#alias)?.take().ok()? + } + } + CloneBehavior::Trait => { + quote! { + #FQClone::clone(#alias) + } + } + CloneBehavior::Func(clone_fn) => { + quote! { + #clone_fn(#alias) + } + } + } + } + + fn on_active_field(&self, _this: &Ident, field: VariantField) -> TokenStream { + self.construct_field(field) + } + + fn on_ignored_field(&self, field: VariantField) -> Option { + let alias = field.alias; + + match &field.field.attrs.clone { + CloneBehavior::Default => None, + CloneBehavior::Trait => Some(quote! { + #FQClone::clone(#alias) + }), + CloneBehavior::Func(clone_fn) => Some(quote! { #clone_fn() }), + } + } + + fn build(&self, this: &Ident) -> Self::Output { + build(self, this) + } } diff --git a/crates/bevy_reflect/derive/src/field_attributes.rs b/crates/bevy_reflect/derive/src/field_attributes.rs index d66daf8382b4d..70d05e5c22902 100644 --- a/crates/bevy_reflect/derive/src/field_attributes.rs +++ b/crates/bevy_reflect/derive/src/field_attributes.rs @@ -13,6 +13,7 @@ use syn::{Attribute, LitStr, Meta, Token}; mod kw { syn::custom_keyword!(ignore); syn::custom_keyword!(skip_serializing); + syn::custom_keyword!(clone); syn::custom_keyword!(default); } @@ -20,6 +21,7 @@ pub(crate) const IGNORE_SERIALIZATION_ATTR: &str = "skip_serializing"; pub(crate) const IGNORE_ALL_ATTR: &str = "ignore"; pub(crate) const DEFAULT_ATTR: &str = "default"; +pub(crate) const CLONE_ATTR: &str = "clone"; /// Stores data about if the field should be visible via the Reflect and serialization interfaces /// @@ -52,6 +54,14 @@ impl ReflectIgnoreBehavior { } } +#[derive(Default, Clone)] +pub(crate) enum CloneBehavior { + #[default] + Default, + Trait, + Func(syn::ExprPath), +} + /// Controls how the default value is determined for a field. #[derive(Default, Clone)] pub(crate) enum DefaultBehavior { @@ -72,6 +82,8 @@ pub(crate) enum DefaultBehavior { pub(crate) struct FieldAttributes { /// Determines how this field should be ignored if at all. pub ignore: ReflectIgnoreBehavior, + /// Sets the clone behavior of this field. + pub clone: CloneBehavior, /// Sets the default behavior of this field. pub default: DefaultBehavior, /// Custom attributes created via `#[reflect(@...)]`. @@ -117,6 +129,8 @@ impl FieldAttributes { self.parse_ignore(input) } else if lookahead.peek(kw::skip_serializing) { self.parse_skip_serializing(input) + } else if lookahead.peek(kw::clone) { + self.parse_clone(input) } else if lookahead.peek(kw::default) { self.parse_default(input) } else { @@ -158,6 +172,30 @@ impl FieldAttributes { Ok(()) } + /// Parse `clone` attribute. + /// + /// Examples: + /// - `#[reflect(clone)]` + /// - `#[reflect(clone = "path::to::func")]` + fn parse_clone(&mut self, input: ParseStream) -> syn::Result<()> { + if !matches!(self.clone, CloneBehavior::Default) { + return Err(input.error(format!("only one of {:?} is allowed", [CLONE_ATTR]))); + } + + input.parse::()?; + + if input.peek(Token![=]) { + input.parse::()?; + + let lit = input.parse::()?; + self.clone = CloneBehavior::Func(lit.parse()?); + } else { + self.clone = CloneBehavior::Trait; + } + + Ok(()) + } + /// Parse `default` attribute. /// /// Examples: diff --git a/crates/bevy_reflect/derive/src/impls/enums.rs b/crates/bevy_reflect/derive/src/impls/enums.rs index b9f156f0a9e50..e63b50d8a0708 100644 --- a/crates/bevy_reflect/derive/src/impls/enums.rs +++ b/crates/bevy_reflect/derive/src/impls/enums.rs @@ -33,10 +33,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream .. } = TryApplyVariantBuilder::new(reflect_enum).build(&ref_value); - let clone_fn = reflect_enum - .meta() - .attrs() - .get_clone_impl(bevy_reflect_path); + let clone_fn = reflect_enum.get_clone_impl(); let hash_fn = reflect_enum .meta() .attrs() diff --git a/crates/bevy_reflect/derive/src/impls/structs.rs b/crates/bevy_reflect/derive/src/impls/structs.rs index fb3b79a4609df..f51eb3950eeaa 100644 --- a/crates/bevy_reflect/derive/src/impls/structs.rs +++ b/crates/bevy_reflect/derive/src/impls/structs.rs @@ -29,10 +29,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS let field_count = field_idents.len(); let field_indices = (0..field_count).collect::>(); - let clone_fn = reflect_struct - .meta() - .attrs() - .get_clone_impl(bevy_reflect_path); + let clone_fn = reflect_struct.get_clone_impl(); let hash_fn = reflect_struct .meta() .attrs() diff --git a/crates/bevy_reflect/derive/src/impls/tuple_structs.rs b/crates/bevy_reflect/derive/src/impls/tuple_structs.rs index ee03af81c63d8..bfb0e36d7dd6a 100644 --- a/crates/bevy_reflect/derive/src/impls/tuple_structs.rs +++ b/crates/bevy_reflect/derive/src/impls/tuple_structs.rs @@ -21,10 +21,7 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2:: let where_clause_options = reflect_struct.where_clause_options(); let get_type_registration_impl = reflect_struct.get_type_registration(&where_clause_options); - let clone_fn = reflect_struct - .meta() - .attrs() - .get_clone_impl(bevy_reflect_path); + let clone_fn = reflect_struct.get_clone_impl(); let hash_fn = reflect_struct .meta() .attrs() diff --git a/crates/bevy_reflect/derive/src/lib.rs b/crates/bevy_reflect/derive/src/lib.rs index 3ce9ccc058ae6..e3b46630c32bc 100644 --- a/crates/bevy_reflect/derive/src/lib.rs +++ b/crates/bevy_reflect/derive/src/lib.rs @@ -148,21 +148,21 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre /// the type's [`Clone`] implementation. /// A custom implementation may be provided using `#[reflect(Clone(my_clone_func))]` where /// `my_clone_func` is the path to a function matching the signature: -/// `(&self) -> Self`. +/// `(&Self) -> Self`. /// * `#[reflect(Debug)]` will force the implementation of `Reflect::reflect_debug` to rely on /// the type's [`Debug`] implementation. /// A custom implementation may be provided using `#[reflect(Debug(my_debug_func))]` where /// `my_debug_func` is the path to a function matching the signature: -/// `(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result`. +/// `(&Self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result`. /// * `#[reflect(PartialEq)]` will force the implementation of `Reflect::reflect_partial_eq` to rely on /// the type's [`PartialEq`] implementation. /// A custom implementation may be provided using `#[reflect(PartialEq(my_partial_eq_func))]` where /// `my_partial_eq_func` is the path to a function matching the signature: -/// `(&self, value: &dyn #bevy_reflect_path::Reflect) -> bool`. +/// `(&Self, value: &dyn #bevy_reflect_path::Reflect) -> bool`. /// * `#[reflect(Hash)]` will force the implementation of `Reflect::reflect_hash` to rely on /// the type's [`Hash`] implementation. /// A custom implementation may be provided using `#[reflect(Hash(my_hash_func))]` where -/// `my_hash_func` is the path to a function matching the signature: `(&self) -> u64`. +/// `my_hash_func` is the path to a function matching the signature: `(&Self) -> u64`. /// * `#[reflect(Default)]` will register the `ReflectDefault` type data as normal. /// However, it will also affect how certain other operations are performed in order /// to improve performance and/or robustness. @@ -332,6 +332,18 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre /// What this does is register the `SerializationData` type within the `GetTypeRegistration` implementation, /// which will be used by the reflection serializers to determine whether or not the field is serializable. /// +/// ## `#[reflect(clone)]` +/// +/// This attribute affects the `Reflect::reflect_clone` implementation. +/// +/// Without this attribute, the implementation will rely on the field's own `Reflect::reflect_clone` implementation. +/// When this attribute is present, the implementation will instead use the field's `Clone` implementation directly. +/// +/// The attribute may also take the path to a custom function like `#[reflect(clone = "path::to::my_clone_func")]`, +/// where `my_clone_func` matches the signature `(&Self) -> Self`. +/// +/// This attribute does nothing if the containing struct/enum has the `#[reflect(Clone)]` attribute. +/// /// ## `#[reflect(@...)]` /// /// This attribute can be used to register custom attributes to the field's `TypeInfo`. diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 061355d1458ee..bbe6a338eb5f5 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -806,13 +806,6 @@ mod tests { let foo = Foo(123); let clone = foo.reflect_clone().unwrap(); assert_eq!(foo, clone.take::().unwrap()); - - #[derive(Reflect, Clone, Debug, PartialEq)] - struct Bar(usize); - - let bar = Bar(123); - let clone = bar.reflect_clone(); - assert!(clone.is_none()); } #[test] @@ -830,6 +823,125 @@ mod tests { assert_eq!(Foo(321), clone.take::().unwrap()); } + #[test] + fn should_not_clone_ignored_fields() { + #[derive(Reflect, Clone, Debug, PartialEq)] + struct Foo(#[reflect(ignore)] usize); + + let foo = Foo(123); + let clone = foo.reflect_clone(); + assert!(clone.is_none()); + } + + #[test] + fn should_clone_ignored_fields_with_clone_attributes() { + #[derive(Reflect, Clone, Debug, PartialEq)] + struct Foo(#[reflect(ignore, clone)] usize); + + let foo = Foo(123); + let clone = foo.reflect_clone().unwrap(); + assert_eq!(Foo(123), clone.take::().unwrap()); + + #[derive(Reflect, Clone, Debug, PartialEq)] + struct Bar(#[reflect(ignore, clone = "clone_usize")] usize); + + fn clone_usize(this: &usize) -> usize { + *this + 198 + } + + let bar = Bar(123); + let clone = bar.reflect_clone().unwrap(); + assert_eq!(Bar(321), clone.take::().unwrap()); + } + + #[test] + fn should_composite_reflect_clone() { + #[derive(Reflect, Debug, PartialEq)] + enum MyEnum { + Unit, + Tuple( + Foo, + #[reflect(ignore, clone)] Bar, + #[reflect(clone = "clone_baz")] Baz, + ), + Struct { + foo: Foo, + #[reflect(ignore, clone)] + bar: Bar, + #[reflect(clone = "clone_baz")] + baz: Baz, + }, + } + + #[derive(Reflect, Debug, PartialEq)] + struct Foo { + #[reflect(clone = "clone_bar")] + bar: Bar, + baz: Baz, + } + + #[derive(Reflect, Default, Clone, Debug, PartialEq)] + #[reflect(Clone)] + struct Bar(String); + + #[derive(Reflect, Debug, PartialEq)] + struct Baz(String); + + fn clone_bar(bar: &Bar) -> Bar { + Bar(format!("{}!", bar.0)) + } + + fn clone_baz(baz: &Baz) -> Baz { + Baz(format!("{}!", baz.0)) + } + + let my_enum = MyEnum::Unit; + let clone = my_enum.reflect_clone().unwrap(); + assert_eq!(MyEnum::Unit, clone.take::().unwrap()); + + let my_enum = MyEnum::Tuple( + Foo { + bar: Bar("bar".to_string()), + baz: Baz("baz".to_string()), + }, + Bar("bar".to_string()), + Baz("baz".to_string()), + ); + let clone = my_enum.reflect_clone().unwrap(); + assert_eq!( + MyEnum::Tuple( + Foo { + bar: Bar("bar!".to_string()), + baz: Baz("baz".to_string()), + }, + Bar("bar".to_string()), + Baz("baz!".to_string()), + ), + clone.take::().unwrap() + ); + + let my_enum = MyEnum::Struct { + foo: Foo { + bar: Bar("bar".to_string()), + baz: Baz("baz".to_string()), + }, + bar: Bar("bar".to_string()), + baz: Baz("baz".to_string()), + }; + let clone = my_enum.reflect_clone().unwrap(); + assert_eq!( + MyEnum::Struct { + foo: Foo { + bar: Bar("bar!".to_string()), + baz: Baz("baz".to_string()), + }, + bar: Bar("bar".to_string()), + baz: Baz("baz!".to_string()), + }, + clone.take::().unwrap() + ); + } + #[test] fn should_call_from_reflect_dynamically() { #[derive(Reflect)] From 4c5e88633f912360f3d863324cd5bfa4c675e55b Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 18 May 2024 17:35:21 -0700 Subject: [PATCH 4/9] Support Reflect::reflect_clone on tuples --- crates/bevy_reflect/src/lib.rs | 12 +++++++++--- crates/bevy_reflect/src/tuple.rs | 8 ++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index bbe6a338eb5f5..665820418487f 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -799,13 +799,19 @@ mod tests { #[test] fn should_reflect_clone() { + // Struct #[derive(Reflect, Clone, Debug, PartialEq)] #[reflect(Clone)] struct Foo(usize); - let foo = Foo(123); - let clone = foo.reflect_clone().unwrap(); - assert_eq!(foo, clone.take::().unwrap()); + let value = Foo(123); + let clone = value.reflect_clone().expect("should reflect_clone struct"); + assert_eq!(value, clone.take::().unwrap()); + + // Tuple + let foo = (123, 4.56); + let clone = foo.reflect_clone().expect("should reflect_clone tuple"); + assert_eq!(foo, clone.take::<(u32, f32)>().unwrap()); } #[test] diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index cf111edcdfb99..ddb5824807d99 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -596,6 +596,14 @@ macro_rules! impl_reflect_tuple { Box::new(self.clone_dynamic()) } + fn reflect_clone(&self) -> Option> { + Some(Box::new(( + $( + self.$index.reflect_clone()?.take::<$name>().ok()?, + )* + ))) + } + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { crate::tuple_partial_eq(self, value) } From 47bf66b50e549a7b6ea91221d9ec2cc9ef064fc9 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 18 May 2024 17:44:28 -0700 Subject: [PATCH 5/9] Update docs on Reflect clone methods --- crates/bevy_reflect/src/reflect.rs | 33 ++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 93bc2807e5d28..92b2041c17a88 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -298,13 +298,25 @@ pub trait Reflect: DynamicTypePath + Any + Send + Sync { /// See [`ReflectOwned`]. fn reflect_owned(self: Box) -> ReflectOwned; - /// Clones the value as a `Reflect` trait object. + /// Clones `Self` into its dynamic representation. /// - /// When deriving `Reflect` for a struct, tuple struct or enum, the value is - /// cloned via [`Struct::clone_dynamic`], [`TupleStruct::clone_dynamic`], - /// or [`Enum::clone_dynamic`], respectively. - /// Implementors of other `Reflect` subtraits (e.g. [`List`], [`Map`]) should - /// use those subtraits' respective `clone_dynamic` methods. + /// For value types or types marked with `#[reflect_value]`, + /// this will simply return a clone of `Self`. + /// + /// Otherwise the associated dynamic type will be returned. + /// + /// For example, a [`List`] type will invoke [`List::clone_dynamic`], returning [`DynamicList`]. + /// A [`Struct`] type will invoke [`Struct::clone_dynamic`], returning [`DynamicStruct`]. + /// And so on. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::{Reflect, DynamicTuple}; + /// let value = (1, true, 3.14); + /// let cloned = value.clone_value(); + /// assert!(cloned.is::()) + /// ``` fn clone_value(&self) -> Box; /// Attempts to clone `Self` using reflection. @@ -313,6 +325,15 @@ pub trait Reflect: DynamicTypePath + Any + Send + Sync { /// this method attempts create a clone of `Self` directly, if possible. /// /// If the clone cannot be performed, `None` is returned. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::Reflect; + /// let value = (1, true, 3.14); + /// let cloned = value.reflect_clone().unwrap(); + /// assert!(cloned.is::<(i32, bool, f64)>()) + /// ``` fn reflect_clone(&self) -> Option> { None } From f9b37c8f6132ee5771ca490749d8e9a4d576a913 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sun, 19 May 2024 18:20:14 -0700 Subject: [PATCH 6/9] Add test for generic types --- crates/bevy_reflect/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 665820418487f..7b64742eb2938 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -808,6 +808,18 @@ mod tests { let clone = value.reflect_clone().expect("should reflect_clone struct"); assert_eq!(value, clone.take::().unwrap()); + // Generic Struct + #[derive(Reflect, Debug, PartialEq)] + struct Bar(T, #[reflect(ignore, clone)] PhantomData); + #[derive(TypePath, Debug, PartialEq)] + struct Baz; + + let value = Bar::(123, PhantomData); + let clone = value + .reflect_clone() + .expect("should reflect_clone generic struct"); + assert_eq!(value, clone.take::>().unwrap()); + // Tuple let foo = (123, 4.56); let clone = foo.reflect_clone().expect("should reflect_clone tuple"); From 74ee004430ba9f3b9263d05dc05ffff29c487171 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 20 May 2024 13:35:30 -0700 Subject: [PATCH 7/9] Better separate tests --- crates/bevy_reflect/src/lib.rs | 88 +++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 11 deletions(-) diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 7b64742eb2938..6a18ec8046bb9 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -800,30 +800,96 @@ mod tests { #[test] fn should_reflect_clone() { // Struct - #[derive(Reflect, Clone, Debug, PartialEq)] - #[reflect(Clone)] + #[derive(Reflect, Debug, PartialEq)] struct Foo(usize); let value = Foo(123); let clone = value.reflect_clone().expect("should reflect_clone struct"); assert_eq!(value, clone.take::().unwrap()); - // Generic Struct + // Tuple + let foo = (123, 4.56); + let clone = foo.reflect_clone().expect("should reflect_clone tuple"); + assert_eq!(foo, clone.take::<(u32, f32)>().unwrap()); + } + + #[test] + fn should_reflect_clone_generic_type() { #[derive(Reflect, Debug, PartialEq)] - struct Bar(T, #[reflect(ignore, clone)] PhantomData); + struct Foo(T, #[reflect(ignore, clone)] PhantomData); #[derive(TypePath, Debug, PartialEq)] - struct Baz; + struct Bar; - let value = Bar::(123, PhantomData); + // `usize` will be cloned via `Reflect::reflect_clone` + // `PhantomData` will be cloned via `Clone::clone` + let value = Foo::(123, PhantomData); let clone = value .reflect_clone() .expect("should reflect_clone generic struct"); - assert_eq!(value, clone.take::>().unwrap()); + assert_eq!(value, clone.take::>().unwrap()); + } - // Tuple - let foo = (123, 4.56); - let clone = foo.reflect_clone().expect("should reflect_clone tuple"); - assert_eq!(foo, clone.take::<(u32, f32)>().unwrap()); + #[test] + fn should_reflect_clone_with_clone() { + // A custom clone function to verify that the `#[reflect(Clone)]` container attribute + // takes precedence over the `#[reflect(clone)]` field attribute. + #[allow(dead_code, unused_variables)] + fn custom_clone(value: &usize) -> usize { + panic!("should not be called"); + } + + // Tuple Struct + #[derive(Reflect, Clone, Debug, PartialEq)] + #[reflect(Clone)] + struct Foo(#[reflect(clone = "custom_clone")] usize); + + let value = Foo(123); + let clone = value + .reflect_clone() + .expect("should reflect_clone tuple struct"); + assert_eq!(value, clone.take::().unwrap()); + + // Struct + #[derive(Reflect, Clone, Debug, PartialEq)] + #[reflect(Clone)] + struct Bar { + #[reflect(clone = "custom_clone")] + value: usize, + } + + let value = Bar { value: 123 }; + let clone = value.reflect_clone().expect("should reflect_clone struct"); + assert_eq!(value, clone.take::().unwrap()); + + // Enum + #[derive(Reflect, Clone, Debug, PartialEq)] + #[reflect(Clone)] + enum Baz { + Unit, + Tuple(#[reflect(clone = "custom_clone")] usize), + Struct { + #[reflect(clone = "custom_clone")] + value: usize, + }, + } + + let value = Baz::Unit; + let clone = value + .reflect_clone() + .expect("should reflect_clone unit variant"); + assert_eq!(value, clone.take::().unwrap()); + + let value = Baz::Tuple(123); + let clone = value + .reflect_clone() + .expect("should reflect_clone tuple variant"); + assert_eq!(value, clone.take::().unwrap()); + + let value = Baz::Struct { value: 123 }; + let clone = value + .reflect_clone() + .expect("should reflect_clone struct variant"); + assert_eq!(value, clone.take::().unwrap()); } #[test] From ad1146e7043f733b23c9af72e956481557ef590e Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Tue, 21 May 2024 12:43:41 -0700 Subject: [PATCH 8/9] Change return type to Result --- .../derive/src/container_attributes.rs | 10 +- crates/bevy_reflect/derive/src/derive_data.rs | 61 +++++-- .../bevy_reflect/derive/src/enum_utility.rs | 167 +++++++++--------- .../bevy_reflect/derive/src/impls/values.rs | 4 +- crates/bevy_reflect/src/error.rs | 61 +++++++ crates/bevy_reflect/src/fields.rs | 17 ++ crates/bevy_reflect/src/impls/std.rs | 26 +-- crates/bevy_reflect/src/lib.rs | 63 ++++++- crates/bevy_reflect/src/reflect.rs | 11 +- crates/bevy_reflect/src/tuple.rs | 12 +- 10 files changed, 308 insertions(+), 124 deletions(-) create mode 100644 crates/bevy_reflect/src/error.rs diff --git a/crates/bevy_reflect/derive/src/container_attributes.rs b/crates/bevy_reflect/derive/src/container_attributes.rs index f93559da384ae..2b268c118eedd 100644 --- a/crates/bevy_reflect/derive/src/container_attributes.rs +++ b/crates/bevy_reflect/derive/src/container_attributes.rs @@ -9,7 +9,7 @@ use crate::custom_attributes::CustomAttributes; use crate::derive_data::ReflectTraitToImpl; use crate::utility; use crate::utility::terminated_parser; -use bevy_macro_utils::fq_std::{FQAny, FQBox, FQClone, FQOption}; +use bevy_macro_utils::fq_std::{FQAny, FQBox, FQClone, FQOption, FQResult}; use proc_macro2::{Ident, Span}; use quote::quote_spanned; use syn::ext::IdentExt; @@ -541,14 +541,14 @@ impl ContainerAttributes { match &self.clone { &TraitImpl::Implemented(span) => Some(quote_spanned! {span=> #[inline] - fn reflect_clone(&self) -> #FQOption<#FQBox> { - #FQOption::Some(#FQBox::new(#FQClone::clone(self))) + fn reflect_clone(&self) -> #FQResult<#FQBox, #bevy_reflect_path::ReflectCloneError> { + #FQResult::Ok(#FQBox::new(#FQClone::clone(self))) } }), &TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=> #[inline] - fn reflect_clone(&self) -> #FQOption<#FQBox> { - #FQOption::Some(#FQBox::new(#impl_fn(self))) + fn reflect_clone(&self) -> #FQResult<#FQBox, #bevy_reflect_path::ReflectCloneError> { + #FQResult::Ok(#FQBox::new(#impl_fn(self))) } }), TraitImpl::NotImplemented => None, diff --git a/crates/bevy_reflect/derive/src/derive_data.rs b/crates/bevy_reflect/derive/src/derive_data.rs index ef75d03ab689d..dffb84090a442 100644 --- a/crates/bevy_reflect/derive/src/derive_data.rs +++ b/crates/bevy_reflect/derive/src/derive_data.rs @@ -14,7 +14,7 @@ use crate::{ utility, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME, TYPE_NAME_ATTRIBUTE_NAME, TYPE_PATH_ATTRIBUTE_NAME, }; -use bevy_macro_utils::fq_std::{FQBox, FQClone, FQOption}; +use bevy_macro_utils::fq_std::{FQBox, FQClone, FQOption, FQResult}; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::{ @@ -520,6 +520,20 @@ impl<'a> StructField<'a> { info } + + /// Returns a token stream for generating a `FieldId` for this field. + pub fn field_id(&self, bevy_reflect_path: &Path) -> proc_macro2::TokenStream { + match &self.data.ident { + Some(ident) => { + let name = ident.to_string(); + quote!(#bevy_reflect_path::FieldId::Named(#name)) + } + None => { + let index = self.declaration_index; + quote!(#bevy_reflect_path::FieldId::Unnamed(#index)) + } + } + } } impl<'a> ReflectStruct<'a> { @@ -635,16 +649,41 @@ impl<'a> ReflectStruct<'a> { let mut tokens = proc_macro2::TokenStream::new(); for field in self.fields() { + let field_ty = &field.data.ty; let member = ident_or_index(field.data.ident.as_ref(), field.declaration_index); match &field.attrs.clone { CloneBehavior::Default => { - if field.attrs.ignore.is_ignored() { - return None; - } + let value = if field.attrs.ignore.is_ignored() { + let field_id = field.field_id(bevy_reflect_path); + + quote! { + return #FQResult::Err(#bevy_reflect_path::ReflectCloneError::FieldNotClonable { + field: #field_id, + variant: #FQOption::None, + container_type_path: ::std::borrow::Cow::Borrowed( + ::type_path() + ) + }) + } + } else { + quote! { + #bevy_reflect_path::Reflect::reflect_clone(&self.#member)? + .take() + .map_err(|value| #bevy_reflect_path::ReflectCloneError::FailedDowncast { + expected: ::std::borrow::Cow::Borrowed( + <#field_ty as #bevy_reflect_path::TypePath>::type_path() + ), + received: ::std::borrow::Cow::Owned( + #bevy_reflect_path::DynamicTypePath::reflect_type_path(&*value) + .to_string() + ), + })? + } + }; tokens.extend(quote! { - #member: #bevy_reflect_path::Reflect::reflect_clone(&self.#member)?.take().ok()?, + #member: #value, }); } CloneBehavior::Trait => { @@ -662,8 +701,9 @@ impl<'a> ReflectStruct<'a> { Some(quote! { #[inline] - fn reflect_clone(&self) -> #FQOption<#FQBox> { - #FQOption::Some(#FQBox::new(Self { + fn reflect_clone(&self) -> #FQResult<#FQBox, #bevy_reflect_path::ReflectCloneError> { + #[allow(unreachable_code)] + #FQResult::Ok(#FQBox::new(Self { #tokens })) } @@ -772,12 +812,13 @@ impl<'a> ReflectEnum<'a> { variant_patterns, variant_constructors, .. - } = ReflectCloneVariantBuilder::new(self).build(&this)?; + } = ReflectCloneVariantBuilder::new(self).build(&this); Some(quote! { #[inline] - fn reflect_clone(&#this) -> #FQOption<#FQBox> { - #FQOption::Some(#FQBox::new(match #this { + fn reflect_clone(&self) -> #FQResult<#FQBox, #bevy_reflect_path::ReflectCloneError> { + #[allow(unreachable_code)] + #FQResult::Ok(#FQBox::new(match #this { #(#variant_patterns => #variant_constructors),* })) } diff --git a/crates/bevy_reflect/derive/src/enum_utility.rs b/crates/bevy_reflect/derive/src/enum_utility.rs index 571a52f0b1147..0302f27f4aa13 100644 --- a/crates/bevy_reflect/derive/src/enum_utility.rs +++ b/crates/bevy_reflect/derive/src/enum_utility.rs @@ -1,7 +1,7 @@ use crate::derive_data::StructField; use crate::field_attributes::{CloneBehavior, DefaultBehavior}; use crate::{derive_data::ReflectEnum, utility::ident_or_index}; -use bevy_macro_utils::fq_std::{FQClone, FQDefault, FQOption}; +use bevy_macro_utils::fq_std::{FQClone, FQDefault, FQOption, FQResult}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; @@ -34,9 +34,6 @@ pub(crate) struct VariantField<'a, 'b> { /// Trait used to control how enum variants are built. pub(crate) trait VariantBuilder: Sized { - /// The variant output data. - type Output; - /// Returns the enum data. fn reflect_enum(&self) -> &ReflectEnum; @@ -121,83 +118,79 @@ pub(crate) trait VariantBuilder: Sized { /// Returns a token stream that constructs an instance of an ignored field. /// - /// If the ignored field cannot be adequately handled, `None` should be returned. - /// /// # Parameters /// * `field`: The field to access - fn on_ignored_field(&self, field: VariantField) -> Option { - Some(match &field.field.attrs.default { + fn on_ignored_field(&self, field: VariantField) -> TokenStream { + match &field.field.attrs.default { DefaultBehavior::Func(path) => quote! { #path() }, _ => quote! { #FQDefault::default() }, - }) + } } /// Builds the enum variant output data. - fn build(&self, this: &Ident) -> Self::Output; -} + fn build(&self, this: &Ident) -> EnumVariantOutputData { + let variants = self.reflect_enum().variants(); -fn build(builder: &V, this: &Ident) -> Option { - let variants = builder.reflect_enum().variants(); + let mut variant_names = Vec::with_capacity(variants.len()); + let mut variant_patterns = Vec::with_capacity(variants.len()); + let mut variant_constructors = Vec::with_capacity(variants.len()); - let mut variant_names = Vec::with_capacity(variants.len()); - let mut variant_patterns = Vec::with_capacity(variants.len()); - let mut variant_constructors = Vec::with_capacity(variants.len()); + for variant in variants { + let variant_ident = &variant.data.ident; + let variant_name = variant_ident.to_string(); + let variant_path = self.reflect_enum().get_unit(variant_ident); - for variant in variants { - let variant_ident = &variant.data.ident; - let variant_name = variant_ident.to_string(); - let variant_path = builder.reflect_enum().get_unit(variant_ident); + let fields = variant.fields(); - let fields = variant.fields(); + let mut field_patterns = Vec::with_capacity(fields.len()); + let mut field_constructors = Vec::with_capacity(fields.len()); - let mut field_patterns = Vec::with_capacity(fields.len()); - let mut field_constructors = Vec::with_capacity(fields.len()); + for field in fields { + let member = ident_or_index(field.data.ident.as_ref(), field.declaration_index); + let alias = format_ident!("_{}", member); - for field in fields { - let member = ident_or_index(field.data.ident.as_ref(), field.declaration_index); - let alias = format_ident!("_{}", member); + let variant_field = VariantField { + alias: &alias, + variant_name: &variant_name, + field, + }; - let variant_field = VariantField { - alias: &alias, - variant_name: &variant_name, - field, - }; + let value = if field.attrs.ignore.is_ignored() { + self.on_ignored_field(variant_field) + } else { + self.on_active_field(this, variant_field) + }; - let value = if field.attrs.ignore.is_ignored() { - builder.on_ignored_field(variant_field)? - } else { - builder.on_active_field(this, variant_field) - }; + field_patterns.push(quote! { + #member: #alias + }); - field_patterns.push(quote! { - #member: #alias - }); + field_constructors.push(quote! { + #member: #value + }); + } - field_constructors.push(quote! { - #member: #value - }); - } + let pattern = quote! { + #variant_path { #( #field_patterns ),* } + }; - let pattern = quote! { - #variant_path { #( #field_patterns ),* } - }; + let constructor = quote! { + #variant_path { + #( #field_constructors ),* + } + }; - let constructor = quote! { - #variant_path { - #( #field_constructors ),* - } - }; + variant_names.push(variant_name); + variant_patterns.push(pattern); + variant_constructors.push(constructor); + } - variant_names.push(variant_name); - variant_patterns.push(pattern); - variant_constructors.push(constructor); + EnumVariantOutputData { + variant_names, + variant_patterns, + variant_constructors, + } } - - Some(EnumVariantOutputData { - variant_names, - variant_patterns, - variant_constructors, - }) } /// Generates the enum variant output data needed to build the `FromReflect::from_reflect` implementation. @@ -212,8 +205,6 @@ impl<'a> FromReflectVariantBuilder<'a> { } impl<'a> VariantBuilder for FromReflectVariantBuilder<'a> { - type Output = EnumVariantOutputData; - fn reflect_enum(&self) -> &ReflectEnum { self.reflect_enum } @@ -232,10 +223,6 @@ impl<'a> VariantBuilder for FromReflectVariantBuilder<'a> { <#field_ty as #bevy_reflect_path::FromReflect>::from_reflect(#alias)? } } - - fn build(&self, this: &Ident) -> Self::Output { - build(self, this).expect("internal bevy_reflect error: ignored fields should be handled") - } } /// Generates the enum variant output data needed to build the `Reflect::try_apply` implementation. @@ -250,8 +237,6 @@ impl<'a> TryApplyVariantBuilder<'a> { } impl<'a> VariantBuilder for TryApplyVariantBuilder<'a> { - type Output = EnumVariantOutputData; - fn reflect_enum(&self) -> &ReflectEnum { self.reflect_enum } @@ -294,10 +279,6 @@ impl<'a> VariantBuilder for TryApplyVariantBuilder<'a> { })? } } - - fn build(&self, this: &Ident) -> Self::Output { - build(self, this).expect("internal bevy_reflect error: ignored fields should be handled") - } } /// Generates the enum variant output data needed to build the `Reflect::reflect_clone` implementation. @@ -312,7 +293,6 @@ impl<'a> ReflectCloneVariantBuilder<'a> { } impl<'a> VariantBuilder for ReflectCloneVariantBuilder<'a> { - type Output = Option; fn reflect_enum(&self) -> &ReflectEnum { self.reflect_enum } @@ -331,11 +311,22 @@ impl<'a> VariantBuilder for ReflectCloneVariantBuilder<'a> { let bevy_reflect_path = self.reflect_enum.meta().bevy_reflect_path(); let alias = field.alias; + let field_ty = &field.field.data.ty; match &field.field.attrs.clone { CloneBehavior::Default => { quote! { - #bevy_reflect_path::Reflect::reflect_clone(#alias)?.take().ok()? + #bevy_reflect_path::Reflect::reflect_clone(#alias)? + .take() + .map_err(|value| #bevy_reflect_path::ReflectCloneError::FailedDowncast { + expected: ::std::borrow::Cow::Borrowed( + <#field_ty as #bevy_reflect_path::TypePath>::type_path() + ), + received: ::std::borrow::Cow::Owned( + #bevy_reflect_path::DynamicTypePath::reflect_type_path(&*value) + .to_string() + ), + })? } } CloneBehavior::Trait => { @@ -355,19 +346,27 @@ impl<'a> VariantBuilder for ReflectCloneVariantBuilder<'a> { self.construct_field(field) } - fn on_ignored_field(&self, field: VariantField) -> Option { + fn on_ignored_field(&self, field: VariantField) -> TokenStream { + let bevy_reflect_path = self.reflect_enum.meta().bevy_reflect_path(); + let variant_name = field.variant_name; let alias = field.alias; match &field.field.attrs.clone { - CloneBehavior::Default => None, - CloneBehavior::Trait => Some(quote! { - #FQClone::clone(#alias) - }), - CloneBehavior::Func(clone_fn) => Some(quote! { #clone_fn() }), - } - } + CloneBehavior::Default => { + let field_id = field.field.field_id(bevy_reflect_path); - fn build(&self, this: &Ident) -> Self::Output { - build(self, this) + quote! { + return #FQResult::Err( + #bevy_reflect_path::ReflectCloneError::FieldNotClonable { + field: #field_id, + variant: #FQOption::Some(::std::borrow::Cow::Borrowed(#variant_name)), + container_type_path: ::std::borrow::Cow::Borrowed(::type_path()) + } + ) + } + } + CloneBehavior::Trait => quote! { #FQClone::clone(#alias) }, + CloneBehavior::Func(clone_fn) => quote! { #clone_fn() }, + } } } diff --git a/crates/bevy_reflect/derive/src/impls/values.rs b/crates/bevy_reflect/derive/src/impls/values.rs index ab18cfc585853..70105ce1227f4 100644 --- a/crates/bevy_reflect/derive/src/impls/values.rs +++ b/crates/bevy_reflect/derive/src/impls/values.rs @@ -86,8 +86,8 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream { } #[inline] - fn reflect_clone(&self) -> #FQOption<#FQBox> { - #FQOption::Some(#FQBox::new(#FQClone::clone(self))) + fn reflect_clone(&self) -> #FQResult<#FQBox, #bevy_reflect_path::ReflectCloneError> { + #FQResult::Ok(#FQBox::new(#FQClone::clone(self))) } #[inline] diff --git a/crates/bevy_reflect/src/error.rs b/crates/bevy_reflect/src/error.rs new file mode 100644 index 0000000000000..cc8193d8facd2 --- /dev/null +++ b/crates/bevy_reflect/src/error.rs @@ -0,0 +1,61 @@ +use crate::FieldId; +use alloc::borrow::Cow; +use thiserror::Error; + +/// An error that occurs when cloning a type via [`Reflect::reflect_clone`]. +/// +/// [`Reflect::reflect_clone`]: crate::Reflect::reflect_clone +#[derive(Clone, Debug, Error, PartialEq, Eq)] +pub enum ReflectCloneError { + /// The type does not have a custom implementation for [`Reflect::reflect_clone`]. + /// + /// [`Reflect::reflect_clone`]: crate::Reflect::reflect_clone + #[error("`Reflect::reflect_clone` not implemented for `{type_path}`")] + NotImplemented { type_path: Cow<'static, str> }, + /// The type cannot be cloned via [`Reflect::reflect_clone`]. + /// + /// This type should be returned when a type is intentionally opting out of reflection cloning. + /// + /// [`Reflect::reflect_clone`]: crate::Reflect::reflect_clone + #[error("`{type_path}` cannot be made clonable for `Reflect::reflect_clone`")] + NotClonable { type_path: Cow<'static, str> }, + /// The field cannot be cloned via [`Reflect::reflect_clone`]. + /// + /// When [deriving `Reflect`], this usually means that a field marked with `#[reflect(ignore)]` + /// is missing a `#[reflect(clone)]` attribute. + /// + /// This may be intentional if the field is not meant/able to be cloned. + /// + /// [`Reflect::reflect_clone`]: crate::Reflect::reflect_clone + /// [deriving `Reflect`]: derive@crate::Reflect + #[error( + "field `{}` cannot be made clonable for `Reflect::reflect_clone` (are you missing a `#[reflect(clone)]` attribute?)", + full_path(.field, .variant, .container_type_path) + )] + FieldNotClonable { + field: FieldId, + variant: Option>, + container_type_path: Cow<'static, str>, + }, + /// Could not downcast to the expected type. + /// + /// Realistically this should only occur when a type has incorrectly implemented [`Reflect`]. + /// + /// [`Reflect`]: crate::Reflect + #[error("expected downcast to `{expected}`, but received `{received}`")] + FailedDowncast { + expected: Cow<'static, str>, + received: Cow<'static, str>, + }, +} + +fn full_path( + field: &FieldId, + variant: &Option>, + container_type_path: &Cow<'static, str>, +) -> String { + match variant { + Some(variant) => format!("{}::{}::{}", container_type_path, variant, field), + None => format!("{}::{}", container_type_path, field), + } +} diff --git a/crates/bevy_reflect/src/fields.rs b/crates/bevy_reflect/src/fields.rs index 31855aeb78228..19614eb73bb0c 100644 --- a/crates/bevy_reflect/src/fields.rs +++ b/crates/bevy_reflect/src/fields.rs @@ -1,5 +1,6 @@ use crate::attributes::{impl_custom_attribute_methods, CustomAttributes}; use crate::{Reflect, TypePath, TypePathTable}; +use core::fmt::{Debug, Display, Formatter}; use std::any::{Any, TypeId}; use std::sync::Arc; @@ -159,3 +160,19 @@ impl UnnamedField { impl_custom_attribute_methods!(self.custom_attributes, "field"); } + +/// A representation of a field's accessor. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FieldId { + Named(&'static str), + Unnamed(usize), +} + +impl Display for FieldId { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + Self::Named(name) => Display::fmt(name, f), + Self::Unnamed(index) => Display::fmt(index, f), + } + } +} diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index 8327ee920de89..9c16e8b4fe2fd 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -6,9 +6,9 @@ use crate::{ self as bevy_reflect, impl_type_path, map_apply, map_partial_eq, map_try_apply, ApplyError, Array, ArrayInfo, ArrayIter, DynamicMap, DynamicTypePath, FromReflect, FromType, GetTypeRegistration, List, ListInfo, ListIter, Map, MapInfo, MapIter, Reflect, - ReflectDeserialize, ReflectFromPtr, ReflectFromReflect, ReflectKind, ReflectMut, ReflectOwned, - ReflectRef, ReflectSerialize, TypeInfo, TypePath, TypeRegistration, TypeRegistry, Typed, - ValueInfo, + ReflectCloneError, ReflectDeserialize, ReflectFromPtr, ReflectFromReflect, ReflectKind, + ReflectMut, ReflectOwned, ReflectRef, ReflectSerialize, TypeInfo, TypePath, TypeRegistration, + TypeRegistry, Typed, ValueInfo, }; use bevy_reflect_derive::{impl_reflect, impl_reflect_value}; use std::fmt; @@ -1219,8 +1219,8 @@ impl Reflect for Cow<'static, str> { Box::new(self.clone()) } - fn reflect_clone(&self) -> Option> { - Some(Box::new(self.clone())) + fn reflect_clone(&self) -> Result, ReflectCloneError> { + Ok(Box::new(self.clone())) } fn reflect_hash(&self) -> Option { @@ -1408,8 +1408,8 @@ impl Reflect for Cow<'s Box::new(List::clone_dynamic(self)) } - fn reflect_clone(&self) -> Option> { - Some(Box::new(self.clone())) + fn reflect_clone(&self) -> Result, ReflectCloneError> { + Ok(Box::new(self.clone())) } fn reflect_hash(&self) -> Option { @@ -1517,8 +1517,8 @@ impl Reflect for &'static str { Box::new(*self) } - fn reflect_clone(&self) -> Option> { - Some(Box::new(*self)) + fn reflect_clone(&self) -> Result, ReflectCloneError> { + Ok(Box::new(*self)) } fn reflect_hash(&self) -> Option { @@ -1631,8 +1631,8 @@ impl Reflect for &'static Path { Box::new(*self) } - fn reflect_clone(&self) -> Option> { - Some(Box::new(*self)) + fn reflect_clone(&self) -> Result, ReflectCloneError> { + Ok(Box::new(*self)) } fn reflect_hash(&self) -> Option { @@ -1740,8 +1740,8 @@ impl Reflect for Cow<'static, Path> { Box::new(self.clone()) } - fn reflect_clone(&self) -> Option> { - Some(Box::new(self.clone())) + fn reflect_clone(&self) -> Result, ReflectCloneError> { + Ok(Box::new(self.clone())) } fn reflect_hash(&self) -> Option { diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 6a18ec8046bb9..52d91397b3663 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -474,6 +474,7 @@ //! [derive `Reflect`]: derive@crate::Reflect mod array; +mod error; mod fields; mod from_reflect; mod list; @@ -530,6 +531,7 @@ pub mod prelude { pub use array::*; pub use enums::*; +pub use error::*; pub use fields::*; pub use from_reflect::*; pub use list::*; @@ -547,6 +549,7 @@ pub use bevy_reflect_derive::*; pub use erased_serde; extern crate alloc; +extern crate core; /// Exports used by the reflection macros. /// @@ -909,12 +912,70 @@ mod tests { #[test] fn should_not_clone_ignored_fields() { + // Tuple Struct #[derive(Reflect, Clone, Debug, PartialEq)] struct Foo(#[reflect(ignore)] usize); let foo = Foo(123); let clone = foo.reflect_clone(); - assert!(clone.is_none()); + assert_eq!( + clone.unwrap_err(), + ReflectCloneError::FieldNotClonable { + field: FieldId::Unnamed(0), + variant: None, + container_type_path: Cow::Borrowed(Foo::type_path()), + } + ); + + // Struct + #[derive(Reflect, Clone, Debug, PartialEq)] + struct Bar { + #[reflect(ignore)] + value: usize, + } + + let bar = Bar { value: 123 }; + let clone = bar.reflect_clone(); + assert_eq!( + clone.unwrap_err(), + ReflectCloneError::FieldNotClonable { + field: FieldId::Named("value"), + variant: None, + container_type_path: Cow::Borrowed(Bar::type_path()), + } + ); + + // Enum + #[derive(Reflect, Clone, Debug, PartialEq)] + enum Baz { + Tuple(#[reflect(ignore)] usize), + Struct { + #[reflect(ignore)] + value: usize, + }, + } + + let baz = Baz::Tuple(123); + let clone = baz.reflect_clone(); + assert_eq!( + clone.unwrap_err(), + ReflectCloneError::FieldNotClonable { + field: FieldId::Unnamed(0), + variant: Some(Cow::Borrowed("Tuple")), + container_type_path: Cow::Borrowed(Baz::type_path()), + } + ); + + let baz = Baz::Struct { value: 123 }; + let clone = baz.reflect_clone(); + assert_eq!( + clone.unwrap_err(), + ReflectCloneError::FieldNotClonable { + field: FieldId::Named("value"), + variant: Some(Cow::Borrowed("Struct")), + container_type_path: Cow::Borrowed(Baz::type_path()), + } + ); } #[test] diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 92b2041c17a88..34524a94d082b 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -1,8 +1,9 @@ use crate::{ array_debug, enum_debug, list_debug, map_debug, serde::Serializable, struct_debug, tuple_debug, - tuple_struct_debug, Array, DynamicTypePath, Enum, List, Map, Struct, Tuple, TupleStruct, - TypeInfo, TypePath, Typed, ValueInfo, + tuple_struct_debug, Array, DynamicTypePath, Enum, List, Map, ReflectCloneError, Struct, Tuple, + TupleStruct, TypeInfo, TypePath, Typed, ValueInfo, }; +use alloc::borrow::Cow; use std::{ any::{Any, TypeId}, fmt::Debug, @@ -334,8 +335,10 @@ pub trait Reflect: DynamicTypePath + Any + Send + Sync { /// let cloned = value.reflect_clone().unwrap(); /// assert!(cloned.is::<(i32, bool, f64)>()) /// ``` - fn reflect_clone(&self) -> Option> { - None + fn reflect_clone(&self) -> Result, ReflectCloneError> { + Err(ReflectCloneError::NotImplemented { + type_path: Cow::Owned(self.reflect_type_path().to_string()), + }) } /// Returns a hash of the value (which includes the type). diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index ddb5824807d99..2d7b039741348 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -3,8 +3,8 @@ use bevy_utils::all_tuples; use crate::{ self as bevy_reflect, utility::GenericTypePathCell, ApplyError, FromReflect, - GetTypeRegistration, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, - TypeRegistration, TypeRegistry, Typed, UnnamedField, + GetTypeRegistration, Reflect, ReflectCloneError, ReflectMut, ReflectOwned, ReflectRef, + TypeInfo, TypePath, TypeRegistration, TypeRegistry, Typed, UnnamedField, }; use crate::{ReflectKind, TypePathTable}; use std::any::{Any, TypeId}; @@ -596,10 +596,12 @@ macro_rules! impl_reflect_tuple { Box::new(self.clone_dynamic()) } - fn reflect_clone(&self) -> Option> { - Some(Box::new(( + fn reflect_clone(&self) -> Result, ReflectCloneError> { + Ok(Box::new(( $( - self.$index.reflect_clone()?.take::<$name>().ok()?, + self.$index.reflect_clone()? + .take::<$name>() + .expect("`Reflect::reflect_clone` should return the same type"), )* ))) } From 0a452bacc6d5dde67064ac188b6986c00b45caf9 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Wed, 22 May 2024 16:17:17 -0700 Subject: [PATCH 9/9] Fix CI errors --- crates/bevy_reflect/src/error.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/bevy_reflect/src/error.rs b/crates/bevy_reflect/src/error.rs index cc8193d8facd2..2f43d8d257118 100644 --- a/crates/bevy_reflect/src/error.rs +++ b/crates/bevy_reflect/src/error.rs @@ -30,7 +30,7 @@ pub enum ReflectCloneError { /// [deriving `Reflect`]: derive@crate::Reflect #[error( "field `{}` cannot be made clonable for `Reflect::reflect_clone` (are you missing a `#[reflect(clone)]` attribute?)", - full_path(.field, .variant, .container_type_path) + full_path(.field, .variant.as_deref(), .container_type_path) )] FieldNotClonable { field: FieldId, @@ -49,11 +49,7 @@ pub enum ReflectCloneError { }, } -fn full_path( - field: &FieldId, - variant: &Option>, - container_type_path: &Cow<'static, str>, -) -> String { +fn full_path(field: &FieldId, variant: Option<&str>, container_type_path: &str) -> String { match variant { Some(variant) => format!("{}::{}::{}", container_type_path, variant, field), None => format!("{}::{}", container_type_path, field),