Skip to content

Commit f5210c5

Browse files
authored
bevy_reflect: Reflection-based cloning (#13432)
# Objective Using `Reflect::clone_value` can be somewhat confusing to those unfamiliar with how Bevy's reflection crate works. For example take the following code: ```rust let value: usize = 123; let clone: Box<dyn Reflect> = value.clone_value(); ``` What can we expect to be the underlying type of `clone`? If you guessed `usize`, then you're correct! Let's try another: ```rust #[derive(Reflect, Clone)] struct Foo(usize); let value: Foo = Foo(123); let clone: Box<dyn Reflect> = value.clone_value(); ``` What about this code? What is the underlying type of `clone`? If you guessed `Foo`, unfortunately you'd be wrong. It's actually `DynamicStruct`. It's not obvious that the generated `Reflect` impl actually calls `Struct::clone_dynamic` under the hood, which always returns `DynamicStruct`. There are already some efforts to make this a bit more apparent to the end-user: #7207 changes the signature of `Reflect::clone_value` to instead return `Box<dyn PartialReflect>`, signaling that we're potentially returning a dynamic type. But why _can't_ we return `Foo`? `Foo` can obviously be cloned— in fact, we already derived `Clone` on it. But even without the derive, this seems like something `Reflect` should be able to handle. Almost all types that implement `Reflect` either contain no data (trivially clonable), they contain a `#[reflect_value]` type (which, by definition, must implement `Clone`), or they contain another `Reflect` type (which recursively fall into one of these three categories). This PR aims to enable true reflection-based cloning where you get back exactly the type that you think you do. ## Solution Add a `Reflect::reflect_clone` method which returns `Result<Box<dyn Reflect>, ReflectCloneError>`, where the `Box<dyn Reflect>` is guaranteed to be the same type as `Self`. ```rust #[derive(Reflect)] struct Foo(usize); let value: Foo = Foo(123); let clone: Box<dyn Reflect> = value.reflect_clone().unwrap(); assert!(clone.is::<Foo>()); ``` Notice that we didn't even need to derive `Clone` for this to work: it's entirely powered via reflection! Under the hood, the macro generates something like this: ```rust fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> { Ok(Box::new(Self { // The `reflect_clone` impl for `usize` just makes use of its `Clone` impl 0: Reflect::reflect_clone(&self.0)?.take().map_err(/* ... */)?, })) } ``` If we did derive `Clone`, we can tell `Reflect` to rely on that instead: ```rust #[derive(Reflect, Clone)] #[reflect(Clone)] struct Foo(usize); ``` <details> <summary>Generated Code</summary> ```rust fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> { Ok(Box::new(Clone::clone(self))) } ``` </details> Or, we can specify our own cloning function: ```rust #[derive(Reflect)] #[reflect(Clone(incremental_clone))] struct Foo(usize); fn incremental_clone(value: &usize) -> usize { *value + 1 } ``` <details> <summary>Generated Code</summary> ```rust fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> { Ok(Box::new(incremental_clone(self))) } ``` </details> Similarly, we can specify how fields should be cloned. This is important for fields that are `#[reflect(ignore)]`'d as we otherwise have no way to know how they should be cloned. ```rust #[derive(Reflect)] struct Foo { #[reflect(ignore, clone)] bar: usize, #[reflect(ignore, clone = "incremental_clone")] baz: usize, } fn incremental_clone(value: &usize) -> usize { *value + 1 } ``` <details> <summary>Generated Code</summary> ```rust fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> { Ok(Box::new(Self { bar: Clone::clone(&self.bar), baz: incremental_clone(&self.baz), })) } ``` </details> If we don't supply a `clone` attribute for an ignored field, then the method will automatically return `Err(ReflectCloneError::FieldNotClonable {/* ... */})`. `Err` values "bubble up" to the caller. So if `Foo` contains `Bar` and the `reflect_clone` method for `Bar` returns `Err`, then the `reflect_clone` method for `Foo` also returns `Err`. ### Attribute Syntax You might have noticed the differing syntax between the container attribute and the field attribute. This was purely done for consistency with the current attributes. There are PRs aimed at improving this. #7317 aims at making the "special-cased" attributes more in line with the field attributes syntactically. And #9323 aims at moving away from the stringified paths in favor of just raw function paths. ### Compatibility with Unique Reflect This PR was designed with Unique Reflect (#7207) in mind. This method actually wouldn't change that much (if at all) under Unique Reflect. It would still exist on `Reflect` and it would still `Option<Box<dyn Reflect>>`. In fact, Unique Reflect would only _improve_ the user's understanding of what this method returns. We may consider moving what's currently `Reflect::clone_value` to `PartialReflect` and possibly renaming it to `partial_reflect_clone` or `clone_dynamic` to better indicate how it differs from `reflect_clone`. ## Testing You can test locally by running the following command: ``` cargo test --package bevy_reflect ``` --- ## Changelog - Added `Reflect::reflect_clone` method - Added `ReflectCloneError` error enum - Added `#[reflect(Clone)]` container attribute - Added `#[reflect(clone)]` field attribute
1 parent 32d53e7 commit f5210c5

23 files changed

+1219
-173
lines changed

crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ mod incorrect_inner_type {
2626
//~| ERROR: `TheirInner<T>` does not implement `PartialReflect` so cannot be introspected
2727
//~| ERROR: `TheirInner<T>` does not implement `PartialReflect` so cannot be introspected
2828
//~| ERROR: `TheirInner<T>` does not implement `TypePath` so cannot provide dynamic type path information
29+
//~| ERROR: `TheirInner<T>` does not implement `TypePath` so cannot provide dynamic type path information
2930
//~| ERROR: `?` operator has incompatible types
3031
struct MyOuter<T: FromReflect + GetTypeRegistration> {
3132
// Reason: Should not use `MyInner<T>` directly

crates/bevy_reflect/derive/src/container_attributes.rs

+45-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
attribute_parser::terminated_parser, custom_attributes::CustomAttributes,
1010
derive_data::ReflectTraitToImpl,
1111
};
12-
use bevy_macro_utils::fq_std::{FQAny, FQOption};
12+
use bevy_macro_utils::fq_std::{FQAny, FQClone, FQOption, FQResult};
1313
use proc_macro2::{Ident, Span};
1414
use quote::quote_spanned;
1515
use syn::{
@@ -23,6 +23,7 @@ mod kw {
2323
syn::custom_keyword!(Debug);
2424
syn::custom_keyword!(PartialEq);
2525
syn::custom_keyword!(Hash);
26+
syn::custom_keyword!(Clone);
2627
syn::custom_keyword!(no_field_bounds);
2728
syn::custom_keyword!(opaque);
2829
}
@@ -175,6 +176,7 @@ impl TypePathAttrs {
175176
/// > __Note:__ Registering a custom function only works for special traits.
176177
#[derive(Default, Clone)]
177178
pub(crate) struct ContainerAttributes {
179+
clone: TraitImpl,
178180
debug: TraitImpl,
179181
hash: TraitImpl,
180182
partial_eq: TraitImpl,
@@ -236,12 +238,14 @@ impl ContainerAttributes {
236238
self.parse_opaque(input)
237239
} else if lookahead.peek(kw::no_field_bounds) {
238240
self.parse_no_field_bounds(input)
241+
} else if lookahead.peek(kw::Clone) {
242+
self.parse_clone(input)
239243
} else if lookahead.peek(kw::Debug) {
240244
self.parse_debug(input)
241-
} else if lookahead.peek(kw::PartialEq) {
242-
self.parse_partial_eq(input)
243245
} else if lookahead.peek(kw::Hash) {
244246
self.parse_hash(input)
247+
} else if lookahead.peek(kw::PartialEq) {
248+
self.parse_partial_eq(input)
245249
} else if lookahead.peek(Ident::peek_any) {
246250
self.parse_ident(input)
247251
} else {
@@ -274,6 +278,26 @@ impl ContainerAttributes {
274278
Ok(())
275279
}
276280

281+
/// Parse `clone` attribute.
282+
///
283+
/// Examples:
284+
/// - `#[reflect(Clone)]`
285+
/// - `#[reflect(Clone(custom_clone_fn))]`
286+
fn parse_clone(&mut self, input: ParseStream) -> syn::Result<()> {
287+
let ident = input.parse::<kw::Clone>()?;
288+
289+
if input.peek(token::Paren) {
290+
let content;
291+
parenthesized!(content in input);
292+
let path = content.parse::<Path>()?;
293+
self.clone.merge(TraitImpl::Custom(path, ident.span))?;
294+
} else {
295+
self.clone = TraitImpl::Implemented(ident.span);
296+
}
297+
298+
Ok(())
299+
}
300+
277301
/// Parse special `Debug` registration.
278302
///
279303
/// Examples:
@@ -523,6 +547,24 @@ impl ContainerAttributes {
523547
}
524548
}
525549

550+
pub fn get_clone_impl(&self, bevy_reflect_path: &Path) -> Option<proc_macro2::TokenStream> {
551+
match &self.clone {
552+
&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>
553+
#[inline]
554+
fn reflect_clone(&self) -> #FQResult<#bevy_reflect_path::__macro_exports::alloc_utils::Box<dyn #bevy_reflect_path::Reflect>, #bevy_reflect_path::ReflectCloneError> {
555+
#FQResult::Ok(#bevy_reflect_path::__macro_exports::alloc_utils::Box::new(#FQClone::clone(self)))
556+
}
557+
}),
558+
&TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=>
559+
#[inline]
560+
fn reflect_clone(&self) -> #FQResult<#bevy_reflect_path::__macro_exports::alloc_utils::Box<dyn #bevy_reflect_path::Reflect>, #bevy_reflect_path::ReflectCloneError> {
561+
#FQResult::Ok(#bevy_reflect_path::__macro_exports::alloc_utils::Box::new(#impl_fn(self)))
562+
}
563+
}),
564+
TraitImpl::NotImplemented => None,
565+
}
566+
}
567+
526568
pub fn custom_attributes(&self) -> &CustomAttributes {
527569
&self.custom_attributes
528570
}

