enum-values-derive 0.1.0

Exposes enum values via reflection
Documentation
#![doc = include_str!("../README.md")]

use proc_macro::TokenStream;
use syn::{Data, DeriveInput, Expr, ExprLit, Lit, parse_macro_input};

use quote::quote;

/// Generates implementation for the `enum_values::EnumValues` trait on an enum.
#[proc_macro_derive(EnumValues)]
pub fn generate_enum_values_impl(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    let Data::Enum(data_enum) = &input.data else {
        return syn::Error::new_spanned(name, "EnumReflect only works on enums")
            .to_compile_error()
            .into();
    };

    let get_fields_arms = data_enum
        .variants
        .iter()
        .map(|v| {
            let variant_ident = &v.ident;
            let doc = v
                .attrs
                .iter()
                .filter(|a| a.path().is_ident("doc"))
                .filter_map(|a| {
                    let expr = if let Ok(nv) = &a.meta.require_name_value() {
                        &nv.value
                    } else {
                        return None;
                    };
                    if let Expr::Lit(ExprLit { lit, .. }) = expr
                        && let Lit::Str(str) = lit
                    {
                        return Some(str.value().clone());
                    }
                    None
                })
                .collect::<String>();

            let Some(discriminant) = v.discriminant.as_ref() else {
                return syn::Error::new_spanned(name, "No discriminant for enum variant")
                    .to_compile_error();
            };

            let value = if let Expr::Lit(lit) = &discriminant.1 {
                if let Lit::Int(int) = &lit.lit {
                    if let Ok(value) = int.base10_parse::<u16>() {
                        value
                    } else {
                        return syn::Error::new_spanned(name, "Cannot parse int literal as u16")
                            .to_compile_error();
                    }
                } else {
                    return syn::Error::new_spanned(name, "Cannot parse int literal")
                        .to_compile_error();
                }
            } else {
                return syn::Error::new_spanned(name, "Enum discriminant should be a literal")
                    .to_compile_error();
            };

            quote! { enum_values::VariantInfo {
                name: stringify!(#variant_ident),
                value: #value,
                doc: #doc.trim()
            } }
        })
        .collect::<Vec<_>>();

    quote! {
        impl enum_values::EnumValues for #name {
            fn variants() -> impl Iterator<Item = enum_values::VariantInfo> {
                [ #(#get_fields_arms),* ].into_iter()
            }
        }
    }
    .into()
}