Skip to content

Commit

Permalink
Add TryFrom<&str> to EnumString
Browse files Browse the repository at this point in the history
The implementation simply delegates to `std::str::FromStr`.

To maintain compatibility with Rust 1.32, a new dependency
is introduced which allows us to emit the `TryFrom<&str>`
implementation only for Rust 1.34 and above when
`std::convert::TryFrom` was stabilized.
  • Loading branch information
dspicher committed Oct 19, 2021
1 parent f50fc72 commit 55e00c9
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 44 deletions.
1 change: 1 addition & 0 deletions strum_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ name = "strum_macros"
heck = "0.3"
proc-macro2 = "1.0"
quote = "1.0"
rustversion = "1.0"
syn = { version = "1.0", features = ["parsing", "extra-traits"] }

[dev-dependencies]
Expand Down
46 changes: 44 additions & 2 deletions strum_macros/src/macros/strings/from_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {

arms.push(default);

Ok(quote! {
let from_str = quote! {
#[allow(clippy::use_self)]
impl #impl_generics ::std::str::FromStr for #name #ty_generics #where_clause {
type Err = #strum_module_path::ParseError;
Expand All @@ -97,5 +97,47 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
}
}
}
})
};

let try_from_str = try_from_str(
name,
impl_generics,
ty_generics,
where_clause,
strum_module_path,
);

Ok(std::iter::FromIterator::from_iter(
vec![from_str, try_from_str].into_iter(),
))
}

#[rustversion::before(1.34)]
fn try_from_str(
_name: &proc_macro2::Ident,
_impl_generics: syn::ImplGenerics,
_ty_generics: syn::TypeGenerics,
_where_clause: Option<&syn::WhereClause>,
_strum_module_path: syn::Path,
) -> TokenStream {
Default::default()
}

#[rustversion::since(1.34)]
fn try_from_str(
name: &proc_macro2::Ident,
impl_generics: syn::ImplGenerics,
ty_generics: syn::TypeGenerics,
where_clause: Option<&syn::WhereClause>,
strum_module_path: syn::Path,
) -> TokenStream {
quote! {
#[allow(clippy::use_self)]
impl #impl_generics ::std::convert::TryFrom<&str> for #name #ty_generics #where_clause {
type Error = #strum_module_path::ParseError;
fn try_from(s: &str) -> ::std::result::Result< #name #ty_generics , Self::Error> {
::std::str::FromStr::from_str(s)
}
}
}
}
86 changes: 44 additions & 42 deletions strum_tests/tests/from_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,46 @@ enum Color {
Black,
}

macro_rules! assert_from_str {
($t:ty, $expected:expr, $lit:literal) => {
assert_eq!($expected, <$t as FromStr>::from_str($lit).unwrap());
assert_eq!(
$expected,
<$t as std::convert::TryFrom<&str>>::try_from($lit).unwrap()
);
};
}

#[test]
fn color_simple() {
assert_eq!(Color::Red, Color::from_str("Red").unwrap());
assert_from_str!(Color, Color::Red, "Red");
}

#[test]
fn color_value() {
assert_eq!(Color::Blue { hue: 0 }, Color::from_str("Blue").unwrap());
assert_from_str!(Color, Color::Blue { hue: 0 }, "Blue");
}

#[test]
fn color_serialize() {
assert_eq!(Color::Yellow, Color::from_str("y").unwrap());
assert_eq!(Color::Yellow, Color::from_str("yellow").unwrap());
assert_from_str!(Color, Color::Yellow, "y");
assert_from_str!(Color, Color::Yellow, "yellow");
}

#[test]
fn color_to_string() {
assert_eq!(Color::Purple, Color::from_str("purp").unwrap());
assert_from_str!(Color, Color::Purple, "purp");
}

#[test]
fn color_default() {
assert_eq!(
Color::Green(String::from("not found")),
Color::from_str("not found").unwrap()
);
assert_from_str!(Color, Color::Green(String::from("not found")), "not found");
}

#[test]
fn color_ascii_case_insensitive() {
assert_eq!(Color::Black, Color::from_str("BLK").unwrap());
assert_eq!(Color::Black, Color::from_str("bLaCk").unwrap());
assert_from_str!(Color, Color::Black, "BLK");
assert_from_str!(Color, Color::Black, "bLaCk");
}

