use super::MatchAttribute;
use hashbrown::HashMap;
use syn::{Ident, Meta, Type, parse2, spanned::Spanned};
#[derive(Debug, Clone)]
pub struct Variant {
pub ident: Ident,
default_matcher: Option<MatchAttribute>,
matchers: HashMap<Type, MatchAttribute>,
}
impl Variant {
pub fn get_match_attr_for(&self, type_bind: &Type) -> Option<&MatchAttribute> {
self.matchers.get(type_bind)
}
pub fn get_match_attr_for_or_default(&self, type_bind: &Type) -> Option<&MatchAttribute> {
self.get_match_attr_for(type_bind)
.or(self.default_matcher.as_ref())
}
pub fn get_precedence(&self, type_bind: &Type) -> Option<u16> {
self.get_match_attr_for_or_default(type_bind)
.map(|attr| attr.precedence)
}
}
impl TryFrom<syn::Variant> for Variant {
type Error = syn::Error;
fn try_from(value: syn::Variant) -> syn::Result<Self> {
const ATTR_NAME_MATCHER: &str = "matches";
let mut variant = Self {
ident: value.ident.clone(),
default_matcher: None,
matchers: HashMap::new(),
};
for attr in value
.attrs
.iter()
.filter(|attr| attr.path().is_ident(ATTR_NAME_MATCHER))
{
let attr = match &attr.meta {
Meta::List(meta) => parse2::<MatchAttribute>(meta.tokens.clone())?,
_ => {
return Err(syn::Error::new(
attr.span(),
format_args!(
"`{ATTR_NAME_MATCHER}` should be assigned through as a list (e.g., `#[{ATTR_NAME_MATCHER}(0..42)]`)."
),
));
}
};
match &attr.type_bind {
Some(bind) => {
if variant
.matchers
.insert(bind.clone(), attr.clone())
.is_some()
{
return Err(syn::Error::new(
bind.span(),
format_args!("Multiple matches found for type {bind:?}"),
));
}
}
None => {
if variant.default_matcher.is_some() {
return Err(syn::Error::new(
attr.span(),
"Only one default match can be specified per type",
));
}
variant.default_matcher = Some(attr)
}
}
}
Ok(variant)
}
}
#[cfg(test)]
mod test;