enumify 0.2.0

A Rust macro that declares an `enum` (and a bunch of `impl From`s) based on a set of types
Documentation
extern crate proc_macro;

use {
    proc_macro::TokenStream,
    proc_macro2::TokenStream as TokenStream2,
    quote::ToTokens,
    std::{iter, mem},
    syn::{
        parse::{Parse, ParseStream},
        parse_macro_input, parse_quote, Attribute, Error, Generics, Ident, Item, ItemEnum,
        ItemImpl, ItemStruct, Meta, Path, Token, Variant, Visibility,
    },
};

enum Enumifiable {
    Struct(ItemStruct),
    Enum(ItemEnum),
}

impl Parse for Enumifiable {
    fn parse(input: ParseStream) -> Result<Self, Error> {
        match input.parse::<Item>()? {
            Item::Struct(item) => Ok(Self::Struct(item)),
            Item::Enum(item) => Ok(Self::Enum(item)),
            _ => Err(input.error("not `struct` or `enum`")),
        }
    }
}

impl Enumifiable {
    fn extract_variant_wrapper(attributes: &mut Vec<Attribute>) -> Option<Result<Path, Error>> {
        let mut wrapper = None;

        for mut attribute in mem::take(attributes) {
            let list = match attribute.meta {
                Meta::List(list) => list,
                Meta::Path(_) | Meta::NameValue(_) => {
                    attributes.push(attribute);
                    continue;
                }
            };

            let ident = match list.path.require_ident() {
                Ok(ident) => ident,
                Err(_) => {
                    attribute.meta = Meta::List(list);
                    attributes.push(attribute);
                    continue;
                }
            };

            if ident != "enumify" {
                attribute.meta = Meta::List(list);
                attributes.push(attribute);
                continue;
            }

            if let Some(result) = &mut wrapper {
                let error = Error::new(
                    ident.span(),
                    "at most one `#[enumify()]` attribute is allowed per
                    type corresponding to a variant",
                );

                match result {
                    Ok(_) => wrapper = Some(Err(error)),
                    Err(previous_error) => previous_error.combine(error),
                }

                continue;
            }

            wrapper = Some(syn::parse2(list.tokens));
        }

        wrapper
    }

    fn construct_enum_variant(&mut self) -> Result<Variant, Error> {
        let (attrs, ident, generics) = match self {
            Enumifiable::Struct(item) => (&mut item.attrs, &item.ident, &item.generics),
            Enumifiable::Enum(item) => (&mut item.attrs, &item.ident, &item.generics),
        };

        match Self::extract_variant_wrapper(attrs) {
            None => Ok(parse_quote! { #ident(#ident #generics) }),
            Some(Ok(path)) => Ok(parse_quote! { #ident(#path<#ident #generics>) }),
            Some(Err(error)) => Err(error),
        }
    }
}

impl From<Enumifiable> for Item {
    fn from(enumifiable: Enumifiable) -> Self {
        match enumifiable {
            Enumifiable::Struct(item) => Self::Struct(item),
            Enumifiable::Enum(item) => Self::Enum(item),
        }
    }
}

struct Enumify {
    enum_attributes: Vec<Attribute>,
    enum_visibility: Visibility,
    enum_token: Token![enum],
    enum_ident: Ident,
    enum_generics: Generics,
    enumifiables: Vec<Enumifiable>,
}

impl Parse for Enumify {
    fn parse(input: ParseStream) -> Result<Self, Error> {
        let enum_attributes = input.call(Attribute::parse_outer)?;
        let enum_visibility = input.parse::<Visibility>()?;
        let enum_token = input.parse::<Token![enum]>()?;
        let enum_ident = input.parse::<Ident>()?;
        let enum_generics = input.parse::<Generics>()?;
        input.parse::<Token![;]>()?;

        let enumifiables = iter::from_fn(|| match input.is_empty() {
            true => None,
            false => Some(input.parse::<Enumifiable>()),
        })
        .collect::<Result<Vec<_>, Error>>()?;

        Ok(Self {
            enum_attributes,
            enum_visibility,
            enum_token,
            enum_ident,
            enum_generics,
            enumifiables,
        })
    }
}

impl Enumify {
    fn construct_enum_item(&mut self) -> (ItemEnum, Vec<Error>) {
        let Self {
            enum_attributes,
            enum_visibility,
            enum_token,
            enum_ident,
            enum_generics,
            enumifiables,
        } = self;

        let mut enum_variants = Vec::new();
        let mut enum_errors = Vec::new();

        for result in enumifiables
            .iter_mut()
            .map(Enumifiable::construct_enum_variant)
        {
            match result {
                Ok(enum_variant) => enum_variants.push(enum_variant),
                Err(error) => enum_errors.push(error),
            }
        }

        let enum_item = parse_quote! {
            #( #enum_attributes )*
            #enum_visibility #enum_token #enum_ident #enum_generics {
                #( #enum_variants ),*
            }
        };

        (enum_item, enum_errors)
    }

    fn construct_impl_items(&self) -> Vec<ItemImpl> {
        let Self {
            enum_ident,
            enum_generics,
            ..
        } = self;

        self.enumifiables
            .iter()
            .map(|enumifiable| {
                let (ident, generics) = match enumifiable {
                    Enumifiable::Struct(item) => (&item.ident, &item.generics),
                    Enumifiable::Enum(item) => (&item.ident, &item.generics),
                };

                parse_quote! {
                    impl #enum_generics ::core::convert::From<#ident #generics> for #enum_ident #enum_generics {
                        fn from(value: #ident #generics) -> Self {
                            #enum_ident::#ident(value.into())
                        }
                    }
                }
            })
            .collect()
    }

    fn generate_tokens(mut self) -> TokenStream2 {
        let (enum_item, enum_errors) = self.construct_enum_item();
        let impl_items = self.construct_impl_items();
        let Self { enumifiables, .. } = self;

        iter::once(Item::Enum(enum_item))
            .chain(enumifiables.into_iter().map(Item::from))
            .chain(impl_items.into_iter().map(Item::Impl))
            .map(Item::into_token_stream)
            .chain(enum_errors.into_iter().map(Error::into_compile_error))
            .collect()
    }
}

#[proc_macro]
pub fn enumify(stream: TokenStream) -> TokenStream {
    TokenStream::from(parse_macro_input!(stream as Enumify).generate_tokens())
}