enum_unit/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Data, DeriveInput, Fields};
4
5#[proc_macro_derive(EnumUnit)]
6pub fn into_unit_enum(input: TokenStream) -> TokenStream {
7    // parse the input tokens into a syntax tree
8    let input = parse_macro_input!(input as DeriveInput);
9
10    // check if the input is an enum
11    let variants = if let Data::Enum(data_enum) = input.data {
12        data_enum.variants
13    } else {
14        return quote! { compile_error!("Unsupported structure (enum's only)") }.into();
15    };
16
17    // return nothing if there are no variants
18    if variants.is_empty() {
19        return quote! {}.into();
20    }
21
22    // prepare names for each enumeration
23    let old_enum_name = input.ident;
24    let new_enum_name = quote::format_ident!("{}Unit", old_enum_name);
25
26    // obtain names of every variant
27    let match_arms = variants.iter().map(|variant| {
28        let ident = &variant.ident;
29
30        // Handle tuple and struct variants
31        match &variant.fields {
32            Fields::Unit => {
33                quote! {
34                    #old_enum_name::#ident => #new_enum_name::#ident,
35                }
36            }
37            Fields::Unnamed(_) => {
38                quote! {
39                    #old_enum_name::#ident(..) => #new_enum_name::#ident,
40                }
41            }
42            Fields::Named(_) => {
43                quote! {
44                    #old_enum_name::#ident { .. } => #new_enum_name::#ident,
45                }
46            }
47        }
48    });
49
50    // primitive derivations
51    let derive_inner = quote! {
52        Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord
53    };
54
55    // serde derivations
56    #[cfg(feature = "serde")]
57    let derive_inner = quote! {
58        #derive_inner, ::serde::Serialize, ::serde::Deserialize
59    };
60
61    let doc_comment = format!(
62        "Automatically generated unit-variants of [`{}`].",
63        old_enum_name
64    );
65
66    // basic implementation
67    let new_enum = {
68        let flag_arms = variants.iter().map(|variant| {
69            let ident = &variant.ident;
70            quote! { #ident, }
71        });
72
73        quote! {
74            #[doc = #doc_comment]
75            #[derive(#derive_inner)]
76            pub enum #new_enum_name {
77                #(#flag_arms)*
78            }
79        }
80    };
81
82    let doc_comment = format!("The [`{}`] of this [`{}`].", new_enum_name, old_enum_name);
83
84    // [`kind`] method and [`From`] trait implementation
85    let new_enum_impl = quote! {
86        impl #old_enum_name {
87            #[doc = #doc_comment]
88            pub const fn kind(&self) -> #new_enum_name {
89                match self {
90                    #(#match_arms)*
91                }
92            }
93        }
94
95        impl From<#old_enum_name> for #new_enum_name {
96            fn from(value: #old_enum_name) -> Self {
97                value.kind()
98            }
99        }
100    };
101
102    // putting it all together
103    quote! {
104        #new_enum
105        #new_enum_impl
106    }
107    .into()
108}
109
110#[cfg(feature = "bitflags")]
111#[proc_macro_derive(EnumUnitBit)]
112pub fn into_unit_enum_bf(input: TokenStream) -> TokenStream {
113    // parse the input tokens into a syntax tree
114    let input = parse_macro_input!(input as DeriveInput);
115
116    // check if the input is an enum
117    let variants = if let Data::Enum(data_enum) = input.data {
118        data_enum.variants
119    } else {
120        return quote! { compile_error!("Unsupported structure (enum's only)") }.into();
121    };
122
123    // return nothing if there are no variants
124    if variants.is_empty() {
125        return quote! {}.into();
126    }
127
128    // prepare names for each enumeration
129    let old_enum_name = input.ident;
130    let new_enum_name = quote::format_ident!("{}Unit", old_enum_name);
131
132    // obtain names of every variant
133    let match_arms = variants.iter().map(|variant| {
134        let ident = &variant.ident;
135
136        // Handle tuple and struct variants
137        match &variant.fields {
138            Fields::Unit => {
139                quote! {
140                    #old_enum_name::#ident => #new_enum_name::#ident,
141                }
142            }
143            Fields::Unnamed(_) => {
144                quote! {
145                    #old_enum_name::#ident(..) => #new_enum_name::#ident,
146                }
147            }
148            Fields::Named(_) => {
149                quote! {
150                    #old_enum_name::#ident { .. } => #new_enum_name::#ident,
151                }
152            }
153        }
154    });
155
156    // primitive derivations
157    let derive_inner = quote! {
158        Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord
159    };
160
161    // serde derivations
162    #[cfg(feature = "serde")]
163    let derive_inner = quote! {
164        #derive_inner, ::serde::Serialize, ::serde::Deserialize
165    };
166
167    let doc_comment = format!(
168        "Automatically generated unit-variants of [`{}`].",
169        old_enum_name
170    );
171
172    // bitflags implementation
173    let new_enum = {
174        // size of the bitflag
175        let size = match variants.len() {
176            1..=8 => quote! { u8 },
177            9..=16 => quote! { u16 },
178            17..=32 => quote! { u32 },
179            33..=64 => quote! { u64 },
180            65..=128 => quote! { u128 },
181            _ => return quote! { compile_error!("Enum has too many variants."); }.into(),
182        };
183
184        let flag_arms = variants.iter().enumerate().map(|(i, variant)| {
185            let ident = &variant.ident;
186            quote! {
187                const #ident = 1 << #i;
188            }
189        });
190
191        quote! {
192            ::bitflags::bitflags! {
193                #[doc = #doc_comment]
194                #[derive(#derive_inner)]
195                pub struct #new_enum_name: #size {
196                    #(#flag_arms)*
197                }
198            }
199        }
200    };
201
202    let doc_comment = format!("The [`{}`] of this [`{}`].", new_enum_name, old_enum_name);
203
204    // [`kind`] method and [`From`] trait implementation
205    let new_enum_impl = quote! {
206        impl #old_enum_name {
207            #[doc = #doc_comment]
208            pub const fn kind(&self) -> #new_enum_name {
209                match self {
210                    #(#match_arms)*
211                }
212            }
213        }
214
215        impl From<#old_enum_name> for #new_enum_name {
216            fn from(value: #old_enum_name) -> Self {
217                value.kind()
218            }
219        }
220    };
221
222    // putting it all together
223    quote! {
224        #new_enum
225        #new_enum_impl
226    }
227    .into()
228}