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
This commit is contained in:
Gino Valente 2025-03-10 23:02:59 -07:00 committed by GitHub
parent 32d53e7bd3
commit f5210c54d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 1220 additions and 174 deletions

View File

@ -26,6 +26,7 @@ mod incorrect_inner_type {
//~| ERROR: `TheirInner<T>` does not implement `PartialReflect` so cannot be introspected
//~| ERROR: `TheirInner<T>` does not implement `PartialReflect` so cannot be introspected
//~| ERROR: `TheirInner<T>` does not implement `TypePath` so cannot provide dynamic type path information
//~| ERROR: `TheirInner<T>` does not implement `TypePath` so cannot provide dynamic type path information
//~| ERROR: `?` operator has incompatible types
struct MyOuter<T: FromReflect + GetTypeRegistration> {
// Reason: Should not use `MyInner<T>` directly

View File

@ -9,7 +9,7 @@ use crate::{
attribute_parser::terminated_parser, custom_attributes::CustomAttributes,
derive_data::ReflectTraitToImpl,
};
use bevy_macro_utils::fq_std::{FQAny, FQOption};
use bevy_macro_utils::fq_std::{FQAny, FQClone, FQOption, FQResult};
use proc_macro2::{Ident, Span};
use quote::quote_spanned;
use syn::{
@ -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);
syn::custom_keyword!(opaque);
}
@ -175,6 +176,7 @@ impl TypePathAttrs {
/// > __Note:__ Registering a custom function only works for special traits.
#[derive(Default, Clone)]
pub(crate) struct ContainerAttributes {
clone: TraitImpl,
debug: TraitImpl,
hash: TraitImpl,
partial_eq: TraitImpl,
@ -236,12 +238,14 @@ impl ContainerAttributes {
self.parse_opaque(input)
} 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 {
@ -274,6 +278,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::<kw::Clone>()?;
if input.peek(token::Paren) {
let content;
parenthesized!(content in input);
let path = content.parse::<Path>()?;
self.clone.merge(TraitImpl::Custom(path, ident.span))?;
} else {
self.clone = TraitImpl::Implemented(ident.span);
}
Ok(())
}
/// Parse special `Debug` registration.
///
/// Examples:
@ -523,6 +547,24 @@ impl ContainerAttributes {
}
}
pub fn get_clone_impl(&self, bevy_reflect_path: &Path) -> Option<proc_macro2::TokenStream> {
match &self.clone {
&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>
#[inline]
fn reflect_clone(&self) -> #FQResult<#bevy_reflect_path::__macro_exports::alloc_utils::Box<dyn #bevy_reflect_path::Reflect>, #bevy_reflect_path::ReflectCloneError> {
#FQResult::Ok(#bevy_reflect_path::__macro_exports::alloc_utils::Box::new(#FQClone::clone(self)))
}
}),
&TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=>
#[inline]
fn reflect_clone(&self) -> #FQResult<#bevy_reflect_path::__macro_exports::alloc_utils::Box<dyn #bevy_reflect_path::Reflect>, #bevy_reflect_path::ReflectCloneError> {
#FQResult::Ok(#bevy_reflect_path::__macro_exports::alloc_utils::Box::new(#impl_fn(self)))
}
}),
TraitImpl::NotImplemented => None,
}
}
pub fn custom_attributes(&self) -> &CustomAttributes {
&self.custom_attributes
}

View File

