Path: blob/main/crates/bevy_reflect/derive/src/container_attributes.rs
6599 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::{8attribute_parser::terminated_parser, custom_attributes::CustomAttributes,9derive_data::ReflectTraitToImpl,10};11use bevy_macro_utils::fq_std::{FQAny, FQClone, FQOption, FQResult};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!(Hash);25syn::custom_keyword!(Clone);26syn::custom_keyword!(no_field_bounds);27syn::custom_keyword!(no_auto_register);28syn::custom_keyword!(opaque);29}3031// The "special" trait idents that are used internally for reflection.32// Received via attributes like `#[reflect(PartialEq, Hash, ...)]`33const DEBUG_ATTR: &str = "Debug";34const PARTIAL_EQ_ATTR: &str = "PartialEq";35const HASH_ATTR: &str = "Hash";3637// The traits listed below are not considered "special" (i.e. they use the `ReflectMyTrait` syntax)38// but useful to know exist nonetheless39pub(crate) const REFLECT_DEFAULT: &str = "ReflectDefault";4041// Attributes for `FromReflect` implementation42const FROM_REFLECT_ATTR: &str = "from_reflect";4344// Attributes for `TypePath` implementation45const TYPE_PATH_ATTR: &str = "type_path";4647// The error message to show when a trait/type is specified multiple times48const CONFLICTING_TYPE_DATA_MESSAGE: &str = "conflicting type data registration";4950/// A marker for trait implementations registered via the `Reflect` derive macro.51#[derive(Clone, Default)]52pub(crate) enum TraitImpl {53/// The trait is not registered as implemented.54#[default]55NotImplemented,5657/// The trait is registered as implemented.58Implemented(Span),5960/// The trait is registered with a custom function rather than an actual implementation.61Custom(Path, Span),62}6364impl TraitImpl {65/// Merges this [`TraitImpl`] with another.66///67/// Update `self` with whichever value is not [`TraitImpl::NotImplemented`].68/// If `other` is [`TraitImpl::NotImplemented`], then `self` is not modified.69/// An error is returned if neither value is [`TraitImpl::NotImplemented`].70pub fn merge(&mut self, other: TraitImpl) -> Result<(), syn::Error> {71match (&self, other) {72(TraitImpl::NotImplemented, value) => {73*self = value;74Ok(())75}76(_, TraitImpl::NotImplemented) => Ok(()),77(_, TraitImpl::Implemented(span) | TraitImpl::Custom(_, span)) => {78Err(syn::Error::new(span, CONFLICTING_TYPE_DATA_MESSAGE))79}80}81}82}8384/// A collection of attributes used for deriving `FromReflect`.85#[derive(Clone, Default)]86pub(crate) struct FromReflectAttrs {87auto_derive: Option<LitBool>,88}8990impl FromReflectAttrs {91/// Returns true if `FromReflect` should be automatically derived as part of the `Reflect` derive.92pub fn should_auto_derive(&self) -> bool {93self.auto_derive.as_ref().is_none_or(LitBool::value)94}95}9697/// A collection of attributes used for deriving `TypePath` via the `Reflect` derive.98///99/// Note that this differs from the attributes used by the `TypePath` derive itself,100/// which look like `[type_path = "my_crate::foo"]`.101/// The attributes used by reflection take the form `#[reflect(type_path = false)]`.102///103/// These attributes should only be used for `TypePath` configuration specific to104/// deriving `Reflect`.105#[derive(Clone, Default)]106pub(crate) struct TypePathAttrs {107auto_derive: Option<LitBool>,108}109110impl TypePathAttrs {111/// Returns true if `TypePath` should be automatically derived as part of the `Reflect` derive.112pub fn should_auto_derive(&self) -> bool {113self.auto_derive.as_ref().is_none_or(LitBool::value)114}115}116117/// A collection of traits that have been registered for a reflected type.118///119/// This keeps track of a few traits that are utilized internally for reflection120/// (we'll call these traits _special traits_ within this context), but it121/// will also keep track of all registered traits. Traits are registered as part of the122/// `Reflect` derive macro using the helper attribute: `#[reflect(...)]`.123///124/// The list of special traits are as follows:125/// * `Debug`126/// * `Hash`127/// * `PartialEq`128///129/// When registering a trait, there are a few things to keep in mind:130/// * Traits must have a valid `Reflect{}` struct in scope. For example, `Default`131/// needs `bevy_reflect::prelude::ReflectDefault` in scope.132/// * Traits must be single path identifiers. This means you _must_ use `Default`133/// instead of `std::default::Default` (otherwise it will try to register `Reflectstd`!)134/// * A custom function may be supplied in place of an actual implementation135/// for the special traits (but still follows the same single-path identifier136/// rules as normal).137///138/// # Example139///140/// Registering the `Default` implementation:141///142/// ```ignore (bevy_reflect is not accessible from this crate)143/// // Import ReflectDefault so it's accessible by the derive macro144/// use bevy_reflect::prelude::ReflectDefault;145///146/// #[derive(Reflect, Default)]147/// #[reflect(Default)]148/// struct Foo;149/// ```150///151/// Registering the `Hash` implementation:152///153/// ```ignore (bevy_reflect is not accessible from this crate)154/// // `Hash` is a "special trait" and does not need (nor have) a ReflectHash struct155///156/// #[derive(Reflect, Hash)]157/// #[reflect(Hash)]158/// struct Foo;159/// ```160///161/// Registering the `Hash` implementation using a custom function:162///163/// ```ignore (bevy_reflect is not accessible from this crate)164/// // This function acts as our `Hash` implementation and165/// // corresponds to the `Reflect::reflect_hash` method.166/// fn get_hash(foo: &Foo) -> Option<u64> {167/// Some(123)168/// }169///170/// #[derive(Reflect)]171/// // Register the custom `Hash` function172/// #[reflect(Hash(get_hash))]173/// struct Foo;174/// ```175///176/// > __Note:__ Registering a custom function only works for special traits.177#[derive(Default, Clone)]178pub(crate) struct ContainerAttributes {179clone: TraitImpl,180debug: TraitImpl,181hash: TraitImpl,182partial_eq: TraitImpl,183from_reflect_attrs: FromReflectAttrs,184type_path_attrs: TypePathAttrs,185custom_where: Option<WhereClause>,186no_field_bounds: bool,187no_auto_register: bool,188custom_attributes: CustomAttributes,189is_opaque: bool,190idents: Vec<Ident>,191}192193impl ContainerAttributes {194/// Parse a comma-separated list of container attributes.195///196/// # Example197/// - `Hash, Debug(custom_debug), MyTrait`198pub fn parse_terminated(199&mut self,200input: ParseStream,201trait_: ReflectTraitToImpl,202) -> syn::Result<()> {203terminated_parser(Token![,], |stream| {204self.parse_container_attribute(stream, trait_)205})(input)?;206207Ok(())208}209210/// Parse the contents of a `#[reflect(...)]` attribute into a [`ContainerAttributes`] instance.211///212/// # Example213/// - `#[reflect(Hash, Debug(custom_debug), MyTrait)]`214/// - `#[reflect(no_field_bounds)]`215pub fn parse_meta_list(216&mut self,217meta: &MetaList,218trait_: ReflectTraitToImpl,219) -> syn::Result<()> {220meta.parse_args_with(|stream: ParseStream| self.parse_terminated(stream, trait_))221}222223/// Parse a single container attribute.224fn parse_container_attribute(225&mut self,226input: ParseStream,227trait_: ReflectTraitToImpl,228) -> syn::Result<()> {229let lookahead = input.lookahead1();230if lookahead.peek(Token![@]) {231self.custom_attributes.parse_custom_attribute(input)232} else if lookahead.peek(Token![where]) {233self.parse_custom_where(input)234} else if lookahead.peek(kw::from_reflect) {235self.parse_from_reflect(input, trait_)236} else if lookahead.peek(kw::type_path) {237self.parse_type_path(input, trait_)238} else if lookahead.peek(kw::opaque) {239self.parse_opaque(input)240} else if lookahead.peek(kw::no_field_bounds) {241self.parse_no_field_bounds(input)242} else if lookahead.peek(kw::Clone) {243self.parse_clone(input)244} else if lookahead.peek(kw::no_auto_register) {245self.parse_no_auto_register(input)246} else if lookahead.peek(kw::Debug) {247self.parse_debug(input)248} else if lookahead.peek(kw::Hash) {249self.parse_hash(input)250} else if lookahead.peek(kw::PartialEq) {251self.parse_partial_eq(input)252} else if lookahead.peek(Ident::peek_any) {253self.parse_ident(input)254} else {255Err(lookahead.error())256}257}258259/// Parse an ident (for registration).260///261/// Examples:262/// - `#[reflect(MyTrait)]` (registers `ReflectMyTrait`)263fn parse_ident(&mut self, input: ParseStream) -> syn::Result<()> {264let ident = input.parse::<Ident>()?;265266if input.peek(token::Paren) {267return Err(syn::Error::new(ident.span(), format!(268"only [{DEBUG_ATTR:?}, {PARTIAL_EQ_ATTR:?}, {HASH_ATTR:?}] may specify custom functions",269)));270}271272let ident_name = ident.to_string();273274// Create the reflect ident275let mut reflect_ident = crate::ident::get_reflect_ident(&ident_name);276// We set the span to the old ident so any compile errors point to that ident instead277reflect_ident.set_span(ident.span());278279add_unique_ident(&mut self.idents, reflect_ident)?;280281Ok(())282}283284/// Parse `clone` attribute.285///286/// Examples:287/// - `#[reflect(Clone)]`288/// - `#[reflect(Clone(custom_clone_fn))]`289fn parse_clone(&mut self, input: ParseStream) -> syn::Result<()> {290let ident = input.parse::<kw::Clone>()?;291292if input.peek(token::Paren) {293let content;294parenthesized!(content in input);295let path = content.parse::<Path>()?;296self.clone.merge(TraitImpl::Custom(path, ident.span))?;297} else {298self.clone = TraitImpl::Implemented(ident.span);299}300301Ok(())302}303304/// Parse special `Debug` registration.305///306/// Examples:307/// - `#[reflect(Debug)]`308/// - `#[reflect(Debug(custom_debug_fn))]`309fn parse_debug(&mut self, input: ParseStream) -> syn::Result<()> {310let ident = input.parse::<kw::Debug>()?;311312if input.peek(token::Paren) {313let content;314parenthesized!(content in input);315let path = content.parse::<Path>()?;316self.debug.merge(TraitImpl::Custom(path, ident.span))?;317} else {318self.debug = TraitImpl::Implemented(ident.span);319}320321Ok(())322}323324/// Parse special `PartialEq` registration.325///326/// Examples:327/// - `#[reflect(PartialEq)]`328/// - `#[reflect(PartialEq(custom_partial_eq_fn))]`329fn parse_partial_eq(&mut self, input: ParseStream) -> syn::Result<()> {330let ident = input.parse::<kw::PartialEq>()?;331332if input.peek(token::Paren) {333let content;334parenthesized!(content in input);335let path = content.parse::<Path>()?;336self.partial_eq.merge(TraitImpl::Custom(path, ident.span))?;337} else {338self.partial_eq = TraitImpl::Implemented(ident.span);339}340341Ok(())342}343344/// Parse special `Hash` registration.345///346/// Examples:347/// - `#[reflect(Hash)]`348/// - `#[reflect(Hash(custom_hash_fn))]`349fn parse_hash(&mut self, input: ParseStream) -> syn::Result<()> {350let ident = input.parse::<kw::Hash>()?;351352if input.peek(token::Paren) {353let content;354parenthesized!(content in input);355let path = content.parse::<Path>()?;356self.hash.merge(TraitImpl::Custom(path, ident.span))?;357} else {358self.hash = TraitImpl::Implemented(ident.span);359}360361Ok(())362}363364/// Parse `opaque` attribute.365///366/// Examples:367/// - `#[reflect(opaque)]`368fn parse_opaque(&mut self, input: ParseStream) -> syn::Result<()> {369input.parse::<kw::opaque>()?;370self.is_opaque = true;371Ok(())372}373374/// Parse `no_field_bounds` attribute.375///376/// Examples:377/// - `#[reflect(no_field_bounds)]`378fn parse_no_field_bounds(&mut self, input: ParseStream) -> syn::Result<()> {379input.parse::<kw::no_field_bounds>()?;380self.no_field_bounds = true;381Ok(())382}383384/// Parse `no_auto_register` attribute.385///386/// Examples:387/// - `#[reflect(no_auto_register)]`388fn parse_no_auto_register(&mut self, input: ParseStream) -> syn::Result<()> {389input.parse::<kw::no_auto_register>()?;390self.no_auto_register = true;391Ok(())392}393394/// Parse `where` attribute.395///396/// Examples:397/// - `#[reflect(where T: Debug)]`398fn parse_custom_where(&mut self, input: ParseStream) -> syn::Result<()> {399self.custom_where = Some(input.parse()?);400Ok(())401}402403/// Parse `from_reflect` attribute.404///405/// Examples:406/// - `#[reflect(from_reflect = false)]`407fn parse_from_reflect(408&mut self,409input: ParseStream,410trait_: ReflectTraitToImpl,411) -> syn::Result<()> {412let pair = input.parse::<MetaNameValue>()?;413let extracted_bool = extract_bool(&pair.value, |lit| {414// Override `lit` if this is a `FromReflect` derive.415// This typically means a user is opting out of the default implementation416// from the `Reflect` derive and using the `FromReflect` derive directly instead.417if trait_ == ReflectTraitToImpl::FromReflect {418LitBool::new(true, Span::call_site())419} else {420lit.clone()421}422})?;423424if let Some(existing) = &self.from_reflect_attrs.auto_derive {425if existing.value() != extracted_bool.value() {426return Err(syn::Error::new(427extracted_bool.span(),428format!("`{FROM_REFLECT_ATTR}` already set to {}", existing.value()),429));430}431} else {432self.from_reflect_attrs.auto_derive = Some(extracted_bool);433}434435Ok(())436}437438/// Parse `type_path` attribute.439///440/// Examples:441/// - `#[reflect(type_path = false)]`442fn parse_type_path(443&mut self,444input: ParseStream,445trait_: ReflectTraitToImpl,446) -> syn::Result<()> {447let pair = input.parse::<MetaNameValue>()?;448let extracted_bool = extract_bool(&pair.value, |lit| {449// Override `lit` if this is a `FromReflect` derive.450// This typically means a user is opting out of the default implementation451// from the `Reflect` derive and using the `FromReflect` derive directly instead.452if trait_ == ReflectTraitToImpl::TypePath {453LitBool::new(true, Span::call_site())454} else {455lit.clone()456}457})?;458459if let Some(existing) = &self.type_path_attrs.auto_derive {460if existing.value() != extracted_bool.value() {461return Err(syn::Error::new(462extracted_bool.span(),463format!("`{TYPE_PATH_ATTR}` already set to {}", existing.value()),464));465}466} else {467self.type_path_attrs.auto_derive = Some(extracted_bool);468}469470Ok(())471}472473/// Returns true if the given reflected trait name (i.e. `ReflectDefault` for `Default`)474/// is registered for this type.475pub fn contains(&self, name: &str) -> bool {476self.idents.iter().any(|ident| ident == name)477}478479/// The list of reflected traits by their reflected ident (i.e. `ReflectDefault` for `Default`).480pub fn idents(&self) -> &[Ident] {481&self.idents482}483484/// The `FromReflect` configuration found within `#[reflect(...)]` attributes on this type.485#[expect(486clippy::wrong_self_convention,487reason = "Method returns `FromReflectAttrs`, does not actually convert data."488)]489pub fn from_reflect_attrs(&self) -> &FromReflectAttrs {490&self.from_reflect_attrs491}492493/// The `TypePath` configuration found within `#[reflect(...)]` attributes on this type.494pub fn type_path_attrs(&self) -> &TypePathAttrs {495&self.type_path_attrs496}497498/// Returns the implementation of `PartialReflect::reflect_hash` as a `TokenStream`.499///500/// If `Hash` was not registered, returns `None`.501pub fn get_hash_impl(&self, bevy_reflect_path: &Path) -> Option<proc_macro2::TokenStream> {502match &self.hash {503&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>504fn reflect_hash(&self) -> #FQOption<u64> {505use ::core::hash::{Hash, Hasher};506let mut hasher = #bevy_reflect_path::utility::reflect_hasher();507Hash::hash(&#FQAny::type_id(self), &mut hasher);508Hash::hash(self, &mut hasher);509#FQOption::Some(Hasher::finish(&hasher))510}511}),512&TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=>513fn reflect_hash(&self) -> #FQOption<u64> {514#FQOption::Some(#impl_fn(self))515}516}),517TraitImpl::NotImplemented => None,518}519}520521/// Returns the implementation of `PartialReflect::reflect_partial_eq` as a `TokenStream`.522///523/// If `PartialEq` was not registered, returns `None`.524pub fn get_partial_eq_impl(525&self,526bevy_reflect_path: &Path,527) -> Option<proc_macro2::TokenStream> {528match &self.partial_eq {529&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>530fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<bool> {531let value = <dyn #bevy_reflect_path::PartialReflect>::try_downcast_ref::<Self>(value);532if let #FQOption::Some(value) = value {533#FQOption::Some(::core::cmp::PartialEq::eq(self, value))534} else {535#FQOption::Some(false)536}537}538}),539&TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=>540fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<bool> {541#FQOption::Some(#impl_fn(self, value))542}543}),544TraitImpl::NotImplemented => None,545}546}547548/// Returns the implementation of `PartialReflect::debug` as a `TokenStream`.549///550/// If `Debug` was not registered, returns `None`.551pub fn get_debug_impl(&self) -> Option<proc_macro2::TokenStream> {552match &self.debug {553&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>554fn debug(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {555::core::fmt::Debug::fmt(self, f)556}557}),558&TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=>559fn debug(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {560#impl_fn(self, f)561}562}),563TraitImpl::NotImplemented => None,564}565}566567pub fn get_clone_impl(&self, bevy_reflect_path: &Path) -> Option<proc_macro2::TokenStream> {568match &self.clone {569&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>570#[inline]571fn reflect_clone(&self) -> #FQResult<#bevy_reflect_path::__macro_exports::alloc_utils::Box<dyn #bevy_reflect_path::Reflect>, #bevy_reflect_path::ReflectCloneError> {572#FQResult::Ok(#bevy_reflect_path::__macro_exports::alloc_utils::Box::new(#FQClone::clone(self)))573}574}),575&TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=>576#[inline]577fn reflect_clone(&self) -> #FQResult<#bevy_reflect_path::__macro_exports::alloc_utils::Box<dyn #bevy_reflect_path::Reflect>, #bevy_reflect_path::ReflectCloneError> {578#FQResult::Ok(#bevy_reflect_path::__macro_exports::alloc_utils::Box::new(#impl_fn(self)))579}580}),581TraitImpl::NotImplemented => None,582}583}584585pub fn custom_attributes(&self) -> &CustomAttributes {586&self.custom_attributes587}588589/// The custom where configuration found within `#[reflect(...)]` attributes on this type.590pub fn custom_where(&self) -> Option<&WhereClause> {591self.custom_where.as_ref()592}593594/// Returns true if the `no_field_bounds` attribute was found on this type.595pub fn no_field_bounds(&self) -> bool {596self.no_field_bounds597}598599/// Returns true if the `no_auto_register` attribute was found on this type.600#[cfg(feature = "auto_register")]601pub fn no_auto_register(&self) -> bool {602self.no_auto_register603}604605/// Returns true if the `opaque` attribute was found on this type.606pub fn is_opaque(&self) -> bool {607self.is_opaque608}609}610611/// Adds an identifier to a vector of identifiers if it is not already present.612///613/// Returns an error if the identifier already exists in the list.614fn add_unique_ident(idents: &mut Vec<Ident>, ident: Ident) -> Result<(), syn::Error> {615let ident_name = ident.to_string();616if idents.iter().any(|i| i == ident_name.as_str()) {617return Err(syn::Error::new(ident.span(), CONFLICTING_TYPE_DATA_MESSAGE));618}619620idents.push(ident);621Ok(())622}623624/// Extract a boolean value from an expression.625///626/// The mapper exists so that the caller can conditionally choose to use the given627/// value or supply their own.628fn extract_bool(629value: &Expr,630mut mapper: impl FnMut(&LitBool) -> LitBool,631) -> Result<LitBool, syn::Error> {632match value {633Expr::Lit(syn::ExprLit {634lit: syn::Lit::Bool(lit),635..636}) => Ok(mapper(lit)),637_ => Err(syn::Error::new(value.span(), "Expected a boolean value")),638}639}640641642