Skip to content

Commit e74ef79

Browse files
committed
bevy_reflect: Add #[reflect(default)] attribute for FromReflect (#4140)
# Objective Currently, `FromReflect` makes a couple assumptions: * Ignored fields must implement `Default` * Active fields must implement `FromReflect` * The reflected must be fully populated for active fields (can't use an empty `DynamicStruct`) However, one or both of these requirements might be unachievable, such as for external types. In these cases, it might be nice to tell `FromReflect` to use a custom default. ## Solution Added the `#[reflect(default)]` derive helper attribute. This attribute can be applied to any field (ignored or not) and will allow a default value to be specified in place of the regular `from_reflect()` call. It takes two forms: `#[reflect(default)]` and `#[reflect(default = "some_func")]`. The former specifies that `Default::default()` should be used while the latter specifies that `some_func()` should be used. This is pretty much [how serde does it](https://serde.rs/field-attrs.html#default). ### Example ```rust #[derive(Reflect, FromReflect)] struct MyStruct { // Use `Default::default()` #[reflect(default)] foo: String, // Use `get_bar_default()` #[reflect(default = "get_bar_default")] #[reflect(ignore)] bar: usize, } fn get_bar_default() -> usize { 123 } ``` ### Active Fields As an added benefit, this also allows active fields to be completely missing from their dynamic object. This is because the attribute tells `FromReflect` how to handle missing active fields (it still tries to use `from_reflect` first so the `FromReflect` trait is still required). ```rust let dyn_struct = DynamicStruct::default(); // We can do this without actually including the active fields since they have `#[reflect(default)]` let my_struct = <MyStruct as FromReflect>::from_reflect(&dyn_struct); ``` ### Container Defaults Also, with the addition of #3733, people will likely start adding `#[reflect(Default)]` to their types now. Just like with the fields, we can use this to mark the entire container as "defaultable". This grants us the ability to completely remove the field markers altogether if our type implements `Default` (and we're okay with fields using that instead of their own `Default` impls): ```rust #[derive(Reflect, FromReflect)] #[reflect(Default)] struct MyStruct { foo: String, #[reflect(ignore)] bar: usize, } impl Default for MyStruct { fn default() -> Self { Self { foo: String::from("Hello"), bar: 123, } } } // Again, we can now construct this from nothing pretty much let dyn_struct = DynamicStruct::default(); let my_struct = <MyStruct as FromReflect>::from_reflect(&dyn_struct); ``` Now if _any_ field is missing when using `FromReflect`, we simply fallback onto the container's `Default` implementation. This behavior can be completely overridden on a per-field basis, of course, by simply defining those same field attributes like before. ### Related * #3733 * #1395 * #2377 --- ## Changelog * Added `#[reflect(default)]` field attribute for `FromReflect` * Allows missing fields to be given a default value when using `FromReflect` * `#[reflect(default)]` - Use the field's `Default` implementation * `#[reflect(default = "some_fn")]` - Use a custom function to get the default value * Allow `#[reflect(Default)]` to have a secondary usage as a container attribute * Allows missing fields to be given a default value based on the container's `Default` impl when using `FromReflect` Co-authored-by: Gino Valente <[email protected]>
1 parent ba53a44 commit e74ef79

File tree

5 files changed

+160
-16
lines changed

5 files changed

+160
-16
lines changed

crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ const PARTIAL_EQ_ATTR: &str = "PartialEq";
2020
const HASH_ATTR: &str = "Hash";
2121
const SERIALIZE_ATTR: &str = "Serialize";
2222

23+
// The traits listed below are not considered "special" (i.e. they use the `ReflectMyTrait` syntax)
24+
// but useful to know exist nonetheless
25+
pub(crate) const REFLECT_DEFAULT: &str = "ReflectDefault";
26+
2327
/// A marker for trait implementations registered via the `Reflect` derive macro.
2428
#[derive(Clone)]
2529
pub(crate) enum TraitImpl {

crates/bevy_reflect/bevy_reflect_derive/src/field_attributes.rs

+44-2
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,37 @@
77
use crate::REFLECT_ATTRIBUTE_NAME;
88
use quote::ToTokens;
99
use syn::spanned::Spanned;
10-
use syn::{Attribute, Meta, NestedMeta};
10+
use syn::{Attribute, Lit, Meta, NestedMeta};
1111

1212
pub(crate) static IGNORE_ATTR: &str = "ignore";
13+
pub(crate) static DEFAULT_ATTR: &str = "default";
1314

14-
/// A container for attributes defined on a field reflected type's field.
15+
/// A container for attributes defined on a reflected type's field.
1516
#[derive(Default)]
1617
pub(crate) struct ReflectFieldAttr {
1718
/// Determines if this field should be ignored.
1819
pub ignore: bool,
20+
/// Sets the default behavior of this field.
21+
pub default: DefaultBehavior,
22+
}
23+
24+
/// Controls how the default value is determined for a field.
25+
pub(crate) enum DefaultBehavior {
26+
/// Field is required.
27+
Required,
28+
/// Field can be defaulted using `Default::default()`.
29+
Default,
30+
/// Field can be created using the given function name.
31+
///
32+
/// This assumes the function is in scope, is callable with zero arguments,
33+
/// and returns the expected type.
34+
Func(syn::ExprPath),
35+
}
36+
37+
impl Default for DefaultBehavior {
38+
fn default() -> Self {
39+
Self::Required
40+
}
1941
}
2042

2143
/// Parse all field attributes marked "reflect" (such as `#[reflect(ignore)]`).
@@ -44,16 +66,36 @@ pub(crate) fn parse_field_attrs(attrs: &[Attribute]) -> Result<ReflectFieldAttr,
4466
}
4567
}
4668

