facet_reflect/peek/
enum_.rs

1use facet_core::{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    #[inline]
89    pub fn discriminant(self) -> i64 {
90        // Read the discriminant based on the enum representation
91        unsafe {
92            let data = self
93                .value
94                .data()
95                .thin()
96                .expect("discriminant must be Sized");
97            match self.ty.enum_repr {
98                EnumRepr::U8 => data.read::<u8>() as i64,
99                EnumRepr::U16 => data.read::<u16>() as i64,
100                EnumRepr::U32 => data.read::<u32>() as i64,
101                EnumRepr::U64 => data.read::<u64>() as i64,
102                EnumRepr::USize => data.read::<usize>() as i64,
103                EnumRepr::I8 => data.read::<i8>() as i64,
104                EnumRepr::I16 => data.read::<i16>() as i64,
105                EnumRepr::I32 => data.read::<i32>() as i64,
106                EnumRepr::I64 => data.read::<i64>(),
107                EnumRepr::ISize => data.read::<isize>() as i64,
108                _ => {
109                    // Default to a reasonable size for other representations that might be added in the future
110                    data.read::<u32>() as i64
111                }
112            }
113        }
114    }
115
116    /// Returns the variant index for this enum value
117    #[inline]
118    pub fn variant_index(self) -> Result<usize, VariantError> {
119        if self.ty.enum_repr == EnumRepr::RustNPO {
120            // Check if enum is all zeros
121            let layout = self
122                .value
123                .shape
124                .layout
125                .sized_layout()
126                .expect("Unsized enums in NPO repr are unsupported");
127
128            let data = self.value.data().thin().unwrap();
129            let slice = unsafe { core::slice::from_raw_parts(data.as_byte_ptr(), layout.size()) };
130            let all_zero = slice.iter().all(|v| *v == 0);
131
132            trace!(
133                "PeekEnum::variant_index (RustNPO): layout size = {}, all_zero = {} (slice is actually {:?})",
134                layout.size(),
135                all_zero,
136                slice
137            );
138
139            Ok(self
140                .ty
141                .variants
142                .iter()
143                .enumerate()
144                .position(|#[allow(unused)] (variant_idx, variant)| {
145                    // Find the maximum end bound
146                    let mut max_offset = 0;
147
148                    for field in variant.data.fields {
149                        let offset = field.offset
150                            + field
151                                .shape
152                                .layout
153                                .sized_layout()
154                                .map(|v| v.size())
155                                .unwrap_or(0);
156                        max_offset = core::cmp::max(max_offset, offset);
157                    }
158
159                    trace!(
160                        "  variant[{}] = '{}', max_offset = {}",
161                        variant_idx, variant.name, max_offset
162                    );
163
164                    // If we are all zero, then find the enum variant that has no size,
165                    // otherwise, the one with size.
166                    if all_zero {
167                        max_offset == 0
168                    } else {
169                        max_offset != 0
170                    }
171                })
172                .expect("No variant found with matching discriminant"))
173        } else {
174            let discriminant = self.discriminant();
175
176            trace!(
177                "PeekEnum::variant_index: discriminant = {} (repr = {:?})",
178                discriminant, self.ty.enum_repr
179            );
180
181            // Find the variant with matching discriminant using position method
182            Ok(self
183                .ty
184                .variants
185                .iter()
186                .enumerate()
187                .position(|#[allow(unused)] (variant_idx, variant)| {
188                    variant.discriminant == Some(discriminant)
189                })
190                .expect("No variant found with matching discriminant"))
191        }
192    }
193
194    /// Returns the active variant
195    #[inline]
196    pub fn active_variant(self) -> Result<&'static Variant, VariantError> {
197        let index = self.variant_index()?;
198        Ok(&self.ty.variants[index])
199    }
200
201    /// Returns the name of the active variant for this enum value
202    #[inline]
203    pub fn variant_name_active(self) -> Result<&'static str, VariantError> {
204        Ok(self.active_variant()?.name)
205    }
206
207    // variant_data has been removed to reduce unsafe code exposure
208
209    /// Returns a PeekValue handle to a field of a tuple or struct variant by index
210    pub fn field(self, index: usize) -> Result<Option<Peek<'mem, 'facet>>, VariantError> {
211        let variant = self.active_variant()?;
212        let fields = &variant.data.fields;
213
214        if index >= fields.len() {
215            return Ok(None);
216        }
217
218        let field = &fields[index];
219        let field_data = unsafe {
220            self.value
221                .data()
222                .thin()
223                .ok_or(VariantError::Unsized)?
224                .field(field.offset)
225        };
226        Ok(Some(unsafe {
227            Peek::unchecked_new(field_data, field.shape())
228        }))
229    }
230
231    /// Returns the index of a field in the active variant by name
232    pub fn field_index(self, field_name: &str) -> Result<Option<usize>, VariantError> {
233        let variant = self.active_variant()?;
234        Ok(variant
235            .data
236            .fields
237            .iter()
238            .position(|f| f.name == field_name))
239    }
240
241    /// Returns a PeekValue handle to a field of a tuple or struct variant by name
242    pub fn field_by_name(
243        self,
244        field_name: &str,
245    ) -> Result<Option<Peek<'mem, 'facet>>, VariantError> {
246        let index_opt = self.field_index(field_name)?;
247        match index_opt {
248            Some(index) => self.field(index),
249            None => Ok(None),
250        }
251    }
252}
253
254impl<'mem, 'facet> HasFields<'mem, 'facet> for PeekEnum<'mem, 'facet> {
255    #[inline]
256    fn fields(&self) -> FieldIter<'mem, 'facet> {
257        FieldIter::new_enum(*self)
258    }
259}
260
261/// Error that can occur when trying to determine variant information
262#[derive(Clone, Copy, PartialEq, Eq)]
263pub enum VariantError {
264    /// Error indicating that enum internals are opaque and cannot be determined
265    OpaqueInternals,
266
267    /// Error indicating the enum value is unsized and cannot be accessed by field offset.
268    Unsized,
269}
270
271impl core::fmt::Display for VariantError {
272    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
273        match self {
274            VariantError::OpaqueInternals => {
275                write!(f, "enum layout is opaque, cannot determine variant")
276            }
277            VariantError::Unsized => {
278                write!(
279                    f,
280                    "enum value is unsized and cannot be accessed by field offset"
281                )
282            }
283        }
284    }
285}
286
287impl core::fmt::Debug for VariantError {
288    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
289        match self {
290            VariantError::OpaqueInternals => {
291                write!(
292                    f,
293                    "VariantError::OpaqueInternals: enum layout is opaque, cannot determine variant"
294                )
295            }
296            VariantError::Unsized => {
297                write!(
298                    f,
299                    "VariantError::Unsized: enum value is unsized and cannot be accessed by field offset"
300                )
301            }
302        }
303    }
304}
305
306impl core::error::Error for VariantError {}