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] - Add macro to implement reflect for struct types and migrate glam types #4540

Closed
Closed
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
71a770f
Implemented impl_reflect_struct
PROMETHIA-27 Apr 19, 2022
5860496
Testing impl_reflect_struct
PROMETHIA-27 Apr 19, 2022
478d9ce
Rephrased impl_struct for from_reflect
PROMETHIA-27 Apr 19, 2022
650e7a7
Added from_reflect to impl_reflect_struct
PROMETHIA-27 Apr 19, 2022
fd4f58d
Altered impl_struct to accept a custom constructor
PROMETHIA-27 Apr 19, 2022
9a9aaf6
Verified trait reflection
PROMETHIA-27 Apr 19, 2022
401fe37
Switched all (less quats) impls to structs
PROMETHIA-27 Apr 19, 2022
6592abe
Removed messy temp test
PROMETHIA-27 Apr 19, 2022
ac61dd2
Ran cargo fmt
PROMETHIA-27 Apr 19, 2022
44b06ba
Added bevy_reflect path override
PROMETHIA-27 Apr 20, 2022
b584f14
Restored `impl_from_reflect_value` for Quat/DQuat
PROMETHIA-27 Apr 21, 2022
fb4c12e
Added use for impl_from_reflect_value
PROMETHIA-27 Apr 21, 2022
1bdefbf
Ran cargo fmt again
PROMETHIA-27 Apr 21, 2022
c5d8ce1
Added tests for Vec3 serialization behavior
PROMETHIA-27 Apr 23, 2022
ed5372f
Ran cargo fmt --all
PROMETHIA-27 Apr 23, 2022
44f66c9
Split parsing into a separate Parse struct
PROMETHIA-27 Apr 25, 2022
b6c196e
Added tests for Vec3 reflection behavior
PROMETHIA-27 Apr 25, 2022
cbbae2b
Merge branch 'main' into glam-vec_struct_reflect_type
PROMETHIA-27 Apr 25, 2022
1e7cd55
Added docstrings
PROMETHIA-27 Apr 26, 2022
080971d
Fixed parsing issue
PROMETHIA-27 Apr 26, 2022
db1cd60
Updated parsing to use custom_keyword!()
PROMETHIA-27 Apr 26, 2022
b83578a
Ran cargo fmt --all
PROMETHIA-27 Apr 26, 2022
7f46274
Added panics for enum and union cases
PROMETHIA-27 Apr 26, 2022
a42f5f0
Switched to attribute-based overrides
PROMETHIA-27 Apr 26, 2022
3ef21d9
Ran cargo fmt --all
PROMETHIA-27 Apr 26, 2022
b2ae524
Added doc comment link backticks
PROMETHIA-27 Apr 26, 2022
91e8255
Fixed doc links, ignored doc comment examples
PROMETHIA-27 Apr 27, 2022
1f90de7
Changed to syn::Error instead of panics
PROMETHIA-27 Apr 27, 2022
21cc040
Switched from syn::Error::new to new_spanned
PROMETHIA-27 Apr 27, 2022
133d2be
Renamed to impl_reflect_struct
PROMETHIA-27 Apr 28, 2022
abf7ed2
Fix use statement
PROMETHIA-27 Apr 28, 2022
f4c3478
Cargo fmt --all
PROMETHIA-27 Apr 28, 2022
8ef79e1
Add reflect(Default) to glam types
PROMETHIA-27 May 5, 2022
259ca60
Merge branch 'bevyengine:main' into glam-vec_struct_reflect_type
PROMETHIA-27 May 5, 2022
7bbff47
Add `use create::prelude::ReflectDefault`
PROMETHIA-27 May 5, 2022
cdae659
Removed explicit ctor attribute
PROMETHIA-27 May 5, 2022
d6232eb
Fixed clippy warning
PROMETHIA-27 May 5, 2022
1864fb3
Removed instances of `use bevy_reflect::Struct`
PROMETHIA-27 May 5, 2022
35bde41
Removed uses of path special attribute
PROMETHIA-27 May 5, 2022
ca0c3a5
cargo fmt --all
PROMETHIA-27 May 5, 2022
dd4c158
Updated doc comments
PROMETHIA-27 May 6, 2022
ad8637f
Fix doc space
PROMETHIA-27 May 8, 2022
907ea55
Split comment into quick and extended summaries
PROMETHIA-27 May 8, 2022
ae91dc6
Merge branch 'bevyengine:main' into glam-vec_struct_reflect_type
PROMETHIA-27 May 8, 2022
27755a3
Replace outdated explanations of special attrs
PROMETHIA-27 May 8, 2022
8b0a422
Merge branch 'glam-vec_struct_reflect_type' of https://github.com/PRO…
PROMETHIA-27 May 8, 2022
9a73dfe
Explain type must be in scope
PROMETHIA-27 May 8, 2022
68fef52
Explain example
PROMETHIA-27 May 8, 2022
467a39a
Show Vec3 in scope
PROMETHIA-27 May 8, 2022
2f63312
.into -> TokenStream::from
PROMETHIA-27 May 8, 2022
c262d94
Merge branch 'glam-vec_struct_reflect_type' of https://github.com/PRO…
PROMETHIA-27 May 8, 2022
7aa58db
Cargo fmt --all
PROMETHIA-27 May 8, 2022
05f4f0b
ctor -> constructor
PROMETHIA-27 May 9, 2022
7683aa9
ctor -> constructor
PROMETHIA-27 May 9, 2022
58e127c
ctor -> constructor
PROMETHIA-27 May 9, 2022
ca936c8
ctor -> constructor
PROMETHIA-27 May 9, 2022
b7d76fb
last ctor -> constructor batch
PROMETHIA-27 May 9, 2022
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
34 changes: 25 additions & 9 deletions crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub fn impl_struct(
bevy_reflect_path: &Path,
active_fields: &[(&Field, usize)],
ignored_fields: &[(&Field, usize)],
custom_constructor: Option<proc_macro2::TokenStream>,
) -> TokenStream {
let field_names = active_fields
.iter()
Expand Down Expand Up @@ -60,20 +61,35 @@ pub fn impl_struct(
#(#field_types: #bevy_reflect_path::FromReflect,)*
});

