mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
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:
parent
9c2257332a
commit
9e30aa7c92
16 changed files with 438 additions and 268 deletions
|
@ -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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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! {
|
||||
|
|
|
@ -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! {
|
||||
|
|
|
@ -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! {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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)]
|
||||
| ^
|
||||
|
|
|
@ -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() {}
|
||||
|
|
Loading…
Reference in a new issue