bevy_reflect_derive: Tidying up the code (#4712)

# Objective

The `bevy_reflect_derive` crate is not the cleanest or easiest to follow/maintain. The `lib.rs` file is especially difficult with over 1000 lines of code written in a confusing order. This is just a result of growth within the crate and it would be nice to clean it up for future work.

## Solution

Split `bevy_reflect_derive` into many more submodules. The submodules include:

* `container_attributes` - Code relating to container attributes
* `derive_data` - Code relating to reflection-based derive metadata
* `field_attributes` - Code relating to field attributes
* `impls` - Code containing actual reflection implementations
* `reflect_value` - Code relating to reflection-based value metadata
* `registration` - Code relating to type registration
* `utility` - General-purpose utility functions

This leaves the `lib.rs` file to contain only the public macros, making it much easier to digest (and fewer than 200 lines).

By breaking up the code into smaller modules, we make it easier for future contributors to find the code they're looking for or identify which module best fits their own additions.

### Metadata Structs

This cleanup also adds two big metadata structs: `ReflectFieldAttr` and `ReflectDeriveData`. The former is used to store all attributes for a struct field (if any). The latter is used to store all metadata for struct-based derive inputs.

Both significantly reduce code duplication and make editing these macros much simpler. The tradeoff is that we may collect more metadata than needed. However, this is usually a small thing (such as checking for attributes when they're not really needed or creating a `ReflectFieldAttr` for every field regardless of whether they actually have an attribute).

We could try to remove these tradeoffs and squeeze some more performance out, but doing so might come at the cost of developer experience. Personally, I think it's much nicer to create a `ReflectFieldAttr` for every field since it means I don't have to do two `Option` checks. Others may disagree, though, and so we can discuss changing this either in this PR or in a future one.

### Out of Scope

_Some_ documentation has been added or improved, but ultimately good docs are probably best saved for a dedicated PR.

## 🔍 Focus Points (for reviewers)

I know it's a lot to sift through, so here is a list of **key points for reviewers**:

- The following files contain code that was mostly just relocated:
  - `reflect_value.rs`
  - `registration.rs`
- `container_attributes.rs` was also mostly moved but features some general cleanup (reducing nesting, removing hardcoded strings, etc.) and lots of doc comments
- Most impl logic was moved from `lib.rs` to `impls.rs`, but they have been significantly modified to use the new `ReflectDeriveData` metadata struct in order to reduce duplication.
- `derive_data.rs` and `field_attributes.rs` contain almost entirely new code and should probably be given the most attention.
- Likewise, `from_reflect.rs` saw major changes using `ReflectDeriveData` so it should also be given focus.
- There was no change to the `lib.rs` exports so the end-user API should be the same.

## Prior Work

This task was initially tackled by @NathanSWard in #2377 (which was closed in favor of this PR), so hats off to them for beating me to the punch by nearly a year!

---

## Changelog

* **[INTERNAL]** Split `bevy_reflect_derive` into smaller submodules
* **[INTERNAL]** Add `ReflectFieldAttr`
* **[INTERNAL]** Add `ReflectDeriveData`
* Add `BevyManifest::get_path_direct()` method (`bevy_macro_utils`)


Co-authored-by: MrGVSV <49806985+MrGVSV@users.noreply.github.com>
This commit is contained in:
MrGVSV 2022-05-12 19:43:23 +00:00
parent d3b2439c82
commit 3d8d922566
13 changed files with 1293 additions and 1154 deletions

View file

@ -78,6 +78,22 @@ impl BevyManifest {
deps.and_then(find_in_deps)
.or_else(|| deps_dev.and_then(find_in_deps))
}
/// Returns the path for the crate with the given name.
///
/// This is a convenience method for constructing a [manifest] and
/// calling the [`get_path`] method.
///
/// This method should only be used where you just need the path and can't
/// cache the [manifest]. If caching is possible, it's recommended to create
/// the [manifest] yourself and use the [`get_path`] method.
///
/// [`get_path`]: Self::get_path
/// [manifest]: Self
pub fn get_path_direct(name: &str) -> syn::Path {
Self::default().get_path(name)
}
pub fn get_path(&self, name: &str) -> syn::Path {
self.maybe_get_path(name)
.unwrap_or_else(|| Self::parse_str(name))

View file

@ -0,0 +1,234 @@
//! Contains code related to container attributes for reflected types.
//!
//! A container attribute is an attribute which applies to an entire struct or enum
//! as opposed to a particular field or variant. An example of such an attribute is
//! the derive helper attribute for `Reflect`, which looks like:
//! `#[reflect(PartialEq, Default, ...)]` and `#[reflect_value(PartialEq, Default, ...)]`.
use crate::utility;
use proc_macro2::Ident;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{Meta, NestedMeta, Path};
// The "special" trait idents that are used internally for reflection.
// Received via attributes like `#[reflect(PartialEq, Hash, ...)]`
const PARTIAL_EQ_ATTR: &str = "PartialEq";
const HASH_ATTR: &str = "Hash";
const SERIALIZE_ATTR: &str = "Serialize";
/// A marker for trait implementations registered via the `Reflect` derive macro.
#[derive(Clone)]
pub(crate) enum TraitImpl {
/// The trait is not registered as implemented.
NotImplemented,
/// The trait is registered as implemented.
Implemented,
// TODO: This can be made to use `ExprPath` instead of `Ident`, allowing for fully qualified paths to be used
/// The trait is registered with a custom function rather than an actual implementation.
Custom(Ident),
}
impl Default for TraitImpl {
fn default() -> Self {
Self::NotImplemented
}
}
/// A collection of traits that have been registered for a reflected type.
///
/// This keeps track of a few traits that are utilized internally for reflection
/// (we'll call these traits _special traits_ within this context), but it
/// will also keep track of all registered traits. Traits are registered as part of the
/// `Reflect` derive macro using the helper attribute: `#[reflect(...)]`.
///
/// The list of special traits are as follows:
/// * `Hash`
/// * `PartialEq`
/// * `Serialize`
///
/// When registering a trait, there are a few things to keep in mind:
/// * Traits must have a valid `Reflect{}` struct in scope. For example, `Default`
/// needs `bevy_reflect::prelude::ReflectDefault` in scope.
/// * Traits must be single path identifiers. This means you _must_ use `Default`
/// instead of `std::default::Default` (otherwise it will try to register `Reflectstd`!)
/// * A custom function may be supplied in place of an actual implementation
/// for the special traits (but still follows the same single-path identifier
/// rules as normal).
///
/// # Example
///
/// Registering the `Default` implementation:
///
/// ```ignore
/// // Import ReflectDefault so it's accessible by the derive macro
/// use bevy_reflect::prelude::ReflectDefault.
///
/// #[derive(Reflect, Default)]
/// #[reflect(Default)]
/// struct Foo;
/// ```
///
/// Registering the `Hash` implementation:
///
/// ```ignore
/// // `Hash` is a "special trait" and does not need (nor have) a ReflectHash struct
///
/// #[derive(Reflect, Hash)]
/// #[reflect(Hash)]
/// struct Foo;
/// ```
///
/// Registering the `Hash` implementation using a custom function:
///
/// ```ignore
/// // This function acts as our `Hash` implementation and
/// // corresponds to the `Reflect::reflect_hash` method.
/// fn get_hash(foo: &Foo) -> Option<u64> {
/// Some(123)
/// }
///
/// #[derive(Reflect)]
/// // Register the custom `Hash` function
/// #[reflect(Hash(get_hash))]
/// struct Foo;
/// ```
///
/// > __Note:__ Registering a custom function only works for special traits.
///
#[derive(Default)]
pub(crate) struct ReflectTraits {
hash: TraitImpl,
partial_eq: TraitImpl,
serialize: TraitImpl,
idents: Vec<Ident>,
}
impl ReflectTraits {
/// Create a new [`ReflectTraits`] instance from a set of nested metas.
pub fn from_nested_metas(nested_metas: &Punctuated<NestedMeta, Comma>) -> Self {
let mut traits = ReflectTraits::default();
for nested_meta in nested_metas.iter() {
match nested_meta {
// Handles `#[reflect( Hash, Default, ... )]`
NestedMeta::Meta(Meta::Path(path)) => {
// Get the first ident in the path (hopefully the path only contains one and not `std::hash::Hash`)
let ident = if let Some(segment) = path.segments.iter().next() {
segment.ident.to_string()
} else {
continue;
};
match ident.as_str() {
PARTIAL_EQ_ATTR => traits.partial_eq = TraitImpl::Implemented,
HASH_ATTR => traits.hash = TraitImpl::Implemented,
SERIALIZE_ATTR => traits.serialize = TraitImpl::Implemented,
// We only track reflected idents for traits not considered special
_ => traits.idents.push(utility::get_reflect_ident(&ident)),
}
}
// Handles `#[reflect( Hash(custom_hash_fn) )]`
NestedMeta::Meta(Meta::List(list)) => {
// Get the first ident in the path (hopefully the path only contains one and not `std::hash::Hash`)
let ident = if let Some(segment) = list.path.segments.iter().next() {
segment.ident.to_string()
} else {
continue;
};
let list_meta = list.nested.iter().next();
if let Some(NestedMeta::Meta(Meta::Path(path))) = list_meta {
if let Some(segment) = path.segments.iter().next() {
// This should be the ident of the custom function
let trait_func_ident = TraitImpl::Custom(segment.ident.clone());
match ident.as_str() {
PARTIAL_EQ_ATTR => traits.partial_eq = trait_func_ident,
HASH_ATTR => traits.hash = trait_func_ident,
SERIALIZE_ATTR => traits.serialize = trait_func_ident,
_ => {}
}
}
}
}
_ => {}
}
}
traits
}
/// Returns true if the given reflected trait name (i.e. `ReflectDefault` for `Default`)
/// is registered for this type.
pub fn contains(&self, name: &str) -> bool {
self.idents.iter().any(|ident| ident == name)
}
/// The list of reflected traits by their reflected ident (i.e. `ReflectDefault` for `Default`).
pub fn idents(&self) -> &[Ident] {
&self.idents
}
/// Returns the logic for `Reflect::reflect_hash` as a `TokenStream`.
///
/// If `Hash` was not registered, returns `None`.
pub fn get_hash_impl(&self, path: &Path) -> Option<proc_macro2::TokenStream> {
match &self.hash {
TraitImpl::Implemented => Some(quote! {
use std::hash::{Hash, Hasher};
let mut hasher = #path::ReflectHasher::default();
Hash::hash(&std::any::Any::type_id(self), &mut hasher);
Hash::hash(self, &mut hasher);
Some(hasher.finish())
}),
TraitImpl::Custom(impl_fn) => Some(quote! {
Some(#impl_fn(self))
}),
TraitImpl::NotImplemented => None,
}
}
/// Returns the logic for `Reflect::reflect_partial_eq` as a `TokenStream`.
///
/// If `PartialEq` was not registered, returns `None`.
pub fn get_partial_eq_impl(&self) -> Option<proc_macro2::TokenStream> {
match &self.partial_eq {
TraitImpl::Implemented => Some(quote! {
let value = value.any();
if let Some(value) = value.downcast_ref::<Self>() {
Some(std::cmp::PartialEq::eq(self, value))
} else {
Some(false)
}
}),
TraitImpl::Custom(impl_fn) => Some(quote! {
Some(#impl_fn(self, value))
}),
TraitImpl::NotImplemented => None,
}
}
/// Returns the logic for `Reflect::serializable` as a `TokenStream`.
///
/// If `Serialize` was not registered, returns `None`.
pub fn get_serialize_impl(&self, path: &Path) -> Option<proc_macro2::TokenStream> {
match &self.serialize {
TraitImpl::Implemented => Some(quote! {
Some(#path::serde::Serializable::Borrowed(self))
}),
TraitImpl::Custom(impl_fn) => Some(quote! {
Some(#impl_fn(self))
}),
TraitImpl::NotImplemented => None,
}
}
}
impl Parse for ReflectTraits {
fn parse(input: ParseStream) -> syn::Result<Self> {
let result = Punctuated::<NestedMeta, Comma>::parse_terminated(input)?;
Ok(ReflectTraits::from_nested_metas(&result))
}
}

View file

@ -0,0 +1,197 @@
use crate::container_attributes::ReflectTraits;
use crate::field_attributes::{parse_field_attrs, ReflectFieldAttr};
use crate::utility::get_bevy_reflect_path;
use crate::{REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME};
use syn::{Data, DataStruct, DeriveInput, Field, Fields, Generics, Ident, Meta, Path};
pub(crate) enum DeriveType {
Struct,
TupleStruct,
UnitStruct,
Value,
}
/// Represents a field on a struct or tuple struct.
pub(crate) struct StructField<'a> {
/// The raw field.
pub data: &'a Field,
/// The reflection-based attributes on the field.
pub attrs: ReflectFieldAttr,
/// The index of this field within the struct.
pub index: usize,
}
/// Data used by derive macros for `Reflect` and `FromReflect`
///
/// # Example
/// ```ignore
/// // attrs
/// // |----------------------------------------|
/// #[reflect(PartialEq, Serialize, Deserialize, Default)]
/// // type_name generics
/// // |-------------------||----------|
/// struct ThingThatImReflecting<T1, T2, T3> {
/// x: T1, // |
/// y: T2, // |- fields
/// z: T3 // |
/// }
/// ```
pub(crate) struct ReflectDeriveData<'a> {
derive_type: DeriveType,
traits: ReflectTraits,
type_name: &'a Ident,
generics: &'a Generics,
fields: Vec<StructField<'a>>,
bevy_reflect_path: Path,
}
impl<'a> ReflectDeriveData<'a> {
pub fn from_input(input: &'a DeriveInput) -> Result<Self, syn::Error> {
let mut output = Self {
type_name: &input.ident,
derive_type: DeriveType::Value,
generics: &input.generics,
fields: Vec::new(),
traits: ReflectTraits::default(),
bevy_reflect_path: get_bevy_reflect_path(),
};
// Should indicate whether `#[reflect_value]` was used
let mut force_reflect_value = false;
for attribute in input.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 {
output.traits = ReflectTraits::from_nested_metas(&meta_list.nested);
} else if ident == REFLECT_VALUE_ATTRIBUTE_NAME {
force_reflect_value = true;
output.traits = ReflectTraits::from_nested_metas(&meta_list.nested);
}
}
}
let fields = match &input.data {
Data::Struct(DataStruct {
fields: Fields::Named(fields),
..
}) => {
if !force_reflect_value {
output.derive_type = DeriveType::Struct;
}
&fields.named
}
Data::Struct(DataStruct {
fields: Fields::Unnamed(fields),
..
}) => {
if !force_reflect_value {
output.derive_type = DeriveType::TupleStruct;
}
&fields.unnamed
}
Data::Struct(DataStruct {
fields: Fields::Unit,
..
}) => {
if !force_reflect_value {
output.derive_type = DeriveType::UnitStruct;
}
return Ok(output);
}
_ => {
return Ok(output);
}
};
let mut errors: Option<syn::Error> = None;
output.fields = fields
.iter()
.enumerate()
.map(|(index, field)| {
let attrs = parse_field_attrs(&field.attrs).unwrap_or_else(|err| {
if let Some(ref mut errors) = errors {
errors.combine(err);
} else {
errors = Some(err);
}
ReflectFieldAttr::default()
});
StructField {
index,
attrs,
data: field,
}
})
.collect::<Vec<StructField>>();
if let Some(errs) = errors {
return Err(errs);
}
Ok(output)
}
/// Get an iterator over the active fields
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.fields.iter().filter(|field| !field.attrs.ignore)
}
/// Get an iterator over the ignored fields
pub fn ignored_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.fields.iter().filter(|field| field.attrs.ignore)
}
/// Get a collection of all active types
pub fn active_types(&self) -> Vec<syn::Type> {
self.active_fields()
.map(|field| field.data.ty.clone())
.collect::<Vec<_>>()
}
/// The [`DeriveType`] of this struct.
pub fn derive_type(&self) -> &DeriveType {
&self.derive_type
}
/// The registered reflect traits on this struct.
pub fn traits(&self) -> &ReflectTraits {
&self.traits
}
/// The name of this struct.
pub fn type_name(&self) -> &'a Ident {
self.type_name
}
/// The generics associated with this struct.
pub fn generics(&self) -> &'a Generics {
self.generics
}
/// The complete set of fields in this struct.
#[allow(dead_code)]
pub fn fields(&self) -> &[StructField<'a>] {
&self.fields
}
/// The cached `bevy_reflect` path.
pub fn bevy_reflect_path(&self) -> &Path {
&self.bevy_reflect_path
}
/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
pub fn get_type_registration(&self) -> proc_macro2::TokenStream {
crate::registration::impl_get_type_registration(
self.type_name,
&self.bevy_reflect_path,
self.traits.idents(),
self.generics,
)
}
}

