Coverage Report

Created: 2025-05-08 06:26

/rust/registry/src/index.crates.io-6f17d22bba15001f/zerovec-0.10.4/src/ule/option.rs
Line
Count
Source (jump to first uncovered line)
1
// This file is part of ICU4X. For terms of use, please see the file
2
// called LICENSE at the top level of the ICU4X source tree
3
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5
use super::*;
6
use core::cmp::Ordering;
7
use core::marker::PhantomData;
8
use core::mem::{self, MaybeUninit};
9
10
/// This type is the [`ULE`] type for `Option<U>` where `U` is a [`ULE`] type
11
///
12
/// # Example
13
///
14
/// ```rust
15
/// use zerovec::ZeroVec;
16
///
17
/// let z = ZeroVec::alloc_from_slice(&[
18
///     Some('a'),
19
///     Some('á'),
20
///     Some('ø'),
21
///     None,
22
///     Some('ł'),
23
/// ]);
24
///
25
/// assert_eq!(z.get(2), Some(Some('ø')));
26
/// assert_eq!(z.get(3), Some(None));
27
/// ```
28
// Invariants:
29
// The MaybeUninit is zeroed when None (bool = false),
30
// and is valid when Some (bool = true)
31
#[repr(C, packed)]
32
pub struct OptionULE<U>(bool, MaybeUninit<U>);
33
34
impl<U: Copy> OptionULE<U> {
35
    /// Obtain this as an `Option<T>`
36
0
    pub fn get(self) -> Option<U> {
37
0
        if self.0 {
38
            unsafe {
39
                // safety: self.0 is true so the MaybeUninit is valid
40
0
                Some(self.1.assume_init())
41
            }
42
        } else {
43
0
            None
44
        }
45
0
    }
Unexecuted instantiation: <zerovec::ule::option::OptionULE<icu_locid::subtags::region::Region>>::get
Unexecuted instantiation: <zerovec::ule::option::OptionULE<icu_locid::subtags::script::Script>>::get
Unexecuted instantiation: <zerovec::ule::option::OptionULE<_>>::get
46
47
    /// Construct an `OptionULE<U>` from an equivalent `Option<T>`
48
0
    pub fn new(opt: Option<U>) -> Self {
49
0
        if let Some(inner) = opt {
50
0
            Self(true, MaybeUninit::new(inner))
51
        } else {
52
0
            Self(false, MaybeUninit::zeroed())
53
        }
54
0
    }
55
}
56
57
impl<U: Copy + core::fmt::Debug> core::fmt::Debug for OptionULE<U> {
58
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
59
0
        self.get().fmt(f)
60
0
    }
