use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields};
#[proc_macro_derive(EnumUnit)]
pub fn into_unit_enum(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let variants = if let Data::Enum(data_enum) = input.data {
data_enum.variants
} else {
return quote! { compile_error!("Unsupported structure (enum's only)") }.into();
};
if variants.is_empty() {
return quote! {}.into();
}
let old_enum_name = input.ident;
let new_enum_name = quote::format_ident!("{}Unit", old_enum_name);
let match_arms = variants.iter().map(|variant| {
let ident = &variant.ident;
match &variant.fields {
Fields::Unit => {
quote! {
#old_enum_name::#ident => #new_enum_name::#ident,
}
}
Fields::Unnamed(_) => {
quote! {
#old_enum_name::#ident(..) => #new_enum_name::#ident,
}
}
Fields::Named(_) => {
quote! {
#old_enum_name::#ident { .. } => #new_enum_name::#ident,
}
}
}
});
let derive_inner = quote! {
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord
};
#[cfg(feature = "serde")]
let derive_inner = quote! {
#derive_inner, ::serde::Serialize, ::serde::Deserialize
};
let doc_comment = format!(
"Automatically generated unit-variants of [`{}`].",
old_enum_name
);
#[cfg(not(feature = "bitflags"))]
let new_enum = {
let flag_arms = variants.iter().map(|variant| {
let ident = &variant.ident;
quote! { #ident, }
});
quote! {
#[doc = #doc_comment]
#[derive(#derive_inner)]
pub enum #new_enum_name {
#(#flag_arms)*
}
}
};
#[cfg(feature = "bitflags")]
let new_enum = {
let size = match variants.len() {
1..=8 => quote! { u8 },
9..=16 => quote! { u16 },
17..=32 => quote! { u32 },
33..=64 => quote! { u64 },
65..=128 => quote! { u128 },
_ => return quote! { compile_error!("Enum has too many variants."); }.into(),
};
let flag_arms = variants.iter().enumerate().map(|(i, variant)| {
let ident = &variant.ident;
quote! {
const #ident = 1 << #i;
}
});
quote! {
::bitflags::bitflags! {
#[doc = #doc_comment]
#[derive(#derive_inner)]
pub struct #new_enum_name: #size {
#(#flag_arms)*
}
}
}
};
let doc_comment = format!("The [`{}`] of this [`{}`].", new_enum_name, old_enum_name);
let new_enum_impl = quote! {
impl #old_enum_name {
#[doc = #doc_comment]
pub const fn kind(&self) -> #new_enum_name {
match self {
#(#match_arms)*
}
}
}
impl From<#old_enum_name> for #new_enum_name {
fn from(value: #old_enum_name) -> Self {
value.kind()
}
}
};
quote! {
#new_enum
#new_enum_impl
}
.into()
}