Skip to content

Commit 6b292d4

Browse files
authored
bevy_reflect: Allow #[reflect(default)] on enum variant fields (#8514)
# Objective When using `FromReflect`, fields can be optionally left out if they are marked with `#[reflect(default)]`. This is very handy for working with serialized data as giant structs only need to list a subset of defined fields in order to be constructed. <details> <summary>Example</summary> Take the following struct: ```rust #[derive(Reflect, FromReflect)] struct Foo { #[reflect(default)] a: usize, #[reflect(default)] b: usize, #[reflect(default)] c: usize, #[reflect(default)] d: usize, } ``` Since all the fields are default-able, we can successfully call `FromReflect` on deserialized data like: ```rust ( "foo::Foo": ( // Only set `b` and default the rest b: 123 ) ) ``` </details> Unfortunately, this does not work with fields in enum variants. Marking a variant field as `#[reflect(default)]` does nothing when calling `FromReflect`. ## Solution Allow enum variant fields to define a default value using `#[reflect(default)]`. ### `#[reflect(Default)]` One thing that structs and tuple structs can do is use their `Default` implementation when calling `FromReflect`. Adding `#[reflect(Default)]` to the struct or tuple struct both registers `ReflectDefault` and alters the `FromReflect` implementation to use `Default` to generate any missing fields. This works well enough for structs and tuple structs, but for enums it's not as simple. Since the `Default` implementation for an enum only covers a single variant, it's not as intuitive as to what the behavior will be. And (imo) it feels weird that we would be able to specify default values in this way for one variant but not the others. Because of this, I chose to not implement that behavior here. However, I'm open to adding it in if anyone feels otherwise. --- ## Changelog - Allow enum variant fields to define a default value using `#[reflect(default)]`
1 parent 27e1cf9 commit 6b292d4

File tree

3 files changed

+83
-22
lines changed

3 files changed

+83
-22
lines changed

crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
use crate::fq_std::FQDefault;
1+
use crate::derive_data::StructField;
2+
use crate::field_attributes::DefaultBehavior;
3+
use crate::fq_std::{FQDefault, FQOption};
24
use crate::{
35
derive_data::{EnumVariantFields, ReflectEnum},
46
utility::ident_or_index,
57
};
68
use proc_macro2::Ident;
79
use quote::{quote, ToTokens};
10+
use syn::Member;
811

