Skip to content

Commit

Permalink
bevy_reflect: Add #[reflect(default)] attribute for FromReflect (b…
Browse files Browse the repository at this point in the history
…evyengine#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 bevyengine#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

* bevyengine#3733
* bevyengine#1395
* bevyengine#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>
  • Loading branch information
2 people authored and ItsDoot committed Feb 1, 2023
1 parent d259461 commit 9fa10b5
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
46 changes: 44 additions & 2 deletions crates/bevy_reflect/bevy_reflect_derive/src/field_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]`).
Expand Down Expand Up @@ -44,16 +66,36 @@ pub(crate) fn parse_field_attrs(attrs: &[Attribute]) -> Result<ReflectFieldAttr,
}
}

/// Recursively parses attribute metadata for things like `#[reflect(ignore)]` and `#[reflect(default = "foo")]`
fn parse_meta(args: &mut ReflectFieldAttr, meta: &Meta) -> 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(
Expand Down
63 changes: 49 additions & 14 deletions crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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,)*
}
)
Expand Down Expand Up @@ -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)
Expand All @@ -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<T>` where `T` is that field's type.
fn get_active_fields(
derive_data: &ReflectDeriveData,
dyn_struct_name: &Ident,
Expand All @@ -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)
})
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_reflect/bevy_reflect_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
61 changes: 61 additions & 0 deletions crates/bevy_reflect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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 = <MyStruct as FromReflect>::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 = <MyStruct as FromReflect>::from_reflect(&dyn_struct);

assert_eq!(Some(expected), my_struct);
}

#[test]
fn reflect_complex_patch() {
#[derive(Reflect, Eq, PartialEq, Debug, FromReflect)]
Expand Down

0 comments on commit 9fa10b5

Please sign in to comment.