derive_custom_enum_traits/
lib.rs

1//! `derive_custom_enum_traits` serves as a collection of (currently one) derivable macros for dealing with enums.
2
3extern crate proc_macro;
4
5use proc_macro::TokenStream;
6use syn::{DeriveInput};
7
8#[proc_macro_derive(DeriveIndex)]
9/// impls a trait for a struct that automatically converts each enum variant into an index and converts an index into an `Option` enum variant.
10
11/// As an example, let's take a simple enum
12///
13/// ```
14/// pub enum Example {
15///     One,
16///     Two,
17///     Three   
18/// }
19/// ```
20///
21/// This derive macro will automatically implement a trait called `custom_enum_traits::EnumIndex` that looks like this:
22///
23/// ```ignore
24/// impl EnumIndex for Example {
25///     fn from_index(idx: usize) -> Option<Example> {
26///         match idx {
27///             0 => Some(Example::One),
28///             1 => Some(Example::Two),
29///             2 => Some(Example::Three),
30///             _ => None
31///         }
32///     }   
33///
34///     fn to_index(&self) -> usize {
35///         match self {
36///             Example::One => 0,
37///             Example::Two => 1,
38///             Example::Three => 2
39///         }
40///     }
41/// }
42///
43pub fn index_enum(input: TokenStream) -> TokenStream {
44    let ast = syn::parse_macro_input!(input as DeriveInput);
45    enum_traits_inner::index_enum_inner(&ast)
46        .unwrap_or_else(syn::Error::into_compile_error)
47        .into()
48}
49
50mod enum_traits_inner {
51    use proc_macro2::{TokenStream, Span};
52    use syn::{DeriveInput, Data};
53    use quote::quote;
54
55    pub fn index_enum_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
56        let name = &ast.ident;
57        let (impl_generics, ty_generics, where_clause) = &ast.generics.split_for_impl();
58        let variants = match &ast.data {
59            Data::Enum(enum_data) => &enum_data.variants,
60            _ => return Err(syn::Error::new(Span::call_site(), "this macro only supports enums"))
61        };
62        let mut from_idxs = Vec::new();
63        let mut to_idxs = Vec::new();
64        for (count, variant) in variants.iter().enumerate() {
65            if !variant.fields.is_empty() {
66                return Err(syn::Error::new(Span::call_site(), "this macro only works on enum variants without fields"));
67            }
68            from_idxs.push(quote! {#count => Some(#name::#variant)});
69            to_idxs.push(quote! {#name::#variant => #count});
70        }
71        from_idxs.push(quote!(_ => None));
72        Ok(quote!{
73            #[doc = "Converts an enum to and from an index"]
74            impl #impl_generics EnumIndex for #name #ty_generics #where_clause {
75                fn from_index(idx: usize) -> Option<#name #ty_generics> where Self:Sized {
76                    match idx {
77                        #(#from_idxs),*
78                    }
79                }
80
81                fn to_index(&self) -> usize {
82                    match self {
83                        #(#to_idxs),*
84                    }
85                }
86            }
87        })
88    }
89}