69+
/// Recursively parses attribute metadata for things like `#[reflect(ignore)]` and `#[reflect(default = "foo")]`
4770
fn parse_meta(args: &mut ReflectFieldAttr, meta: &Meta) -> Result<(), syn::Error> {
4871
match meta {
4972
Meta::Path(path) if path.is_ident(IGNORE_ATTR) => {
5073
args.ignore = true;
5174
Ok(())
5275
}
76+
Meta::Path(path) if path.is_ident(DEFAULT_ATTR) => {
77+
args.default = DefaultBehavior::Default;
78+
Ok(())
79+
}
5380
Meta::Path(path) => Err(syn::Error::new(
5481
path.span(),
5582
format!("unknown attribute parameter: {}", path.to_token_stream()),
5683
)),
84+
Meta::NameValue(pair) if pair.path.is_ident(DEFAULT_ATTR) => {
85+
let lit = &pair.lit;
86+
match lit {
87+
Lit::Str(lit_str) => {
88+
args.default = DefaultBehavior::Func(lit_str.parse()?);
89+
Ok(())
90+
}
91+
err => {
92+
Err(syn::Error::new(
93+
err.span(),
94+
format!("expected a string literal containing the name of a function, but found: {}", err.to_token_stream()),
95+
))
96+
}
97+
}
98+
}
5799
Meta::NameValue(pair) => {
58100
let path = &pair.path;
59101
Err(syn::Error::new(

crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs

+49-14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::container_attributes::REFLECT_DEFAULT;
2+
use crate::field_attributes::DefaultBehavior;
13
use crate::ReflectDeriveData;
24
use proc_macro::TokenStream;
35
use proc_macro2::Span;
@@ -53,24 +55,28 @@ fn impl_struct_internal(derive_data: &ReflectDeriveData, is_tuple: bool) -> Toke
5355
};
5456

5557
let field_types = derive_data.active_types();
56-
let MemberValuePair(ignored_members, ignored_values) =
57-
get_ignored_fields(derive_data, is_tuple);
5858
let MemberValuePair(active_members, active_values) =
5959
get_active_fields(derive_data, &ref_struct, &ref_struct_type, is_tuple);
6060

61-
let constructor = if derive_data.traits().contains("ReflectDefault") {
61+
let constructor = if derive_data.traits().contains(REFLECT_DEFAULT) {
6262
quote!(
6363
let mut __this = Self::default();
6464
#(
65-
__this.#active_members = #active_values;
65+
if let Some(__field) = #active_values() {
66+
// Iff field exists -> use its value
67+
__this.#active_members = __field;
68+
}
6669
)*
6770
Some(__this)
6871
)
6972
} else {
73+
let MemberValuePair(ignored_members, ignored_values) =
74+
get_ignored_fields(derive_data, is_tuple);
75+
7076
quote!(
7177
Some(
7278
Self {
73-
#(#active_members: #active_values,)*
79+
#(#active_members: #active_values()?,)*
7480
#(#ignored_members: #ignored_values,)*
7581
}
7682
)
@@ -106,14 +112,19 @@ fn impl_struct_internal(derive_data: &ReflectDeriveData, is_tuple: bool) -> Toke
106112
}
107113

108114
/// Get the collection of ignored field definitions
115+
///
116+
/// Each value of the `MemberValuePair` is a token stream that generates a
117+
/// a default value for the ignored field.
109118
fn get_ignored_fields(derive_data: &ReflectDeriveData, is_tuple: bool) -> MemberValuePair {
110119
MemberValuePair::new(
111120
derive_data
112121
.ignored_fields()
113122
.map(|field| {
114123
let member = get_ident(field.data, field.index, is_tuple);
115-
let value = quote! {
116-
Default::default()
124+
125+
let value = match &field.attrs.default {
126+
DefaultBehavior::Func(path) => quote! {#path()},
127+
_ => quote! {Default::default()},
117128
};
118129

119130
(member, value)
@@ -122,7 +133,10 @@ fn get_ignored_fields(derive_data: &ReflectDeriveData, is_tuple: bool) -> Member
122133
)
123134
}
124135

125-
/// Get the collection of active field definitions
136+
/// Get the collection of active field definitions.
137+
///
138+
/// Each value of the `MemberValuePair` is a token stream that generates a
139+
/// closure of type `fn() -> Option<T>` where `T` is that field's type.
126140
fn get_active_fields(
127141
derive_data: &ReflectDeriveData,
128142
dyn_struct_name: &Ident,
@@ -139,12 +153,33 @@ fn get_active_fields(
139153
let accessor = get_field_accessor(field.data, field.index, is_tuple);
140154
let ty = field.data.ty.clone();
141155

142-
let value = quote! { {
143-
<#ty as #bevy_reflect_path::FromReflect>::from_reflect(
144-
// Accesses the field on the given dynamic struct or tuple struct
145-
#bevy_reflect_path::#struct_type::field(#dyn_struct_name, #accessor)?
146-
)?
147-
}};
156+
let get_field = quote! {
157+
#bevy_reflect_path::#struct_type::field(#dyn_struct_name, #accessor)
158+
};
159+
160+
let value = match &field.attrs.default {
161+
DefaultBehavior::Func(path) => quote! {
162+
(||
163+
if let Some(field) = #get_field {
164+
<#ty as #bevy_reflect_path::FromReflect>::from_reflect(field)
165+
} else {
166+
Some(#path())
167+
}
168+
)
169+
},
170+
DefaultBehavior::Default => quote! {
171+
(||
172+
if let Some(field) = #get_field {
173+
<#ty as #bevy_reflect_path::FromReflect>::from_reflect(field)
174+
} else {
175+
Some(Default::default())
176+
}
177+
)
178+
},
179+
DefaultBehavior::Required => quote! {
180+
(|| <#ty as #bevy_reflect_path::FromReflect>::from_reflect(#get_field?))
181+
},
182+
};
148183

149184
(member, value)
150185
})

crates/bevy_reflect/bevy_reflect_derive/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ pub fn derive_reflect(input: TokenStream) -> TokenStream {
6161
///
6262
/// This macro supports the following field attributes:
6363
/// * `#[reflect(ignore)]`: Ignores the field. This requires the field to implement [`Default`].
64+
/// * `#[reflect(default)]`: If the field's value cannot be read, uses its [`Default`] implementation.
65+
/// * `#[reflect(default = "some_func")]`: If the field's value cannot be read, uses the function with the given name.
6466
///
6567
#[proc_macro_derive(FromReflect, attributes(reflect))]
6668
pub fn derive_from_reflect(input: TokenStream) -> TokenStream {

crates/bevy_reflect/src/lib.rs

+61
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ mod tests {
9494
};
9595
use std::fmt::{Debug, Formatter};
9696

97+
use super::prelude::*;
9798
use super::*;
9899
use crate as bevy_reflect;
99100
use crate::serde::{ReflectDeserializer, ReflectSerializer};
@@ -232,6 +233,66 @@ mod tests {
232233
assert_eq!(values, vec![1]);
233234
}
234235

236+
#[test]
237+
fn from_reflect_should_use_default_field_attributes() {
238+
#[derive(Reflect, FromReflect, Eq, PartialEq, Debug)]
239+
struct MyStruct {
240+
// Use `Default::default()`
241+
// Note that this isn't an ignored field
242+
#[reflect(default)]
243+
foo: String,
244+
245+
// Use `get_bar_default()`
246+
#[reflect(default = "get_bar_default")]
247+
#[reflect(ignore)]
248+
bar: usize,
249+
}
250+
251+
fn get_bar_default() -> usize {
252+
123
253+
}
254+
255+
let expected = MyStruct {
256+
foo: String::default(),
257+
bar: 123,
258+
};
259+
260+
let dyn_struct = DynamicStruct::default();
261+
let my_struct = <MyStruct as FromReflect>::from_reflect(&dyn_struct);
262+
263+
assert_eq!(Some(expected), my_struct);
264+
}
265+
266+
#[test]
267+
fn from_reflect_should_use_default_container_attribute() {
268+
#[derive(Reflect, FromReflect, Eq, PartialEq, Debug)]
269+
#[reflect(Default)]
270+
struct MyStruct {
271+
foo: String,
272+
#[reflect(ignore)]
273+
bar: usize,
274+
}
275+
276+
impl Default for MyStruct {
277+
fn default() -> Self {
278+
Self {
279+
foo: String::from("Hello"),
280+
bar: 123,
281+
}
282+
}
283+
}
284+
285+
let expected = MyStruct {
286+
foo: String::from("Hello"),
287+
bar: 123,
288+
};
289+
290+
let dyn_struct = DynamicStruct::default();
291+
let my_struct = <MyStruct as FromReflect>::from_reflect(&dyn_struct);
292+
293+
assert_eq!(Some(expected), my_struct);
294+
}
295+
235296
#[test]
236297
fn reflect_complex_patch() {
237298
#[derive(Reflect, Eq, PartialEq, Debug, FromReflect)]

0 commit comments

Comments
 (0)