Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - bevy_reflect: Add #[reflect(default)] attribute for FromReflect #4140

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,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 @@ -93,6 +93,7 @@ mod tests {
Deserializer,
};

use super::prelude::*;
use super::*;
use crate as bevy_reflect;
use crate::serde::{ReflectDeserializer, ReflectSerializer};
Expand Down Expand Up @@ -231,6 +232,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