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        write!(f, "{:?}", self.value)
23    }
24}
25
26/// Returns the enum definition if the shape represents an enum, None otherwise
27pub fn peek_enum<'shape>(shape: &'shape Shape) -> Option<EnumType<'shape>> {
28    match shape.ty {
29        facet_core::Type::User(UserType::Enum(enum_ty)) => Some(enum_ty),
30        _ => None,
31    }
32}
33
34/// Returns the enum representation if the shape represents an enum, None otherwise
35pub fn peek_enum_repr(shape: &Shape) -> Option<EnumRepr> {
36    peek_enum(shape).map(|enum_def| enum_def.enum_repr)
37}
38
39/// Returns the enum variants if the shape represents an enum, None otherwise
40pub fn peek_enum_variants<'shape>(shape: &'shape Shape) -> Option<&'shape [Variant<'shape>]> {
41    peek_enum(shape).map(|enum_def| enum_def.variants)
42}
43
44impl<'mem, 'facet, 'shape> core::ops::Deref for PeekEnum<'mem, 'facet, 'shape> {
45    type Target = Peek<'mem, 'facet, 'shape>;
46
47    #[inline(always)]
48    fn deref(&self) -> &Self::Target {
49        &self.value
50    }
51}
52
53impl<'mem, 'facet, 'shape> PeekEnum<'mem, 'facet, 'shape> {
54    /// Returns the enum definition
55    #[inline(always)]
56    pub fn ty(self) -> EnumType<'shape> {
57        self.ty
58    }
59
60    /// Returns the enum representation
61    #[inline(always)]
62    pub fn enum_repr(self) -> EnumRepr {
63        self.ty.enum_repr
64    }
65
66    /// Returns the enum variants
67    #[inline(always)]
68    pub fn variants(self) -> &'shape [Variant<'shape>] {
69        self.ty.variants
70    }
71
72    /// Returns the number of variants in this enum
73    #[inline(always)]
74    pub fn variant_count(self) -> usize {
75        self.ty.variants.len()
76    }
77
78    /// Returns the variant name at the given index
79    #[inline(always)]
80    pub fn variant_name(self, index: usize) -> Option<&'shape str> {
81        self.ty.variants.get(index).map(|variant| variant.name)
82    }
83
84    /// Returns the discriminant value for the current enum value
85    #[inline]
86    pub fn discriminant(self) -> i64 {
87        // Read the discriminant based on the enum representation
88        unsafe {
89            let data = self
90                .value
91                .data()
92                .thin()
93                .expect("discriminant must be Sized");
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().thin().unwrap();
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 {
217            self.value
218                .data()
219                .thin()
220                .ok_or(VariantError::Unsized)?
221                .field(field.offset)
222        };
223        Ok(Some(unsafe {
224            Peek::unchecked_new(field_data, field.shape())
225        }))
226    }
227
228    /// Returns the index of a field in the active variant by name
229    pub fn field_index(self, field_name: &str) -> Result<Option<usize>, VariantError> {
230        let variant = self.active_variant()?;
231        Ok(variant
232            .data
233            .fields
234            .iter()
235            .position(|f| f.name == field_name))
236    }
237
238    /// Returns a PeekValue handle to a field of a tuple or struct variant by name
239    pub fn field_by_name(
240        self,
241        field_name: &str,
242    ) -> Result<Option<Peek<'mem, 'facet, 'shape>>, VariantError> {
243        let index_opt = self.field_index(field_name)?;
244        match index_opt {
245            Some(index) => self.field(index),
246            None => Ok(None),
247        }
248    }
249}
250
251impl<'mem, 'facet, 'shape> HasFields<'mem, 'facet, 'shape> for PeekEnum<'mem, 'facet, 'shape> {
252    fn fields(&self) -> FieldIter<'mem, 'facet, 'shape> {
253        FieldIter::new_enum(*self)
254    }
255}
256
257/// Error that can occur when trying to determine variant information
258#[derive(Clone, Copy, PartialEq, Eq)]
259pub enum VariantError {
260    /// Error indicating that enum internals are opaque and cannot be determined
261    OpaqueInternals,
262
263    /// Error indicating the enum value is unsized and cannot be accessed by field offset.
264    Unsized,
265}
266
267impl core::fmt::Display for VariantError {
268    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
269        match self {
270            VariantError::OpaqueInternals => {
271                write!(f, "enum layout is opaque, cannot determine variant")
272            }
273            VariantError::Unsized => {
274                write!(
275                    f,
276                    "enum value is unsized and cannot be accessed by field offset"
277                )
278            }
279        }
280    }
281}
282
283impl core::fmt::Debug for VariantError {
284    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
285        match self {
286            VariantError::OpaqueInternals => {
287                write!(
288                    f,
289                    "VariantError::OpaqueInternals: enum layout is opaque, cannot determine variant"
290                )
291            }
292            VariantError::Unsized => {
293                write!(
294                    f,
295                    "VariantError::Unsized: enum value is unsized and cannot be accessed by field offset"
296                )
297            }
298        }
299    }
300}
301
302impl core::error::Error for VariantError {}