int-enum-impl 0.3.0

A procedural macro for conversion between integer and enum types.
Documentation
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{parse_quote, ItemImpl};
use syn::{
    Attribute, Error, Ident, ItemEnum, ItemFn, Meta, NestedMeta, Path, Result, Token, Variant,
};

pub struct IntType {
    pub ty: Ident,
}

impl Parse for IntType {
    fn parse(input: ParseStream) -> Result<Self> {
        let ty: Ident = input.parse()?;

        const VALID_TYPES: &[&str] = &[
            "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize",
        ];

        let valid = VALID_TYPES.iter().any(|t| ty == t);
        if !valid {
            return Err(Error::new_spanned(
                ty,
                "#[int_enum] not supported for this type",
            ));
        }

        Ok(IntType { ty })
    }
}

impl ToTokens for IntType {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        self.ty.to_tokens(tokens);
    }
}

pub fn add_missing_debug(attrs: &mut Vec<Attribute>) {
    let mut missing = true;
    for attr in &attrs[..] {
        if let Ok(meta) = attr.parse_meta() {
            if !meta.path().is_ident("derive") {
                continue;
            }

            if let Meta::List(types) = meta {
                let found = types.nested.iter().any(|m| match m {
                    NestedMeta::Meta(Meta::Path(p)) => p.is_ident("Debug"),
                    _ => false,
                });
                if found {
                    missing = false;
                    break;
                }
            }
        }
    }

    if missing {
        let attr: Attribute = parse_quote!(#[derive(Debug)]);
        attrs.push(attr);
    }
}

pub fn crate_name() -> Ident {
    let crate_ = proc_macro_crate::crate_name("int-enum").expect("int-enum in Cargo.toml");
    Ident::new(&crate_, Span::call_site())
}

pub fn base_impl(
    input: &ItemEnum,
    crate_: &Ident,
    core: &Path,
    enum_: &Ident,
    int_: &Ident,
) -> ItemImpl {
    let to_int_fn = to_int_fn(&core, &enum_, &input.variants);
    let from_int_fn = from_int_fn(&crate_, &core, &enum_, &input.variants);

    parse_quote! {
        impl #crate_::IntEnum for #enum_ {
            type Int = #int_;

            #to_int_fn
            #from_int_fn
        }
    }
}

fn to_int_fn(core: &Path, enum_: &Ident, variants: &Punctuated<Variant, Token![,]>) -> ItemFn {
    let to_int_branches = variants.iter().map(|var| {
        let case = &var.ident;
        let val = match &var.discriminant {
            Some((_, v)) => quote!(#v),
            None => Error::new_spanned(var, "#[int_enum] not supported for non valued variants")
                .to_compile_error(),
        };

        quote!(#enum_::#case => { #val })
    });

    parse_quote! {
        fn to_int(&self) -> Self::Int {
            match *self {
                #(#to_int_branches)*
                _ => { #core::unreachable!() }
            }
        }
    }
}

fn from_int_fn(
    crate_: &Ident,
    core: &Path,
    enum_: &Ident,
    variants: &Punctuated<Variant, Token![,]>,
) -> ItemFn {
    let from_int_branches = variants.iter().map(|var| {
        let case = &var.ident;
        match &var.discriminant {
            Some((_, int_value)) => {
                quote!(#int_value => { #core::result::Result::Ok(#enum_::#case) })
            }
            None => quote!(),
        }
    });

    parse_quote! {
        fn from_int(n: Self::Int) -> #core::result::Result<Self, #crate_::IntEnumError<Self>> {
            match n {
                #(#from_int_branches)*
                _ => { #core::result::Result::Err(#crate_::IntEnumError::__new(n)) }
            }
        }
    }
}