From e74ef790ae10899e497ebf5699760787d2362ceb Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 30 May 2022 18:30:59 +0000 Subject: [PATCH] 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 = ::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 = ::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 <49806985+MrGVSV@users.noreply.github.com> --- .../src/container_attributes.rs | 4 ++ .../src/field_attributes.rs | 46 +++++++++++++- .../bevy_reflect_derive/src/from_reflect.rs | 63 ++++++++++++++----- .../bevy_reflect_derive/src/lib.rs | 2 + crates/bevy_reflect/src/lib.rs | 61 ++++++++++++++++++ 5 files changed, 160 insertions(+), 16 deletions(-) diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs b/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs index d75a987a046d1..dea47069a112d 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs @@ -20,6 +20,10 @@ const PARTIAL_EQ_ATTR: &str = "PartialEq"; const HASH_ATTR: &str = "Hash"; const SERIALIZE_ATTR: &str = "Serialize"; +// The traits listed below are not considered "special" (i.e. they use the `ReflectMyTrait` syntax) +// but useful to know exist nonetheless +pub(crate) const REFLECT_DEFAULT: &str = "ReflectDefault"; + /// A marker for trait implementations registered via the `Reflect` derive macro. #[derive(Clone)] pub(crate) enum TraitImpl { diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/field_attributes.rs b/crates/bevy_reflect/bevy_reflect_derive/src/field_attributes.rs index a0f3d2b6bc3a0..32b9bc1e8dfbf 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/field_attributes.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/field_attributes.rs @@ -7,15 +7,37 @@ use crate::REFLECT_ATTRIBUTE_NAME; use quote::ToTokens; use syn::spanned::Spanned; -use syn::{Attribute, Meta, NestedMeta}; +use syn::{Attribute, Lit, Meta, NestedMeta}; pub(crate) static IGNORE_ATTR: &str = "ignore"; +pub(crate) static DEFAULT_ATTR: &str = "default"; -/// A container for attributes defined on a field reflected type's field. +/// A container for attributes defined on a reflected type's field. #[derive(Default)] pub(crate) struct ReflectFieldAttr { /// Determines if this field should be ignored. pub ignore: bool, + /// Sets the default behavior of this field. + pub default: DefaultBehavior, +} + +/// Controls how the default value is determined for a field. +pub(crate) enum DefaultBehavior { + /// Field is required. + Required, + /// Field can be defaulted using `Default::default()`. + Default, + /// Field can be created using the given function name. + /// + /// This assumes the function is in scope, is callable with zero arguments, + /// and returns the expected type. + Func(syn::ExprPath), +} + +impl Default for DefaultBehavior { + fn default() -> Self { + Self::Required + } } /// Parse all field attributes marked "reflect" (such as `#[reflect(ignore)]`). @@ -44,16 +66,36 @@ pub(crate) fn parse_field_attrs(attrs: &[Attribute]) -> Result Result<(), syn::Error> { match meta { Meta::Path(path) if path.is_ident(IGNORE_ATTR) => { args.ignore = true; Ok(()) } + Meta::Path(path) if path.is_ident(DEFAULT_ATTR) => { + args.default = DefaultBehavior::Default; + Ok(()) + } Meta::Path(path) => Err(syn::Error::new( path.span(), format!("unknown attribute parameter: {}", path.to_token_stream()), )), + Meta::NameValue(pair) if pair.path.is_ident(DEFAULT_ATTR) => { + let lit = &pair.lit; + match lit { + Lit::Str(lit_str) => { + args.default = DefaultBehavior::Func(lit_str.parse()?); + Ok(()) + } + err => { + Err(syn::Error::new( + err.span(), + format!("expected a string literal containing the name of a function, but found: {}", err.to_token_stream()), + )) + } + } + } Meta::NameValue(pair) => { let path = &pair.path; Err(syn::Error::new( diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs b/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs index 8e9e9c679295b..1827424c93d40 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs @@ -1,3 +1,5 @@ +use crate::container_attributes::REFLECT_DEFAULT; +use crate::field_attributes::DefaultBehavior; use crate::ReflectDeriveData; use proc_macro::TokenStream; use proc_macro2::Span; @@ -53,24 +55,28 @@ fn impl_struct_internal(derive_data: &ReflectDeriveData, is_tuple: bool) -> Toke }; let field_types = derive_data.active_types(); - let MemberValuePair(ignored_members, ignored_values) = - get_ignored_fields(derive_data, is_tuple); let MemberValuePair(active_members, active_values) = get_active_fields(derive_data, &ref_struct, &ref_struct_type, is_tuple); - let constructor = if derive_data.traits().contains("ReflectDefault") { + let constructor = if derive_data.traits().contains(REFLECT_DEFAULT) { quote!( let mut __this = Self::default(); #( - __this.#active_members = #active_values; + if let Some(__field) = #active_values() { + // Iff field exists -> use its value + __this.#active_members = __field; + } )* Some(__this) ) } else { + let MemberValuePair(ignored_members, ignored_values) = + get_ignored_fields(derive_data, is_tuple); + quote!( Some( Self { - #(#active_members: #active_values,)* + #(#active_members: #active_values()?,)* #(#ignored_members: #ignored_values,)* } ) @@ -106,14 +112,19 @@ fn impl_struct_internal(derive_data: &ReflectDeriveData, is_tuple: bool) -> Toke } /// Get the collection of ignored field definitions +/// +/// Each value of the `MemberValuePair` is a token stream that generates a +/// a default value for the ignored field. fn get_ignored_fields(derive_data: &ReflectDeriveData, is_tuple: bool) -> MemberValuePair { MemberValuePair::new( derive_data .ignored_fields() .map(|field| { let member = get_ident(field.data, field.index, is_tuple); - let value = quote! { - Default::default() + + let value = match &field.attrs.default { + DefaultBehavior::Func(path) => quote! {#path()}, + _ => quote! {Default::default()}, }; (member, value) @@ -122,7 +133,10 @@ fn get_ignored_fields(derive_data: &ReflectDeriveData, is_tuple: bool) -> Member ) } -/// Get the collection of active field definitions +/// Get the collection of active field definitions. +/// +/// Each value of the `MemberValuePair` is a token stream that generates a +/// closure of type `fn() -> Option` where `T` is that field's type. fn get_active_fields( derive_data: &ReflectDeriveData, dyn_struct_name: &Ident, @@ -139,12 +153,33 @@ fn get_active_fields( let accessor = get_field_accessor(field.data, field.index, is_tuple); let ty = field.data.ty.clone(); - let value = quote! { { - <#ty as #bevy_reflect_path::FromReflect>::from_reflect( - // Accesses the field on the given dynamic struct or tuple struct - #bevy_reflect_path::#struct_type::field(#dyn_struct_name, #accessor)? - )? - }}; + let get_field = quote! { + #bevy_reflect_path::#struct_type::field(#dyn_struct_name, #accessor) + }; + + let value = match &field.attrs.default { + DefaultBehavior::Func(path) => quote! { + (|| + if let Some(field) = #get_field { + <#ty as #bevy_reflect_path::FromReflect>::from_reflect(field) + } else { + Some(#path()) + } + ) + }, + DefaultBehavior::Default => quote! { + (|| + if let Some(field) = #get_field { + <#ty as #bevy_reflect_path::FromReflect>::from_reflect(field) + } else { + Some(Default::default()) + } + ) + }, + DefaultBehavior::Required => quote! { + (|| <#ty as #bevy_reflect_path::FromReflect>::from_reflect(#get_field?)) + }, + }; (member, value) }) diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs index 8d59def2c0f9b..7cf9fe560b004 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs @@ -61,6 +61,8 @@ pub fn derive_reflect(input: TokenStream) -> TokenStream { /// /// This macro supports the following field attributes: /// * `#[reflect(ignore)]`: Ignores the field. This requires the field to implement [`Default`]. +/// * `#[reflect(default)]`: If the field's value cannot be read, uses its [`Default`] implementation. +/// * `#[reflect(default = "some_func")]`: If the field's value cannot be read, uses the function with the given name. /// #[proc_macro_derive(FromReflect, attributes(reflect))] pub fn derive_from_reflect(input: TokenStream) -> TokenStream { diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 0319b144a2461..d82ba808412e4 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -94,6 +94,7 @@ mod tests { }; use std::fmt::{Debug, Formatter}; + use super::prelude::*; use super::*; use crate as bevy_reflect; use crate::serde::{ReflectDeserializer, ReflectSerializer}; @@ -232,6 +233,66 @@ mod tests { assert_eq!(values, vec![1]); } + #[test] + fn from_reflect_should_use_default_field_attributes() { + #[derive(Reflect, FromReflect, Eq, PartialEq, Debug)] + struct MyStruct { + // Use `Default::default()` + // Note that this isn't an ignored field + #[reflect(default)] + foo: String, + + // Use `get_bar_default()` + #[reflect(default = "get_bar_default")] + #[reflect(ignore)] + bar: usize, + } + + fn get_bar_default() -> usize { + 123 + } + + let expected = MyStruct { + foo: String::default(), + bar: 123, + }; + + let dyn_struct = DynamicStruct::default(); + let my_struct = ::from_reflect(&dyn_struct); + + assert_eq!(Some(expected), my_struct); + } + + #[test] + fn from_reflect_should_use_default_container_attribute() { + #[derive(Reflect, FromReflect, Eq, PartialEq, Debug)] + #[reflect(Default)] + struct MyStruct { + foo: String, + #[reflect(ignore)] + bar: usize, + } + + impl Default for MyStruct { + fn default() -> Self { + Self { + foo: String::from("Hello"), + bar: 123, + } + } + } + + let expected = MyStruct { + foo: String::from("Hello"), + bar: 123, + }; + + let dyn_struct = DynamicStruct::default(); + let my_struct = ::from_reflect(&dyn_struct); + + assert_eq!(Some(expected), my_struct); + } + #[test] fn reflect_complex_patch() { #[derive(Reflect, Eq, PartialEq, Debug, FromReflect)]