1macro_rules! const_operator {
2 ($T:ident,$left:ident ($operator:tt) $right:ident) => {
3 match const { core::mem::size_of::<$T>() } {
4 1 => unsafe { *($left as *const $T as *const u8) $operator *($right as *const $T as *const u8) },
5 2 => unsafe { *($left as *const $T as *const u16) $operator *($right as *const $T as *const u16) },
6 4 => unsafe { *($left as *const $T as *const u32) $operator *($right as *const $T as *const u32) },
7 8 => unsafe { *($left as *const $T as *const u64) $operator *($right as *const $T as *const u64) },
8 16 => unsafe { *($left as *const $T as *const u128) $operator *($right as *const $T as *const u128) },
9
10 _ => panic!(
11 "enum-table: Enum discriminants larger than 128 bits are not supported. This is likely due to an extremely large enum or invalid memory layout."
12 ),
13 }
14 };
15}
16
17#[inline(always)]
18pub(crate) const fn const_enum_eq<T>(left: &T, right: &T) -> bool {
19 const_operator!(T, left (==) right)
20}
21
22#[inline(always)]
23pub(crate) const fn const_enum_lt<T>(left: &T, right: &T) -> bool {
24 const_operator!(T, left (<) right)
25}
26
27pub const fn sort_variants<const N: usize, T>(mut arr: [T; N]) -> [T; N] {
28 let mut i = 1;
29 while i < N {
30 let mut j = i;
31 while j > 0 && const_enum_lt(&arr[j], &arr[j - 1]) {
32 arr.swap(j, j - 1);
33 j -= 1;
34 }
35 i += 1;
36 }
37 arr
38}
39
40pub(crate) const fn is_sorted<T>(arr: &[T]) -> bool {
41 if arr.is_empty() {
42 return true;
43 }
44 let mut i = 0;
45 while i < arr.len() - 1 {
46 if !const_enum_lt(&arr[i], &arr[i + 1]) {
47 return false;
48 }
49 i += 1;
50 }
51 true
52}
53
54pub const fn variant_index_of<T>(variant: &T, variants: &[T]) -> usize {
59 let mut i = 0;
60 while i < variants.len() {
61 if const_enum_eq(variant, &variants[i]) {
62 return i;
63 }
64 i += 1;
65 }
66 panic!(
67 "enum-table: variant not found in VARIANTS array. This is a bug in the Enumable implementation."
68 )
69}
70
71pub const fn binary_search_index<T: crate::Enumable>(variant: &T) -> usize {
77 let variants = T::VARIANTS;
78 let mut low = 0;
79 let mut high = variants.len();
80
81 while low < high {
82 let mid = low + (high - low) / 2;
83 if const_enum_lt(&variants[mid], variant) {
84 low = mid + 1;
85 } else {
86 high = mid;
87 }
88 }
89
90 debug_assert!(
91 low < variants.len() && const_enum_eq(&variants[low], variant),
92 "enum-table: variant not found in VARIANTS via binary search. This is a bug in the Enumable implementation."
93 );
94
95 low
96}
97
98pub(crate) fn try_collect_array<V, E, const N: usize>(
104 mut f: impl FnMut(usize) -> Result<V, E>,
105) -> Result<[V; N], E> {
106 let mut array = core::mem::MaybeUninit::<[V; N]>::uninit();
107 let ptr = array.as_mut_ptr().cast::<V>();
108
109 for i in 0..N {
110 match f(i) {
111 Ok(v) => unsafe { ptr.add(i).write(v) },
112 Err(e) => {
113 (0..i).for_each(|j| unsafe { ptr.add(j).drop_in_place() });
114 return Err(e);
115 }
116 }
117 }
118
119 Ok(unsafe { array.assume_init() })
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[repr(u8)]
128 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
129 enum Color {
130 Red = 33,
131 Green = 11,
132 Blue = 222,
133 }
134
135 #[test]
138 fn const_enum_eq_same_variant() {
139 assert!(const_enum_eq(&Color::Red, &Color::Red));
140 assert!(const_enum_eq(&Color::Green, &Color::Green));
141 assert!(const_enum_eq(&Color::Blue, &Color::Blue));
142 }
143
144 #[test]
145 fn const_enum_eq_different_variant() {
146 assert!(!const_enum_eq(&Color::Red, &Color::Green));
147 assert!(!const_enum_eq(&Color::Green, &Color::Blue));
148 assert!(!const_enum_eq(&Color::Red, &Color::Blue));
149 }
150
151 #[test]
154 fn const_enum_lt_ordering() {
155 assert!(const_enum_lt(&Color::Green, &Color::Red));
157 assert!(const_enum_lt(&Color::Red, &Color::Blue));
158 assert!(const_enum_lt(&Color::Green, &Color::Blue));
159 }
160
161 #[test]
162 fn const_enum_lt_not_less() {
163 assert!(!const_enum_lt(&Color::Red, &Color::Green));
164 assert!(!const_enum_lt(&Color::Blue, &Color::Red));
165 assert!(!const_enum_lt(&Color::Red, &Color::Red));
166 }
167
168 #[test]
171 fn sort_variants_already_sorted() {
172 let arr = [Color::Green, Color::Red, Color::Blue];
173 let sorted = sort_variants(arr);
174 assert_eq!(sorted, [Color::Green, Color::Red, Color::Blue]);
175 }
176
177 #[test]
178 fn sort_variants_reverse_order() {
179 let arr = [Color::Blue, Color::Red, Color::Green];
180 let sorted = sort_variants(arr);
181 assert_eq!(sorted, [Color::Green, Color::Red, Color::Blue]);
182 }
183
184 #[test]
185 fn sort_variants_single_element() {
186 let arr = [Color::Red];
187 let sorted = sort_variants(arr);
188 assert_eq!(sorted, [Color::Red]);
189 }
190
191 #[test]
192 fn sort_variants_empty() {
193 let arr: [Color; 0] = [];
194 let sorted = sort_variants(arr);
195 assert_eq!(sorted, []);
196 }
197
198 #[test]
201 fn is_sorted_sorted_slice() {
202 let arr = [Color::Green, Color::Red, Color::Blue];
203 assert!(is_sorted(&arr));
204 }
205
206 #[test]
207 fn is_sorted_unsorted_slice() {
208 let arr = [Color::Red, Color::Green, Color::Blue];
209 assert!(!is_sorted(&arr));
210 }
211
212 #[test]
213 fn is_sorted_single_element() {
214 let arr = [Color::Red];
215 assert!(is_sorted(&arr));
216 }
217
218 #[test]
219 fn is_sorted_empty() {
220 let arr: [Color; 0] = [];
221 assert!(is_sorted(&arr));
222 }
223
224 #[test]
227 fn variant_index_of_finds_each() {
228 let sorted = [Color::Green, Color::Red, Color::Blue];
229 assert_eq!(variant_index_of(&Color::Green, &sorted), 0);
230 assert_eq!(variant_index_of(&Color::Red, &sorted), 1);
231 assert_eq!(variant_index_of(&Color::Blue, &sorted), 2);
232 }
233
234 #[test]
237 fn binary_search_index_finds_each() {
238 #[derive(Debug, Clone, Copy, PartialEq, Eq, crate::Enumable)]
240 #[repr(u8)]
241 enum Fruit {
242 Apple = 50,
243 Banana = 10,
244 Cherry = 200,
245 }
246 assert_eq!(binary_search_index(&Fruit::Banana), 0);
248 assert_eq!(binary_search_index(&Fruit::Apple), 1);
249 assert_eq!(binary_search_index(&Fruit::Cherry), 2);
250 }
251
252 #[test]
255 fn try_collect_array_all_ok() {
256 let result: Result<[i32; 4], &str> = try_collect_array(|i| Ok(i as i32 * 10));
257 assert_eq!(result, Ok([0, 10, 20, 30]));
258 }
259
260 #[test]
261 fn try_collect_array_error_at_first() {
262 let result: Result<[i32; 3], &str> = try_collect_array(|_| Err("fail"));
263 assert_eq!(result, Err("fail"));
264 }
265
266 #[test]
267 fn try_collect_array_error_in_middle() {
268 let result: Result<[i32; 5], usize> =
269 try_collect_array(|i| if i == 2 { Err(i) } else { Ok(i as i32) });
270 assert_eq!(result, Err(2));
271 }
272
273 #[test]
274 fn try_collect_array_zero_length() {
275 let result: Result<[i32; 0], &str> = try_collect_array(|_| unreachable!());
276 assert_eq!(result, Ok([]));
277 }
278
279 #[test]
280 fn try_collect_array_drops_on_error() {
281 use std::sync::atomic::{AtomicUsize, Ordering};
282
283 static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
284
285 struct Droppable;
286 impl Drop for Droppable {
287 fn drop(&mut self) {
288 DROP_COUNT.fetch_add(1, Ordering::SeqCst);
289 }
290 }
291
292 DROP_COUNT.store(0, Ordering::SeqCst);
293
294 let result: Result<[Droppable; 5], &str> =
295 try_collect_array(|i| if i == 3 { Err("boom") } else { Ok(Droppable) });
296
297 assert!(result.is_err());
298 assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3);
300 }
301
302 #[test]
303 fn try_collect_array_no_leak_on_success() {
304 use std::sync::atomic::{AtomicUsize, Ordering};
305
306 static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
307
308 struct Droppable;
309 impl Drop for Droppable {
310 fn drop(&mut self) {
311 DROP_COUNT.fetch_add(1, Ordering::SeqCst);
312 }
313 }
314
315 DROP_COUNT.store(0, Ordering::SeqCst);
316
317 {
318 let result: Result<[Droppable; 3], &str> = try_collect_array(|_| Ok(Droppable));
319 assert!(result.is_ok());
320 assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0);
322 }
323 assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3);
325 }
326}