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