@ -12,13 +12,17 @@ use crate::{
where_clause_options::WhereClauseOptions,
REFLECT_ATTRIBUTE_NAME, TYPE_NAME_ATTRIBUTE_NAME, TYPE_PATH_ATTRIBUTE_NAME,
};
use quote::{quote, ToTokens};
use quote::{format_ident, quote, ToTokens};
use syn::token::Comma;
use crate::enum_utility::{EnumVariantOutputData, ReflectCloneVariantBuilder, VariantBuilder};
use crate::field_attributes::CloneBehavior;
use crate::generics::generate_generics;
use bevy_macro_utils::fq_std::{FQClone, FQOption, FQResult};
use syn::{
parse_str, punctuated::Punctuated, spanned::Spanned, Data, DeriveInput, Field, Fields,
GenericParam, Generics, Ident, LitStr, Meta, Path, PathSegment, Type, TypeParam, Variant,
GenericParam, Generics, Ident, LitStr, Member, Meta, Path, PathSegment, Type, TypeParam,
Variant,
};
pub(crate) enum ReflectDerive<'a> {
@ -266,7 +270,7 @@ impl<'a> ReflectDerive<'a> {
{
return Err(syn::Error::new(
meta.type_path().span(),
format!("a #[{TYPE_PATH_ATTRIBUTE_NAME} = \"...\"] attribute must be specified when using {provenance}")
format!("a #[{TYPE_PATH_ATTRIBUTE_NAME} = \"...\"] attribute must be specified when using {provenance}"),
));
}
@ -546,6 +550,31 @@ impl<'a> StructField<'a> {
pub fn attrs(&self) -> &FieldAttributes {
&self.attrs
}
/// Generates a [`Member`] based on this field.
///
/// If the field is unnamed, the declaration index is used.
/// This allows this member to be used for both active and ignored fields.
pub fn to_member(&self) -> Member {
match &self.data.ident {
Some(ident) => Member::Named(ident.clone()),
None => Member::Unnamed(self.declaration_index.into()),
}
}
/// 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(#bevy_reflect_path::__macro_exports::alloc_utils::Cow::Borrowed(#name)))
}
None => {
let index = self.declaration_index;
quote!(#bevy_reflect_path::FieldId::Unnamed(#index))
}
}
}
}
impl<'a> ReflectStruct<'a> {
@ -655,6 +684,135 @@ 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<proc_macro2::TokenStream> {
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().iter() {
let field_ty = field.reflected_type();
let member = field.to_member();
let accessor = self.access_for_field(field, false);
match &field.attrs.clone {
CloneBehavior::Default => {
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::FieldNotCloneable {
field: #field_id,
variant: #FQOption::None,
container_type_path: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Borrowed(
<Self as #bevy_reflect_path::TypePath>::type_path()
)
})
}
} else {
quote! {
#bevy_reflect_path::PartialReflect::reflect_clone(#accessor)?
.take()
.map_err(|value| #bevy_reflect_path::ReflectCloneError::FailedDowncast {
expected: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Borrowed(
<#field_ty as #bevy_reflect_path::TypePath>::type_path()
),
received: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Owned(
#bevy_reflect_path::__macro_exports::alloc_utils::ToString::to_string(
#bevy_reflect_path::DynamicTypePath::reflect_type_path(&*value)
)
),
})?
}
};
tokens.extend(quote! {
#member: #value,
});
}
CloneBehavior::Trait => {
tokens.extend(quote! {
#member: #FQClone::clone(#accessor),
});
}
CloneBehavior::Func(clone_fn) => {
tokens.extend(quote! {
#member: #clone_fn(#accessor),
});
}
}
}
let ctor = match self.meta.remote_ty() {
Some(ty) => {
let ty = ty.as_expr_path().ok()?.to_token_stream();
quote! {
Self(#ty {
#tokens
})
}
}
None => {
quote! {
Self {
#tokens
}
}
}
};
Some(quote! {
#[inline]
#[allow(unreachable_code, reason = "Ignored fields without a `clone` attribute will early-return with an error")]
fn reflect_clone(&self) -> #FQResult<#bevy_reflect_path::__macro_exports::alloc_utils::Box<dyn #bevy_reflect_path::Reflect>, #bevy_reflect_path::ReflectCloneError> {
#FQResult::Ok(#bevy_reflect_path::__macro_exports::alloc_utils::Box::new(#ctor))
}
})
}
/// Generates an accessor for the given field.
///
/// The mutability of the access can be controlled by the `is_mut` parameter.
///
/// Generally, this just returns something like `&self.field`.
/// However, if the struct is a remote wrapper, this then becomes `&self.0.field` in order to access the field on the inner type.
///
/// If the field itself is a remote type, the above accessor is further wrapped in a call to `ReflectRemote::as_wrapper[_mut]`.
pub fn access_for_field(
&self,
field: &StructField<'a>,
is_mutable: bool,
) -> proc_macro2::TokenStream {
let bevy_reflect_path = self.meta().bevy_reflect_path();
let member = field.to_member();
let prefix_tokens = if is_mutable { quote!(&mut) } else { quote!(&) };
let accessor = if self.meta.is_remote_wrapper() {
quote!(self.0.#member)
} else {
quote!(self.#member)
};
match &field.attrs.remote {
Some(wrapper_ty) => {
let method = if is_mutable {
format_ident!("as_wrapper_mut")
} else {
format_ident!("as_wrapper")
};
quote! {
<#wrapper_ty as #bevy_reflect_path::ReflectRemote>::#method(#prefix_tokens #accessor)
}
}
None => quote!(#prefix_tokens #accessor),
}
}
}
impl<'a> ReflectEnum<'a> {
@ -757,6 +915,48 @@ 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<proc_macro2::TokenStream> {
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("this", Span::call_site());
let EnumVariantOutputData {
variant_patterns,
variant_constructors,
..
} = ReflectCloneVariantBuilder::new(self).build(&this);
let inner = quote! {
match #this {
#(#variant_patterns => #variant_constructors),*
}
};
let body = if self.meta.is_remote_wrapper() {
quote! {
let #this = <Self as #bevy_reflect_path::ReflectRemote>::as_remote(self);
#FQResult::Ok(#bevy_reflect_path::__macro_exports::alloc_utils::Box::new(<Self as #bevy_reflect_path::ReflectRemote>::into_wrapper(#inner)))
}
} else {
quote! {
let #this = self;
#FQResult::Ok(#bevy_reflect_path::__macro_exports::alloc_utils::Box::new(#inner))
}
};
Some(quote! {
#[inline]
#[allow(unreachable_code, reason = "Ignored fields without a `clone` attribute will early-return with an error")]
fn reflect_clone(&self) -> #FQResult<#bevy_reflect_path::__macro_exports::alloc_utils::Box<dyn #bevy_reflect_path::Reflect>, #bevy_reflect_path::ReflectCloneError> {
#body
}
})
}
}
impl<'a> EnumVariant<'a> {

View File

@ -1,16 +1,21 @@
use crate::field_attributes::CloneBehavior;
use crate::{
derive_data::ReflectEnum, derive_data::StructField, field_attributes::DefaultBehavior,
ident::ident_or_index,
};
use bevy_macro_utils::fq_std::{FQDefault, FQOption};
use bevy_macro_utils::fq_std::{FQClone, FQDefault, FQOption, FQResult};
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use quote::{format_ident, quote, ToTokens};
pub(crate) struct EnumVariantOutputData {
/// The names of each variant as a string.
///
/// For example, `Some` and `None` for the `Option` enum.
pub variant_names: Vec<String>,
/// The pattern matching portion of each variant.
///
/// For example, `Option::Some { 0: _0 }` and `Option::None {}` for the `Option` enum.
pub variant_patterns: Vec<TokenStream>,
/// The constructor portion of each variant.
///
/// For example, `Option::Some { 0: value }` and `Option::None {}` for the `Option` enum.
@ -139,6 +144,7 @@ pub(crate) trait VariantBuilder: Sized {
let variants = self.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());
for variant in variants {
@ -148,7 +154,10 @@ pub(crate) trait VariantBuilder: Sized {
let fields = variant.fields();
let field_constructors = fields.iter().map(|field| {
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);
@ -164,12 +173,18 @@ pub(crate) trait VariantBuilder: Sized {
self.on_active_field(this, variant_field)
};
let constructor = quote! {
#member: #value
};
field_patterns.push(quote! {
#member: #alias
});
constructor
});
field_constructors.push(quote! {
#member: #value
});
}
let pattern = quote! {
#variant_path { #( #field_patterns ),* }
};
let constructor = quote! {
#variant_path {
@ -178,11 +193,13 @@ pub(crate) trait VariantBuilder: Sized {
};
variant_names.push(variant_name);
variant_patterns.push(pattern);
variant_constructors.push(constructor);
}
EnumVariantOutputData {
variant_names,
variant_patterns,
variant_constructors,
}
}
@ -275,3 +292,103 @@ impl<'a> VariantBuilder for TryApplyVariantBuilder<'a> {
}
}
}
/// 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> {
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 field_ty = field.field.reflected_type();
let alias = field.alias;
let alias = match &field.field.attrs.remote {
Some(wrapper_ty) => {
quote! {
<#wrapper_ty as #bevy_reflect_path::ReflectRemote>::as_wrapper(#alias)
}
}
None => alias.to_token_stream(),
};
match &field.field.attrs.clone {
CloneBehavior::Default => {
quote! {
#bevy_reflect_path::PartialReflect::reflect_clone(#alias)?
.take()
.map_err(|value| #bevy_reflect_path::ReflectCloneError::FailedDowncast {
expected: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Borrowed(
<#field_ty as #bevy_reflect_path::TypePath>::type_path()
),
received: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Owned(
#bevy_reflect_path::__macro_exports::alloc_utils::ToString::to_string(
#bevy_reflect_path::DynamicTypePath::reflect_type_path(&*value)
)
),
})?
}
}
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) -> 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 => {
let field_id = field.field.field_id(bevy_reflect_path);
quote! {
return #FQResult::Err(
#bevy_reflect_path::ReflectCloneError::FieldNotCloneable {
field: #field_id,
variant: #FQOption::Some(#bevy_reflect_path::__macro_exports::alloc_utils::Cow::Borrowed(#variant_name)),
container_type_path: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Borrowed(<Self as #bevy_reflect_path::TypePath>::type_path())
}
)
}
}
CloneBehavior::Trait => quote! { #FQClone::clone(#alias) },
CloneBehavior::Func(clone_fn) => quote! { #clone_fn() },
}
}
}

