1extern crate proc_macro;
39
40use proc_macro::TokenStream;
41use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
42
43use quote::{quote, quote_spanned};
44use syn::spanned::Spanned;
45use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Error, Fields};
46
47macro_rules! derive_error {
48 ($string: tt) => {
49 Error::new(Span::call_site(), $string)
50 .to_compile_error()
51 .into()
52 };
53}
54
55fn match_enum_to_string(name: &Ident, variants: &DataEnum) -> proc_macro2::TokenStream {
56 let mut match_arms = quote! {};
58 for variant in variants.variants.iter() {
59 let variant_ident = &variant.ident;
60 let fields_in_variant = match &variant.fields {
61 Fields::Unnamed(_) => quote_spanned! {variant.span() => (..) },
62 Fields::Unit => quote_spanned! { variant.span() => },
63 Fields::Named(_) => quote_spanned! {variant.span() => {..} },
64 };
65 let variant_string = variant_ident.to_string();
66
67 match_arms.extend(quote! {
68 #name::#variant_ident #fields_in_variant => #variant_string,
69 });
70 }
71 match_arms
72}
73
74#[proc_macro_derive(NamedVariant)]
75pub fn derive_named_variant(input: TokenStream) -> TokenStream {
76 let input = parse_macro_input!(input as DeriveInput);
77
78 let name = &input.ident;
79 let data = &input.data;
80
81 let mut variant_checker_functions;
82
83 match data {
84 Data::Enum(data_enum) => {
85 variant_checker_functions = TokenStream2::new();
86
87 let variant_arms = match_enum_to_string(name, data_enum);
88
89 variant_checker_functions.extend(quote_spanned! { name.span() =>
90 const fn variant_name(&self) -> &'static str {
91 match self {
92 #variant_arms
93 }
94 }
95 });
96 }
97 _ => return derive_error!("NamedVariant is only implemented for enums"),
98 };
99
100 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
101
102 let expanded = quote! {
103 impl #impl_generics #name #ty_generics #where_clause {
104 #variant_checker_functions
105 }
106 };
107
108 TokenStream::from(expanded)
109}