1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
//! A declarative macro for type-safe enum-to-numbers conversion. `no-std` supported!
//!
//! ```
//! use numeric_enum_macro::numeric_enum;
//!
//! numeric_enum! {
//!     #[repr(i64)] // repr must go first.
//!     /// Some docs.
//!     ///
//!     /// Multiline docs works too.
//!     #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] // all the attributes are forwarded!
//!     pub enum Lol {
//!         // All the constants must have explicit values assigned!
//!         Kek = 14,
//!         Wow = 87,
//!     }
//! }
//! # use ::core::convert::TryFrom;
//! // Conversion to raw number:
//! assert_eq!(14i64, Lol::Kek.into());
//! // Conversion from raw number:
//! assert_eq!(Ok(Lol::Wow), Lol::try_from(87));
//! // Unknown number:
//! assert_eq!(Err(88), Lol::try_from(88));
//! ```

#![no_std]

/// Declares an enum with a given numeric representation.
///
/// Only explicetly enumerated enum constants are supported.
///
/// Automatically derives `TryFrom<$repr>` and `From<$name>`.
///
/// For examples look at the crate root documentation.
#[macro_export]
macro_rules! numeric_enum {
    (#[repr($repr:ident)]
     $(#$attrs:tt)* $vis:vis enum $name:ident {
        $($enum:ident = $constant:expr),* $(,)?
    } ) => {
        #[repr($repr)]
        $(#$attrs)*
        $vis enum $name {
            $($enum = $constant),*
        }

        impl ::core::convert::TryFrom<$repr> for $name {
            type Error = $repr;

            fn try_from(value: $repr) -> ::core::result::Result<Self, $repr> {
                match value {
                    $($constant => Ok($name :: $enum),)*
                    other => Err(other),
                }
            }
        }

        impl ::core::convert::From<$name> for $repr {
            fn from(value: $name) -> $repr {
                match value {
                    $($name :: $enum => $constant,)*
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    numeric_enum! {
        #[repr(i16)]
        /// Documentation.
        ///
        /// Multiline.
        #[derive(Debug, PartialEq, Eq)]
        pub enum PublicEnum { Zero = 0, Lol = -1 }
    }

    numeric_enum! {
        #[repr(u8)]
        enum TrailingComa { A = 0, B = 1, }
    }

    numeric_enum! {
        #[repr(u8)]
        enum NoTrailingComa { A = 0, B = 1 }
    }

    numeric_enum! {
        #[repr(u8)]
        enum PrivateEnum {
            Zero = 0,
            Lol = 10
        }
    }

    #[test]
    fn it_works() {
        use core::convert::TryFrom;

        assert_eq!(-1i16, PublicEnum::Lol.into());
        assert_eq!(PublicEnum::try_from(0), Ok(PublicEnum::Zero));
        assert_eq!(PublicEnum::try_from(-1), Ok(PublicEnum::Lol));
        assert_eq!(PublicEnum::try_from(2), Err(2));
    }
}