extern crate heck;
extern crate proc_macro;
#[macro_use]
extern crate quote;
extern crate syn;
use heck::SnakeCase;
use proc_macro::TokenStream;
#[proc_macro_derive(is_enum_variant, attributes(is_enum_variant))]
pub fn derive_is_enum_variant(tokens: TokenStream) -> TokenStream {
let source = tokens.to_string();
let ast = syn::parse_derive_input(&source).expect("should parse input tokens into AST");
let expanded = expand_derive_is_enum_variant(&ast);
expanded
.parse()
.expect("should parse expanded output source into tokens")
}
enum PredicateConfig {
None,
Skip,
Name(String),
}
impl PredicateConfig {
fn join(self, meta: &syn::MetaItem) -> Self {
match *meta {
syn::MetaItem::Word(ref ident) if ident.to_string() == "skip" => match self {
PredicateConfig::None | PredicateConfig::Skip => PredicateConfig::Skip,
PredicateConfig::Name(_) => panic!(
"Cannot both `#[is_enum_variant(skip)]` and \
`#[is_enum_variant(name = \"..\")]`"
),
},
syn::MetaItem::NameValue(ref ident, syn::Lit::Str(ref s, _))
if ident.to_string() == "name" =>
{
if !s.chars().all(|c| match c {
'_' | 'a'...'z' | 'A'...'Z' | '0'...'9' => true,
_ => false,
}) {
panic!(
"#[is_enum_variant(name = \"..\")] must be provided \
a valid identifier"
)
}
match self {
PredicateConfig::None => PredicateConfig::Name(s.to_string()),
PredicateConfig::Skip => panic!(
"Cannot both `#[is_enum_variant(skip)]` and \
`#[is_enum_variant(name = \"..\")]`"
),
PredicateConfig::Name(_) => panic!(
"Cannot provide more than one \
`#[is_enum_variant(name = \"..\")]`"
),
}
}
ref otherwise => panic!(
"Unknown item inside `#[is_enum_variant(..)]`: {:?}",
otherwise
),
}
}
}
impl<'a> From<&'a Vec<syn::Attribute>> for PredicateConfig {
fn from(attrs: &'a Vec<syn::Attribute>) -> Self {
let our_attr = attrs.iter().find(|a| a.name() == "is_enum_variant");
our_attr.map_or(PredicateConfig::None, |attr| match attr.value {
syn::MetaItem::List(_, ref metas) => metas
.iter()
.map(|m| match *m {
syn::NestedMetaItem::MetaItem(ref m) => m,
syn::NestedMetaItem::Literal(_) => panic!("Invalid #[is_enum_variant] item"),
})
.fold(PredicateConfig::None, PredicateConfig::join),
_ => panic!(
"#[is_enum_variant] must be used with name/value pairs, like \
#[is_enum_variant(name = \"..\")]"
),
})
}
}
fn expand_derive_is_enum_variant(ast: &syn::DeriveInput) -> quote::Tokens {
let variants = match ast.body {
syn::Body::Struct(_) => panic!("#[derive(is_enum_variant)] can only be used with enums"),
syn::Body::Enum(ref variants) => variants,
};
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let predicates = variants.iter().map(
|&syn::Variant {
ref ident,
ref data,
ref attrs,
..
}| {
let cfg = attrs.into();
if let PredicateConfig::Skip = cfg {
return quote!{};
}
let variant_name = ident.to_string();
let doc = format!("Is this `{}` a `{}`?", name, variant_name);
let predicate_name = if let PredicateConfig::Name(name) = cfg {
name
} else {
let mut name = String::from("is_");
name.push_str(&variant_name.to_snake_case());
name
};
let predicate_name = quote::Ident::new(predicate_name);
let data_tokens = match *data {
syn::VariantData::Struct(..) => quote! { { .. } },
syn::VariantData::Tuple(..) => quote! { (..) },
syn::VariantData::Unit => quote!{},
};
quote! {
#[doc = #doc]
#[inline]
#[allow(unreachable_patterns)]
#[allow(dead_code)]
pub fn #predicate_name(&self) -> bool {
match *self {
#name :: #ident #data_tokens => true,
_ => false,
}
}
}
},
);
quote! {
impl #impl_generics #name #ty_generics #where_clause {
#(
#predicates
)*
}
}
}