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
/* Copyright (C) 2024 DorotaC
* SPDX-License-Identifier: LGPL-2.1-or-later OR MPL-2.0
*/
/*! The const_enum macro */
#![no_std]
// The macro will get expanded within the context of the consuming crate, which may not have paste imported. This re-export allows the expanded macro to find `paste` and use it. Direct use by the consuming crate is discouraged. This re-export should be treated as an implementation detail.
#[doc(hidden)]
pub use paste::paste;
/// Turns FFI constants into an enum, freely convertible to and from u32.
/// Enum variants are turned into CamelCase identifiers.
/// An extra variant Other(u32) exists to contain values not known to this num.
/// The parameters are: Enum name, path to constants, constant prefix.
///
/// ```
/// use const_enum::const_enum;
/// mod sys {
/// pub const NAME_B: u32 = 1;
/// }
///
/// const_enum! {
/// // you can also use "this" as the path if the constants are in the same scope. It doesn't work in doctests, though, so I added the "mod sys".
/// enum TestEnum, sys, NAME_ {
/// B,
/// }
/// }
/// ```
#[macro_export]
macro_rules! const_enum {
{enum $name:ident, $path:path, $prefix:ident {
$(
$( #[$meta:meta] )*
$c:ident
),* $(,)?
}} => {
$crate::paste! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum $name {
$(
$( #[$meta] )*
[<$c:camel>],
)*
/// Any value. Methods on this type will always prefer to create one of the above variants. Values covered by them can only be created by instantiating this variant explicitly.
Other(u32),
}
}
impl From<u32> for $name {
fn from(v: u32) -> Self {
match v {
$(
$crate::paste! { $path::[<$prefix$c>] }
=> $crate::paste! { Self::[<$c:camel>] },
)*
other => Self::Other(other),
}
}
}
impl From<$name> for u32 {
fn from(v: $name) -> Self {
match v {
$(
$crate::paste! { $name::[<$c:camel>] } =>
$crate::paste! { $path::[<$prefix$c>] },
)*
$name::Other(v) => v,
}
}
}
};
}
#[cfg(test)]
mod test {
const NAME_B: u32 = 1;
const_enum! {
enum TestEnum, self, NAME_ {
B,
}
}
#[test]
fn test_from() {
assert_eq!(TestEnum::from(1), TestEnum::B);
assert_eq!(TestEnum::from(2), TestEnum::Other(2));
}
#[test]
fn test_to() {
assert_eq!(u32::from(TestEnum::B), 1);
assert_eq!(u32::from(TestEnum::Other(2)), 2);
}
#[test]
fn test_bad_other() {
// It's undesired but allowed to construct an Other with a value that better matches another enum variant
assert_eq!(u32::from(TestEnum::B), u32::from(TestEnum::Other(1)));
}
}