| 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 | |
| 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 | |