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