Skip to main content

facet_reflect/peek/
value.rs

1use core::{cmp::Ordering, marker::PhantomData, ptr::NonNull};
2#[cfg(feature = "alloc")]
3use facet_core::Field;
4use facet_core::{
5    Def, Facet, PointerType, PtrConst, Shape, StructKind, Type, TypeNameOpts, UserType,
6    VTableErased, Variance,
7};
8
9use crate::{PeekNdArray, PeekSet, ReflectError, ScalarType};
10
11use super::{
12    ListLikeDef, PeekDynamicValue, PeekEnum, PeekList, PeekListLike, PeekMap, PeekOption,
13    PeekPointer, PeekResult, PeekStruct, PeekTuple, tuple::TupleType,
14};
15
16#[cfg(feature = "alloc")]
17use super::OwnedPeek;
18
19/// A unique identifier for a peek value
20#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
21pub struct ValueId {
22    pub(crate) shape: &'static Shape,
23    pub(crate) ptr: *const u8,
24}
25
26impl ValueId {
27    #[inline]
28    pub(crate) const fn new(shape: &'static Shape, ptr: *const u8) -> Self {
29        Self { shape, ptr }
30    }
31}
32
33impl core::fmt::Display for ValueId {
34    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
35        write!(f, "{}@{:p}", self.shape, self.ptr)
36    }
37}
38
39impl core::fmt::Debug for ValueId {
40    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
41        core::fmt::Display::fmt(self, f)
42    }
43}
44
45/// A read-only view into a value with runtime type information.
46///
47/// `Peek` provides reflection capabilities for reading values at runtime.
48/// If the value is a struct, you can read its fields; if it's an enum,
49/// you can determine which variant is selected; if it's a scalar, you can
50/// extract a concrete value.
51///
52/// # Lifetime Parameters
53///
54/// - `'mem`: The memory lifetime - how long the underlying data is valid
55/// - `'facet`: The type's lifetime parameter (for types like `&'a str`)
56///
57/// # Variance and Soundness
58///
59/// `Peek` is **invariant** with respect to `'facet`. This is required for soundness:
60/// if `Peek` were covariant, it would be possible to launder lifetimes
61/// through reflection, leading to use-after-free bugs with types like
62/// `fn(&'a str)`. See [issue #1168](https://github.com/facet-rs/facet/issues/1168).
63///
64/// The underlying type's variance is tracked in [`Shape::variance`], which
65/// can be used for future variance-aware APIs.
66#[allow(clippy::type_complexity)]
67#[derive(Clone, Copy)]
68pub struct Peek<'mem, 'facet> {
69    /// Underlying data
70    pub(crate) data: PtrConst,
71
72    /// Shape of the value
73    pub(crate) shape: &'static Shape,
74
75    // Invariant with respect to 'facet: Peek<'mem, 'a> cannot be cast to Peek<'mem, 'b> even if 'a: 'b.
76    //
77    // This is REQUIRED for soundness! If Peek were covariant with respect to 'facet, we could:
78    // 1. Create Peek<'mem, 'static> from FnWrapper<'static> (contains fn(&'static str))
79    // 2. Use covariance to cast it to Peek<'mem, 'short>
80    // 3. Call get::<FnWrapper<'short>>() to get &FnWrapper<'short>
81    // 4. This would allow calling the function with a &'short str that goes out of scope
82    //    while the original function pointer still holds it as 'static
83    //
84    // The fn(&'a ()) -> &'a () pattern makes this type invariant with respect to 'facet.
85    // The &'mem () makes this type covariant with respect to 'mem (safe because we only read through it).
86    // See: https://github.com/facet-rs/facet/issues/1168
87    _invariant: PhantomData<(&'mem (), fn(&'facet ()) -> &'facet ())>,
88}
89
90impl<'mem, 'facet> Peek<'mem, 'facet> {
91    /// Returns a read-only view over a `T` value.
92    pub fn new<T: Facet<'facet> + ?Sized>(t: &'mem T) -> Self {
93        Self {
94            data: PtrConst::new(NonNull::from(t).as_ptr()),
95            shape: T::SHAPE,
96            _invariant: PhantomData,
97        }
98    }
99
100    /// Returns a read-only view over a value (given its shape), trusting you
101    /// that those two match.
102    ///
103    /// # Safety
104    ///
105    /// This function is unsafe because it doesn't check if the provided data
106    /// and shape are compatible. The caller must ensure that the data is valid
107    /// for the given shape.
108    pub unsafe fn unchecked_new(data: PtrConst, shape: &'static Shape) -> Self {
109        Self {
110            data,
111            shape,
112            _invariant: PhantomData,
113        }
114    }
115
116    // =============================================================================
117    // Variance-aware lifetime transformation methods
118    // =============================================================================
119
120    /// Returns the computed variance of the underlying type.
121    ///
122    /// This walks the type's fields to determine if the type is covariant,
123    /// contravariant, or invariant with respect to its lifetime parameter.
124    #[inline]
125    pub fn variance(&self) -> Variance {
126        self.shape.computed_variance()
127    }
128
129    /// Shrinks the `'facet` lifetime parameter.
130    ///
131    /// This is safe for covariant and bivariant types: if data is valid for `'static`,
132    /// it's also valid for any shorter lifetime `'shorter`.
133    ///
134    /// From the [Rust Reference](https://doc.rust-lang.org/reference/subtyping.html):
135    /// - Covariant types can shrink lifetimes (`'static` → `'a`)
136    /// - Bivariant types can go either direction (no lifetime constraints)
137    ///
138    /// # Panics
139    ///
140    /// Panics if the type cannot shrink lifetimes (i.e., if it's contravariant or invariant).
141    #[inline]
142    pub fn shrink_lifetime<'shorter>(self) -> Peek<'mem, 'shorter>
143    where
144        'facet: 'shorter,
145    {
146        self.try_shrink_lifetime()
147            .expect("shrink_lifetime requires a covariant type")
148    }
149
150    /// Tries to shrink the `'facet` lifetime parameter.
151    ///
152    /// Returns `Some` if the type can shrink lifetimes (covariant or bivariant),
153    /// or `None` if the type is invariant or contravariant.
154    ///
155    /// See [`Variance::can_shrink`] for details.
156    #[inline]
157    pub fn try_shrink_lifetime<'shorter>(self) -> Option<Peek<'mem, 'shorter>>
158    where
159        'facet: 'shorter,
160    {
161        if self.variance().can_shrink() {
162            Some(Peek {
163                data: self.data,
164                shape: self.shape,
165                _invariant: PhantomData,
166            })
167        } else {
168            None
169        }
170    }
171
172    /// Grows the `'facet` lifetime parameter.
173    ///
174    /// This is safe for contravariant and bivariant types: if a function accepts `'short`,
175    /// it can also accept `'longer` (a longer lifetime is more restrictive).
176    ///
177    /// From the [Rust Reference](https://doc.rust-lang.org/reference/subtyping.html):
178    /// - Contravariant types can grow lifetimes (`'a` → `'static`)
179    /// - Bivariant types can go either direction (no lifetime constraints)
180    ///
181    /// # Panics
182    ///
183    /// Panics if the type cannot grow lifetimes (i.e., if it's covariant or invariant).
184    #[inline]
185    pub fn grow_lifetime<'longer>(self) -> Peek<'mem, 'longer>
186    where
187        'longer: 'facet,
188    {
189        self.try_grow_lifetime()
190            .expect("grow_lifetime requires a contravariant type")
191    }
192
193    /// Tries to grow the `'facet` lifetime parameter.
194    ///
195    /// Returns `Some` if the type can grow lifetimes (contravariant or bivariant),
196    /// or `None` if the type is invariant or covariant.
197    ///
198    /// See [`Variance::can_grow`] for details.
199    #[inline]
200    pub fn try_grow_lifetime<'longer>(self) -> Option<Peek<'mem, 'longer>>
201    where
202        'longer: 'facet,
203    {
204        if self.variance().can_grow() {
205            Some(Peek {
206                data: self.data,
207                shape: self.shape,
208                _invariant: PhantomData,
209            })
210        } else {
211            None
212        }
213    }
214
215    /// Returns the vtable
216    #[inline(always)]
217    pub const fn vtable(&self) -> VTableErased {
218        self.shape.vtable
219    }
220
221    /// Returns a unique identifier for this value, usable for cycle detection
222    #[inline]
223    pub fn id(&self) -> ValueId {
224        ValueId::new(self.shape, self.data.raw_ptr())
225    }
226
227    /// Returns true if the two values are pointer-equal
228    #[inline]
229    pub fn ptr_eq(&self, other: &Peek<'_, '_>) -> bool {
230        self.data.raw_ptr() == other.data.raw_ptr()
231    }
232
233    /// Returns true if this scalar is equal to the other scalar
234    ///
235    /// # Returns
236    ///
237    /// `false` if equality comparison is not supported for this scalar type
238    #[inline]
239    pub fn partial_eq(&self, other: &Peek<'_, '_>) -> Result<bool, ReflectError> {
240        if self.shape != other.shape {
241            return Err(ReflectError::WrongShape {
242                expected: self.shape,
243                actual: other.shape,
244            });
245        }
246
247        if let Some(result) = unsafe { self.shape.call_partial_eq(self.data, other.data) } {
248            return Ok(result);
249        }
250
251        Err(ReflectError::OperationFailed {
252            shape: self.shape(),
253            operation: "partial_eq",
254        })
255    }
256
257    /// Compares this scalar with another and returns their ordering
258    ///
259    /// # Returns
260    ///
261    /// `None` if comparison is not supported for this scalar type
262    #[inline]
263    pub fn partial_cmp(&self, other: &Peek<'_, '_>) -> Result<Option<Ordering>, ReflectError> {
264        if self.shape != other.shape {
265            return Err(ReflectError::WrongShape {
266                expected: self.shape,
267                actual: other.shape,
268            });
269        }
270
271        if let Some(result) = unsafe { self.shape.call_partial_cmp(self.data, other.data) } {
272            return Ok(result);
273        }
274
275        Err(ReflectError::OperationFailed {
276            shape: self.shape(),
277            operation: "partial_cmp",
278        })
279    }
280
281    /// Hashes this scalar using the vtable hash function.
282    ///
283    /// # Returns
284    ///
285    /// `Err` if hashing is not supported for this scalar type, `Ok` otherwise
286    #[inline(always)]
287    pub fn hash(&self, hasher: &mut dyn core::hash::Hasher) -> Result<(), ReflectError> {
288        let mut proxy = facet_core::HashProxy::new(hasher);
289        if unsafe { self.shape.call_hash(self.data, &mut proxy) }.is_some() {
290            return Ok(());
291        }
292
293        Err(ReflectError::OperationFailed {
294            shape: self.shape(),
295            operation: "hash",
296        })
297    }
298
299    /// Computes a structural hash of this value.
300    ///
301    /// Unlike [`hash`](Self::hash), this method recursively traverses the structure
302    /// and hashes each component, making it work for types that don't implement `Hash`.
303    ///
304    /// For scalars with a vtable hash function, it uses that. For compound types
305    /// (structs, enums, lists, etc.), it recursively hashes the structure.
306    ///
307    /// This is useful for Merkle-tree style hashing where you want to compare
308    /// subtrees for equality based on their structural content.
309    pub fn structural_hash<H: core::hash::Hasher>(&self, hasher: &mut H) {
310        use core::hash::Hash;
311
312        // First, hash the shape's type identifier for type discrimination
313        self.shape.id.hash(hasher);
314
315        // Try vtable hash first for scalars
316        let mut proxy = facet_core::HashProxy::new(hasher);
317        if unsafe { self.shape.call_hash(self.data, &mut proxy) }.is_some() {
318            return;
319        }
320
321        // Otherwise, traverse the structure recursively
322        match self.shape.ty {
323            Type::User(UserType::Struct(struct_type)) => {
324                // Hash struct kind
325                (struct_type.kind as u8).hash(hasher);
326
327                // Hash each field, skipping metadata fields
328                for field in struct_type.fields {
329                    // Skip metadata fields - they don't affect structural identity
330                    if field.is_metadata() {
331                        continue;
332                    }
333
334                    // Hash field name
335                    field.name.hash(hasher);
336
337                    // Get field value and hash it recursively
338                    let field_offset = field.offset;
339                    let field_shape = field.shape();
340                    let field_ptr = unsafe { self.data.field(field_offset) };
341                    let field_peek = unsafe { Peek::unchecked_new(field_ptr, field_shape) };
342                    field_peek.structural_hash(hasher);
343                }
344            }
345
346            Type::User(UserType::Enum(_enum_type)) => {
347                // Get the discriminant and variant
348                if let Ok(peek_enum) = self.into_enum()
349                    && let Ok(variant) = peek_enum.active_variant()
350                {
351                    // Hash variant name
352                    variant.name.hash(hasher);
353
354                    // Hash variant payload based on kind
355                    match variant.data.kind {
356                        StructKind::Unit => {
357                            // No payload to hash
358                        }
359                        StructKind::TupleStruct | StructKind::Tuple => {
360                            // Hash tuple fields (no names)
361                            use super::HasFields;
362                            for (_field, peek) in peek_enum.fields() {
363                                peek.structural_hash(hasher);
364                            }
365                        }
366                        StructKind::Struct => {
367                            // Hash named fields
368                            use super::HasFields;
369                            for (field, peek) in peek_enum.fields() {
370                                field.name.hash(hasher);
371                                peek.structural_hash(hasher);
372                            }
373                        }
374                    }
375                }
376            }
377
378            _ => {
379                // Handle Def-based types
380                match self.shape.def {
381                    Def::List(_) | Def::Array(_) | Def::Slice(_) => {
382                        if let Ok(list_like) = self.into_list_like() {
383                            // Hash length
384                            list_like.len().hash(hasher);
385
386                            // Hash each element
387                            for elem in list_like.iter() {
388                                elem.structural_hash(hasher);
389                            }
390                        }
391                    }
392
393                    Def::Map(_) => {
394                        if let Ok(map) = self.into_map() {
395                            // Hash length
396                            map.len().hash(hasher);
397
398                            // Hash each key-value pair
399                            for (key, value) in map.iter() {
400                                key.structural_hash(hasher);
401                                value.structural_hash(hasher);
402                            }
403                        }
404                    }
405
406                    Def::Set(_) => {
407                        if let Ok(set) = self.into_set() {
408                            // Hash length
409                            set.len().hash(hasher);
410
411                            // Hash each element
412                            for elem in set.iter() {
413                                elem.structural_hash(hasher);
414                            }
415                        }
416                    }
417
418                    Def::Option(_) => {
419                        if let Ok(opt) = self.into_option() {
420                            if let Some(inner) = opt.value() {
421                                true.hash(hasher);
422                                inner.structural_hash(hasher);
423                            } else {
424                                false.hash(hasher);
425                            }
426                        }
427                    }
428
429                    Def::Result(_) => {
430                        if let Ok(result) = self.into_result() {
431                            if result.is_ok() {
432                                0u8.hash(hasher);
433                                if let Some(ok_val) = result.ok() {
434                                    ok_val.structural_hash(hasher);
435                                }
436                            } else {
437                                1u8.hash(hasher);
438                                if let Some(err_val) = result.err() {
439                                    err_val.structural_hash(hasher);
440                                }
441                            }
442                        }
443                    }
444
445                    Def::Pointer(_) => {
446                        if let Ok(ptr) = self.into_pointer()
447                            && let Some(inner) = ptr.borrow_inner()
448                        {
449                            inner.structural_hash(hasher);
450                        }
451                    }
452
453                    Def::DynamicValue(_) => {
454                        if let Ok(dyn_val) = self.into_dynamic_value() {
455                            // Hash based on dynamic value kind
456                            dyn_val.structural_hash_inner(hasher);
457                        }
458                    }
459
460                    Def::NdArray(_) => {
461                        // For ndarray, hash the dimensions and data
462                        if let Ok(arr) = self.into_ndarray() {
463                            let n_dim = arr.n_dim();
464                            n_dim.hash(hasher);
465                            for i in 0..n_dim {
466                                if let Some(dim) = arr.dim(i) {
467                                    dim.hash(hasher);
468                                }
469                            }
470                            // Hash each element
471                            let count = arr.count();
472                            for i in 0..count {
473                                if let Some(elem) = arr.get(i) {
474                                    elem.structural_hash(hasher);
475                                }
476                            }
477                        }
478                    }
479
480                    Def::Scalar | Def::Undefined | _ => {
481                        // Try to handle f32/f64 by hashing their bit representation
482                        match self.scalar_type() {
483                            Some(ScalarType::F32) => {
484                                if let Ok(v) = self.get::<f32>() {
485                                    v.to_bits().hash(hasher);
486                                    return;
487                                }
488                            }
489                            Some(ScalarType::F64) => {
490                                if let Ok(v) = self.get::<f64>() {
491                                    v.to_bits().hash(hasher);
492                                    return;
493                                }
494                            }
495                            _ => {}
496                        }
497                        panic!(
498                            "structural_hash: type {} has no Hash impl and cannot be structurally hashed",
499                            self.shape
500                        );
501                    }
502                }
503            }
504        }
505    }
506
507    /// Returns the type name of this scalar
508    ///
509    /// # Arguments
510    ///
511    /// * `f` - A mutable reference to a `core::fmt::Formatter`
512    /// * `opts` - The `TypeNameOpts` to use for formatting
513    ///
514    /// # Returns
515    ///
516    /// The result of the type name formatting
517    #[inline(always)]
518    pub fn type_name(
519        &self,
520        f: &mut core::fmt::Formatter<'_>,
521        opts: TypeNameOpts,
522    ) -> core::fmt::Result {
523        if let Some(type_name_fn) = self.shape.type_name {
524            type_name_fn(self.shape, f, opts)
525        } else {
526            write!(f, "{}", self.shape.type_identifier)
527        }
528    }
529
530    /// Returns the shape
531    #[inline(always)]
532    pub const fn shape(&self) -> &'static Shape {
533        self.shape
534    }
535
536    /// Returns the data
537    #[inline(always)]
538    pub const fn data(&self) -> PtrConst {
539        self.data
540    }
541
542    /// Get the scalar type if set.
543    #[inline]
544    pub fn scalar_type(&self) -> Option<ScalarType> {
545        ScalarType::try_from_shape(self.shape)
546    }
547
548    /// Read the value from memory into a Rust value.
549    ///
550    /// # Panics
551    ///
552    /// Panics if the shape doesn't match the type `T`.
553    #[inline]
554    pub fn get<T: Facet<'facet> + ?Sized>(&self) -> Result<&'mem T, ReflectError> {
555        if self.shape != T::SHAPE {
556            Err(ReflectError::WrongShape {
557                expected: self.shape,
558                actual: T::SHAPE,
559            })
560        } else {
561            Ok(unsafe { self.data.get::<T>() })
562        }
563    }
564
565    /// Try to get the value as a string if it's a string type
566    /// Returns None if the value is not a string or couldn't be extracted
567    pub fn as_str(&self) -> Option<&'mem str> {
568        let peek = self.innermost_peek();
569        // ScalarType::Str matches both bare `str` and `&str`.
570        // For bare `str` (not a pointer), data points to str bytes directly.
571        // For `&str`, let it fall through to the pointer handler below.
572        if let Some(ScalarType::Str) = peek.scalar_type()
573            && !matches!(peek.shape.ty, Type::Pointer(_))
574        {
575            // Bare `str`: data is a wide pointer to str bytes.
576            // get::<str>() creates a &str reference to that data.
577            return unsafe { Some(peek.data.get::<str>()) };
578        }
579        #[cfg(feature = "alloc")]
580        if let Some(ScalarType::String) = peek.scalar_type() {
581            return unsafe { Some(peek.data.get::<alloc::string::String>().as_str()) };
582        }
583        #[cfg(feature = "alloc")]
584        if let Some(ScalarType::CowStr) = peek.scalar_type() {
585            return unsafe { Some(peek.data.get::<alloc::borrow::Cow<'mem, str>>().as_ref()) };
586        }
587
588        // Handle references, including nested references like &&str
589        if let Type::Pointer(PointerType::Reference(vpt)) = peek.shape.ty {
590            let target_shape = vpt.target;
591
592            // Check if this is a nested reference (&&str) first
593            if let Type::Pointer(PointerType::Reference(inner_vpt)) = target_shape.ty {
594                let inner_target_shape = inner_vpt.target;
595                if let Some(ScalarType::Str) = ScalarType::try_from_shape(inner_target_shape) {
596                    // For &&str, we need to dereference twice.
597                    // Read the outer reference (8 bytes) as a pointer to &str, then dereference
598                    let outer_ptr: *const *const &str =
599                        unsafe { peek.data.as_ptr::<*const &str>() };
600                    let inner_ref: &str = unsafe { **outer_ptr };
601                    return Some(inner_ref);
602                }
603            } else if let Some(ScalarType::Str) = ScalarType::try_from_shape(target_shape)
604                && !matches!(target_shape.ty, Type::Pointer(_))
605            {
606                // Simple case: &str (but only if target is not a pointer itself)
607                return unsafe { Some(peek.data.get::<&str>()) };
608            }
609        }
610
611        // Handle smart pointer types like Box<str>, Arc<str>, Rc<str>
612        // These have Def::Pointer with pointee = str::SHAPE and a borrow_fn
613        #[cfg(feature = "alloc")]
614        if let Def::Pointer(ptr_def) = peek.shape.def
615            && let Some(pointee_shape) = ptr_def.pointee
616            && let Some(ScalarType::Str) = ScalarType::try_from_shape(pointee_shape)
617            && let Some(borrow_fn) = ptr_def.vtable.borrow_fn
618        {
619            // borrow_fn returns a PtrConst pointing to the inner str
620            let inner_ptr = unsafe { borrow_fn(peek.data) };
621            // The inner ptr is a wide pointer to str
622            return unsafe { Some(inner_ptr.get::<str>()) };
623        }
624
625        None
626    }
627
628    /// Try to get the value as a byte slice if it's a &[u8] type
629    /// Returns None if the value is not a byte slice or couldn't be extracted
630    #[inline]
631    pub fn as_bytes(&self) -> Option<&'mem [u8]> {
632        // Check if it's a direct &[u8]
633        if let Type::Pointer(PointerType::Reference(vpt)) = self.shape.ty {
634            let target_shape = vpt.target;
635            if let Def::Slice(sd) = target_shape.def
636                && sd.t().is_type::<u8>()
637            {
638                unsafe { return Some(self.data.get::<&[u8]>()) }
639            }
640        }
641        None
642    }
643
644    /// Tries to identify this value as a struct
645    #[inline]
646    pub const fn into_struct(self) -> Result<PeekStruct<'mem, 'facet>, ReflectError> {
647        if let Type::User(UserType::Struct(ty)) = self.shape.ty {
648            Ok(PeekStruct { value: self, ty })
649        } else {
650            Err(ReflectError::WasNotA {
651                expected: "struct",
652                actual: self.shape,
653            })
654        }
655    }
656
657    /// Tries to identify this value as an enum
658    #[inline]
659    pub const fn into_enum(self) -> Result<PeekEnum<'mem, 'facet>, ReflectError> {
660        if let Type::User(UserType::Enum(ty)) = self.shape.ty {
661            Ok(PeekEnum { value: self, ty })
662        } else {
663            Err(ReflectError::WasNotA {
664                expected: "enum",
665                actual: self.shape,
666            })
667        }
668    }
669
670    /// Tries to identify this value as a map
671    #[inline]
672    pub const fn into_map(self) -> Result<PeekMap<'mem, 'facet>, ReflectError> {
673        if let Def::Map(def) = self.shape.def {
674            // SAFETY: The MapDef comes from self.shape.def, where self.shape is obtained
675            // from a trusted source (either T::SHAPE from the Facet trait, or validated
676            // through other safe constructors). The vtable is therefore trusted.
677            Ok(unsafe { PeekMap::new(self, def) })
678        } else {
679            Err(ReflectError::WasNotA {
680                expected: "map",
681                actual: self.shape,
682            })
683        }
684    }
685
686    /// Tries to identify this value as a set
687    #[inline]
688    pub const fn into_set(self) -> Result<PeekSet<'mem, 'facet>, ReflectError> {
689        if let Def::Set(def) = self.shape.def {
690            // SAFETY: The SetDef comes from self.shape.def, where self.shape is obtained
691            // from a trusted source (either T::SHAPE from the Facet trait, or validated
692            // through other safe constructors). The vtable is therefore trusted.
693            Ok(unsafe { PeekSet::new(self, def) })
694        } else {
695            Err(ReflectError::WasNotA {
696                expected: "set",
697                actual: self.shape,
698            })
699        }
700    }
701
702    /// Tries to identify this value as a list
703    #[inline]
704    pub const fn into_list(self) -> Result<PeekList<'mem, 'facet>, ReflectError> {
705        if let Def::List(def) = self.shape.def {
706            // SAFETY: The ListDef comes from self.shape.def, where self.shape is obtained
707            // from a trusted source (either T::SHAPE from the Facet trait, or validated
708            // through other safe constructors). The vtable is therefore trusted.
709            return Ok(unsafe { PeekList::new(self, def) });
710        }
711
712        Err(ReflectError::WasNotA {
713            expected: "list",
714            actual: self.shape,
715        })
716    }
717
718    /// Tries to identify this value as a ndarray
719    #[inline]
720    pub const fn into_ndarray(self) -> Result<PeekNdArray<'mem, 'facet>, ReflectError> {
721        if let Def::NdArray(def) = self.shape.def {
722            // SAFETY: The NdArrayDef comes from self.shape.def, where self.shape is obtained
723            // from a trusted source (either T::SHAPE from the Facet trait, or validated
724            // through other safe constructors). The vtable is therefore trusted.
725            return Ok(unsafe { PeekNdArray::new(self, def) });
726        }
727
728        Err(ReflectError::WasNotA {
729            expected: "ndarray",
730            actual: self.shape,
731        })
732    }
733
734    /// Tries to identify this value as a list, array or slice
735    #[inline]
736    pub fn into_list_like(self) -> Result<PeekListLike<'mem, 'facet>, ReflectError> {
737        match self.shape.def {
738            Def::List(def) => {
739                // SAFETY: The ListDef comes from self.shape.def, where self.shape is obtained
740                // from a trusted source (either T::SHAPE from the Facet trait, or validated
741                // through other safe constructors). The vtable is therefore trusted.
742                Ok(unsafe { PeekListLike::new(self, ListLikeDef::List(def)) })
743            }
744            Def::Array(def) => {
745                // SAFETY: The ArrayDef comes from self.shape.def, where self.shape is obtained
746                // from a trusted source (either T::SHAPE from the Facet trait, or validated
747                // through other safe constructors). The vtable is therefore trusted.
748                Ok(unsafe { PeekListLike::new(self, ListLikeDef::Array(def)) })
749            }
750            Def::Slice(def) => {
751                // When we have a bare slice shape with a wide pointer,
752                // it means we have a reference to a slice (e.g., from Arc<[T]>::borrow_inner)
753                // SAFETY: The SliceDef comes from self.shape.def, where self.shape is obtained
754                // from a trusted source (either T::SHAPE from the Facet trait, or validated
755                // through other safe constructors). The vtable is therefore trusted.
756                Ok(unsafe { PeekListLike::new(self, ListLikeDef::Slice(def)) })
757            }
758            _ => {
759                // &[i32] is actually a _pointer_ to a slice.
760                match self.shape.ty {
761                    Type::Pointer(ptr) => match ptr {
762                        PointerType::Reference(vpt) | PointerType::Raw(vpt) => {
763                            let target = vpt.target;
764                            match target.def {
765                                Def::Slice(def) => {
766                                    let ptr = unsafe { self.data.as_ptr::<*const [()]>() };
767                                    let ptr = PtrConst::new(unsafe {
768                                        NonNull::new_unchecked((*ptr) as *mut [()]).as_ptr()
769                                    });
770                                    let peek = unsafe { Peek::unchecked_new(ptr, def.t) };
771
772                                    // SAFETY: The SliceDef comes from target.def, where target is obtained
773                                    // from self.shape which comes from a trusted source. The vtable is therefore trusted.
774                                    return Ok(unsafe {
775                                        PeekListLike::new(peek, ListLikeDef::Slice(def))
776                                    });
777                                }
778                                _ => {
779                                    // well it's not list-like then
780                                }
781                            }
782                        }
783                        PointerType::Function(_) => {
784                            // well that's not a list-like
785                        }
786                    },
787                    _ => {
788                        // well that's not a list-like either
789                    }
790                }
791
792                Err(ReflectError::WasNotA {
793                    expected: "list, array or slice",
794                    actual: self.shape,
795                })
796            }
797        }
798    }
799
800    /// Tries to identify this value as a pointer
801    #[inline]
802    pub const fn into_pointer(self) -> Result<PeekPointer<'mem, 'facet>, ReflectError> {
803        if let Def::Pointer(def) = self.shape.def {
804            Ok(PeekPointer { value: self, def })
805        } else {
806            Err(ReflectError::WasNotA {
807                expected: "smart pointer",
808                actual: self.shape,
809            })
810        }
811    }
812
813    /// Tries to identify this value as an option
814    #[inline]
815    pub const fn into_option(self) -> Result<PeekOption<'mem, 'facet>, ReflectError> {
816        if let Def::Option(def) = self.shape.def {
817            Ok(PeekOption { value: self, def })
818        } else {
819            Err(ReflectError::WasNotA {
820                expected: "option",
821                actual: self.shape,
822            })
823        }
824    }
825
826    /// Tries to identify this value as a result
827    #[inline]
828    pub const fn into_result(self) -> Result<PeekResult<'mem, 'facet>, ReflectError> {
829        if let Def::Result(def) = self.shape.def {
830            Ok(PeekResult { value: self, def })
831        } else {
832            Err(ReflectError::WasNotA {
833                expected: "result",
834                actual: self.shape,
835            })
836        }
837    }
838
839    /// Tries to identify this value as a tuple
840    #[inline]
841    pub fn into_tuple(self) -> Result<PeekTuple<'mem, 'facet>, ReflectError> {
842        if let Type::User(UserType::Struct(struct_type)) = self.shape.ty {
843            if struct_type.kind == StructKind::Tuple {
844                Ok(PeekTuple {
845                    value: self,
846                    ty: TupleType {
847                        fields: struct_type.fields,
848                    },
849                })
850            } else {
851                Err(ReflectError::WasNotA {
852                    expected: "tuple",
853                    actual: self.shape,
854                })
855            }
856        } else {
857            Err(ReflectError::WasNotA {
858                expected: "tuple",
859                actual: self.shape,
860            })
861        }
862    }
863
864    /// Tries to identify this value as a dynamic value (like `facet_value::Value`)
865    #[inline]
866    pub const fn into_dynamic_value(self) -> Result<PeekDynamicValue<'mem, 'facet>, ReflectError> {
867        if let Def::DynamicValue(def) = self.shape.def {
868            Ok(PeekDynamicValue { value: self, def })
869        } else {
870            Err(ReflectError::WasNotA {
871                expected: "dynamic value",
872                actual: self.shape,
873            })
874        }
875    }
876
877    /// Tries to return the innermost value — useful for serialization. For example, we serialize a `NonZero<u8>` the same
878    /// as a `u8`. Similarly, we serialize a `Utf8PathBuf` the same as a `String.
879    ///
880    /// Returns a `Peek` to the innermost value, unwrapping transparent wrappers recursively.
881    /// For example, this will peel through newtype wrappers or smart pointers that have an `inner`.
882    pub fn innermost_peek(self) -> Self {
883        let mut current_peek = self;
884        loop {
885            // First, try to dereference if this is a pointer type (Box, Arc, etc.)
886            if let Ok(ptr) = current_peek.into_pointer()
887                && let Some(target) = ptr.borrow_inner()
888            {
889                current_peek = target;
890                continue;
891            }
892
893            // Then, try to unwrap transparent wrappers via shape.inner
894            if let Some(inner_shape) = current_peek.shape.inner {
895                let result = unsafe { current_peek.shape.call_try_borrow_inner(current_peek.data) };
896                match result {
897                    Some(Ok(inner_data)) => {
898                        current_peek = Peek {
899                            data: inner_data.as_const(),
900                            shape: inner_shape,
901                            _invariant: PhantomData,
902                        };
903                        continue;
904                    }
905                    Some(Err(e)) => {
906                        panic!(
907                            "innermost_peek: try_borrow_inner returned an error! was trying to go from {} to {}. error: {e}",
908                            current_peek.shape, inner_shape
909                        );
910                    }
911                    None => {
912                        // No try_borrow_inner function - this might be a pointer type
913                        // that we already tried above, so we're done
914                    }
915                }
916            }
917
918            // No more unwrapping possible
919            break;
920        }
921        current_peek
922    }
923
924    /// Performs custom serialization of the current peek using the provided field's metadata.
925    ///
926    /// Returns an `OwnedPeek` that points to the final type that should be serialized in place
927    /// of the current peek.
928    #[cfg(feature = "alloc")]
929    pub fn custom_serialization(&self, field: Field) -> Result<OwnedPeek<'mem>, ReflectError> {
930        let Some(proxy_def) = field.proxy() else {
931            return Err(ReflectError::OperationFailed {
932                shape: self.shape,
933                operation: "field does not have a proxy definition",
934            });
935        };
936
937        let target_shape = proxy_def.shape;
938        let tptr = target_shape.allocate().map_err(|_| ReflectError::Unsized {
939            shape: target_shape,
940            operation: "Not a Sized type",
941        })?;
942        let ser_res = unsafe { (proxy_def.convert_out)(self.data(), tptr) };
943        let err = match ser_res {
944            Ok(rptr) => {
945                if rptr.as_uninit() != tptr {
946                    ReflectError::CustomSerializationError {
947                        message: "convert_out did not return the expected pointer".into(),
948                        src_shape: self.shape,
949                        dst_shape: target_shape,
950                    }
951                } else {
952                    return Ok(OwnedPeek {
953                        shape: target_shape,
954                        data: rptr,
955                        _phantom: PhantomData,
956                    });
957                }
958            }
959            Err(message) => ReflectError::CustomSerializationError {
960                message,
961                src_shape: self.shape,
962                dst_shape: target_shape,
963            },
964        };
965        // if we reach here we have an error and we need to deallocate the target allocation
966        unsafe {
967            // SAFETY: unwrap should be ok since the allocation was ok
968            target_shape.deallocate_uninit(tptr).unwrap()
969        };
970        Err(err)
971    }
972
973    /// Performs custom serialization using a specific proxy definition.
974    ///
975    /// This is a lower-level method that takes a `ProxyDef` directly, useful when
976    /// the caller has already resolved which proxy to use (e.g., via `effective_proxy()`).
977    #[cfg(feature = "alloc")]
978    pub fn custom_serialization_with_proxy(
979        &self,
980        proxy_def: &'static facet_core::ProxyDef,
981    ) -> Result<OwnedPeek<'mem>, ReflectError> {
982        let target_shape = proxy_def.shape;
983        let tptr = target_shape.allocate().map_err(|_| ReflectError::Unsized {
984            shape: target_shape,
985            operation: "Not a Sized type",
986        })?;
987        let ser_res = unsafe { (proxy_def.convert_out)(self.data(), tptr) };
988        let err = match ser_res {
989            Ok(rptr) => {
990                if rptr.as_uninit() != tptr {
991                    ReflectError::CustomSerializationError {
992                        message: "convert_out did not return the expected pointer".into(),
993                        src_shape: self.shape,
994                        dst_shape: target_shape,
995                    }
996                } else {
997                    return Ok(OwnedPeek {
998                        shape: target_shape,
999                        data: rptr,
1000                        _phantom: PhantomData,
1001                    });
1002                }
1003            }
1004            Err(message) => ReflectError::CustomSerializationError {
1005                message,
1006                src_shape: self.shape,
1007                dst_shape: target_shape,
1008            },
1009        };
1010        // if we reach here we have an error and we need to deallocate the target allocation
1011        unsafe {
1012            // SAFETY: unwrap should be ok since the allocation was ok
1013            target_shape.deallocate_uninit(tptr).unwrap()
1014        };
1015        Err(err)
1016    }
1017
1018    /// Returns an `OwnedPeek` using the shape's container-level proxy for serialization.
1019    ///
1020    /// This is used when a type has `#[facet(proxy = ProxyType)]` at the container level.
1021    /// Unlike field-level proxies which are checked via `custom_serialization(field)`,
1022    /// this method checks the Shape itself for a proxy definition.
1023    ///
1024    /// Returns `None` if the shape has no container-level proxy.
1025    #[cfg(feature = "alloc")]
1026    pub fn custom_serialization_from_shape(&self) -> Result<Option<OwnedPeek<'mem>>, ReflectError> {
1027        self.custom_serialization_from_shape_with_format(None)
1028    }
1029
1030    /// Returns an `OwnedPeek` using the shape's container-level proxy for serialization,
1031    /// with support for format-specific proxies.
1032    ///
1033    /// If `format_namespace` is provided (e.g., `Some("xml")`), looks for a format-specific
1034    /// proxy first, falling back to the format-agnostic proxy.
1035    ///
1036    /// Returns `None` if no applicable proxy is found.
1037    #[cfg(feature = "alloc")]
1038    pub fn custom_serialization_from_shape_with_format(
1039        &self,
1040        format_namespace: Option<&str>,
1041    ) -> Result<Option<OwnedPeek<'mem>>, ReflectError> {
1042        let Some(proxy_def) = self.shape.effective_proxy(format_namespace) else {
1043            return Ok(None);
1044        };
1045
1046        let target_shape = proxy_def.shape;
1047        let tptr = target_shape.allocate().map_err(|_| ReflectError::Unsized {
1048            shape: target_shape,
1049            operation: "Not a Sized type",
1050        })?;
1051
1052        let ser_res = unsafe { (proxy_def.convert_out)(self.data(), tptr) };
1053        let err = match ser_res {
1054            Ok(rptr) => {
1055                if rptr.as_uninit() != tptr {
1056                    ReflectError::CustomSerializationError {
1057                        message: "proxy convert_out did not return the expected pointer".into(),
1058                        src_shape: self.shape,
1059                        dst_shape: target_shape,
1060                    }
1061                } else {
1062                    return Ok(Some(OwnedPeek {
1063                        shape: target_shape,
1064                        data: rptr,
1065                        _phantom: PhantomData,
1066                    }));
1067                }
1068            }
1069            Err(message) => ReflectError::CustomSerializationError {
1070                message,
1071                src_shape: self.shape,
1072                dst_shape: target_shape,
1073            },
1074        };
1075
1076        // if we reach here we have an error and we need to deallocate the target allocation
1077        unsafe {
1078            // SAFETY: unwrap should be ok since the allocation was ok
1079            target_shape.deallocate_uninit(tptr).unwrap()
1080        };
1081        Err(err)
1082    }
1083}
1084
1085impl<'mem, 'facet> core::fmt::Display for Peek<'mem, 'facet> {
1086    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1087        if let Some(result) = unsafe { self.shape.call_display(self.data, f) } {
1088            return result;
1089        }
1090        write!(f, "⟨{}⟩", self.shape)
1091    }
1092}
1093
1094impl<'mem, 'facet> core::fmt::Debug for Peek<'mem, 'facet> {
1095    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1096        if let Some(result) = unsafe { self.shape.call_debug(self.data, f) } {
1097            return result;
1098        }
1099
1100        write!(f, "⟨{}⟩", self.shape)
1101    }
1102}
1103
1104impl<'mem, 'facet> core::cmp::PartialEq for Peek<'mem, 'facet> {
1105    #[inline]
1106    fn eq(&self, other: &Self) -> bool {
1107        self.partial_eq(other).unwrap_or(false)
1108    }
1109}
1110
1111impl<'mem, 'facet> core::cmp::PartialOrd for Peek<'mem, 'facet> {
1112    #[inline]
1113    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1114        self.partial_cmp(other).unwrap_or(None)
1115    }
1116}
1117
1118impl<'mem, 'facet> core::hash::Hash for Peek<'mem, 'facet> {
1119    fn hash<H: core::hash::Hasher>(&self, hasher: &mut H) {
1120        self.hash(hasher)
1121            .expect("Hashing is not supported for this shape");
1122    }
1123}
1124
1125/// A covariant wrapper around [`Peek`] for types that can safely shrink lifetimes.
1126///
1127/// Unlike [`Peek`], which is invariant with respect to `'facet` for soundness reasons,
1128/// `CovariantPeek` is **covariant** with respect to `'facet`. This means a `CovariantPeek<'mem, 'static>`
1129/// can be used where a `CovariantPeek<'mem, 'a>` is expected.
1130///
1131/// # Variance Background
1132///
1133/// From the [Rust Reference on Subtyping](https://doc.rust-lang.org/reference/subtyping.html):
1134/// - **Covariant** types can shrink lifetimes (`'static` → `'a`)
1135/// - **Bivariant** types have no lifetime constraints and can go either direction
1136/// - **Contravariant** types can only grow lifetimes
1137/// - **Invariant** types cannot change lifetimes at all
1138///
1139/// `CovariantPeek` accepts both covariant and bivariant types, since both can
1140/// safely shrink lifetimes.
1141///
1142/// # When to Use
1143///
1144/// Use `CovariantPeek` when you need to:
1145/// - Store multiple `Peek` values with different lifetimes in a single collection
1146/// - Pass `Peek` values to functions expecting shorter lifetimes
1147/// - Build data structures that wrap `Peek` without forcing invariance on the wrapper
1148///
1149/// # Safety
1150///
1151/// `CovariantPeek` can only be constructed from types that can safely shrink lifetimes
1152/// (covariant or bivariant). The constructor verifies this at runtime by checking
1153/// [`Variance::can_shrink`]. This ensures that lifetime shrinking is always safe.
1154///
1155/// # Example
1156///
1157/// ```
1158/// use facet::Facet;
1159/// use facet_reflect::{Peek, CovariantPeek};
1160///
1161/// #[derive(Facet)]
1162/// struct Data<'a> {
1163///     value: &'a str,
1164/// }
1165///
1166/// // Data<'a> is covariant with respect to 'a because &'a str is covariant
1167/// let data = Data { value: "hello" };
1168/// let peek: Peek<'_, 'static> = Peek::new(&data);
1169///
1170/// // Convert to CovariantPeek - this verifies the type can shrink lifetimes
1171/// let covariant = CovariantPeek::new(peek).expect("Data can shrink lifetimes");
1172///
1173/// // Now we can use it where shorter lifetimes are expected
1174/// fn use_shorter<'a>(p: CovariantPeek<'_, 'a>) {
1175///     let _ = p;
1176/// }
1177/// use_shorter(covariant);
1178/// ```
1179#[derive(Clone, Copy)]
1180pub struct CovariantPeek<'mem, 'facet> {
1181    /// Underlying data
1182    data: PtrConst,
1183
1184    /// Shape of the value
1185    shape: &'static Shape,
1186
1187    // Covariant with respect to both 'mem and 'facet: CovariantPeek<'mem, 'static> can be used where
1188    // CovariantPeek<'mem, 'a> is expected.
1189    //
1190    // This is safe ONLY because we verify at construction time that the underlying
1191    // type can shrink lifetimes (is covariant or bivariant).
1192    // See: https://doc.rust-lang.org/reference/subtyping.html
1193    _covariant: PhantomData<(&'mem (), &'facet ())>,
1194}
1195
1196impl<'mem, 'facet> CovariantPeek<'mem, 'facet> {
1197    /// Creates a new `CovariantPeek` from a `Peek`, verifying that the underlying type
1198    /// can be used in covariant contexts.
1199    ///
1200    /// Returns `None` if the type cannot safely shrink lifetimes (i.e., it's contravariant
1201    /// or invariant). Both covariant and bivariant types are accepted.
1202    ///
1203    /// From the [Rust Reference](https://doc.rust-lang.org/reference/subtyping.html):
1204    /// - Covariant types can shrink lifetimes (`'static` → `'a`)
1205    /// - Bivariant types have no lifetime constraints and can go either direction
1206    /// - Both are safe to use in covariant contexts
1207    ///
1208    /// # Example
1209    ///
1210    /// ```
1211    /// use facet::Facet;
1212    /// use facet_reflect::{Peek, CovariantPeek};
1213    ///
1214    /// // i32 has no lifetime parameters, so it's bivariant (can be used as covariant)
1215    /// let value = 42i32;
1216    /// let peek = Peek::new(&value);
1217    /// let covariant = CovariantPeek::new(peek);
1218    /// assert!(covariant.is_some());
1219    /// ```
1220    #[inline]
1221    pub fn new(peek: Peek<'mem, 'facet>) -> Option<Self> {
1222        // Accept types that can shrink lifetimes: Covariant and Bivariant
1223        // See: https://doc.rust-lang.org/reference/subtyping.html
1224        if peek.variance().can_shrink() {
1225            Some(Self {
1226                data: peek.data,
1227                shape: peek.shape,
1228                _covariant: PhantomData,
1229            })
1230        } else {
1231            None
1232        }
1233    }
1234
1235    /// Creates a new `CovariantPeek` from a `Peek`, panicking if the type cannot be
1236    /// used in covariant contexts.
1237    ///
1238    /// # Panics
1239    ///
1240    /// Panics if the underlying type is contravariant or invariant.
1241    ///
1242    /// # Example
1243    ///
1244    /// ```
1245    /// use facet::Facet;
1246    /// use facet_reflect::{Peek, CovariantPeek};
1247    ///
1248    /// let value = "hello";
1249    /// let peek = Peek::new(&value);
1250    /// let covariant = CovariantPeek::new_unchecked(peek); // Will succeed
1251    /// ```
1252    #[inline]
1253    pub fn new_unchecked(peek: Peek<'mem, 'facet>) -> Self {
1254        Self::new(peek).unwrap_or_else(|| {
1255            panic!(
1256                "CovariantPeek::new_unchecked called on type that cannot shrink lifetimes: {} (variance: {:?})",
1257                peek.shape,
1258                peek.variance()
1259            )
1260        })
1261    }
1262
1263    /// Creates a `CovariantPeek` directly from a `Facet` type that can be used
1264    /// in covariant contexts.
1265    ///
1266    /// Returns `None` if the type is contravariant or invariant.
1267    ///
1268    /// # Example
1269    ///
1270    /// ```
1271    /// use facet::Facet;
1272    /// use facet_reflect::CovariantPeek;
1273    ///
1274    /// let value = 42i32;
1275    /// let covariant = CovariantPeek::from_ref(&value);
1276    /// assert!(covariant.is_some());
1277    /// ```
1278    #[inline]
1279    pub fn from_ref<T: Facet<'facet> + ?Sized>(t: &'mem T) -> Option<Self> {
1280        Self::new(Peek::new(t))
1281    }
1282
1283    /// Returns the underlying `Peek`.
1284    ///
1285    /// Note that the returned `Peek` is invariant, so you cannot use it to
1286    /// shrink lifetimes directly. Use `CovariantPeek` for lifetime flexibility.
1287    #[inline]
1288    pub fn into_peek(self) -> Peek<'mem, 'facet> {
1289        Peek {
1290            data: self.data,
1291            shape: self.shape,
1292            _invariant: PhantomData,
1293        }
1294    }
1295
1296    /// Returns the shape of the underlying value.
1297    #[inline]
1298    pub const fn shape(&self) -> &'static Shape {
1299        self.shape
1300    }
1301
1302    /// Returns the data pointer.
1303    #[inline]
1304    pub const fn data(&self) -> PtrConst {
1305        self.data
1306    }
1307}
1308
1309impl<'mem, 'facet> core::ops::Deref for CovariantPeek<'mem, 'facet> {
1310    type Target = Peek<'mem, 'facet>;
1311
1312    #[inline]
1313    fn deref(&self) -> &Self::Target {
1314        // SAFETY: CovariantPeek and Peek have the same memory layout for the
1315        // data and shape fields. The PhantomData fields don't affect layout.
1316        // We're creating a reference to a Peek that views the same data.
1317        //
1318        // This is safe because:
1319        // 1. We only construct CovariantPeek from covariant types
1320        // 2. The Peek reference we return has the same lifetime bounds
1321        // 3. We're not allowing mutation through this reference
1322        unsafe { &*(self as *const CovariantPeek<'mem, 'facet> as *const Peek<'mem, 'facet>) }
1323    }
1324}
1325
1326impl<'mem, 'facet> core::fmt::Debug for CovariantPeek<'mem, 'facet> {
1327    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1328        f.debug_struct("CovariantPeek")
1329            .field("shape", &self.shape)
1330            .field("data", &self.data)
1331            .finish()
1332    }
1333}
1334
1335impl<'mem, 'facet> core::fmt::Display for CovariantPeek<'mem, 'facet> {
1336    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1337        core::fmt::Display::fmt(&**self, f)
1338    }
1339}
1340
1341#[cfg(test)]
1342mod tests {
1343    use super::*;
1344
1345    /// Regression test for issue #1082: UB in `Peek("").as_str()`
1346    /// Previously, `as_str()` used `get::<&str>()` which tried to read a fat pointer
1347    /// from the str data, causing UB for empty strings (reading 16 bytes from 0-byte allocation).
1348    #[test]
1349    fn test_peek_as_str_empty_string() {
1350        let peek = Peek::new("");
1351        assert_eq!(peek.as_str(), Some(""));
1352    }
1353
1354    #[test]
1355    fn test_peek_as_str_non_empty_string() {
1356        let peek = Peek::new("hello");
1357        assert_eq!(peek.as_str(), Some("hello"));
1358    }
1359
1360    #[test]
1361    #[cfg(feature = "alloc")]
1362    fn test_peek_as_str_owned_string() {
1363        let s = alloc::string::String::from("owned string");
1364        let peek = Peek::new(&s);
1365        assert_eq!(peek.as_str(), Some("owned string"));
1366    }
1367
1368    /// Regression test for issue #794: Peek::as_str() with double reference
1369    /// Previously, this would cause UB when trying to read &&str as &str
1370    #[test]
1371    fn test_peek_as_str_double_reference() {
1372        let value = &"hello";
1373        let peek = Peek::new(&value);
1374        assert_eq!(peek.as_str(), Some("hello"));
1375    }
1376
1377    #[test]
1378    fn test_covariant_peek_from_covariant_type() {
1379        // i32 has no lifetime parameters, so it's covariant
1380        let value = 42i32;
1381        let peek = Peek::new(&value);
1382        let covariant = CovariantPeek::new(peek);
1383        assert!(covariant.is_some());
1384
1385        // Verify we can access Peek methods through Deref
1386        let covariant = covariant.unwrap();
1387        assert_eq!(covariant.shape(), peek.shape());
1388    }
1389
1390    #[test]
1391    fn test_covariant_peek_from_ref() {
1392        let value = 42i32;
1393        let covariant = CovariantPeek::from_ref(&value);
1394        assert!(covariant.is_some());
1395    }
1396
1397    #[test]
1398    fn test_covariant_peek_deref_to_peek() {
1399        let value = "hello";
1400        let peek = Peek::new(&value);
1401        let covariant = CovariantPeek::new(peek).unwrap();
1402
1403        // Test that Deref works - we can call Peek methods directly
1404        assert_eq!(covariant.as_str(), Some("hello"));
1405        assert_eq!(covariant.shape(), peek.shape());
1406    }
1407
1408    #[test]
1409    fn test_covariant_peek_into_peek() {
1410        let value = 42i32;
1411        let original_peek = Peek::new(&value);
1412        let covariant = CovariantPeek::new(original_peek).unwrap();
1413        let recovered_peek = covariant.into_peek();
1414
1415        assert_eq!(recovered_peek.shape(), original_peek.shape());
1416    }
1417
1418    #[test]
1419    fn test_covariant_peek_lifetime_covariance() {
1420        // This test verifies that CovariantPeek is actually covariant with respect to 'facet
1421        // by passing a CovariantPeek<'_, 'static> to a function expecting CovariantPeek<'_, 'a>
1422        fn use_shorter<'a>(_p: CovariantPeek<'_, 'a>) {}
1423
1424        let value = 42i32;
1425        let covariant: CovariantPeek<'_, 'static> = CovariantPeek::from_ref(&value).unwrap();
1426
1427        // This compiles because CovariantPeek is covariant with respect to 'facet
1428        use_shorter(covariant);
1429    }
1430
1431    #[test]
1432    #[cfg(feature = "alloc")]
1433    fn test_covariant_peek_vec_type() {
1434        // Vec<T> is covariant with respect to T
1435        let vec = alloc::vec![1i32, 2, 3];
1436        let peek = Peek::new(&vec);
1437        let covariant = CovariantPeek::new(peek);
1438        assert!(covariant.is_some());
1439    }
1440
1441    #[test]
1442    #[cfg(feature = "alloc")]
1443    fn test_covariant_peek_option_type() {
1444        // Option<T> is covariant with respect to T
1445        let opt = Some(42i32);
1446        let peek = Peek::new(&opt);
1447        let covariant = CovariantPeek::new(peek);
1448        assert!(covariant.is_some());
1449    }
1450
1451    #[test]
1452    fn test_spanned_structural_hash_ignores_span() {
1453        use crate::{Span, Spanned};
1454        use core::hash::Hasher;
1455        use std::hash::DefaultHasher;
1456
1457        // Two Spanned values with same inner value but different spans
1458        let a = Spanned::new(42i32, Span::new(0, 10));
1459        let b = Spanned::new(42i32, Span::new(100, 20));
1460
1461        // They should have the same structural hash
1462        let mut hasher_a = DefaultHasher::new();
1463        Peek::new(&a).structural_hash(&mut hasher_a);
1464        let hash_a = hasher_a.finish();
1465
1466        let mut hasher_b = DefaultHasher::new();
1467        Peek::new(&b).structural_hash(&mut hasher_b);
1468        let hash_b = hasher_b.finish();
1469
1470        assert_eq!(
1471            hash_a, hash_b,
1472            "Spanned values with same inner value should have same structural hash"
1473        );
1474    }
1475
1476    #[test]
1477    fn test_spanned_structural_hash_differs_for_different_values() {
1478        use crate::{Span, Spanned};
1479        use core::hash::Hasher;
1480        use std::hash::DefaultHasher;
1481
1482        // Two Spanned values with different inner values
1483        let a = Spanned::new(42i32, Span::new(0, 10));
1484        let b = Spanned::new(99i32, Span::new(0, 10));
1485
1486        // They should have different structural hashes
1487        let mut hasher_a = DefaultHasher::new();
1488        Peek::new(&a).structural_hash(&mut hasher_a);
1489        let hash_a = hasher_a.finish();
1490
1491        let mut hasher_b = DefaultHasher::new();
1492        Peek::new(&b).structural_hash(&mut hasher_b);
1493        let hash_b = hasher_b.finish();
1494
1495        assert_ne!(
1496            hash_a, hash_b,
1497            "Spanned values with different inner values should have different structural hashes"
1498        );
1499    }
1500
1501    #[test]
1502    fn test_spanned_field_metadata() {
1503        use crate::Spanned;
1504        use facet_core::{Type, UserType};
1505
1506        // Get the shape for Spanned<i32>
1507        let shape = <Spanned<i32> as facet_core::Facet>::SHAPE;
1508
1509        // Extract the struct type
1510        let struct_type = match shape.ty {
1511            Type::User(UserType::Struct(st)) => st,
1512            _ => panic!("Expected struct type"),
1513        };
1514
1515        // Find the span field and verify it has metadata = "span"
1516        let span_field = struct_type
1517            .fields
1518            .iter()
1519            .find(|f| f.name == "span")
1520            .expect("Should have span field");
1521
1522        assert!(
1523            span_field.is_metadata(),
1524            "span field should be marked as metadata"
1525        );
1526        assert_eq!(
1527            span_field.metadata_kind(),
1528            Some("span"),
1529            "span field should have metadata kind 'span'"
1530        );
1531
1532        // Verify the value field is NOT metadata
1533        let value_field = struct_type
1534            .fields
1535            .iter()
1536            .find(|f| f.name == "value")
1537            .expect("Should have value field");
1538
1539        assert!(
1540            !value_field.is_metadata(),
1541            "value field should not be marked as metadata"
1542        );
1543    }
1544}