61
}
62
63
// Safety (based on the safety checklist on the ULE trait):
64
//  1. OptionULE does not include any uninitialized or padding bytes.
65
//     (achieved by `#[repr(C, packed)]` on a struct containing only ULE fields,
66
//     in the context of this impl. The MaybeUninit is valid for all byte sequences, and we only generate
67
///    zeroed or valid-T byte sequences to fill it)
68
//  2. OptionULE is aligned to 1 byte.
69
//     (achieved by `#[repr(C, packed)]` on a struct containing only ULE fields, in the context of this impl)
70
//  3. The impl of validate_byte_slice() returns an error if any byte is not valid.
71
//  4. The impl of validate_byte_slice() returns an error if there are extra bytes.
72
//  5. The other ULE methods use the default impl.
73
//  6. OptionULE byte equality is semantic equality by relying on the ULE equality
74
//     invariant on the subfields
75
unsafe impl<U: ULE> ULE for OptionULE<U> {
76
0
    fn validate_byte_slice(bytes: &[u8]) -> Result<(), ZeroVecError> {
77
0
        let size = mem::size_of::<Self>();
78
0
        if bytes.len() % size != 0 {
79
0
            return Err(ZeroVecError::length::<Self>(bytes.len()));
80
0
        }
81
0
        for chunk in bytes.chunks(size) {
82
            #[allow(clippy::indexing_slicing)] // `chunk` will have enough bytes to fit Self
83
0
            match chunk[0] {
84
                // https://doc.rust-lang.org/reference/types/boolean.html
85
                // Rust booleans are always size 1, align 1 values with valid bit patterns 0x0 or 0x1
86
                0 => {
87
0
                    if !chunk[1..].iter().all(|x| *x == 0) {
88
0
                        return Err(ZeroVecError::parse::<Self>());
89
0
                    }
90
                }
91
0
                1 => U::validate_byte_slice(&chunk[1..])?,
92
0
                _ => return Err(ZeroVecError::parse::<Self>()),
93
            }
94
        }
95
0
        Ok(())
96
0
    }
97
}
98
99
impl<T: AsULE> AsULE for Option<T> {
100
    type ULE = OptionULE<T::ULE>;
101
0
    fn to_unaligned(self) -> OptionULE<T::ULE> {
102
0
        OptionULE::new(self.map(T::to_unaligned))
103
0
    }
104
105
0
    fn from_unaligned(other: OptionULE<T::ULE>) -> Self {
106
0
        other.get().map(T::from_unaligned)
107
0
    }
Unexecuted instantiation: <core::option::Option<icu_locid::subtags::region::Region> as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <core::option::Option<icu_locid::subtags::script::Script> as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <core::option::Option<_> as zerovec::ule::AsULE>::from_unaligned
108
}
109
110
impl<U: Copy> Copy for OptionULE<U> {}
111
112
impl<U: Copy> Clone for OptionULE<U> {
113
0
    fn clone(&self) -> Self {
114
0
        *self
115
0
    }
116
}
117
118
impl<U: Copy + PartialEq> PartialEq for OptionULE<U> {
119
0
    fn eq(&self, other: &Self) -> bool {
120
0
        self.get().eq(&other.get())
121
0
    }
122
}
123
124
impl<U: Copy + Eq> Eq for OptionULE<U> {}
125
126
/// A type allowing one to represent `Option<U>` for [`VarULE`] `U` types.
127
///
128
/// ```rust
129
/// use zerovec::ule::OptionVarULE;
130
/// use zerovec::VarZeroVec;
131
///
132
/// let mut zv: VarZeroVec<OptionVarULE<str>> = VarZeroVec::new();
133
///
134
/// zv.make_mut().push(&None::<&str>);
135
/// zv.make_mut().push(&Some("hello"));
136
/// zv.make_mut().push(&Some("world"));
137
/// zv.make_mut().push(&None::<&str>);
138
///
139
/// assert_eq!(zv.get(0).unwrap().as_ref(), None);
140
/// assert_eq!(zv.get(1).unwrap().as_ref(), Some("hello"));
141
/// ```
142
// The slice field is empty when None (bool = false),
143
// and is a valid T when Some (bool = true)
144
#[repr(C, packed)]
145
pub struct OptionVarULE<U: VarULE + ?Sized>(PhantomData<U>, bool, [u8]);
146
147
impl<U: VarULE + ?Sized> OptionVarULE<U> {
148
    /// Obtain this as an `Option<&U>`
149
0
    pub fn as_ref(&self) -> Option<&U> {
150
0
        if self.1 {
151
            unsafe {
152
                // Safety: byte field is a valid T if boolean field is true
153
0
                Some(U::from_byte_slice_unchecked(&self.2))
154
            }
155
        } else {
156
0
            None
157
        }
158
0
    }
159
}
160
161
impl<U: VarULE + ?Sized + core::fmt::Debug> core::fmt::Debug for OptionVarULE<U> {
162
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
163
0
        self.as_ref().fmt(f)
164
0
    }
