bones_schema/
schema.rs

1//! [`Schema`], [`HasSchema`], [`SchemaData`], and related types.
2
3use std::{alloc::Layout, any::TypeId, borrow::Cow, ffi::c_void};
4
5use ustr::Ustr;
6
7use crate::{alloc::TypeDatas, prelude::*};
8
9/// Trait implemented for types that have a [`Schema`].
10///
11/// # Safety
12///
13/// This trait is unsafe to implement manually because it makes claims about the memory layout of a
14/// type that may be depended on in unsafe code, but it is safe to derive [`HasSchema`] on supported
15/// types.
16///
17/// If implemented manually, you must ensure that the schema accurately describes the memory layout
18/// of the type, or else accessing the type according to the schema would be unsound.
19pub unsafe trait HasSchema: Sync + Send + 'static {
20    /// Get this type's [`Schema`].
21    #[must_use = "If you are only calling schema() for it's side-effect of registering the
22        schema, it is more clear to use register_schema() instead."]
23    fn schema() -> &'static Schema;
24
25    /// Register this schema with the global schema registry.
26    ///
27    /// This is automatically done by the framework in many cases, whenever
28    /// [`schema()`][HasSchema::schema] is called, but it may be necessary sometimes to
29    /// manually register it.
30    fn register_schema() {
31        let _ = Self::schema();
32    }
33
34    /// Cast a reference of this type to a reference of another type with the same memory layout.
35    ///
36    /// # Panics
37    ///
38    /// Panics if the schema of `T` doesn't match the schema of `Self`.
39    #[track_caller]
40    fn cast<T: HasSchema>(this: &Self) -> &T {
41        HasSchema::try_cast(this).expect(SchemaMismatchError::MSG)
42    }
43
44    /// Cast a reference of this type to a reference of another type with the same memory layout.
45    ///
46    /// # Errors
47    ///
48    /// Errors if the schema of `T` doesn't match the schema of `Self`.
49    fn try_cast<T: HasSchema>(this: &Self) -> Result<&T, SchemaMismatchError> {
50        let s1 = Self::schema();
51        let s2 = T::schema();
52        if s1.represents(s2) {
53            // SOUND: the schemas have the same memory representation.
54            unsafe { Ok(&*(this as *const Self as *const T)) }
55        } else {
56            Err(SchemaMismatchError)
57        }
58    }
59
60    /// Cast a mutable reference of this type to a reference of another type with the same memory
61    /// layout.
62    ///
63    /// # Panics
64    ///
65    /// Panics if the schema of `T` doesn't match the schema of `Self`.
66    #[track_caller]
67    fn cast_mut<T: HasSchema>(this: &mut Self) -> &mut T {
68        HasSchema::try_cast_mut(this).expect(SchemaMismatchError::MSG)
69    }
70
71    /// Cast a mutable reference of this type to a reference of another type with the same memory
72    /// layout.
73    ///
74    /// # Errors
75    ///
76    /// Errors if the schema of `T` doesn't match the schema of `Self`.
77    fn try_cast_mut<T: HasSchema>(this: &mut Self) -> Result<&mut T, SchemaMismatchError> {
78        let s1 = Self::schema();
79        let s2 = T::schema();
80        if s1.represents(s2) {
81            // SOUND: the schemas have the same memory representation.
82            unsafe { Ok(&mut *(this as *mut Self as *mut T)) }
83        } else {
84            Err(SchemaMismatchError)
85        }
86    }
87
88    /// Converts a reference of `T` to a [`SchemaRef`]
89    fn as_schema_ref(&self) -> SchemaRef<'_>
90    where
91        Self: Sized,
92    {
93        SchemaRef::new(self)
94    }
95
96    /// Converts a reference of `T` to a [`SchemaRefMut`]
97    fn as_schema_mut(&mut self) -> SchemaRefMut<'_>
98    where
99        Self: Sized,
100    {
101        SchemaRefMut::new(self)
102    }
103}
104
105// Export the `Schema` type so it appears in this module. It is defined in the registry module so
106// that the registry is the only module that is allowed to construct `Schema`s.
107#[doc(inline)]
108pub use crate::registry::Schema;
109
110impl Schema {
111    /// Returns whether or not this schema represents the same memory layout as the other schema,
112    /// and you can safely cast a pointer to one to a pointer to the other.
113    pub fn represents(&self, other: &Schema) -> bool {
114        // If these have equal type/schema ids.
115        self == other
116        // If the schemas don't have any opaque fields, and are equal to each-other, then they
117        // have the same representation.
118        || (!self.kind.has_opaque() && !other.kind.has_opaque() && {
119            match (&self.kind, &other.kind) {
120                (SchemaKind::Struct(s1), SchemaKind::Struct(s2)) => {
121                    s1.fields.len() == s2.fields.len() &&
122                        s1.fields.iter().zip(s2.fields.iter())
123                        .all(|(f1, f2)| f1.schema.represents(f2.schema))
124                },
125                (SchemaKind::Vec(v1), SchemaKind::Vec(v2)) => v1.represents(v2),
126                (SchemaKind::Primitive(p1), SchemaKind::Primitive(p2)) => p1 == p2,
127                (SchemaKind::Enum(e1), SchemaKind::Enum(e2)) => e1 == e2,
128                _ => false
129            }
130        })
131    }
132}
133
134/// Schema information describing the memory layout of a type.
135#[derive(Clone)]
136pub struct SchemaData {
137    /// The short name of the type.
138    ///
139    /// **Note:** Currently bones isn't very standardized as far as name generation for Rust or
140    /// other language type names, and this is mostly for diagnostics. This may change in the future
141    /// but for now there are no guarantees.
142    pub name: Ustr,
143    /// The full name of the type, including any module specifiers.
144    ///
145    /// **Note:** Currently bones isn't very standardized as far as name generation for Rust or
146    /// other language type names, and this is mostly for diagnostics. This may change in the future
147    /// but for now there are no guarantees.
148    pub full_name: Ustr,
149    /// The kind of schema.
150    pub kind: SchemaKind,
151    /// Container for storing [`Schema`] type datas.
152    ///
153    /// "Type data" is extra data that is stored in a type's [`Schema`] that may be used for any number
154    /// of different purposes.
155    ///
156    /// Each type data is a type that implements [`HasSchema`] and usually describes something about the
157    /// type that has the schema. For instance, a type data could be added to a struct that can be used
158    /// to serialize/deserialize that type.
159    ///
160    /// If a type data also implements [`FromType`] it can be derived for types that it implements
161    /// [`FromType`] for:
162    ///
163    /// ```rust
164    /// # use bones_schema::prelude::*;
165    /// #[derive(HasSchema, Default, Clone)]
166    /// struct SomeTypeData;
167    ///
168    /// impl<T> FromType<T> for SomeTypeData {
169    ///     fn from_type() -> Self {
170    ///         SomeTypeData
171    ///     }
172    /// }
173    ///
174    /// #[derive(HasSchema, Default, Clone)]
175    /// #[derive_type_data(SomeTypeData)]
176    /// struct MyData;
177    /// ```
178    pub type_data: TypeDatas,
179
180    // NOTE: The fields below could be implemented as type datas, and it would be nicely elegant to
181    // do so, but for performance reasons, we put them right in the [`Schema`] struct because
182    // they're use is so common. If profiling does not reveal any performance issues with using them
183    // as type datas, we may want to remove these fields in favor of the type data.
184    /// The Rust [`TypeId`] that this [`Schema`] was created from, if it was created from a Rust
185    /// type.
186    pub type_id: Option<TypeId>,
187    /// The function pointer that may be used to clone data with this schema.
188    pub clone_fn:
189        Option<Unsafe<&'static (dyn Fn(*const c_void, *mut c_void) + Sync + Send + 'static)>>,
190    /// The function pointer that may be used to drop data with this schema.
191    pub drop_fn: Option<Unsafe<&'static (dyn Fn(*mut c_void) + Sync + Send + 'static)>>,
192    /// The function pointer that may be used to write a default value to a pointer.
193    pub default_fn: Option<Unsafe<&'static (dyn Fn(*mut c_void) + Sync + Send + 'static)>>,
194    /// The function pointer that may be used to hash the value.
195    pub hash_fn: Option<Unsafe<&'static (dyn Fn(*const c_void) -> u64 + Sync + Send + 'static)>>,
196    /// The function pointer that may be used to compare two values for equality. Note that this is
197    /// total equality, not partial equality.
198    pub eq_fn: Option<
199        Unsafe<&'static (dyn Fn(*const c_void, *const c_void) -> bool + Sync + Send + 'static)>,
200    >,
201}
202
203impl std::fmt::Debug for SchemaData {
204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        f.debug_struct("SchemaData")
206            .field("name", &self.name)
207            .field("full_name", &self.full_name)
208            .field("kind", &self.kind)
209            .field("type_data", &self.type_data)
210            .field("type_id", &self.type_id)
211            .finish_non_exhaustive()
212    }
213}
214
215/// A wrapper struct that marks it unsafe to both create and access the inner value.
216#[derive(Clone, Debug)]
217pub struct Unsafe<T>(T);
218
219impl<T> Unsafe<T> {
220    /// Create a new [`Unsafe`] contianing the `value`.
221    /// # Safety
222    /// The safety invariants are dependent on the inner type.
223    pub unsafe fn new(value: T) -> Self {
224        Self(value)
225    }
226
227    /// Unsafely get the inner value.
228    /// # Safety
229    /// The safety invariants are dependent on the inner type.
230    pub unsafe fn get(&self) -> &T {
231        &self.0
232    }
233}
234
235/// A schema describes the data layout of a type, to enable dynamic access to the type's data
236/// through a pointer.
237#[derive(Debug, Clone)]
238pub enum SchemaKind {
239    /// The type represents a struct.
240    Struct(StructSchemaInfo),
241    /// Type represents a [`SchemaVec`], where each item in the vec has the contained [`Schema`].
242    ///
243    /// The scripting solution must facilitate a way for scripts to access data in the [`Vec`] if it
244    /// is to be readable/modifyable from scripts.
245    Vec(&'static Schema),
246    /// Type represents an enum, which in the C layout is called a tagged union.
247    Enum(EnumSchemaInfo),
248    /// Type represents a [`SchemaMap`].
249    Map {
250        /// The schema of the key type.
251        key: &'static Schema,
252        /// The schema of the value type.
253        value: &'static Schema,
254    },
255    /// The represents a [`SchemaBox`] with given type inside.
256    Box(&'static Schema),
257    /// The type represents a primitive value.
258    Primitive(Primitive),
259}
260
261impl SchemaKind {
262    /// Get the primitive, if this is a primitive.
263    pub fn as_primitive(&self) -> Option<&Primitive> {
264        if let Self::Primitive(p) = self {
265            Some(p)
266        } else {
267            None
268        }
269    }
270    /// Get the struct, if this is a struct.
271    pub fn as_struct(&self) -> Option<&StructSchemaInfo> {
272        if let Self::Struct(s) = self {
273            Some(s)
274        } else {
275            None
276        }
277    }
278    /// Get the enum, if this is a enum.
279    pub fn as_enum(&self) -> Option<&EnumSchemaInfo> {
280        if let Self::Enum(e) = self {
281            Some(e)
282        } else {
283            None
284        }
285    }
286    /// Get the schema of the items in the vector, if this is a vector.
287    pub fn as_vec(&self) -> Option<&'static Schema> {
288        if let Self::Vec(v) = self {
289            Some(v)
290        } else {
291            None
292        }
293    }
294}
295
296/// Layout information computed for [`SchemaData`].
297#[derive(Debug, Clone)]
298pub struct SchemaLayoutInfo<'a> {
299    /// The layout of the type.
300    pub layout: Layout,
301    /// If this is a struct, then the field offsets will contain the byte offset from the front of
302    /// the struct to each field in the order the fields are declared.
303    ///
304    /// If this is an enum, there will be exactly one entry specifying the offset from the beginning
305    /// of the struct to the enum value.
306    pub field_offsets: Vec<(Option<&'a str>, usize)>,
307}
308
309/// Schema data for a struct.
310#[derive(Debug, Clone)]
311pub struct StructSchemaInfo {
312    /// The fields in the struct, in the order they are defined.
313    pub fields: Vec<StructFieldInfo>,
314}
315
316/// Schema data for an enum.
317#[derive(Debug, Clone, PartialEq, Eq)]
318pub struct EnumSchemaInfo {
319    /// The layout of the enum tag.
320    pub tag_type: EnumTagType,
321    /// Info for the enum variants.
322    pub variants: Vec<VariantInfo>,
323}
324
325/// A type for an enum tag for [`EnumSchemaInfo`].
326#[derive(Debug, Clone, PartialEq, Eq)]
327pub enum EnumTagType {
328    /// A [`u8`].
329    U8,
330    /// A [`u16`].
331    U16,
332    /// A [`u32`].
333    U32,
334}
335
336impl EnumTagType {
337    /// Get the memory layout of the enum tag.
338    pub fn layout(&self) -> Layout {
339        match self {
340            EnumTagType::U8 => Layout::new::<u8>(),
341            EnumTagType::U16 => Layout::new::<u16>(),
342            EnumTagType::U32 => Layout::new::<u32>(),
343        }
344    }
345}
346
347/// Information about an enum variant for [`EnumSchemaInfo`].
348#[derive(Debug, Clone, PartialEq, Eq)]
349pub struct VariantInfo {
350    /// The name of the enum variant.
351    pub name: Cow<'static, str>,
352    /// The schema of this variant.
353    pub schema: &'static Schema,
354}
355
356/// A field in a [`StructSchemaInfo`].
357#[derive(Debug, Clone)]
358pub struct StructFieldInfo {
359    /// The name of the field. Will be [`None`] if this is a field of a tuple struct.
360    pub name: Option<Ustr>,
361    /// The schema of the field.
362    pub schema: &'static Schema,
363    // TODO: Investigate adding attribute info to `StructFieldInfo`.
364    // It could be very useful if the derive macro could capture custom attribute data for struct
365    // fields and put it into the schema data. This would allow type data implementations that
366    // implement `FromType` to have access to custom attributes that could be used to modify various
367    // behavior, without requiring a new macro.
368}
369
370/// A type of primitive.
371#[derive(Debug, Clone, PartialEq, Eq, Hash)]
372pub enum Primitive {
373    /// A boolean.
374    Bool,
375    /// [`u8`]
376    U8,
377    /// [`u16`]
378    U16,
379    /// [`u32`]
380    U32,
381    /// [`u64`]
382    U64,
383    /// [`u128`]
384    U128,
385    /// [`i8`]
386    I8,
387    /// [`i16`]
388    I16,
389    /// [`i32`]
390    I32,
391    /// [`i64`]
392    I64,
393    /// [`i128`]
394    I128,
395    /// [`f32`]
396    F32,
397    /// [`f64`]
398    F64,
399    /// A Rust [`String`]. Must be manipulated with Rust string methods.
400    String,
401    /// Opaque data that cannot described by a schema.
402    Opaque {
403        /// The size of the data.
404        size: usize,
405        /// The alignment of the data.
406        align: usize,
407    },
408}
409
410/// Trait implemented for types that can produce an instance of themselves from a Rust type.
411///
412/// This is useful for [type datas][`SchemaData::type_data`], which may be derived for a type if the type data
413/// implements [`FromType`] for type that is deriving it.
414pub trait FromType<T> {
415    /// Return the data for the type.
416    fn from_type() -> Self;
417}
418
419impl SchemaKind {
420    /// Calculate the layout of the type represented by the schema.
421    ///
422    /// Usually you don't need to call this and should use the static, cached layout and field
423    /// offsets from [`Schema::layout()`] and [`Schema::field_offsets()`].
424    pub fn compute_layout_info(&self) -> SchemaLayoutInfo<'_> {
425        let mut layout: Option<Layout> = None;
426        let mut field_offsets = Vec::new();
427        let mut offset;
428
429        let extend_layout = |layout: &mut Option<Layout>, l| {
430            if let Some(layout) = layout {
431                let (new_layout, offset) = layout.extend(l).unwrap();
432                *layout = new_layout;
433                offset
434            } else {
435                *layout = Some(l);
436                0
437            }
438        };
439
440        match &self {
441            SchemaKind::Struct(s) => {
442                for field in &s.fields {
443                    let field_layout_info = field.schema.kind.compute_layout_info();
444                    offset = extend_layout(&mut layout, field_layout_info.layout);
445                    field_offsets.push((field.name.as_deref(), offset));
446                }
447            }
448            SchemaKind::Vec(_) => {
449                extend_layout(&mut layout, Layout::new::<SchemaVec>());
450            }
451            SchemaKind::Box(_) => {
452                extend_layout(&mut layout, Layout::new::<SchemaBox>());
453            }
454            SchemaKind::Map { .. } => {
455                extend_layout(&mut layout, Layout::new::<SchemaMap>());
456            }
457            SchemaKind::Enum(e) => {
458                let enum_tag_layout = e.tag_type.layout();
459                extend_layout(&mut layout, enum_tag_layout);
460
461                let mut enum_value_layout = Layout::from_size_align(0, 1).unwrap();
462                // The enum layout is the greatest of it's variants sizes and aligments
463                for var in &e.variants {
464                    let var_layout = var.schema.layout();
465                    if var_layout.size() > enum_value_layout.size() {
466                        enum_value_layout =
467                            Layout::from_size_align(var_layout.size(), enum_value_layout.align())
468                                .unwrap();
469                    }
470                    if var_layout.align() > enum_value_layout.align() {
471                        enum_value_layout =
472                            Layout::from_size_align(enum_value_layout.size(), var_layout.align())
473                                .unwrap();
474                    }
475                }
476                let offset = extend_layout(&mut layout, enum_value_layout);
477                // Enums have a single field offset that is the offset of the value part of the enum
478                // comming after the discriminant.
479                field_offsets.push((None, offset));
480            }
481            SchemaKind::Primitive(p) => {
482                extend_layout(
483                    &mut layout,
484                    match p {
485                        Primitive::Bool => Layout::new::<bool>(),
486                        Primitive::U8 => Layout::new::<u8>(),
487                        Primitive::U16 => Layout::new::<u16>(),
488                        Primitive::U32 => Layout::new::<u32>(),
489                        Primitive::U64 => Layout::new::<u64>(),
490                        Primitive::U128 => Layout::new::<u128>(),
491                        Primitive::I8 => Layout::new::<i8>(),
492                        Primitive::I16 => Layout::new::<i16>(),
493                        Primitive::I32 => Layout::new::<i32>(),
494                        Primitive::I64 => Layout::new::<i64>(),
495                        Primitive::I128 => Layout::new::<i128>(),
496                        Primitive::F32 => Layout::new::<f32>(),
497                        Primitive::F64 => Layout::new::<f64>(),
498                        Primitive::String => Layout::new::<String>(),
499                        Primitive::Opaque { size, align } => {
500                            Layout::from_size_align(*size, *align).unwrap()
501                        }
502                    },
503                );
504            }
505        }
506
507        SchemaLayoutInfo {
508            layout: layout
509                // Handle ZST
510                .unwrap_or_else(|| Layout::from_size_align(0, 1).unwrap())
511                .pad_to_align(),
512            field_offsets,
513        }
514    }
515
516    /// Recursively checks whether or not the schema contains any [`Opaque`][Primitive::Opaque] primitives.
517    pub fn has_opaque(&self) -> bool {
518        match &self {
519            SchemaKind::Struct(s) => s.fields.iter().any(|field| field.schema.kind.has_opaque()),
520            SchemaKind::Vec(v) => v.kind.has_opaque(),
521            SchemaKind::Box(b) => b.schema().kind.has_opaque(),
522            SchemaKind::Enum(e) => e.variants.iter().any(|var| var.schema.kind.has_opaque()),
523            SchemaKind::Map { key, value } => {
524                key.schema().kind.has_opaque() || value.schema().kind.has_opaque()
525            }
526            SchemaKind::Primitive(p) => matches!(p, Primitive::Opaque { .. }),
527        }
528    }
529}