tyenum 0.2.0

Attribute macro for type enums.
Documentation
/*!
Attribute macro for less verbose creation of enums having different types as variants. Also automatically implements From.

## Usage:
```rs
struct A;
struct B;
struct C;

#[tyenum]
enum Test {
    A,
    BB(B),
    C(C),
}
```

results in:

```rs
enum Test {
    A(A),
    BB(B),
    C(C),
}

impl Test {
    fn is<T: IsTypeOf<Test>>(&self) -> bool {
        T::is_type_of(self)
    }
}
```

and for every variant (in this example `A`):

```rs
impl From<A> for Test {
    fn from(variant: A) -> Self {
        Test::A(variant)
    }
}

impl TryFrom<Test> for A {
    type Error = TryFromTyenumError;
    fn try_from(e: Test) -> Result<Self, TryFromTyenumError> {
        if let Test::A(variant) = e {
            Ok(variant)
        } else {
            Err(TryFromTyenumError)
        }
    }
}

impl IsTypeOf<Test> for A {
    fn is_type_of(e: &Test) -> bool {
        if let Test::A(_) = e {
            true
        } else {
            false
        }
    }
}
```
!*/
#![recursion_limit = "128"]

extern crate proc_macro;

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse2, parse_quote, spanned::Spanned, Error, ItemEnum};

#[doc(hidden)]
#[proc_macro_attribute]
pub fn tyenum(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let item: TokenStream = item.into();
    let mut tyenum: ItemEnum = parse2(item).unwrap();
    let name = &tyenum.ident;
    let mut impls = Vec::new();
    for v in tyenum.variants.iter_mut() {
        let ty: &dyn ToTokens;
        let ident;
        {
            let mut iter = v.fields.iter();
            ty = if let Some(f) = iter.next() {
                if let Some(f) = iter.next() {
                    return Error::new(f.span(), "maximum one field in variants allowed").to_compile_error().into();
                }
                &f.ty
            } else {
                &v.ident
            };
            ident = &v.ident;
            impls.push(quote! {
                impl From<#ty> for #name {
                    fn from(variant: #ty) -> Self {
                        #name::#ident(variant)
                    }
                }

                impl TryFrom<#name> for #ty {
                    type Error = TryFromTyenumError;

                    fn try_from(e: #name) -> Result<Self, TryFromTyenumError> {
                        if let #name::#ident(variant) = e {
                            Ok(variant)
                        } else {
                            Err(TryFromTyenumError)
                        }
                    }
                }

                impl IsTypeOf<#name> for #ty {
                    fn is_type_of(e: &#name) -> bool {
                        if let #name::#ident(_) = e {
                            true
                        } else {
                            false
                        }
                    }
                }
            });
        }
        *v = parse_quote!(#ident(#ty));
    }
    quote!(
        use std::convert::TryFrom;
        use std::fmt;
        use std::error::Error;

        #[derive(Debug, Copy, Clone, PartialEq, Eq)]
        struct TryFromTyenumError;

        impl fmt::Display for TryFromTyenumError {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                write!(f, "inner item is of a different type")
            }
        }

        impl Error for TryFromTyenumError {}

        trait IsTypeOf<E> {
            fn is_type_of(e: &E) -> bool;
        }

        #tyenum

        impl #name {
            fn is<T: IsTypeOf<#name>>(&self) -> bool {
                T::is_type_of(self)
            }
        }

        #(#impls)*
    )
    .into()
}