Implement container-level `case_insensitive` from issue #1

rename
pantonshire 3 years ago
parent d0da42b9b7
commit 8c078040ac

@ -3,11 +3,11 @@ use std::collections::HashSet;
use proc_macro2::{Ident, Span};
use quote::{quote, ToTokens};
use syn::spanned::Spanned;
use syn::{DataEnum, Fields};
use syn::{DataEnum, Fields, Attribute};
use crate::attribute::{Dict, Value};
use crate::error::{MacroError, MacroResult};
use crate::TokenStream2;
use crate::{TokenStream2, CASE_SENSITIVE};
use crate::{CASE_INSENSITIVE, CRATE_ATTR, IGNORE, NAME, OTHER};
#[derive(Clone)]
@ -94,13 +94,27 @@ impl VariantConstructor {
}
}
pub(crate) fn parse_enum(data: &DataEnum) -> MacroResult<Enum> {
pub(crate) fn parse_enum<'a>(data: &'a DataEnum, attrs: &'a [Attribute]) -> MacroResult<Enum<'a>> {
let mut variants = Vec::with_capacity(data.variants.len());
let mut taken_names = HashSet::new();
let mut taken_insensitive_names = HashSet::new();
let mut taken_sensitive_names = HashSet::new();
let mut other_variant = false;
let global_case_insensitive = {
let mut dict = Dict::from_attrs(CRATE_ATTR, attrs)?;
let (global_case_insensitive, _) = dict.remove_typed_or_default(
CASE_INSENSITIVE,
(false, data.enum_token.span()),
Value::value_bool,
)?;
dict.assert_empty()?;
global_case_insensitive
};
for variant in data.variants.iter() {
let variant_span = variant.span();
@ -108,17 +122,50 @@ pub(crate) fn parse_enum(data: &DataEnum) -> MacroResult<Enum> {
let mut dict = Dict::from_attrs(CRATE_ATTR, &variant.attrs)?;
// Convert the values in the Dict to the appropriate types
let name_opt = dict.remove_typed(NAME, Value::value_string)?;
let (other, other_span) =
dict.remove_typed_or_default(OTHER, (false, variant_span), Value::value_bool)?;
let (ignore, _) =
dict.remove_typed_or_default(IGNORE, (false, variant_span), Value::value_bool)?;
let name_opt = dict.remove_typed(
NAME,
Value::value_string
)?;
let (other, other_span) = dict.remove_typed_or_default(
OTHER,
(false, variant_span),
Value::value_bool
)?;
let (ignore, _) = dict.remove_typed_or_default(
IGNORE,
(false, variant_span),
Value::value_bool
)?;
let (case_insensitive, _) = dict.remove_typed_or_default(
CASE_INSENSITIVE,
(false, variant_span),
Value::value_bool,
)?;
let (case_sensitive, case_sensitive_span) = dict.remove_typed_or_default(
CASE_SENSITIVE,
(false, variant_span),
Value::value_bool
)?;
let case_insensitive = match (case_insensitive, case_sensitive) {
(false, false) => global_case_insensitive,
(false, true) => false,
(true, false) => true,
(true, true) => {
return Err(MacroError::new(
format!(
"variant {} cannot be both case_insensitive and case_sensitive",
variant.ident,
),
case_sensitive_span,
))
}
};
// Return an error if there are any unrecognised keys in the Dict
dict.assert_empty()?;
@ -247,4 +294,4 @@ pub(crate) fn parse_enum(data: &DataEnum) -> MacroResult<Enum> {
}
Ok(Enum { variants })
}
}

@ -10,7 +10,7 @@ use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use syn::{Data, DataEnum, DeriveInput};
use syn::{Data, DataEnum, DeriveInput, Attribute};
use error::{MacroError, MacroResult};
@ -26,6 +26,7 @@ const NAME: &'static str = "str";
const OTHER: &'static str = "other";
const IGNORE: &'static str = "ignore";
const CASE_INSENSITIVE: &'static str = "case_insensitive";
const CASE_SENSITIVE: &'static str = "case_sensitive";
type TokenStream2 = proc_macro2::TokenStream;
@ -53,8 +54,8 @@ where
{
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_data, enum_attrs) = proc_try!(get_enum_data(&input));
let parsed_enum = proc_try!(enums::parse_enum(enum_data, enum_attrs));
let enum_ident = &input.ident;
@ -95,8 +96,8 @@ where
{
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_data, enum_attrs) = proc_try!(get_enum_data(&input));
let parsed_enum = proc_try!(enums::parse_enum(enum_data, enum_attrs));
let enum_ident = &input.ident;
@ -147,8 +148,8 @@ where
{
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_data, enum_attrs) = proc_try!(get_enum_data(&input));
let parsed_enum = proc_try!(enums::parse_enum(enum_data, enum_attrs));
let enum_ident = &input.ident;
@ -645,8 +646,8 @@ pub fn derive_try_unscribe(input: TokenStream) -> TokenStream {
pub fn derive_enum_serialize(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_data, enum_attrs) = proc_try!(get_enum_data(&input));
let parsed_enum = proc_try!(enums::parse_enum(enum_data, enum_attrs));
let enum_ident = &input.ident;
let serializer_ident = quote! { __enumscribe_serializer };
@ -737,8 +738,8 @@ pub fn derive_enum_serialize(input: TokenStream) -> TokenStream {
pub fn derive_enum_deserialize(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_data, enum_attrs) = proc_try!(get_enum_data(&input));
let parsed_enum = proc_try!(enums::parse_enum(enum_data, enum_attrs));
let enum_ident = &input.ident;
@ -792,7 +793,7 @@ pub fn derive_enum_deserialize(input: TokenStream) -> TokenStream {
.into()
}
fn get_enum_data(input: &DeriveInput) -> MacroResult<&DataEnum> {
fn get_enum_data(input: &DeriveInput) -> MacroResult<(&DataEnum, &[Attribute])> {
let enum_data = match &input.data {
Data::Enum(enum_data) => enum_data,
Data::Struct(_) => {
@ -816,5 +817,5 @@ fn get_enum_data(input: &DeriveInput) -> MacroResult<&DataEnum> {
));
}
Ok(enum_data)
Ok((enum_data, &input.attrs))
}

@ -8,7 +8,7 @@ fn test_unscribe() {
#[enumscribe(str = "foo")]
V1,
V2(),
#[enumscribe(str = "BAA")]
#[enumscribe(str = "BAA", case_sensitive)]
V3(),
V4 {},
#[enumscribe(str = "BaZ")]
@ -100,6 +100,24 @@ fn test_unscribe() {
assert_eq!(E0::unscribe("dolorr"), E0::V12("dolorr".to_owned()));
assert_eq!(E0::unscribe(""), E0::V12("".to_owned()));
assert_eq!(E0::unscribe("\0"), E0::V12("\0".to_owned()));
#[derive(Unscribe, Eq, PartialEq, Debug)]
#[enumscribe(case_insensitive)]
enum E1 {
#[enumscribe(str = "foo")]
V0,
#[enumscribe(str = "baa", case_sensitive)]
V1,
#[enumscribe(other)]
V2(String),
}
assert_eq!(E1::unscribe("foo"), E1::V0);
assert_eq!(E1::unscribe("Foo"), E1::V0);
assert_eq!(E1::unscribe("FOO"), E1::V0);
assert_eq!(E1::unscribe("baa"), E1::V1);
assert_eq!(E1::unscribe("Baa"), E1::V2("Baa".to_owned()));
assert_eq!(E1::unscribe("BAA"), E1::V2("BAA".to_owned()));
}
#[test]
@ -110,7 +128,7 @@ fn test_try_unscribe() {
#[enumscribe(str = "foo")]
V1,
V2(),
#[enumscribe(str = "BAA")]
#[enumscribe(str = "BAA", case_sensitive)]
V3(),
V4 {},
#[enumscribe(str = "BaZ")]
@ -317,4 +335,20 @@ fn test_try_unscribe() {
);
assert_eq!(E1::try_unscribe(""), Some(E1::V12("".to_owned())));
assert_eq!(E1::try_unscribe("\0"), Some(E1::V12("\0".to_owned())));
#[derive(TryUnscribe, Eq, PartialEq, Debug)]
#[enumscribe(case_insensitive)]
enum E2 {
#[enumscribe(str = "foo")]
V0,
#[enumscribe(str = "baa", case_sensitive)]
V1,
}
assert_eq!(E2::try_unscribe("foo"), Some(E2::V0));
assert_eq!(E2::try_unscribe("Foo"), Some(E2::V0));
assert_eq!(E2::try_unscribe("FOO"), Some(E2::V0));
assert_eq!(E2::try_unscribe("baa"), Some(E2::V1));
assert_eq!(E2::try_unscribe("Baa"), None);
assert_eq!(E2::try_unscribe("BAA"), None);
}

Loading…
Cancel
Save