View File

@ -14,6 +14,7 @@ use syn::{parse::ParseStream, Attribute, LitStr, Meta, Token, Type};
mod kw {
syn::custom_keyword!(ignore);
syn::custom_keyword!(skip_serializing);
syn::custom_keyword!(clone);
syn::custom_keyword!(default);
syn::custom_keyword!(remote);
}
@ -22,6 +23,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
///
@ -54,6 +56,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 {
@ -74,6 +84,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(@...)]`.
@ -121,6 +133,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 if lookahead.peek(kw::remote) {
@ -164,6 +178,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::<kw::clone>()?;
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
let lit = input.parse::<LitStr>()?;
self.clone = CloneBehavior::Func(lit.parse()?);
} else {
self.clone = CloneBehavior::Trait;
}
Ok(())
}
/// Parse `default` attribute.
///
/// Examples:

View File

@ -70,6 +70,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
|| Some(quote!(#bevy_reflect_path::enum_partial_eq)),
|| Some(quote!(#bevy_reflect_path::enum_hash)),
);
let clone_fn = reflect_enum.get_clone_impl();
#[cfg(not(feature = "functions"))]
let function_impls = None::<proc_macro2::TokenStream>;
@ -261,6 +262,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
}
#common_methods
#clone_fn
}
}
}

View File

@ -32,6 +32,7 @@ pub(crate) fn impl_opaque(meta: &ReflectMeta) -> proc_macro2::TokenStream {
let type_path_impl = impl_type_path(meta);
let full_reflect_impl = impl_full_reflect(meta, &where_clause_options);
let common_methods = common_partial_reflect_methods(meta, || None, || None);
let clone_fn = meta.attrs().get_clone_impl(bevy_reflect_path);
let apply_impl = if let Some(remote_ty) = meta.remote_ty() {
let ty = remote_ty.type_path();
@ -117,6 +118,8 @@ pub(crate) fn impl_opaque(meta: &ReflectMeta) -> proc_macro2::TokenStream {
}
#common_methods
#clone_fn
}
}
}

View File

@ -47,6 +47,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS
|| Some(quote!(#bevy_reflect_path::struct_partial_eq)),
|| None,
);
let clone_fn = reflect_struct.get_clone_impl();
#[cfg(not(feature = "functions"))]
let function_impls = None::<proc_macro2::TokenStream>;
@ -179,6 +180,8 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS
}
#common_methods
#clone_fn
}
}
}

View File

@ -37,6 +37,7 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::
|| Some(quote!(#bevy_reflect_path::tuple_struct_partial_eq)),
|| None,
);
let clone_fn = reflect_struct.get_clone_impl();
#[cfg(not(feature = "functions"))]
let function_impls = None::<proc_macro2::TokenStream>;
@ -144,6 +145,8 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::
}
#common_methods
#clone_fn
}
}
}

View File

@ -156,20 +156,25 @@ 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
/// `my_debug_func` is the path to a function matching the signature:
/// `(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::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.
@ -339,6 +344,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`.

View File

@ -1,5 +1,4 @@
use crate::{derive_data::StructField, ReflectStruct};
use quote::quote;
use crate::ReflectStruct;
/// A helper struct for creating remote-aware field accessors.
///
@ -20,27 +19,15 @@ pub(crate) struct FieldAccessors {
impl FieldAccessors {
pub fn new(reflect_struct: &ReflectStruct) -> Self {
let bevy_reflect_path = reflect_struct.meta().bevy_reflect_path();
let fields_ref = Self::get_fields(reflect_struct, |field, accessor| {
match &field.attrs.remote {
Some(wrapper_ty) => {
quote! {
<#wrapper_ty as #bevy_reflect_path::ReflectRemote>::as_wrapper(&#accessor)
}
}
None => quote!(& #accessor),
}
});
let fields_mut = Self::get_fields(reflect_struct, |field, accessor| {
match &field.attrs.remote {
Some(wrapper_ty) => {
quote! {
<#wrapper_ty as #bevy_reflect_path::ReflectRemote>::as_wrapper_mut(&mut #accessor)
}
}
None => quote!(&mut #accessor),
}
});
let (fields_ref, fields_mut): (Vec<_>, Vec<_>) = reflect_struct
.active_fields()
.map(|field| {
(
reflect_struct.access_for_field(field, false),
reflect_struct.access_for_field(field, true),
)
})
.unzip();
let field_count = fields_ref.len();
let field_indices = (0..field_count).collect();
@ -52,30 +39,4 @@ impl FieldAccessors {
field_count,
}
}
fn get_fields<F>(
reflect_struct: &ReflectStruct,
mut wrapper_fn: F,
) -> Vec<proc_macro2::TokenStream>
where
F: FnMut(&StructField, proc_macro2::TokenStream) -> proc_macro2::TokenStream,
{
let is_remote = reflect_struct.meta().is_remote_wrapper();
reflect_struct
.active_fields()
.map(|field| {
let member = crate::ident::ident_or_index(
field.data.ident.as_ref(),
field.declaration_index,
);
let accessor = if is_remote {
quote!(self.0.#member)
} else {
quote!(self.#member)
};
wrapper_fn(field, accessor)
})
.collect::<Vec<_>>()
}
}

View File

@ -0,0 +1,61 @@
use crate::FieldId;
use alloc::{borrow::Cow, format};
use thiserror::Error;
/// An error that occurs when cloning a type via [`PartialReflect::reflect_clone`].
///
/// [`PartialReflect::reflect_clone`]: crate::PartialReflect::reflect_clone
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum ReflectCloneError {
/// The type does not have a custom implementation for [`PartialReflect::reflect_clone`].
///
/// [`PartialReflect::reflect_clone`]: crate::PartialReflect::reflect_clone
#[error("`PartialReflect::reflect_clone` not implemented for `{type_path}`")]
NotImplemented { type_path: Cow<'static, str> },
/// The type cannot be cloned via [`PartialReflect::reflect_clone`].
///
/// This type should be returned when a type is intentionally opting out of reflection cloning.
///
/// [`PartialReflect::reflect_clone`]: crate::PartialReflect::reflect_clone
#[error("`{type_path}` cannot be made cloneable for `PartialReflect::reflect_clone`")]
NotCloneable { type_path: Cow<'static, str> },
/// The field cannot be cloned via [`PartialReflect::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.
///
/// [`PartialReflect::reflect_clone`]: crate::PartialReflect::reflect_clone
/// [deriving `Reflect`]: derive@crate::Reflect
#[error(
"field `{}` cannot be made cloneable for `PartialReflect::reflect_clone` (are you missing a `#[reflect(clone)]` attribute?)",
full_path(.field, .variant.as_deref(), .container_type_path)
)]
FieldNotCloneable {
field: FieldId,
variant: Option<Cow<'static, str>>,
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<&str>,
container_type_path: &str,
) -> alloc::string::String {
match variant {
Some(variant) => format!("{}::{}::{}", container_type_path, variant, field),
None => format!("{}::{}", container_type_path, field),
}
}