crates/bevy_reflect/derive/src/derive_data.rs

+203-3
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@ use crate::{
1212
where_clause_options::WhereClauseOptions,
1313
REFLECT_ATTRIBUTE_NAME, TYPE_NAME_ATTRIBUTE_NAME, TYPE_PATH_ATTRIBUTE_NAME,
1414
};
15-
use quote::{quote, ToTokens};
15+
use quote::{format_ident, quote, ToTokens};
1616
use syn::token::Comma;
1717

18+
use crate::enum_utility::{EnumVariantOutputData, ReflectCloneVariantBuilder, VariantBuilder};
19+
use crate::field_attributes::CloneBehavior;
1820
use crate::generics::generate_generics;
21+
use bevy_macro_utils::fq_std::{FQClone, FQOption, FQResult};
1922
use syn::{
2023
parse_str, punctuated::Punctuated, spanned::Spanned, Data, DeriveInput, Field, Fields,
21-
GenericParam, Generics, Ident, LitStr, Meta, Path, PathSegment, Type, TypeParam, Variant,
24+
GenericParam, Generics, Ident, LitStr, Member, Meta, Path, PathSegment, Type, TypeParam,
25+
Variant,
2226
};
2327

2428
pub(crate) enum ReflectDerive<'a> {
@@ -266,7 +270,7 @@ impl<'a> ReflectDerive<'a> {
266270
{
267271
return Err(syn::Error::new(
268272
meta.type_path().span(),
269-
format!("a #[{TYPE_PATH_ATTRIBUTE_NAME} = \"...\"] attribute must be specified when using {provenance}")
273+
format!("a #[{TYPE_PATH_ATTRIBUTE_NAME} = \"...\"] attribute must be specified when using {provenance}"),
270274
));
271275
}
272276

@@ -546,6 +550,31 @@ impl<'a> StructField<'a> {
546550
pub fn attrs(&self) -> &FieldAttributes {
547551
&self.attrs
548552
}
553+
554+
/// Generates a [`Member`] based on this field.
555+
///
556+
/// If the field is unnamed, the declaration index is used.
557+
/// This allows this member to be used for both active and ignored fields.
558+
pub fn to_member(&self) -> Member {
559+
match &self.data.ident {
560+
Some(ident) => Member::Named(ident.clone()),
561+
None => Member::Unnamed(self.declaration_index.into()),
562+
}
563+
}
564+
565+
/// Returns a token stream for generating a `FieldId` for this field.
566+
pub fn field_id(&self, bevy_reflect_path: &Path) -> proc_macro2::TokenStream {
567+
match &self.data.ident {
568+
Some(ident) => {
569+
let name = ident.to_string();
570+
quote!(#bevy_reflect_path::FieldId::Named(#bevy_reflect_path::__macro_exports::alloc_utils::Cow::Borrowed(#name)))
571+
}
572+
None => {
573+
let index = self.declaration_index;
574+
quote!(#bevy_reflect_path::FieldId::Unnamed(#index))
575+
}
576+
}
577+
}
549578
}
550579

551580
impl<'a> ReflectStruct<'a> {
@@ -655,6 +684,135 @@ impl<'a> ReflectStruct<'a> {
655684
#bevy_reflect_path::TypeInfo::#info_variant(#info)
656685
}
657686
}
687+
/// Returns the `Reflect::reflect_clone` impl, if any, as a `TokenStream`.
688+
pub fn get_clone_impl(&self) -> Option<proc_macro2::TokenStream> {
689+
let bevy_reflect_path = self.meta().bevy_reflect_path();
690+
691+
if let container_clone @ Some(_) = self.meta().attrs().get_clone_impl(bevy_reflect_path) {
692+
return container_clone;
693+
}
694+
695+
let mut tokens = proc_macro2::TokenStream::new();
696+
697+
for field in self.fields().iter() {
698+
let field_ty = field.reflected_type();
699+
let member = field.to_member();
700+
let accessor = self.access_for_field(field, false);
701+
702+
match &field.attrs.clone {
703+
CloneBehavior::Default => {
704+
let value = if field.attrs.ignore.is_ignored() {
705+
let field_id = field.field_id(bevy_reflect_path);
706+
707+
quote! {
708+
return #FQResult::Err(#bevy_reflect_path::ReflectCloneError::FieldNotCloneable {
709+
field: #field_id,
710+
variant: #FQOption::None,
711+
container_type_path: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Borrowed(
712+
<Self as #bevy_reflect_path::TypePath>::type_path()
713+
)
714+
})
715+
}
716+
} else {
717+
quote! {
718+
#bevy_reflect_path::PartialReflect::reflect_clone(#accessor)?
719+
.take()
720+
.map_err(|value| #bevy_reflect_path::ReflectCloneError::FailedDowncast {
721+
expected: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Borrowed(
722+
<#field_ty as #bevy_reflect_path::TypePath>::type_path()
723+
),
724+
received: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Owned(
725+
#bevy_reflect_path::__macro_exports::alloc_utils::ToString::to_string(
726+
#bevy_reflect_path::DynamicTypePath::reflect_type_path(&*value)
727+
)
728+
),
729+
})?
730+
}
731+
};
732+
733+
tokens.extend(quote! {
734+
#member: #value,
735+
});
736+
}
737+
CloneBehavior::Trait => {
738+
tokens.extend(quote! {
739+
#member: #FQClone::clone(#accessor),
740+
});
741+
}
742+
CloneBehavior::Func(clone_fn) => {
743+
tokens.extend(quote! {
744+
#member: #clone_fn(#accessor),
745+
});
746+
}
747+
}
748+
}
749+
750+
let ctor = match self.meta.remote_ty() {
751+
Some(ty) => {
752+
let ty = ty.as_expr_path().ok()?.to_token_stream();
753+
quote! {
754+
Self(#ty {
755+
#tokens
756+
})
757+
}
758+
}
759+
None => {
760+
quote! {
761+
Self {
762+
#tokens
763+
}
764+
}
765+
}
766+
};
767+
768+
Some(quote! {
769+
#[inline]
770+
#[allow(unreachable_code, reason = "Ignored fields without a `clone` attribute will early-return with an error")]
771+
fn reflect_clone(&self) -> #FQResult<#bevy_reflect_path::__macro_exports::alloc_utils::Box<dyn #bevy_reflect_path::Reflect>, #bevy_reflect_path::ReflectCloneError> {
772+
#FQResult::Ok(#bevy_reflect_path::__macro_exports::alloc_utils::Box::new(#ctor))
773+
}
774+
})
775+
}
776+
777+
/// Generates an accessor for the given field.
778+
///
779+
/// The mutability of the access can be controlled by the `is_mut` parameter.
780+
///
781+
/// Generally, this just returns something like `&self.field`.
782+
/// However, if the struct is a remote wrapper, this then becomes `&self.0.field` in order to access the field on the inner type.
783+
///
784+
/// If the field itself is a remote type, the above accessor is further wrapped in a call to `ReflectRemote::as_wrapper[_mut]`.
785+
pub fn access_for_field(
786+
&self,
787+
field: &StructField<'a>,
788+
is_mutable: bool,
789+
) -> proc_macro2::TokenStream {
790+
let bevy_reflect_path = self.meta().bevy_reflect_path();
791+
let member = field.to_member();
792+
793+
let prefix_tokens = if is_mutable { quote!(&mut) } else { quote!(&) };
794+
795+
let accessor = if self.meta.is_remote_wrapper() {
796+
quote!(self.0.#member)
797+
} else {
798+
quote!(self.#member)
799+
};
800+
801+
match &field.attrs.remote {
802+
Some(wrapper_ty) => {
803+
let method = if is_mutable {
804+
format_ident!("as_wrapper_mut")
805+
} else {
806+
format_ident!("as_wrapper")
807+
};
808+
809+
quote! {
810+
<#wrapper_ty as #bevy_reflect_path::ReflectRemote>::#method(#prefix_tokens #accessor)
811+
}
812+
}
813+
None => quote!(#prefix_tokens #accessor),
814+
}
815+
}
658816
}
659817

660818
impl<'a> ReflectEnum<'a> {
@@ -757,6 +915,48 @@ impl<'a> ReflectEnum<'a> {
757915
#bevy_reflect_path::TypeInfo::Enum(#info)
758916
}
759917
}
918+
919+
/// Returns the `Reflect::reflect_clone` impl, if any, as a `TokenStream`.
920+
pub fn get_clone_impl(&self) -> Option<proc_macro2::TokenStream> {
921+
let bevy_reflect_path = self.meta().bevy_reflect_path();
922+
923+
if let container_clone @ Some(_) = self.meta().attrs().get_clone_impl(bevy_reflect_path) {
924+
return container_clone;
925+
}
926+
927+
let this = Ident::new("this", Span::call_site());
928+
let EnumVariantOutputData {
929+
variant_patterns,
930+
variant_constructors,
931+
..
932+
} = ReflectCloneVariantBuilder::new(self).build(&this);
933+
934+
let inner = quote! {
935+
match #this {
936+
#(#variant_patterns => #variant_constructors),*
937+
}
938+
};
939+
940+
let body = if self.meta.is_remote_wrapper() {
941+
quote! {
942+
let #this = <Self as #bevy_reflect_path::ReflectRemote>::as_remote(self);
943+
#FQResult::Ok(#bevy_reflect_path::__macro_exports::alloc_utils::Box::new(<Self as #bevy_reflect_path::ReflectRemote>::into_wrapper(#inner)))
944+
}
945+
} else {
946+
quote! {
947+
let #this = self;
948+
#FQResult::Ok(#bevy_reflect_path::__macro_exports::alloc_utils::Box::new(#inner))
949+
}
950+
};
951+
952+
Some(quote! {
953+
#[inline]
954+
#[allow(unreachable_code, reason = "Ignored fields without a `clone` attribute will early-return with an error")]
955+
fn reflect_clone(&self) -> #FQResult<#bevy_reflect_path::__macro_exports::alloc_utils::Box<dyn #bevy_reflect_path::Reflect>, #bevy_reflect_path::ReflectCloneError> {
956+
#body
957+
}
958+
})
959+
}
760960
}
761961

762962
impl<'a> EnumVariant<'a> {

0 commit comments

Comments
 (0)