Skip to content

Commit 2081e07

Browse files
committed
Respect #[reflect(default)] on variant fields
1 parent cb286e5 commit 2081e07

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)