
Takes the first two commits from #15375 and adds suggestions from this comment: https://github.com/bevyengine/bevy/pull/15375#issuecomment-2366968300 See #15375 for more reasoning/motivation. ## Rebasing (rerunning) ```rust git switch simpler-lint-fixes git reset --hard main cargo fmt --all -- --unstable-features --config normalize_comments=true,imports_granularity=Crate cargo fmt --all git add --update git commit --message "rustfmt" cargo clippy --workspace --all-targets --all-features --fix cargo fmt --all -- --unstable-features --config normalize_comments=true,imports_granularity=Crate cargo fmt --all git add --update git commit --message "clippy" git cherry-pick e6c0b94f6795222310fb812fa5c4512661fc7887 ```
237 lines
8.0 KiB
Rust
237 lines
8.0 KiB
Rust
//! Contains code related to field attributes for reflected types.
|
|
//!
|
|
//! A field attribute is an attribute which applies to particular field or variant
|
|
//! 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::{
|
|
attribute_parser::terminated_parser, custom_attributes::CustomAttributes,
|
|
REFLECT_ATTRIBUTE_NAME,
|
|
};
|
|
use quote::ToTokens;
|
|
use syn::{parse::ParseStream, Attribute, LitStr, Meta, Token, Type};
|
|
|
|
mod kw {
|
|
syn::custom_keyword!(ignore);
|
|
syn::custom_keyword!(skip_serializing);
|
|
syn::custom_keyword!(default);
|
|
syn::custom_keyword!(remote);
|
|
}
|
|
|
|
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
|
|
///
|
|
/// Note the relationship between serialization and reflection is such that a member must be reflected in order to be serialized.
|
|
/// In boolean logic this is described as: `is_serialized -> is_reflected`, this means we can reflect something without serializing it but not the other way round.
|
|
/// The `is_reflected` predicate is provided as `self.is_active()`
|
|
#[derive(Default, Clone, Copy, PartialEq, Eq)]
|
|
pub(crate) enum ReflectIgnoreBehavior {
|
|
/// Don't ignore, appear to all systems
|
|
#[default]
|
|
None,
|
|
/// Ignore when serializing but not when reflecting
|
|
IgnoreSerialization,
|
|
/// Ignore both when serializing and reflecting
|
|
IgnoreAlways,
|
|
}
|
|
|
|
impl ReflectIgnoreBehavior {
|
|
/// Returns `true` if the ignoring behavior implies member is included in the reflection API, and false otherwise.
|
|
pub fn is_active(self) -> bool {
|
|
match self {
|
|
ReflectIgnoreBehavior::None | ReflectIgnoreBehavior::IgnoreSerialization => true,
|
|
ReflectIgnoreBehavior::IgnoreAlways => false,
|
|
}
|
|
}
|
|
|
|
/// The exact logical opposite of `self.is_active()` returns true iff this member is not part of the reflection API whatsoever (neither serialized nor reflected)
|
|
pub fn is_ignored(self) -> bool {
|
|
!self.is_active()
|
|
}
|
|
}
|
|
|
|
/// Controls how the default value is determined for a field.
|
|
#[derive(Default, Clone)]
|
|
pub(crate) enum DefaultBehavior {
|
|
/// Field is required.
|
|
#[default]
|
|
Required,
|
|
/// Field can be defaulted using `Default::default()`.
|
|
Default,
|
|
/// Field can be created using the given function name.
|
|
///
|
|
/// This assumes the function is in scope, is callable with zero arguments,
|
|
/// and returns the expected type.
|
|
Func(syn::ExprPath),
|
|
}
|
|
|
|
/// 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,
|
|
/// Custom attributes created via `#[reflect(@...)]`.
|
|
pub custom_attributes: CustomAttributes,
|
|
/// For defining the remote wrapper type that should be used in place of the field for reflection logic.
|
|
pub remote: Option<Type>,
|
|
}
|
|
|
|
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();
|
|
|
|
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(Token![@]) {
|
|
self.parse_custom_attribute(input)
|
|
} else 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 if lookahead.peek(kw::remote) {
|
|
self.parse_remote(input)
|
|
} else {
|
|
Err(lookahead.error())
|
|
}
|
|
}
|
|
|
|
/// 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]
|
|
)));
|
|
}
|
|
|
|
input.parse::<kw::ignore>()?;
|
|
self.ignore = ReflectIgnoreBehavior::IgnoreAlways;
|
|
Ok(())
|
|
}
|
|
|
|
/// 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]
|
|
)));
|
|
}
|
|
|
|
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(())
|
|
}
|
|
|
|
/// Parse `@` (custom attribute) attribute.
|
|
///
|
|
/// Examples:
|
|
/// - `#[reflect(@(foo = "bar"))]`
|
|
/// - `#[reflect(@(min = 0.0, max = 1.0))]`
|
|
fn parse_custom_attribute(&mut self, input: ParseStream) -> syn::Result<()> {
|
|
self.custom_attributes.parse_custom_attribute(input)
|
|
}
|
|
|
|
/// Parse `remote` attribute.
|
|
///
|
|
/// Examples:
|
|
/// - `#[reflect(remote = path::to::RemoteType)]`
|
|
fn parse_remote(&mut self, input: ParseStream) -> syn::Result<()> {
|
|
if let Some(remote) = self.remote.as_ref() {
|
|
return Err(input.error(format!(
|
|
"remote type already specified as {}",
|
|
remote.to_token_stream()
|
|
)));
|
|
}
|
|
|
|
input.parse::<kw::remote>()?;
|
|
input.parse::<Token![=]>()?;
|
|
|
|
self.remote = Some(input.parse()?);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns `Some(true)` if the field has a generic remote type.
|
|
///
|
|
/// If the remote type is not generic, returns `Some(false)`.
|
|
///
|
|
/// If the field does not have a remote type, returns `None`.
|
|
pub fn is_remote_generic(&self) -> Option<bool> {
|
|
if let Type::Path(type_path) = self.remote.as_ref()? {
|
|
type_path
|
|
.path
|
|
.segments
|
|
.last()
|
|
.map(|segment| !segment.arguments.is_empty())
|
|
} else {
|
|
Some(false)
|
|
}
|
|
}
|
|
}
|