use crate::Enumerable;
#[inline(always)]
const unsafe fn bytes_eq<T>(left: &T, right: &T) -> bool {
let left = left as *const T as *const u8;
let right = right as *const T as *const u8;
let len = core::mem::size_of::<T>();
let mut i = 0;
while i < len {
if unsafe { *left.add(i) != *right.add(i) } {
return false;
}
i += 1;
}
true
}
#[inline(always)]
const unsafe fn bytes_lt<T>(left: &T, right: &T) -> bool {
let left = left as *const T as *const u8;
let right = right as *const T as *const u8;
let len = core::mem::size_of::<T>();
let mut i = len;
while i > 0 {
i -= 1;
let byte_index = if cfg!(target_endian = "little") {
i
} else {
len - 1 - i
};
let (l, r) = unsafe { (*left.add(byte_index), *right.add(byte_index)) };
if l != r {
return l < r;
}
}
false
}
#[doc(hidden)]
pub const unsafe fn sort_variants<const N: usize, T: Copy>(mut arr: [T; N]) -> [T; N] {
let mut i = 1;
while i < N {
let mut j = i;
while j > 0 && unsafe { bytes_lt(&arr[j], &arr[j - 1]) } {
arr.swap(j, j - 1);
j -= 1;
}
i += 1;
}
arr
}
#[doc(hidden)]
pub const unsafe fn variant_index_of<T>(variant: &T, variants: &[T]) -> usize {
let mut i = 0;
while i < variants.len() {
if unsafe { bytes_eq(variant, &variants[i]) } {
return i;
}
i += 1;
}
panic!(
"enum-table: variant not found in VARIANTS array. This is a bug in the Enumerable implementation."
)
}
pub(crate) const fn is_sorted<T: Enumerable>(arr: &[T]) -> bool {
if arr.is_empty() {
return true;
}
let mut i = 0;
while i < arr.len() - 1 {
if !unsafe { bytes_lt(&arr[i], &arr[i + 1]) } {
return false;
}
i += 1;
}
true
}
pub(crate) const fn binary_search_index<T: Enumerable>(variant: &T) -> usize {
let variants = T::VARIANTS;
let mut low = 0;
let mut high = variants.len();
while low < high {
let mid = low + (high - low) / 2;
if unsafe { bytes_lt(&variants[mid], variant) } {
low = mid + 1;
} else {
high = mid;
}
}
debug_assert!(
low < variants.len() && unsafe { bytes_eq(&variants[low], variant) },
"enum-table: variant not found in VARIANTS via binary search. This is a bug in the Enumerable implementation."
);
low
}
pub(crate) fn try_collect_array<V, E, const N: usize>(
mut f: impl FnMut(usize) -> Result<V, E>,
) -> Result<[V; N], E> {
struct InitGuard<V> {
ptr: *mut V,
len: usize,
}
impl<V> Drop for InitGuard<V> {
fn drop(&mut self) {
for i in 0..self.len {
unsafe { self.ptr.add(i).drop_in_place() };
}
}
}
let mut array = core::mem::MaybeUninit::<[V; N]>::uninit();
let mut guard = InitGuard {
ptr: array.as_mut_ptr().cast::<V>(),
len: 0,
};
for i in 0..N {
let v = f(i)?;
unsafe { guard.ptr.add(i).write(v) };
guard.len = i + 1;
}
core::mem::forget(guard);
Ok(unsafe { array.assume_init() })
}
#[cfg(test)]
mod tests {
use super::*;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, crate::Enumerable)]
enum Color {
Red = 33,
Green = 11,
Blue = 222,
}
#[repr(i8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, crate::Enumerable)]
enum Signed {
Neg = -1,
Zero = 0,
Pos = 1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, crate::Enumerable)]
enum Zst {
Only,
}
#[test]
fn bytes_eq_same_variant() {
assert!(unsafe { bytes_eq(&Color::Red, &Color::Red) });
assert!(unsafe { bytes_eq(&Color::Green, &Color::Green) });
}
#[test]
fn bytes_eq_different_variant() {
assert!(!unsafe { bytes_eq(&Color::Red, &Color::Green) });
}
#[test]
fn bytes_lt_ordering() {
assert!(unsafe { bytes_lt(&Color::Green, &Color::Red) });
assert!(unsafe { bytes_lt(&Color::Red, &Color::Blue) });
assert!(!unsafe { bytes_lt(&Color::Red, &Color::Green) });
assert!(!unsafe { bytes_lt(&Color::Red, &Color::Red) });
}
#[test]
fn bytes_lt_unsigned_bit_pattern_order() {
assert!(unsafe { bytes_lt(&Signed::Zero, &Signed::Neg) });
assert!(unsafe { bytes_lt(&Signed::Pos, &Signed::Neg) });
assert!(!unsafe { bytes_lt(&Signed::Neg, &Signed::Zero) });
}
#[test]
fn bytes_eq_lt_zero_sized() {
assert!(unsafe { bytes_eq(&Zst::Only, &Zst::Only) });
assert!(!unsafe { bytes_lt(&Zst::Only, &Zst::Only) });
}
#[test]
fn sort_variants_already_sorted() {
let arr = [Color::Green, Color::Red, Color::Blue];
let sorted = unsafe { sort_variants(arr) };
assert_eq!(sorted, [Color::Green, Color::Red, Color::Blue]);
}
#[test]
fn sort_variants_reverse_order() {
let arr = [Color::Blue, Color::Red, Color::Green];
let sorted = unsafe { sort_variants(arr) };
assert_eq!(sorted, [Color::Green, Color::Red, Color::Blue]);
}
#[test]
fn sort_variants_single_element() {
let arr = [Color::Red];
let sorted = unsafe { sort_variants(arr) };
assert_eq!(sorted, [Color::Red]);
}
#[test]
fn sort_variants_empty() {
let arr: [Color; 0] = [];
let sorted = unsafe { sort_variants(arr) };
assert_eq!(sorted, []);
}
#[test]
fn is_sorted_sorted_slice() {
let arr = [Color::Green, Color::Red, Color::Blue];
assert!(is_sorted(&arr));
}
#[test]
fn is_sorted_unsorted_slice() {
let arr = [Color::Red, Color::Green, Color::Blue];
assert!(!is_sorted(&arr));
}
#[test]
fn is_sorted_single_element() {
let arr = [Color::Red];
assert!(is_sorted(&arr));
}
#[test]
fn is_sorted_empty() {
let arr: [Color; 0] = [];
assert!(is_sorted(&arr));
}
#[test]
fn variant_index_of_finds_each() {
let sorted = [Color::Green, Color::Red, Color::Blue];
assert_eq!(unsafe { variant_index_of(&Color::Green, &sorted) }, 0);
assert_eq!(unsafe { variant_index_of(&Color::Red, &sorted) }, 1);
assert_eq!(unsafe { variant_index_of(&Color::Blue, &sorted) }, 2);
}
#[test]
fn binary_search_index_finds_each() {
assert_eq!(binary_search_index(&Color::Green), 0);
assert_eq!(binary_search_index(&Color::Red), 1);
assert_eq!(binary_search_index(&Color::Blue), 2);
}
#[test]
fn try_collect_array_all_ok() {
let result: Result<[i32; 4], &str> = try_collect_array(|i| Ok(i as i32 * 10));
assert_eq!(result, Ok([0, 10, 20, 30]));
}
#[test]
fn try_collect_array_error_at_first() {
let result: Result<[i32; 3], &str> = try_collect_array(|_| Err("fail"));
assert_eq!(result, Err("fail"));
}
#[test]
fn try_collect_array_error_in_middle() {
let result: Result<[i32; 5], usize> =
try_collect_array(|i| if i == 2 { Err(i) } else { Ok(i as i32) });
assert_eq!(result, Err(2));
}
#[test]
fn try_collect_array_zero_length() {
let result: Result<[i32; 0], &str> = try_collect_array(|_| unreachable!());
assert_eq!(result, Ok([]));
}
#[test]
fn try_collect_array_drops_on_error() {
use std::sync::atomic::{AtomicUsize, Ordering};
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
struct Droppable;
impl Drop for Droppable {
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
DROP_COUNT.store(0, Ordering::SeqCst);
let result: Result<[Droppable; 5], &str> =
try_collect_array(|i| if i == 3 { Err("boom") } else { Ok(Droppable) });
assert!(result.is_err());
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3);
}
#[test]
fn try_collect_array_drops_on_panic() {
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::atomic::{AtomicUsize, Ordering};
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
struct Droppable;
impl Drop for Droppable {
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
DROP_COUNT.store(0, Ordering::SeqCst);
let result = catch_unwind(AssertUnwindSafe(|| {
let _: Result<[Droppable; 5], ()> = try_collect_array(|i| {
if i == 3 {
panic!("boom");
}
Ok(Droppable)
});
}));
assert!(result.is_err());
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3);
}
#[test]
fn try_collect_array_no_leak_on_success() {
use std::sync::atomic::{AtomicUsize, Ordering};
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
struct Droppable;
impl Drop for Droppable {
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
DROP_COUNT.store(0, Ordering::SeqCst);
{
let result: Result<[Droppable; 3], &str> = try_collect_array(|_| Ok(Droppable));
assert!(result.is_ok());
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0);
}
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3);
}
}