use crate::{custom_attributes::CustomAttributes, REFLECT_ATTRIBUTE_NAME};
use bevy_macro_utils::terminated_parser;
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!(clone);
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";
pub(crate) const CLONE_ATTR: &str = "clone";
#[derive(Default, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ReflectIgnoreBehavior {
#[default]
None,
IgnoreSerialization,
IgnoreAlways,
}
impl ReflectIgnoreBehavior {
pub fn is_active(self) -> bool {
match self {
ReflectIgnoreBehavior::None | ReflectIgnoreBehavior::IgnoreSerialization => true,
ReflectIgnoreBehavior::IgnoreAlways => false,
}
}
pub fn is_ignored(self) -> bool {
!self.is_active()
}
}
#[derive(Default, Clone)]
pub(crate) enum CloneBehavior {
#[default]
Default,
Trait,
Func(syn::ExprPath),
}
#[derive(Default, Clone)]
pub(crate) enum DefaultBehavior {
#[default]
Required,
Default,
Func(syn::ExprPath),
}
#[derive(Default, Clone)]
pub(crate) struct FieldAttributes {
pub ignore: ReflectIgnoreBehavior,
pub clone: CloneBehavior,
pub default: DefaultBehavior,
pub custom_attributes: CustomAttributes,
pub remote: Option<Type>,
}
impl FieldAttributes {
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) {
return None;
}
let Meta::List(meta) = &attr.meta else {
return Some(syn::Error::new_spanned(attr, "expected meta list"));
};
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)
}
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::clone) {
self.parse_clone(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())
}
}
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(())
}
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(())
}
fn parse_clone(&mut self, input: ParseStream) -> syn::Result<()> {
if !matches!(self.clone, CloneBehavior::Default) {
return Err(input.error(format!("only one of {:?} is allowed", [CLONE_ATTR])));
}
input.parse::<kw::clone>()?;
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
let lit = input.parse::<LitStr>()?;
self.clone = CloneBehavior::Func(lit.parse()?);
} else {
self.clone = CloneBehavior::Trait;
}
Ok(())
}
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(())
}
fn parse_custom_attribute(&mut self, input: ParseStream) -> syn::Result<()> {
self.custom_attributes.parse_custom_attribute(input)
}
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(())
}
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)
}
}
}