#[derive(Debug, Eq, PartialEq, EnumString)]
Expand All @@ -65,18 +72,9 @@ enum Brightness {

#[test]
fn brightness_serialize_all() {
assert_eq!(
Brightness::DarkBlack,
Brightness::from_str("dark_black").unwrap()
);
assert_eq!(
Brightness::Dim { glow: 0 },
Brightness::from_str("dim").unwrap()
);
assert_eq!(
Brightness::BrightWhite,
Brightness::from_str("Bright").unwrap()
);
assert_from_str!(Brightness, Brightness::DarkBlack, "dark_black");
assert_from_str!(Brightness, Brightness::Dim { glow: 0 }, "dim");
assert_from_str!(Brightness, Brightness::BrightWhite, "Bright");
}

#[derive(Debug, Eq, PartialEq, EnumString)]
Expand All @@ -100,13 +98,13 @@ fn week_not_found() {

#[test]
fn week_found() {
assert_eq!(Result::Ok(Week::Sunday), Week::from_str("Sunday"));
assert_eq!(Result::Ok(Week::Monday), Week::from_str("Monday"));
assert_eq!(Result::Ok(Week::Tuesday), Week::from_str("Tuesday"));
assert_eq!(Result::Ok(Week::Wednesday), Week::from_str("Wednesday"));
assert_eq!(Result::Ok(Week::Thursday), Week::from_str("Thursday"));
assert_eq!(Result::Ok(Week::Friday), Week::from_str("Friday"));
assert_eq!(Result::Ok(Week::Saturday), Week::from_str("Saturday"));
assert_from_str!(Week, Week::Sunday, "Sunday");
assert_from_str!(Week, Week::Monday, "Monday");
assert_from_str!(Week, Week::Tuesday, "Tuesday");
assert_from_str!(Week, Week::Wednesday, "Wednesday");
assert_from_str!(Week, Week::Thursday, "Thursday");
assert_from_str!(Week, Week::Friday, "Friday");
assert_from_str!(Week, Week::Saturday, "Saturday");
}

#[derive(Debug, Eq, PartialEq, EnumString)]
Expand All @@ -117,7 +115,7 @@ enum Lifetime<'a> {

#[test]
fn lifetime_test() {
assert_eq!(Lifetime::Life(""), Lifetime::from_str("Life").unwrap());
assert_from_str!(Lifetime, Lifetime::Life(""), "Life");
}

#[derive(Debug, Eq, PartialEq, EnumString)]
Expand All @@ -128,7 +126,7 @@ enum Generic<T: Default> {

#[test]
fn generic_test() {
assert_eq!(Generic::Gen(""), Generic::from_str("Gen").unwrap());
assert_from_str!(Generic<_>, Generic::Gen(""), "Gen");
}

#[derive(Debug, Eq, PartialEq, EnumString)]
Expand All @@ -143,29 +141,33 @@ enum CaseInsensitiveEnum {

#[test]
fn case_insensitive_enum_no_attr() {
assert_eq!(
CaseInsensitiveEnum::NoAttr,
CaseInsensitiveEnum::from_str("noattr").unwrap()
);
assert_from_str!(CaseInsensitiveEnum, CaseInsensitiveEnum::NoAttr, "noattr");
}

#[test]
fn case_insensitive_enum_no_case_insensitive() {
assert_eq!(
assert_from_str!(
CaseInsensitiveEnum,
CaseInsensitiveEnum::NoCaseInsensitive,
CaseInsensitiveEnum::from_str("NoCaseInsensitive").unwrap(),
"NoCaseInsensitive"
);
assert!(CaseInsensitiveEnum::from_str("nocaseinsensitive").is_err());
assert!(
<CaseInsensitiveEnum as std::convert::TryFrom<&str>>::try_from("nocaseinsensitive")
.is_err()
);
}

#[test]
fn case_insensitive_enum_case_insensitive() {
assert_eq!(
assert_from_str!(
CaseInsensitiveEnum,
CaseInsensitiveEnum::CaseInsensitive,
CaseInsensitiveEnum::from_str("CaseInsensitive").unwrap(),
"CaseInsensitive"
);
assert_eq!(
assert_from_str!(
CaseInsensitiveEnum,
CaseInsensitiveEnum::CaseInsensitive,
CaseInsensitiveEnum::from_str("caseinsensitive").unwrap(),
"caseinsensitive"
);
}

0 comments on commit 55e00c9

Please sign in to comment.