shapely_core/
shape.rs

1use std::{alloc::Layout, any::TypeId, collections::HashSet, fmt::Formatter, ptr::NonNull};
2
3/// Schema for reflection of a type
4#[derive(Clone, Copy)]
5pub struct Shape {
6    /// A descriptive name for the type, e.g. `u64`, or `Person`
7    pub name: NameFn,
8
9    /// The typeid of the underlying type
10    pub typeid: TypeId,
11
12    /// Size & alignment
13    pub layout: Layout,
14
15    /// Details/contents of the value
16    pub innards: Innards,
17
18    /// Set the value at a given address to the default value for this type
19    pub set_to_default: Option<SetToDefaultFn>,
20
21    /// Drop the value at a given address
22    ///
23    /// # Safety
24    ///
25    /// This function should be called only for initialized values.
26    /// It's the caller's responsibility to ensure the address points to a valid value.
27    pub drop_in_place: Option<DropFn>,
28}
29
30pub type NameFn = fn(f: &mut std::fmt::Formatter) -> std::fmt::Result;
31
32// Helper struct to format the name for display
33impl std::fmt::Display for Shape {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        (self.name)(f)
36    }
37}
38
39/// A function that sets a value to its default at a specific memory address
40pub type SetToDefaultFn = unsafe fn(*mut u8);
41
42/// A function that drops a value at a specific memory address
43pub type DropFn = unsafe fn(*mut u8);
44
45impl PartialEq for Shape {
46    fn eq(&self, other: &Self) -> bool {
47        self.typeid == other.typeid
48    }
49}
50
51impl Eq for Shape {}
52
53impl std::hash::Hash for Shape {
54    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
55        self.typeid.hash(state);
56    }
57}
58
59impl Shape {
60    const INDENT: usize = 2;
61
62    /// Pretty-print this shape, recursively.
63    pub fn pretty_print_recursive(&self, f: &mut Formatter) -> std::fmt::Result {
64        self.pretty_print_recursive_internal(f, &mut HashSet::new(), 0)
65    }
66
67    fn pretty_print_recursive_internal(
68        &self,
69        f: &mut Formatter,
70        printed_schemas: &mut HashSet<Shape>,
71        indent: usize,
72    ) -> std::fmt::Result {
73        if !printed_schemas.insert(*self) {
74            write!(
75                f,
76                "{:indent$}\x1b[1;33m",
77                "",
78                indent = indent
79            )?;
80            (self.name)(f)?;
81            writeln!(
82                f,
83                "\x1b[0m (\x1b[1;31malready printed\x1b[0m)"
84            )?;
85            return Ok(());
86        }
87
88        write!(
89            f,
90            "{:indent$}\x1b[1;33m",
91            "",
92            indent = indent
93        )?;
94        (self.name)(f)?;
95        writeln!(
96            f,
97            "\x1b[0m (size: \x1b[1;34m{}\x1b[0m, align: \x1b[1;35m{}\x1b[0m)",
98            self.layout.size(),
99            self.layout.align()
100        )?;
101
102        match &self.innards {
103            Innards::Struct { fields } => {
104                for field in *fields {
105                    writeln!(
106                        f,
107                        "{:indent$}\x1b[1;32m{}\x1b[0m: ",
108                        "",
109                        field.name,
110                        indent = indent + Self::INDENT
111                    )?;
112                    field.shape.get().pretty_print_recursive_internal(
113                        f,
114                        printed_schemas,
115                        indent + Self::INDENT * 2,
116                    )?;
117                }
118            }
119            Innards::HashMap { value_shape } => {
120                writeln!(
121                    f,
122                    "{:indent$}\x1b[1;36mHashMap with arbitrary keys and value shape:\x1b[0m",
123                    "",
124                    indent = indent + Self::INDENT
125                )?;
126                value_shape.get().pretty_print_recursive_internal(
127                    f,
128                    printed_schemas,
129                    indent + Self::INDENT * 2,
130                )?;
131            }
132            Innards::Array(elem_schema) => {
133                write!(
134                    f,
135                    "{:indent$}\x1b[1;36mArray of:\x1b[0m ",
136                    "",
137                    indent = indent + Self::INDENT
138                )?;
139                elem_schema.get().pretty_print_recursive_internal(
140                    f,
141                    printed_schemas,
142                    indent + Self::INDENT * 2,
143                )?;
144            }
145            Innards::Transparent(inner_schema) => {
146                write!(
147                    f,
148                    "{:indent$}\x1b[1;36mTransparent wrapper for:\x1b[0m ",
149                    "",
150                    indent = indent + Self::INDENT
151                )?;
152                inner_schema.get().pretty_print_recursive_internal(
153                    f,
154                    printed_schemas,
155                    indent + Self::INDENT * 2,
156                )?;
157            }
158            Innards::Scalar(scalar) => {
159                writeln!(
160                    f,
161                    "{:indent$}\x1b[1;36mScalar:\x1b[0m \x1b[1;33m{:?}\x1b[0m",
162                    "",
163                    scalar,
164                    indent = indent
165                )?;
166            }
167        }
168
169        Ok(())
170    }
171
172    /// Returns a slice of statically known fields. Fields that are not in there might still be inserted if it's a dynamic collection.
173    pub fn known_fields(&self) -> &'static [Field] {
174        match self.innards {
175            Innards::Struct { fields } => fields,
176            _ => &[],
177        }
178    }
179
180    /// Returns a reference to a field with the given name, if it exists
181    pub fn field_by_name(&self, name: &str) -> Option<&Field> {
182        self.known_fields().iter().find(|field| field.name == name)
183    }
184
185    /// Returns a reference to a field with the given index, if it exists
186    pub fn field_by_index(&self, index: usize) -> Result<&Field, FieldError> {
187        match self.innards {
188            Innards::Struct { fields } => fields.get(index).ok_or(FieldError::IndexOutOfBounds),
189            _ => Err(FieldError::NotAStruct),
190        }
191    }
192
193    /// Returns a dangling pointer for this shape.
194    ///
195    /// This is useful for zero-sized types (ZSTs) which don't need actual memory allocation,
196    /// but still need a properly aligned "some address".
197    ///
198    /// # Safety
199    ///
200    /// This function returns a dangling pointer. It should only be used in contexts where
201    /// a non-null pointer is required but no actual memory access will occur, such as for ZSTs.
202    pub fn dangling(&self) -> NonNull<u8> {
203        let dang = NonNull::dangling();
204        let offset = dang.align_offset(self.layout.align());
205        unsafe { dang.byte_add(offset) }
206    }
207}
208
209/// Errors encountered when calling `field_by_index` or `field_by_name`
210#[derive(Debug)]
211pub enum FieldError {
212    /// `field_by_index` was called on a dynamic collection, that has no
213    /// static fields. a HashMap doesn't have a "first field", it can only
214    /// associate by keys.
215    NoStaticFields,
216
217    /// `field_by_name` was called on a struct, and there is no static field
218    /// with the given key.
219    NoSuchStaticField,
220
221    /// `field_by_index` was called on a fixed-size collection (like a tuple,
222    /// a struct, or a fixed-size array) and the index was out of bounds.
223    IndexOutOfBounds,
224
225    /// `field_by_index` or `field_by_name` was called on a non-struct type.
226    NotAStruct,
227}
228
229impl std::error::Error for FieldError {}
230
231impl std::fmt::Display for FieldError {
232    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233        match self {
234            FieldError::NoStaticFields => write!(f, "No static fields available"),
235            FieldError::NoSuchStaticField => write!(f, "No such static field"),
236            FieldError::IndexOutOfBounds => write!(f, "Index out of bounds"),
237            FieldError::NotAStruct => write!(f, "Not a struct"),
238        }
239    }
240}
241
242impl std::fmt::Debug for Shape {
243    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244        self.pretty_print_recursive(f)
245    }
246}
247
248/// The shape of a schema: is it more map-shaped, array-shaped, scalar-shaped?
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
250pub enum Innards {
251    /// Struct with statically-known fields
252    Struct { fields: &'static [Field] },
253
254    /// HashMap — keys are dynamic, values are homogeneous
255    HashMap { value_shape: ShapeDesc },
256
257    /// Ordered list of heterogenous values, variable size
258    Array(ShapeDesc),
259
260    /// Transparent — forwards to another known schema
261    Transparent(ShapeDesc),
262
263    /// Scalar — known based type
264    Scalar(Scalar),
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
268pub struct Field {
269    /// key for the map field
270    pub name: &'static str,
271
272    /// schema of the inner type
273    pub shape: ShapeDesc,
274
275    /// offset of the field in the map, if known.
276    ///
277    /// For example, when deserializing a self-descriptive format like JSON, we're going to get
278    /// some map fields with dynamically discovered field names, and they're not going to have
279    /// an offset.
280    ///
281    /// However, when deserializing formats that are non-self descriptive and working from an
282    /// existing shape, then their map fields are probably going to have offsets, especially if
283    /// they're using derived macros.
284    pub offset: usize,
285}
286
287/// The outcome of trying to set a field on a map
288#[derive(Debug, Clone, Copy)]
289pub enum SetFieldOutcome {
290    /// The field was successfully set
291    Accepted,
292
293    /// The field was rejected (unknown field set in a struct, for example)
294    Rejected,
295}
296
297#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
298#[non_exhaustive]
299pub enum Scalar {
300    // Valid utf-8
301    String,
302
303    // Not valid utf-8 🤷
304    Bytes,
305
306    I8,
307    I16,
308    I32,
309    I64,
310    I128,
311
312    U8,
313    U16,
314    U32,
315    U64,
316    U128,
317
318    F32,
319    F64,
320
321    Boolean,
322
323    /// An empty tuple, null, undefined, whatever you wish
324    Nothing,
325}
326
327/// A function that returns a shape. There should only be one of these per concrete type in a
328/// program. This enables optimizations.
329#[derive(Clone, Copy)]
330pub struct ShapeDesc(pub fn() -> Shape);
331
332impl From<fn() -> Shape> for ShapeDesc {
333    fn from(f: fn() -> Shape) -> Self {
334        Self(f)
335    }
336}
337
338impl ShapeDesc {
339    /// Build the inner shape
340    #[inline(always)]
341    pub fn get(&self) -> Shape {
342        (self.0)()
343    }
344}
345
346impl PartialEq for ShapeDesc {
347    fn eq(&self, other: &Self) -> bool {
348        if std::ptr::eq(self.0 as *const (), other.0 as *const ()) {
349            true
350        } else {
351            let self_shape = self.0();
352            let other_shape = other.0();
353            self_shape == other_shape
354        }
355    }
356}
357
358impl Eq for ShapeDesc {}
359
360impl std::hash::Hash for ShapeDesc {
361    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
362        // Hash the function pointer
363        (self.0 as *const ()).hash(state);
364    }
365}
366
367impl std::fmt::Debug for ShapeDesc {
368    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369        self.get().fmt(f)
370    }
371}