1#![no_std]
43
44#[macro_use]
45extern crate quote;
46extern crate proc_macro;
47extern crate syn;
48
49use proc_macro::TokenStream;
50use quote::Tokens;
51use syn::{DeriveInput, Meta, NestedMeta, Ident, Data, MetaList, DataEnum, Fields};
52use syn::punctuated::Pair;
53
54#[proc_macro_derive(EnumKind, attributes(enum_kind_name))]
55pub fn enum_kind(input: TokenStream) -> TokenStream {
56 let ast = syn::parse(input).expect("#[derive(EnumKind)] failed to parse input");
57 let name = get_enum_name(&ast)
58 .expect("#[derive(EnumKind)] requires an associated #[enum_kind_name(NAME)] to be specified");
59 let enum_ = create_kind_enum(&ast, &name);
60 let impl_ = create_impl(&ast, &name);
61 let code = quote! {
62 #enum_
63 #impl_
64 };
65 code.into()
66}
67
68fn get_enum_name(definition: &DeriveInput) -> Option<Ident> {
69 for attr in definition.attrs.iter() {
70 match attr.interpret_meta() {
71 Some(Meta::List(MetaList { ident, ref nested, .. })) if ident == "enum_kind_name" => {
72 if let Some(Pair::End(&NestedMeta::Meta(Meta::Word(ident)))) = nested.pairs().next() {
73 return Some(ident.clone());
74 } else {
75 panic!("#[enum_kind_name(NAME)] requires an identifier NAME to be specified");
76 }
77 },
78 _ => continue
79 }
80 }
81 return None;
82}
83
84fn create_kind_enum(definition: &DeriveInput, kind_ident: &Ident) -> Tokens {
85 let variant_idents = match &definition.data {
86 &Data::Enum(DataEnum { ref variants, .. }) => {
87 variants.iter().map(|ref v| v.ident.clone())
88 }
89 _ => {
90 panic!("#[derive(EnumKind)] is only allowed for enums");
91 }
92 };
93 let visibility = &definition.vis;
94 quote! {
95 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
96 #[allow(dead_code)]
97 #[allow(non_snake_case)]
98 #visibility enum #kind_ident {
99 #(#variant_idents),*
100 }
101 }
102}
103
104fn create_impl(definition: &DeriveInput, kind_ident: &Ident) -> Tokens {
105 let (impl_generics, ty_generics, where_clause) = definition.generics.split_for_impl();
106 let ident = &definition.ident;
107
108 let arms = match &definition.data {
109 &Data::Enum(DataEnum { ref variants, .. }) => {
110 variants.iter().map(|ref v| {
111 let variant = &v.ident;
112 match v.fields {
113 Fields::Unit => quote! {
114 &#ident::#variant => #kind_ident::#variant,
115 },
116 Fields::Unnamed(_) => quote! {
117 &#ident::#variant(..) => #kind_ident::#variant,
118 },
119 Fields::Named(_) => quote! {
120 &#ident::#variant{..} => #kind_ident::#variant,
121 }
122 }
123 })
124 }
125 _ => {
126 panic!("#[derive(EnumKind)] is only defined for enums");
127 }
128 };
129
130 quote! {
131 #[automatically_derived]
132 #[allow(unused_attributes)]
133 impl #impl_generics ::enum_kinds_traits::ToKind
134 for #ident #ty_generics #where_clause {
135 type Kind = #kind_ident;
136
137 #[inline]
138 fn kind(&self) -> Self::Kind {
139 match self {
140 #(#arms)*
141 }
142 }
143 }
144 }
145}