diff --git a/strum_macros/Cargo.toml b/strum_macros/Cargo.toml index ee84210a..fcf643c6 100644 --- a/strum_macros/Cargo.toml +++ b/strum_macros/Cargo.toml @@ -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] diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index 38eb0b39..8142f1f0 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -30,7 +30,8 @@ fn debug_print_generated(ast: &DeriveInput, toks: &TokenStream) { /// Converts strings to enum variants based on their name. /// -/// auto-derives `std::str::FromStr` on the enum. Each variant of the enum will match on it's own name. +/// auto-derives `std::str::FromStr` on the enum (for Rust 1.34 and above, std::convert::TryFrom<&str> +/// will be derived as well). Each variant of the enum will match on it's own name. /// This can be overridden using `serialize="DifferentName"` or `to_string="DifferentName"` /// on the attribute as shown below. /// Multiple deserializations can be added to the same variant. If the variant contains additional data, diff --git a/strum_macros/src/macros/strings/from_string.rs b/strum_macros/src/macros/strings/from_string.rs index 2977fd19..a42ff4de 100644 --- a/strum_macros/src/macros/strings/from_string.rs +++ b/strum_macros/src/macros/strings/from_string.rs @@ -87,7 +87,7 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result { 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; @@ -97,5 +97,48 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result { } } } + }; + + let try_from_str = try_from_str( + name, + impl_generics, + ty_generics, + where_clause, + strum_module_path, + ); + + Ok(quote! { + #from_str + #try_from_str }) } + +#[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) + } + } + } +} diff --git a/strum_tests/Cargo.toml b/strum_tests/Cargo.toml index a4b3470e..28664a28 100644 --- a/strum_tests/Cargo.toml +++ b/strum_tests/Cargo.toml @@ -13,4 +13,7 @@ structopt = "0.2.18" bitflags = "=1.2" [build-dependencies] -version_check = "0.9.2" \ No newline at end of file +version_check = "0.9.2" + +[dev-dependencies] +rustversion = "1.0" diff --git a/strum_tests/tests/from_str.rs b/strum_tests/tests/from_str.rs index 77922e9d..463e3b52 100644 --- a/strum_tests/tests/from_str.rs +++ b/strum_tests/tests/from_str.rs @@ -17,39 +17,56 @@ enum Color { Black, } +#[rustversion::since(1.34)] +fn assert_from_str<'a, T>(a: T, from: &'a str) +where + T: PartialEq + std::str::FromStr + std::convert::TryFrom<&'a str> + std::fmt::Debug, + ::Err: std::fmt::Debug, + >::Error: std::fmt::Debug, +{ + assert_eq!(a, T::from_str(from).unwrap()); + assert_eq!(a, std::convert::TryFrom::try_from(from).unwrap()); +} + +#[rustversion::before(1.34)] +fn assert_from_str(a: T, from: &str) +where + T: PartialEq + std::str::FromStr + std::fmt::Debug, + ::Err: std::fmt::Debug, +{ + assert_eq!(a, T::from_str(from).unwrap()); +} + #[test] fn color_simple() { - assert_eq!(Color::Red, Color::from_str("Red").unwrap()); + assert_from_str(Color::Red, "Red"); } #[test] fn color_value() { - assert_eq!(Color::Blue { hue: 0 }, Color::from_str("Blue").unwrap()); + assert_from_str(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::Yellow, "y"); + assert_from_str(Color::Yellow, "yellow"); } #[test] fn color_to_string() { - assert_eq!(Color::Purple, Color::from_str("purp").unwrap()); + assert_from_str(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::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::Black, "BLK"); + assert_from_str(Color::Black, "bLaCk"); } #[derive(Debug, Eq, PartialEq, EnumString)] @@ -65,18 +82,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::DarkBlack, "dark_black"); + assert_from_str(Brightness::Dim { glow: 0 }, "dim"); + assert_from_str(Brightness::BrightWhite, "Bright"); } #[derive(Debug, Eq, PartialEq, EnumString)] @@ -100,13 +108,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::Sunday, "Sunday"); + assert_from_str(Week::Monday, "Monday"); + assert_from_str(Week::Tuesday, "Tuesday"); + assert_from_str(Week::Wednesday, "Wednesday"); + assert_from_str(Week::Thursday, "Thursday"); + assert_from_str(Week::Friday, "Friday"); + assert_from_str(Week::Saturday, "Saturday"); } #[derive(Debug, Eq, PartialEq, EnumString)] @@ -117,7 +125,7 @@ enum Lifetime<'a> { #[test] fn lifetime_test() { - assert_eq!(Lifetime::Life(""), Lifetime::from_str("Life").unwrap()); + assert_from_str(Lifetime::Life(""), "Life"); } #[derive(Debug, Eq, PartialEq, EnumString)] @@ -128,7 +136,7 @@ enum Generic { #[test] fn generic_test() { - assert_eq!(Generic::Gen(""), Generic::from_str("Gen").unwrap()); + assert_from_str(Generic::Gen(""), "Gen"); } #[derive(Debug, Eq, PartialEq, EnumString)] @@ -143,29 +151,27 @@ enum CaseInsensitiveEnum { #[test] fn case_insensitive_enum_no_attr() { - assert_eq!( - CaseInsensitiveEnum::NoAttr, - CaseInsensitiveEnum::from_str("noattr").unwrap() - ); + assert_from_str(CaseInsensitiveEnum::NoAttr, "noattr"); } #[test] fn case_insensitive_enum_no_case_insensitive() { - assert_eq!( - CaseInsensitiveEnum::NoCaseInsensitive, - CaseInsensitiveEnum::from_str("NoCaseInsensitive").unwrap(), - ); + assert_from_str(CaseInsensitiveEnum::NoCaseInsensitive, "NoCaseInsensitive"); assert!(CaseInsensitiveEnum::from_str("nocaseinsensitive").is_err()); } +#[rustversion::since(1.34)] #[test] -fn case_insensitive_enum_case_insensitive() { - assert_eq!( - CaseInsensitiveEnum::CaseInsensitive, - CaseInsensitiveEnum::from_str("CaseInsensitive").unwrap(), - ); - assert_eq!( - CaseInsensitiveEnum::CaseInsensitive, - CaseInsensitiveEnum::from_str("caseinsensitive").unwrap(), +fn case_insensitive_enum_no_case_insensitive_try_from() { + assert_from_str(CaseInsensitiveEnum::NoCaseInsensitive, "NoCaseInsensitive"); + assert!( + >::try_from("nocaseinsensitive") + .is_err() ); } + +#[test] +fn case_insensitive_enum_case_insensitive() { + assert_from_str(CaseInsensitiveEnum::CaseInsensitive, "CaseInsensitive"); + assert_from_str(CaseInsensitiveEnum::CaseInsensitive, "caseinsensitive"); +}