Skip to main content

facet_reflect/poke/
list_like.rs

1use core::{fmt::Debug, marker::PhantomData, mem::ManuallyDrop, ptr::NonNull};
2use facet_core::{FieldError, PtrMut, PtrUninit, Shape, ShapeLayout};
3
4use crate::{Guard, HeapValue, ReflectError, ReflectErrorKind, peek::ListLikeDef};
5
6use super::Poke;
7
8/// Lets you mutate a list, array or slice.
9pub struct PokeListLike<'mem, 'facet> {
10    value: Poke<'mem, 'facet>,
11    def: ListLikeDef,
12    len: usize,
13}
14
15impl<'mem, 'facet> Debug for PokeListLike<'mem, 'facet> {
16    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
17        f.debug_struct("PokeListLike").finish_non_exhaustive()
18    }
19}
20
21/// Iterator over a `PokeListLike` yielding mutable `Poke`s.
22///
23/// Constructed by [`PokeListLike::iter_mut`]. Only contiguous list-likes support
24/// mutable iteration — this iterator walks element strides starting from
25/// `as_mut_ptr`. See [`PokeListLike::iter_mut`] for the error conditions.
26pub struct PokeListLikeIter<'mem, 'facet> {
27    data: PtrMut,
28    stride: usize,
29    index: usize,
30    len: usize,
31    elem_shape: &'static Shape,
32    _list: PhantomData<Poke<'mem, 'facet>>,
33}
34
35impl<'mem, 'facet> Iterator for PokeListLikeIter<'mem, 'facet> {
36    type Item = Poke<'mem, 'facet>;
37
38    #[inline]
39    fn next(&mut self) -> Option<Self::Item> {
40        if self.index >= self.len {
41            return None;
42        }
43        let item_ptr = unsafe { self.data.field(self.stride * self.index) };
44        self.index += 1;
45        Some(unsafe { Poke::from_raw_parts(item_ptr, self.elem_shape) })
46    }
47
48    #[inline]
49    fn size_hint(&self) -> (usize, Option<usize>) {
50        let remaining = self.len.saturating_sub(self.index);
51        (remaining, Some(remaining))
52    }
53}
54
55impl<'mem, 'facet> ExactSizeIterator for PokeListLikeIter<'mem, 'facet> {}
56
57impl<'mem, 'facet> PokeListLike<'mem, 'facet> {
58    /// Creates a new poke list-like
59    ///
60    /// # Safety
61    ///
62    /// The caller must ensure that `def` contains valid vtable function pointers that:
63    /// - Correctly implement the list-like operations for the actual type
64    /// - Do not cause undefined behavior when called
65    /// - Return pointers within valid memory bounds
66    /// - Match the element type specified in `def.t()`
67    #[inline]
68    pub unsafe fn new(value: Poke<'mem, 'facet>, def: ListLikeDef) -> Self {
69        let len = match def {
70            ListLikeDef::List(v) => unsafe { (v.vtable.len)(value.data()) },
71            ListLikeDef::Slice(_) => {
72                let slice_as_units = unsafe { value.data().get::<[()]>() };
73                slice_as_units.len()
74            }
75            ListLikeDef::Array(v) => v.n,
76        };
77        Self { value, def, len }
78    }
79
80    fn err(&self, kind: ReflectErrorKind) -> ReflectError {
81        self.value.err(kind)
82    }
83
84    /// Get the length of the list-like.
85    #[inline]
86    pub const fn len(&self) -> usize {
87        self.len
88    }
89
90    /// Returns true if the list-like is empty.
91    #[inline]
92    pub const fn is_empty(&self) -> bool {
93        self.len == 0
94    }
95
96    /// Def getter.
97    #[inline]
98    pub const fn def(&self) -> ListLikeDef {
99        self.def
100    }
101
102    /// Get a read-only `Peek` for the item at the specified index.
103    #[inline]
104    pub fn get(&self, index: usize) -> Option<crate::Peek<'_, 'facet>> {
105        self.as_peek_list_like().get(index)
106    }
107
108    /// Get a mutable `Poke` for the item at the specified index.
109    pub fn get_mut(&mut self, index: usize) -> Option<Poke<'_, 'facet>> {
110        if index >= self.len {
111            return None;
112        }
113
114        let item_ptr = match self.def {
115            ListLikeDef::List(def) => {
116                let get_mut_fn = def.vtable.get_mut?;
117                unsafe { get_mut_fn(self.value.data_mut(), index, self.value.shape())? }
118            }
119            ListLikeDef::Array(def) => {
120                let elem_layout = match self.def.t().layout {
121                    ShapeLayout::Sized(layout) => layout,
122                    ShapeLayout::Unsized => return None,
123                };
124                let base = unsafe { (def.vtable.as_mut_ptr)(self.value.data_mut()) };
125                unsafe { base.field(index * elem_layout.size()) }
126            }
127            ListLikeDef::Slice(def) => {
128                let elem_layout = match self.def.t().layout {
129                    ShapeLayout::Sized(layout) => layout,
130                    ShapeLayout::Unsized => return None,
131                };
132                let base = unsafe { (def.vtable.as_mut_ptr)(self.value.data_mut()) };
133                unsafe { base.field(index * elem_layout.size()) }
134            }
135        };
136
137        Some(unsafe { Poke::from_raw_parts(item_ptr, self.def.t()) })
138    }
139
140    /// Returns a mutable iterator over the list-like.
141    ///
142    /// Requires contiguous mutable access to the backing storage: the element type must be
143    /// sized, and for `List` the vtable must expose `as_mut_ptr`. (`Array` and `Slice` always
144    /// expose `as_mut_ptr`.) Returns [`ReflectErrorKind::OperationFailed`] if either condition
145    /// fails; use [`PokeListLike::get_mut`] per index when `iter_mut` is unavailable.
146    ///
147    /// The previous fallback that synthesized a mutable iterator from the list's `iter_vtable`
148    /// was unsound: that vtable yields `PtrConst` items backed by shared references, and
149    /// writing through them is UB.
150    pub fn iter_mut(self) -> Result<PokeListLikeIter<'mem, 'facet>, ReflectError> {
151        let elem_shape = self.def.t();
152        let stride = match elem_shape.layout {
153            ShapeLayout::Sized(layout) => layout.size(),
154            ShapeLayout::Unsized => {
155                return Err(self.err(ReflectErrorKind::OperationFailed {
156                    shape: self.value.shape,
157                    operation: "iter_mut requires sized element type",
158                }));
159            }
160        };
161
162        let data = match self.def {
163            ListLikeDef::List(def) => match def.vtable.as_mut_ptr {
164                Some(as_mut_ptr_fn) => unsafe { as_mut_ptr_fn(self.value.data) },
165                None => {
166                    return Err(self.err(ReflectErrorKind::OperationFailed {
167                        shape: self.value.shape,
168                        operation:
169                            "iter_mut requires a contiguous `as_mut_ptr` vtable entry; use `get_mut` per index",
170                    }));
171                }
172            },
173            ListLikeDef::Array(def) => unsafe { (def.vtable.as_mut_ptr)(self.value.data) },
174            ListLikeDef::Slice(def) => unsafe { (def.vtable.as_mut_ptr)(self.value.data) },
175        };
176
177        Ok(PokeListLikeIter {
178            data,
179            stride,
180            index: 0,
181            len: self.len,
182            elem_shape,
183            _list: PhantomData,
184        })
185    }
186
187    /// Push a value onto the end of the list.
188    ///
189    /// Only supported for `List` variants whose element type provides a `push`
190    /// operation (e.g. `Vec<T>`). Fails for arrays and slices as their length
191    /// is fixed.
192    pub fn push<T: facet_core::Facet<'facet>>(&mut self, value: T) -> Result<(), ReflectError> {
193        if self.def.t() != T::SHAPE {
194            return Err(self.err(ReflectErrorKind::WrongShape {
195                expected: self.def.t(),
196                actual: T::SHAPE,
197            }));
198        }
199        let push_fn = self.push_fn()?;
200        let mut value = ManuallyDrop::new(value);
201        unsafe {
202            let item_ptr = PtrMut::new(&mut value as *mut ManuallyDrop<T> as *mut u8);
203            push_fn(self.value.data_mut(), item_ptr);
204        }
205        self.len += 1;
206        Ok(())
207    }
208
209    /// Type-erased [`push`](Self::push).
210    ///
211    /// Accepts a [`HeapValue`] whose shape must match the list's element type.
212    /// The value is moved out of the `HeapValue` into the list.
213    pub fn push_from_heap<const BORROW: bool>(
214        &mut self,
215        value: HeapValue<'facet, BORROW>,
216    ) -> Result<(), ReflectError> {
217        if self.def.t() != value.shape() {
218            return Err(self.err(ReflectErrorKind::WrongShape {
219                expected: self.def.t(),
220                actual: value.shape(),
221            }));
222        }
223        let push_fn = self.push_fn()?;
224        let mut value = value;
225        let guard = value
226            .guard
227            .take()
228            .expect("HeapValue guard was already taken");
229        unsafe {
230            let item_ptr = PtrMut::new(guard.ptr.as_ptr());
231            push_fn(self.value.data_mut(), item_ptr);
232        }
233        drop(guard);
234        self.len += 1;
235        Ok(())
236    }
237
238    /// Pop the last value off the end of the list.
239    ///
240    /// Returns `Ok(None)` if the list is empty. Only supported for `List`
241    /// variants whose element type provides a `pop` operation.
242    pub fn pop(&mut self) -> Result<Option<HeapValue<'facet, true>>, ReflectError> {
243        let list_def = match self.def {
244            ListLikeDef::List(def) => def,
245            _ => {
246                return Err(self.err(ReflectErrorKind::OperationFailed {
247                    shape: self.value.shape(),
248                    operation: "pop: only list-backed list-likes support pop",
249                }));
250            }
251        };
252        let pop_fn = list_def.pop().ok_or_else(|| {
253            self.err(ReflectErrorKind::OperationFailed {
254                shape: self.value.shape(),
255                operation: "pop: list type does not support pop",
256            })
257        })?;
258        let elem_shape = self.def.t();
259        let layout = elem_shape.layout.sized_layout().map_err(|_| {
260            self.err(ReflectErrorKind::Unsized {
261                shape: elem_shape,
262                operation: "pop",
263            })
264        })?;
265        let ptr = if layout.size() == 0 {
266            NonNull::<u8>::dangling()
267        } else {
268            let raw = unsafe { alloc::alloc::alloc(layout) };
269            match NonNull::new(raw) {
270                Some(p) => p,
271                None => alloc::alloc::handle_alloc_error(layout),
272            }
273        };
274        let out = PtrUninit::new(ptr.as_ptr());
275        let popped = unsafe { pop_fn(self.value.data_mut(), out) };
276        if !popped {
277            if layout.size() != 0 {
278                unsafe { alloc::alloc::dealloc(ptr.as_ptr(), layout) };
279            }
280            return Ok(None);
281        }
282        self.len -= 1;
283        Ok(Some(HeapValue {
284            guard: Some(Guard {
285                ptr,
286                layout,
287                should_dealloc: layout.size() != 0,
288            }),
289            shape: elem_shape,
290            phantom: PhantomData,
291        }))
292    }
293
294    /// Swap the elements at indices `a` and `b`.
295    ///
296    /// For `List` variants, uses the list's `swap` vtable entry if present and
297    /// errors otherwise. For `Array` and `Slice` variants, performs a generic
298    /// byte-swap using the element stride (always available). Returns an error
299    /// if either index is out of bounds. Swapping an index with itself is a
300    /// no-op.
301    pub fn swap(&mut self, a: usize, b: usize) -> Result<(), ReflectError> {
302        let len = self.len;
303        if a >= len || b >= len {
304            let out_of_bounds = if a >= len { a } else { b };
305            return Err(self.err(ReflectErrorKind::FieldError {
306                shape: self.value.shape(),
307                field_error: FieldError::IndexOutOfBounds {
308                    index: out_of_bounds,
309                    bound: len,
310                },
311            }));
312        }
313        if a == b {
314            return Ok(());
315        }
316
317        match self.def {
318            ListLikeDef::List(def) => {
319                let swap_fn = def.vtable.swap.ok_or_else(|| {
320                    self.err(ReflectErrorKind::OperationFailed {
321                        shape: self.value.shape(),
322                        operation: "swap: list type does not support swap",
323                    })
324                })?;
325                let ok = unsafe { swap_fn(self.value.data_mut(), a, b, self.value.shape()) };
326                if !ok {
327                    return Err(self.err(ReflectErrorKind::OperationFailed {
328                        shape: self.value.shape(),
329                        operation: "swap: vtable refused the operation",
330                    }));
331                }
332                Ok(())
333            }
334            ListLikeDef::Array(def) => {
335                let elem_size = match self.def.t().layout {
336                    ShapeLayout::Sized(l) => l.size(),
337                    ShapeLayout::Unsized => {
338                        return Err(self.err(ReflectErrorKind::Unsized {
339                            shape: self.def.t(),
340                            operation: "swap",
341                        }));
342                    }
343                };
344                unsafe {
345                    let base = (def.vtable.as_mut_ptr)(self.value.data_mut());
346                    let pa = base.field(a * elem_size);
347                    let pb = base.field(b * elem_size);
348                    core::ptr::swap_nonoverlapping(
349                        pa.as_mut_byte_ptr(),
350                        pb.as_mut_byte_ptr(),
351                        elem_size,
352                    );
353                }
354                Ok(())
355            }
356            ListLikeDef::Slice(def) => {
357                let elem_size = match self.def.t().layout {
358                    ShapeLayout::Sized(l) => l.size(),
359                    ShapeLayout::Unsized => {
360                        return Err(self.err(ReflectErrorKind::Unsized {
361                            shape: self.def.t(),
362                            operation: "swap",
363                        }));
364                    }
365                };
366                unsafe {
367                    let base = (def.vtable.as_mut_ptr)(self.value.data_mut());
368                    let pa = base.field(a * elem_size);
369                    let pb = base.field(b * elem_size);
370                    core::ptr::swap_nonoverlapping(
371                        pa.as_mut_byte_ptr(),
372                        pb.as_mut_byte_ptr(),
373                        elem_size,
374                    );
375                }
376                Ok(())
377            }
378        }
379    }
380
381    /// Resolve the per-T push function for the underlying list, or build an
382    /// error if the list-like is an array/slice or the list lacks push.
383    #[inline]
384    fn push_fn(&self) -> Result<facet_core::ListPushFn, ReflectError> {
385        match self.def {
386            ListLikeDef::List(def) => def.push().ok_or_else(|| {
387                self.err(ReflectErrorKind::OperationFailed {
388                    shape: self.value.shape(),
389                    operation: "push: list type does not support push",
390                })
391            }),
392            _ => Err(self.err(ReflectErrorKind::OperationFailed {
393                shape: self.value.shape(),
394                operation: "push: only list-backed list-likes support push",
395            })),
396        }
397    }
398
399    /// Converts this `PokeListLike` back into a `Poke`.
400    #[inline]
401    pub fn into_inner(self) -> Poke<'mem, 'facet> {
402        self.value
403    }
404
405    /// Returns a read-only `PeekListLike` view.
406    #[inline]
407    pub fn as_peek_list_like(&self) -> crate::PeekListLike<'_, 'facet> {
408        unsafe { crate::PeekListLike::new(self.value.as_peek(), self.def) }
409    }
410}
411
412#[cfg(test)]
413mod tests {
414    use alloc::vec::Vec;
415
416    use super::*;
417
418    #[test]
419    fn poke_list_like_vec_len_and_get_mut() {
420        let mut v: Vec<i32> = alloc::vec![1, 2, 3];
421        let poke = Poke::new(&mut v);
422        let mut ll = poke.into_list_like().unwrap();
423        assert_eq!(ll.len(), 3);
424
425        {
426            let mut item = ll.get_mut(1).unwrap();
427            item.set(200i32).unwrap();
428        }
429        assert_eq!(v, alloc::vec![1, 200, 3]);
430    }
431
432    #[test]
433    fn poke_list_like_array_get_mut() {
434        let mut arr: [i32; 3] = [10, 20, 30];
435        let poke = Poke::new(&mut arr);
436        let mut ll = poke.into_list_like().unwrap();
437        assert_eq!(ll.len(), 3);
438
439        {
440            let mut item = ll.get_mut(0).unwrap();
441            item.set(99i32).unwrap();
442        }
443        assert_eq!(arr, [99, 20, 30]);
444    }
445
446    #[test]
447    fn poke_list_like_iter_mut() {
448        let mut v: Vec<i32> = alloc::vec![1, 2, 3];
449        let poke = Poke::new(&mut v);
450        let ll = poke.into_list_like().unwrap();
451        for mut item in ll.iter_mut().unwrap() {
452            let cur = *item.get::<i32>().unwrap();
453            item.set(cur * 10).unwrap();
454        }
455        assert_eq!(v, alloc::vec![10, 20, 30]);
456    }
457
458    #[test]
459    fn poke_list_like_vec_push_pop() {
460        let mut v: Vec<i32> = alloc::vec![];
461        {
462            let poke = Poke::new(&mut v);
463            let mut ll = poke.into_list_like().unwrap();
464            ll.push(10i32).unwrap();
465            ll.push(20i32).unwrap();
466            assert_eq!(ll.len(), 2);
467            let popped = ll.pop().unwrap().unwrap();
468            assert_eq!(popped.materialize::<i32>().unwrap(), 20);
469            assert_eq!(ll.len(), 1);
470        }
471        assert_eq!(v, alloc::vec![10]);
472    }
473
474    #[test]
475    fn poke_list_like_array_push_fails() {
476        let mut arr: [i32; 3] = [1, 2, 3];
477        let poke = Poke::new(&mut arr);
478        let mut ll = poke.into_list_like().unwrap();
479        let res = ll.push(4i32);
480        assert!(matches!(
481            res,
482            Err(ref err) if matches!(err.kind, ReflectErrorKind::OperationFailed { .. })
483        ));
484    }
485
486    #[test]
487    fn poke_list_like_array_pop_fails() {
488        let mut arr: [i32; 3] = [1, 2, 3];
489        let poke = Poke::new(&mut arr);
490        let mut ll = poke.into_list_like().unwrap();
491        let res = ll.pop();
492        assert!(matches!(
493            res,
494            Err(ref err) if matches!(err.kind, ReflectErrorKind::OperationFailed { .. })
495        ));
496    }
497
498    #[test]
499    fn poke_list_like_vec_swap() {
500        let mut v: Vec<i32> = alloc::vec![1, 2, 3];
501        let poke = Poke::new(&mut v);
502        let mut ll = poke.into_list_like().unwrap();
503        ll.swap(0, 2).unwrap();
504        assert_eq!(v, alloc::vec![3, 2, 1]);
505    }
506
507    #[test]
508    fn poke_list_like_array_swap() {
509        let mut arr: [i32; 3] = [10, 20, 30];
510        let poke = Poke::new(&mut arr);
511        let mut ll = poke.into_list_like().unwrap();
512        ll.swap(0, 2).unwrap();
513        assert_eq!(arr, [30, 20, 10]);
514    }
515
516    #[test]
517    fn poke_list_like_swap_out_of_bounds_fails() {
518        let mut v: Vec<i32> = alloc::vec![1, 2, 3];
519        let poke = Poke::new(&mut v);
520        let mut ll = poke.into_list_like().unwrap();
521        let res = ll.swap(5, 0);
522        assert!(matches!(
523            res,
524            Err(ref err) if matches!(err.kind, ReflectErrorKind::FieldError { .. })
525        ));
526    }
527}