diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3790d8e8..778cb558 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,7 +54,7 @@ jobs: strategy: fail-fast: false matrix: - msrv: ["1.65.0"] + msrv: ["1.72.0"] os: - ubuntu - macOS diff --git a/CHANGELOG.md b/CHANGELOG.md index b1ed08cb..2a2baa9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Breaking changes -- The minimum supported Rust version (MSRV) is now Rust 1.65. +- The minimum supported Rust version (MSRV) is now Rust 1.72. - Add the `std` feature which should be disabled in `no_std` environments. - All Cargo features, except `std`, are now disabled by default. The `full` feature can be used to get the old behavior of supporting all possible @@ -61,6 +61,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ([#290](https://github.com/JelteF/derive_more/pull/290)) - Add support for specifying concrete types to `AsRef`/`AsMut` derives. ([#298](https://github.com/JelteF/derive_more/pull/298)) +- Add `TryFrom` derive for enums to convert from their discriminant. + ([#300](https://github.com/JelteF/derive_more/pull/300)) ### Changed diff --git a/Cargo.toml b/Cargo.toml index b32d0f17..2cab4bcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "derive_more" version = "1.0.0-beta.3" edition = "2021" -rust-version = "1.65.0" +rust-version = "1.72.0" description = "Adds #[derive(x)] macros for more traits" authors = ["Jelte Fennema "] license = "MIT" @@ -65,6 +65,7 @@ mul_assign = ["derive_more-impl/mul_assign"] mul = ["derive_more-impl/mul"] not = ["derive_more-impl/not"] sum = ["derive_more-impl/sum"] +try_from = ["derive_more-impl/try_from"] try_into = ["derive_more-impl/try_into"] is_variant = ["derive_more-impl/is_variant"] unwrap = ["derive_more-impl/unwrap"] @@ -92,6 +93,7 @@ full = [ "mul_assign", "not", "sum", + "try_from", "try_into", "try_unwrap", "unwrap", @@ -204,6 +206,11 @@ name = "sum" path = "tests/sum.rs" required-features = ["sum"] +[[test]] +name = "try_from" +path = "tests/try_from.rs" +required-features = ["try_from"] + [[test]] name = "try_into" path = "tests/try_into.rs" diff --git a/README.md b/README.md index cec17fde..25954cfa 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Latest Version](https://img.shields.io/crates/v/derive_more.svg)](https://crates.io/crates/derive_more) [![Rust Documentation](https://docs.rs/derive_more/badge.svg)](https://docs.rs/derive_more) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/JelteF/derive_more/master/LICENSE) -[![Rust 1.65+](https://img.shields.io/badge/rustc-1.65+-lightgray.svg)](https://blog.rust-lang.org/2021/10/21/Rust-1.65.0.html) +[![Rust 1.72+](https://img.shields.io/badge/rustc-1.72+-lightgray.svg)](https://blog.rust-lang.org/2023/08/24/Rust-1.72.0.html) [![Unsafe Forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance) Rust has lots of builtin traits that are implemented for its basic types, such @@ -85,9 +85,10 @@ These are traits that are used to convert automatically between types. 1. [`From`] 2. [`Into`] 3. [`FromStr`] -4. [`TryInto`] -5. [`IntoIterator`] -6. [`AsRef`], [`AsMut`] +4. [`TryFrom`] +5. [`TryInto`] +6. [`IntoIterator`] +7. [`AsRef`], [`AsMut`] ### Formatting traits @@ -140,7 +141,7 @@ These don't derive traits, but derive static methods instead. ## Installation -This library requires Rust 1.65 or higher. To avoid redundant compilation times, by +This library requires Rust 1.72 or higher. To avoid redundant compilation times, by default no derives are supported. You have to enable each type of derive as a feature in `Cargo.toml`: diff --git a/clippy.toml b/clippy.toml index 0c697c31..0d86a7d2 100644 --- a/clippy.toml +++ b/clippy.toml @@ -2,7 +2,7 @@ # See full lints list at: # https://rust-lang.github.io/rust-clippy/master/index.html -msrv = "1.65.0" +msrv = "1.72.0" # Ensures consistent bracing for macro calls in the codebase. # Extends default settings: diff --git a/impl/Cargo.toml b/impl/Cargo.toml index 8fa0f8f6..b6d0459e 100644 --- a/impl/Cargo.toml +++ b/impl/Cargo.toml @@ -2,7 +2,7 @@ name = "derive_more-impl" version = "1.0.0-beta.3" edition = "2021" -rust-version = "1.65.0" +rust-version = "1.72.0" description = "Internal implementation of `derive_more` crate" authors = ["Jelte Fennema "] license = "MIT" @@ -66,6 +66,7 @@ mul = ["syn/extra-traits"] mul_assign = ["syn/extra-traits"] not = ["syn/extra-traits"] sum = [] +try_from = [] try_into = ["syn/extra-traits"] try_unwrap = ["dep:convert_case"] unwrap = ["dep:convert_case"] @@ -91,6 +92,7 @@ full = [ "mul_assign", "not", "sum", + "try_from", "try_into", "try_unwrap", "unwrap", diff --git a/impl/doc/try_from.md b/impl/doc/try_from.md new file mode 100644 index 00000000..7472689f --- /dev/null +++ b/impl/doc/try_from.md @@ -0,0 +1,32 @@ +# What `#[derive(TryFrom)]` generates + +Derive `TryFrom` allows you to convert enum discriminants into their corresponding variants. + + + + +## Enums + +By default, a `TryFrom` is generated, matching the [type of the discriminant](https://doc.rust-lang.org/reference/items/enumerations.html#discriminants). +The type can be changed with a `#[repr(u/i*)]` attribute, e.g., `#[repr(u8)]` or `#[repr(i32)]`. +Only field-less variants can be constructed from their variant, therefor the `TryFrom` implementation will return an error for a discriminant representing a variant with fields. + +```rust +# use derive_more::TryFrom; +#[derive(TryFrom, Debug, PartialEq)] +#[try_from(repr)] +#[repr(u32)] +enum Enum { + ImplicitZero, + ExplicitFive = 5, + FieldSix(usize), + EmptySeven{}, +} + +assert_eq!(Enum::ImplicitZero, Enum::try_from(0).unwrap()); +assert_eq!(Enum::ExplicitFive, Enum::try_from(5).unwrap()); +assert_eq!(Enum::EmptySeven{}, Enum::try_from(7).unwrap()); + +// Variants with fields are not supported, as the value for their fields would be undefined. +assert!(Enum::try_from(6).is_err()); +``` diff --git a/impl/src/lib.rs b/impl/src/lib.rs index 62c32700..a229c848 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -64,6 +64,8 @@ mod not_like; pub(crate) mod parsing; #[cfg(feature = "sum")] mod sum_like; +#[cfg(feature = "try_from")] +mod try_from; #[cfg(feature = "try_into")] mod try_into; #[cfg(feature = "try_unwrap")] @@ -265,6 +267,8 @@ create_derive!("not", not_like, Neg, neg_derive); create_derive!("sum", sum_like, Sum, sum_derive); create_derive!("sum", sum_like, Product, product_derive); +create_derive!("try_from", try_from, TryFrom, try_from_derive, try_from); + create_derive!("try_into", try_into, TryInto, try_into_derive, try_into); create_derive!( diff --git a/impl/src/try_from.rs b/impl/src/try_from.rs new file mode 100644 index 00000000..8a84c685 --- /dev/null +++ b/impl/src/try_from.rs @@ -0,0 +1,208 @@ +//! Implementation of a [`TryFrom`] derive macro. + +use std::mem; + +use proc_macro2::{Literal, Span, TokenStream}; +use quote::{format_ident, quote, ToTokens}; +use syn::spanned::Spanned as _; + +/// Expands a [`TryFrom`] derive macro. +pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result { + match &input.data { + syn::Data::Struct(data) => Err(syn::Error::new( + data.struct_token.span(), + "`TryFrom` cannot be derived for structs", + )), + syn::Data::Enum(data) => Ok(Expansion { + repr: ReprAttribute::parse_attrs(&input.attrs)?, + attr: ItemAttribute::parse_attrs(&input.attrs)?, + ident: input.ident.clone(), + generics: input.generics.clone(), + variants: data.variants.clone().into_iter().collect(), + } + .into_token_stream()), + syn::Data::Union(data) => Err(syn::Error::new( + data.union_token.span(), + "`TryFrom` cannot be derived for unions", + )), + } +} + +/// Representation of a [`TryFrom`] derive macro struct item attribute. +/// +/// ```rust,ignore +/// #[try_from(repr)] +/// ``` +struct ItemAttribute; + +impl ItemAttribute { + /// Parses am [`ItemAttribute`] from the provided [`syn::Attribute`]s. + fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> syn::Result> { + attrs + .as_ref() + .iter() + .filter(|attr| attr.path().is_ident("try_from")) + .try_fold(None, |mut attrs, attr| { + let mut parsed = None; + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("repr") { + parsed = Some(ItemAttribute); + Ok(()) + } else { + Err(meta.error("only `repr` is allowed here")) + } + })?; + if mem::replace(&mut attrs, parsed).is_some() { + Err(syn::Error::new( + attr.span(), + "only single `#[try_from(repr)]` attribute is allowed here", + )) + } else { + Ok(attrs) + } + }) + } +} + +/// Representation of a [`#[repr(u/i*)]` Rust attribute][0]. +/// +/// **NOTE**: Disregards any non-integer representation `#[repr]`s. +/// +/// ```rust,ignore +/// #[repr()] +/// ``` +/// +/// [0]: https://doc.rust-lang.org/reference/type-layout.html#primitive-representations +struct ReprAttribute(syn::Ident); + +impl ReprAttribute { + /// Parses a [`ReprAttribute`] from the provided [`syn::Attribute`]s. + /// + /// If there is no [`ReprAttribute`], then parses a [default `isize` discriminant][0]. + /// + /// [0]: https://doc.rust-lang.org/reference/items/enumerations.html#discriminants + fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> syn::Result { + attrs + .as_ref() + .iter() + .filter(|attr| attr.path().is_ident("repr")) + .try_fold(None, |mut repr, attr| { + attr.parse_nested_meta(|meta| { + if let Some(ident) = meta.path.get_ident() { + if matches!( + ident.to_string().as_str(), + "u8" | "u16" + | "u32" + | "u64" + | "u128" + | "usize" + | "i8" + | "i16" + | "i32" + | "i64" + | "i128" + | "isize" + ) { + repr = Some(ident.clone()); + return Ok(()); + } + } + // Ignore all other attributes that could have a body, e.g. `align`. + _ = meta.input.parse::(); + Ok(()) + }) + .map(|_| repr) + }) + .map(|repr| { + // Default discriminant is interpreted as `isize`: + // https://doc.rust-lang.org/reference/items/enumerations.html#discriminants + repr.unwrap_or_else(|| syn::Ident::new("isize", Span::call_site())) + }) + .map(Self) + } +} + +/// Expansion of a macro for generating [`TryFrom`] implementation of an enum. +struct Expansion { + /// `#[repr(u/i*)]` of the enum. + repr: ReprAttribute, + + /// [`ItemAttribute`] of the enum. + attr: Option, + + /// [`syn::Ident`] of the enum. + ident: syn::Ident, + + /// [`syn::Generics`] of the enum. + generics: syn::Generics, + + /// [`syn::Variant`]s of the enum. + variants: Vec, +} + +impl ToTokens for Expansion { + /// Expands [`TryFrom`] implementations for a struct. + fn to_tokens(&self, tokens: &mut TokenStream) { + if self.attr.is_none() { + return; + } + let ident = &self.ident; + let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + + let repr = &self.repr.0; + + let mut last_discriminant = quote! {0}; + let mut inc = 0usize; + let (consts, (discriminants, variants)): ( + Vec, + (Vec, Vec), + ) = self + .variants + .iter() + .filter_map( + |syn::Variant { + ident, + fields, + discriminant, + .. + }| { + if let Some(d) = discriminant { + last_discriminant = d.1.to_token_stream(); + inc = 0; + } + let ret = { + let inc = Literal::usize_unsuffixed(inc); + fields.is_empty().then_some(( + format_ident!("__DISCRIMINANT_{ident}"), + ( + quote! { #last_discriminant + #inc }, + quote! { #ident #fields }, + ), + )) + }; + inc += 1; + ret + }, + ) + .unzip(); + + quote! { + #[automatically_derived] + impl #impl_generics ::core::convert::TryFrom<#repr #ty_generics> for #ident + #where_clause + { + type Error = ::derive_more::TryFromReprError<#repr>; + + #[allow(non_upper_case_globals)] + #[inline] + fn try_from(val: #repr) -> ::core::result::Result { + #( const #consts: #repr = #discriminants; )* + match val { + #(#consts => ::core::result::Result::Ok(#ident::#variants),)* + _ => ::core::result::Result::Err(::derive_more::TryFromReprError::new(val)), + } + } + } + }.to_tokens(tokens); + } +} diff --git a/src/convert.rs b/src/convert.rs index 4885ace2..a625f504 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -1,47 +1,97 @@ //! Definitions used in derived implementations of [`core::convert`] traits. -use core::fmt; - -/// Error returned by the derived [`TryInto`] implementation. -/// -/// [`TryInto`]: macro@crate::TryInto -#[derive(Clone, Copy, Debug)] -pub struct TryIntoError { - /// Original input value which failed to convert via the derived - /// [`TryInto`] implementation. +#[cfg(feature = "try_from")] +pub use self::try_from::TryFromReprError; +#[cfg(feature = "try_into")] +pub use self::try_into::TryIntoError; + +#[cfg(feature = "try_from")] +mod try_from { + use core::fmt; + + /// Error returned by the derived [`TryFrom`] implementation on enums to + /// convert from their repr. /// - /// [`TryInto`]: macro@crate::TryInto - pub input: T, - variant_names: &'static str, - output_type: &'static str, + /// [`TryFrom`]: macro@crate::TryFrom + #[derive(Clone, Copy, Debug)] + pub struct TryFromReprError { + /// Original input value which failed to convert via the derived + /// [`TryFrom`] implementation. + /// + /// [`TryFrom`]: macro@crate::TryFrom + pub input: T, + } + + impl TryFromReprError { + #[doc(hidden)] + #[must_use] + #[inline] + pub const fn new(input: T) -> Self { + Self { input } + } + } + + // `T`, as a discriminant, should only be an integer type, and therefore be `Debug`. + impl fmt::Display for TryFromReprError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "`{:?}` does not correspond to a unit variant", + self.input + ) + } + } + + #[cfg(feature = "std")] + // `T` should only be an integer type and therefor be debug + impl std::error::Error for TryFromReprError {} } -impl TryIntoError { - #[doc(hidden)] - #[must_use] - #[inline] - pub const fn new( - input: T, +#[cfg(feature = "try_into")] +mod try_into { + use core::fmt; + + /// Error returned by the derived [`TryInto`] implementation. + /// + /// [`TryInto`]: macro@crate::TryInto + #[derive(Clone, Copy, Debug)] + pub struct TryIntoError { + /// Original input value which failed to convert via the derived + /// [`TryInto`] implementation. + /// + /// [`TryInto`]: macro@crate::TryInto + pub input: T, variant_names: &'static str, output_type: &'static str, - ) -> Self { - Self { - input, - variant_names, - output_type, + } + + impl TryIntoError { + #[doc(hidden)] + #[must_use] + #[inline] + pub const fn new( + input: T, + variant_names: &'static str, + output_type: &'static str, + ) -> Self { + Self { + input, + variant_names, + output_type, + } } } -} -impl fmt::Display for TryIntoError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Only {} can be converted to {}", - self.variant_names, self.output_type, - ) + impl fmt::Display for TryIntoError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Only {} can be converted to {}", + self.variant_names, self.output_type, + ) + } } -} -#[cfg(feature = "std")] -impl std::error::Error for TryIntoError {} + #[cfg(feature = "std")] + impl std::error::Error for TryIntoError {} +} diff --git a/src/lib.rs b/src/lib.rs index 93e54cc1..e3dc645c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ //! [`From`]: macro@crate::From //! [`Into`]: macro@crate::Into //! [`FromStr`]: macro@crate::FromStr +//! [`TryFrom`]: macro@crate::TryFrom //! [`TryInto`]: macro@crate::TryInto //! [`IntoIterator`]: macro@crate::IntoIterator //! [`AsRef`]: macro@crate::AsRef @@ -95,8 +96,11 @@ mod r#str; #[doc(inline)] pub use crate::r#str::FromStrError; -#[cfg(feature = "try_into")] +#[cfg(any(feature = "try_into", feature = "try_from"))] mod convert; +#[cfg(feature = "try_from")] +#[doc(inline)] +pub use crate::convert::TryFromReprError; #[cfg(feature = "try_into")] #[doc(inline)] pub use crate::convert::TryIntoError; @@ -209,6 +213,8 @@ re_export_traits!("not", not_traits, core::ops, Neg, Not); re_export_traits!("sum", sum_traits, core::iter, Product, Sum); +re_export_traits!("try_from", try_from_traits, core::convert, TryFrom); + re_export_traits!("try_into", try_into_traits, core::convert, TryInto); // Now re-export our own derives by their exact name to overwrite any derives that the trait @@ -277,6 +283,9 @@ pub use derive_more_impl::{Neg, Not}; #[cfg(feature = "sum")] pub use derive_more_impl::{Product, Sum}; +#[cfg(feature = "try_from")] +pub use derive_more_impl::TryFrom; + #[cfg(feature = "try_into")] pub use derive_more_impl::TryInto; @@ -309,6 +318,7 @@ pub use derive_more_impl::Unwrap; feature = "mul_assign", feature = "not", feature = "sum", + feature = "try_from", feature = "try_into", feature = "try_unwrap", feature = "unwrap", diff --git a/tests/compile_fail/try_from/invalid_repr.rs b/tests/compile_fail/try_from/invalid_repr.rs new file mode 100644 index 00000000..1b748d82 --- /dev/null +++ b/tests/compile_fail/try_from/invalid_repr.rs @@ -0,0 +1,7 @@ +#[derive(derive_more::TryFrom)] +#[repr(a + b)] +enum Enum { + Variant +} + +fn main() {} diff --git a/tests/compile_fail/try_from/invalid_repr.stderr b/tests/compile_fail/try_from/invalid_repr.stderr new file mode 100644 index 00000000..90e7dc7f --- /dev/null +++ b/tests/compile_fail/try_from/invalid_repr.stderr @@ -0,0 +1,11 @@ +error: expected `,` + --> tests/compile_fail/try_from/invalid_repr.rs:2:10 + | +2 | #[repr(a + b)] + | ^ + +error: expected one of `(`, `,`, `::`, or `=`, found `+` + --> tests/compile_fail/try_from/invalid_repr.rs:2:10 + | +2 | #[repr(a + b)] + | ^ expected one of `(`, `,`, `::`, or `=` diff --git a/tests/compile_fail/try_from/struct.rs b/tests/compile_fail/try_from/struct.rs new file mode 100644 index 00000000..f3609b19 --- /dev/null +++ b/tests/compile_fail/try_from/struct.rs @@ -0,0 +1,4 @@ +#[derive(derive_more::TryFrom)] +struct Struct; + +fn main() {} diff --git a/tests/compile_fail/try_from/struct.stderr b/tests/compile_fail/try_from/struct.stderr new file mode 100644 index 00000000..47dacfac --- /dev/null +++ b/tests/compile_fail/try_from/struct.stderr @@ -0,0 +1,5 @@ +error: `TryFrom` cannot be derived for structs + --> tests/compile_fail/try_from/struct.rs:2:1 + | +2 | struct Struct; + | ^^^^^^ diff --git a/tests/compile_fail/try_from/union.rs b/tests/compile_fail/try_from/union.rs new file mode 100644 index 00000000..25d66234 --- /dev/null +++ b/tests/compile_fail/try_from/union.rs @@ -0,0 +1,6 @@ +#[derive(derive_more::TryFrom)] +union Union { + field: i32, +} + +fn main() {} diff --git a/tests/compile_fail/try_from/union.stderr b/tests/compile_fail/try_from/union.stderr new file mode 100644 index 00000000..089a99bb --- /dev/null +++ b/tests/compile_fail/try_from/union.stderr @@ -0,0 +1,5 @@ +error: `TryFrom` cannot be derived for unions + --> tests/compile_fail/try_from/union.rs:2:1 + | +2 | union Union { + | ^^^^^ diff --git a/tests/try_from.rs b/tests/try_from.rs new file mode 100644 index 00000000..a44cdd07 --- /dev/null +++ b/tests/try_from.rs @@ -0,0 +1,79 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(dead_code)] + +use derive_more::TryFrom; + +#[test] +fn test_with_repr() { + #[derive(TryFrom, Clone, Copy, Debug, Eq, PartialEq)] + #[repr(i16)] + #[try_from(repr)] + enum Enum { + A, + B = -21, + C, + D, + } + assert_eq!(Enum::A, Enum::try_from(0i16).unwrap()); + assert_eq!(Enum::B, Enum::try_from(-21).unwrap()); + assert_eq!(Enum::C, Enum::try_from(-20).unwrap()); + assert_eq!(Enum::D, Enum::try_from(-19).unwrap()); + assert!(Enum::try_from(-1).is_err()); +} + +#[test] +fn enum_without_repr() { + #[derive(TryFrom, Clone, Copy, Debug, Eq, PartialEq)] + #[try_from(repr)] + enum Enum { + A, + B = -21, + C, + D, + } + assert_eq!(Enum::A, Enum::try_from(0isize).unwrap()); + assert_eq!(Enum::B, Enum::try_from(-21).unwrap()); + assert_eq!(Enum::C, Enum::try_from(-20).unwrap()); + assert_eq!(Enum::D, Enum::try_from(-19).unwrap()); + assert!(Enum::try_from(-1).is_err()); +} + +#[test] +fn enum_with_complex_repr() { + #[derive(TryFrom, Clone, Copy, Debug, Eq, PartialEq)] + #[try_from(repr)] + #[repr(align(16), i32)] + enum Enum { + A, + B = -21, + C, + D, + } + assert_eq!(Enum::A, Enum::try_from(0i32).unwrap()); + assert_eq!(Enum::B, Enum::try_from(-21).unwrap()); + assert_eq!(Enum::C, Enum::try_from(-20).unwrap()); + assert_eq!(Enum::D, Enum::try_from(-19).unwrap()); + assert!(Enum::try_from(-1).is_err()); +} + +#[test] +fn test_discriminants_on_enum_with_fields() { + #[derive(TryFrom, Clone, Copy, Debug, Eq, PartialEq)] + #[try_from(repr)] + #[repr(i16)] + enum Enum { + A, + Discriminant = 5, + Field(usize), + Empty {}, + FieldWithDiscriminant(u8, i64) = -14, + EmptyTuple(), + } + + assert_eq!(Enum::A, Enum::try_from(0).unwrap()); + assert_eq!(Enum::Discriminant, Enum::try_from(5).unwrap()); + assert!(Enum::try_from(6).is_err()); + assert_eq!(Enum::Empty {}, Enum::try_from(7).unwrap()); + assert!(Enum::try_from(-14).is_err()); + assert_eq!(Enum::EmptyTuple(), Enum::try_from(-13).unwrap()); +}