diff options
Diffstat (limited to 'vendor/zerovec/src/ule/option.rs')
-rw-r--r-- | vendor/zerovec/src/ule/option.rs | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/vendor/zerovec/src/ule/option.rs b/vendor/zerovec/src/ule/option.rs new file mode 100644 index 000000000..e1d2d25fa --- /dev/null +++ b/vendor/zerovec/src/ule/option.rs @@ -0,0 +1,253 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use super::*; +use core::cmp::Ordering; +use core::marker::PhantomData; +use core::mem::{self, MaybeUninit}; + +/// This type is the [`ULE`] type for `Option<U>` where `U` is a [`ULE`] type +/// +/// # Example +/// +/// ```rust +/// use zerovec::ZeroVec; +/// +/// let z = ZeroVec::alloc_from_slice(&[ +/// Some('a'), +/// Some('á'), +/// Some('ø'), +/// None, +/// Some('ł'), +/// ]); +/// +/// assert_eq!(z.get(2), Some(Some(('ø')))); +/// assert_eq!(z.get(3), Some(None)); +/// ``` +// Invariants: +// The MaybeUninit is zeroed when None (bool = false), +// and is valid when Some (bool = true) +#[repr(packed)] +pub struct OptionULE<U>(bool, MaybeUninit<U>); + +impl<U: Copy> OptionULE<U> { + /// Obtain this as an Option<T> + pub fn get(self) -> Option<U> { + if self.0 { + unsafe { + // safety: self.0 is true so the MaybeUninit is valid + Some(self.1.assume_init()) + } + } else { + None + } + } + + /// Construct an OptionULE<U> from an equivalent Option<T> + pub fn new(opt: Option<U>) -> Self { + if let Some(inner) = opt { + Self(true, MaybeUninit::new(inner)) + } else { + Self(false, MaybeUninit::zeroed()) + } + } +} + +// Safety (based on the safety checklist on the ULE trait): +// 1. OptionULE does not include any uninitialized or padding bytes. +// (achieved by `#[repr(packed)]` on a struct containing only ULE fields, +// in the context of this impl. The MaybeUninit is valid for all byte sequences, and we only generate +/// zeroed or valid-T byte sequences to fill it) +// 2. OptionULE is aligned to 1 byte. +// (achieved by `#[repr(packed)]` on a struct containing only ULE fields, in the context of this impl) +// 3. The impl of validate_byte_slice() returns an error if any byte is not valid. +// 4. The impl of validate_byte_slice() returns an error if there are extra bytes. +// 5. The other ULE methods use the default impl. +// 6. OptionULE byte equality is semantic equality by relying on the ULE equality +// invariant on the subfields +unsafe impl<U: ULE> ULE for OptionULE<U> { + fn validate_byte_slice(bytes: &[u8]) -> Result<(), ZeroVecError> { + let size = mem::size_of::<Self>(); + if bytes.len() % size != 0 { + return Err(ZeroVecError::length::<Self>(bytes.len())); + } + for chunk in bytes.chunks(size) { + #[allow(clippy::indexing_slicing)] // `chunk` will have enough bytes to fit Self + match chunk[0] { + // https://doc.rust-lang.org/reference/types/boolean.html + // Rust booleans are always size 1, align 1 values with valid bit patterns 0x0 or 0x1 + 0 => { + if !chunk[1..].iter().all(|x| *x == 0) { + return Err(ZeroVecError::parse::<Self>()); + } + } + 1 => U::validate_byte_slice(&chunk[1..])?, + _ => return Err(ZeroVecError::parse::<Self>()), + } + } + Ok(()) + } +} + +impl<T: AsULE> AsULE for Option<T> { + type ULE = OptionULE<T::ULE>; + fn to_unaligned(self) -> OptionULE<T::ULE> { + OptionULE::new(self.map(T::to_unaligned)) + } + + fn from_unaligned(other: OptionULE<T::ULE>) -> Self { + other.get().map(T::from_unaligned) + } +} + +impl<U: Copy> Copy for OptionULE<U> {} + +impl<U: Copy> Clone for OptionULE<U> { + fn clone(&self) -> Self { + *self + } +} + +impl<U: Copy + PartialEq> PartialEq for OptionULE<U> { + fn eq(&self, other: &Self) -> bool { + self.get().eq(&other.get()) + } +} + +impl<U: Copy + Eq> Eq for OptionULE<U> {} + +/// A type allowing one to represent `Option<U>` for [`VarULE`] `U` types. +/// +/// ```rust +/// use zerovec::ule::OptionVarULE; +/// use zerovec::VarZeroVec; +/// +/// let mut zv: VarZeroVec<OptionVarULE<str>> = VarZeroVec::new(); +/// +/// zv.make_mut().push(&None::<&str>); +/// zv.make_mut().push(&Some("hello")); +/// zv.make_mut().push(&Some("world")); +/// zv.make_mut().push(&None::<&str>); +/// +/// assert_eq!(zv.get(0).unwrap().as_ref(), None); +/// assert_eq!(zv.get(1).unwrap().as_ref(), Some("hello")); +/// ``` +// The slice field is empty when None (bool = false), +// and is a valid T when Some (bool = true) +#[repr(packed)] +pub struct OptionVarULE<U: VarULE + ?Sized>(PhantomData<U>, bool, [u8]); + +impl<U: VarULE + ?Sized> OptionVarULE<U> { + /// Obtain this as an `Option<&U>` + pub fn as_ref(&self) -> Option<&U> { + if self.1 { + unsafe { + // Safety: byte field is a valid T if boolean field is true + Some(U::from_byte_slice_unchecked(&self.2)) + } + } else { + None + } + } +} + +// Safety (based on the safety checklist on the VarULE trait): +// 1. OptionVarULE<T> does not include any uninitialized or padding bytes +// (achieved by being repr(packed) on ULE types) +// 2. OptionVarULE<T> is aligned to 1 byte (achieved by being repr(packed) on ULE types) +// 3. The impl of `validate_byte_slice()` returns an error if any byte is not valid. +// 4. The impl of `validate_byte_slice()` returns an error if the slice cannot be used in its entirety +// 5. The impl of `from_byte_slice_unchecked()` returns a reference to the same data. +// 6. All other methods are defaulted +// 7. OptionVarULE<T> byte equality is semantic equality (achieved by being an aggregate) +unsafe impl<U: VarULE + ?Sized> VarULE for OptionVarULE<U> { + #[inline] + fn validate_byte_slice(slice: &[u8]) -> Result<(), ZeroVecError> { + if slice.is_empty() { + return Err(ZeroVecError::length::<Self>(slice.len())); + } + #[allow(clippy::indexing_slicing)] // slice already verified to be nonempty + match slice[0] { + // https://doc.rust-lang.org/reference/types/boolean.html + // Rust booleans are always size 1, align 1 values with valid bit patterns 0x0 or 0x1 + 0 => { + if slice.len() != 1 { + Err(ZeroVecError::length::<Self>(slice.len())) + } else { + Ok(()) + } + } + 1 => U::validate_byte_slice(&slice[1..]), + _ => Err(ZeroVecError::parse::<Self>()), + } + } + + #[inline] + unsafe fn from_byte_slice_unchecked(bytes: &[u8]) -> &Self { + let metadata = bytes.len() - 1; + let entire_struct_as_slice: *const [u8] = + ::core::slice::from_raw_parts(bytes.as_ptr(), metadata); + &*(entire_struct_as_slice as *const Self) + } +} + +unsafe impl<T, U> EncodeAsVarULE<OptionVarULE<U>> for Option<T> +where + T: EncodeAsVarULE<U>, + U: VarULE + ?Sized, +{ + fn encode_var_ule_as_slices<R>(&self, _: impl FnOnce(&[&[u8]]) -> R) -> R { + // unnecessary if the other two are implemented + unreachable!() + } + + #[inline] + fn encode_var_ule_len(&self) -> usize { + if let Some(ref inner) = *self { + // slice + boolean + 1 + inner.encode_var_ule_len() + } else { + // boolean + empty slice + 1 + } + } + + #[allow(clippy::indexing_slicing)] // This method is allowed to panic when lengths are invalid + fn encode_var_ule_write(&self, dst: &mut [u8]) { + if let Some(ref inner) = *self { + debug_assert!( + !dst.is_empty(), + "OptionVarULE must have at least one byte when Some" + ); + dst[0] = 1; + inner.encode_var_ule_write(&mut dst[1..]); + } else { + debug_assert!( + dst.len() == 1, + "OptionVarULE must have exactly one byte when None" + ); + dst[0] = 0; + } + } +} + +impl<U: VarULE + ?Sized + PartialEq> PartialEq for OptionVarULE<U> { + fn eq(&self, other: &Self) -> bool { + self.as_ref().eq(&other.as_ref()) + } +} + +impl<U: VarULE + ?Sized + Eq> Eq for OptionVarULE<U> {} + +impl<U: VarULE + ?Sized + PartialOrd> PartialOrd for OptionVarULE<U> { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + self.as_ref().partial_cmp(&other.as_ref()) + } +} + +impl<U: VarULE + ?Sized + Ord> Ord for OptionVarULE<U> { + fn cmp(&self, other: &Self) -> Ordering { + self.as_ref().cmp(&other.as_ref()) + } +} |