• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use proc_macro2::{Span, TokenStream};
2 use quote::{format_ident, quote};
3 use syn::{Data, DeriveInput, Fields, Type};
4 
5 use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
6 
from_repr_inner(ast: &DeriveInput) -> syn::Result<TokenStream>7 pub fn from_repr_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
8     let name = &ast.ident;
9     let gen = &ast.generics;
10     let (impl_generics, ty_generics, where_clause) = gen.split_for_impl();
11     let vis = &ast.vis;
12 
13     let mut discriminant_type: Type = syn::parse("usize".parse().unwrap()).unwrap();
14     if let Some(type_path) = ast
15         .get_type_properties()
16         .ok()
17         .and_then(|tp| tp.enum_repr)
18         .and_then(|repr_ts| syn::parse2::<Type>(repr_ts).ok())
19     {
20         if let Type::Path(path) = type_path.clone() {
21             if let Some(seg) = path.path.segments.last() {
22                 for t in &[
23                     "u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize",
24                 ] {
25                     if seg.ident == t {
26                         discriminant_type = type_path;
27                         break;
28                     }
29                 }
30             }
31         }
32     }
33 
34     if gen.lifetimes().count() > 0 {
35         return Err(syn::Error::new(
36             Span::call_site(),
37             "This macro doesn't support enums with lifetimes. \
38              The resulting enums would be unbounded.",
39         ));
40     }
41 
42     let variants = match &ast.data {
43         Data::Enum(v) => &v.variants,
44         _ => return Err(non_enum_error()),
45     };
46 
47     let mut arms = Vec::new();
48     let mut constant_defs = Vec::new();
49     let mut has_additional_data = false;
50     let mut prev_const_var_ident = None;
51     for variant in variants {
52         if variant.get_variant_properties()?.disabled.is_some() {
53             continue;
54         }
55 
56         let ident = &variant.ident;
57         let params = match &variant.fields {
58             Fields::Unit => quote! {},
59             Fields::Unnamed(fields) => {
60                 has_additional_data = true;
61                 let defaults = ::core::iter::repeat(quote!(::core::default::Default::default()))
62                     .take(fields.unnamed.len());
63                 quote! { (#(#defaults),*) }
64             }
65             Fields::Named(fields) => {
66                 has_additional_data = true;
67                 let fields = fields
68                     .named
69                     .iter()
70                     .map(|field| field.ident.as_ref().unwrap());
71                 quote! { {#(#fields: ::core::default::Default::default()),*} }
72             }
73         };
74 
75         let const_var_str = format!("{}_DISCRIMINANT", variant.ident);
76         let const_var_ident = format_ident!("{}", const_var_str);
77 
78         let const_val_expr = match &variant.discriminant {
79             Some((_, expr)) => quote! { #expr },
80             None => match &prev_const_var_ident {
81                 Some(prev) => quote! { #prev + 1 },
82                 None => quote! { 0 },
83             },
84         };
85 
86         constant_defs.push(quote! {
87             #[allow(non_upper_case_globals)]
88             const #const_var_ident: #discriminant_type = #const_val_expr;
89         });
90         arms.push(quote! {v if v == #const_var_ident => ::core::option::Option::Some(#name::#ident #params)});
91 
92         prev_const_var_ident = Some(const_var_ident);
93     }
94 
95     arms.push(quote! { _ => ::core::option::Option::None });
96 
97     let const_if_possible = if has_additional_data {
98         quote! {}
99     } else {
100         #[rustversion::before(1.46)]
101         fn filter_by_rust_version(_: TokenStream) -> TokenStream {
102             quote! {}
103         }
104 
105         #[rustversion::since(1.46)]
106         fn filter_by_rust_version(s: TokenStream) -> TokenStream {
107             s
108         }
109         filter_by_rust_version(quote! { const })
110     };
111 
112     Ok(quote! {
113         #[allow(clippy::use_self)]
114         impl #impl_generics #name #ty_generics #where_clause {
115             #[doc = "Try to create [Self] from the raw representation"]
116             #[inline]
117             #vis #const_if_possible fn from_repr(discriminant: #discriminant_type) -> Option<#name #ty_generics> {
118                 #(#constant_defs)*
119                 match discriminant {
120                     #(#arms),*
121                 }
122             }
123         }
124     })
125 }
126