bevy_reflect_derive: Clean up attribute logic (#11777)

# Objective

The code in `bevy_reflect_derive` could use some cleanup.

## Solution

Took some of the changes in #11659 to create a dedicated PR for cleaning
up the field and container attribute logic.

#### Updated Naming

I renamed `ReflectTraits` and `ReflectFieldAttr` to
`ContainerAttributes` and `FieldAttributes`, respectively. I think these
are clearer.

#### Updated Parsing

##### Readability

The parsing logic wasn't too bad before, but it was getting difficult to
read. There was some duplicated logic between `Meta::List` and
`Meta::Path` attributes. Additionally, all the logic was kept inside a
large method.

To simply things, I replaced the nested meta parsing with `ParseStream`
parsing. In my opinion, this is easier to follow since it breaks up the
large match statement into a small set of single-line if statements,
where each if-block contains a single call to the appropriate attribute
parsing method.

##### Flexibility

On top of the added simplicity, this also makes our attribute parsing
much more flexible. It allows us to more elegantly handle custom where
clauses (i.e. `#[reflect(where T: Foo)]`) and it opens the door for more
non-standard attribute syntax (e.g. #11659).

##### Errors

This also allows us to automatically provide certain errors when
parsing. For example, since we can use `stream.lookahead1()`, we get
errors like the following for free:

```
error: expected one of: `ignore`, `skip_serializing`, `default`
    --> crates/bevy_reflect/src/lib.rs:1988:23
     |
1988 |             #[reflect(foo)]
     |                       ^^^
```

---

## Changelog

> [!note]
> All changes are internal to `bevy_reflect_derive` and should not
affect the public API[^1].

- Renamed `ReflectTraits` to `ContainerAttributes`
  - Renamed `ReflectMeta::traits` to `ReflectMeta::attrs`
- Renamed `ReflectFieldAttr` to `FieldAttributes`
- Updated parsing logic for field/container attribute parsing
  - Now uses a `ParseStream` directly instead of nested meta parsing
- General code cleanup of the field/container attribute modules for
`bevy_reflect_derive`


[^1]: Does not include errors, which may look slightly different.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
Gino Valente 2024-02-12 08:16:27 -07:00 committed by GitHub
parent 9c2257332a
commit 9e30aa7c92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 438 additions and 268 deletions

View file

@ -7,14 +7,23 @@
use crate::derive_data::ReflectTraitToImpl;
use crate::utility;
use crate::utility::terminated_parser;
use bevy_macro_utils::fq_std::{FQAny, FQOption};
use proc_macro2::{Ident, Span, TokenTree};
use proc_macro2::{Ident, Span};
use quote::quote_spanned;
use syn::ext::IdentExt;
use syn::parse::ParseStream;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{Expr, LitBool, Meta, MetaList, Path, WhereClause};
use syn::{parenthesized, token, Expr, LitBool, MetaList, MetaNameValue, Path, Token, WhereClause};
mod kw {
syn::custom_keyword!(from_reflect);
syn::custom_keyword!(type_path);
syn::custom_keyword!(Debug);
syn::custom_keyword!(PartialEq);
syn::custom_keyword!(Hash);
syn::custom_keyword!(no_field_bounds);
}
// The "special" trait idents that are used internally for reflection.
// Received via attributes like `#[reflect(PartialEq, Hash, ...)]`
@ -26,9 +35,6 @@ const HASH_ATTR: &str = "Hash";
// but useful to know exist nonetheless
pub(crate) const REFLECT_DEFAULT: &str = "ReflectDefault";
// Attributes for `Reflect` implementation
const NO_FIELD_BOUNDS_ATTR: &str = "no_field_bounds";
// Attributes for `FromReflect` implementation
const FROM_REFLECT_ATTR: &str = "from_reflect";
@ -209,7 +215,7 @@ impl TypePathAttrs {
/// > __Note:__ Registering a custom function only works for special traits.
///
#[derive(Default, Clone)]
pub(crate) struct ReflectTraits {
pub(crate) struct ContainerAttributes {
debug: TraitImpl,
hash: TraitImpl,
partial_eq: TraitImpl,
@ -220,120 +226,208 @@ pub(crate) struct ReflectTraits {
idents: Vec<Ident>,
}
impl ReflectTraits {
pub fn from_meta_list(meta: &MetaList, trait_: ReflectTraitToImpl) -> Result<Self, syn::Error> {
match meta.tokens.clone().into_iter().next() {
// Handles `#[reflect(where T: Trait, U::Assoc: Trait)]`
Some(TokenTree::Ident(ident)) if ident == "where" => Ok(Self {
custom_where: Some(meta.parse_args::<WhereClause>()?),
..Self::default()
}),
_ => Self::from_metas(
meta.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)?,
trait_,
),
}
impl ContainerAttributes {
/// Parse a comma-separated list of container attributes.
///
/// # Example
/// - `Hash, Debug(custom_debug), MyTrait`
pub fn parse_terminated(input: ParseStream, trait_: ReflectTraitToImpl) -> syn::Result<Self> {
let mut this = Self::default();
terminated_parser(Token![,], |stream| {
this.parse_container_attribute(stream, trait_)
})(input)?;
Ok(this)
}
fn from_metas(
metas: Punctuated<Meta, Comma>,
/// Parse the contents of a `#[reflect(...)]` attribute into a [`ContainerAttributes`] instance.
///
/// # Example
/// - `#[reflect(Hash, Debug(custom_debug), MyTrait)]`
/// - `#[reflect(no_field_bounds)]`
pub fn parse_meta_list(meta: &MetaList, trait_: ReflectTraitToImpl) -> syn::Result<Self> {
meta.parse_args_with(|stream: ParseStream| Self::parse_terminated(stream, trait_))
}
/// Parse a single container attribute.
fn parse_container_attribute(
&mut self,
input: ParseStream,
trait_: ReflectTraitToImpl,
) -> Result<Self, syn::Error> {
let mut traits = ReflectTraits::default();
for meta in &metas {
match meta {
// Handles `#[reflect( Debug, PartialEq, Hash, SomeTrait )]`
Meta::Path(path) => {
let Some(ident) = path.get_ident() else {
continue;
};
let ident_name = ident.to_string();
// Track the span where the trait is implemented for future errors
let span = ident.span();
match ident_name.as_str() {
DEBUG_ATTR => {
traits.debug.merge(TraitImpl::Implemented(span))?;
}
PARTIAL_EQ_ATTR => {
traits.partial_eq.merge(TraitImpl::Implemented(span))?;
}
HASH_ATTR => {
traits.hash.merge(TraitImpl::Implemented(span))?;
}
NO_FIELD_BOUNDS_ATTR => {
traits.no_field_bounds = true;
}
// We only track reflected idents for traits not considered special
_ => {
// Create the reflect ident
// We set the span to the old ident so any compile errors point to that ident instead
let mut reflect_ident = utility::get_reflect_ident(&ident_name);
reflect_ident.set_span(span);
add_unique_ident(&mut traits.idents, reflect_ident)?;
}
}
}
// Handles `#[reflect( Debug(custom_debug_fn) )]`
Meta::List(list) if list.path.is_ident(DEBUG_ATTR) => {
let ident = list.path.get_ident().unwrap();
list.parse_nested_meta(|meta| {
let trait_func_ident = TraitImpl::Custom(meta.path, ident.span());
traits.debug.merge(trait_func_ident)
})?;
}
// Handles `#[reflect( PartialEq(custom_partial_eq_fn) )]`
Meta::List(list) if list.path.is_ident(PARTIAL_EQ_ATTR) => {
let ident = list.path.get_ident().unwrap();
list.parse_nested_meta(|meta| {
let trait_func_ident = TraitImpl::Custom(meta.path, ident.span());
traits.partial_eq.merge(trait_func_ident)
})?;
}
// Handles `#[reflect( Hash(custom_hash_fn) )]`
Meta::List(list) if list.path.is_ident(HASH_ATTR) => {
let ident = list.path.get_ident().unwrap();
list.parse_nested_meta(|meta| {
let trait_func_ident = TraitImpl::Custom(meta.path, ident.span());
traits.hash.merge(trait_func_ident)
})?;
}
Meta::List(list) => {
return Err(syn::Error::new_spanned(
list,
format!(
"expected one of [{DEBUG_ATTR:?}, {PARTIAL_EQ_ATTR:?}, {HASH_ATTR:?}]"
),
));
}
Meta::NameValue(pair) => {
if pair.path.is_ident(FROM_REFLECT_ATTR) {
traits.from_reflect_attrs.auto_derive =
Some(extract_bool(&pair.value, |lit| {
// Override `lit` if this is a `FromReflect` derive.
// This typically means a user is opting out of the default implementation
// from the `Reflect` derive and using the `FromReflect` derive directly instead.
(trait_ == ReflectTraitToImpl::FromReflect)
.then(|| LitBool::new(true, Span::call_site()))
.unwrap_or_else(|| lit.clone())
})?);
} else if pair.path.is_ident(TYPE_PATH_ATTR) {
traits.type_path_attrs.auto_derive =
Some(extract_bool(&pair.value, Clone::clone)?);
} else {
return Err(syn::Error::new(pair.path.span(), "Unknown attribute"));
}
}
}
) -> syn::Result<()> {
let lookahead = input.lookahead1();
if lookahead.peek(Token![where]) {
self.parse_custom_where(input)
} else if lookahead.peek(kw::from_reflect) {
self.parse_from_reflect(input, trait_)
} else if lookahead.peek(kw::type_path) {
self.parse_type_path(input, trait_)
} else if lookahead.peek(kw::no_field_bounds) {
self.parse_no_field_bounds(input)
} else if lookahead.peek(kw::Debug) {
self.parse_debug(input)
} else if lookahead.peek(kw::PartialEq) {
self.parse_partial_eq(input)
} else if lookahead.peek(kw::Hash) {
self.parse_hash(input)
} else if lookahead.peek(Ident::peek_any) {
self.parse_ident(input)
} else {
Err(lookahead.error())
}
Ok(traits)
}
pub fn parse(input: ParseStream, trait_: ReflectTraitToImpl) -> syn::Result<Self> {
ReflectTraits::from_metas(Punctuated::parse_terminated(input)?, trait_)
/// Parse an ident (for registration).
///
/// Examples:
/// - `#[reflect(MyTrait)]` (registers `ReflectMyTrait`)
fn parse_ident(&mut self, input: ParseStream) -> syn::Result<()> {
let ident = input.parse::<Ident>()?;
if input.peek(token::Paren) {
return Err(syn::Error::new(ident.span(), format!(
"only [{DEBUG_ATTR:?}, {PARTIAL_EQ_ATTR:?}, {HASH_ATTR:?}] may specify custom functions",
)));
}
let ident_name = ident.to_string();
// Create the reflect ident
let mut reflect_ident = utility::get_reflect_ident(&ident_name);
// We set the span to the old ident so any compile errors point to that ident instead
reflect_ident.set_span(ident.span());
add_unique_ident(&mut self.idents, reflect_ident)?;
Ok(())
}
/// Parse special `Debug` registration.
///
/// Examples:
/// - `#[reflect(Debug)]`
/// - `#[reflect(Debug(custom_debug_fn))]`
fn parse_debug(&mut self, input: ParseStream) -> syn::Result<()> {
let ident = input.parse::<kw::Debug>()?;
if input.peek(token::Paren) {
let content;
parenthesized!(content in input);
let path = content.parse::<Path>()?;
self.debug.merge(TraitImpl::Custom(path, ident.span))?;
} else {
self.debug = TraitImpl::Implemented(ident.span);
}
Ok(())
}
/// Parse special `PartialEq` registration.
///
/// Examples:
/// - `#[reflect(PartialEq)]`
/// - `#[reflect(PartialEq(custom_partial_eq_fn))]`
fn parse_partial_eq(&mut self, input: ParseStream) -> syn::Result<()> {
let ident = input.parse::<kw::PartialEq>()?;
if input.peek(token::Paren) {
let content;
parenthesized!(content in input);
let path = content.parse::<Path>()?;
self.partial_eq.merge(TraitImpl::Custom(path, ident.span))?;
} else {
self.partial_eq = TraitImpl::Implemented(ident.span);
}
Ok(())
}
/// Parse special `Hash` registration.
///
/// Examples:
/// - `#[reflect(Hash)]`
/// - `#[reflect(Hash(custom_hash_fn))]`
fn parse_hash(&mut self, input: ParseStream) -> syn::Result<()> {
let ident = input.parse::<kw::Hash>()?;
if input.peek(token::Paren) {
let content;
parenthesized!(content in input);
let path = content.parse::<Path>()?;
self.hash.merge(TraitImpl::Custom(path, ident.span))?;
} else {
self.hash = TraitImpl::Implemented(ident.span);
}
Ok(())
}
/// Parse `no_field_bounds` attribute.
///
/// Examples:
/// - `#[reflect(no_field_bounds)]`
fn parse_no_field_bounds(&mut self, input: ParseStream) -> syn::Result<()> {
input.parse::<kw::no_field_bounds>()?;
self.no_field_bounds = true;
Ok(())
}
/// Parse `where` attribute.
///
/// Examples:
/// - `#[reflect(where T: Debug)]`
fn parse_custom_where(&mut self, input: ParseStream) -> syn::Result<()> {
self.custom_where = Some(input.parse()?);
Ok(())
}
/// Parse `from_reflect` attribute.
///
/// Examples:
/// - `#[reflect(from_reflect = false)]`
fn parse_from_reflect(
&mut self,
input: ParseStream,
trait_: ReflectTraitToImpl,
) -> syn::Result<()> {
let pair = input.parse::<MetaNameValue>()?;
let value = extract_bool(&pair.value, |lit| {
// Override `lit` if this is a `FromReflect` derive.
// This typically means a user is opting out of the default implementation
// from the `Reflect` derive and using the `FromReflect` derive directly instead.
(trait_ == ReflectTraitToImpl::FromReflect)
.then(|| LitBool::new(true, Span::call_site()))
.unwrap_or_else(|| lit.clone())
})?;
self.from_reflect_attrs.auto_derive = Some(value);
Ok(())
}
/// Parse `type_path` attribute.
///
/// Examples:
/// - `#[reflect(type_path = false)]`
fn parse_type_path(
&mut self,
input: ParseStream,
trait_: ReflectTraitToImpl,
) -> syn::Result<()> {
let pair = input.parse::<MetaNameValue>()?;
let value = extract_bool(&pair.value, |lit| {
// Override `lit` if this is a `FromReflect` derive.
// This typically means a user is opting out of the default implementation
// from the `Reflect` derive and using the `FromReflect` derive directly instead.
(trait_ == ReflectTraitToImpl::TypePath)
.then(|| LitBool::new(true, Span::call_site()))
.unwrap_or_else(|| lit.clone())
})?;
self.type_path_attrs.auto_derive = Some(value);
Ok(())
}
/// Returns true if the given reflected trait name (i.e. `ReflectDefault` for `Default`)
@ -427,41 +521,55 @@ impl ReflectTraits {
}
}
/// The custom where configuration found within `#[reflect(...)]` attributes on this type.
pub fn custom_where(&self) -> Option<&WhereClause> {
self.custom_where.as_ref()
}
/// Returns true if the `no_field_bounds` attribute was found on this type.
pub fn no_field_bounds(&self) -> bool {
self.no_field_bounds
}
/// Merges the trait implementations of this [`ReflectTraits`] with another one.
/// Merges the trait implementations of this [`ContainerAttributes`] with another one.
///
/// An error is returned if the two [`ReflectTraits`] have conflicting implementations.
pub fn merge(&mut self, other: ReflectTraits) -> Result<(), syn::Error> {
self.debug.merge(other.debug)?;
self.hash.merge(other.hash)?;
self.partial_eq.merge(other.partial_eq)?;
self.from_reflect_attrs.merge(other.from_reflect_attrs)?;
self.type_path_attrs.merge(other.type_path_attrs)?;
/// An error is returned if the two [`ContainerAttributes`] have conflicting implementations.
pub fn merge(&mut self, other: ContainerAttributes) -> Result<(), syn::Error> {
// Destructuring is used to help ensure that all fields are merged
let Self {
debug,
hash,
partial_eq,
from_reflect_attrs,
type_path_attrs,
custom_where,
no_field_bounds,
idents,
} = self;
self.merge_custom_where(other.custom_where);
debug.merge(other.debug)?;
hash.merge(other.hash)?;
partial_eq.merge(other.partial_eq)?;
from_reflect_attrs.merge(other.from_reflect_attrs)?;
type_path_attrs.merge(other.type_path_attrs)?;
self.no_field_bounds |= other.no_field_bounds;
Self::merge_custom_where(custom_where, other.custom_where);
*no_field_bounds |= other.no_field_bounds;
for ident in other.idents {
add_unique_ident(&mut self.idents, ident)?;
add_unique_ident(idents, ident)?;
}
Ok(())
}
fn merge_custom_where(&mut self, other: Option<WhereClause>) {
match (&mut self.custom_where, other) {
fn merge_custom_where(this: &mut Option<WhereClause>, other: Option<WhereClause>) {
match (this, other) {
(Some(this), Some(other)) => {
this.predicates.extend(other.predicates);
}
(None, Some(other)) => {
self.custom_where = Some(other);
(this @ None, Some(other)) => {
*this = Some(other);
}
_ => {}
}

View file

@ -1,7 +1,7 @@
use core::fmt;
use crate::container_attributes::{FromReflectAttrs, ReflectTraits, TypePathAttrs};
use crate::field_attributes::{parse_field_attrs, ReflectFieldAttr};
use crate::container_attributes::{ContainerAttributes, FromReflectAttrs, TypePathAttrs};
use crate::field_attributes::FieldAttributes;
use crate::type_path::parse_path_no_leading_colon;
use crate::utility::{StringExpr, WhereClauseOptions};
use quote::{quote, ToTokens};
@ -42,7 +42,7 @@ pub(crate) enum ReflectDerive<'a> {
/// ```
pub(crate) struct ReflectMeta<'a> {
/// The registered traits for this type.
traits: ReflectTraits,
attrs: ContainerAttributes,
/// The path to this type.
type_path: ReflectTypePath<'a>,
/// A cached instance of the path to the `bevy_reflect` crate.
@ -95,7 +95,7 @@ pub(crate) struct StructField<'a> {
/// The raw field.
pub data: &'a Field,
/// The reflection-based attributes on the field.
pub attrs: ReflectFieldAttr,
pub attrs: FieldAttributes,
/// The index of this field within the struct.
pub declaration_index: usize,
/// The index of this field as seen by the reflection API.
@ -118,7 +118,7 @@ pub(crate) struct EnumVariant<'a> {
pub fields: EnumVariantFields<'a>,
/// The reflection-based attributes on the variant.
#[allow(dead_code)]
pub attrs: ReflectFieldAttr,
pub attrs: FieldAttributes,
/// The index of this variant within the enum.
#[allow(dead_code)]
pub index: usize,
@ -183,7 +183,7 @@ impl<'a> ReflectDerive<'a> {
input: &'a DeriveInput,
provenance: ReflectProvenance,
) -> Result<Self, syn::Error> {
let mut traits = ReflectTraits::default();
let mut traits = ContainerAttributes::default();
// Should indicate whether `#[reflect_value]` was used.
let mut reflect_mode = None;
// Should indicate whether `#[type_path = "..."]` was used.
@ -205,7 +205,8 @@ impl<'a> ReflectDerive<'a> {
}
reflect_mode = Some(ReflectMode::Normal);
let new_traits = ReflectTraits::from_meta_list(meta_list, provenance.trait_)?;
let new_traits =
ContainerAttributes::parse_meta_list(meta_list, provenance.trait_)?;
traits.merge(new_traits)?;
}
Meta::List(meta_list) if meta_list.path.is_ident(REFLECT_VALUE_ATTRIBUTE_NAME) => {
@ -217,7 +218,8 @@ impl<'a> ReflectDerive<'a> {
}
reflect_mode = Some(ReflectMode::Value);
let new_traits = ReflectTraits::from_meta_list(meta_list, provenance.trait_)?;
let new_traits =
ContainerAttributes::parse_meta_list(meta_list, provenance.trait_)?;
traits.merge(new_traits)?;
}
Meta::Path(path) if path.is_ident(REFLECT_VALUE_ATTRIBUTE_NAME) => {
@ -361,7 +363,7 @@ impl<'a> ReflectDerive<'a> {
.enumerate()
.map(
|(declaration_index, field)| -> Result<StructField, syn::Error> {
let attrs = parse_field_attrs(&field.attrs)?;
let attrs = FieldAttributes::parse_attributes(&field.attrs)?;
let reflection_index = if attrs.ignore.is_ignored() {
None
@ -404,7 +406,7 @@ impl<'a> ReflectDerive<'a> {
};
Ok(EnumVariant {
fields,
attrs: parse_field_attrs(&variant.attrs)?,
attrs: FieldAttributes::parse_attributes(&variant.attrs)?,
data: variant,
index,
#[cfg(feature = "documentation")]
@ -421,9 +423,9 @@ impl<'a> ReflectDerive<'a> {
}
impl<'a> ReflectMeta<'a> {
pub fn new(type_path: ReflectTypePath<'a>, traits: ReflectTraits) -> Self {
pub fn new(type_path: ReflectTypePath<'a>, attrs: ContainerAttributes) -> Self {
Self {
traits,
attrs,
type_path,
bevy_reflect_path: utility::get_bevy_reflect_path(),
#[cfg(feature = "documentation")]
@ -438,19 +440,19 @@ impl<'a> ReflectMeta<'a> {
}
/// The registered reflect traits on this struct.
pub fn traits(&self) -> &ReflectTraits {
&self.traits
pub fn attrs(&self) -> &ContainerAttributes {
&self.attrs
}
/// The `FromReflect` attributes on this type.
#[allow(clippy::wrong_self_convention)]
pub fn from_reflect(&self) -> &FromReflectAttrs {
self.traits.from_reflect_attrs()
self.attrs.from_reflect_attrs()
}
/// The `TypePath` attributes on this type.
pub fn type_path_attrs(&self) -> &TypePathAttrs {
self.traits.type_path_attrs()
self.attrs.type_path_attrs()
}
/// The path to this type.

View file

@ -4,14 +4,21 @@
//! 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::utility::terminated_parser;
use crate::REFLECT_ATTRIBUTE_NAME;
use syn::meta::ParseNestedMeta;
use syn::{Attribute, LitStr, Token};
use syn::parse::ParseStream;
use syn::{Attribute, LitStr, Meta, Token};
pub(crate) static IGNORE_SERIALIZATION_ATTR: &str = "skip_serializing";
pub(crate) static IGNORE_ALL_ATTR: &str = "ignore";
mod kw {
syn::custom_keyword!(ignore);
syn::custom_keyword!(skip_serializing);
syn::custom_keyword!(default);
}
pub(crate) static DEFAULT_ATTR: &str = "default";
pub(crate) const IGNORE_SERIALIZATION_ATTR: &str = "skip_serializing";
pub(crate) const IGNORE_ALL_ATTR: &str = "ignore";
pub(crate) const DEFAULT_ATTR: &str = "default";
/// Stores data about if the field should be visible via the Reflect and serialization interfaces
///
@ -44,15 +51,6 @@ impl ReflectIgnoreBehavior {
}
}
/// A container for attributes defined on a reflected type's field.
#[derive(Default, Clone)]
pub(crate) struct ReflectFieldAttr {
/// Determines how this field should be ignored if at all.
pub ignore: ReflectIgnoreBehavior,
/// Sets the default behavior of this field.
pub default: DefaultBehavior,
}
/// Controls how the default value is determined for a field.
#[derive(Default, Clone)]
pub(crate) enum DefaultBehavior {
@ -68,79 +66,114 @@ pub(crate) enum DefaultBehavior {
Func(syn::ExprPath),
}
/// 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 result = attr.parse_nested_meta(|meta| parse_meta(&mut args, meta));
if let Err(err) = result {
if let Some(ref mut error) = errors {
error.combine(err);
} else {
errors = Some(err);
}
}
}
if let Some(error) = errors {
Err(error)
} else {
Ok(args)
}
/// A container for attributes defined on a reflected type's field.
#[derive(Default, Clone)]
pub(crate) struct FieldAttributes {
/// Determines how this field should be ignored if at all.
pub ignore: ReflectIgnoreBehavior,
/// Sets the default behavior of this field.
pub default: DefaultBehavior,
}
fn parse_meta(args: &mut ReflectFieldAttr, meta: ParseNestedMeta) -> Result<(), syn::Error> {
if meta.path.is_ident(DEFAULT_ATTR) {
// Allow:
// - `#[reflect(default)]`
// - `#[reflect(default = "path::to::func")]`
if !matches!(args.default, DefaultBehavior::Required) {
return Err(meta.error(format!("only one of [{:?}] is allowed", [DEFAULT_ATTR])));
}
impl FieldAttributes {
/// Parse all field attributes marked "reflect" (such as `#[reflect(ignore)]`).
pub fn parse_attributes(attrs: &[Attribute]) -> syn::Result<Self> {
let mut args = FieldAttributes::default();
if meta.input.peek(Token![=]) {
let lit = meta.value()?.parse::<LitStr>()?;
args.default = DefaultBehavior::Func(lit.parse()?);
attrs
.iter()
.filter_map(|attr| {
if !attr.path().is_ident(REFLECT_ATTRIBUTE_NAME) {
// Not a reflect attribute -> skip
return None;
}
let Meta::List(meta) = &attr.meta else {
return Some(syn::Error::new_spanned(attr, "expected meta list"));
};
// Parse all attributes inside the list, collecting any errors
meta.parse_args_with(terminated_parser(Token![,], |stream| {
args.parse_field_attribute(stream)
}))
.err()
})
.reduce(|mut acc, err| {
acc.combine(err);
acc
})
.map_or(Ok(args), Err)
}
/// Parses a single field attribute.
fn parse_field_attribute(&mut self, input: ParseStream) -> syn::Result<()> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::ignore) {
self.parse_ignore(input)
} else if lookahead.peek(kw::skip_serializing) {
self.parse_skip_serializing(input)
} else if lookahead.peek(kw::default) {
self.parse_default(input)
} else {
args.default = DefaultBehavior::Default;
Err(lookahead.error())
}
}
Ok(())
} else if meta.path.is_ident(IGNORE_ALL_ATTR) {
// Allow:
// - `#[reflect(ignore)]`
if args.ignore != ReflectIgnoreBehavior::None {
return Err(meta.error(format!(
"only one of [{:?}] is allowed",
/// Parse `ignore` attribute.
///
/// Examples:
/// - `#[reflect(ignore)]`
fn parse_ignore(&mut self, input: ParseStream) -> syn::Result<()> {
if self.ignore != ReflectIgnoreBehavior::None {
return Err(input.error(format!(
"only one of {:?} is allowed",
[IGNORE_ALL_ATTR, IGNORE_SERIALIZATION_ATTR]
)));
}
args.ignore = ReflectIgnoreBehavior::IgnoreAlways;
input.parse::<kw::ignore>()?;
self.ignore = ReflectIgnoreBehavior::IgnoreAlways;
Ok(())
} else if meta.path.is_ident(IGNORE_SERIALIZATION_ATTR) {
// Allow:
// - `#[reflect(skip_serializing)]`
if args.ignore != ReflectIgnoreBehavior::None {
return Err(meta.error(format!(
"only one of [{:?}] is allowed",
}
/// Parse `skip_serializing` attribute.
///
/// Examples:
/// - `#[reflect(skip_serializing)]`
fn parse_skip_serializing(&mut self, input: ParseStream) -> syn::Result<()> {
if self.ignore != ReflectIgnoreBehavior::None {
return Err(input.error(format!(
"only one of {:?} is allowed",
[IGNORE_ALL_ATTR, IGNORE_SERIALIZATION_ATTR]
)));
}
args.ignore = ReflectIgnoreBehavior::IgnoreSerialization;
input.parse::<kw::skip_serializing>()?;
self.ignore = ReflectIgnoreBehavior::IgnoreSerialization;
Ok(())
}
/// Parse `default` attribute.
///
/// Examples:
/// - `#[reflect(default)]`
/// - `#[reflect(default = "path::to::func")]`
fn parse_default(&mut self, input: ParseStream) -> syn::Result<()> {
if !matches!(self.default, DefaultBehavior::Required) {
return Err(input.error(format!("only one of {:?} is allowed", [DEFAULT_ATTR])));
}
input.parse::<kw::default>()?;
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
let lit = input.parse::<LitStr>()?;
self.default = DefaultBehavior::Func(lit.parse()?);
} else {
self.default = DefaultBehavior::Default;
}
Ok(())
} else {
Err(meta.error(format!(
"unknown attribute, expected {:?}",
[DEFAULT_ATTR, IGNORE_ALL_ATTR, IGNORE_SERIALIZATION_ATTR]
)))
}
}

View file

@ -98,7 +98,7 @@ fn impl_struct_internal(
let MemberValuePair(active_members, active_values) =
get_active_fields(reflect_struct, &ref_struct, &ref_struct_type, is_tuple);
let is_defaultable = reflect_struct.meta().traits().contains(REFLECT_DEFAULT);
let is_defaultable = reflect_struct.meta().attrs().contains(REFLECT_DEFAULT);
let constructor = if is_defaultable {
quote!(
let mut __this: Self = #FQDefault::default();

View file

@ -35,7 +35,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
let hash_fn = reflect_enum
.meta()
.traits()
.attrs()
.get_hash_impl(bevy_reflect_path)
.unwrap_or_else(|| {
quote! {
@ -44,10 +44,10 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
}
}
});
let debug_fn = reflect_enum.meta().traits().get_debug_impl();
let debug_fn = reflect_enum.meta().attrs().get_debug_impl();
let partial_eq_fn = reflect_enum
.meta()
.traits()
.attrs()
.get_partial_eq_impl(bevy_reflect_path)
.unwrap_or_else(|| {
quote! {

View file

@ -32,11 +32,11 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS
let hash_fn = reflect_struct
.meta()
.traits()
.attrs()
.get_hash_impl(bevy_reflect_path);
let debug_fn = reflect_struct.meta().traits().get_debug_impl();
let debug_fn = reflect_struct.meta().attrs().get_debug_impl();
let partial_eq_fn = reflect_struct.meta()
.traits()
.attrs()
.get_partial_eq_impl(bevy_reflect_path)
.unwrap_or_else(|| {
quote! {

View file

@ -24,12 +24,12 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::
let hash_fn = reflect_struct
.meta()
.traits()
.attrs()
.get_hash_impl(bevy_reflect_path);
let debug_fn = reflect_struct.meta().traits().get_debug_impl();
let debug_fn = reflect_struct.meta().attrs().get_debug_impl();
let partial_eq_fn = reflect_struct
.meta()
.traits()
.attrs()
.get_partial_eq_impl(bevy_reflect_path)
.unwrap_or_else(|| {
quote! {

View file

@ -51,7 +51,7 @@ pub(crate) enum TypedProperty {
pub(crate) fn impl_type_path(meta: &ReflectMeta) -> proc_macro2::TokenStream {
let where_clause_options = WhereClauseOptions::new(meta);
if !meta.traits().type_path_attrs().should_auto_derive() {
if !meta.attrs().type_path_attrs().should_auto_derive() {
return proc_macro2::TokenStream::new();
}

View file

@ -9,9 +9,9 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream {
let bevy_reflect_path = meta.bevy_reflect_path();
let type_path = meta.type_path();
let hash_fn = meta.traits().get_hash_impl(bevy_reflect_path);
let partial_eq_fn = meta.traits().get_partial_eq_impl(bevy_reflect_path);
let debug_fn = meta.traits().get_debug_impl();
let hash_fn = meta.attrs().get_hash_impl(bevy_reflect_path);
let partial_eq_fn = meta.attrs().get_partial_eq_impl(bevy_reflect_path);
let debug_fn = meta.attrs().get_debug_impl();
#[cfg(feature = "documentation")]
let with_docs = {

View file

@ -30,7 +30,7 @@ mod type_path;
mod utility;
use crate::derive_data::{ReflectDerive, ReflectMeta, ReflectStruct};
use container_attributes::ReflectTraits;
use container_attributes::ContainerAttributes;
use derive_data::{ReflectImplSource, ReflectProvenance, ReflectTraitToImpl, ReflectTypePath};
use proc_macro::TokenStream;
use quote::quote;
@ -657,7 +657,7 @@ pub fn impl_type_path(input: TokenStream) -> TokenStream {
NamedTypePathDef::Primitive(ref ident) => ReflectTypePath::Primitive(ident),
};
let meta = ReflectMeta::new(type_path, ReflectTraits::default());
let meta = ReflectMeta::new(type_path, ContainerAttributes::default());
let type_path_impl = impls::impl_type_path(&meta);

View file

@ -1,4 +1,4 @@
use crate::container_attributes::ReflectTraits;
use crate::container_attributes::ContainerAttributes;
use crate::derive_data::ReflectTraitToImpl;
use crate::type_path::CustomPathDef;
use syn::parse::ParseStream;
@ -29,7 +29,7 @@ pub(crate) struct ReflectValueDef {
pub attrs: Vec<Attribute>,
pub type_path: Path,
pub generics: Generics,
pub traits: Option<ReflectTraits>,
pub traits: Option<ContainerAttributes>,
pub custom_path: Option<CustomPathDef>,
}
@ -55,7 +55,7 @@ impl ReflectValueDef {
if input.peek(Paren) {
let content;
parenthesized!(content in input);
traits = Some(ReflectTraits::parse(&content, trait_)?);
traits = Some(ContainerAttributes::parse_terminated(&content, trait_)?);
}
Ok(ReflectValueDef {
attrs,

View file

@ -14,7 +14,7 @@ pub(crate) fn impl_get_type_registration(
) -> proc_macro2::TokenStream {
let type_path = meta.type_path();
let bevy_reflect_path = meta.bevy_reflect_path();
let registration_data = meta.traits().idents();
let registration_data = meta.attrs().idents();
let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);

View file

@ -7,6 +7,7 @@ use bevy_macro_utils::{
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream, Peek};
use syn::punctuated::Punctuated;
use syn::{spanned::Spanned, LitStr, Member, Path, Token, Type, WhereClause};
@ -183,7 +184,7 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
predicates.extend(field_predicates);
}
if let Some(custom_where) = self.meta.traits().custom_where() {
if let Some(custom_where) = self.meta.attrs().custom_where() {
predicates.push(custom_where.predicates.to_token_stream());
}
@ -208,7 +209,7 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
/// Returns an iterator over the where clause predicates for the active fields.
fn active_field_predicates(&self) -> Option<impl Iterator<Item = TokenStream> + '_> {
if self.meta.traits().no_field_bounds() {
if self.meta.attrs().no_field_bounds() {
None
} else {
let bevy_reflect_path = self.meta.bevy_reflect_path();
@ -405,3 +406,37 @@ impl FromIterator<StringExpr> for StringExpr {
}
}
}
/// Returns a [`syn::parse::Parser`] which parses a stream of zero or more occurences of `T`
/// separated by punctuation of type `P`, with optional trailing punctuation.
///
/// This is functionally the same as [`Punctuated::parse_terminated`],
/// but accepts a closure rather than a function pointer.
pub(crate) fn terminated_parser<T, P, F: FnMut(ParseStream) -> syn::Result<T>>(
terminator: P,
mut parser: F,
) -> impl FnOnce(ParseStream) -> syn::Result<Punctuated<T, P::Token>>
where
P: Peek,
P::Token: Parse,
{
let _ = terminator;
move |stream: ParseStream| {
let mut punctuated = Punctuated::new();
loop {
if stream.is_empty() {
break;
}
let value = parser(stream)?;
punctuated.push_value(value);
if stream.is_empty() {
break;
}
let punct = stream.parse()?;
punctuated.push_punct(punct);
}
Ok(punctuated)
}
}

View file

@ -1,4 +1,4 @@
use bevy_reflect::{Reflect, FromType};
use bevy_reflect::{FromType, Reflect};
use std::marker::PhantomData;
#[derive(Clone)]
@ -10,22 +10,13 @@ impl<T> FromType<T> for ReflectMyTrait {
}
}
// Reason: where clause cannot be used with #[reflect(MyTrait)]
// Reason: populated `where` clause must be last with #[reflect(MyTrait)]
#[derive(Reflect)]
#[reflect(MyTrait, where)]
#[reflect(where T: std::fmt::Debug, MyTrait)]
pub struct Foo<T> {
value: String,
#[reflect(ignore)]
_marker: PhantomData<T>,
}
// Reason: where clause cannot be used with #[reflect(MyTrait)]
#[derive(Reflect)]
#[reflect(where, MyTrait)]
pub struct Bar<T> {
value: String,
#[reflect(ignore)]
_marker: PhantomData<T>,
}
fn main() {}
fn main() {}

View file

@ -1,11 +1,5 @@
error: expected identifier, found keyword `where`
--> tests/reflect_derive/custom_where.fail.rs:15:20
error: expected `:`
--> tests/reflect_derive/custom_where.fail.rs:15:44
|
15 | #[reflect(MyTrait, where)]
| ^^^^^
error: unexpected token
--> tests/reflect_derive/custom_where.fail.rs:24:16
|
24 | #[reflect(where, MyTrait)]
| ^
15 | #[reflect(where T: std::fmt::Debug, MyTrait)]
| ^

View file

@ -1,4 +1,4 @@
use bevy_reflect::{Reflect, FromType};
use bevy_reflect::{FromType, Reflect};
use std::marker::PhantomData;
#[derive(Clone)]
@ -11,8 +11,7 @@ impl<T> FromType<T> for ReflectMyTrait {
}
#[derive(Reflect)]
#[reflect(MyTrait)]
#[reflect(where)]
#[reflect(MyTrait, where T: std::fmt::Debug)]
pub struct Foo<T> {
value: String,
#[reflect(ignore)]
@ -20,12 +19,20 @@ pub struct Foo<T> {
}
#[derive(Reflect)]
#[reflect(where)]
#[reflect(MyTrait)]
#[reflect(where, MyTrait)]
pub struct Bar<T> {
value: String,
#[reflect(ignore)]
_marker: PhantomData<T>,
}
#[derive(Reflect)]
#[reflect(MyTrait)]
#[reflect(where T: std::fmt::Debug)]
pub struct Baz<T> {
value: String,
#[reflect(ignore)]
_marker: PhantomData<T>,
}
fn main() {}