use proc_macro2::TokenStream; use quote::quote; use syn::{Data, DeriveInput, Fields}; use crate::helpers::{ non_enum_error, occurrence_error, HasStrumVariantProperties, HasTypeProperties, }; pub fn from_string_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); let variants = match &ast.data { Data::Enum(v) => &v.variants, _ => return Err(non_enum_error()), }; let type_properties = ast.get_type_properties()?; let strum_module_path = type_properties.crate_module_path(); let mut default_kw = None; let mut default = quote! { ::core::result::Result::Err(#strum_module_path::ParseError::VariantNotFound) }; let mut phf_exact_match_arms = Vec::new(); let mut standard_match_arms = Vec::new(); for variant in variants { let ident = &variant.ident; let variant_properties = variant.get_variant_properties()?; if variant_properties.disabled.is_some() { continue; } if let Some(kw) = variant_properties.default { if let Some(fst_kw) = default_kw { return Err(occurrence_error(fst_kw, kw, "default")); } match &variant.fields { Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {} _ => { return Err(syn::Error::new_spanned( variant, "Default only works on newtype structs with a single String field", )) } } default_kw = Some(kw); default = quote! { ::core::result::Result::Ok(#name::#ident(s.into())) }; continue; } let params = match &variant.fields { Fields::Unit => quote! {}, Fields::Unnamed(fields) => { let defaults = ::core::iter::repeat(quote!(Default::default())).take(fields.unnamed.len()); quote! { (#(#defaults),*) } } Fields::Named(fields) => { let fields = fields .named .iter() .map(|field| field.ident.as_ref().unwrap()); quote! { {#(#fields: Default::default()),*} } } }; let is_ascii_case_insensitive = variant_properties .ascii_case_insensitive .unwrap_or(type_properties.ascii_case_insensitive); // If we don't have any custom variants, add the default serialized name. for serialization in variant_properties.get_serializations(type_properties.case_style) { if type_properties.use_phf { phf_exact_match_arms.push(quote! { #serialization => #name::#ident #params, }); if is_ascii_case_insensitive { // Store the lowercase and UPPERCASE variants in the phf map to capture let ser_string = serialization.value(); let lower = syn::LitStr::new(&ser_string.to_ascii_lowercase(), serialization.span()); let upper = syn::LitStr::new(&ser_string.to_ascii_uppercase(), serialization.span()); phf_exact_match_arms.push(quote! { #lower => #name::#ident #params, }); phf_exact_match_arms.push(quote! { #upper => #name::#ident #params, }); standard_match_arms.push(quote! { s if s.eq_ignore_ascii_case(#serialization) => #name::#ident #params, }); } } else { standard_match_arms.push(if !is_ascii_case_insensitive { quote! { #serialization => #name::#ident #params, } } else { quote! { s if s.eq_ignore_ascii_case(#serialization) => #name::#ident #params, } }); } } } let phf_body = if phf_exact_match_arms.is_empty() { quote!() } else { quote! { use #strum_module_path::_private_phf_reexport_for_macro_if_phf_feature as phf; static PHF: phf::Map<&'static str, #name> = phf::phf_map! { #(#phf_exact_match_arms)* }; if let Some(value) = PHF.get(s).cloned() { return ::core::result::Result::Ok(value); } } }; let standard_match_body = if standard_match_arms.is_empty() { default } else { quote! { ::core::result::Result::Ok(match s { #(#standard_match_arms)* _ => return #default, }) } }; let from_str = quote! { #[allow(clippy::use_self)] impl #impl_generics ::core::str::FromStr for #name #ty_generics #where_clause { type Err = #strum_module_path::ParseError; fn from_str(s: &str) -> ::core::result::Result< #name #ty_generics , ::Err> { #phf_body #standard_match_body } } }; let try_from_str = try_from_str( name, &impl_generics, &ty_generics, where_clause, &strum_module_path, ); Ok(quote! { #from_str #try_from_str }) } #[rustversion::before(1.34)] fn try_from_str( _name: &proc_macro2::Ident, _impl_generics: &syn::ImplGenerics, _ty_generics: &syn::TypeGenerics, _where_clause: Option<&syn::WhereClause>, _strum_module_path: &syn::Path, ) -> TokenStream { Default::default() } #[rustversion::since(1.34)] fn try_from_str( name: &proc_macro2::Ident, impl_generics: &syn::ImplGenerics, ty_generics: &syn::TypeGenerics, where_clause: Option<&syn::WhereClause>, strum_module_path: &syn::Path, ) -> TokenStream { quote! { #[allow(clippy::use_self)] impl #impl_generics ::core::convert::TryFrom<&str> for #name #ty_generics #where_clause { type Error = #strum_module_path::ParseError; fn try_from(s: &str) -> ::core::result::Result< #name #ty_generics , >::Error> { ::core::str::FromStr::from_str(s) } } } }