Skip to main content

enum_kinds_macros/
lib.rs

1//! Generate enums with matching variants, but without any of the associated data.
2//! `enum-kinds-traits` crate contains trait definitions used by this crate.
3//! 
4//! In other words, `enum-kinds-macros` automatically generates `enum`s that have
5//! the same set of variants as the original `enum`, but with all the embedded data
6//! stripped away (that is, all the variants are unit variants). Additionally,
7//! `enum-kinds-macros` implements `ToKind` trait for the original `enum` allowing
8//! one to get the associated unit variant.
9//! 
10//! The crates are compatible with stable Rust releases.
11//! 
12//! # Example
13//! 
14//! ```rust,ignore
15//! #[macro_use]
16//! extern crate enum_kinds_macros;
17//! extern crate enum_kinds_traits;
18//! 
19//! use enum_kinds_traits::ToKind;
20//! 
21//! #[derive(EnumKind)]
22//! #[enum_kind_name(SomeEnumKind)]
23//! enum SomeEnum {
24//!     First(String, u32),
25//!     Second(char),
26//!     Third
27//! }
28//! 
29//! #[test]
30//! fn test_enum_kind() {
31//!     let first = SomeEnum::First("Example".to_owned(), 32);
32//!     assert_eq!(first.kind(), SomeEnumKind::First);
33//! }
34//! ```
35//! 
36//! The `#[derive(EnumKind)]` attribute automatically creates another `enum` named
37//! `SomeEnumKind` that contains matching unit variant for each of the variants in
38//! `SomeEnum`. Additionally, `SomeEnum` implements `ToKind` trait that provides the
39//! `kind` method for constructing matching values from `SomeEnumKind`.
40//!
41
42#![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}