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
40#[cfg(any(debug_assertions, test))]
41pub(crate) const fn is_sorted<T>(arr: &[T]) -> bool {
42 if arr.is_empty() {
43 return true;
44 }
45 let mut i = 0;
46 while i < arr.len() - 1 {
47 if !const_enum_lt(&arr[i], &arr[i + 1]) {
48 return false;
49 }
50 i += 1;
51 }
52 true
53}
54
55pub const fn variant_index_of<T>(variant: &T, variants: &[T]) -> usize {
60 let mut i = 0;
61 while i < variants.len() {
62 if const_enum_eq(variant, &variants[i]) {
63 return i;
64 }
65 i += 1;
66 }
67 panic!(
68 "enum-table: variant not found in VARIANTS array. This is a bug in the Enumable implementation."
69 )
70}
71
72pub const fn binary_search_index<T: crate::Enumable>(variant: &T) -> usize {
78 let variants = T::VARIANTS;
79 let mut low = 0;
80 let mut high = variants.len();
81
82 while low < high {
83 let mid = low + (high - low) / 2;
84 if const_enum_lt(&variants[mid], variant) {
85 low = mid + 1;
86 } else {
87 high = mid;
88 }
89 }
90
91 debug_assert!(
92 low < variants.len() && const_enum_eq(&variants[low], variant),
93 "enum-table: variant not found in VARIANTS via binary search. This is a bug in the Enumable implementation."
94 );
95
96 low
97}
98
99pub(crate) fn try_collect_array<V, E, const N: usize>(
105 mut f: impl FnMut(usize) -> Result<V, E>,
106) -> Result<[V; N], E> {
107 let mut array = core::mem::MaybeUninit::<[V; N]>::uninit();
108 let mut initialized: usize = 0;
109
110 for i in 0..N {
111 match f(i) {
112 Ok(v) => unsafe {
113 array.as_mut_ptr().cast::<V>().add(i).write(v);
114 },
115 Err(e) => {
116 for i in 0..initialized {
117 unsafe {
118 array.as_mut_ptr().cast::<V>().add(i).drop_in_place();
119 }
120 }
121 return Err(e);
122 }
123 }
124
125 initialized += 1;
126 }
127
128 Ok(unsafe { array.assume_init() })
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[repr(u8)]
137 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
138 enum Color {
139 Red = 33,
140 Green = 11,
141 Blue = 222,
142 }
143
144 #[test]
147 fn const_enum_eq_same_variant() {
148 assert!(const_enum_eq(&Color::Red, &Color::Red));
149 assert!(const_enum_eq(&Color::Green, &Color::Green));
150 assert!(const_enum_eq(&Color::Blue, &Color::Blue));
151 }
152
153 #[test]
154 fn const_enum_eq_different_variant() {
155 assert!(!const_enum_eq(&Color::Red, &Color::Green));
156 assert!(!const_enum_eq(&Color::Green, &Color::Blue));
157 assert!(!const_enum_eq(&Color::Red, &Color::Blue));
158 }
159
160 #[test]
163 fn const_enum_lt_ordering() {
164 assert!(const_enum_lt(&Color::Green, &Color::Red));
166 assert!(const_enum_lt(&Color::Red, &Color::Blue));
167 assert!(const_enum_lt(&Color::Green, &Color::Blue));
168 }
169
170 #[test]
171 fn const_enum_lt_not_less() {
172 assert!(!const_enum_lt(&Color::Red, &Color::Green));
173 assert!(!const_enum_lt(&Color::Blue, &Color::Red));
174 assert!(!const_enum_lt(&Color::Red, &Color::Red));
175 }
176
177 #[test]
180 fn sort_variants_already_sorted() {
181 let arr = [Color::Green, Color::Red, Color::Blue];
182 let sorted = sort_variants(arr);
183 assert_eq!(sorted, [Color::Green, Color::Red, Color::Blue]);
184 }
185
186 #[test]
187 fn sort_variants_reverse_order() {
188 let arr = [Color::Blue, Color::Red, Color::Green];
189 let sorted = sort_variants(arr);
190 assert_eq!(sorted, [Color::Green, Color::Red, Color::Blue]);
191 }
192
193 #[test]
194 fn sort_variants_single_element() {
195 let arr = [Color::Red];
196 let sorted = sort_variants(arr);
197 assert_eq!(sorted, [Color::Red]);
198 }
199
200 #[test]
201 fn sort_variants_empty() {
202 let arr: [Color; 0] = [];
203 let sorted = sort_variants(arr);
204 assert_eq!(sorted, []);
205 }
206
207 #[test]
210 fn is_sorted_sorted_slice() {
211 let arr = [Color::Green, Color::Red, Color::Blue];
212 assert!(is_sorted(&arr));
213 }
214
215 #[test]
216 fn is_sorted_unsorted_slice() {
217 let arr = [Color::Red, Color::Green, Color::Blue];
218 assert!(!is_sorted(&arr));
219 }
220
221 #[test]
222 fn is_sorted_single_element() {
223 let arr = [Color::Red];
224 assert!(is_sorted(&arr));
225 }
226
227 #[test]
228 fn is_sorted_empty() {
229 let arr: [Color; 0] = [];
230 assert!(is_sorted(&arr));
231 }
232
233 #[test]
236 fn variant_index_of_finds_each() {
237 let sorted = [Color::Green, Color::Red, Color::Blue];
238 assert_eq!(variant_index_of(&Color::Green, &sorted), 0);
239 assert_eq!(variant_index_of(&Color::Red, &sorted), 1);
240 assert_eq!(variant_index_of(&Color::Blue, &sorted), 2);
241 }
242
243 #[test]
246 fn binary_search_index_finds_each() {
247 #[derive(Debug, Clone, Copy, PartialEq, Eq, crate::Enumable)]
249 #[repr(u8)]
250 enum Fruit {
251 Apple = 50,
252 Banana = 10,
253 Cherry = 200,
254 }
255 assert_eq!(binary_search_index(&Fruit::Banana), 0);
257 assert_eq!(binary_search_index(&Fruit::Apple), 1);
258 assert_eq!(binary_search_index(&Fruit::Cherry), 2);
259 }
260
261 #[test]
264 fn try_collect_array_all_ok() {
265 let result: Result<[i32; 4], &str> = try_collect_array(|i| Ok(i as i32 * 10));
266 assert_eq!(result, Ok([0, 10, 20, 30]));
267 }
268
269 #[test]
270 fn try_collect_array_error_at_first() {
271 let result: Result<[i32; 3], &str> = try_collect_array(|_| Err("fail"));
272 assert_eq!(result, Err("fail"));
273 }
274
275 #[test]
276 fn try_collect_array_error_in_middle() {
277 let result: Result<[i32; 5], usize> =
278 try_collect_array(|i| if i == 2 { Err(i) } else { Ok(i as i32) });
279 assert_eq!(result, Err(2));
280 }
281
282 #[test]
283 fn try_collect_array_zero_length() {
284 let result: Result<[i32; 0], &str> = try_collect_array(|_| unreachable!());
285 assert_eq!(result, Ok([]));
286 }
287
288 #[test]
289 fn try_collect_array_drops_on_error() {
290 use std::sync::atomic::{AtomicUsize, Ordering};
291
292 static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
293
294 struct Droppable;
295 impl Drop for Droppable {
296 fn drop(&mut self) {
297 DROP_COUNT.fetch_add(1, Ordering::SeqCst);
298 }
299 }
300
301 DROP_COUNT.store(0, Ordering::SeqCst);
302
303 let result: Result<[Droppable; 5], &str> =
304 try_collect_array(|i| if i == 3 { Err("boom") } else { Ok(Droppable) });
305
306 assert!(result.is_err());
307 assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3);
309 }
310
311 #[test]
312 fn try_collect_array_no_leak_on_success() {
313 use std::sync::atomic::{AtomicUsize, Ordering};
314
315 static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
316
317 struct Droppable;
318 impl Drop for Droppable {
319 fn drop(&mut self) {
320 DROP_COUNT.fetch_add(1, Ordering::SeqCst);
321 }
322 }
323
324 DROP_COUNT.store(0, Ordering::SeqCst);
325
326 {
327 let result: Result<[Droppable; 3], &str> = try_collect_array(|_| Ok(Droppable));
328 assert!(result.is_ok());
329 assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0);
331 }
332 assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3);
334 }
335}