const_enum/
lib.rs

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/*
 * SPDX-FileCopyrightText: 2024 DorotaC
 *
 * SPDX-License-Identifier: MPL-2.0 OR LGPL-2.1-or-later
 */

/*! 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_camel;
/// mod sys {
///     pub const NAME_B: u32 = 1;
/// }
/// 
/// const_enum_camel! {
/// // 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_camel {
    {
        $( #[$enummeta:meta] )*
        enum $name:ident, $path:path, $prefix:ident {
        $(
            $( #[$meta:meta] )*
            $c:ident
        ),* $(,)?
    }} => {
        $crate::paste! {
            $( #[$enummeta] )*
            #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
            #[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,
                }
            }
        }
    };
}

/// Turns FFI constants into an enum, freely convertible to and from u32.
/// 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 {
    {
        $( #[$enummeta:meta] )*
        enum $name:ident, $path:path, $prefix:ident {
        $(
            $( #[$meta:meta] )*
            $c:ident
        ),* $(,)?
    }} => {
        $crate::paste! {
            $( #[$enummeta] )*
            #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
            #[repr(u32)]
            pub enum $name {
                $(
                    $( #[$meta] )*
                    [<$c>],
                )*
                /// 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>] },
                    )*
                    other => Self::Other(other),
                }
            }
        }
        impl From<$name> for u32 {
            fn from(v: $name) -> Self {
                match v {
                    $(
                        $crate::paste! { $name::[<$c>] } => 
                        $crate::paste! { $path::[<$prefix$c>] },
                    )*
                    $name::Other(v) => v,
                }
            }
        }
        impl $name {
            /// Iterates all known valid values
            pub fn iter() -> impl Iterator<Item=Self> {
                [$(
                    $crate::paste! { Self::[<$c>] },
                )*].into_iter()
            }
        }
    };
}

#[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)));
    }
}