View File

@ -3,7 +3,9 @@ use crate::{
type_info::impl_type_methods,
MaybeTyped, PartialReflect, Type, TypeInfo, TypePath,
};
use alloc::borrow::Cow;
use bevy_platform_support::sync::Arc;
use core::fmt::{Display, Formatter};
/// The named field of a reflected struct.
#[derive(Clone, Debug)]
@ -129,3 +131,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(Cow<'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),
}
}
}

View File

@ -18,7 +18,7 @@ macro_rules! reflect_enum {
}
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct IVec2 {
x: i32,
@ -26,7 +26,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct IVec3 {
x: i32,
@ -35,7 +35,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct IVec4 {
x: i32,
@ -46,7 +46,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct I8Vec2 {
x: i8,
@ -55,7 +55,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct I8Vec3 {
x: i8,
@ -65,7 +65,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct I8Vec4 {
x: i8,
@ -76,7 +76,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct I16Vec2 {
x: i16,
@ -85,7 +85,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct I16Vec3 {
x: i16,
@ -95,7 +95,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct I16Vec4 {
x: i16,
@ -106,7 +106,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct I64Vec2 {
x: i64,
@ -115,7 +115,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct I64Vec3 {
x: i64,
@ -125,7 +125,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct I64Vec4 {
x: i64,
@ -136,7 +136,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct UVec2 {
x: u32,
@ -144,7 +144,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct UVec3 {
x: u32,
@ -153,7 +153,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct UVec4 {
x: u32,
@ -164,7 +164,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct U8Vec2 {
x: u8,
@ -172,7 +172,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct U8Vec3 {
x: u8,
@ -181,7 +181,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct U8Vec4 {
x: u8,
@ -192,7 +192,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct U16Vec2 {
x: u16,
@ -200,7 +200,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct U16Vec3 {
x: u16,
@ -209,7 +209,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct U16Vec4 {
x: u16,
@ -220,7 +220,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct U64Vec2 {
x: u64,
@ -228,7 +228,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct U64Vec3 {
x: u64,
@ -237,7 +237,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, Hash, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct U64Vec4 {
x: u64,
@ -248,7 +248,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct Vec2 {
x: f32,
@ -256,7 +256,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct Vec3 {
x: f32,
@ -265,7 +265,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct Vec3A {
x: f32,
@ -274,7 +274,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct Vec4 {
x: f32,
@ -285,7 +285,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct BVec2 {
x: bool,
@ -293,7 +293,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct BVec3 {
x: bool,
@ -302,7 +302,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct BVec4 {
x: bool,
@ -313,7 +313,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct DVec2 {
x: f64,
@ -321,7 +321,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct DVec3 {
x: f64,
@ -330,7 +330,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct DVec4 {
x: f64,
@ -341,7 +341,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct Mat2 {
x_axis: Vec2,
@ -349,7 +349,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct Mat3 {
x_axis: Vec3,
@ -358,7 +358,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct Mat3A {
x_axis: Vec3A,
@ -367,7 +367,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct Mat4 {
x_axis: Vec4,
@ -378,7 +378,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct DMat2 {
x_axis: DVec2,
@ -386,7 +386,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct DMat3 {
x_axis: DVec3,
@ -395,7 +395,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct DMat4 {
x_axis: DVec4,
@ -406,7 +406,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct Affine2 {
matrix2: Mat2,
@ -414,7 +414,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct Affine3A {
matrix3: Mat3A,
@ -423,7 +423,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct DAffine2 {
matrix2: DMat2,
@ -431,7 +431,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct DAffine3 {
matrix3: DMat3,
@ -440,7 +440,7 @@ impl_reflect!(
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct Quat {
x: f32,
@ -450,7 +450,7 @@ impl_reflect!(
}
);
impl_reflect!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
struct DQuat {
x: f64,
@ -461,7 +461,7 @@ impl_reflect!(
);
reflect_enum!(
#[reflect(Debug, PartialEq, Default, Deserialize, Serialize)]
#[reflect(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
#[type_path = "glam"]
enum EulerRot {
ZYX,
@ -491,8 +491,20 @@ reflect_enum!(
}
);
impl_reflect_opaque!(::glam::BVec3A(Debug, Default, Deserialize, Serialize));
impl_reflect_opaque!(::glam::BVec4A(Debug, Default, Deserialize, Serialize));
impl_reflect_opaque!(::glam::BVec3A(
Clone,
Debug,
Default,
Deserialize,
Serialize
));
impl_reflect_opaque!(::glam::BVec4A(
Clone,
Debug,
Default,
Deserialize,
Serialize
));
#[cfg(test)]
mod tests {

View File

@ -1,6 +1,7 @@
use crate::{impl_reflect_opaque, prelude::ReflectDefault, ReflectDeserialize, ReflectSerialize};
impl_reflect_opaque!(::petgraph::graph::NodeIndex(
Clone,
Default,
Serialize,
Deserialize
@ -9,4 +10,4 @@ impl_reflect_opaque!(::petgraph::graph::DiGraph<
N: ::core::clone::Clone,
E: ::core::clone::Clone,
Ix: ::petgraph::graph::IndexType
>());
>(Clone));

View File

@ -1,14 +1,14 @@
use alloc::{boxed::Box, vec::Vec};
use bevy_reflect_derive::impl_type_path;
use core::any::Any;
use smallvec::{Array as SmallArray, SmallVec};
use crate::{
utility::GenericTypeInfoCell, ApplyError, FromReflect, FromType, Generics, GetTypeRegistration,
List, ListInfo, ListIter, MaybeTyped, PartialReflect, Reflect, ReflectFromPtr, ReflectKind,
ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypeParamInfo, TypePath, TypeRegistration,
Typed,
};
use alloc::{borrow::Cow, boxed::Box, string::ToString, vec::Vec};
use bevy_reflect::ReflectCloneError;
use bevy_reflect_derive::impl_type_path;
use core::any::Any;
use smallvec::{Array as SmallArray, SmallVec};
impl<T: SmallArray + TypePath + Send + Sync> List for SmallVec<T>
where
@ -138,6 +138,22 @@ where
Box::new(self.clone_dynamic())
}
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
Ok(Box::new(
self.iter()
.map(|value| {
value
.reflect_clone()?
.take()
.map_err(|_| ReflectCloneError::FailedDowncast {
expected: Cow::Borrowed(<T::Item as TypePath>::type_path()),
received: Cow::Owned(value.reflect_type_path().to_string()),
})
})
.collect::<Result<Self, ReflectCloneError>>()?,
))
}
fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option<bool> {
crate::list_partial_eq(self, value)
}

View File

@ -2,6 +2,7 @@ use crate::{std_traits::ReflectDefault, ReflectDeserialize, ReflectSerialize};
use bevy_reflect_derive::impl_reflect_opaque;
impl_reflect_opaque!(::smol_str::SmolStr(
Clone,
Debug,
Hash,
PartialEq,

View File

@ -11,15 +11,17 @@ use crate::{
utility::{reflect_hasher, GenericTypeInfoCell, GenericTypePathCell, NonGenericTypeInfoCell},
ApplyError, Array, ArrayInfo, ArrayIter, DynamicMap, DynamicSet, DynamicTypePath, FromReflect,
FromType, Generics, GetTypeRegistration, List, ListInfo, ListIter, Map, MapInfo, MapIter,
MaybeTyped, OpaqueInfo, PartialReflect, Reflect, ReflectDeserialize, ReflectFromPtr,
ReflectFromReflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, ReflectSerialize, Set,
SetInfo, TypeInfo, TypeParamInfo, TypePath, TypeRegistration, TypeRegistry, Typed,
MaybeTyped, OpaqueInfo, PartialReflect, Reflect, ReflectCloneError, ReflectDeserialize,
ReflectFromPtr, ReflectFromReflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef,
ReflectSerialize, Set, SetInfo, TypeInfo, TypeParamInfo, TypePath, TypeRegistration,
TypeRegistry, Typed,
};
use alloc::{
borrow::{Cow, ToOwned},
boxed::Box,
collections::VecDeque,
format,
string::ToString,
vec::Vec,
};
use bevy_reflect_derive::{impl_reflect, impl_reflect_opaque};
@ -34,6 +36,7 @@ use core::{
use std::path::Path;
impl_reflect_opaque!(bool(
Clone,
Debug,
Hash,
PartialEq,
@ -42,6 +45,43 @@ impl_reflect_opaque!(bool(
Default
));
impl_reflect_opaque!(char(
Clone,
Debug,
Hash,
PartialEq,
Serialize,
Deserialize,
Default
));
impl_reflect_opaque!(u8(
Clone,
Debug,
Hash,
PartialEq,
Serialize,
Deserialize,
Default
));
impl_reflect_opaque!(u16(
Clone,
Debug,
Hash,
PartialEq,
Serialize,
Deserialize,
Default
));
impl_reflect_opaque!(u32(
Clone,
Debug,
Hash,
PartialEq,
Serialize,
Deserialize,
Default
));
impl_reflect_opaque!(u64(
Clone,
Debug,
Hash,
PartialEq,
@ -49,11 +89,8 @@ impl_reflect_opaque!(char(
Deserialize,
Default
));
impl_reflect_opaque!(u8(Debug, Hash, PartialEq, Serialize, Deserialize, Default));
impl_reflect_opaque!(u16(Debug, Hash, PartialEq, Serialize, Deserialize, Default));
impl_reflect_opaque!(u32(Debug, Hash, PartialEq, Serialize, Deserialize, Default));
impl_reflect_opaque!(u64(Debug, Hash, PartialEq, Serialize, Deserialize, Default));
impl_reflect_opaque!(u128(
Clone,
Debug,
Hash,
PartialEq,
@ -62,6 +99,43 @@ impl_reflect_opaque!(u128(
Default
));
impl_reflect_opaque!(usize(
Clone,
Debug,
Hash,
PartialEq,
Serialize,
Deserialize,
Default
));
impl_reflect_opaque!(i8(
Clone,
Debug,
Hash,
PartialEq,
Serialize,
Deserialize,
Default
));
impl_reflect_opaque!(i16(
Clone,
Debug,
Hash,
PartialEq,
Serialize,
Deserialize,
Default
));
impl_reflect_opaque!(i32(
Clone,
Debug,
Hash,
PartialEq,
Serialize,
Deserialize,
Default
));
impl_reflect_opaque!(i64(
Clone,
Debug,
Hash,
PartialEq,
@ -69,11 +143,8 @@ impl_reflect_opaque!(usize(
Deserialize,
Default
));
impl_reflect_opaque!(i8(Debug, Hash, PartialEq, Serialize, Deserialize, Default));
impl_reflect_opaque!(i16(Debug, Hash, PartialEq, Serialize, Deserialize, Default));
impl_reflect_opaque!(i32(Debug, Hash, PartialEq, Serialize, Deserialize, Default));
impl_reflect_opaque!(i64(Debug, Hash, PartialEq, Serialize, Deserialize, Default));
impl_reflect_opaque!(i128(
Clone,
Debug,
Hash,
PartialEq,
@ -82,6 +153,7 @@ impl_reflect_opaque!(i128(
Default
));
impl_reflect_opaque!(isize(
Clone,
Debug,
Hash,
PartialEq,
@ -89,10 +161,25 @@ impl_reflect_opaque!(isize(
Deserialize,
Default
));
impl_reflect_opaque!(f32(Debug, PartialEq, Serialize, Deserialize, Default));
impl_reflect_opaque!(f64(Debug, PartialEq, Serialize, Deserialize, Default));
impl_reflect_opaque!(f32(
Clone,
Debug,
PartialEq,
Serialize,
Deserialize,
Default
));
impl_reflect_opaque!(f64(
Clone,
Debug,
PartialEq,
Serialize,
Deserialize,
Default
));
impl_type_path!(str);
impl_reflect_opaque!(::alloc::string::String(
Clone,
Debug,
Hash,
PartialEq,
@ -102,6 +189,7 @@ impl_reflect_opaque!(::alloc::string::String(
));
#[cfg(feature = "std")]
impl_reflect_opaque!(::std::path::PathBuf(
Clone,
Debug,
Hash,
PartialEq,
@ -109,16 +197,17 @@ impl_reflect_opaque!(::std::path::PathBuf(
Deserialize,
Default
));
impl_reflect_opaque!(::core::any::TypeId(Debug, Hash, PartialEq,));
impl_reflect_opaque!(::alloc::collections::BTreeSet<T: Ord + Eq + Clone + Send + Sync>());
impl_reflect_opaque!(::core::ops::Range<T: Clone + Send + Sync>());
impl_reflect_opaque!(::core::ops::RangeInclusive<T: Clone + Send + Sync>());
impl_reflect_opaque!(::core::ops::RangeFrom<T: Clone + Send + Sync>());
impl_reflect_opaque!(::core::ops::RangeTo<T: Clone + Send + Sync>());
impl_reflect_opaque!(::core::ops::RangeToInclusive<T: Clone + Send + Sync>());
impl_reflect_opaque!(::core::ops::RangeFull());
impl_reflect_opaque!(::core::ops::Bound<T: Clone + Send + Sync>());
impl_reflect_opaque!(::core::any::TypeId(Clone, Debug, Hash, PartialEq,));
impl_reflect_opaque!(::alloc::collections::BTreeSet<T: Ord + Eq + Clone + Send + Sync>(Clone));
impl_reflect_opaque!(::core::ops::Range<T: Clone + Send + Sync>(Clone));
impl_reflect_opaque!(::core::ops::RangeInclusive<T: Clone + Send + Sync>(Clone));
impl_reflect_opaque!(::core::ops::RangeFrom<T: Clone + Send + Sync>(Clone));
impl_reflect_opaque!(::core::ops::RangeTo<T: Clone + Send + Sync>(Clone));
impl_reflect_opaque!(::core::ops::RangeToInclusive<T: Clone + Send + Sync>(Clone));
impl_reflect_opaque!(::core::ops::RangeFull(Clone));
impl_reflect_opaque!(::core::ops::Bound<T: Clone + Send + Sync>(Clone));
impl_reflect_opaque!(::core::time::Duration(
Clone,
Debug,
Hash,
PartialEq,
@ -127,9 +216,10 @@ impl_reflect_opaque!(::core::time::Duration(
Default
));
impl_reflect_opaque!(::bevy_platform_support::time::Instant(
Debug, Hash, PartialEq
Clone, Debug, Hash, PartialEq
));
impl_reflect_opaque!(::core::num::NonZeroI128(
Clone,
Debug,
Hash,
PartialEq,
@ -137,6 +227,7 @@ impl_reflect_opaque!(::core::num::NonZeroI128(
Deserialize
));
impl_reflect_opaque!(::core::num::NonZeroU128(
Clone,
Debug,
Hash,
PartialEq,
@ -144,6 +235,7 @@ impl_reflect_opaque!(::core::num::NonZeroU128(
Deserialize
));
impl_reflect_opaque!(::core::num::NonZeroIsize(
Clone,
Debug,
Hash,
PartialEq,
@ -151,6 +243,7 @@ impl_reflect_opaque!(::core::num::NonZeroIsize(
Deserialize
));
impl_reflect_opaque!(::core::num::NonZeroUsize(
Clone,
Debug,
Hash,
PartialEq,
@ -158,6 +251,7 @@ impl_reflect_opaque!(::core::num::NonZeroUsize(
Deserialize
));
impl_reflect_opaque!(::core::num::NonZeroI64(
Clone,
Debug,
Hash,
PartialEq,
@ -165,6 +259,7 @@ impl_reflect_opaque!(::core::num::NonZeroI64(
Deserialize
));
impl_reflect_opaque!(::core::num::NonZeroU64(
Clone,
Debug,
Hash,
PartialEq,
@ -172,6 +267,7 @@ impl_reflect_opaque!(::core::num::NonZeroU64(
Deserialize
));
impl_reflect_opaque!(::core::num::NonZeroU32(
Clone,
Debug,
Hash,
PartialEq,
@ -179,6 +275,7 @@ impl_reflect_opaque!(::core::num::NonZeroU32(
Deserialize
));
impl_reflect_opaque!(::core::num::NonZeroI32(
Clone,
Debug,
Hash,
PartialEq,
@ -186,6 +283,7 @@ impl_reflect_opaque!(::core::num::NonZeroI32(
Deserialize
));
impl_reflect_opaque!(::core::num::NonZeroI16(
Clone,
Debug,
Hash,
PartialEq,
@ -193,6 +291,7 @@ impl_reflect_opaque!(::core::num::NonZeroI16(
Deserialize
));
impl_reflect_opaque!(::core::num::NonZeroU16(
Clone,
Debug,
Hash,
PartialEq,
@ -200,6 +299,7 @@ impl_reflect_opaque!(::core::num::NonZeroU16(
Deserialize
));
impl_reflect_opaque!(::core::num::NonZeroU8(
Clone,
Debug,
Hash,
PartialEq,
@ -207,20 +307,22 @@ impl_reflect_opaque!(::core::num::NonZeroU8(
Deserialize
));
impl_reflect_opaque!(::core::num::NonZeroI8(
Clone,
Debug,
Hash,
PartialEq,
Serialize,
Deserialize
));
impl_reflect_opaque!(::core::num::Wrapping<T: Clone + Send + Sync>());
impl_reflect_opaque!(::core::num::Saturating<T: Clone + Send + Sync>());
impl_reflect_opaque!(::bevy_platform_support::sync::Arc<T: Send + Sync + ?Sized>);
impl_reflect_opaque!(::core::num::Wrapping<T: Clone + Send + Sync>(Clone));
impl_reflect_opaque!(::core::num::Saturating<T: Clone + Send + Sync>(Clone));
impl_reflect_opaque!(::bevy_platform_support::sync::Arc<T: Send + Sync + ?Sized>(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(all(any(unix, windows), feature = "std"))]
impl_reflect_opaque!(::std::ffi::OsString(
Clone,
Debug,
Hash,
PartialEq,
@ -228,8 +330,8 @@ impl_reflect_opaque!(::std::ffi::OsString(
Deserialize
));
#[cfg(all(not(any(unix, windows)), feature = "std"))]
impl_reflect_opaque!(::std::ffi::OsString(Debug, Hash, PartialEq));
impl_reflect_opaque!(::alloc::collections::BinaryHeap<T: Clone>);
impl_reflect_opaque!(::std::ffi::OsString(Clone, Debug, Hash, PartialEq));
impl_reflect_opaque!(::alloc::collections::BinaryHeap<T: Clone>(Clone));
macro_rules! impl_reflect_for_atomic {
($ty:ty, $ordering:expr) => {
@ -311,6 +413,12 @@ macro_rules! impl_reflect_for_atomic {
fn clone_value(&self) -> Box<dyn PartialReflect> {
Box::new(<$ty>::new(self.load($ordering)))
}
#[inline]
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
Ok(Box::new(<$ty>::new(self.load($ordering))))
}
#[inline]
fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> {
if let Some(value) = value.try_downcast_ref::<Self>() {
@ -522,6 +630,21 @@ macro_rules! impl_reflect_for_veclike {
Box::new(self.clone_dynamic())
}
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
Ok(Box::new(
self.iter()
.map(|value| {
value.reflect_clone()?.take().map_err(|_| {
ReflectCloneError::FailedDowncast {
expected: Cow::Borrowed(<T as TypePath>::type_path()),
received: Cow::Owned(value.reflect_type_path().to_string()),
}
})
})
.collect::<Result<Self, ReflectCloneError>>()?,
))
}
fn reflect_hash(&self) -> Option<u64> {
crate::list_hash(self)
}
@ -608,7 +731,7 @@ macro_rules! impl_reflect_for_hashmap {
where
K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash,
V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration,
S: TypePath + BuildHasher + Send + Sync,
S: TypePath + BuildHasher + Default + Send + Sync,
{
fn get(&self, key: &dyn PartialReflect) -> Option<&dyn PartialReflect> {
key.try_downcast_ref::<K>()
@ -708,7 +831,7 @@ macro_rules! impl_reflect_for_hashmap {
where
K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash,
V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration,
S: TypePath + BuildHasher + Send + Sync,
S: TypePath + BuildHasher + Default + Send + Sync,
{
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
@ -761,6 +884,27 @@ macro_rules! impl_reflect_for_hashmap {
Box::new(self.clone_dynamic())
}
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
let mut map = Self::with_capacity_and_hasher(self.len(), S::default());
for (key, value) in self.iter() {
let key = key.reflect_clone()?.take().map_err(|_| {
ReflectCloneError::FailedDowncast {
expected: Cow::Borrowed(<K as TypePath>::type_path()),
received: Cow::Owned(key.reflect_type_path().to_string()),
}
})?;
let value = value.reflect_clone()?.take().map_err(|_| {
ReflectCloneError::FailedDowncast {
expected: Cow::Borrowed(<V as TypePath>::type_path()),
received: Cow::Owned(value.reflect_type_path().to_string()),
}
})?;
map.insert(key, value);
}
Ok(Box::new(map))
}
fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option<bool> {
map_partial_eq(self, value)
}
@ -779,14 +923,14 @@ macro_rules! impl_reflect_for_hashmap {
where
K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash,
V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration,
S: TypePath + BuildHasher + Send + Sync,
S: TypePath + BuildHasher + Default + Send + Sync,
);
impl<K, V, S> Typed for $ty
where
K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash,
V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration,
S: TypePath + BuildHasher + Send + Sync,
S: TypePath + BuildHasher + Default + Send + Sync,
{
fn type_info() -> &'static TypeInfo {
static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new();
@ -805,7 +949,7 @@ macro_rules! impl_reflect_for_hashmap {
where
K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash,
V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration,
S: TypePath + BuildHasher + Send + Sync + Default,
S: TypePath + BuildHasher + Default + Send + Sync + Default,
{
fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<Self>();
@ -875,7 +1019,7 @@ macro_rules! impl_reflect_for_hashset {
impl<V, S> Set for $ty
where
V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash,
S: TypePath + BuildHasher + Send + Sync,
S: TypePath + BuildHasher + Default + Send + Sync,
{
fn get(&self, value: &dyn PartialReflect) -> Option<&dyn PartialReflect> {
value
@ -944,7 +1088,7 @@ macro_rules! impl_reflect_for_hashset {
impl<V, S> PartialReflect for $ty
where
V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash,
S: TypePath + BuildHasher + Send + Sync,
S: TypePath + BuildHasher + Default + Send + Sync,
{
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
@ -1006,6 +1150,21 @@ macro_rules! impl_reflect_for_hashset {
Box::new(self.clone_dynamic())
}
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
let mut set = Self::with_capacity_and_hasher(self.len(), S::default());
for value in self.iter() {
let value = value.reflect_clone()?.take().map_err(|_| {
ReflectCloneError::FailedDowncast {
expected: Cow::Borrowed(<V as TypePath>::type_path()),
received: Cow::Owned(value.reflect_type_path().to_string()),
}
})?;
set.insert(value);
}
Ok(Box::new(set))
}
fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option<bool> {
set_partial_eq(self, value)
}
@ -1014,7 +1173,7 @@ macro_rules! impl_reflect_for_hashset {
impl<V, S> Typed for $ty
where
V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash,
S: TypePath + BuildHasher + Send + Sync,
S: TypePath + BuildHasher + Default + Send + Sync,
{
fn type_info() -> &'static TypeInfo {
static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new();
@ -1031,7 +1190,7 @@ macro_rules! impl_reflect_for_hashset {
impl<V, S> GetTypeRegistration for $ty
where
V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash,
S: TypePath + BuildHasher + Send + Sync + Default,
S: TypePath + BuildHasher + Default + Send + Sync + Default,
{
fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<Self>();
@ -1049,7 +1208,7 @@ macro_rules! impl_reflect_for_hashset {
<V, S> for $ty
where
V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash,
S: TypePath + BuildHasher + Send + Sync,
S: TypePath + BuildHasher + Default + Send + Sync,
);
impl<V, S> FromReflect for $ty
@ -1252,6 +1411,30 @@ where
Box::new(self.clone_dynamic())
}
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
let mut map = Self::new();
for (key, value) in self.iter() {
let key =
key.reflect_clone()?
.take()
.map_err(|_| ReflectCloneError::FailedDowncast {
expected: Cow::Borrowed(<K as TypePath>::type_path()),
received: Cow::Owned(key.reflect_type_path().to_string()),
})?;
let value =
value
.reflect_clone()?
.take()
.map_err(|_| ReflectCloneError::FailedDowncast {
expected: Cow::Borrowed(<V as TypePath>::type_path()),
received: Cow::Owned(value.reflect_type_path().to_string()),
})?;
map.insert(key, value);
}
Ok(Box::new(map))
}
fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option<bool> {
map_partial_eq(self, value)
}
@ -1616,6 +1799,10 @@ impl PartialReflect for Cow<'static, str> {
Box::new(self.clone())
}
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
Ok(Box::new(self.clone()))
}
fn reflect_hash(&self) -> Option<u64> {
let mut hasher = reflect_hasher();
Hash::hash(&Any::type_id(self), &mut hasher);
@ -1804,6 +1991,10 @@ impl<T: FromReflect + MaybeTyped + Clone + TypePath + GetTypeRegistration> Parti
Box::new(List::clone_dynamic(self))
}
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
Ok(Box::new(self.clone()))
}
fn reflect_hash(&self) -> Option<u64> {
crate::list_hash(self)
}
@ -1913,6 +2104,10 @@ impl PartialReflect for &'static str {
Box::new(*self)
}
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
Ok(Box::new(*self))
}
fn reflect_hash(&self) -> Option<u64> {
let mut hasher = reflect_hasher();
Hash::hash(&Any::type_id(self), &mut hasher);
@ -2052,6 +2247,10 @@ impl PartialReflect for &'static Path {
Box::new(*self)
}
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
Ok(Box::new(*self))
}
fn reflect_hash(&self) -> Option<u64> {
let mut hasher = reflect_hasher();
Hash::hash(&Any::type_id(self), &mut hasher);
@ -2191,6 +2390,10 @@ impl PartialReflect for Cow<'static, Path> {
Box::new(self.clone())
}
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
Ok(Box::new(self.clone()))
}
fn reflect_hash(&self) -> Option<u64> {
let mut hasher = reflect_hasher();
Hash::hash(&Any::type_id(self), &mut hasher);
@ -2349,6 +2552,10 @@ impl PartialReflect for &'static Location<'static> {
Box::new(*self)
}
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
Ok(Box::new(*self))
}
fn reflect_hash(&self) -> Option<u64> {
let mut hasher = reflect_hasher();
Hash::hash(&Any::type_id(self), &mut hasher);

View File

@ -5,6 +5,7 @@ impl_reflect_opaque!(::uuid::Uuid(
Serialize,
Deserialize,
Default,
Clone,
Debug,
PartialEq,
Hash

View File

@ -1,6 +1,7 @@
use crate::{impl_reflect_opaque, ReflectDeserialize, ReflectSerialize};
impl_reflect_opaque!(::wgpu_types::TextureFormat(
Clone,
Debug,
Hash,
PartialEq,

View File

@ -568,6 +568,7 @@ extern crate alloc;
extern crate self as bevy_reflect;
mod array;
mod error;
mod fields;
mod from_reflect;
#[cfg(feature = "functions")]
@ -633,6 +634,7 @@ pub mod prelude {
pub use array::*;
pub use enums::*;
pub use error::*;
pub use fields::*;
pub use from_reflect::*;
pub use generics::*;
@ -986,6 +988,296 @@ mod tests {
assert_eq!(values, vec![1]);
}
#[test]
fn should_reflect_clone() {
// Struct
#[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::<Foo>().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_generic_type() {
#[derive(Reflect, Debug, PartialEq)]
struct Foo<T, U>(T, #[reflect(ignore, clone)] PhantomData<U>);
#[derive(TypePath, Debug, PartialEq)]
struct Bar;
// `usize` will be cloned via `Reflect::reflect_clone`
// `PhantomData<Bar>` will be cloned via `Clone::clone`
let value = Foo::<usize, Bar>(123, PhantomData);
let clone = value
.reflect_clone()
.expect("should reflect_clone generic struct");
assert_eq!(value, clone.take::<Foo<usize, Bar>>().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.
#[expect(
dead_code,
reason = "if things are working correctly, this function should never be called"
)]
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::<Foo>().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::<Bar>().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::<Baz>().unwrap());
let value = Baz::Tuple(123);
let clone = value
.reflect_clone()
.expect("should reflect_clone tuple variant");
assert_eq!(value, clone.take::<Baz>().unwrap());
let value = Baz::Struct { value: 123 };
let clone = value
.reflect_clone()
.expect("should reflect_clone struct variant");
assert_eq!(value, clone.take::<Baz>().unwrap());
}
#[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::<Foo>().unwrap());
}
#[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_eq!(
clone.unwrap_err(),
ReflectCloneError::FieldNotCloneable {
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::FieldNotCloneable {
field: FieldId::Named(Cow::Borrowed("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::FieldNotCloneable {
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::FieldNotCloneable {
field: FieldId::Named(Cow::Borrowed("value")),
variant: Some(Cow::Borrowed("Struct")),
container_type_path: Cow::Borrowed(Baz::type_path()),
}
);
}
#[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::<Foo>().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::<Bar>().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::<MyEnum>().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::<MyEnum>().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::<MyEnum>().unwrap()
);
}
#[test]
fn should_call_from_reflect_dynamically() {
#[derive(Reflect)]

View File

@ -1,9 +1,11 @@
use crate::{
array_debug, enum_debug, list_debug, map_debug, set_debug, struct_debug, tuple_debug,
tuple_struct_debug, DynamicTypePath, DynamicTyped, OpaqueInfo, ReflectKind,
tuple_struct_debug, DynamicTypePath, DynamicTyped, OpaqueInfo, ReflectCloneError, ReflectKind,
ReflectKindMismatchError, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, Typed,
};
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::string::ToString;
use core::{
any::{Any, TypeId},
fmt::Debug,
@ -216,21 +218,57 @@ where
/// See [`ReflectOwned`].
fn reflect_owned(self: Box<Self>) -> 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.
///
/// If the dynamic behavior is not desired, a concrete clone can be obtained using [`PartialReflect::reflect_clone`].
///
/// # Example
///
/// ```
/// # use bevy_reflect::{PartialReflect};
/// let value = (1, true, 3.14);
/// let cloned = value.clone_value();
/// assert!(cloned.is_dynamic())
/// ```
///
/// [`Struct::clone_dynamic`]: crate::Struct::clone_dynamic
/// [`TupleStruct::clone_dynamic`]: crate::TupleStruct::clone_dynamic
/// [`Enum::clone_dynamic`]: crate::Enum::clone_dynamic
/// [`List`]: crate::List
/// [`Map`]: crate::Map
/// [`List::clone_dynamic`]: crate::List::clone_dynamic
/// [`DynamicList`]: crate::DynamicList
/// [`Struct`]: crate::Struct
/// [`Struct::clone_dynamic`]: crate::Struct::clone_dynamic
/// [`DynamicStruct`]: crate::DynamicStruct
fn clone_value(&self) -> Box<dyn PartialReflect>;
/// Attempts to clone `Self` using reflection.
///
/// Unlike [`PartialReflect::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, an appropriate [`ReflectCloneError`] is returned.
///
/// # Example
///
/// ```
/// # use bevy_reflect::PartialReflect;
/// let value = (1, true, 3.14);
/// let cloned = value.reflect_clone().unwrap();
/// assert!(cloned.is::<(i32, bool, f64)>())
/// ```
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
Err(ReflectCloneError::NotImplemented {
type_path: Cow::Owned(self.reflect_type_path().to_string()),
})
}
/// Returns a hash of the value (which includes the type).
///
/// If the underlying type does not support hashing, returns `None`.

View File

@ -4,9 +4,9 @@ use variadics_please::all_tuples;
use crate::generics::impl_generic_info_methods;
use crate::{
type_info::impl_type_methods, utility::GenericTypePathCell, ApplyError, FromReflect, Generics,
GetTypeRegistration, MaybeTyped, PartialReflect, Reflect, ReflectKind, ReflectMut,
ReflectOwned, ReflectRef, Type, TypeInfo, TypePath, TypeRegistration, TypeRegistry, Typed,
UnnamedField,
GetTypeRegistration, MaybeTyped, PartialReflect, Reflect, ReflectCloneError, ReflectKind,
ReflectMut, ReflectOwned, ReflectRef, Type, TypeInfo, TypePath, TypeRegistration, TypeRegistry,
Typed, UnnamedField,
};
use alloc::{boxed::Box, vec, vec::Vec};
use core::{
@ -593,6 +593,16 @@ macro_rules! impl_reflect_tuple {
fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> {
crate::tuple_try_apply(self, value)
}
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
Ok(Box::new((
$(
self.$index.reflect_clone()?
.take::<$name>()
.expect("`Reflect::reflect_clone` should return the same type"),
)*
)))
}
}
impl<$($name: Reflect + MaybeTyped + TypePath + GetTypeRegistration),*> Reflect for ($($name,)*) {