Repository: Peternator7/strum
Pull request #217 (2022-04-01 16:44:37)

Add TryFrom to FromRepr
Similar to #186, automatically implements TryFrom based on the discriminant type.


Peternator7: Hello @danog, Thanks for the PR! I'm onboard with the general idea. The test failures in 1.32 look legitimate so if you can fix those, we can merge this.
Peternator7: Hello @danog, Thanks for the PR! I'm onboard with the general idea. The test failures in 1.32 look legitimate so if you can fix those, we can merge this.
danog: This should do the trick! :D

Patch:
diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs
index 32f9a78..a6ff225 100644
--- a/strum_macros/src/lib.rs
+++ b/strum_macros/src/lib.rs
@@ -381,7 +381,8 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     toks.into()
 }
 
-/// Add a function to enum that allows accessing variants by its discriminant
+/// Add a function to enum that allows accessing variants by its discriminant.
+/// On Rust 1.34 and above, std::convert::TryFrom<TDiscriminant> will be derived as well.
 ///
 /// This macro adds a standalone function to obtain an enum variant by its discriminant. The macro adds
 /// `from_repr(discriminant: usize) -> Option<YourEnum>` as a standalone function on the enum. For
diff --git a/strum_macros/src/macros/from_repr.rs b/strum_macros/src/macros/from_repr.rs
index ec9f557..ef0bcef 100644
--- a/strum_macros/src/macros/from_repr.rs
+++ b/strum_macros/src/macros/from_repr.rs
@@ -3,7 +3,7 @@ use proc_macro2::{Span, TokenStream};
 use quote::{format_ident, quote};
 use syn::{Data, DeriveInput, Fields, PathArguments, Type, TypeParen};
 
-use crate::helpers::{non_enum_error, HasStrumVariantProperties};
+use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
 
 pub fn from_repr_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     let name = &ast.ident;
@@ -12,6 +12,9 @@ pub fn from_repr_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     let vis = &ast.vis;
     let attrs = &ast.attrs;
 
+    let type_properties = ast.get_type_properties()?;
+    let strum_module_path = type_properties.crate_module_path();
+
     let mut discriminant_type: Type = syn::parse("usize".parse().unwrap()).unwrap();
     for attr in attrs {
         let path = &attr.path;
@@ -124,7 +127,7 @@ pub fn from_repr_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
         filter_by_rust_version(quote! { const })
     };
 
-    Ok(quote! {
+    let from_repr = quote! {
         #[allow(clippy::use_self)]
         impl #impl_generics #name #ty_generics #where_clause {
             #[doc = "Try to create [Self] from the raw representation"]
@@ -135,5 +138,51 @@ pub fn from_repr_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
                 }
             }
         }
+    };
+
+    let try_from_repr = try_from_repr(
+        name,
+        &discriminant_type,
+        &impl_generics,
+        &ty_generics,
+        where_clause,
+        &strum_module_path,
+    );
+
+    Ok(quote! {
+        #from_repr
+        #try_from_repr
     })
 }
+
+#[rustversion::before(1.34)]
+fn try_from_repr(
+    _name: &proc_macro2::Ident,
+    _discriminant_type: &Type,
+    _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_repr(
+    name: &proc_macro2::Ident,
+    discriminant_type: &Type,
+    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 ::core::convert::TryFrom<#discriminant_type> for #name #ty_generics #where_clause {
+            type Error = #strum_module_path::ParseError;
+            fn try_from(s: #discriminant_type) -> ::core::result::Result< #name #ty_generics , <Self as ::core::convert::TryFrom<#discriminant_type>>::Error> {
+                Self::from_repr(s).ok_or(#strum_module_path::ParseError::VariantNotFound)
+            }
+        }
+    }
+}


Test patch:
diff --git a/strum_tests/Cargo.toml b/strum_tests/Cargo.toml
index 796a069..cf15adf 100644
--- a/strum_tests/Cargo.toml
+++ b/strum_tests/Cargo.toml
@@ -11,6 +11,7 @@ clap = "=2.33.0"
 enum_variant_type = "=0.2.0"
 structopt = "0.2.18"
 bitflags = "=1.2"
+if_rust_version = "1.0"
 
 [dev-dependencies]
 rustversion = "1.0"
diff --git a/strum_tests/tests/from_repr.rs b/strum_tests/tests/from_repr.rs
index bb7263f..2b46042 100644
--- a/strum_tests/tests/from_repr.rs
+++ b/strum_tests/tests/from_repr.rs
@@ -1,3 +1,4 @@
+use if_rust_version::if_rust_version;
 use strum::FromRepr;
 
 #[derive(Debug, FromRepr, PartialEq)]
@@ -12,14 +13,31 @@ enum Week {
     Saturday = 8,
 }
 
+macro_rules! assert_eq_repr {
+    ( $type:ident::from_repr($number:literal), Some($enum:expr) ) => {
+        assert_eq!($type::from_repr($number), Some($enum));
+        if_rust_version! { >= 1.34 {
+            assert_eq!(core::convert::TryInto::<$type>::try_into($number), Ok($enum));
+            assert_eq!(<$type as core::convert::TryFrom<_>>::try_from($number), Ok($enum));
+        }}
+    };
+    ( $type:ident::from_repr($number:literal), None ) => {
+        assert_eq!($type::from_repr($number), None);
+        if_rust_version! { >= 1.34 {
+            assert_eq!(core::convert::TryInto::<$type>::try_into($number), Err(::strum::ParseError::VariantNotFound));
+            assert_eq!(<$type as core::convert::TryFrom<_>>::try_from($number), Err(::strum::ParseError::VariantNotFound));
+        }}
+    };
+}
+
 #[test]
 fn simple_test() {
-    assert_eq!(Week::from_repr(0), Some(Week::Sunday));
-    assert_eq!(Week::from_repr(1), Some(Week::Monday));
-    assert_eq!(Week::from_repr(6), None);
-    assert_eq!(Week::from_repr(7), Some(Week::Friday));
-    assert_eq!(Week::from_repr(8), Some(Week::Saturday));
-    assert_eq!(Week::from_repr(9), None);
+    assert_eq_repr!(Week::from_repr(0), Some(Week::Sunday));
+    assert_eq_repr!(Week::from_repr(1), Some(Week::Monday));
+    assert_eq_repr!(Week::from_repr(6), None);
+    assert_eq_repr!(Week::from_repr(7), Some(Week::Friday));
+    assert_eq_repr!(Week::from_repr(8), Some(Week::Saturday));
+    assert_eq_repr!(Week::from_repr(9), None);
 }
 
 #[rustversion::since(1.46)]
@@ -57,7 +75,7 @@ fn crate_module_path_test() {
         Saturday,
     }
 
-    assert_eq!(Week::from_repr(0), Some(Week::Sunday));
-    assert_eq!(Week::from_repr(6), Some(Week::Saturday));
-    assert_eq!(Week::from_repr(7), None);
+    assert_eq_repr!(Week::from_repr(0), Some(Week::Sunday));
+    assert_eq_repr!(Week::from_repr(6), Some(Week::Saturday));
+    assert_eq_repr!(Week::from_repr(7), None);
 }