use std::default::Default;
use syn::{spanned::Spanned, Meta, Path, Result};
use crate::{util::MetaListExt, DeriveWhere, Error, Trait};
#[cfg_attr(test, derive(Debug))]
pub enum Skip {
None,
All,
Traits(Vec<SkipGroup>),
}
impl Default for Skip {
fn default() -> Self {
Skip::None
}
}
impl Skip {
pub const SKIP: &'static str = "skip";
pub const SKIP_INNER: &'static str = "skip_inner";
pub fn is_none(&self) -> bool {
matches!(self, Skip::None)
}
pub fn add_attribute(
&mut self,
derive_wheres: &[DeriveWhere],
skip_inner: Option<&Skip>,
meta: &Meta,
) -> Result<()> {
debug_assert!(meta.path().is_ident(Self::SKIP) || meta.path().is_ident(Self::SKIP_INNER));
match meta {
Meta::Path(path) => {
if self.is_none() {
match skip_inner {
Some(Skip::None) | Some(Skip::Traits(..)) | None => {
if derive_wheres
.iter()
.any(|derive_where| derive_where.any_skip())
{
*self = Skip::All;
Ok(())
} else {
Err(Error::option_skip_no_trait(path.span()))
}
}
Some(Skip::All) => Err(Error::option_skip_inner(path.span())),
}
} else {
Err(Error::option_duplicate(
path.span(),
&meta
.path()
.get_ident()
.expect("unexpected skip syntax")
.to_string(),
))
}
}
Meta::List(list) => {
let nested = list.parse_non_empty_nested_metas()?;
let traits = match self {
Skip::None => {
*self = Skip::Traits(Vec::new());
if let Skip::Traits(traits) = self {
traits
} else {
unreachable!("unexpected variant")
}
}
Skip::All => return Err(Error::option_skip_all(list.span())),
Skip::Traits(traits) => traits,
};
for nested_meta in &nested {
if let Meta::Path(path) = nested_meta {
let skip_group = SkipGroup::from_path(path)?;
if traits.contains(&skip_group) {
return Err(Error::option_skip_duplicate(
path.span(),
skip_group.as_str(),
));
} else {
match skip_inner {
Some(skip_inner) if skip_inner.group_skipped(skip_group) => {
return Err(Error::option_skip_inner(path.span()))
}
_ => {
if derive_wheres.iter().any(|derive_where| {
skip_group
.traits()
.any(|trait_| derive_where.contains(trait_))
}) {
traits.push(skip_group)
} else {
return Err(Error::option_skip_trait(path.span()));
}
}
}
}
} else {
return Err(Error::option_syntax(nested_meta.span()));
}
}
Ok(())
}
_ => Err(Error::option_syntax(meta.span())),
}
}
pub fn trait_skipped(&self, trait_: Trait) -> bool {
match self {
Skip::None => false,
Skip::All => SkipGroup::trait_supported(trait_),
Skip::Traits(skip_groups) => skip_groups
.iter()
.any(|skip_group| skip_group.traits().any(|this_trait| this_trait == trait_)),
}
}
pub fn group_skipped(&self, group: SkipGroup) -> bool {
match self {
Skip::None => false,
Skip::All => true,
Skip::Traits(groups) => groups.iter().any(|this_group| *this_group == group),
}
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
#[cfg_attr(test, derive(Debug))]
pub enum SkipGroup {
Debug,
EqHashOrd,
Hash,
#[cfg(feature = "zeroize")]
Zeroize,
}
impl SkipGroup {
fn from_path(path: &Path) -> Result<Self> {
if let Some(ident) = path.get_ident() {
use SkipGroup::*;
match ident.to_string().as_str() {
"Debug" => Ok(Debug),
"EqHashOrd" => Ok(EqHashOrd),
"Hash" => Ok(Hash),
#[cfg(feature = "zeroize")]
"Zeroize" => Ok(Zeroize),
_ => Err(Error::skip_group(path.span())),
}
} else {
Err(Error::skip_group(path.span()))
}
}
const fn as_str(self) -> &'static str {
match self {
Self::Debug => "Debug",
Self::EqHashOrd => "EqHashOrd",
Self::Hash => "Hash",
#[cfg(feature = "zeroize")]
Self::Zeroize => "Zeroize",
}
}
fn traits(self) -> impl Iterator<Item = Trait> {
match self {
Self::Debug => [Some(Trait::Debug), None, None, None, None]
.into_iter()
.flatten(),
Self::EqHashOrd => [
Some(Trait::Eq),
Some(Trait::Hash),
Some(Trait::Ord),
Some(Trait::PartialEq),
Some(Trait::PartialOrd),
]
.into_iter()
.flatten(),
Self::Hash => [Some(Trait::Hash), None, None, None, None]
.into_iter()
.flatten(),
#[cfg(feature = "zeroize")]
Self::Zeroize => [
Some(Trait::Zeroize),
Some(Trait::ZeroizeOnDrop),
None,
None,
None,
]
.into_iter()
.flatten(),
}
}
pub fn trait_supported(trait_: Trait) -> bool {
match trait_ {
Trait::Clone | Trait::Copy | Trait::Default => false,
Trait::Debug
| Trait::Eq
| Trait::Hash
| Trait::Ord
| Trait::PartialEq
| Trait::PartialOrd => true,
#[cfg(feature = "zeroize")]
Trait::Zeroize | Trait::ZeroizeOnDrop => true,
}
}
}