1#![allow(clippy::uninlined_format_args)]
3
4extern crate proc_macro;
5
6use proc_macro::TokenStream;
7use proc_macro2::Span;
8use quote::quote;
9use syn::{parse_macro_input, Expr, Ident};
10
11mod enum_attributes;
12mod parsing;
13use parsing::{get_crate_path, EnumInfo};
14mod utils;
15mod variant_attributes;
16
17#[proc_macro_derive(IntoPrimitive, attributes(num_enum, catch_all))]
37pub fn derive_into_primitive(input: TokenStream) -> TokenStream {
38 let enum_info = parse_macro_input!(input as EnumInfo);
39 let catch_all = enum_info.catch_all();
40 let name = &enum_info.name;
41 let repr = &enum_info.repr;
42
43 let body = if let Some(catch_all_ident) = catch_all {
44 quote! {
45 match enum_value {
46 #name::#catch_all_ident(raw) => raw,
47 rest => unsafe { *(&rest as *const #name as *const Self) }
48 }
49 }
50 } else {
51 quote! { enum_value as Self }
52 };
53
54 TokenStream::from(quote! {
55 impl From<#name> for #repr {
56 #[inline]
57 fn from (enum_value: #name) -> Self
58 {
59 #body
60 }
61 }
62 })
63}
64
65#[proc_macro_derive(FromPrimitive, attributes(num_enum, default, catch_all))]
91pub fn derive_from_primitive(input: TokenStream) -> TokenStream {
92 let enum_info: EnumInfo = parse_macro_input!(input);
93 let krate = get_crate_path(enum_info.crate_path.clone());
94
95 let is_naturally_exhaustive = enum_info.is_naturally_exhaustive();
96 let catch_all_body = match is_naturally_exhaustive {
97 Ok(is_naturally_exhaustive) => {
98 if is_naturally_exhaustive {
99 quote! { unreachable!("exhaustive enum") }
100 } else if let Some(default_ident) = enum_info.default() {
101 quote! { Self::#default_ident }
102 } else if let Some(catch_all_ident) = enum_info.catch_all() {
103 quote! { Self::#catch_all_ident(number) }
104 } else {
105 let span = Span::call_site();
106 let message =
107 "#[derive(num_enum::FromPrimitive)] requires enum to be exhaustive, or a variant marked with `#[default]`, `#[num_enum(default)]`, or `#[num_enum(catch_all)`";
108 return syn::Error::new(span, message).to_compile_error().into();
109 }
110 }
111 Err(err) => {
112 return err.to_compile_error().into();
113 }
114 };
115
116 let EnumInfo {
117 ref name, ref repr, ..
118 } = enum_info;
119
120 let variant_idents: Vec<Ident> = enum_info.variant_idents();
121 let expression_idents: Vec<Vec<Ident>> = enum_info.expression_idents();
122 let variant_expressions: Vec<Vec<Expr>> = enum_info.variant_expressions();
123
124 debug_assert_eq!(variant_idents.len(), variant_expressions.len());
125
126 TokenStream::from(quote! {
127 impl #krate::FromPrimitive for #name {
128 type Primitive = #repr;
129
130 fn from_primitive(number: Self::Primitive) -> Self {
131 #![allow(non_upper_case_globals)]
134 #(
135 #(
136 const #expression_idents: #repr = #variant_expressions;
137 )*
138 )*
139 #[deny(unreachable_patterns)]
140 match number {
141 #(
142 #( #expression_idents )|*
143 => Self::#variant_idents,
144 )*
145 #[allow(unreachable_patterns)]
146 _ => #catch_all_body,
147 }
148 }
149 }
150
151 impl ::core::convert::From<#repr> for #name {
152 #[inline]
153 fn from (
154 number: #repr,
155 ) -> Self {
156 #krate::FromPrimitive::from_primitive(number)
157 }
158 }
159
160 #[doc(hidden)]
161 impl #krate::CannotDeriveBothFromPrimitiveAndTryFromPrimitive for #name {}
162 })
163}
164
165#[proc_macro_derive(TryFromPrimitive, attributes(num_enum))]
191pub fn derive_try_from_primitive(input: TokenStream) -> TokenStream {
192 let enum_info: EnumInfo = parse_macro_input!(input);
193 let krate = get_crate_path(enum_info.crate_path.clone());
194 let EnumInfo {
195 ref name,
196 ref repr,
197 ref error_type_info,
198 ..
199 } = enum_info;
200
201 let variant_idents: Vec<Ident> = enum_info.variant_idents();
202 let expression_idents: Vec<Vec<Ident>> = enum_info.expression_idents();
203 let variant_expressions: Vec<Vec<Expr>> = enum_info.variant_expressions();
204
205 debug_assert_eq!(variant_idents.len(), variant_expressions.len());
206
207 let error_type = &error_type_info.name;
208 let error_constructor = &error_type_info.constructor;
209
210 TokenStream::from(quote! {
211 impl #krate::TryFromPrimitive for #name {
212 type Primitive = #repr;
213 type Error = #error_type;
214
215 const NAME: &'static str = stringify!(#name);
216
217 fn try_from_primitive (
218 number: Self::Primitive,
219 ) -> ::core::result::Result<
220 Self,
221 #error_type
222 > {
223 #![allow(non_upper_case_globals)]
226 #(
227 #(
228 const #expression_idents: #repr = #variant_expressions;
229 )*
230 )*
231 #[deny(unreachable_patterns)]
232 match number {
233 #(
234 #( #expression_idents )|*
235 => ::core::result::Result::Ok(Self::#variant_idents),
236 )*
237 #[allow(unreachable_patterns)]
238 _ => ::core::result::Result::Err(
239 #error_constructor ( number )
240 ),
241 }
242 }
243 }
244
245 impl ::core::convert::TryFrom<#repr> for #name {
246 type Error = #error_type;
247
248 #[inline]
249 fn try_from (
250 number: #repr,
251 ) -> ::core::result::Result<Self, #error_type>
252 {
253 #krate::TryFromPrimitive::try_from_primitive(number)
254 }
255 }
256
257 #[doc(hidden)]
258 impl #krate::CannotDeriveBothFromPrimitiveAndTryFromPrimitive for #name {}
259 })
260}
261
262#[proc_macro_derive(UnsafeFromPrimitive, attributes(num_enum))]
302pub fn derive_unsafe_from_primitive(stream: TokenStream) -> TokenStream {
303 let enum_info = parse_macro_input!(stream as EnumInfo);
304 let krate = get_crate_path(enum_info.crate_path);
305
306 let EnumInfo {
307 ref name, ref repr, ..
308 } = enum_info;
309
310 TokenStream::from(quote! {
311 impl #krate::UnsafeFromPrimitive for #name {
312 type Primitive = #repr;
313
314 unsafe fn unchecked_transmute_from(number: Self::Primitive) -> Self {
315 ::core::mem::transmute(number)
316 }
317 }
318 })
319}
320
321#[proc_macro_derive(Default, attributes(num_enum, default))]
339pub fn derive_default(stream: TokenStream) -> TokenStream {
340 let enum_info = parse_macro_input!(stream as EnumInfo);
341
342 let default_ident = match enum_info.default() {
343 Some(ident) => ident,
344 None => {
345 let span = Span::call_site();
346 let message =
347 "#[derive(num_enum::Default)] requires enum to be exhaustive, or a variant marked with `#[default]` or `#[num_enum(default)]`";
348 return syn::Error::new(span, message).to_compile_error().into();
349 }
350 };
351
352 let EnumInfo { ref name, .. } = enum_info;
353
354 TokenStream::from(quote! {
355 impl ::core::default::Default for #name {
356 #[inline]
357 fn default() -> Self {
358 Self::#default_ident
359 }
360 }
361 })
362}