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, 'shape> {
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, 'shape>,
15
16    /// The definition of the enum.
17    pub(crate) ty: EnumType<'shape>,
18}
19
20impl core::fmt::Debug for PeekEnum<'_, '_, '_> {
21    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
22        if let Some(debug_fn) = (self.vtable().debug)() {
23            unsafe { debug_fn(self.data, f) }
24        } else {
25            write!(f, "⟨{}⟩", self.shape)
26        }
27    }
28}
29
30/// Returns the enum definition if the shape represents an enum, None otherwise
31pub fn peek_enum<'shape>(shape: &'shape Shape) -> Option<EnumType<'shape>> {
32    match shape.ty {
33        facet_core::Type::User(UserType::Enum(enum_ty)) => Some(enum_ty),
34        _ => None,
35    }
36}
37
38/// Returns the enum representation if the shape represents an enum, None otherwise
39pub fn peek_enum_repr(shape: &Shape) -> Option<EnumRepr> {
40    peek_enum(shape).map(|enum_def| enum_def.enum_repr)
41}
42
43/// Returns the enum variants if the shape represents an enum, None otherwise
44pub fn peek_enum_variants<'shape>(shape: &'shape Shape) -> Option<&'shape [Variant<'shape>]> {
45    peek_enum(shape).map(|enum_def| enum_def.variants)
46}
47
48impl<'mem, 'facet, 'shape> core::ops::Deref for PeekEnum<'mem, 'facet, 'shape> {
49    type Target = Peek<'mem, 'facet, 'shape>;
50
51    #[inline(always)]
52    fn deref(&self) -> &Self::Target {
53        &self.value
54    }
55}
56
57impl<'mem, 'facet, 'shape> PeekEnum<'mem, 'facet, 'shape> {
58    /// Returns the enum definition
59    #[inline(always)]
60    pub fn ty(self) -> EnumType<'shape> {
61        self.ty
62    }
63
64    /// Returns the enum representation
65    #[inline(always)]
66    pub fn enum_repr(self) -> EnumRepr {
67        self.ty.enum_repr
68    }
69
70    /// Returns the enum variants
71    #[inline(always)]
72    pub fn variants(self) -> &'shape [Variant<'shape>] {
73        self.ty.variants
74    }
75
76    /// Returns the number of variants in this enum
77    #[inline(always)]
78    pub fn variant_count(self) -> usize {
79        self.ty.variants.len()
80    }
81
82    /// Returns the variant name at the given index
83    #[inline(always)]
84    pub fn variant_name(self, index: usize) -> Option<&'shape str> {
85        self.ty.variants.get(index).map(|variant| variant.name)
86    }
87
88    /// Returns the discriminant value for the current enum value
89    #[inline]
90    pub fn discriminant(self) -> i64 {
91        // Read the discriminant based on the enum representation
92        unsafe {
93            let data = self.value.data();
94            match self.ty.enum_repr {
95                EnumRepr::U8 => data.read::<u8>() as i64,
96                EnumRepr::U16 => data.read::<u16>() as i64,
97                EnumRepr::U32 => data.read::<u32>() as i64,
98                EnumRepr::U64 => data.read::<u64>() as i64,
99                EnumRepr::USize => data.read::<usize>() as i64,
100                EnumRepr::I8 => data.read::<i8>() as i64,
101                EnumRepr::I16 => data.read::<i16>() as i64,
102                EnumRepr::I32 => data.read::<i32>() as i64,
103                EnumRepr::I64 => data.read::<i64>(),
104                EnumRepr::ISize => data.read::<isize>() as i64,
105                _ => {
106                    // Default to a reasonable size for other representations that might be added in the future
107                    data.read::<u32>() as i64
108                }
109            }
110        }
111    }
112
113    /// Returns the variant index for this enum value
114    #[inline]
115    pub fn variant_index(self) -> Result<usize, VariantError> {
116        if self.ty.enum_repr == EnumRepr::RustNPO {
117            // Check if enum is all zeros
118            let layout = self
119                .value
120                .shape
121                .layout
122                .sized_layout()
123                .expect("Unsized enums in NPO repr are unsupported");
124
125            let data = self.value.data();
126            let slice = unsafe { core::slice::from_raw_parts(data.as_byte_ptr(), layout.size()) };
127            let all_zero = slice.iter().all(|v| *v == 0);
128
129            trace!(
130                "PeekEnum::variant_index (RustNPO): layout size = {}, all_zero = {} (slice is actually {:?})",
131                layout.size(),
132                all_zero,
133                slice
134            );
135
136            Ok(self
137                .ty
138                .variants
139                .iter()
140                .enumerate()
141                .position(|#[allow(unused)] (variant_idx, variant)| {
142                    // Find the maximum end bound
143                    let mut max_offset = 0;
144
145                    for field in variant.data.fields {
146                        let offset = field.offset
147                            + field
148                                .shape
149                                .layout
150                                .sized_layout()
151                                .map(|v| v.size())
152                                .unwrap_or(0);
153                        max_offset = core::cmp::max(max_offset, offset);
154                    }
155
156                    trace!(
157                        "  variant[{}] = '{}', max_offset = {}",
158                        variant_idx, variant.name, max_offset
159                    );
160
161                    // If we are all zero, then find the enum variant that has no size,
162                    // otherwise, the one with size.
163                    if all_zero {
164                        max_offset == 0
165                    } else {
166                        max_offset != 0
167                    }
168                })
169                .expect("No variant found with matching discriminant"))
170        } else {
171            let discriminant = self.discriminant();
172
173            trace!(
174                "PeekEnum::variant_index: discriminant = {} (repr = {:?})",
175                discriminant, self.ty.enum_repr
176            );
177
178            // Find the variant with matching discriminant using position method
179            Ok(self
180                .ty
181                .variants
182                .iter()
183                .enumerate()
184                .position(|#[allow(unused)] (variant_idx, variant)| {
185                    variant.discriminant == Some(discriminant)
186                })
187                .expect("No variant found with matching discriminant"))
188        }
189    }
190
191    /// Returns the active variant
192    #[inline]
193    pub fn active_variant(self) -> Result<&'shape Variant<'shape>, VariantError> {
194        let index = self.variant_index()?;
195        Ok(&self.ty.variants[index])
196    }
197
198    /// Returns the name of the active variant for this enum value
199    #[inline]
200    pub fn variant_name_active(self) -> Result<&'shape str, VariantError> {
201        Ok(self.active_variant()?.name)
202    }
203
204    // variant_data has been removed to reduce unsafe code exposure
205
206    /// Returns a PeekValue handle to a field of a tuple or struct variant by index
207    pub fn field(self, index: usize) -> Result<Option<Peek<'mem, 'facet, 'shape>>, VariantError> {
208        let variant = self.active_variant()?;
209        let fields = &variant.data.fields;
210
211        if index >= fields.len() {
212            return Ok(None);
213        }
214
215        let field = &fields[index];
216        let field_data = unsafe { self.value.data().field(field.offset) };
217        Ok(Some(unsafe {
218            Peek::unchecked_new(field_data, field.shape())
219        }))
220    }
221
222    /// Returns the index of a field in the active variant by name
223    pub fn field_index(self, field_name: &str) -> Result<Option<usize>, VariantError> {
224        let variant = self.active_variant()?;
225        Ok(variant
226            .data
227            .fields
228            .iter()
229            .position(|f| f.name == field_name))
230    }
231
232    /// Returns a PeekValue handle to a field of a tuple or struct variant by name
233    pub fn field_by_name(
234        self,
235        field_name: &str,
236    ) -> Result<Option<Peek<'mem, 'facet, 'shape>>, VariantError> {
237        let index_opt = self.field_index(field_name)?;
238        match index_opt {
239            Some(index) => self.field(index),
240            None => Ok(None),
241        }
242    }
243}
244
245impl<'mem, 'facet, 'shape> HasFields<'mem, 'facet, 'shape> for PeekEnum<'mem, 'facet, 'shape> {
246    fn fields(&self) -> FieldIter<'mem, 'facet, 'shape> {
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
258impl core::fmt::Display for VariantError {
259    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
260        write!(f, "enum layout is opaque, cannot determine variant")
261    }
262}
263
264impl core::fmt::Debug for VariantError {
265    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
266        write!(
267            f,
268            "VariantError::OpaqueInternals: enum layout is opaque, cannot determine variant"
269        )
270    }
271}
272
273impl core::error::Error for VariantError {}