Skip to content

Commit

Permalink
Add TryFrom to FromRepr (#217)
Browse files Browse the repository at this point in the history
* Add TryFrom to FromRepr

* Fix building on rust < 1.34
  • Loading branch information
danog committed Apr 30, 2022
1 parent 054df84 commit ac757fa
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 12 deletions.
3 changes: 2 additions & 1 deletion strum_macros/src/lib.rs
Expand Up @@ -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
Expand Down
53 changes: 51 additions & 2 deletions strum_macros/src/macros/from_repr.rs
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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"]
Expand All @@ -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)
}
}
}
}
1 change: 1 addition & 0 deletions strum_tests/Cargo.toml
Expand Up @@ -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"
36 changes: 27 additions & 9 deletions strum_tests/tests/from_repr.rs
@@ -1,3 +1,4 @@
use if_rust_version::if_rust_version;
use strum::FromRepr;

#[derive(Debug, FromRepr, PartialEq)]
Expand All @@ -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)]
Expand Down Expand Up @@ -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);
}

0 comments on commit ac757fa

Please sign in to comment.