facet_reflect/peek/
enum_.rs

1use facet_core::{EnumRepr, EnumType, Field, Shape, UserType, Variant};
2
3use crate::{Peek, trace};
4
5use super::HasFields;
6
7/// Lets you read from an enum (implements read-only enum operations)
8#[derive(Clone, Copy)]
9pub struct PeekEnum<'mem, 'facet_lifetime> {
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_lifetime>,
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        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: &'static Shape) -> Option<EnumType> {
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: &'static 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: &'static Shape) -> Option<&'static [Variant]> {
45    peek_enum(shape).map(|enum_def| enum_def.variants)
46}
47
48impl<'mem, 'facet_lifetime> core::ops::Deref for PeekEnum<'mem, 'facet_lifetime> {
49    type Target = Peek<'mem, 'facet_lifetime>;
50
51    #[inline(always)]
52    fn deref(&self) -> &Self::Target {
53        &self.value
54    }
55}
56
57impl<'mem, 'facet_lifetime> PeekEnum<'mem, 'facet_lifetime> {
58    /// Returns the enum definition
59    #[inline(always)]
60    pub fn ty(self) -> EnumType {
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) -> &'static [Variant] {
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<&'static 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<&'static Variant, 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<&'static 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_lifetime>>, 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_lifetime>>, 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_lifetime> HasFields<'mem, 'facet_lifetime> for PeekEnum<'mem, 'facet_lifetime> {
246    fn fields(&self) -> impl DoubleEndedIterator<Item = (Field, Peek<'mem, 'facet_lifetime>)> {
247        // Get the active variant and its fields
248        let variant = match self.active_variant() {
249            Ok(v) => v,
250            Err(e) => panic!("Cannot get active variant: {:?}", e),
251        };
252        let fields = &variant.data.fields;
253
254        // Create an iterator that yields the field definition and field value
255        (0..fields.len()).filter_map(move |i| {
256            // Get the field definition
257            let field = fields[i];
258            // Get the field value
259            let field_value = match self.field(i) {
260                Ok(Some(v)) => v,
261                Ok(None) => return None,
262                Err(e) => panic!("Cannot get field: {:?}", e),
263            };
264            // Return the field definition and value
265            Some((field, field_value))
266        })
267    }
268}
269
270/// Error that can occur when trying to determine variant information
271#[derive(Clone, Copy, PartialEq, Eq)]
272pub enum VariantError {
273    /// Error indicating that enum internals are opaque and cannot be determined
274    OpaqueInternals,
275}
276
277impl core::fmt::Display for VariantError {
278    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
279        write!(f, "enum layout is opaque, cannot determine variant")
280    }
281}
282
283impl core::fmt::Debug for VariantError {
284    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
285        write!(
286            f,
287            "VariantError::OpaqueInternals: enum layout is opaque, cannot determine variant"
288        )
289    }
290}
291
292impl core::error::Error for VariantError {}