View file

@ -0,0 +1,76 @@
//! Contains code related to field attributes for reflected types.
//!
//! A field attribute is an attribute which applies to particular field or variant
//! as opposed to an entire struct or enum. An example of such an attribute is
//! the derive helper attribute for `Reflect`, which looks like: `#[reflect(ignore)]`.
use crate::REFLECT_ATTRIBUTE_NAME;
use quote::ToTokens;
use syn::spanned::Spanned;
use syn::{Attribute, Meta, NestedMeta};
pub(crate) static IGNORE_ATTR: &str = "ignore";
/// A container for attributes defined on a field reflected type's field.
#[derive(Default)]
pub(crate) struct ReflectFieldAttr {
/// Determines if this field should be ignored.
pub ignore: bool,
}
/// Parse all field attributes marked "reflect" (such as `#[reflect(ignore)]`).
pub(crate) fn parse_field_attrs(attrs: &[Attribute]) -> Result<ReflectFieldAttr, syn::Error> {
let mut args = ReflectFieldAttr::default();
let mut errors: Option<syn::Error> = None;
let attrs = attrs
.iter()
.filter(|a| a.path.is_ident(REFLECT_ATTRIBUTE_NAME));
for attr in attrs {
let meta = attr.parse_meta()?;
if let Err(err) = parse_meta(&mut args, &meta) {
if let Some(ref mut error) = errors {
error.combine(err);
} else {
errors = Some(err);
}
}
}
if let Some(error) = errors {
Err(error)
} else {
Ok(args)
}
}
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) => Err(syn::Error::new(
path.span(),
format!("unknown attribute parameter: {}", path.to_token_stream()),
)),
Meta::NameValue(pair) => {
let path = &pair.path;
Err(syn::Error::new(
path.span(),
format!("unknown attribute parameter: {}", path.to_token_stream()),
))
}
Meta::List(list) if !list.path.is_ident(REFLECT_ATTRIBUTE_NAME) => {
Err(syn::Error::new(list.path.span(), "unexpected property"))
}
Meta::List(list) => {
for nested in list.nested.iter() {
if let NestedMeta::Meta(meta) = nested {
parse_meta(args, meta)?;
}
}
Ok(())
}
}
}

