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