912
/// Contains all data needed to construct all variants within an enum.
1013
pub(crate) struct EnumVariantConstructors {
@@ -30,7 +33,7 @@ pub(crate) fn get_variant_constructors(
3033
let name = ident.to_string();
3134
let variant_constructor = reflect_enum.get_unit(ident);
3235

33-
let fields = match &variant.fields {
36+
let fields: &[StructField] = match &variant.fields {
3437
EnumVariantFields::Unit => &[],
3538
EnumVariantFields::Named(fields) | EnumVariantFields::Unnamed(fields) => {
3639
fields.as_slice()
@@ -39,35 +42,59 @@ pub(crate) fn get_variant_constructors(
3942
let mut reflect_index: usize = 0;
4043
let constructor_fields = fields.iter().enumerate().map(|(declare_index, field)| {
4144
let field_ident = ident_or_index(field.data.ident.as_ref(), declare_index);
45+
let field_ty = &field.data.ty;
46+
4247
let field_value = if field.attrs.ignore.is_ignored() {
43-
quote! { #FQDefault::default() }
48+
match &field.attrs.default {
49+
DefaultBehavior::Func(path) => quote! { #path() },
50+
_ => quote! { #FQDefault::default() }
51+
}
4452
} else {
45-
let error_repr = field.data.ident.as_ref().map_or_else(
46-
|| format!("at index {reflect_index}"),
47-
|name| format!("`{name}`"),
48-
);
49-
let unwrapper = if can_panic {
50-
let type_err_message = format!(
51-
"the field {error_repr} should be of type `{}`",
52-
field.data.ty.to_token_stream()
53-
);
54-
quote!(.expect(#type_err_message))
53+
let (resolve_error, resolve_missing) = if can_panic {
54+
let field_ref_str = match &field_ident {
55+
Member::Named(ident) => format!("the field `{ident}`"),
56+
Member::Unnamed(index) => format!("the field at index {}", index.index)
57+
};
58+
let ty = field.data.ty.to_token_stream();
59+
60+
let on_error = format!("{field_ref_str} should be of type `{ty}`");
61+
let on_missing = format!("{field_ref_str} is required but could not be found");
62+
63+
(quote!(.expect(#on_error)), quote!(.expect(#on_missing)))
5564
} else {
56-
quote!(?)
65+
(quote!(?), quote!(?))
5766
};
67+
5868
let field_accessor = match &field.data.ident {
5969
Some(ident) => {
6070
let name = ident.to_string();
61-
quote!(.field(#name))
71+
quote!(#ref_value.field(#name))
6272
}
63-
None => quote!(.field_at(#reflect_index)),
73+
None => quote!(#ref_value.field_at(#reflect_index)),
6474
};
6575
reflect_index += 1;
66-
let missing_field_err_message = format!("the field {error_repr} was not declared");
67-
let accessor = quote!(#field_accessor .expect(#missing_field_err_message));
68-
quote! {
69-
#bevy_reflect_path::FromReflect::from_reflect(#ref_value #accessor)
70-
#unwrapper
76+
77+
match &field.attrs.default {
78+
DefaultBehavior::Func(path) => quote! {
79+
if let #FQOption::Some(field) = #field_accessor {
80+
<#field_ty as #bevy_reflect_path::FromReflect>::from_reflect(field)
81+
#resolve_error
82+
} else {
83+
#path()
84+
}
85+
},
86+
DefaultBehavior::Default => quote! {
87+
if let #FQOption::Some(field) = #field_accessor {
88+
<#field_ty as #bevy_reflect_path::FromReflect>::from_reflect(field)
89+
#resolve_error
90+
} else {
91+
#FQDefault::default()
92+
}
93+
},
94+
DefaultBehavior::Required => quote! {
95+
<#field_ty as #bevy_reflect_path::FromReflect>::from_reflect(#field_accessor #resolve_missing)
96+
#resolve_error
97+
},
7198
}
7299
};
73100
quote! { #field_ident : #field_value }

crates/bevy_reflect/bevy_reflect_derive/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ pub(crate) static REFLECT_VALUE_ATTRIBUTE_NAME: &str = "reflect_value";
8787
/// to improve performance and/or robustness.
8888
/// An example of where this is used is in the [`FromReflect`] derive macro,
8989
/// where adding this attribute will cause the `FromReflect` implementation to create
90-
/// a base value using its [`Default`] implementation avoiding issues with ignored fields.
90+
/// a base value using its [`Default`] implementation avoiding issues with ignored fields
91+
/// (for structs and tuple structs only).
9192
///
9293
/// ## `#[reflect_value]`
9394
///

crates/bevy_reflect/src/lib.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,39 @@ mod tests {
751751
assert_eq!(Some(expected), my_struct);
752752
}
753753

754+
#[test]
755+
fn from_reflect_should_use_default_variant_field_attributes() {
756+
#[derive(Reflect, FromReflect, Eq, PartialEq, Debug)]
757+
enum MyEnum {
758+
Foo(#[reflect(default)] String),
759+
Bar {
760+
#[reflect(default = "get_baz_default")]
761+
#[reflect(ignore)]
762+
baz: usize,
763+
},
764+
}
765+
766+
fn get_baz_default() -> usize {
767+
123
768+
}
769+
770+
let expected = MyEnum::Foo(String::default());
771+
772+
let dyn_enum = DynamicEnum::new("Foo", DynamicTuple::default());
773+
let my_enum = <MyEnum as FromReflect>::from_reflect(&dyn_enum);
774+
775+
assert_eq!(Some(expected), my_enum);
776+
777+
let expected = MyEnum::Bar {
778+
baz: get_baz_default(),
779+
};
780+
781+
let dyn_enum = DynamicEnum::new("Bar", DynamicStruct::default());
782+
let my_enum = <MyEnum as FromReflect>::from_reflect(&dyn_enum);
783+
784+
assert_eq!(Some(expected), my_enum);
785+
}
786+
754787
#[test]
755788
fn from_reflect_should_use_default_container_attribute() {
756789
#[derive(Reflect, FromReflect, Eq, PartialEq, Debug)]

0 commit comments

Comments
 (0)