View file

@ -1,161 +1,25 @@
use crate::ReflectDeriveData;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{Field, Generics, Ident, Index, Member, Path};
use syn::{Field, Generics, Ident, Index, Lit, LitInt, LitStr, Member, Path};
pub fn impl_struct(
struct_name: &Ident,
generics: &Generics,
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()
.map(|(field, index)| {
field
.ident
.as_ref()
.map(|i| i.to_string())
.unwrap_or_else(|| index.to_string())
})
.collect::<Vec<String>>();
let field_idents = active_fields
.iter()
.map(|(field, index)| {
field
.ident
.as_ref()
.map(|ident| Member::Named(ident.clone()))
.unwrap_or_else(|| Member::Unnamed(Index::from(*index)))
})
.collect::<Vec<_>>();
let field_types = active_fields
.iter()
.map(|(field, _index)| field.ty.clone())
.collect::<Vec<_>>();
let field_count = active_fields.len();
let ignored_field_idents = ignored_fields
.iter()
.map(|(field, index)| {
field
.ident
.as_ref()
.map(|ident| Member::Named(ident.clone()))
.unwrap_or_else(|| Member::Unnamed(Index::from(*index)))
})
.collect::<Vec<_>>();
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
// Add FromReflect bound for each active field
let mut where_from_reflect_clause = if where_clause.is_some() {
quote! {#where_clause}
} else if field_count > 0 {
quote! {where}
} else {
quote! {}
};
where_from_reflect_clause.extend(quote! {
#(#field_types: #bevy_reflect_path::FromReflect,)*
});
let constructor = if let Some(constructor) = custom_constructor {
quote!(
let mut value: Self = #constructor;
#(
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> {
if let #bevy_reflect_path::ReflectRef::Struct(ref_struct) = reflect.reflect_ref() {
#constructor
} else {
None
}
}
}
})
/// Implements `FromReflect` for the given struct
pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream {
impl_struct_internal(derive_data, false)
}
pub fn impl_tuple_struct(
struct_name: &Ident,
generics: &Generics,
bevy_reflect_path: &Path,
active_fields: &[(&Field, usize)],
ignored_fields: &[(&Field, usize)],
) -> TokenStream {
let field_idents = active_fields
.iter()
.map(|(_field, index)| Member::Unnamed(Index::from(*index)))
.collect::<Vec<_>>();
let field_types = active_fields
.iter()
.map(|(field, _index)| field.ty.clone())
.collect::<Vec<_>>();
let field_count = active_fields.len();
let field_indices = (0..field_count).collect::<Vec<usize>>();
let ignored_field_idents = ignored_fields
.iter()
.map(|(_field, index)| Member::Unnamed(Index::from(*index)))
.collect::<Vec<_>>();
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
// Add FromReflect bound for each active field
let mut where_from_reflect_clause = if where_clause.is_some() {
quote! {#where_clause}
} else if field_count > 0 {
quote! {where}
} else {
quote! {}
};
where_from_reflect_clause.extend(quote! {
#(#field_types: #bevy_reflect_path::FromReflect,)*
});
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::TupleStruct;
if let #bevy_reflect_path::ReflectRef::TupleStruct(ref_tuple_struct) = reflect.reflect_ref() {
Some(
Self{
#(#field_idents:
<#field_types as #bevy_reflect_path::FromReflect>::from_reflect(ref_tuple_struct.field(#field_indices)?)?
,)*
#(#ignored_field_idents: Default::default(),)*
}
)
} else {
None
}
}
}
})
/// Implements `FromReflect` for the given tuple struct
pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream {
impl_struct_internal(derive_data, true)
}
pub fn impl_value(type_name: &Ident, generics: &Generics, bevy_reflect_path: &Path) -> TokenStream {
/// Implements `FromReflect` for the given value type
pub(crate) fn impl_value(
type_name: &Ident,
generics: &Generics,
bevy_reflect_path: &Path,
) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #bevy_reflect_path::FromReflect for #type_name #ty_generics #where_clause {
@ -165,3 +29,154 @@ pub fn impl_value(type_name: &Ident, generics: &Generics, bevy_reflect_path: &Pa
}
})
}
/// Container for a struct's members (field name or index) and their
/// corresponding values.
struct MemberValuePair(Vec<Member>, Vec<proc_macro2::TokenStream>);
impl MemberValuePair {
pub fn new(items: (Vec<Member>, Vec<proc_macro2::TokenStream>)) -> Self {
Self(items.0, items.1)
}
}
fn impl_struct_internal(derive_data: &ReflectDeriveData, is_tuple: bool) -> TokenStream {
let struct_name = derive_data.type_name();
let generics = derive_data.generics();
let bevy_reflect_path = derive_data.bevy_reflect_path();
let ref_struct = Ident::new("__ref_struct", Span::call_site());
let ref_struct_type = if is_tuple {
Ident::new("TupleStruct", Span::call_site())
} else {
Ident::new("Struct", Span::call_site())
};
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") {
quote!(
let mut __this = Self::default();
#(
__this.#active_members = #active_values;
)*
Some(__this)
)
} else {
quote!(
Some(
Self {
#(#active_members: #active_values,)*
#(#ignored_members: #ignored_values,)*
}
)
)
};
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
// Add FromReflect bound for each active field
let mut where_from_reflect_clause = if where_clause.is_some() {
quote! {#where_clause}
} else if !active_members.is_empty() {
quote! {where}
} else {
quote! {}
};
where_from_reflect_clause.extend(quote! {
#(#field_types: #bevy_reflect_path::FromReflect,)*
});
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> {
if let #bevy_reflect_path::ReflectRef::#ref_struct_type(#ref_struct) = reflect.reflect_ref() {
#constructor
} else {
None
}
}
}
})
}
/// Get the collection of ignored field definitions
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()
};
(member, value)
})
.unzip(),
)
}
/// Get the collection of active field definitions
fn get_active_fields(
derive_data: &ReflectDeriveData,
dyn_struct_name: &Ident,
struct_type: &Ident,
is_tuple: bool,
) -> MemberValuePair {
let bevy_reflect_path = derive_data.bevy_reflect_path();
MemberValuePair::new(
derive_data
.active_fields()
.map(|field| {
let member = get_ident(field.data, field.index, is_tuple);
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)?
)?
}};
(member, value)
})
.unzip(),
)
}
/// Returns the member for a given field of a struct or tuple struct.
fn get_ident(field: &Field, index: usize, is_tuple: bool) -> Member {
if is_tuple {
Member::Unnamed(Index::from(index))
} else {
field
.ident
.as_ref()
.map(|ident| Member::Named(ident.clone()))
.unwrap_or_else(|| Member::Unnamed(Index::from(index)))
}
}
/// Returns the accessor for a given field of a struct or tuple struct.
///
/// This differs from a member in that it needs to be a number for tuple structs
/// and a string for standard structs.
fn get_field_accessor(field: &Field, index: usize, is_tuple: bool) -> Lit {
if is_tuple {
Lit::Int(LitInt::new(&index.to_string(), Span::call_site()))
} else {
field
.ident
.as_ref()
.map(|ident| Lit::Str(LitStr::new(&ident.to_string(), Span::call_site())))
.unwrap_or_else(|| Lit::Str(LitStr::new(&index.to_string(), Span::call_site())))
}
}

