extern crate proc_macro;
use inflector::Inflector;
use pmutil::{smart_quote, Quote, ToTokensExt};
use proc_macro2::Span;
use quote::quote;
use syn::punctuated::{Pair, Punctuated};
use syn::spanned::Spanned;
use syn::{
parse, Data, DataEnum, DeriveInput, Field, Fields, Generics, Ident, ImplItem, ItemImpl, Lit,
Meta, MetaNameValue, NestedMeta, Path, Token, Type, TypePath, TypeTuple, WhereClause,
};
#[proc_macro_derive(Is, attributes(is))]
pub fn is(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: DeriveInput = syn::parse(input).expect("failed to parse derive input");
let generics: Generics = input.generics.clone();
let items = match input.data {
Data::Enum(e) => expand(e),
_ => panic!("`Is` can be applied only on enums"),
};
ItemImpl {
attrs: vec![],
defaultness: None,
unsafety: None,
impl_token: Default::default(),
generics: Default::default(),
trait_: None,
self_ty: Box::new(Type::Path(TypePath {
qself: None,
path: Path::from(input.ident),
})),
brace_token: Default::default(),
items,
}
.with_generics(generics)
.dump()
.into()
}
#[derive(Debug)]
struct Input {
name: String,
}
fn expand(input: DataEnum) -> Vec<ImplItem> {
let mut items = vec![];
for v in &input.variants {
let attrs = v
.attrs
.iter()
.filter(|attr| attr.path.is_ident("is"))
.collect::<Vec<_>>();
if attrs.len() > 2 {
panic!("derive(Is) expects no attribute or one attribute")
}
let i = match attrs.into_iter().next() {
None => Input {
name: {
println!("Using default: {}", v.ident.to_string().to_snake_case());
v.ident.to_string().to_snake_case()
},
},
Some(attr) => {
let meta = attr
.parse_meta()
.unwrap_or_else(|err| panic!("failed to parse is({:?}): {}", attr.tokens, err));
let mut input = Input {
name: Default::default(),
};
let mut apply = |v: MetaNameValue| {
assert!(
v.path.is_ident("name"),
"Currently, is() only supports `is(name = 'foo')`"
);
input.name = match v.lit {
Lit::Str(s) => s.value(),
_ => unimplemented!(
"is(): name must be a string li teral but {:?} is provided",
v.lit
),
};
};
match meta {
Meta::NameValue(v) => {
apply(v)
}
Meta::List(l) => {
for meta in l.nested {
match meta {
NestedMeta::Lit(l) => {
unimplemented!("is($literal) where $literal = {:?}", l)
}
NestedMeta::Meta(Meta::NameValue(v)) => apply(v),
_ => unimplemented!("is({})", meta.dump()),
}
}
}
_ => unimplemented!("is({:?})", meta),
}
input
}
};
let name = &*i.name;
{
let name_of_is = Ident::new(&format!("is_{}", name), v.ident.span());
let docs_of_is = format!(
"Returns `true` if `self` is of variant [`{variant}`].\n\n\
[`{variant}`]: #variant.{variant}",
variant = v.ident,
);
items.extend(
Quote::new_call_site()
.quote_with(smart_quote!(
Vars {
docs_of_is,
name_of_is,
Variant: &v.ident
},
{
impl Type {
#[doc = docs_of_is]
#[inline]
pub fn name_of_is(&self) -> bool {
match *self {
Self::Variant { .. } => true,
_ => false,
}
}
}
}
))
.parse::<ItemImpl>()
.items,
);
}
{
let name_of_expect = Ident::new(&format!("expect_{}", name), v.ident.span());
let name_of_take = Ident::new(&name, v.ident.span());
let docs_of_expect = format!(
"Unwraps the value, yielding the content of [`{variant}`].\n\n\
# Panics\n\n\
Panics if the value is not [`{variant}`], with a panic message including \
the content of `self`.\n\n\
[`{variant}`]: #variant.{variant}",
variant = v.ident,
);
let docs_of_take = format!(
"Returns `Some` if `self` is of variant [`{variant}`], and `None` otherwise.\n\n\
[`{variant}`]: #variant.{variant}",
variant = v.ident,
);
match &v.fields {
Fields::Unnamed(fields) => {
let ty = if fields.unnamed.len() == 1 {
let mut ty = fields.unnamed.iter();
let name = ty.next().expect("names len is 1").ty.clone();
Quote::new_call_site()
.quote_with(smart_quote!(Vars { name }, { name }))
.parse()
} else {
Type::Tuple(TypeTuple {
paren_token: Default::default(),
elems: fields
.unnamed
.clone()
.into_pairs()
.map(|pair| {
let handle = |f: Field| {
f.ty
};
match pair {
Pair::Punctuated(v, p) => Pair::Punctuated(handle(v), p),
Pair::End(v) => Pair::End(handle(v)),
}
})
.collect(),
})
};
let fields: Punctuated<Ident, Token![,]> = fields
.unnamed
.clone()
.into_pairs()
.enumerate()
.map(|(i, pair)| {
let handle = |f: Field| {
Ident::new(&format!("v{}", i), f.span())
};
match pair {
Pair::Punctuated(v, p) => Pair::Punctuated(handle(v), p),
Pair::End(v) => Pair::End(handle(v)),
}
})
.collect();
items.extend(
Quote::new_call_site()
.quote_with(smart_quote!(
Vars {
docs_of_expect,
docs_of_take,
name_of_expect,
name_of_take,
Variant: &v.ident,
Type: ty,
v: &fields,
},
{
impl Type {
#[doc = docs_of_expect]
#[inline]
pub fn name_of_expect(self) -> Type
where
Self: ::std::fmt::Debug,
{
match self {
Self::Variant(v) => (v),
_ => panic!("called expect on {:?}", self),
}
}
#[doc = docs_of_take]
#[inline]
pub fn name_of_take(self) -> Option<Type> {
match self {
Self::Variant(v) => Some((v)),
_ => None,
}
}
}
}
))
.parse::<ItemImpl>()
.items,
);
}
_ => {}
}
}
}
items
}
trait ItemImplExt {
fn with_generics(self, generics: Generics) -> Self;
}
impl ItemImplExt for ItemImpl {
fn with_generics(mut self, mut generics: Generics) -> Self {
let need_new_punct = !generics.params.empty_or_trailing();
if need_new_punct {
generics
.params
.push_punct(syn::token::Comma(Span::call_site()));
}
if let Some(t) = generics.lt_token {
self.generics.lt_token = Some(t)
}
if let Some(t) = generics.gt_token {
self.generics.gt_token = Some(t)
}
let ty = self.self_ty;
let mut item: ItemImpl = {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let item = if let Some((ref polarity, ref path, ref for_token)) = self.trait_ {
quote! {
impl #impl_generics #polarity #path #for_token #ty #ty_generics #where_clause {}
}
} else {
quote! {
impl #impl_generics #ty #ty_generics #where_clause {}
}
};
parse(item.dump().into())
.unwrap_or_else(|err| panic!("with_generics failed: {}\n{}", err, item.dump()))
};
item.generics
.params
.extend(self.generics.params.into_pairs());
match self.generics.where_clause {
Some(WhereClause {
ref mut predicates, ..
}) => {
println!("FOO");
predicates.extend(
generics
.where_clause
.into_iter()
.flat_map(|wc| wc.predicates.into_pairs()),
)
}
ref mut opt @ None => *opt = generics.where_clause,
}
ItemImpl {
attrs: self.attrs,
defaultness: self.defaultness,
unsafety: self.unsafety,
impl_token: self.impl_token,
brace_token: self.brace_token,
items: self.items,
..item
}
}
}