From 867b09a1fed084a1b7730a88fb47a31b2749dd7d Mon Sep 17 00:00:00 2001 From: Pantonshire Date: Thu, 20 May 2021 17:32:15 +0100 Subject: [PATCH] Derive for TryUnscribe --- enumscribe_derive/src/lib.rs | 279 +++++++++++++---------- enumscribe_examples/examples/airports.rs | 16 +- 2 files changed, 162 insertions(+), 133 deletions(-) diff --git a/enumscribe_derive/src/lib.rs b/enumscribe_derive/src/lib.rs index c3e5461..a9e30f2 100644 --- a/enumscribe_derive/src/lib.rs +++ b/enumscribe_derive/src/lib.rs @@ -34,7 +34,7 @@ macro_rules! proc_try { }; } -fn derive_scribe( +fn gen_scribe_impl( input: TokenStream, trait_ident: TokenStream2, trait_return_type: TokenStream2, @@ -76,7 +76,7 @@ fn derive_scribe( }).into() } -fn derive_try_scribe( +fn gen_try_scribe_impl( input: TokenStream, trait_ident: TokenStream2, trait_return_type: TokenStream2, @@ -125,9 +125,133 @@ fn derive_try_scribe( }).into() } +fn gen_unscribe_impl( + input: TokenStream, + trait_ident: TokenStream2, + trait_fn_name: TokenStream2, + trait_return_type: TokenStream2, + named_fn: F, + other_fn: G, + other_missing_fn: E +) -> TokenStream +where + F: Fn(TokenStream2) -> TokenStream2, + G: Fn(TokenStream2) -> TokenStream2, + E: Fn(&Ident) -> MacroResult +{ + let input: DeriveInput = syn::parse(input) + .expect("failed to parse input"); + + let enum_data = proc_try!(get_enum_data(&input)); + let parsed_enum = proc_try!(enums::parse_enum(enum_data)); + + let enum_ident = &input.ident; + + let mut other_arm = None; + let mut case_sensitive_arms = Vec::new(); + let mut case_insensitive_arms = Vec::new(); + + let to_unscribe_ident = quote! { __enumscribe_to_unscribe }; + + for variant in parsed_enum.variants.iter() { + let variant_ident = &variant.data.ident; + + match &variant.v_type { + VariantType::Ignore => (), + + VariantType::Named { name, constructor, case_insensitive } => { + let match_pattern = if *case_insensitive { + let lowercase_name = name.to_lowercase(); + quote! { #lowercase_name } + } else { + quote! { #name } + }; + + let constructor_tokens = constructor.empty(); + let constructed_variant = quote! { #enum_ident::#variant_ident #constructor_tokens }; + let match_result = named_fn(constructed_variant); + + if *case_insensitive { + &mut case_insensitive_arms + } else { + &mut case_sensitive_arms + }.push(quote! { #match_pattern => #match_result }); + } + + VariantType::Other { field_name } => { + let unscribe_value = quote! { <_ as ::std::convert::Into<_>>::into(#to_unscribe_ident) }; + + let constructed_variant = match field_name { + None => quote! { + #enum_ident::#variant_ident(#unscribe_value) + }, + Some(field_name) => quote! { + #enum_ident::#variant_ident { #field_name: #unscribe_value } + } + }; + + let match_result = other_fn(constructed_variant); + + other_arm = Some(quote! { _ => #match_result }) + } + } + } + + let other_arm = match other_arm { + Some(other_arm) => other_arm, + None => proc_try!(other_missing_fn(enum_ident)) + }; + + let case_insensitive_match = if case_insensitive_arms.is_empty() { + None + } else { + Some(quote! { + let __enumscribe_unscribe_lowercase = #to_unscribe_ident.to_lowercase(); + match __enumscribe_unscribe_lowercase.as_str() { + #(#case_insensitive_arms,)* + #other_arm, + } + }) + }; + + let main_match = match (case_sensitive_arms.is_empty(), case_insensitive_match) { + (true, None) => quote! { + match #to_unscribe_ident { + #other_arm, + } + }, + + (false, None) => quote! { + match #to_unscribe_ident { + #(#case_sensitive_arms,)* + #other_arm, + } + }, + + (true, Some(case_insensitive_match)) => { + case_insensitive_match + }, + + (false, Some(case_insensitive_match)) => quote! { + match #to_unscribe_ident { + #(#case_sensitive_arms,)* + _ => { #case_insensitive_match }, + } + } + }; + + (quote! { + impl #trait_ident for #enum_ident { + fn #trait_fn_name(#to_unscribe_ident: &str) -> #trait_return_type { + #main_match + } + } + }).into() +} + #[proc_macro_derive(ScribeStaticStr, attributes(enumscribe))] pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream { - derive_scribe( + gen_scribe_impl( input, quote! { ::enumscribe::ScribeStaticStr }, quote! { &'static str }, @@ -151,7 +275,7 @@ pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream { #[proc_macro_derive(TryScribeStaticStr, attributes(enumscribe))] pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream { - derive_try_scribe( + gen_try_scribe_impl( input, quote! { ::enumscribe::TryScribeStaticStr }, quote! { ::std::option::Option<&'static str> }, @@ -170,7 +294,7 @@ pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream { #[proc_macro_derive(ScribeString, attributes(enumscribe))] pub fn derive_scribe_string(input: TokenStream) -> TokenStream { - derive_scribe( + gen_scribe_impl( input, quote! { ::enumscribe::ScribeString }, quote! { ::std::string::String }, @@ -193,7 +317,7 @@ pub fn derive_scribe_string(input: TokenStream) -> TokenStream { #[proc_macro_derive(TryScribeString, attributes(enumscribe))] pub fn derive_try_scribe_string(input: TokenStream) -> TokenStream { - derive_try_scribe( + gen_try_scribe_impl( input, quote! { ::enumscribe::TryScribeString }, quote! { ::std::option::Option<::std::string::String> }, @@ -213,7 +337,7 @@ pub fn derive_try_scribe_string(input: TokenStream) -> TokenStream { #[proc_macro_derive(ScribeCowStr, attributes(enumscribe))] pub fn derive_scribe_cow_str(input: TokenStream) -> TokenStream { - derive_scribe( + gen_scribe_impl( input, quote! { ::enumscribe::ScribeCowStr }, quote! { ::std::borrow::Cow<'static, str> }, @@ -238,7 +362,7 @@ pub fn derive_scribe_cow_str(input: TokenStream) -> TokenStream { #[proc_macro_derive(TryScribeCowStr, attributes(enumscribe))] pub fn derive_try_scribe_cow_str(input: TokenStream) -> TokenStream { - derive_try_scribe( + gen_try_scribe_impl( input, quote! { ::enumscribe::TryScribeCowStr }, quote! { ::std::option::Option<::std::borrow::Cow<'static, str>> }, @@ -260,137 +384,42 @@ pub fn derive_try_scribe_cow_str(input: TokenStream) -> TokenStream { #[proc_macro_derive(Unscribe, attributes(enumscribe))] pub fn derive_unscribe(input: TokenStream) -> TokenStream { - let input: DeriveInput = syn::parse(input) - .expect("failed to parse input"); - - let enum_data = proc_try!(get_enum_data(&input)); - let parsed_enum = proc_try!(enums::parse_enum(enum_data)); - - let enum_ident = &input.ident; - - let mut all_ignored = true; - let mut other_arm = None; - let mut case_sensitive_arms = Vec::new(); - let mut case_insensitive_arms = Vec::new(); - - let to_unscribe_ident = quote! { __enumscribe_to_unscribe }; - - for variant in parsed_enum.variants.iter() { - let variant_ident = &variant.data.ident; - - match &variant.v_type { - VariantType::Ignore => (), - - VariantType::Named { name, constructor, case_insensitive } => { - all_ignored = false; - - let match_pattern = if *case_insensitive { - let lowercase_name = name.to_lowercase(); - quote! { #lowercase_name } - } else { - quote! { #name } - }; - - let constructor_tokens = constructor.empty(); - let match_result = quote! { #enum_ident::#variant_ident #constructor_tokens }; + gen_unscribe_impl( + input, - if *case_insensitive { - &mut case_insensitive_arms - } else { - &mut case_sensitive_arms - }.push(quote! { #match_pattern => #match_result }); - } + quote! { ::enumscribe::Unscribe }, + quote! { unscribe }, + quote! { Self }, - VariantType::Other { field_name } => { - all_ignored = false; + |constructed_named_variant| constructed_named_variant, - let unscribe_value = quote! { <_ as ::std::convert::Into<_>>::into(#to_unscribe_ident) }; + |constructed_other_variant| constructed_other_variant, - let match_result = match field_name { - None => quote! { - #enum_ident::#variant_ident(#unscribe_value) - }, - Some(field_name) => quote! { - #enum_ident::#variant_ident { #field_name: #unscribe_value } - } - }; - - other_arm = Some(quote! { _ => #match_result }) - } - } - } - - if all_ignored { - return MacroError::new(format!( - "cannot derive Unscribe for {} because all variants are marked as {}\n\ - hint: introduce at least one variant that is not marked as {}", - enum_ident.to_string(), IGNORE, IGNORE - ), enum_ident.span()).into() - } - - let other_arm = match other_arm { - Some(other_arm) => other_arm, - None => return MacroError::new(format!( + |enum_ident| Err(MacroError::new(format!( "cannot derive Unscribe for {} because no variant is marked as {}\n\ explanation: since there is no {} variant, it cannot be guaranteed that every string \ can be successfully converted to a variant of {}\n\ hint: either introduce an {} variant, or try deriving TryUnscribe instead", enum_ident.to_string(), OTHER, OTHER, enum_ident.to_string(), OTHER - ), enum_ident.span()).into() - }; - - let case_insensitive_match = if case_insensitive_arms.is_empty() { - None - } else { - Some(quote! { - let __enumscribe_unscribe_lowercase = #to_unscribe_ident.to_lowercase(); - match __enumscribe_unscribe_lowercase.as_str() { - #(#case_insensitive_arms,)* - #other_arm, - } - }) - }; - - let main_match = match (case_sensitive_arms.is_empty(), case_insensitive_match) { - (true, None) => quote! { - match #to_unscribe_ident { - #other_arm, - } - }, - - (false, None) => quote! { - match #to_unscribe_ident { - #(#case_sensitive_arms,)* - #other_arm, - } - }, - - (true, Some(case_insensitive_match)) => { - case_insensitive_match - }, - - (false, Some(case_insensitive_match)) => quote! { - match #to_unscribe_ident { - #(#case_sensitive_arms,)* - _ => { #case_insensitive_match }, - } - } - }; - - (quote! { - impl ::enumscribe::Unscribe for #enum_ident { - fn unscribe(#to_unscribe_ident: &str) -> Self { - #main_match - } - } - }).into() + ), enum_ident.span())) + ) } #[proc_macro_derive(TryUnscribe, attributes(enumscribe))] pub fn derive_try_unscribe(input: TokenStream) -> TokenStream { - //TODO: make sure at least one variant is non-ignore + gen_unscribe_impl( + input, + + quote! { ::enumscribe::TryUnscribe }, + quote! { try_unscribe }, + quote! { ::std::option::Option }, - todo!() + |constructed_named_variant| quote! { ::std::option::Option::Some(#constructed_named_variant) }, + + |constructed_other_variant| quote! { ::std::option::Option::Some(#constructed_other_variant) }, + + |_| Ok(quote! { _ => ::std::option::Option::None }) + ) } fn get_enum_data(input: &DeriveInput) -> MacroResult<&DataEnum> { diff --git a/enumscribe_examples/examples/airports.rs b/enumscribe_examples/examples/airports.rs index 139e25b..16f9cf3 100644 --- a/enumscribe_examples/examples/airports.rs +++ b/enumscribe_examples/examples/airports.rs @@ -1,6 +1,6 @@ use enumscribe::*; -#[derive(TryScribeCowStr, Unscribe, Eq, PartialEq, Debug)] +#[derive(TryScribeCowStr, TryUnscribe, Eq, PartialEq, Debug)] enum Airport { #[enumscribe(str = "LHR")] Heathrow, @@ -10,18 +10,18 @@ enum Airport { Luton {}, #[enumscribe(str = "BHX", case_insensitive, ignore)] BirminghamInternational, - #[enumscribe(other)] - Other(String), + // #[enumscribe(other)] + // Other(String), } fn main() { let luton = Airport::Luton {}; println!("Hello, {:?}!", luton.try_scribe()); - let other = Airport::Other("Dedicated EasyJet-only airport".to_owned()); - println!("Hello, {:?}!", other.try_scribe()); + // let other = Airport::Other("Dedicated EasyJet-only airport".to_owned()); + // println!("Hello, {:?}!", other.try_scribe()); - println!("{:?}", Airport::unscribe("LHR")); - println!("{:?}", Airport::unscribe("lhr")); - println!("{:?}", Airport::unscribe("lgw")); + println!("{:?}", Airport::try_unscribe("LHR")); + println!("{:?}", Airport::try_unscribe("lhr")); + println!("{:?}", Airport::try_unscribe("lgw")); }