View file

@ -0,0 +1,410 @@
use crate::container_attributes::ReflectTraits;
use crate::ReflectDeriveData;
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use syn::{Generics, Index, Member, Path};
/// Implements `Struct`, `GetTypeRegistration`, and `Reflect` for the given derive data.
pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream {
let bevy_reflect_path = derive_data.bevy_reflect_path();
let struct_name = derive_data.type_name();
let field_names = derive_data
.active_fields()
.map(|field| {
field
.data
.ident
.as_ref()
.map(|i| i.to_string())
.unwrap_or_else(|| field.index.to_string())
})
.collect::<Vec<String>>();
let field_idents = derive_data
.active_fields()
.map(|field| {
field
.data
.ident
.as_ref()
.map(|ident| Member::Named(ident.clone()))
.unwrap_or_else(|| Member::Unnamed(Index::from(field.index)))
})
.collect::<Vec<_>>();
let field_count = field_idents.len();
let field_indices = (0..field_count).collect::<Vec<usize>>();
let hash_fn = derive_data
.traits()
.get_hash_impl(bevy_reflect_path)
.unwrap_or_else(|| quote!(None));
let serialize_fn = derive_data
.traits()
.get_serialize_impl(bevy_reflect_path)
.unwrap_or_else(|| quote!(None));
let partial_eq_fn = derive_data
.traits()
.get_partial_eq_impl()
.unwrap_or_else(|| {
quote! {
#bevy_reflect_path::struct_partial_eq(self, value)
}
});
let get_type_registration_impl = derive_data.get_type_registration();
let (impl_generics, ty_generics, where_clause) = derive_data.generics().split_for_impl();
TokenStream::from(quote! {
#get_type_registration_impl
impl #impl_generics #bevy_reflect_path::Struct for #struct_name #ty_generics #where_clause {
fn field(&self, name: &str) -> Option<&dyn #bevy_reflect_path::Reflect> {
match name {
#(#field_names => Some(&self.#field_idents),)*
_ => None,
}
}
fn field_mut(&mut self, name: &str) -> Option<&mut dyn #bevy_reflect_path::Reflect> {
match name {
#(#field_names => Some(&mut self.#field_idents),)*
_ => None,
}
}
fn field_at(&self, index: usize) -> Option<&dyn #bevy_reflect_path::Reflect> {
match index {
#(#field_indices => Some(&self.#field_idents),)*
_ => None,
}
}
fn field_at_mut(&mut self, index: usize) -> Option<&mut dyn #bevy_reflect_path::Reflect> {
match index {
#(#field_indices => Some(&mut self.#field_idents),)*
_ => None,
}
}
fn name_at(&self, index: usize) -> Option<&str> {
match index {
#(#field_indices => Some(#field_names),)*
_ => None,
}
}
fn field_len(&self) -> usize {
#field_count
}
fn iter_fields(&self) -> #bevy_reflect_path::FieldIter {
#bevy_reflect_path::FieldIter::new(self)
}
fn clone_dynamic(&self) -> #bevy_reflect_path::DynamicStruct {
let mut dynamic = #bevy_reflect_path::DynamicStruct::default();
dynamic.set_name(self.type_name().to_string());
#(dynamic.insert_boxed(#field_names, self.#field_idents.clone_value());)*
dynamic
}
}
// SAFE: any and any_mut both return self
unsafe impl #impl_generics #bevy_reflect_path::Reflect for #struct_name #ty_generics #where_clause {
#[inline]
fn type_name(&self) -> &str {
std::any::type_name::<Self>()
}
#[inline]
fn any(&self) -> &dyn std::any::Any {
self
}
#[inline]
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
#[inline]
fn as_reflect(&self) -> &dyn #bevy_reflect_path::Reflect {
self
}
#[inline]
fn as_reflect_mut(&mut self) -> &mut dyn #bevy_reflect_path::Reflect {
self
}
#[inline]
fn clone_value(&self) -> Box<dyn #bevy_reflect_path::Reflect> {
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>> {
*self = value.take()?;
Ok(())
}
#[inline]
fn apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) {
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();
#bevy_reflect_path::Struct::field_mut(self, name).map(|v| v.apply(value));
}
} else {
panic!("Attempted to apply non-struct type to struct type.");
}
}
fn reflect_ref(&self) -> #bevy_reflect_path::ReflectRef {
#bevy_reflect_path::ReflectRef::Struct(self)
}
fn reflect_mut(&mut self) -> #bevy_reflect_path::ReflectMut {
#bevy_reflect_path::ReflectMut::Struct(self)
}
fn serializable(&self) -> Option<#bevy_reflect_path::serde::Serializable> {
#serialize_fn
}
fn reflect_hash(&self) -> Option<u64> {
#hash_fn
}
fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> Option<bool> {
#partial_eq_fn
}
}
})
}
/// Implements `TupleStruct`, `GetTypeRegistration`, and `Reflect` for the given derive data.
pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream {
let bevy_reflect_path = derive_data.bevy_reflect_path();
let struct_name = derive_data.type_name();
let get_type_registration_impl = derive_data.get_type_registration();
let field_idents = derive_data
.active_fields()
.map(|field| Member::Unnamed(Index::from(field.index)))
.collect::<Vec<_>>();
let field_count = field_idents.len();
let field_indices = (0..field_count).collect::<Vec<usize>>();
let hash_fn = derive_data
.traits()
.get_hash_impl(bevy_reflect_path)
.unwrap_or_else(|| quote!(None));
let serialize_fn = derive_data
.traits()
.get_serialize_impl(bevy_reflect_path)
.unwrap_or_else(|| quote!(None));
let partial_eq_fn = derive_data
.traits()
.get_partial_eq_impl()
.unwrap_or_else(|| {
quote! {
#bevy_reflect_path::tuple_struct_partial_eq(self, value)
}
});
let (impl_generics, ty_generics, where_clause) = derive_data.generics().split_for_impl();
TokenStream::from(quote! {
#get_type_registration_impl
impl #impl_generics #bevy_reflect_path::TupleStruct for #struct_name #ty_generics #where_clause {
fn field(&self, index: usize) -> Option<&dyn #bevy_reflect_path::Reflect> {
match index {
#(#field_indices => Some(&self.#field_idents),)*
_ => None,
}
}
fn field_mut(&mut self, index: usize) -> Option<&mut dyn #bevy_reflect_path::Reflect> {
match index {
#(#field_indices => Some(&mut self.#field_idents),)*
_ => None,
}
}
fn field_len(&self) -> usize {
#field_count
}
fn iter_fields(&self) -> #bevy_reflect_path::TupleStructFieldIter {
#bevy_reflect_path::TupleStructFieldIter::new(self)
}
fn clone_dynamic(&self) -> #bevy_reflect_path::DynamicTupleStruct {
let mut dynamic = #bevy_reflect_path::DynamicTupleStruct::default();
dynamic.set_name(self.type_name().to_string());
#(dynamic.insert_boxed(self.#field_idents.clone_value());)*
dynamic
}
}
// SAFE: any and any_mut both return self
unsafe impl #impl_generics #bevy_reflect_path::Reflect for #struct_name #ty_generics #where_clause {
#[inline]
fn type_name(&self) -> &str {
std::any::type_name::<Self>()
}
#[inline]
fn any(&self) -> &dyn std::any::Any {
self
}
#[inline]
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
#[inline]
fn as_reflect(&self) -> &dyn #bevy_reflect_path::Reflect {
self
}
#[inline]
fn as_reflect_mut(&mut self) -> &mut dyn #bevy_reflect_path::Reflect {
self
}
#[inline]
fn clone_value(&self) -> Box<dyn #bevy_reflect_path::Reflect> {
Box::new(#bevy_reflect_path::TupleStruct::clone_dynamic(self))
}
#[inline]
fn set(&mut self, value: Box<dyn #bevy_reflect_path::Reflect>) -> Result<(), Box<dyn #bevy_reflect_path::Reflect>> {
*self = value.take()?;
Ok(())
}
#[inline]
fn apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) {
if let #bevy_reflect_path::ReflectRef::TupleStruct(struct_value) = value.reflect_ref() {
for (i, value) in struct_value.iter_fields().enumerate() {
#bevy_reflect_path::TupleStruct::field_mut(self, i).map(|v| v.apply(value));
}
} else {
panic!("Attempted to apply non-TupleStruct type to TupleStruct type.");
}
}
fn reflect_ref(&self) -> #bevy_reflect_path::ReflectRef {
#bevy_reflect_path::ReflectRef::TupleStruct(self)
}
fn reflect_mut(&mut self) -> #bevy_reflect_path::ReflectMut {
#bevy_reflect_path::ReflectMut::TupleStruct(self)
}
fn serializable(&self) -> Option<#bevy_reflect_path::serde::Serializable> {
#serialize_fn
}
fn reflect_hash(&self) -> Option<u64> {
#hash_fn
}
fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> Option<bool> {
#partial_eq_fn
}
}
})
}
/// Implements `GetTypeRegistration` and `Reflect` for the given type data.
pub(crate) fn impl_value(
type_name: &Ident,
generics: &Generics,
get_type_registration_impl: proc_macro2::TokenStream,
bevy_reflect_path: &Path,
reflect_attrs: &ReflectTraits,
) -> TokenStream {
let hash_fn = reflect_attrs
.get_hash_impl(bevy_reflect_path)
.unwrap_or_else(|| quote!(None));
let partial_eq_fn = reflect_attrs
.get_partial_eq_impl()
.unwrap_or_else(|| quote!(None));
let serialize_fn = reflect_attrs
.get_serialize_impl(bevy_reflect_path)
.unwrap_or_else(|| quote!(None));
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
TokenStream::from(quote! {
#get_type_registration_impl
// SAFE: any and any_mut both return self
unsafe impl #impl_generics #bevy_reflect_path::Reflect for #type_name #ty_generics #where_clause {
#[inline]
fn type_name(&self) -> &str {
std::any::type_name::<Self>()
}
#[inline]
fn any(&self) -> &dyn std::any::Any {
self
}
#[inline]
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
#[inline]
fn as_reflect(&self) -> &dyn #bevy_reflect_path::Reflect {
self
}
#[inline]
fn as_reflect_mut(&mut self) -> &mut dyn #bevy_reflect_path::Reflect {
self
}
#[inline]
fn clone_value(&self) -> Box<dyn #bevy_reflect_path::Reflect> {
Box::new(self.clone())
}
#[inline]
fn apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) {
let value = value.any();
if let Some(value) = value.downcast_ref::<Self>() {
*self = value.clone();
} else {
panic!("Value is not {}.", std::any::type_name::<Self>());
}
}
#[inline]
fn set(&mut self, value: Box<dyn #bevy_reflect_path::Reflect>) -> Result<(), Box<dyn #bevy_reflect_path::Reflect>> {
*self = value.take()?;
Ok(())
}
fn reflect_ref(&self) -> #bevy_reflect_path::ReflectRef {
#bevy_reflect_path::ReflectRef::Value(self)
}
fn reflect_mut(&mut self) -> #bevy_reflect_path::ReflectMut {
#bevy_reflect_path::ReflectMut::Value(self)
}
fn reflect_hash(&self) -> Option<u64> {
#hash_fn
}
fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> Option<bool> {
#partial_eq_fn
}
fn serializable(&self) -> Option<#bevy_reflect_path::serde::Serializable> {
#serialize_fn
}
}
})
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,54 @@
use crate::container_attributes::ReflectTraits;
use proc_macro2::Ident;
use syn::parse::{Parse, ParseStream};
use syn::token::{Paren, Where};
use syn::{parenthesized, Generics};
/// A struct used to define a simple reflected value type (such as primitives).
///
/// This takes the form:
///
/// ```ignore
/// // Standard
/// foo(TraitA, TraitB)
///
/// // With generics
/// foo<T1: Bar, T2>(TraitA, TraitB)
///
/// // With generics and where clause
/// foo<T1, T2> where T1: Bar (TraitA, TraitB)
/// ```
pub(crate) struct ReflectValueDef {
pub type_name: Ident,
pub generics: Generics,
pub traits: Option<ReflectTraits>,
}
impl Parse for ReflectValueDef {
fn parse(input: ParseStream) -> syn::Result<Self> {
let type_ident = input.parse::<Ident>()?;
let generics = input.parse::<Generics>()?;
let mut lookahead = input.lookahead1();
let mut where_clause = None;
if lookahead.peek(Where) {
where_clause = Some(input.parse()?);
lookahead = input.lookahead1();
}
let mut traits = None;
if lookahead.peek(Paren) {
let content;
parenthesized!(content in input);
traits = Some(content.parse::<ReflectTraits>()?);
}
Ok(ReflectValueDef {
type_name: type_ident,
generics: Generics {
where_clause,
..generics
},
traits,
})
}
}

