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
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
54/// Finds the index of `variant` in the `variants` slice using const-compatible equality.
55///
56/// This function is intended to be called inside `const { }` blocks in the derive macro,
57/// so its O(N) cost is paid at compile time, not runtime.
58pub 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
71/// Binary search for a variant's index in the sorted `VARIANTS` array.
72///
73/// This is a `const fn` used by:
74/// - The default `Enumable::variant_index` implementation (O(log N) fallback).
75/// - The `get_const`, `get_mut_const`, `set_const`, and `remove_const` methods.
76pub 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
98/// Stable polyfill for `core::array::try_from_fn` (unstable `array_try_from_fn`).
99///
100/// Builds an array of `N` elements by calling `f(0)`, `f(1)`, …, `f(N-1)`.
101/// If any call returns `Err(e)`, already-initialized elements are properly
102/// dropped and the error is propagated.
103pub(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    // SAFETY: all N elements have been initialized in the loop above.
120    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    // --- const_enum_eq ---
136
137    #[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    // --- const_enum_lt ---
152
153    #[test]
154    fn const_enum_lt_ordering() {
155        // Green(11) < Red(33) < Blue(222)
156        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    // --- sort_variants ---
169
170    #[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    // --- is_sorted ---
199
200    #[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    // --- variant_index_of ---
225
226    #[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    // --- binary_search_index ---
235
236    #[test]
237    fn binary_search_index_finds_each() {
238        // Uses Enumable impl, so we need the derive
239        #[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        // VARIANTS sorted by discriminant: Banana(10), Apple(50), Cherry(200)
247        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    // --- try_collect_array ---
253
254    #[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        // Elements 0, 1, 2 were initialized then must be dropped by the guard
299        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            // Array is still alive, nothing dropped yet
321            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0);
322        }
323        // Array goes out of scope, all 3 elements dropped
324        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3);
325    }
326}