assert_enum_variants/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3
4/// This macro performs a compile-time check to validate that all variants of an enum
5/// are as provided in the macro invocation.
6///
7/// # Example
8///
9/// ```rust
10/// use assert_enum_variants::assert_enum_variants;
11///
12/// #[allow(dead_code)]
13/// pub enum MyEnum {
14///   A,
15///   B(u32),
16///   C {
17///     a: String,
18///     b: u32,
19///   },
20/// }
21///
22/// // This will compile successfully
23/// // because all variants of `MyEnum` are accounted for.
24/// assert_enum_variants!(MyEnum, { A, B, C });
25/// ```
26///
27/// It will fail to compile if any of the variants are missing or if there are any
28/// extra variants.
29///
30/// # Example of faliure due to missing variants
31///
32/// ```rust,compile_fail
33/// use assert_enum_variants::assert_enum_variants;
34///
35/// #[allow(dead_code)]
36/// pub enum MyEnum {
37///     A,
38///     B(u32),
39///     C {
40///         a: String,
41///         b: u32,
42///     },
43/// }
44///
45/// // This will fail to compile
46/// // because the `C` variant is missing.
47/// assert_enum_variants!(MyEnum, { A, B });
48///```
49///
50/// # Example of failure due to extra variants
51///
52/// ```rust,compile_fail
53/// use assert_enum_variants::assert_enum_variants;
54///
55/// #[allow(dead_code)]
56/// pub enum MyEnum {
57///     A,
58///     B(u32),
59///     C {
60///         a: String,
61///         b: u32,
62///     },
63/// }
64///
65/// // This will fail to compile
66/// // because the `D` variant is not present on `MyEnum`.
67/// assert_enum_variants!(MyEnum, { A, B, C, D });
68/// ```
69///
70/// # Reasons for using this macro
71///
72/// Let's say you're writing some code that needs to handle all variants of an enum
73/// but there could be a situation that none of the variants fits.
74///
75/// ```rust
76/// enum ResumeFileFormat {
77///    Pdf,
78///    Docx,
79///    Doc,
80/// }
81///
82/// // ...
83///
84/// impl ResumeFileFormat {
85///   fn from_extension(ext: &str) -> Option<Self> {
86///       use ResumeFileFormat::{Pdf, Docx, Doc};
87///
88///       let file_format: ResumeFileFormat = match ext {
89///           "pdf" => Pdf,
90///           "docx" => Docx,
91///           "doc" => Doc,
92///           _ => return None,
93///       };
94///
95///       Some(file_format)
96///   }
97/// }
98/// ```
99///
100/// Notice that due to a wildcard pattern, the compiler will not warn you if you
101/// add a new variant to the enum and forget to modify the `from_extension`.
102///
103/// That is, unless you use the [`assert_enum_variants!`] macro.
104///
105/// The following code will fail to compile if you add a new variant to the enum
106/// and forget to modify the `from_extension` function.
107///
108/// ```rust,compile_fail
109/// use assert_enum_variants::assert_enum_variants;
110///
111/// enum ResumeFileFormat {
112///    Pdf,
113///    Docx,
114///    Doc,
115///    Json,
116/// }
117///
118/// // ...
119///
120/// impl ResumeFileFormat {
121///   fn from_extension(ext: &str) -> Option<Self> {
122///       use ResumeFileFormat::{Pdf, Docx, Doc};
123///
124///       // This will fail to compile because the `Json` variant is missing.
125///       assert_enum_variants!(ResumeFileFormat, { Pdf, Docx, Doc });
126///
127///       let file_format: ResumeFileFormat = match ext {
128///           "pdf" => Pdf,
129///           "docx" => Docx,
130///           "doc" => Doc,
131///           _ => return None,
132///       };
133///
134///       Some(file_format)
135///   }
136/// }
137/// ```
138#[macro_export]
139macro_rules! assert_enum_variants {
140    ($enum:path, { $($variant:ident),* $(,)? }) => {
141        const _: () = {
142            #[allow(unreachable_code)]
143            if false {
144                let _unreachable_obj: $enum = core::unreachable!();
145
146                #[allow(unused_imports)]
147                use $enum::{ $($variant),* };
148
149                match _unreachable_obj {
150                    $(
151                        $variant { .. } => (),
152                    )*
153                };
154            }
155        };
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    mod my_mod {
162        #[allow(dead_code)]
163        pub enum MyEnum {
164            A,
165            B(u32),
166            C { a: String, b: u32 },
167        }
168    }
169
170    #[allow(dead_code)]
171    enum Never {}
172
173    #[test]
174    fn test_enum_variants() {
175        assert_enum_variants!(my_mod::MyEnum, { A, B, C });
176        assert_enum_variants!(Never, {});
177    }
178}