Skip to main content

enum_table/
intrinsics.rs

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
55/// Finds the index of `variant` in the `variants` slice using const-compatible equality.
56///
57/// This function is intended to be called inside `const { }` blocks in the derive macro,
58/// so its O(N) cost is paid at compile time, not runtime.
59pub 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
72/// Binary search for a variant's index in the sorted `VARIANTS` array.
73///
74/// This is a `const fn` used by:
75/// - The default `Enumable::variant_index` implementation (O(log N) fallback).
76/// - The `get_const`, `get_mut_const`, `set_const`, and `remove_const` methods.
77pub 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
99/// Stable polyfill for `core::array::try_from_fn` (unstable `array_try_from_fn`).
100///
101/// Builds an array of `N` elements by calling `f(0)`, `f(1)`, …, `f(N-1)`.
102/// If any call returns `Err(e)`, already-initialized elements are properly
103/// dropped and the error is propagated.
104pub(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    // SAFETY: all N elements have been initialized in the loop above.
129    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    // --- const_enum_eq ---
145
146    #[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    // --- const_enum_lt ---
161
162    #[test]
163    fn const_enum_lt_ordering() {
164        // Green(11) < Red(33) < Blue(222)
165        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    // --- sort_variants ---
178
179    #[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    // --- is_sorted ---
208
209    #[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    // --- variant_index_of ---
234
235    #[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    // --- binary_search_index ---
244
245    #[test]
246    fn binary_search_index_finds_each() {
247        // Uses Enumable impl, so we need the derive
248        #[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        // VARIANTS sorted by discriminant: Banana(10), Apple(50), Cherry(200)
256        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    // --- try_collect_array ---
262
263    #[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        // Elements 0, 1, 2 were initialized then must be dropped by the guard
308        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            // Array is still alive, nothing dropped yet
330            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0);
331        }
332        // Array goes out of scope, all 3 elements dropped
333        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3);
334    }
335}