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}