bevy/crates/bevy_reflect/derive/src/container_attributes.rs
ickshonpe c73daea341
Replace map + unwrap_or(true) with is_none_or (#17070)
# Objective

Reduce all varieties of `my_maybe.map(|x| x.is_true).unwrap_or(true)`
using `is_none_or`.
2024-12-31 20:17:03 +00:00

575 lines
20 KiB
Rust

//! Contains code related to container attributes for reflected types.
//!
//! A container attribute is an attribute which applies to an entire struct or enum
//! as opposed to a particular field or variant. An example of such an attribute is
//! the derive helper attribute for `Reflect`, which looks like:
//! `#[reflect(PartialEq, Default, ...)]`.
use crate::{
attribute_parser::terminated_parser, custom_attributes::CustomAttributes,
derive_data::ReflectTraitToImpl,
};
use bevy_macro_utils::fq_std::{FQAny, FQOption};
use proc_macro2::{Ident, Span};
use quote::quote_spanned;
use syn::{
ext::IdentExt, parenthesized, parse::ParseStream, spanned::Spanned, 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);
syn::custom_keyword!(opaque);
}
// The "special" trait idents that are used internally for reflection.
// Received via attributes like `#[reflect(PartialEq, Hash, ...)]`
const DEBUG_ATTR: &str = "Debug";
const PARTIAL_EQ_ATTR: &str = "PartialEq";
const HASH_ATTR: &str = "Hash";
// The traits listed below are not considered "special" (i.e. they use the `ReflectMyTrait` syntax)
// but useful to know exist nonetheless
pub(crate) const REFLECT_DEFAULT: &str = "ReflectDefault";
// Attributes for `FromReflect` implementation
const FROM_REFLECT_ATTR: &str = "from_reflect";
// Attributes for `TypePath` implementation
const TYPE_PATH_ATTR: &str = "type_path";
// The error message to show when a trait/type is specified multiple times
const CONFLICTING_TYPE_DATA_MESSAGE: &str = "conflicting type data registration";
/// A marker for trait implementations registered via the `Reflect` derive macro.
#[derive(Clone, Default)]
pub(crate) enum TraitImpl {
/// The trait is not registered as implemented.
#[default]
NotImplemented,
/// The trait is registered as implemented.
Implemented(Span),
/// The trait is registered with a custom function rather than an actual implementation.
Custom(Path, Span),
}
impl TraitImpl {
/// Merges this [`TraitImpl`] with another.
///
/// Update `self` with whichever value is not [`TraitImpl::NotImplemented`].
/// If `other` is [`TraitImpl::NotImplemented`], then `self` is not modified.
/// An error is returned if neither value is [`TraitImpl::NotImplemented`].
pub fn merge(&mut self, other: TraitImpl) -> Result<(), syn::Error> {
match (&self, other) {
(TraitImpl::NotImplemented, value) => {
*self = value;
Ok(())
}
(_, TraitImpl::NotImplemented) => Ok(()),
(_, TraitImpl::Implemented(span) | TraitImpl::Custom(_, span)) => {
Err(syn::Error::new(span, CONFLICTING_TYPE_DATA_MESSAGE))
}
}
}
}
/// A collection of attributes used for deriving `FromReflect`.
#[derive(Clone, Default)]
pub(crate) struct FromReflectAttrs {
auto_derive: Option<LitBool>,
}
impl FromReflectAttrs {
/// Returns true if `FromReflect` should be automatically derived as part of the `Reflect` derive.
pub fn should_auto_derive(&self) -> bool {
self.auto_derive.as_ref().is_none_or(LitBool::value)
}
}
/// A collection of attributes used for deriving `TypePath` via the `Reflect` derive.
///
/// Note that this differs from the attributes used by the `TypePath` derive itself,
/// which look like `[type_path = "my_crate::foo"]`.
/// The attributes used by reflection take the form `#[reflect(type_path = false)]`.
///
/// These attributes should only be used for `TypePath` configuration specific to
/// deriving `Reflect`.
#[derive(Clone, Default)]
pub(crate) struct TypePathAttrs {
auto_derive: Option<LitBool>,
}
impl TypePathAttrs {
/// Returns true if `TypePath` should be automatically derived as part of the `Reflect` derive.
pub fn should_auto_derive(&self) -> bool {
self.auto_derive.as_ref().is_none_or(LitBool::value)
}
}
/// A collection of traits that have been registered for a reflected type.
///
/// This keeps track of a few traits that are utilized internally for reflection
/// (we'll call these traits _special traits_ within this context), but it
/// will also keep track of all registered traits. Traits are registered as part of the
/// `Reflect` derive macro using the helper attribute: `#[reflect(...)]`.
///
/// The list of special traits are as follows:
/// * `Debug`
/// * `Hash`
/// * `PartialEq`
///
/// When registering a trait, there are a few things to keep in mind:
/// * Traits must have a valid `Reflect{}` struct in scope. For example, `Default`
/// needs `bevy_reflect::prelude::ReflectDefault` in scope.
/// * Traits must be single path identifiers. This means you _must_ use `Default`
/// instead of `std::default::Default` (otherwise it will try to register `Reflectstd`!)
/// * A custom function may be supplied in place of an actual implementation
/// for the special traits (but still follows the same single-path identifier
/// rules as normal).
///
/// # Example
///
/// Registering the `Default` implementation:
///
/// ```ignore (bevy_reflect is not accessible from this crate)
/// // Import ReflectDefault so it's accessible by the derive macro
/// use bevy_reflect::prelude::ReflectDefault;
///
/// #[derive(Reflect, Default)]
/// #[reflect(Default)]
/// struct Foo;
/// ```
///
/// Registering the `Hash` implementation:
///
/// ```ignore (bevy_reflect is not accessible from this crate)
/// // `Hash` is a "special trait" and does not need (nor have) a ReflectHash struct
///
/// #[derive(Reflect, Hash)]
/// #[reflect(Hash)]
/// struct Foo;
/// ```
///
/// Registering the `Hash` implementation using a custom function:
///
/// ```ignore (bevy_reflect is not accessible from this crate)
/// // This function acts as our `Hash` implementation and
/// // corresponds to the `Reflect::reflect_hash` method.
/// fn get_hash(foo: &Foo) -> Option<u64> {
/// Some(123)
/// }
///
/// #[derive(Reflect)]
/// // Register the custom `Hash` function
/// #[reflect(Hash(get_hash))]
/// struct Foo;
/// ```
///
/// > __Note:__ Registering a custom function only works for special traits.
#[derive(Default, Clone)]
pub(crate) struct ContainerAttributes {
debug: TraitImpl,
hash: TraitImpl,
partial_eq: TraitImpl,
from_reflect_attrs: FromReflectAttrs,
type_path_attrs: TypePathAttrs,
custom_where: Option<WhereClause>,
no_field_bounds: bool,
custom_attributes: CustomAttributes,
is_opaque: bool,
idents: Vec<Ident>,
}
impl ContainerAttributes {
/// Parse a comma-separated list of container attributes.
///
/// # Example
/// - `Hash, Debug(custom_debug), MyTrait`
pub fn parse_terminated(
&mut self,
input: ParseStream,
trait_: ReflectTraitToImpl,
) -> syn::Result<()> {
terminated_parser(Token![,], |stream| {
self.parse_container_attribute(stream, trait_)
})(input)?;
Ok(())
}
/// 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(
&mut self,
meta: &MetaList,
trait_: ReflectTraitToImpl,
) -> syn::Result<()> {
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,
) -> syn::Result<()> {
let lookahead = input.lookahead1();
if lookahead.peek(Token![@]) {
self.custom_attributes.parse_custom_attribute(input)
} else 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::opaque) {
self.parse_opaque(input)
} 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())
}
}
/// 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 = crate::ident::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 `opaque` attribute.
///
/// Examples:
/// - `#[reflect(opaque)]`
fn parse_opaque(&mut self, input: ParseStream) -> syn::Result<()> {
input.parse::<kw::opaque>()?;
self.is_opaque = true;
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 extracted_bool = 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())
})?;
if let Some(existing) = &self.from_reflect_attrs.auto_derive {
if existing.value() != extracted_bool.value() {
return Err(syn::Error::new(
extracted_bool.span(),
format!("`{FROM_REFLECT_ATTR}` already set to {}", existing.value()),
));
}
} else {
self.from_reflect_attrs.auto_derive = Some(extracted_bool);
}
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 extracted_bool = 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())
})?;
if let Some(existing) = &self.type_path_attrs.auto_derive {
if existing.value() != extracted_bool.value() {
return Err(syn::Error::new(
extracted_bool.span(),
format!("`{TYPE_PATH_ATTR}` already set to {}", existing.value()),
));
}
} else {
self.type_path_attrs.auto_derive = Some(extracted_bool);
}
Ok(())
}
/// Returns true if the given reflected trait name (i.e. `ReflectDefault` for `Default`)
/// is registered for this type.
pub fn contains(&self, name: &str) -> bool {
self.idents.iter().any(|ident| ident == name)
}
/// The list of reflected traits by their reflected ident (i.e. `ReflectDefault` for `Default`).
pub fn idents(&self) -> &[Ident] {
&self.idents
}
/// The `FromReflect` configuration found within `#[reflect(...)]` attributes on this type.
#[expect(
clippy::wrong_self_convention,
reason = "Method returns `FromReflectAttrs`, does not actually convert data."
)]
pub fn from_reflect_attrs(&self) -> &FromReflectAttrs {
&self.from_reflect_attrs
}
/// The `TypePath` configuration found within `#[reflect(...)]` attributes on this type.
pub fn type_path_attrs(&self) -> &TypePathAttrs {
&self.type_path_attrs
}
/// Returns the implementation of `PartialReflect::reflect_hash` as a `TokenStream`.
///
/// If `Hash` was not registered, returns `None`.
pub fn get_hash_impl(&self, bevy_reflect_path: &Path) -> Option<proc_macro2::TokenStream> {
match &self.hash {
&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>
fn reflect_hash(&self) -> #FQOption<u64> {
use ::core::hash::{Hash, Hasher};
let mut hasher = #bevy_reflect_path::utility::reflect_hasher();
Hash::hash(&#FQAny::type_id(self), &mut hasher);
Hash::hash(self, &mut hasher);
#FQOption::Some(Hasher::finish(&hasher))
}
}),
&TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=>
fn reflect_hash(&self) -> #FQOption<u64> {
#FQOption::Some(#impl_fn(self))
}
}),
TraitImpl::NotImplemented => None,
}
}
/// Returns the implementation of `PartialReflect::reflect_partial_eq` as a `TokenStream`.
///
/// If `PartialEq` was not registered, returns `None`.
pub fn get_partial_eq_impl(
&self,
bevy_reflect_path: &Path,
) -> Option<proc_macro2::TokenStream> {
match &self.partial_eq {
&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>
fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<bool> {
let value = <dyn #bevy_reflect_path::PartialReflect>::try_downcast_ref::<Self>(value);
if let #FQOption::Some(value) = value {
#FQOption::Some(::core::cmp::PartialEq::eq(self, value))
} else {
#FQOption::Some(false)
}
}
}),
&TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=>
fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<bool> {
#FQOption::Some(#impl_fn(self, value))
}
}),
TraitImpl::NotImplemented => None,
}
}
/// Returns the implementation of `PartialReflect::debug` as a `TokenStream`.
///
/// If `Debug` was not registered, returns `None`.
pub fn get_debug_impl(&self) -> Option<proc_macro2::TokenStream> {
match &self.debug {
&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>
fn debug(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
::core::fmt::Debug::fmt(self, f)
}
}),
&TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=>
fn debug(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
#impl_fn(self, f)
}
}),
TraitImpl::NotImplemented => None,
}
}
pub fn custom_attributes(&self) -> &CustomAttributes {
&self.custom_attributes
}
/// 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
}
/// Returns true if the `opaque` attribute was found on this type.
pub fn is_opaque(&self) -> bool {
self.is_opaque
}
}
/// Adds an identifier to a vector of identifiers if it is not already present.
///
/// Returns an error if the identifier already exists in the list.
fn add_unique_ident(idents: &mut Vec<Ident>, ident: Ident) -> Result<(), syn::Error> {
let ident_name = ident.to_string();
if idents.iter().any(|i| i == ident_name.as_str()) {
return Err(syn::Error::new(ident.span(), CONFLICTING_TYPE_DATA_MESSAGE));
}
idents.push(ident);
Ok(())
}
/// Extract a boolean value from an expression.
///
/// The mapper exists so that the caller can conditionally choose to use the given
/// value or supply their own.
fn extract_bool(
value: &Expr,
mut mapper: impl FnMut(&LitBool) -> LitBool,
) -> Result<LitBool, syn::Error> {
match value {
Expr::Lit(syn::ExprLit {
lit: syn::Lit::Bool(lit),
..
}) => Ok(mapper(lit)),
_ => Err(syn::Error::new(value.span(), "Expected a boolean value")),
}
}