From 8c078040ac747a3934a4e8c23ff5cfb2e1c0111b Mon Sep 17 00:00:00 2001 From: pantonshire Date: Wed, 8 Feb 2023 11:10:48 +0000 Subject: [PATCH] Implement container-level `case_insensitive` from issue #1 --- enumscribe_derive/src/enums.rs | 65 +++++++++++++++++++++---- enumscribe_derive/src/lib.rs | 27 +++++----- enumscribe_tests/tests/test_unscribe.rs | 38 ++++++++++++++- 3 files changed, 106 insertions(+), 24 deletions(-) diff --git a/enumscribe_derive/src/enums.rs b/enumscribe_derive/src/enums.rs index 1a24e15..4363b98 100644 --- a/enumscribe_derive/src/enums.rs +++ b/enumscribe_derive/src/enums.rs @@ -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 { +pub(crate) fn parse_enum<'a>(data: &'a DataEnum, attrs: &'a [Attribute]) -> MacroResult> { 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 { 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 { } Ok(Enum { variants }) -} +} \ No newline at end of file diff --git a/enumscribe_derive/src/lib.rs b/enumscribe_derive/src/lib.rs index 45d99ff..52abd3e 100644 --- a/enumscribe_derive/src/lib.rs +++ b/enumscribe_derive/src/lib.rs @@ -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)) } diff --git a/enumscribe_tests/tests/test_unscribe.rs b/enumscribe_tests/tests/test_unscribe.rs index affef22..e063341 100644 --- a/enumscribe_tests/tests/test_unscribe.rs +++ b/enumscribe_tests/tests/test_unscribe.rs @@ -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); }