Path: blob/main/crates/bevy_reflect/derive/src/container_attributes.rs
9427 views
//! Contains code related to container attributes for reflected types.1//!2//! A container attribute is an attribute which applies to an entire struct or enum3//! as opposed to a particular field or variant. An example of such an attribute is4//! the derive helper attribute for `Reflect`, which looks like:5//! `#[reflect(PartialEq, Default, ...)]`.67use crate::{custom_attributes::CustomAttributes, derive_data::ReflectTraitToImpl};8use bevy_macro_utils::{9fq_std::{FQAny, FQClone, FQOption, FQResult},10terminated_parser,11};12use proc_macro2::{Ident, Span};13use quote::quote_spanned;14use syn::{15ext::IdentExt, parenthesized, parse::ParseStream, spanned::Spanned, token, Expr, LitBool,16MetaList, MetaNameValue, Path, Token, WhereClause,17};1819mod kw {20syn::custom_keyword!(from_reflect);21syn::custom_keyword!(type_path);22syn::custom_keyword!(Debug);23syn::custom_keyword!(PartialEq);24syn::custom_keyword!(PartialOrd);25syn::custom_keyword!(Hash);26syn::custom_keyword!(Clone);27syn::custom_keyword!(no_field_bounds);28syn::custom_keyword!(no_auto_register);29syn::custom_keyword!(opaque);30}3132// The "special" trait idents that are used internally for reflection.33// Received via attributes like `#[reflect(PartialEq, Hash, ...)]`34const DEBUG_ATTR: &str = "Debug";35const PARTIAL_EQ_ATTR: &str = "PartialEq";36const PARTIAL_ORD_ATTR: &str = "PartialOrd";37const HASH_ATTR: &str = "Hash";3839// The traits listed below are not considered "special" (i.e. they use the `ReflectMyTrait` syntax)40// but useful to know exist nonetheless41pub(crate) const REFLECT_DEFAULT: &str = "ReflectDefault";4243// Attributes for `FromReflect` implementation44const FROM_REFLECT_ATTR: &str = "from_reflect";4546// Attributes for `TypePath` implementation47const TYPE_PATH_ATTR: &str = "type_path";4849// The error message to show when a trait/type is specified multiple times50const CONFLICTING_TYPE_DATA_MESSAGE: &str = "conflicting type data registration";5152/// A marker for trait implementations registered via the `Reflect` derive macro.53#[derive(Clone, Default)]54pub(crate) enum TraitImpl {55/// The trait is not registered as implemented.56#[default]57NotImplemented,5859/// The trait is registered as implemented.60Implemented(Span),6162/// The trait is registered with a custom function rather than an actual implementation.63Custom(Path, Span),64}6566impl TraitImpl {67/// Merges this [`TraitImpl`] with another.68///69/// Update `self` with whichever value is not [`TraitImpl::NotImplemented`].70/// If `other` is [`TraitImpl::NotImplemented`], then `self` is not modified.71/// An error is returned if neither value is [`TraitImpl::NotImplemented`].72pub fn merge(&mut self, other: TraitImpl) -> Result<(), syn::Error> {73match (&self, other) {74(TraitImpl::NotImplemented, value) => {75*self = value;76Ok(())77}78(_, TraitImpl::NotImplemented) => Ok(()),79(_, TraitImpl::Implemented(span) | TraitImpl::Custom(_, span)) => {80Err(syn::Error::new(span, CONFLICTING_TYPE_DATA_MESSAGE))81}82}83}84}8586/// A collection of attributes used for deriving `FromReflect`.87#[derive(Clone, Default)]88pub(crate) struct FromReflectAttrs {89auto_derive: Option<LitBool>,90}9192impl FromReflectAttrs {93/// Returns true if `FromReflect` should be automatically derived as part of the `Reflect` derive.94pub fn should_auto_derive(&self) -> bool {95self.auto_derive.as_ref().is_none_or(LitBool::value)96}97}9899/// A collection of attributes used for deriving `TypePath` via the `Reflect` derive.100///101/// Note that this differs from the attributes used by the `TypePath` derive itself,102/// which look like `[type_path = "my_crate::foo"]`.103/// The attributes used by reflection take the form `#[reflect(type_path = false)]`.104///105/// These attributes should only be used for `TypePath` configuration specific to106/// deriving `Reflect`.107#[derive(Clone, Default)]108pub(crate) struct TypePathAttrs {109auto_derive: Option<LitBool>,110}111112impl TypePathAttrs {113/// Returns true if `TypePath` should be automatically derived as part of the `Reflect` derive.114pub fn should_auto_derive(&self) -> bool {115self.auto_derive.as_ref().is_none_or(LitBool::value)116}117}118119/// A collection of traits that have been registered for a reflected type.120///121/// This keeps track of a few traits that are utilized internally for reflection122/// (we'll call these traits _special traits_ within this context), but it123/// will also keep track of all registered traits. Traits are registered as part of the124/// `Reflect` derive macro using the helper attribute: `#[reflect(...)]`.125///126/// The list of special traits are as follows:127/// * `Debug`128/// * `Hash`129/// * `PartialEq`130///131/// When registering a trait, there are a few things to keep in mind:132/// * Traits must have a valid `Reflect{}` struct in scope. For example, `Default`133/// needs `bevy_reflect::prelude::ReflectDefault` in scope.134/// * Traits must be single path identifiers. This means you _must_ use `Default`135/// instead of `std::default::Default` (otherwise it will try to register `Reflectstd`!)136/// * A custom function may be supplied in place of an actual implementation137/// for the special traits (but still follows the same single-path identifier138/// rules as normal).139///140/// # Example141///142/// Registering the `Default` implementation:143///144/// ```ignore (bevy_reflect is not accessible from this crate)145/// // Import ReflectDefault so it's accessible by the derive macro146/// use bevy_reflect::prelude::ReflectDefault;147///148/// #[derive(Reflect, Default)]149/// #[reflect(Default)]150/// struct Foo;151/// ```152///153/// Registering the `Hash` implementation:154///155/// ```ignore (bevy_reflect is not accessible from this crate)156/// // `Hash` is a "special trait" and does not need (nor have) a ReflectHash struct157///158/// #[derive(Reflect, Hash)]159/// #[reflect(Hash)]160/// struct Foo;161/// ```162///163/// Registering the `Hash` implementation using a custom function:164///165/// ```ignore (bevy_reflect is not accessible from this crate)166/// // This function acts as our `Hash` implementation and167/// // corresponds to the `Reflect::reflect_hash` method.168/// fn get_hash(foo: &Foo) -> Option<u64> {169/// Some(123)170/// }171///172/// #[derive(Reflect)]173/// // Register the custom `Hash` function174/// #[reflect(Hash(get_hash))]175/// struct Foo;176/// ```177///178/// > __Note:__ Registering a custom function only works for special traits.179#[derive(Default, Clone)]180pub(crate) struct ContainerAttributes {181clone: TraitImpl,182debug: TraitImpl,183hash: TraitImpl,184partial_ord: TraitImpl,185partial_eq: TraitImpl,186from_reflect_attrs: FromReflectAttrs,187type_path_attrs: TypePathAttrs,188custom_where: Option<WhereClause>,189no_field_bounds: bool,190no_auto_register: bool,191custom_attributes: CustomAttributes,192is_opaque: bool,193idents: Vec<Ident>,194}195196impl ContainerAttributes {197/// Parse a comma-separated list of container attributes.198///199/// # Example200/// - `Hash, Debug(custom_debug), MyTrait`201pub fn parse_terminated(202&mut self,203input: ParseStream,204trait_: ReflectTraitToImpl,205) -> syn::Result<()> {206terminated_parser(Token![,], |stream| {207self.parse_container_attribute(stream, trait_)208})(input)?;209210Ok(())211}212213/// Parse the contents of a `#[reflect(...)]` attribute into a [`ContainerAttributes`] instance.214///215/// # Example216/// - `#[reflect(Hash, Debug(custom_debug), MyTrait)]`217/// - `#[reflect(no_field_bounds)]`218pub fn parse_meta_list(219&mut self,220meta: &MetaList,221trait_: ReflectTraitToImpl,222) -> syn::Result<()> {223meta.parse_args_with(|stream: ParseStream| self.parse_terminated(stream, trait_))224}225226/// Parse a single container attribute.227fn parse_container_attribute(228&mut self,229input: ParseStream,230trait_: ReflectTraitToImpl,231) -> syn::Result<()> {232let lookahead = input.lookahead1();233if lookahead.peek(Token![@]) {234self.custom_attributes.parse_custom_attribute(input)235} else if lookahead.peek(Token![where]) {236self.parse_custom_where(input)237} else if lookahead.peek(kw::from_reflect) {238self.parse_from_reflect(input, trait_)239} else if lookahead.peek(kw::type_path) {240self.parse_type_path(input, trait_)241} else if lookahead.peek(kw::opaque) {242self.parse_opaque(input)243} else if lookahead.peek(kw::no_field_bounds) {244self.parse_no_field_bounds(input)245} else if lookahead.peek(kw::Clone) {246self.parse_clone(input)247} else if lookahead.peek(kw::no_auto_register) {248self.parse_no_auto_register(input)249} else if lookahead.peek(kw::Debug) {250self.parse_debug(input)251} else if lookahead.peek(kw::Hash) {252self.parse_hash(input)253} else if lookahead.peek(kw::PartialOrd) {254self.parse_partial_ord(input)255} else if lookahead.peek(kw::PartialEq) {256self.parse_partial_eq(input)257} else if lookahead.peek(Ident::peek_any) {258self.parse_ident(input)259} else {260Err(lookahead.error())261}262}263264/// Parse an ident (for registration).265///266/// Examples:267/// - `#[reflect(MyTrait)]` (registers `ReflectMyTrait`)268fn parse_ident(&mut self, input: ParseStream) -> syn::Result<()> {269let ident = input.parse::<Ident>()?;270271if input.peek(token::Paren) {272return Err(syn::Error::new(ident.span(), format!(273"only [{DEBUG_ATTR:?}, {PARTIAL_EQ_ATTR:?}, {PARTIAL_ORD_ATTR:?}, {HASH_ATTR:?}] may specify custom functions",274)));275}276277let ident_name = ident.to_string();278279// Create the reflect ident280let mut reflect_ident = crate::ident::get_reflect_ident(&ident_name);281// We set the span to the old ident so any compile errors point to that ident instead282reflect_ident.set_span(ident.span());283284add_unique_ident(&mut self.idents, reflect_ident)?;285286Ok(())287}288289/// Parse `clone` attribute.290///291/// Examples:292/// - `#[reflect(Clone)]`293/// - `#[reflect(Clone(custom_clone_fn))]`294fn parse_clone(&mut self, input: ParseStream) -> syn::Result<()> {295let ident = input.parse::<kw::Clone>()?;296297if input.peek(token::Paren) {298let content;299parenthesized!(content in input);300let path = content.parse::<Path>()?;301self.clone.merge(TraitImpl::Custom(path, ident.span))?;302} else {303self.clone = TraitImpl::Implemented(ident.span);304}305306Ok(())307}308309/// Parse special `Debug` registration.310///311/// Examples:312/// - `#[reflect(Debug)]`313/// - `#[reflect(Debug(custom_debug_fn))]`314fn parse_debug(&mut self, input: ParseStream) -> syn::Result<()> {315let ident = input.parse::<kw::Debug>()?;316317if input.peek(token::Paren) {318let content;319parenthesized!(content in input);320let path = content.parse::<Path>()?;321self.debug.merge(TraitImpl::Custom(path, ident.span))?;322} else {323self.debug = TraitImpl::Implemented(ident.span);324}325326Ok(())327}328329/// Parse special `PartialEq` registration.330///331/// Examples:332/// - `#[reflect(PartialEq)]`333/// - `#[reflect(PartialEq(custom_partial_eq_fn))]`334fn parse_partial_eq(&mut self, input: ParseStream) -> syn::Result<()> {335let ident = input.parse::<kw::PartialEq>()?;336337if input.peek(token::Paren) {338let content;339parenthesized!(content in input);340let path = content.parse::<Path>()?;341self.partial_eq.merge(TraitImpl::Custom(path, ident.span))?;342} else {343self.partial_eq = TraitImpl::Implemented(ident.span);344}345346Ok(())347}348349/// Parse special `PartialOrd` registration.350///351/// Examples:352/// - `#[reflect(PartialOrd)]`353/// - `#[reflect(PartialOrd(custom_partial_cmp_fn))]`354fn parse_partial_ord(&mut self, input: ParseStream) -> syn::Result<()> {355let ident = input.parse::<kw::PartialOrd>()?;356357if input.peek(token::Paren) {358let content;359parenthesized!(content in input);360let path = content.parse::<Path>()?;361self.partial_ord362.merge(TraitImpl::Custom(path, ident.span))?;363} else {364self.partial_ord = TraitImpl::Implemented(ident.span);365}366367Ok(())368}369370/// Parse special `Hash` registration.371///372/// Examples:373/// - `#[reflect(Hash)]`374/// - `#[reflect(Hash(custom_hash_fn))]`375fn parse_hash(&mut self, input: ParseStream) -> syn::Result<()> {376let ident = input.parse::<kw::Hash>()?;377378if input.peek(token::Paren) {379let content;380parenthesized!(content in input);381let path = content.parse::<Path>()?;382self.hash.merge(TraitImpl::Custom(path, ident.span))?;383} else {384self.hash = TraitImpl::Implemented(ident.span);385}386387Ok(())388}389390/// Parse `opaque` attribute.391///392/// Examples:393/// - `#[reflect(opaque)]`394fn parse_opaque(&mut self, input: ParseStream) -> syn::Result<()> {395input.parse::<kw::opaque>()?;396self.is_opaque = true;397Ok(())398}399400/// Parse `no_field_bounds` attribute.401///402/// Examples:403/// - `#[reflect(no_field_bounds)]`404fn parse_no_field_bounds(&mut self, input: ParseStream) -> syn::Result<()> {405input.parse::<kw::no_field_bounds>()?;406self.no_field_bounds = true;407Ok(())408}409410/// Parse `no_auto_register` attribute.411///412/// Examples:413/// - `#[reflect(no_auto_register)]`414fn parse_no_auto_register(&mut self, input: ParseStream) -> syn::Result<()> {415input.parse::<kw::no_auto_register>()?;416self.no_auto_register = true;417Ok(())418}419420/// Parse `where` attribute.421///422/// Examples:423/// - `#[reflect(where T: Debug)]`424fn parse_custom_where(&mut self, input: ParseStream) -> syn::Result<()> {425self.custom_where = Some(input.parse()?);426Ok(())427}428429/// Parse `from_reflect` attribute.430///431/// Examples:432/// - `#[reflect(from_reflect = false)]`433fn parse_from_reflect(434&mut self,435input: ParseStream,436trait_: ReflectTraitToImpl,437) -> syn::Result<()> {438let pair = input.parse::<MetaNameValue>()?;439let extracted_bool = extract_bool(&pair.value, |lit| {440// Override `lit` if this is a `FromReflect` derive.441// This typically means a user is opting out of the default implementation442// from the `Reflect` derive and using the `FromReflect` derive directly instead.443if trait_ == ReflectTraitToImpl::FromReflect {444LitBool::new(true, Span::call_site())445} else {446lit.clone()447}448})?;449450if let Some(existing) = &self.from_reflect_attrs.auto_derive {451if existing.value() != extracted_bool.value() {452return Err(syn::Error::new(453extracted_bool.span(),454format!("`{FROM_REFLECT_ATTR}` already set to {}", existing.value()),455));456}457} else {458self.from_reflect_attrs.auto_derive = Some(extracted_bool);459}460461Ok(())462}463464/// Parse `type_path` attribute.465///466/// Examples:467/// - `#[reflect(type_path = false)]`468fn parse_type_path(469&mut self,470input: ParseStream,471trait_: ReflectTraitToImpl,472) -> syn::Result<()> {473let pair = input.parse::<MetaNameValue>()?;474let extracted_bool = extract_bool(&pair.value, |lit| {475// Override `lit` if this is a `FromReflect` derive.476// This typically means a user is opting out of the default implementation477// from the `Reflect` derive and using the `FromReflect` derive directly instead.478if trait_ == ReflectTraitToImpl::TypePath {479LitBool::new(true, Span::call_site())480} else {481lit.clone()482}483})?;484485if let Some(existing) = &self.type_path_attrs.auto_derive {486if existing.value() != extracted_bool.value() {487return Err(syn::Error::new(488extracted_bool.span(),489format!("`{TYPE_PATH_ATTR}` already set to {}", existing.value()),490));491}492} else {493self.type_path_attrs.auto_derive = Some(extracted_bool);494}495496Ok(())497}498499/// Returns true if the given reflected trait name (i.e. `ReflectDefault` for `Default`)500/// is registered for this type.501pub fn contains(&self, name: &str) -> bool {502self.idents.iter().any(|ident| ident == name)503}504505/// The list of reflected traits by their reflected ident (i.e. `ReflectDefault` for `Default`).506pub fn idents(&self) -> &[Ident] {507&self.idents508}509510/// The `FromReflect` configuration found within `#[reflect(...)]` attributes on this type.511#[expect(512clippy::wrong_self_convention,513reason = "Method returns `FromReflectAttrs`, does not actually convert data."514)]515pub fn from_reflect_attrs(&self) -> &FromReflectAttrs {516&self.from_reflect_attrs517}518519/// The `TypePath` configuration found within `#[reflect(...)]` attributes on this type.520pub fn type_path_attrs(&self) -> &TypePathAttrs {521&self.type_path_attrs522}523524/// Returns the implementation of `PartialReflect::reflect_hash` as a `TokenStream`.525///526/// If `Hash` was not registered, returns `None`.527pub fn get_hash_impl(&self, bevy_reflect_path: &Path) -> Option<proc_macro2::TokenStream> {528match &self.hash {529&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>530fn reflect_hash(&self) -> #FQOption<u64> {531use ::core::hash::{Hash, Hasher};532let mut hasher = #bevy_reflect_path::utility::reflect_hasher();533Hash::hash(&#FQAny::type_id(self), &mut hasher);534Hash::hash(self, &mut hasher);535#FQOption::Some(Hasher::finish(&hasher))536}537}),538&TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=>539fn reflect_hash(&self) -> #FQOption<u64> {540#FQOption::Some(#impl_fn(self))541}542}),543TraitImpl::NotImplemented => None,544}545}546547/// Returns the implementation of `PartialReflect::reflect_partial_eq` as a `TokenStream`.548///549/// If `PartialEq` was not registered, returns `None`.550pub fn get_partial_eq_impl(551&self,552bevy_reflect_path: &Path,553) -> Option<proc_macro2::TokenStream> {554match &self.partial_eq {555&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>556fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<bool> {557let value = <dyn #bevy_reflect_path::PartialReflect>::try_downcast_ref::<Self>(value);558if let #FQOption::Some(value) = value {559#FQOption::Some(::core::cmp::PartialEq::eq(self, value))560} else {561#FQOption::Some(false)562}563}564}),565&TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=>566fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<bool> {567#FQOption::Some(#impl_fn(self, value))568}569}),570TraitImpl::NotImplemented => None,571}572}573574/// Returns the implementation of `PartialReflect::reflect_partial_cmp` as a `TokenStream`.575///576/// If `PartialOrd` was not registered, returns `None`.577pub fn get_partial_ord_impl(578&self,579bevy_reflect_path: &Path,580) -> Option<proc_macro2::TokenStream> {581match &self.partial_ord {582&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>583fn reflect_partial_cmp(&self, value: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<::core::cmp::Ordering> {584let value = <dyn #bevy_reflect_path::PartialReflect>::try_downcast_ref::<Self>(value);585if let #FQOption::Some(value) = value {586::core::cmp::PartialOrd::partial_cmp(self, value)587} else {588#FQOption::None589}590}591}),592&TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=>593fn reflect_partial_cmp(&self, value: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<::core::cmp::Ordering> {594#impl_fn(self, value)595}596}),597TraitImpl::NotImplemented => None,598}599}600601/// Returns the implementation of `PartialReflect::debug` as a `TokenStream`.602///603/// If `Debug` was not registered, returns `None`.604pub fn get_debug_impl(&self) -> Option<proc_macro2::TokenStream> {605match &self.debug {606&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>607fn debug(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {608::core::fmt::Debug::fmt(self, f)609}610}),611&TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=>612fn debug(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {613#impl_fn(self, f)614}615}),616TraitImpl::NotImplemented => None,617}618}619620pub fn get_clone_impl(&self, bevy_reflect_path: &Path) -> Option<proc_macro2::TokenStream> {621match &self.clone {622&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>623#[inline]624fn reflect_clone(&self) -> #FQResult<#bevy_reflect_path::__macro_exports::alloc_utils::Box<dyn #bevy_reflect_path::Reflect>, #bevy_reflect_path::ReflectCloneError> {625#FQResult::Ok(#bevy_reflect_path::__macro_exports::alloc_utils::Box::new(#FQClone::clone(self)))626}627}),628&TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=>629#[inline]630fn reflect_clone(&self) -> #FQResult<#bevy_reflect_path::__macro_exports::alloc_utils::Box<dyn #bevy_reflect_path::Reflect>, #bevy_reflect_path::ReflectCloneError> {631#FQResult::Ok(#bevy_reflect_path::__macro_exports::alloc_utils::Box::new(#impl_fn(self)))632}633}),634TraitImpl::NotImplemented => None,635}636}637638pub fn custom_attributes(&self) -> &CustomAttributes {639&self.custom_attributes640}641642/// The custom where configuration found within `#[reflect(...)]` attributes on this type.643pub fn custom_where(&self) -> Option<&WhereClause> {644self.custom_where.as_ref()645}646647/// Returns true if the `no_field_bounds` attribute was found on this type.648pub fn no_field_bounds(&self) -> bool {649self.no_field_bounds650}651652/// Returns true if the `no_auto_register` attribute was found on this type.653#[cfg(feature = "auto_register")]654pub fn no_auto_register(&self) -> bool {655self.no_auto_register656}657658/// Returns true if the `opaque` attribute was found on this type.659pub fn is_opaque(&self) -> bool {660self.is_opaque661}662}663664/// Adds an identifier to a vector of identifiers if it is not already present.665///666/// Returns an error if the identifier already exists in the list.667fn add_unique_ident(idents: &mut Vec<Ident>, ident: Ident) -> Result<(), syn::Error> {668let ident_name = ident.to_string();669if idents.iter().any(|i| i == ident_name.as_str()) {670return Err(syn::Error::new(ident.span(), CONFLICTING_TYPE_DATA_MESSAGE));671}672673idents.push(ident);674Ok(())675}676677/// Extract a boolean value from an expression.678///679/// The mapper exists so that the caller can conditionally choose to use the given680/// value or supply their own.681fn extract_bool(682value: &Expr,683mut mapper: impl FnMut(&LitBool) -> LitBool,684) -> Result<LitBool, syn::Error> {685match value {686Expr::Lit(syn::ExprLit {687lit: syn::Lit::Bool(lit),688..689}) => Ok(mapper(lit)),690_ => Err(syn::Error::new(value.span(), "Expected a boolean value")),691}692}693694695