From 04dc34d7a2f526e29a403bc2e864f441084d5aac Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Fri, 3 May 2024 06:56:22 -0400 Subject: [PATCH 1/7] add ConstIntoPrimitive --- num_enum/src/lib.rs | 3 +- num_enum/tests/const_into_primitive.rs | 49 ++++++++++++++++++++++++++ num_enum_derive/src/lib.rs | 47 ++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100755 num_enum/tests/const_into_primitive.rs diff --git a/num_enum/src/lib.rs b/num_enum/src/lib.rs index 86a0b73..caa2314 100644 --- a/num_enum/src/lib.rs +++ b/num_enum/src/lib.rs @@ -8,7 +8,8 @@ #![cfg_attr(not(feature = "std"), no_std)] pub use ::num_enum_derive::{ - Default, FromPrimitive, IntoPrimitive, TryFromPrimitive, UnsafeFromPrimitive, + ConstIntoPrimitive, Default, FromPrimitive, IntoPrimitive, TryFromPrimitive, + UnsafeFromPrimitive, }; use ::core::fmt; diff --git a/num_enum/tests/const_into_primitive.rs b/num_enum/tests/const_into_primitive.rs new file mode 100755 index 0000000..9521e63 --- /dev/null +++ b/num_enum/tests/const_into_primitive.rs @@ -0,0 +1,49 @@ +#![allow(non_upper_case_globals)] + +use ::num_enum::ConstIntoPrimitive; + +// Guard against https://github.com/illicitonion/num_enum/issues/27 +mod alloc {} +mod core {} +mod num_enum {} +mod std {} + +#[derive(ConstIntoPrimitive)] +#[repr(u8)] +enum Enum { + Zero, + One, + Two, +} + +#[test] +fn simple() { + const zero: u8 = Enum::Zero.const_into(); + assert_eq!(zero, 0u8); + + const one: u8 = Enum::One.const_into(); + assert_eq!(one, 1u8); + + const two: u8 = Enum::Two.const_into(); + assert_eq!(two, 2u8); +} + +#[test] +fn catch_all() { + #[derive(Debug, Eq, PartialEq, ConstIntoPrimitive)] + #[repr(u8)] + enum Enum { + Zero = 0, + #[num_enum(catch_all)] + NonZero(u8), + } + + const zero: u8 = Enum::Zero.const_into(); + assert_eq!(zero, 0u8); + + const one: u8 = Enum::NonZero(1u8).const_into(); + assert_eq!(one, 1u8); + + const two: u8 = Enum::NonZero(2u8).const_into(); + assert_eq!(two, 2u8); +} diff --git a/num_enum_derive/src/lib.rs b/num_enum_derive/src/lib.rs index 4fc4927..c6615cf 100644 --- a/num_enum_derive/src/lib.rs +++ b/num_enum_derive/src/lib.rs @@ -62,6 +62,53 @@ pub fn derive_into_primitive(input: TokenStream) -> TokenStream { }) } +/// Generates a `const_into(self)` method which can be used to extract the primitive value from the +/// enum in `const` contexts. +/// +/// ## Allows turning an enum into a primitive. +/// +/// ```rust +/// use num_enum::ConstIntoPrimitive; +/// +/// #[derive(ConstIntoPrimitive)] +/// #[repr(u8)] +/// enum Number { +/// Zero, +/// One, +/// } +/// +/// const zero: u8 = Number::Zero.const_into(); +/// assert_eq!(zero, 0u8); +/// ``` +#[proc_macro_derive(ConstIntoPrimitive, attributes(num_enum, catch_all))] +pub fn derive_const_into_primitive(input: TokenStream) -> TokenStream { + let enum_info = parse_macro_input!(input as EnumInfo); + let catch_all = enum_info.catch_all(); + let name = &enum_info.name; + let repr = &enum_info.repr; + + let body = if let Some(catch_all_ident) = catch_all { + quote! { + match self { + #name::#catch_all_ident(raw) => raw, + rest => unsafe { *(&rest as *const #name as *const Self as *const #repr) } + } + } + } else { + quote! { unsafe { *(&self as *const #name as *const Self as *const #repr) } } + }; + + TokenStream::from(quote! { + impl #name { + #[inline] + pub const fn const_into(self) -> #repr + { + #body + } + } + }) +} + /// Implements `From` for a `#[repr(Primitive)] enum`. /// /// Turning a primitive into an enum with `from`. From 6cd542d979f821fbc3ed07e8b579c260357f27ab Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Fri, 3 May 2024 07:06:23 -0400 Subject: [PATCH 2/7] add ConstFromPrimitive --- num_enum/src/lib.rs | 4 +- num_enum/tests/const_from_primitive.rs | 116 +++++++++++++++++++++++++ num_enum_derive/src/lib.rs | 91 +++++++++++++++++++ 3 files changed, 209 insertions(+), 2 deletions(-) create mode 100755 num_enum/tests/const_from_primitive.rs diff --git a/num_enum/src/lib.rs b/num_enum/src/lib.rs index caa2314..7af9deb 100644 --- a/num_enum/src/lib.rs +++ b/num_enum/src/lib.rs @@ -8,8 +8,8 @@ #![cfg_attr(not(feature = "std"), no_std)] pub use ::num_enum_derive::{ - ConstIntoPrimitive, Default, FromPrimitive, IntoPrimitive, TryFromPrimitive, - UnsafeFromPrimitive, + ConstFromPrimitive, ConstIntoPrimitive, Default, FromPrimitive, IntoPrimitive, + TryFromPrimitive, UnsafeFromPrimitive, }; use ::core::fmt; diff --git a/num_enum/tests/const_from_primitive.rs b/num_enum/tests/const_from_primitive.rs new file mode 100755 index 0000000..ea04119 --- /dev/null +++ b/num_enum/tests/const_from_primitive.rs @@ -0,0 +1,116 @@ +#![allow(non_upper_case_globals)] + +use ::num_enum::ConstFromPrimitive; + +// Guard against https://github.com/illicitonion/num_enum/issues/27 +mod alloc {} +mod core {} +mod num_enum {} +mod std {} + +macro_rules! has_from_primitive_number { + ( $type:ty ) => { + paste::paste! { + #[test] + fn []() { + #[derive(Debug, Eq, PartialEq, ConstFromPrimitive)] + #[repr($type)] + enum Enum { + Zero = 0, + #[num_enum(default)] + NonZero = 1, + } + + const zero: Enum = Enum::const_from(0 as $type); + assert_eq!(zero, Enum::Zero); + + const one: Enum = Enum::const_from(1 as $type); + assert_eq!(one, Enum::NonZero); + + const two: Enum = Enum::const_from(2 as $type); + assert_eq!(two, Enum::NonZero); + } + } + }; +} + +has_from_primitive_number!(u8); +has_from_primitive_number!(u16); +has_from_primitive_number!(u32); +has_from_primitive_number!(u64); +has_from_primitive_number!(usize); +has_from_primitive_number!(i8); +has_from_primitive_number!(i16); +has_from_primitive_number!(i32); +has_from_primitive_number!(i64); +has_from_primitive_number!(isize); +// repr with 128-bit type is unstable +// has_from_primitive_number!(u128); +// has_from_primitive_number!(i128); + +#[test] +fn has_from_primitive_number_standard_default_attribute() { + #[derive(Debug, Eq, PartialEq, ConstFromPrimitive)] + #[repr(u8)] + enum Enum { + Zero = 0, + #[default] + NonZero = 1, + } + + const zero: Enum = Enum::const_from(0_u8); + assert_eq!(zero, Enum::Zero); + + const one: Enum = Enum::const_from(1_u8); + assert_eq!(one, Enum::NonZero); + + const two: Enum = Enum::const_from(2_u8); + assert_eq!(two, Enum::NonZero); +} + +#[test] +fn from_primitive_number_catch_all() { + #[derive(Debug, Eq, PartialEq, ConstFromPrimitive)] + #[repr(u8)] + enum Enum { + Zero = 0, + #[num_enum(catch_all)] + NonZero(u8), + } + + const zero: Enum = Enum::const_from(0_u8); + assert_eq!(zero, Enum::Zero); + + const one: Enum = Enum::const_from(1_u8); + assert_eq!(one, Enum::NonZero(1_u8)); + + const two: Enum = Enum::const_from(2_u8); + assert_eq!(two, Enum::NonZero(2_u8)); +} + +#[cfg(feature = "complex-expressions")] +#[test] +fn from_primitive_number_with_inclusive_range() { + #[derive(Debug, Eq, PartialEq, ConstFromPrimitive)] + #[repr(u8)] + enum Enum { + Zero = 0, + #[num_enum(alternatives = [2..=255])] + NonZero, + } + + const zero: Enum = Enum::const_from(0_u8); + assert_eq!(zero, Enum::Zero); + + const one: Enum = Enum::const_from(1_u8); + assert_eq!(one, Enum::NonZero); + + const two: Enum = Enum::const_from(2_u8); + assert_eq!(two, Enum::NonZero); + + const three: Enum = Enum::const_from(3_u8); + assert_eq!(three, Enum::NonZero); + + const twofivefive: Enum = Enum::const_from(255_u8); + assert_eq!(twofivefive, Enum::NonZero); +} diff --git a/num_enum_derive/src/lib.rs b/num_enum_derive/src/lib.rs index c6615cf..7e9348b 100644 --- a/num_enum_derive/src/lib.rs +++ b/num_enum_derive/src/lib.rs @@ -209,6 +209,97 @@ pub fn derive_from_primitive(input: TokenStream) -> TokenStream { }) } +/// Generates a `Self::const_from()` method which can be used to extract an enum from a primitive +/// value in `const` contexts. +/// +/// Turning a primitive into an enum with `const_from`. +/// ---------------------------------------------- +/// +/// ```rust +/// use num_enum::ConstFromPrimitive; +/// +/// #[derive(Debug, Eq, PartialEq, ConstFromPrimitive)] +/// #[repr(u8)] +/// enum Number { +/// Zero, +/// #[num_enum(default)] +/// NonZero, +/// } +/// +/// const zero: Number = Number::const_from(0u8); +/// assert_eq!(zero, Number::Zero); +/// +/// const one: Number = Number::const_from(1u8); +/// assert_eq!(one, Number::NonZero); +/// +/// const two: Number = Number::const_from(2u8); +/// assert_eq!(two, Number::NonZero); +/// ``` +#[proc_macro_derive(ConstFromPrimitive, attributes(num_enum, default, catch_all))] +pub fn derive_const_from_primitive(input: TokenStream) -> TokenStream { + let enum_info: EnumInfo = parse_macro_input!(input); + let krate = Ident::new(&get_crate_name(), Span::call_site()); + + let is_naturally_exhaustive = enum_info.is_naturally_exhaustive(); + let catch_all_body = match is_naturally_exhaustive { + Ok(is_naturally_exhaustive) => { + if is_naturally_exhaustive { + quote! { unreachable!("exhaustive enum") } + } else if let Some(default_ident) = enum_info.default() { + quote! { Self::#default_ident } + } else if let Some(catch_all_ident) = enum_info.catch_all() { + quote! { Self::#catch_all_ident(number) } + } else { + let span = Span::call_site(); + let message = + "#[derive(num_enum::ConstFromPrimitive)] requires enum to be exhaustive, or a variant marked with `#[default]`, `#[num_enum(default)]`, or `#[num_enum(catch_all)`"; + return syn::Error::new(span, message).to_compile_error().into(); + } + } + Err(err) => { + return err.to_compile_error().into(); + } + }; + + let EnumInfo { + ref name, ref repr, .. + } = enum_info; + + let variant_idents: Vec = enum_info.variant_idents(); + let expression_idents: Vec> = enum_info.expression_idents(); + let variant_expressions: Vec> = enum_info.variant_expressions(); + + debug_assert_eq!(variant_idents.len(), variant_expressions.len()); + + TokenStream::from(quote! { + impl #name { + #[inline] + pub const fn const_from(number: #repr) -> Self { + // Use intermediate const(s) so that enums defined like + // `Two = ONE + 1u8` work properly. + #![allow(non_upper_case_globals)] + #( + #( + const #expression_idents: #repr = #variant_expressions; + )* + )* + #[deny(unreachable_patterns)] + match number { + #( + #( #expression_idents )|* + => Self::#variant_idents, + )* + #[allow(unreachable_patterns)] + _ => #catch_all_body, + } + } + } + + #[doc(hidden)] + impl ::#krate::CannotDeriveBothFromPrimitiveAndTryFromPrimitive for #name {} + }) +} + /// Implements `TryFrom` for a `#[repr(Primitive)] enum`. /// /// Attempting to turn a primitive into an enum with `try_from`. From 5ac2ed7de04d76b2c9faff9dc963f70b89be743f Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Fri, 3 May 2024 07:33:51 -0400 Subject: [PATCH 3/7] add ConstTryFromPrimitive --- num_enum/src/lib.rs | 47 +- num_enum/tests/const_try_from_primitive.rs | 553 +++++++++++++++++++++ num_enum_derive/src/lib.rs | 93 +++- num_enum_derive/src/parsing.rs | 31 +- 4 files changed, 710 insertions(+), 14 deletions(-) create mode 100755 num_enum/tests/const_try_from_primitive.rs diff --git a/num_enum/src/lib.rs b/num_enum/src/lib.rs index 7af9deb..b1ba6ab 100644 --- a/num_enum/src/lib.rs +++ b/num_enum/src/lib.rs @@ -8,8 +8,8 @@ #![cfg_attr(not(feature = "std"), no_std)] pub use ::num_enum_derive::{ - ConstFromPrimitive, ConstIntoPrimitive, Default, FromPrimitive, IntoPrimitive, - TryFromPrimitive, UnsafeFromPrimitive, + ConstFromPrimitive, ConstIntoPrimitive, ConstTryFromPrimitive, Default, FromPrimitive, + IntoPrimitive, TryFromPrimitive, UnsafeFromPrimitive, }; use ::core::fmt; @@ -29,6 +29,13 @@ pub trait TryFromPrimitive: Sized { fn try_from_primitive(number: Self::Primitive) -> Result; } +pub trait ConstTryFromPrimitive: Sized { + type Primitive: Copy + Eq + fmt::Debug; + type Error; + + const NAME: &'static str; +} + pub trait UnsafeFromPrimitive: Sized { type Primitive: Copy + Eq; @@ -85,6 +92,38 @@ impl fmt::Display for TryFromPrimitiveError { #[cfg(feature = "std")] impl ::std::error::Error for TryFromPrimitiveError {} +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct ConstTryFromPrimitiveError { + pub number: Enum::Primitive, +} + +impl ConstTryFromPrimitiveError { + pub const fn new(number: Enum::Primitive) -> Self { + Self { number } + } +} + +impl fmt::Debug for ConstTryFromPrimitiveError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("ConstTryFromPrimitiveError") + .field("number", &self.number) + .finish() + } +} +impl fmt::Display for ConstTryFromPrimitiveError { + fn fmt(&self, stream: &'_ mut fmt::Formatter<'_>) -> fmt::Result { + write!( + stream, + "No discriminant in enum `{name}` matches the value `{input:?}`", + name = Enum::NAME, + input = self.number, + ) + } +} + +#[cfg(feature = "std")] +impl ::std::error::Error for ConstTryFromPrimitiveError {} + // This trait exists to try to give a more clear error message when someone attempts to derive both FromPrimitive and TryFromPrimitive. // This isn't allowed because both end up creating a `TryFrom` implementation. // TryFromPrimitive explicitly implements TryFrom with Error=TryFromPrimitiveError, which conflicts with: @@ -94,3 +133,7 @@ impl ::std::error::Error for TryFromPrimitiveError // It is subject to change in any release regardless of semver. #[doc(hidden)] pub trait CannotDeriveBothFromPrimitiveAndTryFromPrimitive {} + +// Similar, for const primitive coercion methods. +#[doc(hidden)] +pub trait CannotDeriveBothConstFromPrimitiveAndConstTryFromPrimitive {} diff --git a/num_enum/tests/const_try_from_primitive.rs b/num_enum/tests/const_try_from_primitive.rs new file mode 100755 index 0000000..e8e93d7 --- /dev/null +++ b/num_enum/tests/const_try_from_primitive.rs @@ -0,0 +1,553 @@ +#![allow(non_upper_case_globals)] + +use ::num_enum::{ConstTryFromPrimitive, ConstTryFromPrimitiveError}; + +// Guard against https://github.com/illicitonion/num_enum/issues/27 +mod alloc {} +mod core {} +mod num_enum {} +mod std {} + +#[test] +fn simple() { + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(u8)] + enum Enum { + Zero, + One, + Two, + } + + const zero: Result> = Enum::const_try_from(0u8); + assert_eq!(zero, Ok(Enum::Zero)); + + const three: Result> = Enum::const_try_from(3u8); + assert_eq!( + three.unwrap_err().to_string(), + "No discriminant in enum `Enum` matches the value `3`" + ); +} + +#[test] +fn even() { + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(u8)] + enum Enum { + Zero = 0, + Two = 2, + Four = 4, + } + + const zero: Result> = Enum::const_try_from(0u8); + assert_eq!(zero, Ok(Enum::Zero)); + + const one: Result> = Enum::const_try_from(1u8); + assert_eq!( + one.unwrap_err().to_string(), + "No discriminant in enum `Enum` matches the value `1`" + ); + + const two: Result> = Enum::const_try_from(2u8); + assert_eq!(two, Ok(Enum::Two)); + + const three: Result> = Enum::const_try_from(3u8); + assert_eq!( + three.unwrap_err().to_string(), + "No discriminant in enum `Enum` matches the value `3`" + ); + + const four: Result> = Enum::const_try_from(4u8); + assert_eq!(four, Ok(Enum::Four)); +} + +#[test] +fn skipped_value() { + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(u8)] + enum Enum { + Zero, + One, + Three = 3, + Four, + } + + const zero: Result> = Enum::const_try_from(0u8); + assert_eq!(zero, Ok(Enum::Zero)); + + const one: Result> = Enum::const_try_from(1u8); + assert_eq!(one, Ok(Enum::One)); + + const two: Result> = Enum::const_try_from(2u8); + assert_eq!( + two.unwrap_err().to_string(), + "No discriminant in enum `Enum` matches the value `2`" + ); + + const three: Result> = Enum::const_try_from(3u8); + assert_eq!(three, Ok(Enum::Three)); + + const four: Result> = Enum::const_try_from(4u8); + assert_eq!(four, Ok(Enum::Four)); +} + +#[test] +fn wrong_order() { + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(u8)] + enum Enum { + Four = 4, + Three = 3, + Zero = 0, + One, // Zero + 1 + } + + const zero: Result> = Enum::const_try_from(0u8); + assert_eq!(zero, Ok(Enum::Zero)); + + const one: Result> = Enum::const_try_from(1u8); + assert_eq!(one, Ok(Enum::One)); + + const two: Result> = Enum::const_try_from(2u8); + assert_eq!( + two.unwrap_err().to_string(), + "No discriminant in enum `Enum` matches the value `2`" + ); + + const three: Result> = Enum::const_try_from(3u8); + assert_eq!(three, Ok(Enum::Three)); + + const four: Result> = Enum::const_try_from(4u8); + assert_eq!(four, Ok(Enum::Four)); +} + +#[test] +fn negative_values() { + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(i8)] + enum Enum { + MinusTwo = -2, + MinusOne = -1, + Zero = 0, + One = 1, + Two = 2, + } + + const minus_two: Result> = Enum::const_try_from(-2i8); + assert_eq!(minus_two, Ok(Enum::MinusTwo)); + + const minus_one: Result> = Enum::const_try_from(-1i8); + assert_eq!(minus_one, Ok(Enum::MinusOne)); + + const zero: Result> = Enum::const_try_from(0i8); + assert_eq!(zero, Ok(Enum::Zero)); + + const one: Result> = Enum::const_try_from(1i8); + assert_eq!(one, Ok(Enum::One)); + + const two: Result> = Enum::const_try_from(2i8); + assert_eq!(two, Ok(Enum::Two)); +} + +#[test] +fn discriminant_expressions() { + const ONE: u8 = 1; + + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(u8)] + enum Enum { + Zero, + One = ONE, + Two, + Four = 4u8, + Five, + Six = ONE + ONE + 2u8 + 2, + } + + const zero: Result> = Enum::const_try_from(0u8); + assert_eq!(zero, Ok(Enum::Zero)); + + const one: Result> = Enum::const_try_from(1u8); + assert_eq!(one, Ok(Enum::One)); + + const two: Result> = Enum::const_try_from(2u8); + assert_eq!(two, Ok(Enum::Two)); + + const three: Result> = Enum::const_try_from(3u8); + assert_eq!( + three.unwrap_err().to_string(), + "No discriminant in enum `Enum` matches the value `3`", + ); + + const four: Result> = Enum::const_try_from(4u8); + assert_eq!(four, Ok(Enum::Four)); + + const five: Result> = Enum::const_try_from(5u8); + assert_eq!(five, Ok(Enum::Five)); + + const six: Result> = Enum::const_try_from(6u8); + assert_eq!(six, Ok(Enum::Six)); +} + +#[cfg(feature = "complex-expressions")] +mod complex { + use num_enum::ConstTryFromPrimitive; + use std::convert::TryInto; + + const ONE: u8 = 1; + + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(u8)] + enum Enum { + Zero, + One = ONE, + Two, + Four = 4u8, + Five, + Six = ONE + ONE + 2u8 + 2, + Seven = (7, 2).0, + } + + #[test] + fn different_values() { + const zero: Result> = Enum::const_try_from(0u8); + assert_eq!(zero, Ok(Enum::Zero)); + + const one: Result> = Enum::const_try_from(1u8); + assert_eq!(one, Ok(Enum::One)); + + const two: Result> = Enum::const_try_from(2u8); + assert_eq!(two, Ok(Enum::Two)); + + const three: Result> = Enum::const_try_from(3u8); + assert_eq!( + three.unwrap_err().to_string(), + "No discriminant in enum `Enum` matches the value `3`", + ); + + const four: Result> = Enum::const_try_from(4u8); + assert_eq!(four, Ok(Enum::Four)); + + const five: Result> = Enum::const_try_from(5u8); + assert_eq!(five, Ok(Enum::Five)); + + const six: Result> = Enum::const_try_from(6u8); + assert_eq!(six, Ok(Enum::Six)); + + const seven: Result> = Enum::const_try_from(7u8); + assert_eq!(seven, Ok(Enum::Seven)); + } + + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(u8)] + enum EnumWithExclusiveRange { + Zero = 0, + #[num_enum(alternatives = [2..4])] + OneOrTwoOrThree, + } + + #[test] + fn different_values_with_exclusive_range() { + const zero: Result = Enum::const_try_from(0u8); + assert_eq!(zero, Ok(EnumWithExclusiveRange::Zero)); + + const one: Result = Enum::const_try_from(1u8); + assert_eq!(one, Ok(EnumWithExclusiveRange::OneOrTwoOrThree)); + + const two: Result = Enum::const_try_from(2u8); + assert_eq!(two, Ok(EnumWithExclusiveRange::OneOrTwoOrThree)); + + const three: Result = Enum::const_try_from(3u8); + assert_eq!(three, Ok(EnumWithExclusiveRange::OneOrTwoOrThree)); + + const four: Result = Enum::const_try_from(4u8); + assert_eq!( + four.unwrap_err().to_string(), + "No discriminant in enum `EnumWithExclusiveRange` matches the value `4`", + ); + } + + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(u8)] + enum EnumWithInclusiveRange { + Zero = 0, + #[num_enum(alternatives = [2..=3])] + OneOrTwoOrThree, + } + + #[test] + fn different_values_with_inclusive_range() { + const zero: Result = Enum::const_try_from(0u8); + assert_eq!(zero, Ok(EnumWithInclusiveRange::Zero)); + + const one: Result = Enum::const_try_from(1u8); + assert_eq!(one, Ok(EnumWithInclusiveRange::OneOrTwoOrThree)); + + const two: Result = Enum::const_try_from(2u8); + assert_eq!(two, Ok(EnumWithInclusiveRange::OneOrTwoOrThree)); + + const three: Result = Enum::const_try_from(3u8); + assert_eq!(three, Ok(EnumWithInclusiveRange::OneOrTwoOrThree)); + + const four: Result = Enum::const_try_from(4u8); + assert_eq!( + four.unwrap_err().to_string(), + "No discriminant in enum `EnumWithInclusiveRange` matches the value `4`", + ); + } +} + +#[test] +fn missing_trailing_comma() { + #[rustfmt::skip] +#[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] +#[repr(u8)] +enum Enum { + Zero, + One +} + + const zero: Result> = Enum::const_try_from(0u8); + assert_eq!(zero, Ok(Enum::Zero)); + + const one: Result> = Enum::const_try_from(1u8); + assert_eq!(one, Ok(Enum::One)); + + const two: Result> = Enum::const_try_from(2u8); + assert_eq!( + two.unwrap_err().to_string(), + "No discriminant in enum `Enum` matches the value `2`" + ); +} + +#[test] +fn ignores_extra_attributes() { + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[allow(unused)] + #[repr(u8)] + enum Enum { + Zero, + #[allow(unused)] + One, + } + + const zero: Result> = Enum::const_try_from(0u8); + assert_eq!(zero, Ok(Enum::Zero)); + + const one: Result> = Enum::const_try_from(1u8); + assert_eq!(one, Ok(Enum::One)); + + const two: Result> = Enum::const_try_from(2u8); + assert_eq!( + two.unwrap_err().to_string(), + "No discriminant in enum `Enum` matches the value `2`" + ); +} + +#[test] +fn visibility_is_fine() { + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(u8)] + pub(crate) enum Enum { + Zero, + One, + } + + const zero: Result> = Enum::const_try_from(0u8); + assert_eq!(zero, Ok(Enum::Zero)); + + const one: Result> = Enum::const_try_from(1u8); + assert_eq!(one, Ok(Enum::One)); + + const two: Result> = Enum::const_try_from(2u8); + assert_eq!( + two.unwrap_err().to_string(), + "No discriminant in enum `Enum` matches the value `2`" + ); +} + +#[test] +fn error_variant_is_allowed() { + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(u8)] + pub enum Enum { + Ok, + Error, + } + + const ok: Result> = Enum::const_try_from(0u8); + assert_eq!(ok, Ok(Enum::Ok)); + + const err: Result> = Enum::const_try_from(1u8); + assert_eq!(err, Ok(Enum::Error)); + + const unknown: Result> = Enum::const_try_from(2u8); + assert_eq!( + unknown.unwrap_err().to_string(), + "No discriminant in enum `Enum` matches the value `2`" + ); +} + +#[test] +fn alternative_values() { + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(i8)] + enum Enum { + Zero = 0, + #[num_enum(alternatives = [-1, 2, 3])] + OneTwoThreeOrMinusOne = 1, + } + + const minus_one: Result> = Enum::const_try_from(-1i8); + assert_eq!(minus_one, Ok(Enum::OneTwoThreeOrMinusOne)); + + const zero: Result> = Enum::const_try_from(0i8); + assert_eq!(zero, Ok(Enum::Zero)); + + const one: Result> = Enum::const_try_from(1i8); + assert_eq!(one, Ok(Enum::OneTwoThreeOrMinusOne)); + + const two: Result> = Enum::const_try_from(2i8); + assert_eq!(two, Ok(Enum::OneTwoThreeOrMinusOne)); + + const three: Result> = Enum::const_try_from(3i8); + assert_eq!(three, Ok(Enum::OneTwoThreeOrMinusOne)); + + const four: Result> = Enum::const_try_from(4i8); + assert_eq!( + four.unwrap_err().to_string(), + "No discriminant in enum `Enum` matches the value `4`" + ); +} + +#[test] +fn default_value() { + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(u8)] + enum Enum { + Zero = 0, + One = 1, + #[num_enum(default)] + Other = 2, + } + + const zero: Result> = Enum::const_try_from(0u8); + assert_eq!(zero, Ok(Enum::Zero)); + + const one: Result> = Enum::const_try_from(1u8); + assert_eq!(one, Ok(Enum::One)); + + const two: Result> = Enum::const_try_from(2u8); + assert_eq!(two, Ok(Enum::Other)); + + const max_value: Result> = + Enum::const_try_from(u8::max_value()); + assert_eq!( + max_value.unwrap_err().to_string(), + "No discriminant in enum `Enum` matches the value `255`" + ); +} + +#[test] +fn alternative_values_and_default_value() { + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(u8)] + enum Enum { + #[num_enum(default)] + Zero = 0, + One = 1, + #[num_enum(alternatives = [3])] + TwoOrThree = 2, + Four = 4, + } + + const zero: Result> = Enum::const_try_from(0u8); + assert_eq!(zero, Ok(Enum::Zero)); + + const one: Result> = Enum::const_try_from(1u8); + assert_eq!(one, Ok(Enum::One)); + + const two: Result> = Enum::const_try_from(2u8); + assert_eq!(two, Ok(Enum::TwoOrThree)); + + const three: Result> = Enum::const_try_from(3u8); + assert_eq!(three, Ok(Enum::TwoOrThree)); + + const four: Result> = Enum::const_try_from(4u8); + assert_eq!(four, Ok(Enum::Four)); + + const five: Result> = Enum::const_try_from(5u8); + assert_eq!( + five.unwrap_err().to_string(), + "No discriminant in enum `Enum` matches the value `5`" + ); +} + +#[test] +fn try_from_primitive_number() { + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[repr(u8)] + enum Enum { + #[num_enum(default)] + Whatever = 0, + } + + // #[derive(FromPrimitive)] generates implementations for the following traits: + // + // - `ConstTryFromPrimitive` + // - `TryFrom` + + const try_from_primitive: Result> = + Enum::const_try_from(0_u8); + assert_eq!(try_from_primitive, Ok(Enum::Whatever)); + + const try_from: Result> = Enum::const_try_from(0_u8); + assert_eq!(try_from, Ok(Enum::Whatever)); +} + +#[test] +fn custom_error() { + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[num_enum(error_type(name = CustomError, constructor = CustomError::new))] + #[repr(u8)] + enum FirstNumber { + Zero, + One, + Two, + } + + #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] + #[num_enum(error_type(constructor = CustomError::new, name = CustomError))] + #[repr(u8)] + enum SecondNumber { + Zero, + One, + Two, + } + + #[derive(Debug, PartialEq, Eq)] + struct CustomError { + bad_value: u8, + } + + impl CustomError { + const fn new(value: u8) -> CustomError { + CustomError { bad_value: value } + } + } + + let zero: Result = FirstNumber::const_try_from(0u8); + assert_eq!(zero, Ok(FirstNumber::Zero)); + + let three: Result = FirstNumber::const_try_from(3u8); + assert_eq!(three.unwrap_err(), CustomError { bad_value: 3u8 }); + + let three: Result = SecondNumber::const_try_from(3u8); + assert_eq!(three.unwrap_err(), CustomError { bad_value: 3u8 }); +} + +// #[derive(FromPrimitive)] generates implementations for the following traits: +// +// - `FromPrimitive` +// - `From` +// - `ConstTryFromPrimitive` +// - `TryFrom` diff --git a/num_enum_derive/src/lib.rs b/num_enum_derive/src/lib.rs index 7e9348b..c066bf6 100644 --- a/num_enum_derive/src/lib.rs +++ b/num_enum_derive/src/lib.rs @@ -213,7 +213,7 @@ pub fn derive_from_primitive(input: TokenStream) -> TokenStream { /// value in `const` contexts. /// /// Turning a primitive into an enum with `const_from`. -/// ---------------------------------------------- +/// --------------------------------------------------- /// /// ```rust /// use num_enum::ConstFromPrimitive; @@ -296,7 +296,7 @@ pub fn derive_const_from_primitive(input: TokenStream) -> TokenStream { } #[doc(hidden)] - impl ::#krate::CannotDeriveBothFromPrimitiveAndTryFromPrimitive for #name {} + impl ::#krate::CannotDeriveBothConstFromPrimitiveAndConstTryFromPrimitive for #name {} }) } @@ -398,6 +398,95 @@ pub fn derive_try_from_primitive(input: TokenStream) -> TokenStream { }) } +/// Generates a `Self::const_try_from()` method which can be used to extract an enum from +/// a primitive value in `const` contexts. +/// +/// Attempting to turn a primitive into an enum with `const_try_from`. +/// ------------------------------------------------------------------ +/// +/// ```rust +/// use num_enum::ConstTryFromPrimitive; +/// +/// #[derive(Debug, Eq, PartialEq, ConstTryFromPrimitive)] +/// #[repr(u8)] +/// enum Number { +/// Zero, +/// One, +/// } +/// +/// let zero: Number = Number::const_try_from(0u8); +/// assert_eq!(zero, Ok(Number::Zero)); +/// +/// let three: Number = Number::const_try_from(3u8); +/// assert_eq!( +/// three.unwrap_err().to_string(), +/// "No discriminant in enum `Number` matches the value `3`", +/// ); +/// ``` +#[proc_macro_derive(ConstTryFromPrimitive, attributes(num_enum))] +pub fn derive_const_try_from_primitive(input: TokenStream) -> TokenStream { + let enum_info: EnumInfo = parse_macro_input!(input); + let krate = Ident::new(&get_crate_name(), Span::call_site()); + + let EnumInfo { + ref name, + ref repr, + ref error_type_info, + .. + } = enum_info; + + let variant_idents: Vec = enum_info.variant_idents(); + let expression_idents: Vec> = enum_info.expression_idents(); + let variant_expressions: Vec> = enum_info.variant_expressions(); + + debug_assert_eq!(variant_idents.len(), variant_expressions.len()); + + let error_type = &error_type_info.name; + let error_constructor = &error_type_info.constructor; + + TokenStream::from(quote! { + impl ::#krate::ConstTryFromPrimitive for #name { + type Primitive = #repr; + type Error = #error_type; + + const NAME: &'static str = stringify!(#name); + } + + impl #name { + #[inline] + pub const fn const_try_from ( + number: #repr, + ) -> ::core::result::Result< + Self, + #error_type + > { + // Use intermediate const(s) so that enums defined like + // `Two = ONE + 1u8` work properly. + #![allow(non_upper_case_globals)] + #( + #( + const #expression_idents: #repr = #variant_expressions; + )* + )* + #[deny(unreachable_patterns)] + match number { + #( + #( #expression_idents )|* + => ::core::result::Result::Ok(Self::#variant_idents), + )* + #[allow(unreachable_patterns)] + _ => ::core::result::Result::Err( + #error_constructor ( number ) + ), + } + } + } + + #[doc(hidden)] + impl ::#krate::CannotDeriveBothConstFromPrimitiveAndConstTryFromPrimitive for #name {} + }) +} + /// Generates a `unsafe fn unchecked_transmute_from(number: Primitive) -> Self` /// associated function. /// diff --git a/num_enum_derive/src/parsing.rs b/num_enum_derive/src/parsing.rs index cfc0b46..66f3824 100644 --- a/num_enum_derive/src/parsing.rs +++ b/num_enum_derive/src/parsing.rs @@ -10,14 +10,14 @@ use syn::{ LitInt, Meta, Path, Result, UnOp, }; -pub(crate) struct EnumInfo { +pub(crate) struct EnumInfo { pub(crate) name: Ident, pub(crate) repr: Ident, pub(crate) variants: Vec, pub(crate) error_type_info: ErrorType, } -impl EnumInfo { +impl EnumInfo { /// Returns whether the number of variants (ignoring defaults, catch-alls, etc) is the same as /// the capacity of the repr. pub(crate) fn is_naturally_exhaustive(&self) -> Result { @@ -131,7 +131,7 @@ impl EnumInfo { } } -impl Parse for EnumInfo { +impl Parse for EnumInfo { fn parse(input: ParseStream) -> Result { Ok({ let input: DeriveInput = input.parse()?; @@ -385,13 +385,24 @@ impl Parse for EnumInfo { let error_type_info = maybe_error_type.unwrap_or_else(|| { let crate_name = Ident::new(&get_crate_name(), Span::call_site()); - ErrorType { - name: parse_quote! { - ::#crate_name::TryFromPrimitiveError - }, - constructor: parse_quote! { - ::#crate_name::TryFromPrimitiveError::::new - }, + if IS_CONST { + ErrorType { + name: parse_quote! { + ::#crate_name::ConstTryFromPrimitiveError + }, + constructor: parse_quote! { + ::#crate_name::ConstTryFromPrimitiveError::::new + }, + } + } else { + ErrorType { + name: parse_quote! { + ::#crate_name::TryFromPrimitiveError + }, + constructor: parse_quote! { + ::#crate_name::TryFromPrimitiveError::::new + }, + } } }); From 88ce96f13782c3a47daa64c7ccee998b0cddd2fb Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Fri, 3 May 2024 07:41:08 -0400 Subject: [PATCH 4/7] add ConstDefault --- num_enum/src/lib.rs | 4 +-- num_enum/tests/const_default.rs | 37 +++++++++++++++++++++++++++ num_enum_derive/src/lib.rs | 44 +++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100755 num_enum/tests/const_default.rs diff --git a/num_enum/src/lib.rs b/num_enum/src/lib.rs index b1ba6ab..f4562d6 100644 --- a/num_enum/src/lib.rs +++ b/num_enum/src/lib.rs @@ -8,8 +8,8 @@ #![cfg_attr(not(feature = "std"), no_std)] pub use ::num_enum_derive::{ - ConstFromPrimitive, ConstIntoPrimitive, ConstTryFromPrimitive, Default, FromPrimitive, - IntoPrimitive, TryFromPrimitive, UnsafeFromPrimitive, + ConstDefault, ConstFromPrimitive, ConstIntoPrimitive, ConstTryFromPrimitive, Default, + FromPrimitive, IntoPrimitive, TryFromPrimitive, UnsafeFromPrimitive, }; use ::core::fmt; diff --git a/num_enum/tests/const_default.rs b/num_enum/tests/const_default.rs new file mode 100755 index 0000000..c71eb33 --- /dev/null +++ b/num_enum/tests/const_default.rs @@ -0,0 +1,37 @@ +#![allow(non_upper_case_globals)] + +// Guard against https://github.com/illicitonion/num_enum/issues/27 +mod alloc {} +mod core {} +mod num_enum {} +mod std {} + +#[test] +fn default() { + #[derive(Debug, Eq, PartialEq, ::num_enum::ConstDefault)] + #[repr(u8)] + enum Enum { + #[allow(unused)] + Zero = 0, + #[num_enum(default)] + NonZero = 1, + } + + const nz: Enum = Enum::const_default(); + assert_eq!(Enum::NonZero, nz); +} + +#[test] +fn default_standard_default_attribute() { + #[derive(Debug, Eq, PartialEq, ::num_enum::ConstDefault)] + #[repr(u8)] + enum Enum { + #[allow(unused)] + Zero = 0, + #[default] + NonZero = 1, + } + + const nz: Enum = Enum::const_default(); + assert_eq!(Enum::NonZero, nz); +} diff --git a/num_enum_derive/src/lib.rs b/num_enum_derive/src/lib.rs index c066bf6..af79317 100644 --- a/num_enum_derive/src/lib.rs +++ b/num_enum_derive/src/lib.rs @@ -588,3 +588,47 @@ pub fn derive_default(stream: TokenStream) -> TokenStream { } }) } + +/// Generates a `const_default() -> Self` method to obtain the default enum value in const contexts. +/// +/// Whichever variant has the `#[default]` or `#[num_enum(default)]` attribute will be returned. +/// ---------------------------------------------- +/// +/// ```rust +/// #[derive(Debug, Eq, PartialEq, num_enum::ConstDefault)] +/// #[repr(u8)] +/// enum Number { +/// Zero, +/// #[default] +/// One, +/// } +/// +/// const one: Number = Number::const_default(); +/// assert_eq!(one, Number::One); +/// assert_eq!(Number::One, Number::const_default()); +/// ``` +#[proc_macro_derive(ConstDefault, attributes(num_enum, default))] +pub fn derive_const_default(stream: TokenStream) -> TokenStream { + let enum_info = parse_macro_input!(stream as EnumInfo); + + let default_ident = match enum_info.default() { + Some(ident) => ident, + None => { + let span = Span::call_site(); + let message = + "#[derive(num_enum::ConstDefault)] requires enum to be exhaustive, or a variant marked with `#[default]` or `#[num_enum(default)]`"; + return syn::Error::new(span, message).to_compile_error().into(); + } + }; + + let EnumInfo { ref name, .. } = enum_info; + + TokenStream::from(quote! { + impl #name { + #[inline] + pub const fn const_default() -> Self { + Self::#default_ident + } + } + }) +} From 14d31b911cd4e07f7c00b40a758c06b70e014ae4 Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Sat, 18 May 2024 13:22:58 -0400 Subject: [PATCH 5/7] fix unnecessary `as` --- num_enum_derive/src/lib.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/num_enum_derive/src/lib.rs b/num_enum_derive/src/lib.rs index af79317..cc66520 100644 --- a/num_enum_derive/src/lib.rs +++ b/num_enum_derive/src/lib.rs @@ -44,11 +44,11 @@ pub fn derive_into_primitive(input: TokenStream) -> TokenStream { quote! { match enum_value { #name::#catch_all_ident(raw) => raw, - rest => unsafe { *(&rest as *const #name as *const Self) } + rest => unsafe { *(&rest as *const #name as *const #repr) }, } } } else { - quote! { enum_value as Self } + quote! { enum_value as #repr } }; TokenStream::from(quote! { @@ -62,6 +62,20 @@ pub fn derive_into_primitive(input: TokenStream) -> TokenStream { }) } + + +/* enum ConversionPlane { */ +/* ConstFn, */ +/* NormalFn, */ +/* } */ + +/* enum ConversionDirection { */ +/* From, */ +/* To, */ +/* } */ + +/* fn catch_all_clause(); */ + /// Generates a `const_into(self)` method which can be used to extract the primitive value from the /// enum in `const` contexts. /// @@ -91,11 +105,11 @@ pub fn derive_const_into_primitive(input: TokenStream) -> TokenStream { quote! { match self { #name::#catch_all_ident(raw) => raw, - rest => unsafe { *(&rest as *const #name as *const Self as *const #repr) } + rest => unsafe { *(&rest as *const #name as *const #repr) } } } } else { - quote! { unsafe { *(&self as *const #name as *const Self as *const #repr) } } + quote! { self as #repr } }; TokenStream::from(quote! { From f2798c2cc2d62ebeecc1658cf2a31720d11ea19a Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Sat, 18 May 2024 13:51:51 -0400 Subject: [PATCH 6/7] move *Into* logic into common helper methods --- num_enum_derive/src/lib.rs | 50 ++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/num_enum_derive/src/lib.rs b/num_enum_derive/src/lib.rs index cc66520..ee295da 100644 --- a/num_enum_derive/src/lib.rs +++ b/num_enum_derive/src/lib.rs @@ -40,19 +40,15 @@ pub fn derive_into_primitive(input: TokenStream) -> TokenStream { let name = &enum_info.name; let repr = &enum_info.repr; + let arg = Ident::new("enum_value", Span::call_site()); let body = if let Some(catch_all_ident) = catch_all { - quote! { - match enum_value { - #name::#catch_all_ident(raw) => raw, - rest => unsafe { *(&rest as *const #name as *const #repr) }, - } - } + catch_all_clause(arg, name, repr, catch_all_ident) } else { - quote! { enum_value as #repr } + exhaustive_clause(arg, repr) }; TokenStream::from(quote! { - impl From<#name> for #repr { + impl ::core::convert::From<#name> for #repr { #[inline] fn from (enum_value: #name) -> Self { @@ -62,19 +58,23 @@ pub fn derive_into_primitive(input: TokenStream) -> TokenStream { }) } +fn catch_all_clause( + arg: Ident, + this_name: &Ident, + that_repr: &Ident, + catch_all_ident: &Ident, +) -> proc_macro2::TokenStream { + quote! { + match #arg { + #this_name::#catch_all_ident(raw) => raw, + rest => unsafe { *(&rest as *const #this_name as *const #that_repr) } + } + } +} - -/* enum ConversionPlane { */ -/* ConstFn, */ -/* NormalFn, */ -/* } */ - -/* enum ConversionDirection { */ -/* From, */ -/* To, */ -/* } */ - -/* fn catch_all_clause(); */ +fn exhaustive_clause(arg: Ident, that_repr: &Ident) -> proc_macro2::TokenStream { + quote! { #arg as #that_repr } +} /// Generates a `const_into(self)` method which can be used to extract the primitive value from the /// enum in `const` contexts. @@ -101,15 +101,11 @@ pub fn derive_const_into_primitive(input: TokenStream) -> TokenStream { let name = &enum_info.name; let repr = &enum_info.repr; + let arg = Ident::new("self", Span::call_site()); let body = if let Some(catch_all_ident) = catch_all { - quote! { - match self { - #name::#catch_all_ident(raw) => raw, - rest => unsafe { *(&rest as *const #name as *const #repr) } - } - } + catch_all_clause(arg, name, repr, catch_all_ident) } else { - quote! { self as #repr } + exhaustive_clause(arg, repr) }; TokenStream::from(quote! { From c49b8858d2da29077b77518af349d7693cbf5d01 Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Sat, 18 May 2024 14:17:19 -0400 Subject: [PATCH 7/7] impl IntoPrimitive when ConstIntoPrimitive is selected --- num_enum/tests/const_into_primitive.rs | 3 +++ num_enum_derive/src/lib.rs | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/num_enum/tests/const_into_primitive.rs b/num_enum/tests/const_into_primitive.rs index 9521e63..f225eb5 100755 --- a/num_enum/tests/const_into_primitive.rs +++ b/num_enum/tests/const_into_primitive.rs @@ -20,12 +20,15 @@ enum Enum { fn simple() { const zero: u8 = Enum::Zero.const_into(); assert_eq!(zero, 0u8); + assert_eq!(zero, Enum::Zero.into()); const one: u8 = Enum::One.const_into(); assert_eq!(one, 1u8); + assert_eq!(one, Enum::One.into()); const two: u8 = Enum::Two.const_into(); assert_eq!(two, 2u8); + assert_eq!(two, Enum::Two.into()); } #[test] diff --git a/num_enum_derive/src/lib.rs b/num_enum_derive/src/lib.rs index ee295da..b23b903 100644 --- a/num_enum_derive/src/lib.rs +++ b/num_enum_derive/src/lib.rs @@ -79,6 +79,9 @@ fn exhaustive_clause(arg: Ident, that_repr: &Ident) -> proc_macro2::TokenStream /// Generates a `const_into(self)` method which can be used to extract the primitive value from the /// enum in `const` contexts. /// +/// Also generates the equivalent of `IntoPrimitive`, so `From` and `Into` may be used in +/// non-const contexts. +/// /// ## Allows turning an enum into a primitive. /// /// ```rust @@ -91,11 +94,15 @@ fn exhaustive_clause(arg: Ident, that_repr: &Ident) -> proc_macro2::TokenStream /// One, /// } /// -/// const zero: u8 = Number::Zero.const_into(); -/// assert_eq!(zero, 0u8); +/// const ZERO: u8 = Number::Zero.const_into(); +/// assert_eq!(ZERO, 0u8); +/// // Non-const expression: +/// let one: u8 = Number::One.into(); +/// assert_eq!(one, 1u8); /// ``` #[proc_macro_derive(ConstIntoPrimitive, attributes(num_enum, catch_all))] pub fn derive_const_into_primitive(input: TokenStream) -> TokenStream { + let input2 = input.clone(); let enum_info = parse_macro_input!(input as EnumInfo); let catch_all = enum_info.catch_all(); let name = &enum_info.name; @@ -108,7 +115,11 @@ pub fn derive_const_into_primitive(input: TokenStream) -> TokenStream { exhaustive_clause(arg, repr) }; + let non_const_impl: proc_macro2::TokenStream = derive_into_primitive(input2).into(); + TokenStream::from(quote! { + #non_const_impl + impl #name { #[inline] pub const fn const_into(self) -> #repr