Derive for TryUnscribe

rename
Pantonshire 5 years ago
parent 8f7f76d8ba
commit 867b09a1fe

@ -34,7 +34,7 @@ macro_rules! proc_try {
}; };
} }
fn derive_scribe<F, G, E>( fn gen_scribe_impl<F, G, E>(
input: TokenStream, input: TokenStream,
trait_ident: TokenStream2, trait_ident: TokenStream2,
trait_return_type: TokenStream2, trait_return_type: TokenStream2,
@ -76,7 +76,7 @@ fn derive_scribe<F, G, E>(
}).into() }).into()
} }
fn derive_try_scribe<F, G>( fn gen_try_scribe_impl<F, G>(
input: TokenStream, input: TokenStream,
trait_ident: TokenStream2, trait_ident: TokenStream2,
trait_return_type: TokenStream2, trait_return_type: TokenStream2,
@ -125,9 +125,133 @@ fn derive_try_scribe<F, G>(
}).into() }).into()
} }
fn gen_unscribe_impl<F, G, E>(
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<TokenStream2>
{
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))] #[proc_macro_derive(ScribeStaticStr, attributes(enumscribe))]
pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream { pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream {
derive_scribe( gen_scribe_impl(
input, input,
quote! { ::enumscribe::ScribeStaticStr }, quote! { ::enumscribe::ScribeStaticStr },
quote! { &'static str }, quote! { &'static str },
@ -151,7 +275,7 @@ pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream {
#[proc_macro_derive(TryScribeStaticStr, attributes(enumscribe))] #[proc_macro_derive(TryScribeStaticStr, attributes(enumscribe))]
pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream { pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream {
derive_try_scribe( gen_try_scribe_impl(
input, input,
quote! { ::enumscribe::TryScribeStaticStr }, quote! { ::enumscribe::TryScribeStaticStr },
quote! { ::std::option::Option<&'static str> }, 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))] #[proc_macro_derive(ScribeString, attributes(enumscribe))]
pub fn derive_scribe_string(input: TokenStream) -> TokenStream { pub fn derive_scribe_string(input: TokenStream) -> TokenStream {
derive_scribe( gen_scribe_impl(
input, input,
quote! { ::enumscribe::ScribeString }, quote! { ::enumscribe::ScribeString },
quote! { ::std::string::String }, quote! { ::std::string::String },
@ -193,7 +317,7 @@ pub fn derive_scribe_string(input: TokenStream) -> TokenStream {
#[proc_macro_derive(TryScribeString, attributes(enumscribe))] #[proc_macro_derive(TryScribeString, attributes(enumscribe))]
pub fn derive_try_scribe_string(input: TokenStream) -> TokenStream { pub fn derive_try_scribe_string(input: TokenStream) -> TokenStream {
derive_try_scribe( gen_try_scribe_impl(
input, input,
quote! { ::enumscribe::TryScribeString }, quote! { ::enumscribe::TryScribeString },
quote! { ::std::option::Option<::std::string::String> }, 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))] #[proc_macro_derive(ScribeCowStr, attributes(enumscribe))]
pub fn derive_scribe_cow_str(input: TokenStream) -> TokenStream { pub fn derive_scribe_cow_str(input: TokenStream) -> TokenStream {
derive_scribe( gen_scribe_impl(
input, input,
quote! { ::enumscribe::ScribeCowStr }, quote! { ::enumscribe::ScribeCowStr },
quote! { ::std::borrow::Cow<'static, str> }, 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))] #[proc_macro_derive(TryScribeCowStr, attributes(enumscribe))]
pub fn derive_try_scribe_cow_str(input: TokenStream) -> TokenStream { pub fn derive_try_scribe_cow_str(input: TokenStream) -> TokenStream {
derive_try_scribe( gen_try_scribe_impl(
input, input,
quote! { ::enumscribe::TryScribeCowStr }, quote! { ::enumscribe::TryScribeCowStr },
quote! { ::std::option::Option<::std::borrow::Cow<'static, str>> }, 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))] #[proc_macro_derive(Unscribe, attributes(enumscribe))]
pub fn derive_unscribe(input: TokenStream) -> TokenStream { pub fn derive_unscribe(input: TokenStream) -> TokenStream {
let input: DeriveInput = syn::parse(input) gen_unscribe_impl(
.expect("failed to parse input"); 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 };
if *case_insensitive { quote! { ::enumscribe::Unscribe },
&mut case_insensitive_arms quote! { unscribe },
} else { quote! { Self },
&mut case_sensitive_arms
}.push(quote! { #match_pattern => #match_result });
}
VariantType::Other { field_name } => { |constructed_named_variant| constructed_named_variant,
all_ignored = false;
let unscribe_value = quote! { <_ as ::std::convert::Into<_>>::into(#to_unscribe_ident) }; |constructed_other_variant| constructed_other_variant,
let match_result = match field_name { |enum_ident| Err(MacroError::new(format!(
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\ "cannot derive Unscribe for {} because no variant is marked as {}\n\
explanation: since there is no {} variant, it cannot be guaranteed that every string \ explanation: since there is no {} variant, it cannot be guaranteed that every string \
can be successfully converted to a variant of {}\n\ can be successfully converted to a variant of {}\n\
hint: either introduce an {} variant, or try deriving TryUnscribe instead", hint: either introduce an {} variant, or try deriving TryUnscribe instead",
enum_ident.to_string(), OTHER, OTHER, enum_ident.to_string(), OTHER enum_ident.to_string(), OTHER, OTHER, enum_ident.to_string(), OTHER
), enum_ident.span()).into() ), enum_ident.span()))
}; )
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))] #[proc_macro_derive(TryUnscribe, attributes(enumscribe))]
pub fn derive_try_unscribe(input: TokenStream) -> TokenStream { 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<Self> },
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> { fn get_enum_data(input: &DeriveInput) -> MacroResult<&DataEnum> {

@ -1,6 +1,6 @@
use enumscribe::*; use enumscribe::*;
#[derive(TryScribeCowStr, Unscribe, Eq, PartialEq, Debug)] #[derive(TryScribeCowStr, TryUnscribe, Eq, PartialEq, Debug)]
enum Airport { enum Airport {
#[enumscribe(str = "LHR")] #[enumscribe(str = "LHR")]
Heathrow, Heathrow,
@ -10,18 +10,18 @@ enum Airport {
Luton {}, Luton {},
#[enumscribe(str = "BHX", case_insensitive, ignore)] #[enumscribe(str = "BHX", case_insensitive, ignore)]
BirminghamInternational, BirminghamInternational,
#[enumscribe(other)] // #[enumscribe(other)]
Other(String), // Other(String),
} }
fn main() { fn main() {
let luton = Airport::Luton {}; let luton = Airport::Luton {};
println!("Hello, {:?}!", luton.try_scribe()); println!("Hello, {:?}!", luton.try_scribe());
let other = Airport::Other("Dedicated EasyJet-only airport".to_owned()); // let other = Airport::Other("Dedicated EasyJet-only airport".to_owned());
println!("Hello, {:?}!", other.try_scribe()); // println!("Hello, {:?}!", other.try_scribe());
println!("{:?}", Airport::unscribe("LHR")); println!("{:?}", Airport::try_unscribe("LHR"));
println!("{:?}", Airport::unscribe("lhr")); println!("{:?}", Airport::try_unscribe("lhr"));
println!("{:?}", Airport::unscribe("lgw")); println!("{:?}", Airport::try_unscribe("lgw"));
} }

Loading…
Cancel
Save