shapely_core/
shape.rs

1use std::{alloc::Layout, any::TypeId, ptr::NonNull};
2
3mod pretty_print;
4
5mod struct_shape;
6pub use struct_shape::*;
7
8mod enum_shape;
9pub use enum_shape::*;
10
11mod scalar_shape;
12pub use scalar_shape::*;
13
14mod list_shape;
15pub use list_shape::*;
16
17mod map_shape;
18pub use map_shape::*;
19
20/// Schema for reflection of a type
21#[derive(Clone, Copy)]
22pub struct Shape {
23    /// A descriptive name for the type, e.g. `u64`, or `Person`
24    pub name: NameFn,
25
26    /// The typeid of the underlying type
27    pub typeid: TypeId,
28
29    /// Size, alignment
30    pub layout: Layout,
31
32    /// Details/contents of the value
33    pub innards: Innards,
34
35    /// Set the value at a given address to the default value for this type
36    pub set_to_default: Option<SetToDefaultFn>,
37
38    /// Drop the value at a given address
39    ///
40    /// # Safety
41    ///
42    /// This function should be called only for initialized values.
43    /// It's the caller's responsibility to ensure the address points to a valid value.
44    pub drop_in_place: Option<DropFn>,
45}
46
47/// Options for formatting the name of a type
48#[non_exhaustive]
49#[derive(Clone, Copy)]
50pub struct NameOpts {
51    /// as long as this is > 0, keep formatting the type parameters
52    /// when it reaches 0, format type parameters as `...`
53    /// if negative, all type parameters are formatted
54    pub recurse_ttl: isize,
55}
56
57impl Default for NameOpts {
58    fn default() -> Self {
59        Self { recurse_ttl: -1 }
60    }
61}
62
63impl NameOpts {
64    /// Create a new `NameOpts` for which none of the type parameters are formatted
65    pub fn none() -> Self {
66        Self { recurse_ttl: 0 }
67    }
68
69    /// Create a new `NameOpts` for which only the direct children are formatted
70    pub fn one() -> Self {
71        Self { recurse_ttl: 1 }
72    }
73
74    /// Decrease the `recurse_ttl` — if it's != 0, returns options to pass when
75    /// formatting children type parameters.
76    ///
77    /// If this returns `None` and you have type parameters, you should render a
78    /// `…` (unicode ellipsis) character instead of your list of types.
79    ///
80    /// See the implementation for `Vec` for examples.
81    pub fn for_children(&self) -> Option<Self> {
82        if self.recurse_ttl > 0 {
83            Some(Self {
84                recurse_ttl: self.recurse_ttl - 1,
85            })
86        } else {
87            None
88        }
89    }
90}
91
92/// A function that formats the name of a type.
93///
94/// This helps avoid allocations, and it takes options.
95pub type NameFn = fn(f: &mut std::fmt::Formatter, opts: NameOpts) -> std::fmt::Result;
96
97// Helper struct to format the name for display
98impl std::fmt::Display for Shape {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        (self.name)(f, NameOpts::default())
101    }
102}
103
104/// A function that sets a value to its default at a specific memory address
105pub type SetToDefaultFn = unsafe fn(*mut u8);
106
107/// A function that drops a value at a specific memory address
108pub type DropFn = unsafe fn(*mut u8);
109
110impl PartialEq for Shape {
111    fn eq(&self, other: &Self) -> bool {
112        self.typeid == other.typeid
113    }
114}
115
116impl Eq for Shape {}
117
118impl std::hash::Hash for Shape {
119    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
120        self.typeid.hash(state);
121    }
122}
123
124impl Shape {
125    const INDENT: usize = 2;
126
127    /// Extract the scalar contents from a shape and a pointer
128    ///
129    /// # Safety
130    ///
131    /// This function is unsafe because it reads from raw memory.
132    /// The caller must ensure that:
133    /// 1. The pointer points to a valid, initialized value of the correct type
134    /// 2. The memory is properly aligned for the type
135    /// 3. The memory is not mutated while the returned ScalarContents is in use
136    pub unsafe fn get_scalar_contents<'a>(&self, ptr: *const u8) -> crate::ScalarContents<'a> {
137        match self.innards {
138            Innards::Scalar(scalar) => unsafe { scalar.get_contents(ptr) },
139            _ => panic!("Expected a scalar shape"),
140        }
141    }
142
143    /// Returns a slice of statically known fields. Fields that are not in there might still be inserted if it's a dynamic collection.
144    pub fn known_fields(&self) -> &'static [Field] {
145        match self.innards {
146            Innards::Struct { fields } => fields,
147            _ => &[],
148        }
149    }
150
151    /// Returns a reference to a field with the given name, if it exists
152    pub fn field_by_name(&self, name: &str) -> Option<&Field> {
153        // this is O(n), but shrug. maybe phf in the future? who knows.
154        self.known_fields().iter().find(|field| field.name == name)
155    }
156
157    /// Returns a reference to a field with the given index, if it exists
158    pub fn field_by_index(&self, index: usize) -> Result<&Field, FieldError> {
159        match self.innards {
160            Innards::Struct { fields } => fields.get(index).ok_or(FieldError::IndexOutOfBounds),
161            _ => Err(FieldError::NotAStruct),
162        }
163    }
164
165    /// Returns a dangling pointer for this shape.
166    ///
167    /// This is useful for zero-sized types (ZSTs) which don't need actual memory allocation,
168    /// but still need a properly aligned "some address".
169    ///
170    /// # Safety
171    ///
172    /// This function returns a dangling pointer. It should only be used in contexts where
173    /// a non-null pointer is required but no actual memory access will occur, such as for ZSTs.
174    pub fn dangling(&self) -> NonNull<u8> {
175        let dang = NonNull::dangling();
176        let offset = dang.align_offset(self.layout.align());
177        unsafe { dang.byte_add(offset) }
178    }
179
180    /// Returns a slice of enum variants, if this shape represents an enum
181    pub fn variants(&self) -> &'static [Variant] {
182        match self.innards {
183            Innards::Enum { variants, repr: _ } => variants,
184            _ => &[],
185        }
186    }
187
188    /// Returns a reference to a variant with the given name, if it exists
189    pub fn variant_by_name(&self, name: &str) -> Option<&Variant> {
190        self.variants().iter().find(|variant| variant.name == name)
191    }
192
193    /// Returns a reference to a variant with the given index, if it exists
194    pub fn variant_by_index(&self, index: usize) -> Result<&Variant, VariantError> {
195        match self.innards {
196            Innards::Enum { variants, repr: _ } => {
197                variants.get(index).ok_or(VariantError::IndexOutOfBounds)
198            }
199            _ => Err(VariantError::NotAnEnum),
200        }
201    }
202
203    /// Returns the enum representation, if this shape represents an enum
204    pub fn enum_repr(&self) -> Option<EnumRepr> {
205        match self.innards {
206            Innards::Enum { variants: _, repr } => Some(repr),
207            _ => None,
208        }
209    }
210}
211
212/// Errors encountered when calling `field_by_index` or `field_by_name`
213#[derive(Debug, Copy, Clone, PartialEq, Eq)]
214pub enum FieldError {
215    /// `field_by_index` was called on a dynamic collection, that has no
216    /// static fields. a map doesn't have a "first field", it can only
217    /// associate by keys.
218    NoStaticFields,
219
220    /// `field_by_name` was called on a struct, and there is no static field
221    /// with the given key.
222    NoSuchStaticField,
223
224    /// `field_by_index` was called on a fixed-size collection (like a tuple,
225    /// a struct, or a fixed-size array) and the index was out of bounds.
226    IndexOutOfBounds,
227
228    /// `field_by_index` or `field_by_name` was called on a non-struct type.
229    NotAStruct,
230}
231
232impl std::error::Error for FieldError {}
233
234impl std::fmt::Display for FieldError {
235    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236        match self {
237            FieldError::NoStaticFields => write!(f, "No static fields available"),
238            FieldError::NoSuchStaticField => write!(f, "No such static field"),
239            FieldError::IndexOutOfBounds => write!(f, "Index out of bounds"),
240            FieldError::NotAStruct => write!(f, "Not a struct"),
241        }
242    }
243}
244
245impl std::fmt::Debug for Shape {
246    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247        self.pretty_print_recursive(f)
248    }
249}
250
251/// The shape of a schema: is it more map-shaped, array-shaped, scalar-shaped?
252#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
253pub enum Innards {
254    /// Struct with statically-known, named fields
255    ///
256    /// e.g. `struct Struct { field: u32 }`
257    Struct {
258        /// all fields, in declaration order (not necessarily in memory order)
259        fields: &'static [Field],
260    },
261
262    /// Tuple-struct, with numbered fields
263    ///
264    /// e.g. `struct TupleStruct(u32, u32);`
265    TupleStruct {
266        /// all fields, in declaration order (not necessarily in memory order)
267        fields: &'static [Field],
268    },
269
270    /// Tuple, with numbered fields
271    ///
272    /// e.g. `(u32, u32);`
273    Tuple {
274        /// all fields, in declaration order (not necessarily in memory order)
275        fields: &'static [Field],
276    },
277
278    /// Map — keys are dynamic (and strings, sorry), values are homogeneous
279    ///
280    /// e.g. `Map<String, T>`
281    Map {
282        /// vtable for interacting with the map
283        vtable: MapVTable,
284
285        /// shape of the values in the map (keys must be String, sorry)
286        value_shape: ShapeDesc,
287    },
288
289    /// Ordered list of heterogenous values, variable size
290    ///
291    /// e.g. `Vec<T>`
292    List {
293        /// vtable for interacting with the list
294        vtable: ListVTable,
295
296        /// shape of the items in the list
297        item_shape: ShapeDesc,
298    },
299
300    /// Transparent — forwards to another known schema
301    ///
302    /// e.g. `#[repr(transparent)] struct Transparent<T>(T);`
303    Transparent(ShapeDesc),
304
305    /// Scalar — known based type
306    ///
307    /// e.g. `u32`, `String`, `bool`
308    Scalar(Scalar),
309
310    /// Enum with variants
311    ///
312    /// e.g. `enum Enum { Variant1, Variant2 }`
313    Enum {
314        /// all variants for this enum
315        variants: &'static [Variant],
316
317        /// representation of the enum
318        repr: EnumRepr,
319    },
320}
321
322/// A function that returns a shape. There should only be one of these per concrete type in a
323#[derive(Clone, Copy)]
324pub struct ShapeDesc(pub fn() -> Shape);
325
326impl From<fn() -> Shape> for ShapeDesc {
327    fn from(f: fn() -> Shape) -> Self {
328        Self(f)
329    }
330}
331
332impl ShapeDesc {
333    /// Build the inner shape
334    #[inline(always)]
335    pub fn get(&self) -> Shape {
336        (self.0)()
337    }
338}
339
340impl PartialEq for ShapeDesc {
341    fn eq(&self, other: &Self) -> bool {
342        if std::ptr::eq(self.0 as *const (), other.0 as *const ()) {
343            true
344        } else {
345            let self_shape = self.0();
346            let other_shape = other.0();
347            self_shape == other_shape
348        }
349    }
350}
351
352impl Eq for ShapeDesc {}
353
354impl std::hash::Hash for ShapeDesc {
355    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
356        // Hash the function pointer
357        (self.0 as *const ()).hash(state);
358    }
359}
360
361impl std::fmt::Debug for ShapeDesc {
362    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363        self.get().fmt(f)
364    }
365}