Skip to main content

facet_reflect/poke/
value.rs

1use core::marker::PhantomData;
2
3use facet_core::{Def, Facet, PtrConst, PtrMut, Shape, StructKind, Type, UserType, Variance};
4use facet_path::{Path, PathAccessError, PathStep};
5
6use crate::{
7    ReflectError, ReflectErrorKind,
8    peek::{ListLikeDef, TupleType, VariantError},
9};
10
11use super::{
12    PokeDynamicValue, PokeList, PokeListLike, PokeMap, PokeNdArray, PokeOption, PokePointer,
13    PokeResult, PokeSet, PokeStruct, PokeTuple,
14};
15
16/// A mutable view into a value with runtime type information.
17///
18/// `Poke` provides reflection capabilities for mutating values at runtime.
19/// It is the mutable counterpart to [`Peek`](crate::Peek).
20///
21/// # Wholesale Replacement vs Field Mutation
22///
23/// `Poke` can be created for any type. Replacing a value wholesale with [`Poke::set`]
24/// is always safe - it just drops the old value and writes the new one.
25///
26/// However, mutating individual struct fields via [`PokeStruct::set_field`] requires
27/// the struct to be marked as POD (`#[facet(pod)]`). This is because field mutation
28/// could violate struct-level invariants.
29///
30/// # Lifetime Parameters
31///
32/// - `'mem`: The memory lifetime - how long the underlying data is valid
33/// - `'facet`: The type's lifetime parameter (for types like `&'a str`)
34///
35/// # Example
36///
37/// ```ignore
38/// // Wholesale replacement works on any type
39/// let mut s = String::from("hello");
40/// let mut poke = Poke::new(&mut s);
41/// poke.set(String::from("world")).unwrap();
42///
43/// // Field mutation requires #[facet(pod)]
44/// #[derive(Facet)]
45/// #[facet(pod)]
46/// struct Point { x: i32, y: i32 }
47///
48/// let mut point = Point { x: 1, y: 2 };
49/// let mut poke = Poke::new(&mut point);
50/// poke.into_struct().unwrap().set_field_by_name("x", 10i32).unwrap();
51/// assert_eq!(point.x, 10);
52/// ```
53pub struct Poke<'mem, 'facet> {
54    /// Underlying data (mutable)
55    pub(crate) data: PtrMut,
56
57    /// Shape of the value
58    pub(crate) shape: &'static Shape,
59
60    /// Invariant with respect to 'facet (same reasoning as Peek)
61    /// Covariant with respect to 'mem but with mutable access
62    #[allow(clippy::type_complexity)]
63    _marker: PhantomData<(&'mem mut (), fn(&'facet ()) -> &'facet ())>,
64}
65
66impl<'mem, 'facet> Poke<'mem, 'facet> {
67    /// Creates a mutable view over a `T` value.
68    ///
69    /// This always succeeds - wholesale replacement via [`Poke::set`] is safe for any type.
70    /// The POD check happens when you try to mutate individual struct fields.
71    pub fn new<T: Facet<'facet>>(t: &'mem mut T) -> Self {
72        Self {
73            data: PtrMut::new(t as *mut T as *mut u8),
74            shape: T::SHAPE,
75            _marker: PhantomData,
76        }
77    }
78
79    /// Creates a mutable view from raw parts without any validation.
80    ///
81    /// # Safety
82    ///
83    /// - `data` must point to a valid, initialized value of the type described by `shape`
84    /// - `data` must be valid for the lifetime `'mem`
85    pub unsafe fn from_raw_parts(data: PtrMut, shape: &'static Shape) -> Self {
86        Self {
87            data,
88            shape,
89            _marker: PhantomData,
90        }
91    }
92
93    /// Returns the shape of the value.
94    #[inline(always)]
95    pub const fn shape(&self) -> &'static Shape {
96        self.shape
97    }
98
99    /// Returns a const pointer to the underlying data.
100    #[inline(always)]
101    pub const fn data(&self) -> PtrConst {
102        self.data.as_const()
103    }
104
105    /// Construct a ReflectError with this poke's shape as the root path.
106    #[inline]
107    pub(crate) fn err(&self, kind: ReflectErrorKind) -> ReflectError {
108        ReflectError::new(kind, Path::new(self.shape))
109    }
110
111    /// Returns a mutable pointer to the underlying data.
112    #[inline(always)]
113    pub const fn data_mut(&mut self) -> PtrMut {
114        self.data
115    }
116
117    /// Returns the computed variance of the underlying type.
118    #[inline]
119    pub fn variance(&self) -> Variance {
120        self.shape.computed_variance()
121    }
122
123    /// Attempts to reborrow this mutable view as an owned `Poke`.
124    ///
125    /// This is useful when only `&mut Poke` is available (e.g. through `DerefMut`)
126    /// but an API requires ownership of `Poke`.
127    ///
128    /// Returns `Some` if the underlying type can shrink the `'facet` lifetime
129    /// (covariant or bivariant), or `None` otherwise.
130    #[inline]
131    pub fn try_reborrow<'shorter>(&mut self) -> Option<Poke<'_, 'shorter>>
132    where
133        'facet: 'shorter,
134    {
135        if self.variance().can_shrink() {
136            Some(Poke {
137                data: self.data,
138                shape: self.shape,
139                _marker: PhantomData,
140            })
141        } else {
142            None
143        }
144    }
145
146    /// Returns true if this value is a struct.
147    #[inline]
148    pub const fn is_struct(&self) -> bool {
149        matches!(self.shape.ty, Type::User(UserType::Struct(_)))
150    }
151
152    /// Returns true if this value is an enum.
153    #[inline]
154    pub const fn is_enum(&self) -> bool {
155        matches!(self.shape.ty, Type::User(UserType::Enum(_)))
156    }
157
158    /// Returns true if this value is a scalar (primitive type).
159    #[inline]
160    pub const fn is_scalar(&self) -> bool {
161        matches!(self.shape.def, Def::Scalar)
162    }
163
164    /// Returns true if this value is a tuple (anonymous `(A, B, ...)`).
165    ///
166    /// Note: tuple structs (named `struct Foo(A, B);`) report `false` here; they match
167    /// [`is_struct`](Self::is_struct).
168    #[inline]
169    pub const fn is_tuple(&self) -> bool {
170        matches!(
171            self.shape.ty,
172            Type::User(UserType::Struct(s)) if matches!(s.kind, StructKind::Tuple)
173        )
174    }
175
176    /// Returns true if this value is a list (variable-length, homogeneous, e.g. `Vec<T>`).
177    #[inline]
178    pub const fn is_list(&self) -> bool {
179        matches!(self.shape.def, Def::List(_))
180    }
181
182    /// Returns true if this value is a fixed-size array (e.g. `[T; N]`).
183    #[inline]
184    pub const fn is_array(&self) -> bool {
185        matches!(self.shape.def, Def::Array(_))
186    }
187
188    /// Returns true if this value is a slice (e.g. `[T]`).
189    #[inline]
190    pub const fn is_slice(&self) -> bool {
191        matches!(self.shape.def, Def::Slice(_))
192    }
193
194    /// Returns true if this value is a list, array, or slice
195    /// (the set accepted by [`into_list_like`](Self::into_list_like)).
196    #[inline]
197    pub const fn is_list_like(&self) -> bool {
198        matches!(self.shape.def, Def::List(_) | Def::Array(_) | Def::Slice(_))
199    }
200
201    /// Returns true if this value is a map.
202    #[inline]
203    pub const fn is_map(&self) -> bool {
204        matches!(self.shape.def, Def::Map(_))
205    }
206
207    /// Returns true if this value is a set.
208    #[inline]
209    pub const fn is_set(&self) -> bool {
210        matches!(self.shape.def, Def::Set(_))
211    }
212
213    /// Returns true if this value is an option.
214    #[inline]
215    pub const fn is_option(&self) -> bool {
216        matches!(self.shape.def, Def::Option(_))
217    }
218
219    /// Returns true if this value is a result.
220    #[inline]
221    pub const fn is_result(&self) -> bool {
222        matches!(self.shape.def, Def::Result(_))
223    }
224
225    /// Returns true if this value is a (smart) pointer.
226    #[inline]
227    pub const fn is_pointer(&self) -> bool {
228        matches!(self.shape.def, Def::Pointer(_))
229    }
230
231    /// Returns true if this value is an n-dimensional array.
232    #[inline]
233    pub const fn is_ndarray(&self) -> bool {
234        matches!(self.shape.def, Def::NdArray(_))
235    }
236
237    /// Returns true if this value is a dynamic value
238    /// (e.g. `facet_value::Value` — runtime-kind-dispatched).
239    #[inline]
240    pub const fn is_dynamic_value(&self) -> bool {
241        matches!(self.shape.def, Def::DynamicValue(_))
242    }
243
244    /// Converts this into a `PokeStruct` if the value is a struct.
245    pub fn into_struct(self) -> Result<PokeStruct<'mem, 'facet>, ReflectError> {
246        match self.shape.ty {
247            Type::User(UserType::Struct(struct_type)) => Ok(PokeStruct {
248                value: self,
249                ty: struct_type,
250            }),
251            _ => Err(self.err(ReflectErrorKind::WasNotA {
252                expected: "struct",
253                actual: self.shape,
254            })),
255        }
256    }
257
258    /// Converts this into a `PokeEnum` if the value is an enum.
259    pub fn into_enum(self) -> Result<super::PokeEnum<'mem, 'facet>, ReflectError> {
260        match self.shape.ty {
261            Type::User(UserType::Enum(enum_type)) => Ok(super::PokeEnum {
262                value: self,
263                ty: enum_type,
264            }),
265            _ => Err(self.err(ReflectErrorKind::WasNotA {
266                expected: "enum",
267                actual: self.shape,
268            })),
269        }
270    }
271
272    /// Converts this into a `PokeList` if the value is a list.
273    #[inline]
274    pub fn into_list(self) -> Result<PokeList<'mem, 'facet>, ReflectError> {
275        if let Def::List(def) = self.shape.def {
276            // SAFETY: The ListDef comes from self.shape.def, where self.shape is obtained
277            // from a trusted source (either T::SHAPE from the Facet trait, or validated
278            // through other safe constructors). The vtable is therefore trusted.
279            return Ok(unsafe { PokeList::new(self, def) });
280        }
281
282        Err(self.err(ReflectErrorKind::WasNotA {
283            expected: "list",
284            actual: self.shape,
285        }))
286    }
287
288    /// Converts this into a `PokeListLike` if the value is a list, array, or slice.
289    #[inline]
290    pub fn into_list_like(self) -> Result<PokeListLike<'mem, 'facet>, ReflectError> {
291        match self.shape.def {
292            // SAFETY: The defs come from self.shape.def, where self.shape is obtained from
293            // a trusted source. The vtables are therefore trusted.
294            Def::List(def) => Ok(unsafe { PokeListLike::new(self, ListLikeDef::List(def)) }),
295            Def::Array(def) => Ok(unsafe { PokeListLike::new(self, ListLikeDef::Array(def)) }),
296            Def::Slice(def) => Ok(unsafe { PokeListLike::new(self, ListLikeDef::Slice(def)) }),
297            _ => Err(self.err(ReflectErrorKind::WasNotA {
298                expected: "list, array or slice",
299                actual: self.shape,
300            })),
301        }
302    }
303
304    /// Converts this into a `PokeMap` if the value is a map.
305    #[inline]
306    pub fn into_map(self) -> Result<PokeMap<'mem, 'facet>, ReflectError> {
307        if let Def::Map(def) = self.shape.def {
308            return Ok(unsafe { PokeMap::new(self, def) });
309        }
310
311        Err(self.err(ReflectErrorKind::WasNotA {
312            expected: "map",
313            actual: self.shape,
314        }))
315    }
316
317    /// Converts this into a `PokeSet` if the value is a set.
318    #[inline]
319    pub fn into_set(self) -> Result<PokeSet<'mem, 'facet>, ReflectError> {
320        if let Def::Set(def) = self.shape.def {
321            return Ok(unsafe { PokeSet::new(self, def) });
322        }
323
324        Err(self.err(ReflectErrorKind::WasNotA {
325            expected: "set",
326            actual: self.shape,
327        }))
328    }
329
330    /// Converts this into a `PokeOption` if the value is an option.
331    #[inline]
332    pub fn into_option(self) -> Result<PokeOption<'mem, 'facet>, ReflectError> {
333        if let Def::Option(def) = self.shape.def {
334            return Ok(unsafe { PokeOption::new(self, def) });
335        }
336
337        Err(self.err(ReflectErrorKind::WasNotA {
338            expected: "option",
339            actual: self.shape,
340        }))
341    }
342
343    /// Converts this into a `PokeResult` if the value is a result.
344    #[inline]
345    pub fn into_result(self) -> Result<PokeResult<'mem, 'facet>, ReflectError> {
346        if let Def::Result(def) = self.shape.def {
347            return Ok(unsafe { PokeResult::new(self, def) });
348        }
349
350        Err(self.err(ReflectErrorKind::WasNotA {
351            expected: "result",
352            actual: self.shape,
353        }))
354    }
355
356    /// Converts this into a `PokeTuple` if the value is a tuple (or tuple struct).
357    #[inline]
358    pub fn into_tuple(self) -> Result<PokeTuple<'mem, 'facet>, ReflectError> {
359        if let Type::User(UserType::Struct(struct_type)) = self.shape.ty
360            && struct_type.kind == StructKind::Tuple
361        {
362            return Ok(PokeTuple {
363                value: self,
364                ty: TupleType {
365                    fields: struct_type.fields,
366                },
367            });
368        }
369
370        Err(self.err(ReflectErrorKind::WasNotA {
371            expected: "tuple",
372            actual: self.shape,
373        }))
374    }
375
376    /// Converts this into a `PokePointer` if the value is a pointer.
377    #[inline]
378    pub fn into_pointer(self) -> Result<PokePointer<'mem, 'facet>, ReflectError> {
379        if let Def::Pointer(def) = self.shape.def {
380            return Ok(PokePointer { value: self, def });
381        }
382
383        Err(self.err(ReflectErrorKind::WasNotA {
384            expected: "smart pointer",
385            actual: self.shape,
386        }))
387    }
388
389    /// Converts this into a `PokeNdArray` if the value is an n-dimensional array.
390    #[inline]
391    pub fn into_ndarray(self) -> Result<PokeNdArray<'mem, 'facet>, ReflectError> {
392        if let Def::NdArray(def) = self.shape.def {
393            return Ok(unsafe { PokeNdArray::new(self, def) });
394        }
395
396        Err(self.err(ReflectErrorKind::WasNotA {
397            expected: "ndarray",
398            actual: self.shape,
399        }))
400    }
401
402    /// Converts this into a `PokeDynamicValue` if the value is a dynamic value.
403    #[inline]
404    pub fn into_dynamic_value(self) -> Result<PokeDynamicValue<'mem, 'facet>, ReflectError> {
405        if let Def::DynamicValue(def) = self.shape.def {
406            return Ok(PokeDynamicValue { value: self, def });
407        }
408
409        Err(self.err(ReflectErrorKind::WasNotA {
410            expected: "dynamic value",
411            actual: self.shape,
412        }))
413    }
414
415    /// Gets a reference to the underlying value.
416    ///
417    /// Returns an error if the shape doesn't match `T`.
418    pub fn get<T: Facet<'facet>>(&self) -> Result<&T, ReflectError> {
419        if self.shape != T::SHAPE {
420            return Err(self.err(ReflectErrorKind::WrongShape {
421                expected: self.shape,
422                actual: T::SHAPE,
423            }));
424        }
425        Ok(unsafe { self.data.as_const().get::<T>() })
426    }
427
428    /// Gets a mutable reference to the underlying value.
429    ///
430    /// Returns an error if the shape doesn't match `T`.
431    pub fn get_mut<T: Facet<'facet>>(&mut self) -> Result<&mut T, ReflectError> {
432        if self.shape != T::SHAPE {
433            return Err(self.err(ReflectErrorKind::WrongShape {
434                expected: self.shape,
435                actual: T::SHAPE,
436            }));
437        }
438        Ok(unsafe { self.data.as_mut::<T>() })
439    }
440
441    /// Sets the value to a new value.
442    ///
443    /// This replaces the entire value. The new value must have the same shape.
444    pub fn set<T: Facet<'facet>>(&mut self, value: T) -> Result<(), ReflectError> {
445        if self.shape != T::SHAPE {
446            return Err(self.err(ReflectErrorKind::WrongShape {
447                expected: self.shape,
448                actual: T::SHAPE,
449            }));
450        }
451        unsafe {
452            // Drop the old value and write the new one
453            self.shape.call_drop_in_place(self.data);
454            core::ptr::write(self.data.as_mut_byte_ptr() as *mut T, value);
455        }
456        Ok(())
457    }
458
459    /// Borrows this `Poke` as a read-only `Peek`.
460    #[inline]
461    pub fn as_peek(&self) -> crate::Peek<'_, 'facet> {
462        unsafe { crate::Peek::unchecked_new(self.data.as_const(), self.shape) }
463    }
464
465    /// Consumes this `Poke`, returning a read-only `Peek` with the same `'mem` lifetime.
466    #[inline]
467    pub fn into_peek(self) -> crate::Peek<'mem, 'facet> {
468        unsafe { crate::Peek::unchecked_new(self.data.as_const(), self.shape) }
469    }
470
471    /// Navigate to a nested value by following a [`Path`], returning a mutable view.
472    ///
473    /// Each [`PathStep`] in the path is applied in order, descending into
474    /// structs, enums, and lists. If any step cannot be applied, a
475    /// [`PathAccessError`] is returned with the step index and context.
476    ///
477    /// # Supported steps
478    ///
479    /// - `Field` — struct fields and enum variant fields (after `Variant`)
480    /// - `Variant` — verify enum variant matches, then allow `Field` access
481    /// - `Index` — list/array element access
482    /// - `OptionSome` — navigate into `Some(T)` or return `OptionIsNone`
483    ///
484    /// `MapKey`, `MapValue`, `Deref`, `Inner`, and `Proxy` are currently not
485    /// supported for mutable access and return
486    /// [`PathAccessError::MissingTarget`].
487    ///
488    /// # Errors
489    ///
490    /// Returns [`PathAccessError`] if:
491    /// - The path's root shape doesn't match this value's shape
492    /// - A step kind doesn't apply to the current shape
493    /// - A field/list index is out of bounds
494    /// - An enum variant doesn't match the runtime variant
495    pub fn at_path_mut(self, path: &Path) -> Result<Poke<'mem, 'facet>, PathAccessError> {
496        if self.shape != path.shape {
497            return Err(PathAccessError::RootShapeMismatch {
498                expected: path.shape,
499                actual: self.shape,
500            });
501        }
502
503        let mut data = self.data;
504        let mut shape: &'static Shape = self.shape;
505
506        for (step_index, step) in path.steps().iter().enumerate() {
507            let (new_data, new_shape) = apply_step_mut(data, shape, *step, step_index)?;
508            data = new_data;
509            shape = new_shape;
510        }
511
512        Ok(unsafe { Poke::from_raw_parts(data, shape) })
513    }
514}
515
516/// Apply a single [`PathStep`] to mutable data, returning the new pointer and shape.
517///
518/// This is a free function rather than a method to avoid lifetime issues with
519/// chaining mutable borrows through `Poke`.
520fn apply_step_mut(
521    data: PtrMut,
522    shape: &'static Shape,
523    step: PathStep,
524    step_index: usize,
525) -> Result<(PtrMut, &'static Shape), PathAccessError> {
526    match step {
527        PathStep::Field(idx) => {
528            let idx = idx as usize;
529            match shape.ty {
530                Type::User(UserType::Struct(sd)) => {
531                    if idx >= sd.fields.len() {
532                        return Err(PathAccessError::IndexOutOfBounds {
533                            step,
534                            step_index,
535                            shape,
536                            index: idx,
537                            bound: sd.fields.len(),
538                        });
539                    }
540                    let field = &sd.fields[idx];
541                    let field_data = unsafe { data.field(field.offset) };
542                    Ok((field_data, field.shape()))
543                }
544                Type::User(UserType::Enum(enum_type)) => {
545                    // Determine active variant to get field layout
546                    let variant_idx = variant_index_from_raw(data.as_const(), shape, enum_type)
547                        .map_err(|_| PathAccessError::WrongStepKind {
548                            step,
549                            step_index,
550                            shape,
551                        })?;
552                    let variant = &enum_type.variants[variant_idx];
553                    if idx >= variant.data.fields.len() {
554                        return Err(PathAccessError::IndexOutOfBounds {
555                            step,
556                            step_index,
557                            shape,
558                            index: idx,
559                            bound: variant.data.fields.len(),
560                        });
561                    }
562                    let field = &variant.data.fields[idx];
563                    let field_data = unsafe { data.field(field.offset) };
564                    Ok((field_data, field.shape()))
565                }
566                _ => Err(PathAccessError::WrongStepKind {
567                    step,
568                    step_index,
569                    shape,
570                }),
571            }
572        }
573
574        PathStep::Variant(expected_idx) => {
575            let expected_idx = expected_idx as usize;
576            let enum_type = match shape.ty {
577                Type::User(UserType::Enum(et)) => et,
578                _ => {
579                    return Err(PathAccessError::WrongStepKind {
580                        step,
581                        step_index,
582                        shape,
583                    });
584                }
585            };
586
587            if expected_idx >= enum_type.variants.len() {
588                return Err(PathAccessError::IndexOutOfBounds {
589                    step,
590                    step_index,
591                    shape,
592                    index: expected_idx,
593                    bound: enum_type.variants.len(),
594                });
595            }
596
597            let actual_idx =
598                variant_index_from_raw(data.as_const(), shape, enum_type).map_err(|_| {
599                    PathAccessError::WrongStepKind {
600                        step,
601                        step_index,
602                        shape,
603                    }
604                })?;
605
606            if actual_idx != expected_idx {
607                return Err(PathAccessError::VariantMismatch {
608                    step_index,
609                    shape,
610                    expected_variant: expected_idx,
611                    actual_variant: actual_idx,
612                });
613            }
614
615            // Stay at the same location — next Field step reads variant fields
616            Ok((data, shape))
617        }
618
619        PathStep::Index(idx) => {
620            let idx = idx as usize;
621            match shape.def {
622                Def::List(def) => {
623                    let get_mut_fn = def.vtable.get_mut.ok_or(PathAccessError::WrongStepKind {
624                        step,
625                        step_index,
626                        shape,
627                    })?;
628                    let len = unsafe { (def.vtable.len)(data.as_const()) };
629                    let item = unsafe { get_mut_fn(data, idx, shape) };
630                    item.map(|ptr| (ptr, def.t()))
631                        .ok_or(PathAccessError::IndexOutOfBounds {
632                            step,
633                            step_index,
634                            shape,
635                            index: idx,
636                            bound: len,
637                        })
638                }
639                Def::Array(def) => {
640                    // Arrays have a fixed element type and contiguous layout
641                    let elem_shape = def.t();
642                    let layout = elem_shape.layout.sized_layout().map_err(|_| {
643                        PathAccessError::WrongStepKind {
644                            step,
645                            step_index,
646                            shape,
647                        }
648                    })?;
649                    let len = def.n;
650                    if idx >= len {
651                        return Err(PathAccessError::IndexOutOfBounds {
652                            step,
653                            step_index,
654                            shape,
655                            index: idx,
656                            bound: len,
657                        });
658                    }
659                    let elem_data = unsafe { data.field(layout.size() * idx) };
660                    Ok((elem_data, elem_shape))
661                }
662                _ => Err(PathAccessError::WrongStepKind {
663                    step,
664                    step_index,
665                    shape,
666                }),
667            }
668        }
669
670        PathStep::OptionSome => {
671            if let Def::Option(option_def) = shape.def {
672                // Check if the option is Some
673                let is_some = unsafe { (option_def.vtable.is_some)(data.as_const()) };
674                if !is_some {
675                    return Err(PathAccessError::OptionIsNone { step_index, shape });
676                }
677                // Option is Some — get the inner value pointer.
678                // Use get_value to find the PtrConst, then compute the offset
679                // from the Option base to construct a PtrMut.
680                let inner_raw_ptr = unsafe { (option_def.vtable.get_value)(data.as_const()) };
681                assert!(
682                    !inner_raw_ptr.is_null(),
683                    "is_some was true but get_value returned null"
684                );
685                let inner_ptr_const = facet_core::PtrConst::new_sized(inner_raw_ptr);
686                // Compute offset from option base to inner value
687                let offset = unsafe {
688                    inner_ptr_const
689                        .as_byte_ptr()
690                        .offset_from(data.as_const().as_byte_ptr())
691                } as usize;
692                let inner_data = unsafe { data.field(offset) };
693                Ok((inner_data, option_def.t()))
694            } else {
695                Err(PathAccessError::WrongStepKind {
696                    step,
697                    step_index,
698                    shape,
699                })
700            }
701        }
702
703        PathStep::MapKey(_) | PathStep::MapValue(_) => {
704            if matches!(shape.def, Def::Map(_)) {
705                Err(PathAccessError::MissingTarget {
706                    step,
707                    step_index,
708                    shape,
709                })
710            } else {
711                Err(PathAccessError::WrongStepKind {
712                    step,
713                    step_index,
714                    shape,
715                })
716            }
717        }
718
719        PathStep::Deref => {
720            if matches!(shape.def, Def::Pointer(_)) {
721                Err(PathAccessError::MissingTarget {
722                    step,
723                    step_index,
724                    shape,
725                })
726            } else {
727                Err(PathAccessError::WrongStepKind {
728                    step,
729                    step_index,
730                    shape,
731                })
732            }
733        }
734
735        PathStep::Inner => Err(PathAccessError::MissingTarget {
736            step,
737            step_index,
738            shape,
739        }),
740
741        PathStep::Proxy => Err(PathAccessError::MissingTarget {
742            step,
743            step_index,
744            shape,
745        }),
746    }
747}
748
749/// Determine the active variant index from raw data, replicating the logic
750/// from `PeekEnum::variant_index` without constructing a `Peek`.
751fn variant_index_from_raw(
752    data: PtrConst,
753    shape: &'static Shape,
754    enum_type: facet_core::EnumType,
755) -> Result<usize, VariantError> {
756    use facet_core::EnumRepr;
757
758    // For Option<T>, use the OptionVTable
759    if let Def::Option(option_def) = shape.def {
760        let is_some = unsafe { (option_def.vtable.is_some)(data) };
761        return Ok(enum_type
762            .variants
763            .iter()
764            .position(|variant| {
765                let has_fields = !variant.data.fields.is_empty();
766                has_fields == is_some
767            })
768            .expect("No variant found matching Option state"));
769    }
770
771    if enum_type.enum_repr == EnumRepr::RustNPO {
772        let layout = shape
773            .layout
774            .sized_layout()
775            .map_err(|_| VariantError::Unsized)?;
776        let slice = unsafe { core::slice::from_raw_parts(data.as_byte_ptr(), layout.size()) };
777        let all_zero = slice.iter().all(|v| *v == 0);
778
779        Ok(enum_type
780            .variants
781            .iter()
782            .position(|variant| {
783                let mut max_offset = 0;
784                for field in variant.data.fields {
785                    let offset = field.offset
786                        + field
787                            .shape()
788                            .layout
789                            .sized_layout()
790                            .map(|v| v.size())
791                            .unwrap_or(0);
792                    max_offset = core::cmp::max(max_offset, offset);
793                }
794                if all_zero {
795                    max_offset == 0
796                } else {
797                    max_offset != 0
798                }
799            })
800            .expect("No variant found with matching discriminant"))
801    } else {
802        let discriminant = match enum_type.enum_repr {
803            EnumRepr::Rust => {
804                panic!("cannot read discriminant from Rust enum with unspecified layout")
805            }
806            EnumRepr::RustNPO => 0,
807            EnumRepr::U8 => unsafe { data.read::<u8>() as i64 },
808            EnumRepr::U16 => unsafe { data.read::<u16>() as i64 },
809            EnumRepr::U32 => unsafe { data.read::<u32>() as i64 },
810            EnumRepr::U64 => unsafe { data.read::<u64>() as i64 },
811            EnumRepr::USize => unsafe { data.read::<usize>() as i64 },
812            EnumRepr::I8 => unsafe { data.read::<i8>() as i64 },
813            EnumRepr::I16 => unsafe { data.read::<i16>() as i64 },
814            EnumRepr::I32 => unsafe { data.read::<i32>() as i64 },
815            EnumRepr::I64 => unsafe { data.read::<i64>() },
816            EnumRepr::ISize => unsafe { data.read::<isize>() as i64 },
817        };
818
819        Ok(enum_type
820            .variants
821            .iter()
822            .position(|variant| variant.discriminant == Some(discriminant))
823            .expect("No variant found with matching discriminant"))
824    }
825}
826
827impl core::fmt::Debug for Poke<'_, '_> {
828    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
829        write!(f, "Poke<{}>", self.shape)
830    }
831}
832
833#[cfg(test)]
834mod tests {
835    use super::*;
836
837    #[test]
838    fn poke_primitive_get_set() {
839        let mut x: i32 = 42;
840        let mut poke = Poke::new(&mut x);
841
842        assert_eq!(*poke.get::<i32>().unwrap(), 42);
843
844        poke.set(100i32).unwrap();
845        assert_eq!(x, 100);
846    }
847
848    #[test]
849    fn poke_primitive_get_mut() {
850        let mut x: i32 = 42;
851        let mut poke = Poke::new(&mut x);
852
853        *poke.get_mut::<i32>().unwrap() = 99;
854        assert_eq!(x, 99);
855    }
856
857    #[test]
858    fn poke_wrong_type_fails() {
859        let mut x: i32 = 42;
860        let poke = Poke::new(&mut x);
861
862        let result = poke.get::<u32>();
863        assert!(matches!(
864            result,
865            Err(ReflectError {
866                kind: ReflectErrorKind::WrongShape { .. },
867                ..
868            })
869        ));
870    }
871
872    #[test]
873    fn poke_set_wrong_type_fails() {
874        let mut x: i32 = 42;
875        let mut poke = Poke::new(&mut x);
876
877        let result = poke.set(42u32);
878        assert!(matches!(
879            result,
880            Err(ReflectError {
881                kind: ReflectErrorKind::WrongShape { .. },
882                ..
883            })
884        ));
885    }
886
887    #[test]
888    fn poke_string_drop_and_replace() {
889        // Wholesale replacement works on any type, including String
890        let mut s = String::from("hello");
891        let mut poke = Poke::new(&mut s);
892
893        poke.set(String::from("world")).unwrap();
894        assert_eq!(s, "world");
895    }
896
897    #[test]
898    fn poke_is_predicates() {
899        let mut v: alloc::vec::Vec<i32> = alloc::vec![1, 2, 3];
900        let poke = Poke::new(&mut v);
901        assert!(poke.is_list());
902        assert!(poke.is_list_like());
903        assert!(!poke.is_map());
904        assert!(!poke.is_set());
905        assert!(!poke.is_option());
906        assert!(!poke.is_result());
907        assert!(!poke.is_tuple());
908        assert!(!poke.is_scalar());
909
910        let mut x: Option<i32> = Some(1);
911        let poke = Poke::new(&mut x);
912        assert!(poke.is_option());
913        assert!(!poke.is_list());
914
915        let mut r: Result<i32, i32> = Ok(1);
916        let poke = Poke::new(&mut r);
917        assert!(poke.is_result());
918
919        let mut t: (i32, i32) = (1, 2);
920        let poke = Poke::new(&mut t);
921        assert!(poke.is_tuple());
922
923        let mut n: i32 = 42;
924        let poke = Poke::new(&mut n);
925        assert!(poke.is_scalar());
926
927        let mut a: [i32; 3] = [1, 2, 3];
928        let poke = Poke::new(&mut a);
929        assert!(poke.is_array());
930        assert!(poke.is_list_like());
931    }
932}