spacetimedb_sats/typespace.rs
1use crate::algebraic_type::AlgebraicType;
2use crate::algebraic_type_ref::AlgebraicTypeRef;
3use crate::WithTypespace;
4use core::any::TypeId;
5use core::ops::{Index, IndexMut};
6use ethnum::{i256, u256};
7use smallvec::SmallVec;
8use std::rc::Rc;
9use std::sync::Arc;
10
11/// An error that occurs when attempting to resolve a type.
12#[derive(thiserror::Error, Debug, PartialOrd, Ord, PartialEq, Eq)]
13pub enum TypeRefError {
14 // TODO: ideally this should give some useful type name or path.
15 // Figure out if we can provide that even though it's not encoded in SATS.
16 #[error("Found recursive type reference {0}")]
17 RecursiveTypeRef(AlgebraicTypeRef),
18
19 #[error("Type reference {0} out of bounds")]
20 InvalidTypeRef(AlgebraicTypeRef),
21}
22
23/// A `Typespace` represents the typing context in SATS.
24///
25/// That is, this is the `Δ` or `Γ` you'll see in type theory literature.
26///
27/// We use (sort of) [deBrujin indices](https://en.wikipedia.org/wiki/De_Bruijn_index)
28/// to represent our type variables.
29/// Notably however, these are given for the entire module
30/// and there are no universal quantifiers (i.e., `Δ, α ⊢ τ | Δ ⊢ ∀ α. τ`)
31/// nor are there type lambdas (i.e., `Λτ. v`).
32/// See [System F], the second-order lambda calculus, for more on `∀` and `Λ`.
33///
34/// There are however recursive types in SATs,
35/// e.g., `&0 = { Cons({ v: U8, t: &0 }), Nil }` represents a basic cons list
36/// where `&0` is the type reference at index `0`.
37///
38/// [System F]: https://en.wikipedia.org/wiki/System_F
39#[derive(Clone, SpacetimeType)]
40#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
41#[sats(crate = crate)]
42pub struct Typespace {
43 /// The types in our typing context that can be referred to with [`AlgebraicTypeRef`]s.
44 pub types: Vec<AlgebraicType>,
45}
46
47impl std::fmt::Debug for Typespace {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 f.write_str("Typespace ")?;
50 f.debug_list().entries(&self.types).finish()
51 }
52}
53
54impl Default for Typespace {
55 fn default() -> Self {
56 Self::new(Vec::new())
57 }
58}
59
60impl Index<AlgebraicTypeRef> for Typespace {
61 type Output = AlgebraicType;
62
63 fn index(&self, index: AlgebraicTypeRef) -> &Self::Output {
64 &self.types[index.idx()]
65 }
66}
67impl IndexMut<AlgebraicTypeRef> for Typespace {
68 fn index_mut(&mut self, index: AlgebraicTypeRef) -> &mut Self::Output {
69 &mut self.types[index.idx()]
70 }
71}
72
73impl Typespace {
74 pub const EMPTY: &'static Typespace = &Self::new(Vec::new());
75
76 /// Returns a context ([`Typespace`]) with the given `types`.
77 pub const fn new(types: Vec<AlgebraicType>) -> Self {
78 Self { types }
79 }
80
81 /// Returns the [`AlgebraicType`] referred to by `r` within this context.
82 pub fn get(&self, r: AlgebraicTypeRef) -> Option<&AlgebraicType> {
83 self.types.get(r.idx())
84 }
85
86 /// Returns a mutable reference to the [`AlgebraicType`] referred to by `r` within this context.
87 pub fn get_mut(&mut self, r: AlgebraicTypeRef) -> Option<&mut AlgebraicType> {
88 self.types.get_mut(r.idx())
89 }
90
91 /// Inserts an `AlgebraicType` into the typespace
92 /// and returns an `AlgebraicTypeRef` that refers to the inserted `AlgebraicType`.
93 ///
94 /// This allows for self referential,
95 /// recursive or other complex types to be declared in the typespace.
96 ///
97 /// You can also use this to later change the meaning of the returned `AlgebraicTypeRef`
98 /// when you cannot provide the full definition of the type yet.
99 ///
100 /// Panics if the number of type references exceeds an `u32`.
101 pub fn add(&mut self, ty: AlgebraicType) -> AlgebraicTypeRef {
102 let index = self
103 .types
104 .len()
105 .try_into()
106 .expect("ran out of space for `AlgebraicTypeRef`s");
107
108 self.types.push(ty);
109 AlgebraicTypeRef(index)
110 }
111
112 /// Returns `ty` combined with the context `self`.
113 pub const fn with_type<'a, T: ?Sized>(&'a self, ty: &'a T) -> WithTypespace<'a, T> {
114 WithTypespace::new(self, ty)
115 }
116
117 /// Returns the `AlgebraicType` that `r` resolves to in the context of the `Typespace`.
118 ///
119 /// Panics if `r` is not known by the `Typespace`.
120 ///
121 /// Note, this is not recursive.
122 /// To resolve all nested refs, call `resolve_refs()` on the result.
123 pub fn resolve(&self, r: AlgebraicTypeRef) -> WithTypespace<'_, AlgebraicType> {
124 self.with_type(&self[r])
125 }
126
127 /// Inlines all type references in `ty` recursively using the current typeset.
128 pub fn inline_typerefs_in_type(&mut self, ty: &mut AlgebraicType) -> Result<(), TypeRefError> {
129 match ty {
130 AlgebraicType::Sum(sum_ty) => {
131 for variant in &mut *sum_ty.variants {
132 self.inline_typerefs_in_type(&mut variant.algebraic_type)?;
133 }
134 }
135 AlgebraicType::Product(product_ty) => {
136 for element in &mut *product_ty.elements {
137 self.inline_typerefs_in_type(&mut element.algebraic_type)?;
138 }
139 }
140 AlgebraicType::Array(array_ty) => {
141 self.inline_typerefs_in_type(&mut array_ty.elem_ty)?;
142 }
143 AlgebraicType::Ref(r) => {
144 // Lazily resolve any nested references first.
145 let resolved_ty = self.inline_typerefs_in_ref(*r)?;
146 // Now we can clone the fully-resolved type.
147 *ty = resolved_ty.clone();
148 }
149 _ => {}
150 }
151 Ok(())
152 }
153
154 /// Inlines all nested references behind the current [`AlgebraicTypeRef`] recursively using the current typeset.
155 ///
156 /// Returns the fully-resolved type or an error if the type reference is invalid or self-referential.
157 fn inline_typerefs_in_ref(&mut self, r: AlgebraicTypeRef) -> Result<&AlgebraicType, TypeRefError> {
158 let resolved_ty = match self.get_mut(r) {
159 None => return Err(TypeRefError::InvalidTypeRef(r)),
160 // If we encountered a type reference, that means one of the parent calls
161 // to `inline_typerefs_in_ref(r)` swapped its definition out,
162 // i.e. the type referred to by `r` is recursive.
163 // Note that it doesn't necessarily need to be the current call,
164 // e.g. A -> B -> A dependency also forms a recursive cycle.
165 // Our database can't handle recursive types, so return an error.
166 // TODO: support recursive types in the future.
167 Some(AlgebraicType::Ref(_)) => return Err(TypeRefError::RecursiveTypeRef(r)),
168 Some(resolved_ty) => resolved_ty,
169 };
170 // First, swap the type with a reference.
171 // This allows us to:
172 // 1. Recurse into each type mutably while holding a mutable
173 // reference to the typespace as well, without cloning.
174 // 2. Easily detect self-references at arbitrary depth without
175 // having to keep a separate `seen: HashSet<_>` or something.
176 let mut resolved_ty = std::mem::replace(resolved_ty, AlgebraicType::Ref(r));
177 // Next, recurse into the type and inline any nested type references.
178 self.inline_typerefs_in_type(&mut resolved_ty)?;
179 // Resolve the place again, since we couldn't hold the mutable reference across the call above.
180 let place = &mut self[r];
181 // Now we can put the fully-resolved type back and return that place.
182 *place = resolved_ty;
183 Ok(place)
184 }
185
186 /// Inlines all type references in the typespace recursively.
187 ///
188 /// Errors out if any type reference is invalid or self-referential.
189 pub fn inline_all_typerefs(&mut self) -> Result<(), TypeRefError> {
190 // We need to use indices here to allow mutable reference on each iteration.
191 for r in 0..self.types.len() as u32 {
192 self.inline_typerefs_in_ref(AlgebraicTypeRef(r))?;
193 }
194 Ok(())
195 }
196
197 /// Iterate over types in the typespace with their references.
198 pub fn refs_with_types(&self) -> impl Iterator<Item = (AlgebraicTypeRef, &AlgebraicType)> {
199 self.types
200 .iter()
201 .enumerate()
202 .map(|(idx, ty)| (AlgebraicTypeRef(idx as _), ty))
203 }
204
205 /// Check that the entire typespace is valid for generating a `SpacetimeDB` client module.
206 /// See also the `spacetimedb_schema` crate, which layers additional validation on top
207 /// of these checks.
208 ///
209 /// All types in the typespace must either satisfy
210 /// [`is_valid_for_client_type_definition`](AlgebraicType::is_valid_for_client_type_definition) or
211 /// [`is_valid_for_client_type_use`](AlgebraicType::is_valid_for_client_type_use).
212 /// (Only the types that are `valid_for_client_type_definition` will have types generated in
213 /// the client, but the other types are allowed for the convenience of module binding codegen.)
214 pub fn is_valid_for_client_code_generation(&self) -> bool {
215 self.types
216 .iter()
217 .all(|ty| ty.is_valid_for_client_type_definition() || ty.is_valid_for_client_type_use())
218 }
219}
220
221impl FromIterator<AlgebraicType> for Typespace {
222 fn from_iter<T: IntoIterator<Item = AlgebraicType>>(iter: T) -> Self {
223 Self {
224 types: iter.into_iter().collect(),
225 }
226 }
227}
228
229/// A trait for Rust types that can be represented as an [`AlgebraicType`]
230/// with an empty typing context.
231///
232/// The returned `AlgebraicType` must have no free variables,
233/// that is, no `AlgebraicTypeRef`s in its tree at all.
234pub trait GroundSpacetimeType {
235 /// Returns the `AlgebraicType` representation of `Self`.
236 fn get_type() -> AlgebraicType;
237}
238
239/// This trait makes types self-describing, allowing them to automatically register their structure
240/// with SpacetimeDB. This is used to tell SpacetimeDB about the structure of a module's tables and
241/// reducers.
242///
243/// Deriving this trait also derives [`Serialize`](crate::ser::Serialize), [`Deserialize`](crate::de::Deserialize),
244/// and [`Debug`](std::fmt::Debug). (There are currently no trait bounds on `SpacetimeType` documenting this fact.)
245/// `Serialize` and `Deserialize` are used to convert Rust data structures to other formats, suitable for storing on disk or passing over the network. `Debug` is simply for debugging convenience.
246///
247/// Any Rust type implementing `SpacetimeType` can be used as a table column or reducer argument. A derive macro is provided, and can be used on both structs and enums:
248///
249/// ```rust
250/// # use spacetimedb_sats::SpacetimeType;
251///
252/// #[derive(SpacetimeType)]
253/// # #[sats(crate = spacetimedb_sats)]
254/// struct Location {
255/// x: u64,
256/// y: u64
257/// }
258///
259/// #[derive(SpacetimeType)]
260/// # #[sats(crate = spacetimedb_sats)]
261/// struct PlasticCrate {
262/// count: u32,
263/// }
264///
265/// #[derive(SpacetimeType)]
266/// # #[sats(crate = spacetimedb_sats)]
267/// struct AppleCrate {
268/// variety: String,
269/// count: u32,
270/// freshness: u32,
271/// }
272///
273/// #[derive(SpacetimeType)]
274/// # #[sats(crate = spacetimedb_sats)]
275/// enum FruitCrate {
276/// Apples(AppleCrate),
277/// Plastic(PlasticCrate),
278/// }
279/// ```
280///
281/// The fields of the struct/enum must also implement `SpacetimeType`.
282///
283/// Any type annotated with `#[table(..)]` automatically derives `SpacetimeType`.
284///
285/// SpacetimeType is implemented for many of the primitive types in the standard library:
286///
287/// - `bool`
288/// - `u8`, `u16`, `u32`, `u64`, `u128`
289/// - `i8`, `i16`, `i32`, `i64`, `i128`
290/// - `f32`, `f64`
291///
292/// And common data structures:
293///
294/// - `String` and `&str`, utf-8 string data
295/// - `()`, the unit type
296/// - `Option<T> where T: SpacetimeType`
297/// - `Result<T, E> where T: SpacetimeType, E: SpacetimeType`
298/// - `Vec<T> where T: SpacetimeType`
299///
300/// (Storing collections in rows of a database table is a form of [denormalization](https://en.wikipedia.org/wiki/Denormalization).)
301///
302/// Do not manually implement this trait unless you are VERY sure you know what you're doing.
303/// Implementations must be consistent with `Deerialize<'de> for T`, `Serialize for T` and `Serialize, Deserialize for AlgebraicValue`.
304/// Implementations that are inconsistent across these traits may result in data loss.
305///
306/// N.B.: It's `SpacetimeType`, not `SpaceTimeType`.
307// TODO: we might want to have a note about what to do if you're trying to use a type from another crate in your table.
308// keep this note in sync with the ones on spacetimedb::rt::{ReducerArg, TableColumn}
309#[diagnostic::on_unimplemented(note = "if you own the type, try adding `#[derive(SpacetimeType)]` to its definition")]
310pub trait SpacetimeType {
311 /// Returns an `AlgebraicType` representing the type for `Self` in SATS
312 /// and in the typing context in `typespace`. This is used by the
313 /// automatic type registration system in Rust modules.
314 ///
315 /// The resulting `AlgebraicType` may contain `Ref`s that only make sense
316 /// within the context of this particular `typespace`.
317 fn make_type<S: TypespaceBuilder>(typespace: &mut S) -> AlgebraicType;
318}
319
320pub use spacetimedb_bindings_macro::SpacetimeType;
321
322/// A trait for types that can build a [`Typespace`].
323pub trait TypespaceBuilder {
324 /// Returns and adds a representation of type `T: 'static` as an [`AlgebraicType`]
325 /// with an optional `name` to the typing context in `self`.
326 fn add(
327 &mut self,
328 typeid: TypeId,
329 name: Option<&'static str>,
330 make_ty: impl FnOnce(&mut Self) -> AlgebraicType,
331 ) -> AlgebraicType;
332
333 fn add_type<T: SpacetimeType>(&mut self) -> AlgebraicType
334 where
335 Self: Sized,
336 {
337 T::make_type(self)
338 }
339}
340
341/// Implements [`SpacetimeType`] for a type in a simplified manner.
342///
343/// An example:
344/// ```ignore
345/// struct Foo<'a, T>(&'a T, u8);
346/// impl_st!(
347/// // Type parameters Impl type
348/// // v v
349/// // -------------------- ----------
350/// ['a, T: SpacetimeType] Foo<'a, T>,
351/// // The `make_type` implementation where `ts: impl TypespaceBuilder`
352/// // and the expression right of `=>` is an `AlgebraicType`.
353/// ts => AlgebraicType::product([T::make_type(ts), AlgebraicType::U8])
354/// );
355/// ```
356#[macro_export]
357macro_rules! impl_st {
358 ([ $($generic_wrapped:ident $($other_generics:tt)*)? ] $rty:ty, $stty:expr) => {
359 impl<$($generic_wrapped $($other_generics)*)?> $crate::GroundSpacetimeType for $rty
360 $(where $generic_wrapped: $crate::GroundSpacetimeType)?
361 {
362 fn get_type() -> $crate::AlgebraicType {
363 $stty
364 }
365 }
366
367 impl_st!([ $($generic $($other_generics)*)? ] $rty, _ts => $stty);
368 };
369 ([ $($generic_wrapped:ident $($other_generics:tt)*)? ] $rty:ty, $ts:ident => $stty:expr) => {
370 impl<$($generic_wrapped $($other_generics)*)?> $crate::SpacetimeType for $rty
371 $(where $generic_wrapped: $crate::SpacetimeType)?
372 {
373 fn make_type<S: $crate::typespace::TypespaceBuilder>($ts: &mut S) -> $crate::AlgebraicType {
374 $stty
375 }
376 }
377 };
378}
379
380macro_rules! impl_primitives {
381 ($($t:ty => $x:ident,)*) => {
382 $(impl_st!([] $t, AlgebraicType::$x);)*
383 };
384}
385
386impl_primitives! {
387 bool => Bool,
388 u8 => U8,
389 i8 => I8,
390 u16 => U16,
391 i16 => I16,
392 u32 => U32,
393 i32 => I32,
394 u64 => U64,
395 i64 => I64,
396 u128 => U128,
397 i128 => I128,
398 u256 => U256,
399 i256 => I256,
400 f32 => F32,
401 f64 => F64,
402 String => String,
403}
404
405impl_st!([](), AlgebraicType::unit());
406impl_st!([] str, AlgebraicType::String);
407impl_st!([T] [T], ts => AlgebraicType::array(T::make_type(ts)));
408impl_st!([T: ?Sized] &T, ts => T::make_type(ts));
409impl_st!([T: ?Sized] Box<T>, ts => T::make_type(ts));
410impl_st!([T: ?Sized] Rc<T>, ts => T::make_type(ts));
411impl_st!([T: ?Sized] Arc<T>, ts => T::make_type(ts));
412impl_st!([T] Vec<T>, ts => <[T]>::make_type(ts));
413impl_st!([T, const N: usize] SmallVec<[T; N]>, ts => <[T]>::make_type(ts));
414impl_st!([T] Option<T>, ts => AlgebraicType::option(T::make_type(ts)));
415
416impl_st!([] spacetimedb_primitives::ArgId, AlgebraicType::U64);
417impl_st!([] spacetimedb_primitives::ColId, AlgebraicType::U16);
418impl_st!([] spacetimedb_primitives::TableId, AlgebraicType::U32);
419impl_st!([] spacetimedb_primitives::ViewId, AlgebraicType::U32);
420impl_st!([] spacetimedb_primitives::IndexId, AlgebraicType::U32);
421impl_st!([] spacetimedb_primitives::SequenceId, AlgebraicType::U32);
422impl_st!([] spacetimedb_primitives::ConstraintId, AlgebraicType::U32);
423impl_st!([] spacetimedb_primitives::ScheduleId, AlgebraicType::U32);
424
425impl_st!([] spacetimedb_primitives::ColList, ts => AlgebraicType::array(spacetimedb_primitives::ColId::make_type(ts)));
426impl_st!([] spacetimedb_primitives::ColSet, ts => AlgebraicType::array(spacetimedb_primitives::ColId::make_type(ts)));
427
428impl_st!([] bytes::Bytes, AlgebraicType::bytes());
429
430#[cfg(feature = "bytestring")]
431impl_st!([] bytestring::ByteString, AlgebraicType::String);
432
433impl<T, E> SpacetimeType for Result<T, E>
434where
435 T: SpacetimeType,
436 E: SpacetimeType,
437{
438 fn make_type<S: TypespaceBuilder>(typespace: &mut S) -> AlgebraicType {
439 AlgebraicType::result(T::make_type(typespace), E::make_type(typespace))
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use crate::proptest::generate_typespace_valid_for_codegen;
446 use proptest::prelude::*;
447
448 use super::*;
449
450 proptest! {
451 #![proptest_config(ProptestConfig::with_cases(512))]
452 #[test]
453 fn is_valid_for_client_code_generation(typespace in generate_typespace_valid_for_codegen(5)) {
454 prop_assert!(typespace.is_valid_for_client_code_generation());
455 }
456 }
457
458 #[test]
459 fn is_not_valid_for_client_code_generation() {
460 let bad_inner_1 = AlgebraicType::sum([("red", AlgebraicType::U8), ("green", AlgebraicType::U8)]);
461 let bad_inner_2 = AlgebraicType::product([("red", AlgebraicType::U8), ("green", AlgebraicType::U8)]);
462
463 fn assert_not_valid(ty: AlgebraicType) {
464 let typespace = Typespace::new(vec![ty.clone()]);
465 assert!(!typespace.is_valid_for_client_code_generation(), "{ty:?}");
466 }
467 assert_not_valid(AlgebraicType::product([AlgebraicType::U8, bad_inner_1.clone()]));
468 assert_not_valid(AlgebraicType::product([AlgebraicType::U8, bad_inner_2.clone()]));
469
470 assert_not_valid(AlgebraicType::sum([AlgebraicType::U8, bad_inner_1.clone()]));
471 assert_not_valid(AlgebraicType::sum([AlgebraicType::U8, bad_inner_2.clone()]));
472
473 assert_not_valid(AlgebraicType::array(bad_inner_1.clone()));
474 assert_not_valid(AlgebraicType::array(bad_inner_2.clone()));
475
476 assert_not_valid(AlgebraicType::option(bad_inner_1.clone()));
477 assert_not_valid(AlgebraicType::option(bad_inner_2.clone()));
478
479 assert_not_valid(AlgebraicType::option(AlgebraicType::array(AlgebraicType::option(
480 bad_inner_1.clone(),
481 ))));
482
483 assert_not_valid(AlgebraicType::result(bad_inner_1.clone(), AlgebraicType::U8));
484 assert_not_valid(AlgebraicType::result(AlgebraicType::U8, bad_inner_2.clone()));
485
486 assert_not_valid(AlgebraicType::result(
487 AlgebraicType::array(AlgebraicType::result(bad_inner_1.clone(), AlgebraicType::U8)),
488 AlgebraicType::U8,
489 ));
490
491 assert_not_valid(AlgebraicType::result(
492 AlgebraicType::U8,
493 AlgebraicType::array(AlgebraicType::result(AlgebraicType::U8, bad_inner_2.clone())),
494 ));
495 }
496}