bevy_reflect: Fix ignored/skipped field order (#7575)

# Objective

Fixes #5101
Alternative to #6511

## Solution

Corrected the behavior for ignored fields in `FromReflect`, which was
previously using the incorrect field indexes.

Similarly, fields marked with `#[reflect(skip_serializing)]` no longer
break when using `FromReflect` after deserialization. This was done by
modifying `SerializationData` to store a function pointer that can later
be used to generate a default instance of the skipped field during
deserialization.

The function pointer points to a function generated by the derive macro
using the behavior designated by `#[reflect(default)]` (or just
`Default` if none provided). The entire output of the macro is now
wrapped in an [unnamed
constant](https://doc.rust-lang.org/stable/reference/items/constant-items.html#unnamed-constant)
which keeps this behavior hygienic.

#### Rationale

The biggest downside to this approach is that it requires fields marked
`#[reflect(skip_serializing)]` to provide the ability to create a
default instance— either via a `Default` impl or by specifying a custom
one. While this isn't great, I think it might be justified by the fact
that we really need to create this value when using `FromReflect` on a
deserialized object. And we need to do this _during_ deserialization
because after that (at least for tuples and tuple structs) we lose
information about which field is which: _"is the value at index 1 in
this `DynamicTupleStruct` the actual value for index 1 or is it really
the value for index 2 since index 1 is skippable...?"_

#### Alternatives

An alternative would be to store `Option<Box<dyn Reflect>>` within
`DynamicTuple` and `DynamicTupleStruct` instead of just `Box<dyn
Reflect>`. This would allow us to insert "empty"/"missing" fields during
deserialization, thus saving the positional information of the skipped
fields. However, this may require changing the API of `Tuple` and
`TupleStruct` such that they can account for their dynamic counterparts
returning `None` for a skipped field. In practice this would probably
mean exposing the `Option`-ness of the dynamics onto implementors via
methods like `Tuple::drain` or `TupleStruct::field`.

Personally, I think requiring `Default` would be better than muddying up
the API to account for these special cases. But I'm open to trying out
this other approach if the community feels that it's better.

---

## Changelog

### Public Changes

#### Fixed

- The behaviors of `#[reflect(ignore)]` and
`#[reflect(skip_serializing)]` are no longer dependent on field order

#### Changed

- Fields marked with `#[reflect(skip_serializing)]` now need to either
implement `Default` or specify a custom default function using
`#[reflect(default = "path::to::some_func")]`
- Deserializing a type with fields marked `#[reflect(skip_serializing)]`
will now include that field initialized to its specified default value
- `SerializationData::new` now takes the new `SkippedField` struct along
with the skipped field index
- Renamed `SerializationData::is_ignored_field` to
`SerializationData::is_field_skipped`

#### Added

- Added `SkippedField` struct
- Added methods `SerializationData::generate_default` and
`SerializationData::iter_skipped`

### Internal Changes

#### Changed

- Replaced `members_to_serialization_denylist` and `BitSet<u32>` with
`SerializationDataDef`
- The `Reflect` derive is more hygienic as it now outputs within an
[unnamed
constant](https://doc.rust-lang.org/stable/reference/items/constant-items.html#unnamed-constant)
- `StructField::index` has been split up into
`StructField::declaration_index` and `StructField::reflection_index`

#### Removed

- Removed `bitset` dependency

## Migration Guide

* Fields marked `#[reflect(skip_serializing)]` now must implement
`Default` or specify a custom default function with `#[reflect(default =
"path::to::some_func")]`
    ```rust
    #[derive(Reflect)]
    struct MyStruct {
      #[reflect(skip_serializing)]
      #[reflect(default = "get_foo_default")]
foo: Foo, // <- `Foo` does not impl `Default` so requires a custom
function
      #[reflect(skip_serializing)]
      bar: Bar, // <- `Bar` impls `Default`
    }
    
    #[derive(Reflect)]
    struct Foo(i32);
    
    #[derive(Reflect, Default)]
    struct Bar(i32);
    
    fn get_foo_default() -> Foo {
      Foo(123)
    }
    ```
* `SerializationData::new` has been changed to expect an iterator of
`(usize, SkippedField)` rather than one of just `usize`
    ```rust
    // BEFORE
    SerializationData::new([0, 3].into_iter());
    
    // AFTER
    SerializationData::new([
      (0, SkippedField::new(field_0_default_fn)),
      (3, SkippedField::new(field_3_default_fn)),
    ].into_iter());
    ```
* `Serialization::is_ignored_field` has been renamed to
`Serialization::is_field_skipped`
* Fields marked `#[reflect(skip_serializing)]` are now included in
deserialization output. This may affect logic that expected those fields
to be absent.
This commit is contained in:
Gino Valente 2023-10-22 05:43:31 -07:00 committed by GitHub
parent 8efcbf3e4f
commit 60773e6787
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 610 additions and 299 deletions

View file

@ -23,4 +23,3 @@ syn = { version = "2.0", features = ["full"] }
proc-macro2 = "1.0"
quote = "1.0"
uuid = { version = "1.1", features = ["v4"] }
bit-set = "0.5.2"

View file

@ -1,11 +1,11 @@
use crate::container_attributes::{FromReflectAttrs, ReflectTraits};
use crate::field_attributes::{parse_field_attrs, ReflectFieldAttr};
use crate::type_path::parse_path_no_leading_colon;
use crate::utility::{members_to_serialization_denylist, StringExpr, WhereClauseOptions};
use bit_set::BitSet;
use crate::utility::{StringExpr, WhereClauseOptions};
use quote::{quote, ToTokens};
use syn::token::Comma;
use crate::serialization::SerializationDataDef;
use crate::{
utility, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME, TYPE_NAME_ATTRIBUTE_NAME,
TYPE_PATH_ATTRIBUTE_NAME,
@ -65,7 +65,7 @@ pub(crate) struct ReflectMeta<'a> {
/// ```
pub(crate) struct ReflectStruct<'a> {
meta: ReflectMeta<'a>,
serialization_denylist: BitSet<u32>,
serialization_data: Option<SerializationDataDef>,
fields: Vec<StructField<'a>>,
}
@ -95,7 +95,14 @@ pub(crate) struct StructField<'a> {
/// The reflection-based attributes on the field.
pub attrs: ReflectFieldAttr,
/// The index of this field within the struct.
pub index: usize,
pub declaration_index: usize,
/// The index of this field as seen by the reflection API.
///
/// This index accounts for the removal of [ignored] fields.
/// It will only be `Some(index)` when the field is not ignored.
///
/// [ignored]: crate::field_attributes::ReflectIgnoreBehavior::IgnoreAlways
pub reflection_index: Option<usize>,
/// The documentation for this field, if any
#[cfg(feature = "documentation")]
pub doc: crate::documentation::Documentation,
@ -272,9 +279,7 @@ impl<'a> ReflectDerive<'a> {
let fields = Self::collect_struct_fields(&data.fields)?;
let reflect_struct = ReflectStruct {
meta,
serialization_denylist: members_to_serialization_denylist(
fields.iter().map(|v| v.attrs.ignore),
),
serialization_data: SerializationDataDef::new(&fields)?,
fields,
};
@ -308,19 +313,31 @@ impl<'a> ReflectDerive<'a> {
}
fn collect_struct_fields(fields: &'a Fields) -> Result<Vec<StructField<'a>>, syn::Error> {
let mut active_index = 0;
let sifter: utility::ResultSifter<StructField<'a>> = fields
.iter()
.enumerate()
.map(|(index, field)| -> Result<StructField, syn::Error> {
let attrs = parse_field_attrs(&field.attrs)?;
Ok(StructField {
index,
attrs,
data: field,
#[cfg(feature = "documentation")]
doc: crate::documentation::Documentation::from_attributes(&field.attrs),
})
})
.map(
|(declaration_index, field)| -> Result<StructField, syn::Error> {
let attrs = parse_field_attrs(&field.attrs)?;
let reflection_index = if attrs.ignore.is_ignored() {
None
} else {
active_index += 1;
Some(active_index - 1)
};
Ok(StructField {
declaration_index,
reflection_index,
attrs,
data: field,
#[cfg(feature = "documentation")]
doc: crate::documentation::Documentation::from_attributes(&field.attrs),
})
},
)
.fold(
utility::ResultSifter::default(),
utility::ResultSifter::fold,
@ -420,12 +437,9 @@ impl<'a> ReflectStruct<'a> {
&self.meta
}
/// Access the data about which fields should be ignored during serialization.
///
/// The returned bitset is a collection of indices obtained from the [`members_to_serialization_denylist`] function.
#[allow(dead_code)]
pub fn serialization_denylist(&self) -> &BitSet<u32> {
&self.serialization_denylist
/// Returns the [`SerializationDataDef`] for this struct.
pub fn serialization_data(&self) -> Option<&SerializationDataDef> {
self.serialization_data.as_ref()
}
/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
@ -438,7 +452,7 @@ impl<'a> ReflectStruct<'a> {
crate::registration::impl_get_type_registration(
self.meta(),
where_clause_options,
Some(&self.serialization_denylist),
self.serialization_data(),
)
}

View file

@ -189,7 +189,7 @@ fn get_ignored_fields(reflect_struct: &ReflectStruct) -> MemberValuePair {
reflect_struct
.ignored_fields()
.map(|field| {
let member = ident_or_index(field.data.ident.as_ref(), field.index);
let member = ident_or_index(field.data.ident.as_ref(), field.declaration_index);
let value = match &field.attrs.default {
DefaultBehavior::Func(path) => quote! {#path()},
@ -218,8 +218,12 @@ fn get_active_fields(
reflect_struct
.active_fields()
.map(|field| {
let member = ident_or_index(field.data.ident.as_ref(), field.index);
let accessor = get_field_accessor(field.data, field.index, is_tuple);
let member = ident_or_index(field.data.ident.as_ref(), field.declaration_index);
let accessor = get_field_accessor(
field.data,
field.reflection_index.expect("field should be active"),
is_tuple,
);
let ty = field.data.ty.clone();
let get_field = quote! {

View file

@ -346,7 +346,11 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden
// Ignored field
continue;
}
constructor_argument.push(generate_for_field(reflect_idx, field.index, field));
constructor_argument.push(generate_for_field(
reflect_idx,
field.declaration_index,
field,
));
reflect_idx += 1;
}
constructor_argument

View file

@ -19,12 +19,12 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS
.ident
.as_ref()
.map(|i| i.to_string())
.unwrap_or_else(|| field.index.to_string())
.unwrap_or_else(|| field.declaration_index.to_string())
})
.collect::<Vec<String>>();
let field_idents = reflect_struct
.active_fields()
.map(|field| ident_or_index(field.data.ident.as_ref(), field.index))
.map(|field| ident_or_index(field.data.ident.as_ref(), field.declaration_index))
.collect::<Vec<_>>();
let field_types = reflect_struct.active_types();
let field_count = field_idents.len();

View file

@ -14,7 +14,7 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::
let field_idents = reflect_struct
.active_fields()
.map(|field| Member::Unnamed(Index::from(field.index)))
.map(|field| Member::Unnamed(Index::from(field.declaration_index)))
.collect::<Vec<_>>();
let field_types = reflect_struct.active_types();
let field_count = field_idents.len();

View file

@ -24,6 +24,7 @@ mod from_reflect;
mod impls;
mod reflect_value;
mod registration;
mod serialization;
mod trait_reflection;
mod type_path;
mod type_uuid;
@ -201,8 +202,10 @@ pub fn derive_reflect(input: TokenStream) -> TokenStream {
};
TokenStream::from(quote! {
#reflect_impls
#from_reflect_impl
const _: () = {
#reflect_impls
#from_reflect_impl
};
})
}
@ -241,15 +244,20 @@ pub fn derive_from_reflect(input: TokenStream) -> TokenStream {
Err(err) => return err.into_compile_error().into(),
};
match derive_data {
let from_reflect_impl = match derive_data {
ReflectDerive::Struct(struct_data) | ReflectDerive::UnitStruct(struct_data) => {
from_reflect::impl_struct(&struct_data)
}
ReflectDerive::TupleStruct(struct_data) => from_reflect::impl_tuple_struct(&struct_data),
ReflectDerive::Enum(meta) => from_reflect::impl_enum(&meta),
ReflectDerive::Value(meta) => from_reflect::impl_value(&meta),
}
.into()
};
TokenStream::from(quote! {
const _: () = {
#from_reflect_impl
};
})
}
/// Derives the `TypePath` trait, providing a stable alternative to [`std::any::type_name`].
@ -275,21 +283,31 @@ pub fn derive_type_path(input: TokenStream) -> TokenStream {
Err(err) => return err.into_compile_error().into(),
};
impls::impl_type_path(
let type_path_impl = impls::impl_type_path(
derive_data.meta(),
// Use `WhereClauseOptions::new_value` here so we don't enforce reflection bounds
&WhereClauseOptions::new_value(derive_data.meta()),
)
.into()
);
TokenStream::from(quote! {
const _: () = {
#type_path_impl
};
})
}
// From https://github.com/randomPoison/type-uuid
#[proc_macro_derive(TypeUuid, attributes(uuid))]
pub fn derive_type_uuid(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
type_uuid::type_uuid_derive(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
let uuid_impl =
type_uuid::type_uuid_derive(input).unwrap_or_else(syn::Error::into_compile_error);
TokenStream::from(quote! {
const _: () = {
#uuid_impl
};
})
}
/// A macro that automatically generates type data for traits, which their implementors can then register.
@ -401,8 +419,10 @@ pub fn impl_reflect_value(input: TokenStream) -> TokenStream {
let from_reflect_impl = from_reflect::impl_value(&meta);
TokenStream::from(quote! {
#reflect_impls
#from_reflect_impl
const _: () = {
#reflect_impls
#from_reflect_impl
};
})
}
@ -446,7 +466,7 @@ pub fn impl_reflect_struct(input: TokenStream) -> TokenStream {
Err(err) => return err.into_compile_error().into(),
};
match derive_data {
let output = match derive_data {
ReflectDerive::Struct(struct_data) => {
if !struct_data.meta().type_path().has_custom_path() {
return syn::Error::new(
@ -460,27 +480,30 @@ pub fn impl_reflect_struct(input: TokenStream) -> TokenStream {
let impl_struct = impls::impl_struct(&struct_data);
let impl_from_struct = from_reflect::impl_struct(&struct_data);
TokenStream::from(quote! {
quote! {
#impl_struct
#impl_from_struct
})
}
}
ReflectDerive::TupleStruct(..) => syn::Error::new(
ast.span(),
"impl_reflect_struct does not support tuple structs",
)
.into_compile_error()
.into(),
.into_compile_error(),
ReflectDerive::UnitStruct(..) => syn::Error::new(
ast.span(),
"impl_reflect_struct does not support unit structs",
)
.into_compile_error()
.into(),
.into_compile_error(),
_ => syn::Error::new(ast.span(), "impl_reflect_struct only supports structs")
.into_compile_error()
.into(),
}
.into_compile_error(),
};
TokenStream::from(quote! {
const _: () = {
#output
};
})
}
/// A macro used to generate a `FromReflect` trait implementation for the given type.
@ -521,7 +544,14 @@ pub fn impl_from_reflect_value(input: TokenStream) -> TokenStream {
}
};
from_reflect::impl_value(&ReflectMeta::new(type_path, def.traits.unwrap_or_default())).into()
let from_reflect_impl =
from_reflect::impl_value(&ReflectMeta::new(type_path, def.traits.unwrap_or_default()));
TokenStream::from(quote! {
const _: () = {
#from_reflect_impl
};
})
}
/// A replacement for [deriving `TypePath`] for use on foreign types.
@ -583,12 +613,24 @@ pub fn impl_type_path(input: TokenStream) -> TokenStream {
let meta = ReflectMeta::new(type_path, ReflectTraits::default());
impls::impl_type_path(&meta, &WhereClauseOptions::new_value(&meta)).into()
let type_path_impl = impls::impl_type_path(&meta, &WhereClauseOptions::new_value(&meta));
TokenStream::from(quote! {
const _: () = {
#type_path_impl
};
})
}
/// Derives `TypeUuid` for the given type. This is used internally to implement `TypeUuid` on foreign types, such as those in the std. This macro should be used in the format of `<[Generic Params]> [Type (Path)], [Uuid (String Literal)]`.
#[proc_macro]
pub fn impl_type_uuid(input: TokenStream) -> TokenStream {
let def = parse_macro_input!(input as type_uuid::TypeUuidDef);
type_uuid::gen_impl_type_uuid(def).into()
let uuid_impl = type_uuid::gen_impl_type_uuid(def);
TokenStream::from(quote! {
const _: () = {
#uuid_impl
};
})
}

View file

@ -1,8 +1,8 @@
//! Contains code related specifically to Bevy's type registration.
use crate::derive_data::ReflectMeta;
use crate::serialization::SerializationDataDef;
use crate::utility::{extend_where_clause, WhereClauseOptions};
use bit_set::BitSet;
use quote::quote;
/// Creates the `GetTypeRegistration` impl for the given type data.
@ -10,7 +10,7 @@ use quote::quote;
pub(crate) fn impl_get_type_registration(
meta: &ReflectMeta,
where_clause_options: &WhereClauseOptions,
serialization_denylist: Option<&BitSet<u32>>,
serialization_data: Option<&SerializationDataDef>,
) -> proc_macro2::TokenStream {
let type_path = meta.type_path();
let bevy_reflect_path = meta.bevy_reflect_path();
@ -20,17 +20,16 @@ pub(crate) fn impl_get_type_registration(
let from_reflect_data = if meta.from_reflect().should_auto_derive() {
Some(quote! {
registration.insert::<#bevy_reflect_path::ReflectFromReflect>(#bevy_reflect_path::FromType::<Self>::from_type());
registration.insert::<#bevy_reflect_path::ReflectFromReflect>(#bevy_reflect_path::FromType::<Self>::from_type());
})
} else {
None
};
let serialization_data = serialization_denylist.map(|denylist| {
let denylist = denylist.into_iter();
let serialization_data = serialization_data.map(|data| {
let serialization_data = data.as_serialization_data(bevy_reflect_path);
quote! {
let ignored_indices = ::core::iter::IntoIterator::into_iter([#(#denylist),*]);
registration.insert::<#bevy_reflect_path::serde::SerializationData>(#bevy_reflect_path::serde::SerializationData::new(ignored_indices));
registration.insert::<#bevy_reflect_path::serde::SerializationData>(#serialization_data);
}
});

View file

@ -0,0 +1,91 @@
use crate::derive_data::StructField;
use crate::field_attributes::{DefaultBehavior, ReflectIgnoreBehavior};
use bevy_macro_utils::fq_std::{FQBox, FQDefault};
use quote::quote;
use std::collections::HashMap;
use syn::spanned::Spanned;
use syn::Path;
type ReflectionIndex = usize;
/// Collected serialization data used to generate a `SerializationData` type.
pub(crate) struct SerializationDataDef {
/// Maps a field's _reflection_ index to its [`SkippedFieldDef`] if marked as `#[reflect(skip_serializing)]`.
skipped: HashMap<ReflectionIndex, SkippedFieldDef>,
}
impl SerializationDataDef {
/// Attempts to create a new `SerializationDataDef` from the given collection of fields.
///
/// Returns `Ok(Some(data))` if there are any fields needing to be skipped during serialization.
/// Otherwise, returns `Ok(None)`.
pub fn new(fields: &[StructField<'_>]) -> Result<Option<Self>, syn::Error> {
let mut skipped = HashMap::default();
for field in fields {
match field.attrs.ignore {
ReflectIgnoreBehavior::IgnoreSerialization => {
skipped.insert(
field.reflection_index.ok_or_else(|| {
syn::Error::new(
field.data.span(),
"internal error: field is missing a reflection index",
)
})?,
SkippedFieldDef::new(field)?,
);
}
_ => continue,
}
}
if skipped.is_empty() {
Ok(None)
} else {
Ok(Some(Self { skipped }))
}
}
/// Returns a `TokenStream` containing an initialized `SerializationData` type.
pub fn as_serialization_data(&self, bevy_reflect_path: &Path) -> proc_macro2::TokenStream {
let fields =
self.skipped
.iter()
.map(|(reflection_index, SkippedFieldDef { default_fn })| {
quote! {(
#reflection_index,
#bevy_reflect_path::serde::SkippedField::new(#default_fn)
)}
});
quote! {
#bevy_reflect_path::serde::SerializationData::new(
::core::iter::IntoIterator::into_iter([#(#fields),*])
)
}
}
}
/// Collected field data used to generate a `SkippedField` type.
pub(crate) struct SkippedFieldDef {
/// The default function for this field.
///
/// This is of type `fn() -> Box<dyn Reflect>`.
default_fn: proc_macro2::TokenStream,
}
impl SkippedFieldDef {
pub fn new(field: &StructField<'_>) -> Result<Self, syn::Error> {
let ty = &field.data.ty;
let default_fn = match &field.attrs.default {
DefaultBehavior::Func(func) => quote! {
|| { #FQBox::new(#func()) }
},
_ => quote! {
|| { #FQBox::new(<#ty as #FQDefault>::default()) }
},
};
Ok(Self { default_fn })
}
}

View file

@ -1,12 +1,10 @@
//! General-purpose utility functions for internal usage within this crate.
use crate::derive_data::{ReflectMeta, StructField};
use crate::field_attributes::ReflectIgnoreBehavior;
use bevy_macro_utils::{
fq_std::{FQAny, FQOption, FQSend, FQSync},
BevyManifest,
};
use bit_set::BitSet;
use proc_macro2::{Ident, Span};
use quote::{quote, ToTokens};
use syn::{spanned::Spanned, LitStr, Member, Path, Type, WhereClause};
@ -286,42 +284,6 @@ impl<T> ResultSifter<T> {
}
}
/// Converts an iterator over ignore behavior of members to a bitset of ignored members.
///
/// Takes into account the fact that always ignored (non-reflected) members are skipped.
///
/// # Example
/// ```rust,ignore
/// pub struct HelloWorld {
/// reflected_field: u32 // index: 0
///
/// #[reflect(ignore)]
/// non_reflected_field: u32 // index: N/A (not 1!)
///
/// #[reflect(skip_serializing)]
/// non_serialized_field: u32 // index: 1
/// }
/// ```
/// Would convert to the `0b01` bitset (i.e second field is NOT serialized)
///
pub(crate) fn members_to_serialization_denylist<T>(member_iter: T) -> BitSet<u32>
where
T: Iterator<Item = ReflectIgnoreBehavior>,
{
let mut bitset = BitSet::default();
member_iter.fold(0, |next_idx, member| match member {
ReflectIgnoreBehavior::IgnoreAlways => next_idx,
ReflectIgnoreBehavior::IgnoreSerialization => {
bitset.insert(next_idx);
next_idx + 1
}
ReflectIgnoreBehavior::None => next_idx + 1,
});
bitset
}
/// Turns an `Option<TokenStream>` into a `TokenStream` for an `Option`.
pub(crate) fn wrap_in_option(tokens: Option<proc_macro2::TokenStream>) -> proc_macro2::TokenStream {
match tokens {

View file

@ -764,6 +764,39 @@ mod tests {
.unwrap_or_default());
}
#[test]
fn from_reflect_should_allow_ignored_unnamed_fields() {
#[derive(Reflect, Eq, PartialEq, Debug)]
struct MyTupleStruct(i8, #[reflect(ignore)] i16, i32);
let expected = MyTupleStruct(1, 0, 3);
let mut dyn_tuple_struct = DynamicTupleStruct::default();
dyn_tuple_struct.insert(1_i8);
dyn_tuple_struct.insert(3_i32);
let my_tuple_struct = <MyTupleStruct as FromReflect>::from_reflect(&dyn_tuple_struct);
assert_eq!(Some(expected), my_tuple_struct);
#[derive(Reflect, Eq, PartialEq, Debug)]
enum MyEnum {
Tuple(i8, #[reflect(ignore)] i16, i32),
}
let expected = MyEnum::Tuple(1, 0, 3);
let mut dyn_tuple = DynamicTuple::default();
dyn_tuple.insert(1_i8);
dyn_tuple.insert(3_i32);
let mut dyn_enum = DynamicEnum::default();
dyn_enum.set_variant("Tuple", dyn_tuple);
let my_enum = <MyEnum as FromReflect>::from_reflect(&dyn_enum);
assert_eq!(Some(expected), my_enum);
}
#[test]
fn from_reflect_should_use_default_field_attributes() {
#[derive(Reflect, Eq, PartialEq, Debug)]

View file

@ -2,9 +2,8 @@ use crate::serde::SerializationData;
use crate::{
ArrayInfo, DynamicArray, DynamicEnum, DynamicList, DynamicMap, DynamicStruct, DynamicTuple,
DynamicTupleStruct, DynamicVariant, EnumInfo, ListInfo, Map, MapInfo, NamedField, Reflect,
ReflectDeserialize, StructInfo, StructVariantInfo, Tuple, TupleInfo, TupleStruct,
TupleStructInfo, TupleVariantInfo, TypeInfo, TypeRegistration, TypeRegistry, UnnamedField,
VariantInfo,
ReflectDeserialize, StructInfo, StructVariantInfo, TupleInfo, TupleStructInfo,
TupleVariantInfo, TypeInfo, TypeRegistration, TypeRegistry, UnnamedField, VariantInfo,
};
use erased_serde::Deserializer;
use serde::de::{
@ -27,6 +26,8 @@ pub trait DeserializeValue {
trait StructLikeInfo {
fn get_path(&self) -> &str;
fn get_field(&self, name: &str) -> Option<&NamedField>;
fn field_at(&self, index: usize) -> Option<&NamedField>;
fn get_field_len(&self) -> usize;
fn iter_fields(&self) -> Iter<'_, NamedField>;
}
@ -49,10 +50,18 @@ impl StructLikeInfo for StructInfo {
self.type_path()
}
fn field_at(&self, index: usize) -> Option<&NamedField> {
self.field_at(index)
}
fn get_field(&self, name: &str) -> Option<&NamedField> {
self.field(name)
}
fn get_field_len(&self) -> usize {
self.field_len()
}
fn iter_fields(&self) -> Iter<'_, NamedField> {
self.iter()
}
@ -80,10 +89,18 @@ impl StructLikeInfo for StructVariantInfo {
self.name()
}
fn field_at(&self, index: usize) -> Option<&NamedField> {
self.field_at(index)
}
fn get_field(&self, name: &str) -> Option<&NamedField> {
self.field(name)
}
fn get_field_len(&self) -> usize {
self.field_len()
}
fn iter_fields(&self) -> Iter<'_, NamedField> {
self.iter()
}
@ -120,6 +137,54 @@ impl TupleLikeInfo for TupleInfo {
}
}
impl Container for TupleInfo {
fn get_field_registration<'a, E: Error>(
&self,
index: usize,
registry: &'a TypeRegistry,
) -> Result<&'a TypeRegistration, E> {
let field = self.field_at(index).ok_or_else(|| {
de::Error::custom(format_args!(
"no field at index {} on tuple {}",
index,
self.type_path(),
))
})?;
get_registration(field.type_id(), field.type_path(), registry)
}
}
impl TupleLikeInfo for TupleStructInfo {
fn get_path(&self) -> &str {
self.type_path()
}
fn get_field(&self, index: usize) -> Option<&UnnamedField> {
self.field_at(index)
}
fn get_field_len(&self) -> usize {
self.field_len()
}
}
impl Container for TupleStructInfo {
fn get_field_registration<'a, E: Error>(
&self,
index: usize,
registry: &'a TypeRegistry,
) -> Result<&'a TypeRegistration, E> {
let field = self.field_at(index).ok_or_else(|| {
de::Error::custom(format_args!(
"no field at index {} on tuple struct {}",
index,
self.type_path(),
))
})?;
get_registration(field.type_id(), field.type_path(), registry)
}
}
impl TupleLikeInfo for TupleVariantInfo {
fn get_path(&self) -> &str {
self.name()
@ -134,6 +199,23 @@ impl TupleLikeInfo for TupleVariantInfo {
}
}
impl Container for TupleVariantInfo {
fn get_field_registration<'a, E: Error>(
&self,
index: usize,
registry: &'a TypeRegistry,
) -> Result<&'a TypeRegistration, E> {
let field = self.field_at(index).ok_or_else(|| {
de::Error::custom(format_args!(
"no field at index {} on tuple variant {}",
index,
self.name(),
))
})?;
get_registration(field.type_id(), field.type_path(), registry)
}
}
/// A debug struct used for error messages that displays a list of expected values.
///
/// # Example
@ -444,6 +526,7 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> {
tuple_info.field_len(),
TupleVisitor {
tuple_info,
registration: self.registration,
registry: self.registry,
},
)?;
@ -500,43 +583,14 @@ impl<'a, 'de> Visitor<'de> for StructVisitor<'a> {
where
V: MapAccess<'de>,
{
visit_struct(&mut map, self.struct_info, self.registry)
visit_struct(&mut map, self.struct_info, self.registration, self.registry)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut index = 0usize;
let mut output = DynamicStruct::default();
let ignored_len = self
.registration
.data::<SerializationData>()
.map(|data| data.len())
.unwrap_or(0);
let field_len = self.struct_info.field_len().saturating_sub(ignored_len);
if field_len == 0 {
// Handle unit structs and ignored fields
return Ok(output);
}
while let Some(value) = seq.next_element_seed(TypedReflectDeserializer {
registration: self
.struct_info
.get_field_registration(index, self.registry)?,
registry: self.registry,
})? {
let name = self.struct_info.field_at(index).unwrap().name();
output.insert_boxed(name, value);
index += 1;
if index >= self.struct_info.field_len() {
break;
}
}
Ok(output)
visit_struct_seq(&mut seq, self.struct_info, self.registration, self.registry)
}
}
@ -557,64 +611,19 @@ impl<'a, 'de> Visitor<'de> for TupleStructVisitor<'a> {
where
V: SeqAccess<'de>,
{
let mut index = 0usize;
let mut tuple_struct = DynamicTupleStruct::default();
let ignored_len = self
.registration
.data::<SerializationData>()
.map(|data| data.len())
.unwrap_or(0);
let field_len = self
.tuple_struct_info
.field_len()
.saturating_sub(ignored_len);
if field_len == 0 {
// Handle unit structs and ignored fields
return Ok(tuple_struct);
}
let get_field_registration = |index: usize| -> Result<&'a TypeRegistration, V::Error> {
let field = self.tuple_struct_info.field_at(index).ok_or_else(|| {
de::Error::custom(format_args!(
"no field at index {} on tuple {}",
index,
self.tuple_struct_info.type_path(),
))
})?;
get_registration(field.type_id(), field.type_path(), self.registry)
};
while let Some(value) = seq.next_element_seed(TypedReflectDeserializer {
registration: get_field_registration(index)?,
registry: self.registry,
})? {
tuple_struct.insert_boxed(value);
index += 1;
if index >= self.tuple_struct_info.field_len() {
break;
}
}
let ignored_len = self
.registration
.data::<SerializationData>()
.map(|data| data.len())
.unwrap_or(0);
if tuple_struct.field_len() != self.tuple_struct_info.field_len() - ignored_len {
return Err(Error::invalid_length(
tuple_struct.field_len(),
&self.tuple_struct_info.field_len().to_string().as_str(),
));
}
Ok(tuple_struct)
visit_tuple(
&mut seq,
self.tuple_struct_info,
self.registration,
self.registry,
)
.map(DynamicTupleStruct::from)
}
}
struct TupleVisitor<'a> {
tuple_info: &'static TupleInfo,
registration: &'a TypeRegistration,
registry: &'a TypeRegistry,
}
@ -629,7 +638,7 @@ impl<'a, 'de> Visitor<'de> for TupleVisitor<'a> {
where
V: SeqAccess<'de>,
{
visit_tuple(&mut seq, self.tuple_info, self.registry)
visit_tuple(&mut seq, self.tuple_info, self.registration, self.registry)
}
}
@ -782,9 +791,7 @@ impl<'a, 'de> Visitor<'de> for EnumVisitor<'a> {
)?
.into(),
VariantInfo::Tuple(tuple_info) if tuple_info.field_len() == 1 => {
let field = tuple_info.field_at(0).unwrap();
let registration =
get_registration(field.type_id(), field.type_path(), self.registry)?;
let registration = tuple_info.get_field_registration(0, self.registry)?;
let value = variant.newtype_variant_seed(TypedReflectDeserializer {
registration,
registry: self.registry,
@ -879,43 +886,14 @@ impl<'a, 'de> Visitor<'de> for StructVariantVisitor<'a> {
where
V: MapAccess<'de>,
{
visit_struct(&mut map, self.struct_info, self.registry)
visit_struct(&mut map, self.struct_info, self.registration, self.registry)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut index = 0usize;
let mut output = DynamicStruct::default();
let ignored_len = self
.registration
.data::<SerializationData>()
.map(|data| data.len())
.unwrap_or(0);
let field_len = self.struct_info.field_len().saturating_sub(ignored_len);
if field_len == 0 {
// Handle all fields being ignored
return Ok(output);
}
while let Some(value) = seq.next_element_seed(TypedReflectDeserializer {
registration: self
.struct_info
.get_field_registration(index, self.registry)?,
registry: self.registry,
})? {
let name = self.struct_info.field_at(index).unwrap().name();
output.insert_boxed(name, value);
index += 1;
if index >= self.struct_info.field_len() {
break;
}
}
Ok(output)
visit_struct_seq(&mut seq, self.struct_info, self.registration, self.registry)
}
}
@ -936,19 +914,7 @@ impl<'a, 'de> Visitor<'de> for TupleVariantVisitor<'a> {
where
V: SeqAccess<'de>,
{
let ignored_len = self
.registration
.data::<SerializationData>()
.map(|data| data.len())
.unwrap_or(0);
let field_len = self.tuple_info.field_len().saturating_sub(ignored_len);
if field_len == 0 {
// Handle all fields being ignored
return Ok(DynamicTuple::default());
}
visit_tuple(&mut seq, self.tuple_info, self.registry)
visit_tuple(&mut seq, self.tuple_info, self.registration, self.registry)
}
}
@ -1005,6 +971,7 @@ impl<'a, 'de> Visitor<'de> for OptionVisitor<'a> {
fn visit_struct<'de, T, V>(
map: &mut V,
info: &'static T,
registration: &TypeRegistration,
registry: &TypeRegistry,
) -> Result<DynamicStruct, V::Error>
where
@ -1029,51 +996,103 @@ where
dynamic_struct.insert_boxed(&key, value);
}
if let Some(serialization_data) = registration.data::<SerializationData>() {
for (skipped_index, skipped_field) in serialization_data.iter_skipped() {
let Some(field) = info.field_at(*skipped_index) else {
continue;
};
dynamic_struct.insert_boxed(field.name(), skipped_field.generate_default());
}
}
Ok(dynamic_struct)
}
fn visit_tuple<'de, T, V>(
seq: &mut V,
info: &T,
registration: &TypeRegistration,
registry: &TypeRegistry,
) -> Result<DynamicTuple, V::Error>
where
T: TupleLikeInfo,
T: TupleLikeInfo + Container,
V: SeqAccess<'de>,
{
let mut tuple = DynamicTuple::default();
let mut index = 0usize;
let get_field_registration = |index: usize| -> Result<&TypeRegistration, V::Error> {
let field = info.get_field(index).ok_or_else(|| {
Error::invalid_length(index, &info.get_field_len().to_string().as_str())
})?;
get_registration(field.type_id(), field.type_path(), registry)
};
while let Some(value) = seq.next_element_seed(TypedReflectDeserializer {
registration: get_field_registration(index)?,
registry,
})? {
tuple.insert_boxed(value);
index += 1;
if index >= info.get_field_len() {
break;
}
}
let len = info.get_field_len();
if tuple.field_len() != len {
return Err(Error::invalid_length(
tuple.field_len(),
&len.to_string().as_str(),
));
if len == 0 {
// Handle empty tuple/tuple struct
return Ok(tuple);
}
let serialization_data = registration.data::<SerializationData>();
for index in 0..len {
if let Some(value) = serialization_data.and_then(|data| data.generate_default(index)) {
tuple.insert_boxed(value);
continue;
}
let value = seq
.next_element_seed(TypedReflectDeserializer {
registration: info.get_field_registration(index, registry)?,
registry,
})?
.ok_or_else(|| Error::invalid_length(index, &len.to_string().as_str()))?;
tuple.insert_boxed(value);
}
Ok(tuple)
}
fn visit_struct_seq<'de, T, V>(
seq: &mut V,
info: &T,
registration: &TypeRegistration,
registry: &TypeRegistry,
) -> Result<DynamicStruct, V::Error>
where
T: StructLikeInfo + Container,
V: SeqAccess<'de>,
{
let mut dynamic_struct = DynamicStruct::default();
let len = info.get_field_len();
if len == 0 {
// Handle unit structs
return Ok(dynamic_struct);
}
let serialization_data = registration.data::<SerializationData>();
for index in 0..len {
let name = info.field_at(index).unwrap().name();
if serialization_data
.map(|data| data.is_field_skipped(index))
.unwrap_or_default()
{
if let Some(value) = serialization_data.unwrap().generate_default(index) {
dynamic_struct.insert_boxed(name, value);
}
continue;
}
let value = seq
.next_element_seed(TypedReflectDeserializer {
registration: info.get_field_registration(index, registry)?,
registry,
})?
.ok_or_else(|| Error::invalid_length(index, &len.to_string().as_str()))?;
dynamic_struct.insert_boxed(name, value);
}
Ok(dynamic_struct)
}
fn get_registration<'a, E: Error>(
type_id: TypeId,
type_path: &str,

View file

@ -12,7 +12,7 @@ mod tests {
use crate::{
serde::{ReflectSerializer, UntypedReflectDeserializer},
type_registry::TypeRegistry,
DynamicStruct, Reflect,
DynamicStruct, FromReflect, Reflect,
};
use serde::de::DeserializeSeed;
@ -26,7 +26,14 @@ mod tests {
b: i32,
#[reflect(skip_serializing)]
c: i32,
#[reflect(skip_serializing)]
#[reflect(default = "custom_default")]
d: i32,
e: i32,
}
fn custom_default() -> i32 {
-1
}
let mut registry = TypeRegistry::default();
@ -37,24 +44,42 @@ mod tests {
b: 4,
c: 5,
d: 6,
e: 7,
};
let serializer = ReflectSerializer::new(&test_struct, &registry);
let serialized =
ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap();
let mut expected = DynamicStruct::default();
expected.insert("a", 3);
expected.insert("d", 6);
let mut deserializer = ron::de::Deserializer::from_str(&serialized).unwrap();
let reflect_deserializer = UntypedReflectDeserializer::new(&registry);
let value = reflect_deserializer.deserialize(&mut deserializer).unwrap();
let deserialized = value.take::<DynamicStruct>().unwrap();
let mut expected = DynamicStruct::default();
expected.insert("a", 3);
// Ignored: expected.insert("b", 0);
expected.insert("c", 0);
expected.insert("d", -1);
expected.insert("e", 7);
assert!(
expected.reflect_partial_eq(&deserialized).unwrap(),
"Expected {expected:?} found {deserialized:?}"
"Deserialization failed: expected {expected:?} found {deserialized:?}"
);
let expected = TestStruct {
a: 3,
b: 0,
c: 0,
d: -1,
e: 7,
};
let received = <TestStruct as FromReflect>::from_reflect(&deserialized).unwrap();
assert_eq!(
expected, received,
"FromReflect failed: expected {expected:?} found {received:?}"
);
}
@ -66,30 +91,48 @@ mod tests {
i32,
#[reflect(ignore)] i32,
#[reflect(skip_serializing)] i32,
#[reflect(skip_serializing)]
#[reflect(default = "custom_default")]
i32,
i32,
);
fn custom_default() -> i32 {
-1
}
let mut registry = TypeRegistry::default();
registry.register::<TestStruct>();
let test_struct = TestStruct(3, 4, 5, 6);
let test_struct = TestStruct(3, 4, 5, 6, 7);
let serializer = ReflectSerializer::new(&test_struct, &registry);
let serialized =
ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap();
let mut expected = DynamicTupleStruct::default();
expected.insert(3);
expected.insert(6);
let mut deserializer = ron::de::Deserializer::from_str(&serialized).unwrap();
let reflect_deserializer = UntypedReflectDeserializer::new(&registry);
let value = reflect_deserializer.deserialize(&mut deserializer).unwrap();
let deserialized = value.take::<DynamicTupleStruct>().unwrap();
let mut expected = DynamicTupleStruct::default();
expected.insert(3);
// Ignored: expected.insert(0);
expected.insert(0);
expected.insert(-1);
expected.insert(7);
assert!(
expected.reflect_partial_eq(&deserialized).unwrap(),
"Expected {expected:?} found {deserialized:?}"
"Deserialization failed: expected {expected:?} found {deserialized:?}"
);
let expected = TestStruct(3, 0, 0, -1, 7);
let received = <TestStruct as FromReflect>::from_reflect(&deserialized).unwrap();
assert_eq!(
expected, received,
"FromReflect failed: expected {expected:?} found {received:?}"
);
}

View file

@ -212,7 +212,7 @@ impl<'a> Serialize for StructSerializer<'a> {
for (index, value) in self.struct_value.iter_fields().enumerate() {
if serialization_data
.map(|data| data.is_ignored_field(index))
.map(|data| data.is_field_skipped(index))
.unwrap_or(false)
{
continue;
@ -265,7 +265,7 @@ impl<'a> Serialize for TupleStructSerializer<'a> {
for (index, value) in self.tuple_struct.iter_fields().enumerate() {
if serialization_data
.map(|data| data.is_ignored_field(index))
.map(|data| data.is_field_skipped(index))
.unwrap_or(false)
{
continue;

View file

@ -1,44 +1,136 @@
use std::collections::HashSet;
use crate::Reflect;
use bevy_utils::hashbrown::hash_map::Iter;
use bevy_utils::HashMap;
/// Contains data relevant to the automatic reflect powered serialization of a type
/// Contains data relevant to the automatic reflect powered (de)serialization of a type.
#[derive(Debug, Clone)]
pub struct SerializationData {
ignored_field_indices: HashSet<usize>,
skipped_fields: HashMap<usize, SkippedField>,
}
impl SerializationData {
/// Creates a new `SerializationData` instance given:
/// Creates a new `SerializationData` instance with the given skipped fields.
///
/// - `ignored_iter`: the iterator of member indices to be ignored during serialization. Indices are assigned only to reflected members, those which are not reflected are skipped.
pub fn new<I: Iterator<Item = usize>>(ignored_iter: I) -> Self {
/// # Arguments
///
/// * `skipped_iter`: The iterator of field indices to be skipped during (de)serialization.
/// Indices are assigned only to reflected fields.
/// Ignored fields (i.e. those marked `#[reflect(ignore)]`) are implicitly skipped
/// and do not need to be included in this iterator.
pub fn new<I: Iterator<Item = (usize, SkippedField)>>(skipped_iter: I) -> Self {
Self {
ignored_field_indices: ignored_iter.collect(),
skipped_fields: skipped_iter.collect(),
}
}
/// Returns true if the given index corresponds to a field meant to be ignored in serialization.
///
/// Indices start from 0 and ignored fields are skipped.
/// Returns true if the given index corresponds to a field meant to be skipped during (de)serialization.
///
/// # Example
///
/// ```rust,ignore
/// ```
/// # use std::any::TypeId;
/// # use bevy_reflect::{Reflect, Struct, TypeRegistry, serde::SerializationData};
/// #[derive(Reflect)]
/// struct MyStruct {
/// serialize_me: i32,
/// #[reflect(skip_serializing)]
/// skip_me: i32
/// }
///
/// let mut registry = TypeRegistry::new();
/// registry.register::<MyStruct>();
///
/// let my_struct = MyStruct {
/// serialize_me: 123,
/// skip_me: 321,
/// };
///
/// let serialization_data = registry.get_type_data::<SerializationData>(TypeId::of::<MyStruct>()).unwrap();
///
/// for (idx, field) in my_struct.iter_fields().enumerate(){
/// if serialization_data.is_ignored_field(idx){
/// // serialize ...
/// }
/// if serialization_data.is_field_skipped(idx) {
/// // Skipped!
/// assert_eq!(1, idx);
/// } else {
/// // Not Skipped!
/// assert_eq!(0, idx);
/// }
/// }
/// ```
pub fn is_ignored_field(&self, index: usize) -> bool {
self.ignored_field_indices.contains(&index)
pub fn is_field_skipped(&self, index: usize) -> bool {
self.skipped_fields.contains_key(&index)
}
/// Returns the number of ignored fields.
/// Generates a default instance of the skipped field at the given index.
///
/// Returns `None` if the field is not skipped.
///
/// # Example
///
/// ```
/// # use std::any::TypeId;
/// # use bevy_reflect::{Reflect, Struct, TypeRegistry, serde::SerializationData};
/// #[derive(Reflect)]
/// struct MyStruct {
/// serialize_me: i32,
/// #[reflect(skip_serializing)]
/// #[reflect(default = "skip_me_default")]
/// skip_me: i32
/// }
///
/// fn skip_me_default() -> i32 {
/// 789
/// }
///
/// let mut registry = TypeRegistry::new();
/// registry.register::<MyStruct>();
///
/// let serialization_data = registry.get_type_data::<SerializationData>(TypeId::of::<MyStruct>()).unwrap();
/// assert_eq!(789, serialization_data.generate_default(1).unwrap().take::<i32>().unwrap());
/// ```
pub fn generate_default(&self, index: usize) -> Option<Box<dyn Reflect>> {
self.skipped_fields
.get(&index)
.map(|field| field.generate_default())
}
/// Returns the number of skipped fields.
pub fn len(&self) -> usize {
self.ignored_field_indices.len()
self.skipped_fields.len()
}
/// Returns true if there are no ignored fields.
/// Returns true if there are no skipped fields.
pub fn is_empty(&self) -> bool {
self.ignored_field_indices.is_empty()
self.skipped_fields.is_empty()
}
/// Returns an iterator over the skipped fields.
///
/// Each item in the iterator is a tuple containing:
/// 1. The reflected index of the field
/// 2. The (de)serialization metadata of the field
pub fn iter_skipped(&self) -> Iter<'_, usize, SkippedField> {
self.skipped_fields.iter()
}
}
/// Data needed for (de)serialization of a skipped field.
#[derive(Debug, Clone)]
pub struct SkippedField {
default_fn: fn() -> Box<dyn Reflect>,
}
impl SkippedField {
/// Create a new `SkippedField`.
///
/// # Arguments
///
/// * `default_fn`: A function pointer used to generate a default instance of the field.
pub fn new(default_fn: fn() -> Box<dyn Reflect>) -> Self {
Self { default_fn }
}
/// Generates a default instance of the field.
pub fn generate_default(&self) -> Box<dyn Reflect> {
(self.default_fn)()
}
}

View file

@ -1,8 +1,8 @@
use bevy_reflect_derive::impl_type_path;
use crate::{
self as bevy_reflect, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath,
TypePathTable, UnnamedField,
self as bevy_reflect, DynamicTuple, Reflect, ReflectMut, ReflectOwned, ReflectRef, Tuple,
TypeInfo, TypePath, TypePathTable, UnnamedField,
};
use std::any::{Any, TypeId};
use std::fmt::{Debug, Formatter};
@ -390,6 +390,15 @@ impl Debug for DynamicTupleStruct {
}
}
impl From<DynamicTuple> for DynamicTupleStruct {
fn from(value: DynamicTuple) -> Self {
Self {
represented_type: None,
fields: Box::new(value).drain(),
}
}
}
/// Compares a [`TupleStruct`] with a [`Reflect`] value.
///
/// Returns true if and only if all of the following are true: