From ed83ffc5f3c0a794e04d7eeaa059fb9cf182be30 Mon Sep 17 00:00:00 2001 From: Pantonshire Date: Wed, 19 May 2021 17:54:34 +0100 Subject: [PATCH] Cleanup --- enumscribe/src/lib.rs | 4 +- enumscribe_derive/src/enums.rs | 25 +- enumscribe_derive/src/lib.rs | 406 ++++++++++------------- enumscribe_examples/examples/airports.rs | 12 +- 4 files changed, 189 insertions(+), 258 deletions(-) diff --git a/enumscribe/src/lib.rs b/enumscribe/src/lib.rs index a81278b..57741f8 100644 --- a/enumscribe/src/lib.rs +++ b/enumscribe/src/lib.rs @@ -116,10 +116,10 @@ pub trait TryScribeCowStr { fn try_scribe(&self) -> Option>; } -pub trait Unscribe { +pub trait Unscribe: Sized { fn unscribe(to_unscribe: &str) -> Self; } -pub trait TryUnscribe { +pub trait TryUnscribe: Sized { fn try_unscribe(to_unscribe: &str) -> Option; } diff --git a/enumscribe_derive/src/enums.rs b/enumscribe_derive/src/enums.rs index 3c5ba4c..dbae8f8 100644 --- a/enumscribe_derive/src/enums.rs +++ b/enumscribe_derive/src/enums.rs @@ -42,10 +42,15 @@ pub(crate) enum VariantConstructor { } impl<'a> Variant<'a> { - pub(crate) fn match_variant(&self, named_fn: F, other_fn: G) -> MacroResult> + pub(crate) fn match_variant( + &self, + enum_ident: &Ident, + named_fn: &F, + other_fn: &G + ) -> MacroResult> where - F: Fn(&str) -> MacroResult, - G: Fn(TokenStream) -> MacroResult + F: Fn(&Variant, &Ident, &str) -> MacroResult, + G: Fn(&Variant, &Ident, TokenStream) -> MacroResult { let variant_ident = &self.data.ident; @@ -54,11 +59,11 @@ impl<'a> Variant<'a> { VariantType::Named { name, constructor, .. } => { let pattern = match constructor { - VariantConstructor::None => quote! { #variant_ident }, - VariantConstructor::Paren => quote! { #variant_ident () }, - VariantConstructor::Brace => quote! { #variant_ident {} } + VariantConstructor::None => quote! { #enum_ident::#variant_ident }, + VariantConstructor::Paren => quote! { #enum_ident::#variant_ident() }, + VariantConstructor::Brace => quote! { #enum_ident::#variant_ident{} } }; - Ok(Some((pattern, named_fn(name)?))) + Ok(Some((pattern, named_fn(self, enum_ident, name)?))) } VariantType::Other { field_name } => { @@ -67,10 +72,10 @@ impl<'a> Variant<'a> { None => quote! { __enumscribe_other_inner } }; let pattern = match field_name { - Some(_) => quote! { #variant_ident { #field_name_tokens } }, - None => quote! { #variant_ident (#field_name_tokens) } + Some(_) => quote! { #enum_ident::#variant_ident{#field_name_tokens} }, + None => quote! { #enum_ident::#variant_ident(#field_name_tokens) } }; - Ok(Some((pattern, other_fn(field_name_tokens)?))) + Ok(Some((pattern, other_fn(self, enum_ident, field_name_tokens)?))) } } } diff --git a/enumscribe_derive/src/lib.rs b/enumscribe_derive/src/lib.rs index 5d3e443..46b619c 100644 --- a/enumscribe_derive/src/lib.rs +++ b/enumscribe_derive/src/lib.rs @@ -9,6 +9,9 @@ use syn::{Data, DataEnum, DeriveInput}; use error::{MacroError, MacroResult}; +use crate::enums::Variant; +use proc_macro2::Ident; + mod enums; mod attribute; mod error; @@ -20,6 +23,8 @@ const OTHER: &'static str = "other"; const IGNORE: &'static str = "ignore"; const CASE_INSENSITIVE: &'static str = "case_insensitive"; +type TokenStream2 = proc_macro2::TokenStream; + macro_rules! proc_try { ($x:expr) => { match $x { @@ -29,8 +34,19 @@ macro_rules! proc_try { }; } -#[proc_macro_derive(ScribeStaticStr, attributes(enumscribe))] -pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream { +fn derive_scribe( + input: TokenStream, + trait_ident: TokenStream2, + trait_return_type: TokenStream2, + named_fn: F, + other_fn: G, + ignore_err_fn: E +) -> TokenStream + where + F: Fn(&Variant, &Ident, &str) -> MacroResult, + G: Fn(&Variant, &Ident, TokenStream2) -> MacroResult, + E: Fn(&Variant, &Ident) -> MacroError +{ let input: DeriveInput = syn::parse(input) .expect("failed to parse input"); @@ -38,54 +54,44 @@ pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream { let parsed_enum = proc_try!(enums::parse_enum(enum_data)); let enum_ident = &input.ident; - let enum_idents = iter::repeat(enum_ident); let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len()); let mut match_results = Vec::with_capacity(parsed_enum.variants.len()); for variant in parsed_enum.variants.iter() { - match variant.match_variant( - |name| Ok(quote! { - #name - }), - |_| 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)), - ) { + match variant.match_variant(enum_ident, &named_fn, &other_fn) { Ok(Some((pattern, result))) => { match_patterns.push(pattern); match_results.push(result); - }, - - Ok(None) => return 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 \ - always be successfully converted to a String\n\ - hint: try deriving TryScribeStaticStr instead", - enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, - variant.data.ident.to_string(), - ), variant.span).into(), - + } + Ok(None) => return ignore_err_fn(variant, enum_ident).into(), Err(err) => return err.into() } } (quote! { - impl ::enumscribe::ScribeStaticStr for #enum_ident { - fn scribe(&self) -> &'static str { + impl #trait_ident for #enum_ident { + fn scribe(&self) -> #trait_return_type { match self { - #(#enum_idents::#match_patterns => #match_results,)* + #(#match_patterns => #match_results,)* } } } }).into() } -#[proc_macro_derive(TryScribeStaticStr, attributes(enumscribe))] -pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream { +fn derive_try_scribe( + input: TokenStream, + trait_ident: TokenStream2, + trait_return_type: TokenStream2, + named_fn: F, + other_fn: G, + ignore_result: TokenStream2 +) -> TokenStream + where + F: Fn(&Variant, &Ident, &str) -> MacroResult, + G: Fn(&Variant, &Ident, TokenStream2) -> MacroResult +{ let input: DeriveInput = syn::parse(input) .expect("failed to parse input"); @@ -93,46 +99,33 @@ pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream { let parsed_enum = proc_try!(enums::parse_enum(enum_data)); let enum_ident = &input.ident; - let enum_idents = iter::repeat(enum_ident); let mut ignore_variant = false; let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len()); let mut match_results = Vec::with_capacity(parsed_enum.variants.len()); for variant in parsed_enum.variants.iter() { - match variant.match_variant( - |name| Ok(quote! { - ::std::option::Option::Some(#name) - }), - |_| 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 ScribeCowStr instead", - enum_ident.to_string(), variant.data.ident.to_string(), OTHER - ), variant.span)), - ) { + match variant.match_variant(enum_ident, &named_fn, &other_fn) { Ok(Some((pattern, result))) => { match_patterns.push(pattern); match_results.push(result); - }, - + } Ok(None) => ignore_variant = true, - Err(err) => return err.into() } } let ignore_arm = if ignore_variant { - quote! { _ => ::std::option::Option::None, } + quote! { _ => #ignore_result, } } else { quote! {} }; (quote! { - impl ::enumscribe::TryScribeStaticStr for #enum_ident { - fn try_scribe(&self) -> ::std::option::Option<&'static str> { + impl #trait_ident for #enum_ident { + fn try_scribe(&self) -> #trait_return_type { match self { - #(#enum_idents::#match_patterns => #match_results,)* + #(#match_patterns => #match_results,)* #ignore_arm } } @@ -140,226 +133,161 @@ pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream { }).into() } -#[proc_macro_derive(ScribeString, attributes(enumscribe))] -pub fn derive_scribe_string(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)); +#[proc_macro_derive(ScribeStaticStr, attributes(enumscribe))] +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 \ + always be successfully converted to a String\n\ + hint: try deriving TryScribeStaticStr instead", + enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, + variant.data.ident.to_string(), + ), variant.span) + ) +} - let enum_ident = &input.ident; - let enum_idents = iter::repeat(enum_ident); +#[proc_macro_derive(TryScribeStaticStr, attributes(enumscribe))] +pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream { + derive_try_scribe( + input, - let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len()); - let mut match_results = Vec::with_capacity(parsed_enum.variants.len()); + quote! { ::enumscribe::TryScribeStaticStr }, + quote! { ::std::option::Option<&'static str> }, - for variant in parsed_enum.variants.iter() { - match variant.match_variant( - |name| Ok(quote! { - <_ as ::std::borrow::ToOwned>::to_owned(#name) - }), - |field| Ok(quote! { - <_ as ::std::convert::Into<::std::string::String>>::into(#field) - }), - ) { - Ok(Some((pattern, result))) => { - match_patterns.push(pattern); - match_results.push(result); - }, + |_, _, name| Ok(quote! { + ::std::option::Option::Some(#name) + }), - Ok(None) => return 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 \ - always be successfully converted to a String\n\ - hint: try deriving TryScribeString instead", - enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, - variant.data.ident.to_string(), - ), variant.span).into(), + |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)), - Err(err) => return err.into() - } - } + quote! { ::std::option::Option::None } + ) +} - (quote! { - impl ::enumscribe::ScribeString for #enum_ident { - fn scribe(&self) -> ::std::string::String { - match self { - #(#enum_idents::#match_patterns => #match_results,)* - } - } - } - }).into() +#[proc_macro_derive(ScribeString, attributes(enumscribe))] +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 \ + always be successfully converted to a String\n\ + hint: try deriving TryScribeString instead", + enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, + variant.data.ident.to_string(), + ), variant.span) + ) } #[proc_macro_derive(TryScribeString, attributes(enumscribe))] pub fn derive_try_scribe_string(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 enum_idents = iter::repeat(enum_ident); - - let mut ignore_variant = false; - let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len()); - let mut match_results = Vec::with_capacity(parsed_enum.variants.len()); - - for variant in parsed_enum.variants.iter() { - match variant.match_variant( - |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) - ) - }), - ) { - Ok(Some((pattern, result))) => { - match_patterns.push(pattern); - match_results.push(result); - }, + derive_try_scribe( + input, - Ok(None) => ignore_variant = true, + quote! { ::enumscribe::TryScribeString }, + quote! { ::std::option::Option<::std::string::String> }, - Err(err) => return err.into() - } - } + |_, _, name| Ok(quote! { + ::std::option::Option::Some( + <_ as ::std::borrow::ToOwned>::to_owned(#name) + ) + }), - let ignore_arm = if ignore_variant { - quote! { _ => ::std::option::Option::None, } - } else { - quote! {} - }; + |_, _, field| Ok(quote! { + ::std::option::Option::Some( + <_ as ::std::convert::Into<::std::string::String>>::into(#field) + ) + }), - (quote! { - impl ::enumscribe::TryScribeString for #enum_ident { - fn try_scribe(&self) -> ::std::option::Option<::std::string::String> { - match self { - #(#enum_idents::#match_patterns => #match_results,)* - #ignore_arm - } - } - } - }).into() + quote! { ::std::option::Option::None } + ) } #[proc_macro_derive(ScribeCowStr, attributes(enumscribe))] pub fn derive_scribe_cow_str(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 enum_idents = iter::repeat(enum_ident); - - let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len()); - let mut match_results = Vec::with_capacity(parsed_enum.variants.len()); + derive_scribe( + input, - for variant in parsed_enum.variants.iter() { - match variant.match_variant( - |name| Ok(quote! { - ::std::borrow::Cow::Borrowed(#name) - }), - |field| Ok(quote! { - ::std::borrow::Cow::Owned( - <_ as ::std::convert::Into<::std::string::String>>::into(#field) - ) - }), - ) { - Ok(Some((pattern, result))) => { - match_patterns.push(pattern); - match_results.push(result); - }, - - Ok(None) => return 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 \ - always be successfully converted to a String\n\ - hint: try deriving TryScribeCowStr instead", - enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, - variant.data.ident.to_string(), - ), variant.span).into(), + quote! { ::enumscribe::ScribeCowStr }, + quote! { ::std::borrow::Cow<'static, str> }, - Err(err) => return err.into() - } - } + |_, _, name| Ok(quote! { + ::std::borrow::Cow::Borrowed(#name) + }), - (quote! { - impl ::enumscribe::ScribeCowStr for #enum_ident { - fn scribe(&self) -> ::std::borrow::Cow<'static, str> { - match self { - #(#enum_idents::#match_patterns => #match_results,)* - } - } - } - }).into() + |_, _, 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 \ + always be successfully converted to a String\n\ + hint: try deriving TryScribeCowStr instead", + enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, + variant.data.ident.to_string(), + ), variant.span) + ) } #[proc_macro_derive(TryScribeCowStr, attributes(enumscribe))] pub fn derive_try_scribe_cow_str(input: TokenStream) -> TokenStream { - let input: DeriveInput = syn::parse(input) - .expect("failed to parse input"); + derive_try_scribe( + input, - let enum_data = proc_try!(get_enum_data(&input)); - let parsed_enum = proc_try!(enums::parse_enum(enum_data)); + quote! { ::enumscribe::TryScribeCowStr }, + quote! { ::std::option::Option<::std::borrow::Cow<'static, str>> }, - let enum_ident = &input.ident; - let enum_idents = iter::repeat(enum_ident); - - let mut ignore_variant = false; - let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len()); - let mut match_results = Vec::with_capacity(parsed_enum.variants.len()); + |_, _, name| Ok(quote! { + ::std::option::Option::Some( + ::std::borrow::Cow::Borrowed(#name) + ) + }), - for variant in parsed_enum.variants.iter() { - match variant.match_variant( - |name| Ok(quote! { - ::std::option::Option::Some( - ::std::borrow::Cow::Borrowed(#name) - ) - }), - |field| Ok(quote! { - ::std::option::Option::Some( - ::std::borrow::Cow::Owned( - <_ as ::std::convert::Into<::std::string::String>>::into(#field) - ) + |_, _, field| Ok(quote! { + ::std::option::Option::Some( + ::std::borrow::Cow::Owned( + <_ as ::std::convert::Into<::std::string::String>>::into(#field) ) - }), - ) { - Ok(Some((pattern, result))) => { - match_patterns.push(pattern); - match_results.push(result); - }, + ) + }), - Ok(None) => ignore_variant = true, - - Err(err) => return err.into() - } - } - - let ignore_arm = if ignore_variant { - quote! { _ => ::std::option::Option::None, } - } else { - quote! {} - }; - - (quote! { - impl ::enumscribe::TryScribeCowStr for #enum_ident { - fn try_scribe(&self) -> ::std::option::Option<::std::borrow::Cow<'static, str>> { - match self { - #(#enum_idents::#match_patterns => #match_results,)* - #ignore_arm - } - } - } - }).into() + 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 8f719a8..1070cfd 100644 --- a/enumscribe_examples/examples/airports.rs +++ b/enumscribe_examples/examples/airports.rs @@ -1,8 +1,6 @@ use enumscribe::*; -// #[derive(ScribeString)] - -#[derive(TryScribeStaticStr)] +#[derive(TryScribeCowStr)] enum Airport { #[enumscribe(str = "LHR", case_insensitive)] Heathrow, @@ -12,14 +10,14 @@ 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()); }