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}