let constructor = if let Some(ctor) = custom_constructor {
PROMETHIA-27 marked this conversation as resolved.
Show resolved Hide resolved
quote!(
let mut value: Self = #ctor;
PROMETHIA-27 marked this conversation as resolved.
Show resolved Hide resolved
#(
value.#field_idents = {
<#field_types as #bevy_reflect_path::FromReflect>::from_reflect(#bevy_reflect_path::Struct::field(ref_struct, #field_names)?)?
};
)*
Some(value)
)
} else {
quote!(
Some(
Self {
#(#field_idents: {
<#field_types as #bevy_reflect_path::FromReflect>::from_reflect(#bevy_reflect_path::Struct::field(ref_struct, #field_names)?)?
},)*
#(#ignored_field_idents: Default::default(),)*
}
)
)
};

TokenStream::from(quote! {
impl #impl_generics #bevy_reflect_path::FromReflect for #struct_name #ty_generics #where_from_reflect_clause
{
fn from_reflect(reflect: &dyn #bevy_reflect_path::Reflect) -> Option<Self> {
use #bevy_reflect_path::Struct;
if let #bevy_reflect_path::ReflectRef::Struct(ref_struct) = reflect.reflect_ref() {
Some(
Self{
#(#field_idents: {
<#field_types as #bevy_reflect_path::FromReflect>::from_reflect(ref_struct.field(#field_names)?)?
},)*
#(#ignored_field_idents: Default::default(),)*
}
)
#constructor
} else {
None
}
Expand Down
205 changes: 201 additions & 4 deletions crates/bevy_reflect/bevy_reflect_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,7 @@ fn impl_struct(

#[inline]
fn clone_value(&self) -> Box<dyn #bevy_reflect_path::Reflect> {
use #bevy_reflect_path::Struct;
Box::new(self.clone_dynamic())
Box::new(#bevy_reflect_path::Struct::clone_dynamic(self))
}
#[inline]
fn set(&mut self, value: Box<dyn #bevy_reflect_path::Reflect>) -> Result<(), Box<dyn #bevy_reflect_path::Reflect>> {
Expand All @@ -298,11 +297,10 @@ fn impl_struct(

#[inline]
fn apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) {
use #bevy_reflect_path::Struct;
if let #bevy_reflect_path::ReflectRef::Struct(struct_value) = value.reflect_ref() {
for (i, value) in struct_value.iter_fields().enumerate() {
let name = struct_value.name_at(i).unwrap();
self.field_mut(name).map(|v| v.apply(value));
#bevy_reflect_path::Struct::field_mut(self, name).map(|v| v.apply(value));
}
} else {
panic!("Attempted to apply non-struct type to struct type.");
Expand Down Expand Up @@ -607,6 +605,204 @@ pub fn impl_reflect_value(input: TokenStream) -> TokenStream {
)
}

/// Represents the information needed to implement a type as a Reflect Struct.
///
/// # Example
/// ```ignore
/// impl_reflect_struct!(
/// // attrs
/// // |----------------------------------------|
/// #[reflect(PartialEq, Serialize, Deserialize, Default)]
/// // type_name generics
/// // |-------------------||----------|
/// struct ThingThatImReflecting<T1, T2, T3> {
/// x: T1, // |
/// y: T2, // |- fields
/// z: T3 // |
/// }
/// );
/// ```
struct ReflectStructDef {
type_name: Ident,
generics: Generics,
attrs: ReflectAttrs,
fields: Fields,
}

impl Parse for ReflectStructDef {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ast = input.parse::<DeriveInput>()?;

let type_name = ast.ident;
let generics = ast.generics;
let fields = match ast.data {
Data::Struct(data) => data.fields,
Data::Enum(data) => {
return Err(syn::Error::new_spanned(
data.enum_token,
"Enums are not currently supported for reflection",
))
}
Data::Union(data) => {
return Err(syn::Error::new_spanned(
data.union_token,
"Unions are not supported for reflection",
))
}
};

let mut attrs = ReflectAttrs::default();
for attribute in ast.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) {
let meta_list = if let Meta::List(meta_list) = attribute {
meta_list
} else {
continue;
};

if let Some(ident) = meta_list.path.get_ident() {
if ident == REFLECT_ATTRIBUTE_NAME || ident == REFLECT_VALUE_ATTRIBUTE_NAME {
attrs = ReflectAttrs::from_nested_metas(&meta_list.nested);
}
}
}

Ok(Self {
type_name,
generics,
attrs,
fields,
})
}
}

/// A replacement for `#[derive(Reflect)]` to be used with foreign types which
PROMETHIA-27 marked this conversation as resolved.
Show resolved Hide resolved
/// the definitions of cannot be altered.
///
/// This macro is an alternative to [`impl_reflect_value!`] and [`impl_from_reflect_value!`]
/// which implement foreign types as Value types. Note that there is no `impl_from_reflect_struct`,
/// as this macro will do the job of both. This macro implements them as `Struct` types,
/// which have greater functionality. The type being reflected must be in scope, as you cannot
/// qualify it in the macro as e.g. `bevy::prelude::Vec3`.
///
PROMETHIA-27 marked this conversation as resolved.
Show resolved Hide resolved
/// It may be necessary to add `#[reflect(Default)]` for some types, specifically non-constructible
/// foreign types. Without `Default` reflected for such types, you will usually get an arcane
/// error message and fail to compile. If the type does not implement `Default`, it may not
/// be possible to reflect without extending the macro.
///
/// # Example
/// Implementing `Reflect` for `bevy::prelude::Vec3` as a struct type:
/// ```ignore
/// use bevy::prelude::Vec3;
///
/// impl_reflect_struct!(
/// #[reflect(PartialEq, Serialize, Deserialize, Default)]
/// struct Vec3 {
/// x: f32,
/// y: f32,
/// z: f32
/// }
/// );
/// ```
#[proc_macro]
pub fn impl_reflect_struct(input: TokenStream) -> TokenStream {
let ReflectStructDef {
type_name,
generics,
attrs,
fields,
} = parse_macro_input!(input as ReflectStructDef);

let bevy_reflect_path = BevyManifest::default().get_path("bevy_reflect");

let fields_and_args = fields
.iter()
.enumerate()
.map(|(i, f)| {
(
f,
f.attrs
.iter()
.find(|a| *a.path.get_ident().as_ref().unwrap() == REFLECT_ATTRIBUTE_NAME)
.map(|a| {
syn::custom_keyword!(ignore);
let mut attribute_args = PropAttributeArgs { ignore: None };
a.parse_args_with(|input: ParseStream| {
if input.parse::<Option<ignore>>()?.is_some() {
attribute_args.ignore = Some(true);
return Ok(());
}
Ok(())
})
.expect("Invalid 'property' attribute format.");

attribute_args
}),
i,
)
})
.collect::<Vec<(&Field, Option<PropAttributeArgs>, usize)>>();
let active_fields = fields_and_args
.iter()
.filter(|(_field, attrs, _i)| {
attrs.is_none()
|| match attrs.as_ref().unwrap().ignore {
Some(ignore) => !ignore,
None => true,
}
})
.map(|(f, _attr, i)| (*f, *i))
.collect::<Vec<(&Field, usize)>>();
let ignored_fields = fields_and_args
.iter()
.filter(|(_field, attrs, _i)| {
attrs
.as_ref()
.map(|attrs| attrs.ignore.unwrap_or(false))
.unwrap_or(false)
})
.map(|(f, _attr, i)| (*f, *i))
.collect::<Vec<(&Field, usize)>>();

let ctor = if attrs
PROMETHIA-27 marked this conversation as resolved.
Show resolved Hide resolved
.data
.contains(&Ident::new("ReflectDefault", Span::call_site()))
{
Some(quote! { Default::default() })
} else {
None
};
Copy link
Member

@MrGVSV MrGVSV May 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a note for other reviewers, this whole ctor thing is explored a little bit more in #4140. I think it's fine to use this method in the interim while something like that awaits more community review.


let registration_data = &attrs.data;
let get_type_registration_impl =
impl_get_type_registration(&type_name, &bevy_reflect_path, registration_data, &generics);

let impl_struct: proc_macro2::TokenStream = impl_struct(
&type_name,
&generics,
&get_type_registration_impl,
&bevy_reflect_path,
&attrs,
&active_fields,
)
.into();

let impl_from_struct: proc_macro2::TokenStream = from_reflect::impl_struct(
&type_name,
&generics,
&bevy_reflect_path,
&active_fields,
&ignored_fields,
ctor,
PROMETHIA-27 marked this conversation as resolved.
Show resolved Hide resolved
)
.into();

TokenStream::from(quote! {
#impl_struct

#impl_from_struct
})
}

#[derive(Default)]
struct ReflectAttrs {
reflect_hash: TraitImpl,
Expand Down Expand Up @@ -862,6 +1058,7 @@ pub fn derive_from_reflect(input: TokenStream) -> TokenStream {
&bevy_reflect_path,
&active_fields,
&ignored_fields,
None,
),
DeriveType::TupleStruct => from_reflect::impl_tuple_struct(
type_name,
Expand Down
Loading