facet_reflect/peek/
enum_.rs

1use facet_core::{Def, EnumRepr, EnumType, Shape, UserType, Variant};
2
3use crate::{Peek, trace};
4
5use super::{FieldIter, HasFields};
6
7/// Lets you read from an enum (implements read-only enum operations)
8#[derive(Clone, Copy)]
9pub struct PeekEnum<'mem, 'facet> {
10    /// The internal data storage for the enum
11    ///
12    /// Note that this stores both the discriminant and the variant data
13    /// (if any), and the layout depends on the enum representation.
14    pub(crate) value: Peek<'mem, 'facet>,
15
16    /// The definition of the enum.
17    pub(crate) ty: EnumType,
18}
19
20impl core::fmt::Debug for PeekEnum<'_, '_> {
21    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
22        write!(f, "{:?}", self.value)
23    }
24}
25
26/// Returns the enum definition if the shape represents an enum, None otherwise
27#[inline]
28pub fn peek_enum(shape: &'static Shape) -> Option<EnumType> {
29    match shape.ty {
30        facet_core::Type::User(UserType::Enum(enum_ty)) => Some(enum_ty),
31        _ => None,
32    }
33}
34
35/// Returns the enum representation if the shape represents an enum, None otherwise
36#[inline]
37pub fn peek_enum_repr(shape: &'static Shape) -> Option<EnumRepr> {
38    peek_enum(shape).map(|enum_def| enum_def.enum_repr)
39}
40
41/// Returns the enum variants if the shape represents an enum, None otherwise
42#[inline]
43pub fn peek_enum_variants(shape: &'static Shape) -> Option<&'static [Variant]> {
44    peek_enum(shape).map(|enum_def| enum_def.variants)
45}
46
47impl<'mem, 'facet> core::ops::Deref for PeekEnum<'mem, 'facet> {
48    type Target = Peek<'mem, 'facet>;
49
50    #[inline(always)]
51    fn deref(&self) -> &Self::Target {
52        &self.value
53    }
54}
55
56impl<'mem, 'facet> PeekEnum<'mem, 'facet> {
57    /// Returns the enum definition
58    #[inline(always)]
59    pub fn ty(self) -> EnumType {
60        self.ty
61    }
62
63    /// Returns the enum representation
64    #[inline(always)]
65    pub fn enum_repr(self) -> EnumRepr {
66        self.ty.enum_repr
67    }
68
69    /// Returns the enum variants
70    #[inline(always)]
71    pub fn variants(self) -> &'static [Variant] {
72        self.ty.variants
73    }
74
75    /// Returns the number of variants in this enum
76    #[inline(always)]
77    pub fn variant_count(self) -> usize {
78        self.ty.variants.len()
79    }
80
81    /// Returns the variant name at the given index
82    #[inline(always)]
83    pub fn variant_name(self, index: usize) -> Option<&'static str> {
84        self.ty.variants.get(index).map(|variant| variant.name)
85    }
86
87    /// Returns the discriminant value for the current enum value
88    ///
89    /// Note: For `RustNPO` (null pointer optimization) types, there is no explicit
90    /// discriminant stored in memory. In this case, 0 is returned. Use
91    /// [`variant_index()`](Self::variant_index) to determine the active variant for NPO types.
92    #[inline]
93    pub fn discriminant(self) -> i64 {
94        // Read the discriminant based on the enum representation
95        match self.ty.enum_repr {
96            // For RustNPO types, there is no explicit discriminant stored in memory.
97            // The variant is determined by niche optimization (e.g., null pointer pattern).
98            // Return 0 since that's the declared discriminant for NPO variants.
99            // This also prevents UB when reading from zero-sized types.
100            EnumRepr::RustNPO => 0,
101            EnumRepr::U8 => unsafe { self.value.data().read::<u8>() as i64 },
102            EnumRepr::U16 => unsafe { self.value.data().read::<u16>() as i64 },
103            EnumRepr::U32 => unsafe { self.value.data().read::<u32>() as i64 },
104            EnumRepr::U64 => unsafe { self.value.data().read::<u64>() as i64 },
105            EnumRepr::USize => unsafe { self.value.data().read::<usize>() as i64 },
106            EnumRepr::I8 => unsafe { self.value.data().read::<i8>() as i64 },
107            EnumRepr::I16 => unsafe { self.value.data().read::<i16>() as i64 },
108            EnumRepr::I32 => unsafe { self.value.data().read::<i32>() as i64 },
109            EnumRepr::I64 => unsafe { self.value.data().read::<i64>() },
110            EnumRepr::ISize => unsafe { self.value.data().read::<isize>() as i64 },
111        }
112    }
113
114    /// Returns the variant index for this enum value
115    #[inline]
116    pub fn variant_index(self) -> Result<usize, VariantError> {
117        if self.ty.enum_repr == EnumRepr::RustNPO {
118            // For Option<T> types with niche optimization, use the OptionVTable
119            // to correctly determine if the value is Some or None.
120            // This is necessary because Rust's niche optimization may use non-zero
121            // values to represent None (when the inner type has unused discriminants).
122            if let Def::Option(option_def) = self.value.shape.def {
123                let is_some = unsafe { (option_def.vtable.is_some)(self.value.data()) };
124                trace!("PeekEnum::variant_index (RustNPO Option): is_some = {is_some}");
125                // Find the variant by checking which has fields (Some) vs no fields (None)
126                return Ok(self
127                    .ty
128                    .variants
129                    .iter()
130                    .position(|variant| {
131                        let has_fields = !variant.data.fields.is_empty();
132                        has_fields == is_some
133                    })
134                    .expect("No variant found matching Option state"));
135            }
136
137            // Fallback for other RustNPO types (e.g., Option<&T> where all-zeros means None)
138            let layout = self
139                .value
140                .shape
141                .layout
142                .sized_layout()
143                .expect("Unsized enums in NPO repr are unsupported");
144
145            let data = self.value.data();
146            let slice = unsafe { core::slice::from_raw_parts(data.as_byte_ptr(), layout.size()) };
147            let all_zero = slice.iter().all(|v| *v == 0);
148
149            trace!(
150                "PeekEnum::variant_index (RustNPO): layout size = {}, all_zero = {} (slice is actually {:?})",
151                layout.size(),
152                all_zero,
153                slice
154            );
155
156            Ok(self
157                .ty
158                .variants
159                .iter()
160                .enumerate()
161                .position(|#[allow(unused)] (variant_idx, variant)| {
162                    // Find the maximum end bound
163                    let mut max_offset = 0;
164
165                    for field in variant.data.fields {
166                        let offset = field.offset
167                            + field
168                                .shape()
169                                .layout
170                                .sized_layout()
171                                .map(|v| v.size())
172                                .unwrap_or(0);
173                        max_offset = core::cmp::max(max_offset, offset);
174                    }
175
176                    trace!(
177                        "  variant[{}] = '{}', max_offset = {}",
178                        variant_idx, variant.name, max_offset
179                    );
180
181                    // If we are all zero, then find the enum variant that has no size,
182                    // otherwise, the one with size.
183                    if all_zero {
184                        max_offset == 0
185                    } else {
186                        max_offset != 0
187                    }
188                })
189                .expect("No variant found with matching discriminant"))
190        } else {
191            let discriminant = self.discriminant();
192
193            trace!(
194                "PeekEnum::variant_index: discriminant = {} (repr = {:?})",
195                discriminant, self.ty.enum_repr
196            );
197
198            // Find the variant with matching discriminant using position method
199            Ok(self
200                .ty
201                .variants
202                .iter()
203                .enumerate()
204                .position(|#[allow(unused)] (variant_idx, variant)| {
205                    variant.discriminant == Some(discriminant)
206                })
207                .expect("No variant found with matching discriminant"))
208        }
209    }
210
211    /// Returns the active variant
212    #[inline]
213    pub fn active_variant(self) -> Result<&'static Variant, VariantError> {
214        let index = self.variant_index()?;
215        Ok(&self.ty.variants[index])
216    }
217
218    /// Returns the name of the active variant for this enum value
219    #[inline]
220    pub fn variant_name_active(self) -> Result<&'static str, VariantError> {
221        Ok(self.active_variant()?.name)
222    }
223
224    // variant_data has been removed to reduce unsafe code exposure
225
226    /// Returns a PeekValue handle to a field of a tuple or struct variant by index
227    pub fn field(self, index: usize) -> Result<Option<Peek<'mem, 'facet>>, VariantError> {
228        let variant = self.active_variant()?;
229        let fields = &variant.data.fields;
230
231        if index >= fields.len() {
232            return Ok(None);
233        }
234
235        let field = &fields[index];
236        let field_data = unsafe { self.value.data().field(field.offset) };
237        Ok(Some(unsafe {
238            Peek::unchecked_new(field_data, field.shape())
239        }))
240    }
241
242    /// Returns the index of a field in the active variant by name
243    pub fn field_index(self, field_name: &str) -> Result<Option<usize>, VariantError> {
244        let variant = self.active_variant()?;
245        Ok(variant
246            .data
247            .fields
248            .iter()
249            .position(|f| f.name == field_name))
250    }
251
252    /// Returns a PeekValue handle to a field of a tuple or struct variant by name
253    pub fn field_by_name(
254        self,
255        field_name: &str,
256    ) -> Result<Option<Peek<'mem, 'facet>>, VariantError> {
257        let index_opt = self.field_index(field_name)?;
258        match index_opt {
259            Some(index) => self.field(index),
260            None => Ok(None),
261        }
262    }
263}
264
265impl<'mem, 'facet> HasFields<'mem, 'facet> for PeekEnum<'mem, 'facet> {
266    #[inline]
267    fn fields(&self) -> FieldIter<'mem, 'facet> {
268        FieldIter::new_enum(*self)
269    }
270}
271
272/// Error that can occur when trying to determine variant information
273#[derive(Clone, Copy, PartialEq, Eq)]
274pub enum VariantError {
275    /// Error indicating that enum internals are opaque and cannot be determined
276    OpaqueInternals,
277
278    /// Error indicating the enum value is unsized and cannot be accessed by field offset.
279    Unsized,
280}
281
282impl core::fmt::Display for VariantError {
283    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
284        match self {
285            VariantError::OpaqueInternals => {
286                write!(f, "enum layout is opaque, cannot determine variant")
287            }
288            VariantError::Unsized => {
289                write!(
290                    f,
291                    "enum value is unsized and cannot be accessed by field offset"
292                )
293            }
294        }
295    }
296}
297
298impl core::fmt::Debug for VariantError {
299    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
300        match self {
301            VariantError::OpaqueInternals => {
302                write!(
303                    f,
304                    "VariantError::OpaqueInternals: enum layout is opaque, cannot determine variant"
305                )
306            }
307            VariantError::Unsized => {
308                write!(
309                    f,
310                    "VariantError::Unsized: enum value is unsized and cannot be accessed by field offset"
311                )
312            }
313        }
314    }
315}
316
317impl core::error::Error for VariantError {}