165
}
166
167
// Safety (based on the safety checklist on the VarULE trait):
168
//  1. OptionVarULE<T> does not include any uninitialized or padding bytes
169
//     (achieved by being repr(C, packed) on ULE types)
170
//  2. OptionVarULE<T> is aligned to 1 byte (achieved by being repr(C, packed) on ULE types)
171
//  3. The impl of `validate_byte_slice()` returns an error if any byte is not valid.
172
//  4. The impl of `validate_byte_slice()` returns an error if the slice cannot be used in its entirety
173
//  5. The impl of `from_byte_slice_unchecked()` returns a reference to the same data.
174
//  6. All other methods are defaulted
175
//  7. OptionVarULE<T> byte equality is semantic equality (achieved by being an aggregate)
176
unsafe impl<U: VarULE + ?Sized> VarULE for OptionVarULE<U> {
177
    #[inline]
178
0
    fn validate_byte_slice(slice: &[u8]) -> Result<(), ZeroVecError> {
179
0
        if slice.is_empty() {
180
0
            return Err(ZeroVecError::length::<Self>(slice.len()));
181
0
        }
182
0
        #[allow(clippy::indexing_slicing)] // slice already verified to be nonempty
183
0
        match slice[0] {
184
            // https://doc.rust-lang.org/reference/types/boolean.html
185
            // Rust booleans are always size 1, align 1 values with valid bit patterns 0x0 or 0x1
186
            0 => {
187
0
                if slice.len() != 1 {
188
0
                    Err(ZeroVecError::length::<Self>(slice.len()))
189
                } else {
190
0
                    Ok(())
191
                }
192
            }
193
0
            1 => U::validate_byte_slice(&slice[1..]),
194
0
            _ => Err(ZeroVecError::parse::<Self>()),
195
        }
196
0
    }
197
198
    #[inline]
199
0
    unsafe fn from_byte_slice_unchecked(bytes: &[u8]) -> &Self {
200
0
        let entire_struct_as_slice: *const [u8] =
201
0
            ::core::ptr::slice_from_raw_parts(bytes.as_ptr(), bytes.len() - 1);
202
0
        &*(entire_struct_as_slice as *const Self)
203
0
    }
204
}
205
206
unsafe impl<T, U> EncodeAsVarULE<OptionVarULE<U>> for Option<T>
207
where
208
    T: EncodeAsVarULE<U>,
209
    U: VarULE + ?Sized,
210
{
211
0
    fn encode_var_ule_as_slices<R>(&self, _: impl FnOnce(&[&[u8]]) -> R) -> R {
212
0
        // unnecessary if the other two are implemented
213
0
        unreachable!()
214
    }
215
216
    #[inline]
217
0
    fn encode_var_ule_len(&self) -> usize {
218
0
        if let Some(ref inner) = *self {
219
            // slice + boolean
220
0
            1 + inner.encode_var_ule_len()
221
        } else {
222
            // boolean + empty slice
223
0
            1
224
        }
225
0
    }
226
227
    #[allow(clippy::indexing_slicing)] // This method is allowed to panic when lengths are invalid
228
0
    fn encode_var_ule_write(&self, dst: &mut [u8]) {
229
0
        if let Some(ref inner) = *self {
230
0
            debug_assert!(
231
0
                !dst.is_empty(),
232
0
                "OptionVarULE must have at least one byte when Some"
233
            );
234
0
            dst[0] = 1;
235
0
            inner.encode_var_ule_write(&mut dst[1..]);
236
        } else {
237
0
            debug_assert!(
238
0
                dst.len() == 1,
239
0
                "OptionVarULE must have exactly one byte when None"
240
            );
241
0
            dst[0] = 0;
242
        }
243
0
    }
244
}
245
246
impl<U: VarULE + ?Sized + PartialEq> PartialEq for OptionVarULE<U> {
247
0
    fn eq(&self, other: &Self) -> bool {
248
0
        self.as_ref().eq(&other.as_ref())
249
0
    }
250
}
251
252
impl<U: VarULE + ?Sized + Eq> Eq for OptionVarULE<U> {}
253
254
impl<U: VarULE + ?Sized + PartialOrd> PartialOrd for OptionVarULE<U> {
255
0
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
256
0
        self.as_ref().partial_cmp(&other.as_ref())
257
0
    }
258
}
259
260
impl<U: VarULE + ?Sized + Ord> Ord for OptionVarULE<U> {
261
0
    fn cmp(&self, other: &Self) -> Ordering {
262
0
        self.as_ref().cmp(&other.as_ref())
263
0
    }
264
}