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 let input = parse_macro_input!(input as DeriveInput);
9
10 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 if variants.is_empty() {
19 return quote! {}.into();
20 }
21
22 let old_enum_name = input.ident;
24 let new_enum_name = quote::format_ident!("{}Unit", old_enum_name);
25
26 let match_arms = variants.iter().map(|variant| {
28 let ident = &variant.ident;
29
30 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 let derive_inner = quote! {
52 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord
53 };
54
55 #[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 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 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 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 let input = parse_macro_input!(input as DeriveInput);
115
116 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 if variants.is_empty() {
125 return quote! {}.into();
126 }
127
128 let old_enum_name = input.ident;
130 let new_enum_name = quote::format_ident!("{}Unit", old_enum_name);
131
132 let match_arms = variants.iter().map(|variant| {
134 let ident = &variant.ident;
135
136 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 let derive_inner = quote! {
158 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord
159 };
160
161 #[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 let new_enum = {
174 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 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 quote! {
224 #new_enum
225 #new_enum_impl
226 }
227 .into()
228}