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 {
16    pub(crate) shape: &'static Shape,
17    pub(crate) ptr: *const u8,
18}
19
20impl ValueId {
21    pub(crate) fn new(shape: &'static 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_lifetime> {
41    /// Underlying data
42    pub(crate) data: PtrConst<'mem>,
43
44    /// Shape of the value
45    pub(crate) shape: &'static Shape,
46
47    invariant: PhantomData<fn(&'facet_lifetime ()) -> &'facet_lifetime ()>,
48}
49
50impl<'mem, 'facet_lifetime> Peek<'mem, 'facet_lifetime> {
51    /// Creates a new `PeekValue` instance for a value of type `T`.
52    pub fn new<T: Facet<'facet_lifetime>>(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: &'static 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) -> &'static 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 {
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) -> &'static 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_lifetime>>(&self) -> Result<&T, ReflectError> {
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_lifetime>, ReflectError> {
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_lifetime>, ReflectError> {
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_lifetime>, ReflectError> {
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_lifetime>, ReflectError> {
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(self) -> Result<PeekListLike<'mem, 'facet_lifetime>, ReflectError> {
264        match self.shape.def {
265            Def::List(def) => Ok(PeekListLike::new(self, ListLikeDef::List(def))),
266            Def::Array(def) => Ok(PeekListLike::new(self, ListLikeDef::Array(def))),
267            _ => {
268                // &[i32] is actually a _pointer_ to a slice.
269                match self.shape.ty {
270                    Type::Pointer(ptr) => match ptr {
271                        PointerType::Reference(vpt) | PointerType::Raw(vpt) => {
272                            let target = (vpt.target)();
273                            match target.def {
274                                Def::Slice(def) => {
275                                    return Ok(PeekListLike::new(self, ListLikeDef::Slice(def)));
276                                }
277                                _ => {
278                                    // well it's not list-like then
279                                }
280                            }
281                        }
282                        PointerType::Function(_) => {
283                            // well that's not a list-like
284                        }
285                    },
286                    _ => {
287                        // well that's not a list-like either
288                    }
289                }
290
291                Err(ReflectError::WasNotA {
292                    expected: "list, array or slice",
293                    actual: self.shape,
294                })
295            }
296        }
297    }
298
299    /// Tries to identify this value as a smart pointer
300    pub fn into_smart_pointer(
301        self,
302    ) -> Result<PeekSmartPointer<'mem, 'facet_lifetime>, ReflectError> {
303        if let Def::SmartPointer(def) = self.shape.def {
304            Ok(PeekSmartPointer { value: self, def })
305        } else {
306            Err(ReflectError::WasNotA {
307                expected: "smart pointer",
308                actual: self.shape,
309            })
310        }
311    }
312
313    /// Tries to identify this value as an option
314    pub fn into_option(self) -> Result<super::PeekOption<'mem, 'facet_lifetime>, ReflectError> {
315        if let Def::Option(def) = self.shape.def {
316            Ok(super::PeekOption { value: self, def })
317        } else {
318            Err(ReflectError::WasNotA {
319                expected: "option",
320                actual: self.shape,
321            })
322        }
323    }
324
325    /// Tries to identify this value as a tuple
326    pub fn into_tuple(self) -> Result<PeekTuple<'mem, 'facet_lifetime>, ReflectError> {
327        if let Type::Sequence(SequenceType::Tuple(ty)) = self.shape.ty {
328            Ok(PeekTuple { value: self, ty })
329        } else {
330            Err(ReflectError::WasNotA {
331                expected: "tuple",
332                actual: self.shape,
333            })
334        }
335    }
336
337    /// Tries to return the innermost value — useful for serialization. For example, we serialize a `NonZero<u8>` the same
338    /// as a `u8`. Similarly, we serialize a `Utf8PathBuf` the same as a `String.
339    ///
340    /// Returns a `Peek` to the innermost value, unwrapping transparent wrappers recursively.
341    /// For example, this will peel through newtype wrappers or smart pointers that have an `inner`.
342    pub fn innermost_peek(self) -> Self {
343        let mut current_peek = self;
344        while let (Some(try_borrow_inner_fn), Some(inner_shape)) = (
345            current_peek.shape.vtable.try_borrow_inner,
346            current_peek.shape.inner,
347        ) {
348            unsafe {
349                let inner_data = try_borrow_inner_fn(current_peek.data).unwrap_or_else(|e| {
350                    panic!("innermost_peek: try_borrow_inner returned an error! was trying to go from {} to {}. error: {e}", current_peek.shape,
351                        inner_shape())
352                });
353
354                current_peek = Peek {
355                    data: inner_data,
356                    shape: inner_shape(),
357                    invariant: PhantomData,
358                };
359            }
360        }
361        current_peek
362    }
363}
364
365impl core::fmt::Display for Peek<'_, '_> {
366    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
367        if let Some(display_fn) = self.vtable().display {
368            unsafe { display_fn(self.data, f) }
369        } else {
370            write!(f, "⟨{}⟩", self.shape)
371        }
372    }
373}
374
375impl core::fmt::Debug for Peek<'_, '_> {
376    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
377        if let Some(debug_fn) = self.vtable().debug {
378            unsafe { debug_fn(self.data, f) }
379        } else {
380            write!(f, "⟨{}⟩", self.shape)
381        }
382    }
383}
384
385impl core::cmp::PartialEq for Peek<'_, '_> {
386    fn eq(&self, other: &Self) -> bool {
387        if self.shape != other.shape {
388            return false;
389        }
390        let eq_fn = match self.shape.vtable.eq {
391            Some(eq_fn) => eq_fn,
392            None => return false,
393        };
394        unsafe { eq_fn(self.data, other.data) }
395    }
396}
397
398impl core::cmp::PartialOrd for Peek<'_, '_> {
399    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
400        if self.shape != other.shape {
401            return None;
402        }
403        let partial_ord_fn = self.shape.vtable.partial_ord?;
404        unsafe { partial_ord_fn(self.data, other.data) }
405    }
406}
407
408impl core::hash::Hash for Peek<'_, '_> {
409    fn hash<H: core::hash::Hasher>(&self, hasher: &mut H) {
410        if let Some(hash_fn) = self.shape.vtable.hash {
411            let hasher_opaque = PtrMut::new(hasher);
412            unsafe {
413                hash_fn(self.data, hasher_opaque, |opaque, bytes| {
414                    opaque.as_mut::<H>().write(bytes)
415                })
416            };
417        } else {
418            panic!("Hashing is not supported for this shape");
419        }
420    }
421}