View file

@ -0,0 +1,25 @@
//! Contains code related specifically to Bevy's type registration.
use proc_macro2::Ident;
use quote::quote;
use syn::{Generics, Path};
/// Creates the `GetTypeRegistration` impl for the given type data.
pub(crate) fn impl_get_type_registration(
type_name: &Ident,
bevy_reflect_path: &Path,
registration_data: &[Ident],
generics: &Generics,
) -> proc_macro2::TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote! {
#[allow(unused_mut)]
impl #impl_generics #bevy_reflect_path::GetTypeRegistration for #type_name #ty_generics #where_clause {
fn get_type_registration() -> #bevy_reflect_path::TypeRegistration {
let mut registration = #bevy_reflect_path::TypeRegistration::of::<#type_name #ty_generics>();
#(registration.insert::<#registration_data>(#bevy_reflect_path::FromType::<#type_name #ty_generics>::from_type());)*
registration
}
}
}
}

View file

@ -1,10 +1,9 @@
use bevy_macro_utils::BevyManifest;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{parse::Parse, parse_macro_input, Attribute, Ident, ItemTrait, Token};
use syn::{parse::Parse, parse_macro_input, Attribute, ItemTrait, Token};
pub struct TraitInfo {
pub(crate) struct TraitInfo {
item_trait: ItemTrait,
}
@ -22,13 +21,12 @@ impl Parse for TraitInfo {
}
}
pub fn reflect_trait(_args: &TokenStream, input: TokenStream) -> TokenStream {
pub(crate) fn reflect_trait(_args: &TokenStream, input: TokenStream) -> TokenStream {
let trait_info = parse_macro_input!(input as TraitInfo);
let item_trait = &trait_info.item_trait;
let trait_ident = &item_trait.ident;
let trait_vis = &item_trait.vis;
let reflect_trait_ident =
Ident::new(&format!("Reflect{}", item_trait.ident), Span::call_site());
let reflect_trait_ident = crate::utility::get_reflect_ident(&item_trait.ident.to_string());
let bevy_reflect_path = BevyManifest::default().get_path("bevy_reflect");
TokenStream::from(quote! {
#item_trait

View file

@ -5,7 +5,7 @@ use quote::quote;
use syn::*;
use uuid::Uuid;
pub fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
pub(crate) fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let mut ast: DeriveInput = syn::parse(input).unwrap();

View file

@ -0,0 +1,23 @@
//! General-purpose utility functions for internal usage within this crate.
use bevy_macro_utils::BevyManifest;
use proc_macro2::{Ident, Span};
use syn::Path;
/// Returns the correct path for `bevy_reflect`.
pub(crate) fn get_bevy_reflect_path() -> Path {
BevyManifest::get_path_direct("bevy_reflect")
}
/// Returns the "reflected" ident for a given string.
///
/// # Example
///
/// ```ignore
/// let reflected: Ident = get_reflect_ident("Hash");
/// assert_eq!("ReflectHash", reflected.to_string());
/// ```
pub(crate) fn get_reflect_ident(name: &str) -> Ident {
let reflected = format!("Reflect{}", name);
Ident::new(&reflected, Span::call_site())
}

View file

@ -85,7 +85,6 @@ mod tests {
#[cfg(feature = "glam")]
use ::glam::{vec3, Vec3};
use ::serde::de::DeserializeSeed;
use ::serde::Serialize;
use bevy_utils::HashMap;
use ron::{
ser::{to_string_pretty, PrettyConfig},
@ -478,6 +477,7 @@ mod tests {
#[cfg(feature = "glam")]
mod glam {
use super::*;
use ::serde::Serialize;
#[test]
fn vec3_serialization() {