From 8f7f76d8bae87a77344ed807bcd937ce9ffe1e62 Mon Sep 17 00:00:00 2001 From: Pantonshire Date: Thu, 20 May 2021 13:08:20 +0100 Subject: [PATCH] Derive for Unscribe --- enumscribe_derive/src/enums.rs | 25 +-- enumscribe_derive/src/lib.rs | 189 ++++++++++++++++------- enumscribe_examples/examples/airports.rs | 14 +- 3 files changed, 160 insertions(+), 68 deletions(-) diff --git a/enumscribe_derive/src/enums.rs b/enumscribe_derive/src/enums.rs index f166fdd..7f2a99e 100644 --- a/enumscribe_derive/src/enums.rs +++ b/enumscribe_derive/src/enums.rs @@ -31,7 +31,7 @@ pub(crate) enum VariantType<'a> { case_insensitive: bool, }, Other { - field_name: Option<&'a Ident> //use {} for constructor if Some, use () if None + field_name: Option<&'a Ident> }, } @@ -59,11 +59,8 @@ impl<'a> Variant<'a> { VariantType::Ignore => Ok(None), VariantType::Named { name, constructor, .. } => { - let pattern = match constructor { - VariantConstructor::None => quote! { #enum_ident::#variant_ident }, - VariantConstructor::Paren => quote! { #enum_ident::#variant_ident() }, - VariantConstructor::Brace => quote! { #enum_ident::#variant_ident{} } - }; + let constructor_tokens = constructor.empty(); + let pattern = quote! { #enum_ident::#variant_ident #constructor_tokens }; Ok(Some((pattern, named_fn(self, enum_ident, name)?))) } @@ -82,6 +79,16 @@ impl<'a> Variant<'a> { } } +impl VariantConstructor { + pub(crate) fn empty(&self) -> TokenStream2 { + match self { + VariantConstructor::None => quote! {}, + VariantConstructor::Paren => quote! { () }, + VariantConstructor::Brace => quote! { {} } + } + } +} + pub(crate) fn parse_enum(data: &DataEnum) -> MacroResult { let mut variants = Vec::with_capacity(data.variants.len()); let mut taken_names = HashSet::new(); @@ -182,10 +189,10 @@ pub(crate) fn parse_enum(data: &DataEnum) -> MacroResult { } if case_insensitive { - taken_insensitive_names.insert(lowercase_name); + &mut taken_insensitive_names } else { - taken_sensitive_names.insert(lowercase_name); - } + &mut taken_sensitive_names + }.insert(lowercase_name); // Return an error if the variant has any fields if variant.fields.len() != 0 { diff --git a/enumscribe_derive/src/lib.rs b/enumscribe_derive/src/lib.rs index ed87040..c3e5461 100644 --- a/enumscribe_derive/src/lib.rs +++ b/enumscribe_derive/src/lib.rs @@ -4,13 +4,13 @@ use proc_macro::TokenStream; use std::iter; +use proc_macro2::Ident; use quote::quote; use syn::{Data, DataEnum, DeriveInput}; use error::{MacroError, MacroResult}; -use crate::enums::{Variant, VariantType}; -use proc_macro2::Ident; +use crate::enums::{Variant, VariantType, VariantConstructor}; mod enums; mod attribute; @@ -40,7 +40,7 @@ fn derive_scribe( trait_return_type: TokenStream2, named_fn: F, other_fn: G, - ignore_err_fn: E + ignore_err_fn: E, ) -> TokenStream where F: Fn(&Variant, &Ident, &str) -> MacroResult, @@ -82,7 +82,7 @@ fn derive_try_scribe( trait_return_type: TokenStream2, named_fn: F, other_fn: G, - ignore_result: TokenStream2 + ignore_result: TokenStream2, ) -> TokenStream where F: Fn(&Variant, &Ident, &str) -> MacroResult, @@ -129,19 +129,15 @@ fn derive_try_scribe( pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream { derive_scribe( input, - quote! { ::enumscribe::ScribeStaticStr }, quote! { &'static str }, - |_, _, name| Ok(quote! { #name }), - |variant, enum_ident, _| Err(MacroError::new(format!( "cannot derive ScribeStaticStr for {} because the variant {} is marked as {}, so \ there is no &'static str associated with it\n\ hint: try deriving ScribeCowStr instead", enum_ident.to_string(), variant.data.ident.to_string(), OTHER ), variant.span)), - |variant, enum_ident| MacroError::new(format!( "cannot derive ScribeStaticStr for {} because the variant {} is marked as {}\n\ explanation: since {} is ignored, it cannot be guaranteed that the enum can \ @@ -149,7 +145,7 @@ pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream { hint: try deriving TryScribeStaticStr instead", enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, variant.data.ident.to_string(), - ), variant.span) + ), variant.span), ) } @@ -157,22 +153,18 @@ pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream { pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream { derive_try_scribe( input, - quote! { ::enumscribe::TryScribeStaticStr }, quote! { ::std::option::Option<&'static str> }, - |_, _, name| Ok(quote! { ::std::option::Option::Some(#name) }), - |variant, enum_ident, _| Err(MacroError::new(format!( "cannot derive TryScribeStaticStr for {} because the variant {} is marked as {}, so \ there is no &'static str associated with it\n\ hint: try deriving TryScribeCowStr instead", enum_ident.to_string(), variant.data.ident.to_string(), OTHER ), variant.span)), - - quote! { ::std::option::Option::None } + quote! { ::std::option::Option::None }, ) } @@ -180,18 +172,14 @@ pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream { pub fn derive_scribe_string(input: TokenStream) -> TokenStream { derive_scribe( input, - quote! { ::enumscribe::ScribeString }, quote! { ::std::string::String }, - |_, _, name| Ok(quote! { <_ as ::std::borrow::ToOwned>::to_owned(#name) }), - |_, _, field| Ok(quote! { <_ as ::std::convert::Into<::std::string::String>>::into(#field) }), - |variant, enum_ident| MacroError::new(format!( "cannot derive ScribeString for {} because the variant {} is marked as {}\n\ explanation: since {} is ignored, it cannot be guaranteed that the enum can \ @@ -199,7 +187,7 @@ pub fn derive_scribe_string(input: TokenStream) -> TokenStream { hint: try deriving TryScribeString instead", enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, variant.data.ident.to_string(), - ), variant.span) + ), variant.span), ) } @@ -207,23 +195,19 @@ pub fn derive_scribe_string(input: TokenStream) -> TokenStream { pub fn derive_try_scribe_string(input: TokenStream) -> TokenStream { derive_try_scribe( input, - quote! { ::enumscribe::TryScribeString }, quote! { ::std::option::Option<::std::string::String> }, - |_, _, name| Ok(quote! { ::std::option::Option::Some( <_ as ::std::borrow::ToOwned>::to_owned(#name) ) }), - |_, _, field| Ok(quote! { ::std::option::Option::Some( <_ as ::std::convert::Into<::std::string::String>>::into(#field) ) }), - - quote! { ::std::option::Option::None } + quote! { ::std::option::Option::None }, ) } @@ -231,20 +215,16 @@ pub fn derive_try_scribe_string(input: TokenStream) -> TokenStream { pub fn derive_scribe_cow_str(input: TokenStream) -> TokenStream { derive_scribe( input, - quote! { ::enumscribe::ScribeCowStr }, quote! { ::std::borrow::Cow<'static, str> }, - |_, _, name| Ok(quote! { ::std::borrow::Cow::Borrowed(#name) }), - |_, _, field| Ok(quote! { ::std::borrow::Cow::Owned( <_ as ::std::convert::Into<::std::string::String>>::into(#field) ) }), - |variant, enum_ident| MacroError::new(format!( "cannot derive ScribeCowStr for {} because the variant {} is marked as {}\n\ explanation: since {} is ignored, it cannot be guaranteed that the enum can \ @@ -252,7 +232,7 @@ pub fn derive_scribe_cow_str(input: TokenStream) -> TokenStream { hint: try deriving TryScribeCowStr instead", enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, variant.data.ident.to_string(), - ), variant.span) + ), variant.span), ) } @@ -260,16 +240,13 @@ pub fn derive_scribe_cow_str(input: TokenStream) -> TokenStream { pub fn derive_try_scribe_cow_str(input: TokenStream) -> TokenStream { derive_try_scribe( input, - quote! { ::enumscribe::TryScribeCowStr }, quote! { ::std::option::Option<::std::borrow::Cow<'static, str>> }, - |_, _, name| Ok(quote! { ::std::option::Option::Some( ::std::borrow::Cow::Borrowed(#name) ) }), - |_, _, field| Ok(quote! { ::std::option::Option::Some( ::std::borrow::Cow::Owned( @@ -277,38 +254,142 @@ pub fn derive_try_scribe_cow_str(input: TokenStream) -> TokenStream { ) ) }), - - quote! { ::std::option::Option::None } + quote! { ::std::option::Option::None }, ) } #[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 other_arm = None; - // let mut case_sensitive_arms = Vec::new(); - // let mut case_insensitive_arms = Vec::new(); - // - // for variant in parsed_enum.variants.iter() { - // match variant.v_type { - // VariantType::Ignore => {} - // VariantType::Named { .. } => {} - // VariantType::Other { .. } => {} - // } - // } + let input: DeriveInput = syn::parse(input) + .expect("failed to parse input"); - todo!() + 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 }; + + if *case_insensitive { + &mut case_insensitive_arms + } else { + &mut case_sensitive_arms + }.push(quote! { #match_pattern => #match_result }); + } + + VariantType::Other { field_name } => { + all_ignored = false; + + let unscribe_value = quote! { <_ as ::std::convert::Into<_>>::into(#to_unscribe_ident) }; + + 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!( + "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() } #[proc_macro_derive(TryUnscribe, attributes(enumscribe))] pub fn derive_try_unscribe(input: TokenStream) -> TokenStream { + //TODO: make sure at least one variant is non-ignore + todo!() } diff --git a/enumscribe_examples/examples/airports.rs b/enumscribe_examples/examples/airports.rs index 1070cfd..139e25b 100644 --- a/enumscribe_examples/examples/airports.rs +++ b/enumscribe_examples/examples/airports.rs @@ -1,13 +1,13 @@ use enumscribe::*; -#[derive(TryScribeCowStr)] +#[derive(TryScribeCowStr, Unscribe, Eq, PartialEq, Debug)] enum Airport { - #[enumscribe(str = "LHR", case_insensitive)] + #[enumscribe(str = "LHR")] Heathrow, #[enumscribe(str = "LGW", case_insensitive)] - Gatwick, + Gatwick(), #[enumscribe(str = "LTN", case_insensitive)] - Luton, + Luton {}, #[enumscribe(str = "BHX", case_insensitive, ignore)] BirminghamInternational, #[enumscribe(other)] @@ -15,9 +15,13 @@ enum Airport { } fn main() { - let luton = Airport::Luton; + let luton = Airport::Luton {}; println!("Hello, {:?}!", luton.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")); }