Skip to main content

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 const 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 const fn ty(self) -> EnumType {
60        self.ty
61    }
62
63    /// Returns the enum representation
64    #[inline(always)]
65    pub const fn enum_repr(self) -> EnumRepr {
66        self.ty.enum_repr
67    }
68
69    /// Returns the enum variants
70    #[inline(always)]
71    pub const 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 const 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 Rust enums with unspecified layout, we cannot read the discriminant.
97            // Panic since the caller should check the repr before calling this.
98            EnumRepr::Rust => {
99                panic!("cannot read discriminant from Rust enum with unspecified layout")
100            }
101            // For RustNPO types, there is no explicit discriminant stored in memory.
102            // The variant is determined by niche optimization (e.g., null pointer pattern).
103            // Return 0 since that's the declared discriminant for NPO variants.
104            // This also prevents UB when reading from zero-sized types.
105            EnumRepr::RustNPO => 0,
106            EnumRepr::U8 => unsafe { self.value.data().read::<u8>() as i64 },
107            EnumRepr::U16 => unsafe { self.value.data().read::<u16>() as i64 },
108            EnumRepr::U32 => unsafe { self.value.data().read::<u32>() as i64 },
109            EnumRepr::U64 => unsafe { self.value.data().read::<u64>() as i64 },
110            EnumRepr::USize => unsafe { self.value.data().read::<usize>() as i64 },
111            EnumRepr::I8 => unsafe { self.value.data().read::<i8>() as i64 },
112            EnumRepr::I16 => unsafe { self.value.data().read::<i16>() as i64 },
113            EnumRepr::I32 => unsafe { self.value.data().read::<i32>() as i64 },
114            EnumRepr::I64 => unsafe { self.value.data().read::<i64>() },
115            EnumRepr::ISize => unsafe { self.value.data().read::<isize>() as i64 },
116        }
117    }
118
119    /// Returns the variant index for this enum value
120    #[inline]
121    pub fn variant_index(self) -> Result<usize, VariantError> {
122        // For Option<T> types, use the OptionVTable to correctly determine if the value is Some or None.
123        // This handles both RustNPO (niche-optimized) and Rust (non-niche) representations.
124        if let Def::Option(option_def) = self.value.shape.def {
125            let is_some = unsafe { (option_def.vtable.is_some)(self.value.data()) };
126            trace!("PeekEnum::variant_index (Option): is_some = {is_some}");
127            // Find the variant by checking which has fields (Some) vs no fields (None)
128            return Ok(self
129                .ty
130                .variants
131                .iter()
132                .position(|variant| {
133                    let has_fields = !variant.data.fields.is_empty();
134                    has_fields == is_some
135                })
136                .expect("No variant found matching Option state"));
137        }
138
139        if self.ty.enum_repr == EnumRepr::RustNPO {
140            // Fallback for other RustNPO types (e.g., Option<&T> where all-zeros means None)
141            let layout = self
142                .value
143                .shape
144                .layout
145                .sized_layout()
146                .expect("Unsized enums in NPO repr are unsupported");
147
148            let data = self.value.data();
149            let slice = unsafe { core::slice::from_raw_parts(data.as_byte_ptr(), layout.size()) };
150            let all_zero = slice.iter().all(|v| *v == 0);
151
152            trace!(
153                "PeekEnum::variant_index (RustNPO): layout size = {}, all_zero = {} (slice is actually {:?})",
154                layout.size(),
155                all_zero,
156                slice
157            );
158
159            Ok(self
160                .ty
161                .variants
162                .iter()
163                .enumerate()
164                .position(|#[allow(unused)] (variant_idx, variant)| {
165                    // Find the maximum end bound
166                    let mut max_offset = 0;
167
168                    for field in variant.data.fields {
169                        let offset = field.offset
170                            + field
171                                .shape()
172                                .layout
173                                .sized_layout()
174                                .map(|v| v.size())
175                                .unwrap_or(0);
176                        max_offset = core::cmp::max(max_offset, offset);
177                    }
178
179                    trace!(
180                        "  variant[{}] = '{}', max_offset = {}",
181                        variant_idx, variant.name, max_offset
182                    );
183
184                    // If we are all zero, then find the enum variant that has no size,
185                    // otherwise, the one with size.
186                    if all_zero {
187                        max_offset == 0
188                    } else {
189                        max_offset != 0
190                    }
191                })
192                .expect("No variant found with matching discriminant"))
193        } else {
194            let discriminant = self.discriminant();
195
196            trace!(
197                "PeekEnum::variant_index: discriminant = {} (repr = {:?})",
198                discriminant, self.ty.enum_repr
199            );
200
201            // Find the variant with matching discriminant using position method
202            Ok(self
203                .ty
204                .variants
205                .iter()
206                .enumerate()
207                .position(|#[allow(unused)] (variant_idx, variant)| {
208                    variant.discriminant == Some(discriminant)
209                })
210                .expect("No variant found with matching discriminant"))
211        }
212    }
213
214    /// Returns the active variant
215    #[inline]
216    pub fn active_variant(self) -> Result<&'static Variant, VariantError> {
217        let index = self.variant_index()?;
218        Ok(&self.ty.variants[index])
219    }
220
221    /// Returns the name of the active variant for this enum value
222    #[inline]
223    pub fn variant_name_active(self) -> Result<&'static str, VariantError> {
224        Ok(self.active_variant()?.name)
225    }
226
227    // variant_data has been removed to reduce unsafe code exposure
228
229    /// Returns a PeekValue handle to a field of a tuple or struct variant by index
230    pub fn field(self, index: usize) -> Result<Option<Peek<'mem, 'facet>>, VariantError> {
231        let variant = self.active_variant()?;
232        let fields = &variant.data.fields;
233
234        if index >= fields.len() {
235            return Ok(None);
236        }
237
238        let field = &fields[index];
239        let field_data = unsafe { self.value.data().field(field.offset) };
240        Ok(Some(unsafe {
241            Peek::unchecked_new(field_data, field.shape())
242        }))
243    }
244
245    /// Returns the index of a field in the active variant by name
246    pub fn field_index(self, field_name: &str) -> Result<Option<usize>, VariantError> {
247        let variant = self.active_variant()?;
248        Ok(variant
249            .data
250            .fields
251            .iter()
252            .position(|f| f.name == field_name))
253    }
254
255    /// Returns a PeekValue handle to a field of a tuple or struct variant by name
256    pub fn field_by_name(
257        self,
258        field_name: &str,
259    ) -> Result<Option<Peek<'mem, 'facet>>, VariantError> {
260        let index_opt = self.field_index(field_name)?;
261        match index_opt {
262            Some(index) => self.field(index),
263            None => Ok(None),
264        }
265    }
266}
267
268impl<'mem, 'facet> HasFields<'mem, 'facet> for PeekEnum<'mem, 'facet> {
269    #[inline]
270    fn fields(&self) -> FieldIter<'mem, 'facet> {
271        FieldIter::new_enum(*self)
272    }
273}
274
275/// Error that can occur when trying to determine variant information
276#[derive(Clone, Copy, PartialEq, Eq)]
277pub enum VariantError {
278    /// Error indicating that enum internals are opaque and cannot be determined
279    OpaqueInternals,
280
281    /// Error indicating the enum value is unsized and cannot be accessed by field offset.
282    Unsized,
283}
284
285impl core::fmt::Display for VariantError {
286    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
287        match self {
288            VariantError::OpaqueInternals => {
289                write!(f, "enum layout is opaque, cannot determine variant")
290            }
291            VariantError::Unsized => {
292                write!(
293                    f,
294                    "enum value is unsized and cannot be accessed by field offset"
295                )
296            }
297        }
298    }
299}
300
301impl core::fmt::Debug for VariantError {
302    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
303        match self {
304            VariantError::OpaqueInternals => {
305                write!(
306                    f,
307                    "VariantError::OpaqueInternals: enum layout is opaque, cannot determine variant"
308                )
309            }
310            VariantError::Unsized => {
311                write!(
312                    f,
313                    "VariantError::Unsized: enum value is unsized and cannot be accessed by field offset"
314                )
315            }
316        }
317    }
318}
319
320impl core::error::Error for VariantError {}