From ac757fa9702cacd4559c444b309c27b97fa1de59 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 30 Apr 2022 20:24:17 +0200 Subject: [PATCH] Add TryFrom to FromRepr (#217) * Add TryFrom to FromRepr * Fix building on rust < 1.34 --- strum_macros/src/lib.rs | 3 +- strum_macros/src/macros/from_repr.rs | 53 ++++++++++++++++++++++++++-- strum_tests/Cargo.toml | 1 + strum_tests/tests/from_repr.rs | 36 ++++++++++++++----- 4 files changed, 81 insertions(+), 12 deletions(-) diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index 32f9a780..a6ff2259 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 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` 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 ec9f5577..ef0bcefc 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 { let name = &ast.ident; @@ -12,6 +12,9 @@ pub fn from_repr_inner(ast: &DeriveInput) -> syn::Result { 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 { 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 { } } } + }; + + 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 , >::Error> { + Self::from_repr(s).ok_or(#strum_module_path::ParseError::VariantNotFound) + } + } + } +} diff --git a/strum_tests/Cargo.toml b/strum_tests/Cargo.toml index 796a0699..cf15adfa 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 bb7263f6..2b46042a 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); }