 60773e6787
			
		
	
	
		60773e6787
		
			
		
	
	
	
	
		
			
			# 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.
		
			
				
	
	
		
			92 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			92 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| 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 })
 | |
|     }
 | |
| }
 |