use super::{SlotLocation, StableSlot};
use crate::storage::warp_table::{__private::invariant_violation, ColumnLease};
use core::ops::{Deref, DerefMut};
pub trait StableSlotColumn<T>: Deref<Target = [T]> + DerefMut {
fn append_admitted(&mut self, value: T);
fn remove_admitted(&mut self, row: usize);
}
#[cfg(feature = "alloc")]
impl<T> StableSlotColumn<T> for alloc::vec::Vec<T> {
fn append_admitted(&mut self, value: T) {
self.push(value);
}
fn remove_admitted(&mut self, row: usize) {
self.swap_remove(row);
}
}
impl<T> StableSlotColumn<T> for ColumnLease<'_, T> {
fn append_admitted(&mut self, value: T) {
if self.push(value).is_err() {
invariant_violation(StableSlotsStorageError::ColumnFull);
}
}
fn remove_admitted(&mut self, row: usize) {
if self.swap_remove(row).is_none() {
invariant_violation(StableSlotsStorageError::RowAbsent { row });
}
}
}
pub trait StableSlotsBacking {
type Rows: StableSlotColumn<StableSlot>;
type Locations: StableSlotColumn<SlotLocation>;
}
#[cfg(feature = "alloc")]
pub enum AllocatedStableSlots {}
#[cfg(feature = "alloc")]
impl StableSlotsBacking for AllocatedStableSlots {
type Rows = alloc::vec::Vec<StableSlot>;
type Locations = alloc::vec::Vec<SlotLocation>;
}
pub struct CallerStableSlots<'storage> {
pub rows: ColumnLease<'storage, StableSlot>,
pub locations: ColumnLease<'storage, SlotLocation>,
}
impl<'storage> StableSlotsBacking for CallerStableSlots<'storage> {
type Rows = ColumnLease<'storage, StableSlot>;
type Locations = ColumnLease<'storage, SlotLocation>;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum StableSlotsStorageError {
CapacityUnrepresentable { maximum_rows: usize },
ColumnsNotEmpty,
InsufficientRows { required: usize, available: usize },
InsufficientLocations { required: usize, available: usize },
ColumnFull,
RowAbsent { row: usize },
}
pub struct StableSlotsStorageRefusal<'storage> {
pub storage: CallerStableSlots<'storage>,
pub reason: StableSlotsStorageError,
}
impl<'storage> super::StableSlots<CallerStableSlots<'storage>> {
pub fn from_storage(
maximum_rows: usize,
storage: CallerStableSlots<'storage>,
) -> Result<Self, StableSlotsStorageRefusal<'storage>> {
let refusal = if u32::try_from(maximum_rows).is_err() {
Some(StableSlotsStorageError::CapacityUnrepresentable { maximum_rows })
} else if !storage.rows.is_empty() || !storage.locations.is_empty() {
Some(StableSlotsStorageError::ColumnsNotEmpty)
} else if storage.rows.capacity() < maximum_rows {
Some(StableSlotsStorageError::InsufficientRows {
required: maximum_rows,
available: storage.rows.capacity(),
})
} else if storage.locations.capacity() < maximum_rows {
Some(StableSlotsStorageError::InsufficientLocations {
required: maximum_rows,
available: storage.locations.capacity(),
})
} else {
None
};
if let Some(reason) = refusal {
return Err(StableSlotsStorageRefusal { storage, reason });
}
Ok(Self {
maximum_rows,
row_to_slot: storage.rows,
slot_locations: storage.locations,
first_free: None,
})
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use core::{mem::MaybeUninit, panic::AssertUnwindSafe};
use std::panic::catch_unwind;
#[test]
fn borrowed_slot_admission_returns_every_refused_column() -> Result<(), &'static str> {
for (rows, locations, requested, expected) in [
(
0,
1,
1,
StableSlotsStorageError::InsufficientRows {
required: 1,
available: 0,
},
),
(
1,
0,
1,
StableSlotsStorageError::InsufficientLocations {
required: 1,
available: 0,
},
),
#[cfg(target_pointer_width = "64")]
(
1,
1,
usize::MAX,
StableSlotsStorageError::CapacityUnrepresentable {
maximum_rows: usize::MAX,
},
),
] {
let mut row_storage = [MaybeUninit::uninit(); 1];
let mut location_storage = [const { MaybeUninit::uninit() }; 1];
let rows = ColumnLease::new(row_storage.get_mut(..rows).ok_or("rows")?);
let locations =
ColumnLease::new(location_storage.get_mut(..locations).ok_or("locations")?);
let storage = CallerStableSlots { rows, locations };
let Err(refusal) = super::super::StableSlots::from_storage(requested, storage) else {
return Err("accepted invalid columns");
};
assert_eq!(refusal.reason, expected);
assert!(refusal.storage.rows.is_empty());
assert!(refusal.storage.locations.is_empty());
}
let mut rows = [MaybeUninit::uninit(); 1];
let mut locations = [const { MaybeUninit::uninit() }; 1];
let mut storage = CallerStableSlots {
rows: ColumnLease::new(&mut rows),
locations: ColumnLease::new(&mut locations),
};
storage.rows.push(StableSlot::new(0)).map_err(|_| "push")?;
let Err(refusal) = super::super::StableSlots::from_storage(1, storage) else {
return Err("accepted populated columns");
};
assert_eq!(refusal.reason, StableSlotsStorageError::ColumnsNotEmpty);
assert_eq!(refusal.storage.rows.as_slice(), &[StableSlot::new(0)]);
Ok(())
}
#[test]
fn invalid_admitted_column_operations_cannot_silently_lose_members() {
let mut storage = [];
let mut column = ColumnLease::<u32>::new(&mut storage);
assert!(catch_unwind(AssertUnwindSafe(|| column.append_admitted(7))).is_err());
assert!(catch_unwind(AssertUnwindSafe(|| column.remove_admitted(0))).is_err());
assert!(column.is_empty());
}
}