use quote::quote;
use syn::{
GenericParam, Ident, Meta, Path, Type, WherePredicate, punctuated::Punctuated, token::Comma,
};
use crate::common::{
r#type::BoundExceptions,
where_predicates_bool::{
WherePredicates, WherePredicatesOrBool,
create_where_predicates_from_all_generic_parameters,
create_where_predicates_from_bare_field_types, create_where_predicates_from_field_types,
meta_2_where_predicates,
},
};
pub(crate) const BOUND_EXCEPTIONS_CLONE: BoundExceptions = BoundExceptions {
unconditional_types: &["Arc", "Rc", "Weak", "NonNull", "Cow", "Discriminant"],
forwarding_types: &[
"Option",
"Result",
"Box",
"Vec",
"VecDeque",
"LinkedList",
"BTreeMap",
"BTreeSet",
"BinaryHeap",
"HashMap",
"HashSet",
"RefCell",
"Wrapping",
"Reverse",
"Saturating",
],
shared_reference_is_unconditional: true,
};
pub(crate) const BOUND_EXCEPTIONS_COPY: BoundExceptions = BoundExceptions {
unconditional_types: &["Arc", "Rc", "Weak", "NonNull", "Cow", "Discriminant"],
forwarding_types: &["Option", "Result", "Wrapping", "Reverse", "Saturating"],
shared_reference_is_unconditional: true,
};
pub(crate) const BOUND_EXCEPTIONS_DEBUG: BoundExceptions = BoundExceptions {
unconditional_types: &["Weak", "NonNull", "AtomicPtr", "Discriminant"],
forwarding_types: &[
"Option",
"Result",
"Box",
"Vec",
"VecDeque",
"LinkedList",
"BTreeMap",
"BTreeSet",
"BinaryHeap",
"HashMap",
"HashSet",
"Arc",
"Rc",
"RefCell",
"Mutex",
"RwLock",
"Wrapping",
"Reverse",
"Saturating",
],
shared_reference_is_unconditional: false,
};
const FORWARDING_TYPES_COMPARISON: &[&str] = &[
"Option",
"Result",
"Box",
"Vec",
"VecDeque",
"LinkedList",
"BTreeMap",
"BTreeSet",
"Arc",
"Rc",
"RefCell",
"Wrapping",
"Reverse",
"Saturating",
];
pub(crate) const BOUND_EXCEPTIONS_EQUALITY: BoundExceptions = BoundExceptions {
unconditional_types: &["NonNull", "Discriminant"],
forwarding_types: FORWARDING_TYPES_COMPARISON,
shared_reference_is_unconditional: false,
};
pub(crate) const BOUND_EXCEPTIONS_HASH: BoundExceptions = BoundExceptions {
unconditional_types: &["NonNull", "Discriminant"],
forwarding_types: &[
"Option",
"Result",
"Box",
"Vec",
"VecDeque",
"LinkedList",
"BTreeMap",
"BTreeSet",
"Arc",
"Rc",
"Wrapping",
"Reverse",
"Saturating",
],
shared_reference_is_unconditional: false,
};
pub(crate) const BOUND_EXCEPTIONS_ORDER: BoundExceptions = BoundExceptions {
unconditional_types: &["NonNull"],
forwarding_types: FORWARDING_TYPES_COMPARISON,
shared_reference_is_unconditional: false,
};
pub(crate) const BOUND_EXCEPTIONS_DEFAULT: BoundExceptions = BoundExceptions {
unconditional_types: &[
"Option",
"Vec",
"VecDeque",
"LinkedList",
"HashMap",
"HashSet",
"BTreeMap",
"BTreeSet",
"Weak",
],
forwarding_types: &[
"Box",
"Arc",
"Rc",
"Cell",
"RefCell",
"Mutex",
"RwLock",
"Wrapping",
"Reverse",
"Saturating",
],
shared_reference_is_unconditional: false,
};
pub(crate) enum Bound {
Disabled,
Auto,
All,
Custom(WherePredicates),
}
impl Bound {
#[inline]
pub(crate) fn from_meta(meta: &Meta) -> syn::Result<Self> {
debug_assert!(meta.path().is_ident("bound"));
Ok(match meta_2_where_predicates(meta)? {
WherePredicatesOrBool::WherePredicates(where_predicates) => {
Self::Custom(where_predicates)
},
WherePredicatesOrBool::Bool(b) => {
if b {
Self::Auto
} else {
Self::Disabled
}
},
WherePredicatesOrBool::All => Self::All,
})
}
}
impl Bound {
#[inline]
pub(crate) fn into_where_predicates_by_generic_parameters_check_types(
self,
params: &Punctuated<GenericParam, Comma>,
bound_trait: &Path,
types: &[&Type],
self_ident: &Ident,
exceptions: &BoundExceptions,
) -> Punctuated<WherePredicate, Comma> {
match self {
Self::Disabled => Punctuated::new(),
Self::Auto => create_where_predicates_from_field_types(
params,
bound_trait,
types,
self_ident,
exceptions,
),
Self::All => create_where_predicates_from_all_generic_parameters(params, bound_trait),
Self::Custom(where_predicates) => where_predicates,
}
}
#[inline]
pub(crate) fn packed_copy_predicates(
&self,
params: &Punctuated<GenericParam, Comma>,
types: &[&Type],
self_ident: &Ident,
) -> WherePredicates {
let copy_trait: Path = syn::parse2(quote!(::core::marker::Copy)).unwrap();
match self {
Self::Auto => create_where_predicates_from_field_types(
params,
©_trait,
types,
self_ident,
&BOUND_EXCEPTIONS_COPY,
),
Self::All => create_where_predicates_from_all_generic_parameters(params, ©_trait),
Self::Disabled | Self::Custom(_) => Punctuated::new(),
}
}
#[inline]
pub(crate) fn into_where_predicates_by_generic_parameters_check_types_shallow(
self,
params: &Punctuated<GenericParam, Comma>,
bound_trait: &Path,
types: &[&Type],
) -> Punctuated<WherePredicate, Comma> {
match self {
Self::Disabled => Punctuated::new(),
Self::Auto => create_where_predicates_from_bare_field_types(params, bound_trait, types),
Self::All => create_where_predicates_from_all_generic_parameters(params, bound_trait),
Self::Custom(where_predicates) => where_predicates,
}
}
}
#[allow(unused_macros)]
macro_rules! bound_only_type_attribute {
($trait:ident, $build:ident $(, $fallback:ident, $feature:literal)?) => {
#[doc = concat!("The parsed settings of a type-level (or variant-level) `", stringify!($trait), "` attribute.")]
pub(crate) struct TypeAttribute {
pub(crate) bound: crate::common::bound::Bound,
}
#[derive(Debug)]
#[doc = concat!("Parses `", stringify!($trait), "` metas; the `enable_*` switches describe which parameters are allowed at the current position.")]
pub(crate) struct TypeAttributeBuilder {
pub(crate) enable_flag: bool,
pub(crate) enable_bound: bool,
}
impl TypeAttributeBuilder {
#[doc = concat!("Parses one `", stringify!($trait), "` meta into a `TypeAttribute`, rejecting parameters that are not enabled here.")]
pub(crate) fn $build(&self, meta: &syn::Meta) -> syn::Result<TypeAttribute> {
debug_assert!(
meta.path().is_ident(stringify!($trait))
$(|| meta.path().is_ident(stringify!($fallback)))?
);
let mut bound = crate::common::bound::Bound::Auto;
let correct_usage = {
let mut usage = vec![];
if self.enable_flag {
usage.push(stringify!(#[educe($trait)]));
}
if self.enable_bound {
usage.push(stringify!(#[educe($trait(bound(where_predicates)))]));
usage.push(stringify!(#[educe($trait(bound = false))]));
}
usage
};
match meta {
syn::Meta::Path(_) => {
if !self.enable_flag {
return Err(crate::panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage,
));
}
},
syn::Meta::NameValue(_) => {
return Err(crate::panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage,
));
},
syn::Meta::List(list) => {
let result = list.parse_args_with(
syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
)?;
if result.is_empty() && !self.enable_flag {
return Err(crate::panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage,
));
}
let mut bound_is_set = false;
let mut handler = |meta: syn::Meta| -> syn::Result<bool> {
if let Some(ident) = meta.path().get_ident()
&& ident == "bound"
{
if !self.enable_bound {
return Ok(false);
}
let v = crate::common::bound::Bound::from_meta(&meta)?;
if bound_is_set {
return Err(crate::panic::parameter_reset(ident));
}
bound_is_set = true;
bound = v;
return Ok(true);
}
Ok(false)
};
for p in result {
if !handler(p)? {
return Err(crate::panic::attribute_incorrect_format(
meta.path().get_ident().unwrap(),
&correct_usage,
));
}
}
},
}
Ok(TypeAttribute {
bound,
})
}
#[doc = concat!("Scans the `#[educe(...)]` attributes of an item (typically an enum variant) and parses its `", stringify!($trait), "` meta if present.")]
pub(crate) fn build_from_attributes(
&self,
attributes: &[syn::Attribute],
traits: &[crate::Trait],
) -> syn::Result<TypeAttribute> {
let mut output = None;
$(
#[cfg(feature = $feature)]
let mut fallback = None;
)?
for attribute in attributes.iter() {
let path = attribute.path();
if path.is_ident("educe")
&& let syn::Meta::List(list) = &attribute.meta
{
let result = list.parse_args_with(
syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
)?;
for meta in result {
let path = meta.path();
let t = match crate::Trait::from_path(path) {
Some(t) => t,
None => return Err(crate::panic::unsupported_trait(meta.path())),
};
if !traits.contains(&t) {
return Err(crate::panic::trait_not_used(path.get_ident().unwrap()));
}
if t == crate::Trait::$trait {
if output.is_some() {
return Err(crate::panic::reuse_a_trait(
path.get_ident().unwrap(),
));
}
output = Some(self.$build(&meta)?);
}
$(
#[cfg(feature = $feature)]
if t == crate::Trait::$fallback
&& fallback.is_none()
&& let Ok(type_attribute) = self.$build(&meta)
{
fallback = Some(type_attribute);
}
)?
}
}
}
$(
#[cfg(feature = $feature)]
let output = output.or(fallback);
)?
Ok(output.unwrap_or(TypeAttribute {
bound: crate::common::bound::Bound::Auto,
}))
}
}
};
}
#[allow(unused_imports)]
pub(crate) use bound_only_type_attribute;