facet_reflect/peek/
value.rs

1use core::{cmp::Ordering, marker::PhantomData};
2use facet_core::{
3    Def, Facet, PointerType, PtrConst, PtrMut, SequenceType, Shape, Type, TypeNameOpts, UserType,
4    ValueVTable,
5};
6
7use crate::{ReflectError, ScalarType};
8
9use super::{
10    ListLikeDef, PeekEnum, PeekList, PeekListLike, PeekMap, PeekSmartPointer, PeekStruct, PeekTuple,
11};
12
13/// A unique identifier for a peek value
14#[derive(Clone, Copy, PartialEq, Eq, Hash)]
15pub struct ValueId<'shape> {
16    pub(crate) shape: &'shape Shape<'shape>,
17    pub(crate) ptr: *const u8,
18}
19
20impl<'shape> ValueId<'shape> {
21    pub(crate) fn new(shape: &'shape Shape<'shape>, ptr: *const u8) -> Self {
22        Self { shape, ptr }
23    }
24}
25
26impl core::fmt::Display for ValueId<'_> {
27    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
28        write!(f, "{}@{:p}", self.shape, self.ptr)
29    }
30}
31
32impl core::fmt::Debug for ValueId<'_> {
33    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
34        core::fmt::Display::fmt(self, f)
35    }
36}
37
38/// Lets you read from a value (implements read-only [`ValueVTable`] proxies)
39#[derive(Clone, Copy)]
40pub struct Peek<'mem, 'facet, 'shape> {
41    /// Underlying data
42    pub(crate) data: PtrConst<'mem>,
43
44    /// Shape of the value
45    pub(crate) shape: &'shape Shape<'shape>,
46
47    invariant: PhantomData<fn(&'facet ()) -> &'facet ()>,
48}
49
50impl<'mem, 'facet, 'shape> Peek<'mem, 'facet, 'shape> {
51    /// Creates a new `PeekValue` instance for a value of type `T`.
52    pub fn new<T: Facet<'facet>>(t: &'mem T) -> Self {
53        Self {
54            data: PtrConst::new(t as *const T),
55            shape: T::SHAPE,
56            invariant: PhantomData,
57        }
58    }
59
60    /// Creates a new `PeekValue` instance without checking the type.
61    ///
62    /// # Safety
63    ///
64    /// This function is unsafe because it doesn't check if the provided data
65    /// and shape are compatible. The caller must ensure that the data is valid
66    /// for the given shape.
67    pub unsafe fn unchecked_new(data: PtrConst<'mem>, shape: &'shape Shape<'shape>) -> Self {
68        Self {
69            data,
70            shape,
71            invariant: PhantomData,
72        }
73    }
74
75    /// Returns the vtable
76    #[inline(always)]
77    pub fn vtable(&self) -> &'shape ValueVTable {
78        self.shape.vtable
79    }
80
81    /// Returns a unique identifier for this value, usable for cycle detection
82    pub fn id(&self) -> ValueId<'shape> {
83        ValueId::new(self.shape, self.data.as_byte_ptr())
84    }
85
86    /// Returns true if the two values are pointer-equal
87    #[inline]
88    pub fn ptr_eq(&self, other: &Peek<'_, '_, '_>) -> bool {
89        self.data.as_byte_ptr() == other.data.as_byte_ptr()
90    }
91
92    /// Returns true if this scalar is equal to the other scalar
93    ///
94    /// # Returns
95    ///
96    /// `false` if equality comparison is not supported for this scalar type
97    #[inline]
98    pub fn eq(&self, other: &Peek<'_, '_, '_>) -> Option<bool> {
99        unsafe {
100            self.shape
101                .vtable
102                .eq
103                .map(|eq_fn| eq_fn(self.data, other.data))
104        }
105    }
106
107    /// Compares this scalar with another and returns their ordering
108    ///
109    /// # Returns
110    ///
111    /// `None` if comparison is not supported for this scalar type
112    #[inline]
113    pub fn partial_cmp(&self, other: &Peek<'_, '_, '_>) -> Option<Ordering> {
114        unsafe {
115            self.shape
116                .vtable
117                .partial_ord
118                .and_then(|partial_ord_fn| partial_ord_fn(self.data, other.data))
119        }
120    }
121
122    /// Hashes this scalar
123    ///
124    /// # Returns
125    ///
126    /// `false` if hashing is not supported for this scalar type, `true` otherwise
127    #[inline(always)]
128    pub fn hash<H: core::hash::Hasher>(&self, hasher: &mut H) -> bool {
129        unsafe {
130            if let Some(hash_fn) = self.shape.vtable.hash {
131                let hasher_opaque = PtrMut::new(hasher);
132                hash_fn(self.data, hasher_opaque, |opaque, bytes| {
133                    opaque.as_mut::<H>().write(bytes)
134                });
135                true
136            } else {
137                false
138            }
139        }
140    }
141
142    /// Returns the type name of this scalar
143    ///
144    /// # Arguments
145    ///
146    /// * `f` - A mutable reference to a `core::fmt::Formatter`
147    /// * `opts` - The `TypeNameOpts` to use for formatting
148    ///
149    /// # Returns
150    ///
151    /// The result of the type name formatting
152    #[inline(always)]
153    pub fn type_name(
154        &self,
155        f: &mut core::fmt::Formatter<'_>,
156        opts: TypeNameOpts,
157    ) -> core::fmt::Result {
158        (self.shape.vtable.type_name)(f, opts)
159    }
160
161    /// Returns the shape
162    #[inline(always)]
163    pub const fn shape(&self) -> &'shape Shape<'shape> {
164        self.shape
165    }
166
167    /// Returns the data
168    #[inline(always)]
169    pub const fn data(&self) -> PtrConst<'mem> {
170        self.data
171    }
172
173    /// Get the scalar type if set.
174    pub fn scalar_type(&self) -> Option<ScalarType> {
175        ScalarType::try_from_shape(self.shape)
176    }
177
178    /// Read the value from memory into a Rust value.
179    ///
180    /// # Panics
181    ///
182    /// Panics if the shape doesn't match the type `T`.
183    pub fn get<T: Facet<'facet>>(&self) -> Result<&T, ReflectError<'shape>> {
184        if self.shape != T::SHAPE {
185            Err(ReflectError::WrongShape {
186                expected: self.shape,
187                actual: T::SHAPE,
188            })
189        } else {
190            Ok(unsafe { self.data.get::<T>() })
191        }
192    }
193
194    /// Try to get the value as a string if it's a string type
195    /// Returns None if the value is not a string or couldn't be extracted
196    pub fn as_str(&self) -> Option<&'mem str> {
197        let peek = self.innermost_peek();
198        if let Some(ScalarType::Str) = peek.scalar_type() {
199            unsafe { Some(peek.data.get::<&str>()) }
200        } else if let Some(ScalarType::String) = peek.scalar_type() {
201            unsafe { Some(peek.data.get::<alloc::string::String>().as_str()) }
202        } else if let Type::Pointer(PointerType::Reference(vpt)) = peek.shape.ty {
203            let target_shape = (vpt.target)();
204            if let Some(ScalarType::Str) = ScalarType::try_from_shape(target_shape) {
205                unsafe { Some(peek.data.get::<&str>()) }
206            } else {
207                None
208            }
209        } else {
210            None
211        }
212    }
213
214    /// Tries to identify this value as a struct
215    pub fn into_struct(self) -> Result<PeekStruct<'mem, 'facet, 'shape>, ReflectError<'shape>> {
216        if let Type::User(UserType::Struct(ty)) = self.shape.ty {
217            Ok(PeekStruct { value: self, ty })
218        } else {
219            Err(ReflectError::WasNotA {
220                expected: "struct",
221                actual: self.shape,
222            })
223        }
224    }
225
226    /// Tries to identify this value as an enum
227    pub fn into_enum(self) -> Result<PeekEnum<'mem, 'facet, 'shape>, ReflectError<'shape>> {
228        if let Type::User(UserType::Enum(ty)) = self.shape.ty {
229            Ok(PeekEnum { value: self, ty })
230        } else {
231            Err(ReflectError::WasNotA {
232                expected: "enum",
233                actual: self.shape,
234            })
235        }
236    }
237
238    /// Tries to identify this value as a map
239    pub fn into_map(self) -> Result<PeekMap<'mem, 'facet, 'shape>, ReflectError<'shape>> {
240        if let Def::Map(def) = self.shape.def {
241            Ok(PeekMap { value: self, def })
242        } else {
243            Err(ReflectError::WasNotA {
244                expected: "map",
245                actual: self.shape,
246            })
247        }
248    }
249
250    /// Tries to identify this value as a list
251    pub fn into_list(self) -> Result<PeekList<'mem, 'facet, 'shape>, ReflectError<'shape>> {
252        if let Def::List(def) = self.shape.def {
253            return Ok(PeekList { value: self, def });
254        }
255
256        Err(ReflectError::WasNotA {
257            expected: "list",
258            actual: self.shape,
259        })
260    }
261
262    /// Tries to identify this value as a list, array or slice
263    pub fn into_list_like(
264        self,
265    ) -> Result<PeekListLike<'mem, 'facet, 'shape>, ReflectError<'shape>> {
266        match self.shape.def {
267            Def::List(def) => Ok(PeekListLike::new(self, ListLikeDef::List(def))),
268            Def::Array(def) => Ok(PeekListLike::new(self, ListLikeDef::Array(def))),
269            _ => {
270                // &[i32] is actually a _pointer_ to a slice.
271                match self.shape.ty {
272                    Type::Pointer(ptr) => match ptr {
273                        PointerType::Reference(vpt) | PointerType::Raw(vpt) => {
274                            let target = (vpt.target)();
275                            match target.def {
276                                Def::Slice(def) => {
277                                    return Ok(PeekListLike::new(self, ListLikeDef::Slice(def)));
278                                }
279                                _ => {
280                                    // well it's not list-like then
281                                }
282                            }
283                        }
284                        PointerType::Function(_) => {
285                            // well that's not a list-like
286                        }
287                    },
288                    _ => {
289                        // well that's not a list-like either
290                    }
291                }
292
293                Err(ReflectError::WasNotA {
294                    expected: "list, array or slice",
295                    actual: self.shape,
296                })
297            }
298        }
299    }
300
301    /// Tries to identify this value as a smart pointer
302    pub fn into_smart_pointer(
303        self,
304    ) -> Result<PeekSmartPointer<'mem, 'facet, 'shape>, ReflectError<'shape>> {
305        if let Def::SmartPointer(def) = self.shape.def {
306            Ok(PeekSmartPointer { value: self, def })
307        } else {
308            Err(ReflectError::WasNotA {
309                expected: "smart pointer",
310                actual: self.shape,
311            })
312        }
313    }
314
315    /// Tries to identify this value as an option
316    pub fn into_option(
317        self,
318    ) -> Result<super::PeekOption<'mem, 'facet, 'shape>, ReflectError<'shape>> {
319        if let Def::Option(def) = self.shape.def {
320            Ok(super::PeekOption { value: self, def })
321        } else {
322            Err(ReflectError::WasNotA {
323                expected: "option",
324                actual: self.shape,
325            })
326        }
327    }
328
329    /// Tries to identify this value as a tuple
330    pub fn into_tuple(self) -> Result<PeekTuple<'mem, 'facet, 'shape>, ReflectError<'shape>> {
331        if let Type::Sequence(SequenceType::Tuple(ty)) = self.shape.ty {
332            Ok(PeekTuple { value: self, ty })
333        } else {
334            Err(ReflectError::WasNotA {
335                expected: "tuple",
336                actual: self.shape,
337            })
338        }
339    }
340
341    /// Tries to return the innermost value — useful for serialization. For example, we serialize a `NonZero<u8>` the same
342    /// as a `u8`. Similarly, we serialize a `Utf8PathBuf` the same as a `String.
343    ///
344    /// Returns a `Peek` to the innermost value, unwrapping transparent wrappers recursively.
345    /// For example, this will peel through newtype wrappers or smart pointers that have an `inner`.
346    pub fn innermost_peek(self) -> Self {
347        let mut current_peek = self;
348        while let (Some(try_borrow_inner_fn), Some(inner_shape)) = (
349            current_peek.shape.vtable.try_borrow_inner,
350            current_peek.shape.inner,
351        ) {
352            unsafe {
353                let inner_data = try_borrow_inner_fn(current_peek.data).unwrap_or_else(|e| {
354                    panic!("innermost_peek: try_borrow_inner returned an error! was trying to go from {} to {}. error: {e}", current_peek.shape,
355                        inner_shape())
356                });
357
358                current_peek = Peek {
359                    data: inner_data,
360                    shape: inner_shape(),
361                    invariant: PhantomData,
362                };
363            }
364        }
365        current_peek
366    }
367}
368
369impl<'mem, 'facet, 'shape> core::fmt::Display for Peek<'mem, 'facet, 'shape> {
370    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
371        if let Some(display_fn) = self.vtable().display {
372            unsafe { display_fn(self.data, f) }
373        } else {
374            write!(f, "⟨{}⟩", self.shape)
375        }
376    }
377}
378
379impl<'mem, 'facet, 'shape> core::fmt::Debug for Peek<'mem, 'facet, 'shape> {
380    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
381        if let Some(debug_fn) = self.vtable().debug {
382            unsafe { debug_fn(self.data, f) }
383        } else {
384            write!(f, "⟨{}⟩", self.shape)
385        }
386    }
387}
388
389impl<'mem, 'facet, 'shape> core::cmp::PartialEq for Peek<'mem, 'facet, 'shape> {
390    fn eq(&self, other: &Self) -> bool {
391        if self.shape != other.shape {
392            return false;
393        }
394        let eq_fn = match self.shape.vtable.eq {
395            Some(eq_fn) => eq_fn,
396            None => return false,
397        };
398        unsafe { eq_fn(self.data, other.data) }
399    }
400}
401
402impl<'mem, 'facet, 'shape> core::cmp::PartialOrd for Peek<'mem, 'facet, 'shape> {
403    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
404        if self.shape != other.shape {
405            return None;
406        }
407        let partial_ord_fn = self.shape.vtable.partial_ord?;
408        unsafe { partial_ord_fn(self.data, other.data) }
409    }
410}
411
412impl<'mem, 'facet, 'shape> core::hash::Hash for Peek<'mem, 'facet, 'shape> {
413    fn hash<H: core::hash::Hasher>(&self, hasher: &mut H) {
414        if let Some(hash_fn) = self.shape.vtable.hash {
415            let hasher_opaque = PtrMut::new(hasher);
416            unsafe {
417                hash_fn(self.data, hasher_opaque, |opaque, bytes| {
418                    opaque.as_mut::<H>().write(bytes)
419                })
420            };
421        } else {
422            panic!("Hashing is not supported for this shape");
423        }
424    }
425}