use crate::algebraic_type::AlgebraicType;
use crate::algebraic_type_ref::AlgebraicTypeRef;
use crate::WithTypespace;
use core::any::TypeId;
use core::ops::{Index, IndexMut};
use ethnum::{i256, u256};
use smallvec::SmallVec;
use std::rc::Rc;
use std::sync::Arc;
#[derive(thiserror::Error, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub enum TypeRefError {
#[error("Found recursive type reference {0}")]
RecursiveTypeRef(AlgebraicTypeRef),
#[error("Type reference {0} out of bounds")]
InvalidTypeRef(AlgebraicTypeRef),
}
#[derive(Clone, SpacetimeType)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
#[sats(crate = crate)]
pub struct Typespace {
pub types: Vec<AlgebraicType>,
}
impl std::fmt::Debug for Typespace {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Typespace ")?;
f.debug_list().entries(&self.types).finish()
}
}
impl Default for Typespace {
fn default() -> Self {
Self::new(Vec::new())
}
}
impl Index<AlgebraicTypeRef> for Typespace {
type Output = AlgebraicType;
fn index(&self, index: AlgebraicTypeRef) -> &Self::Output {
&self.types[index.idx()]
}
}
impl IndexMut<AlgebraicTypeRef> for Typespace {
fn index_mut(&mut self, index: AlgebraicTypeRef) -> &mut Self::Output {
&mut self.types[index.idx()]
}
}
impl Typespace {
pub const EMPTY: &'static Typespace = &Self::new(Vec::new());
pub const fn new(types: Vec<AlgebraicType>) -> Self {
Self { types }
}
pub fn get(&self, r: AlgebraicTypeRef) -> Option<&AlgebraicType> {
self.types.get(r.idx())
}
pub fn get_mut(&mut self, r: AlgebraicTypeRef) -> Option<&mut AlgebraicType> {
self.types.get_mut(r.idx())
}
pub fn add(&mut self, ty: AlgebraicType) -> AlgebraicTypeRef {
let index = self
.types
.len()
.try_into()
.expect("ran out of space for `AlgebraicTypeRef`s");
self.types.push(ty);
AlgebraicTypeRef(index)
}
pub const fn with_type<'a, T: ?Sized>(&'a self, ty: &'a T) -> WithTypespace<'a, T> {
WithTypespace::new(self, ty)
}
pub fn resolve(&self, r: AlgebraicTypeRef) -> WithTypespace<'_, AlgebraicType> {
self.with_type(&self[r])
}
pub fn inline_typerefs_in_type(&mut self, ty: &mut AlgebraicType) -> Result<(), TypeRefError> {
match ty {
AlgebraicType::Sum(sum_ty) => {
for variant in &mut *sum_ty.variants {
self.inline_typerefs_in_type(&mut variant.algebraic_type)?;
}
}
AlgebraicType::Product(product_ty) => {
for element in &mut *product_ty.elements {
self.inline_typerefs_in_type(&mut element.algebraic_type)?;
}
}
AlgebraicType::Array(array_ty) => {
self.inline_typerefs_in_type(&mut array_ty.elem_ty)?;
}
AlgebraicType::Ref(r) => {
let resolved_ty = self.inline_typerefs_in_ref(*r)?;
*ty = resolved_ty.clone();
}
_ => {}
}
Ok(())
}
fn inline_typerefs_in_ref(&mut self, r: AlgebraicTypeRef) -> Result<&AlgebraicType, TypeRefError> {
let resolved_ty = match self.get_mut(r) {
None => return Err(TypeRefError::InvalidTypeRef(r)),
Some(AlgebraicType::Ref(_)) => return Err(TypeRefError::RecursiveTypeRef(r)),
Some(resolved_ty) => resolved_ty,
};
let mut resolved_ty = std::mem::replace(resolved_ty, AlgebraicType::Ref(r));
self.inline_typerefs_in_type(&mut resolved_ty)?;
let place = &mut self[r];
*place = resolved_ty;
Ok(place)
}
pub fn inline_all_typerefs(&mut self) -> Result<(), TypeRefError> {
for r in 0..self.types.len() as u32 {
self.inline_typerefs_in_ref(AlgebraicTypeRef(r))?;
}
Ok(())
}
pub fn refs_with_types(&self) -> impl Iterator<Item = (AlgebraicTypeRef, &AlgebraicType)> {
self.types
.iter()
.enumerate()
.map(|(idx, ty)| (AlgebraicTypeRef(idx as _), ty))
}
pub fn is_valid_for_client_code_generation(&self) -> bool {
self.types
.iter()
.all(|ty| ty.is_valid_for_client_type_definition() || ty.is_valid_for_client_type_use())
}
}
impl FromIterator<AlgebraicType> for Typespace {
fn from_iter<T: IntoIterator<Item = AlgebraicType>>(iter: T) -> Self {
Self {
types: iter.into_iter().collect(),
}
}
}
pub trait GroundSpacetimeType {
fn get_type() -> AlgebraicType;
}
#[diagnostic::on_unimplemented(note = "if you own the type, try adding `#[derive(SpacetimeType)]` to its definition")]
pub trait SpacetimeType {
fn make_type<S: TypespaceBuilder>(typespace: &mut S) -> AlgebraicType;
}
pub use spacetimedb_bindings_macro::SpacetimeType;
pub trait TypespaceBuilder {
fn add(
&mut self,
typeid: TypeId,
name: Option<&'static str>,
make_ty: impl FnOnce(&mut Self) -> AlgebraicType,
) -> AlgebraicType;
fn add_type<T: SpacetimeType>(&mut self) -> AlgebraicType
where
Self: Sized,
{
T::make_type(self)
}
}
#[macro_export]
macro_rules! impl_st {
([ $($generic_wrapped:ident $($other_generics:tt)*)? ] $rty:ty, $stty:expr) => {
impl<$($generic_wrapped $($other_generics)*)?> $crate::GroundSpacetimeType for $rty
$(where $generic_wrapped: $crate::GroundSpacetimeType)?
{
fn get_type() -> $crate::AlgebraicType {
$stty
}
}
impl_st!([ $($generic $($other_generics)*)? ] $rty, _ts => $stty);
};
([ $($generic_wrapped:ident $($other_generics:tt)*)? ] $rty:ty, $ts:ident => $stty:expr) => {
impl<$($generic_wrapped $($other_generics)*)?> $crate::SpacetimeType for $rty
$(where $generic_wrapped: $crate::SpacetimeType)?
{
fn make_type<S: $crate::typespace::TypespaceBuilder>($ts: &mut S) -> $crate::AlgebraicType {
$stty
}
}
};
}
macro_rules! impl_primitives {
($($t:ty => $x:ident,)*) => {
$(impl_st!([] $t, AlgebraicType::$x);)*
};
}
impl_primitives! {
bool => Bool,
u8 => U8,
i8 => I8,
u16 => U16,
i16 => I16,
u32 => U32,
i32 => I32,
u64 => U64,
i64 => I64,
u128 => U128,
i128 => I128,
u256 => U256,
i256 => I256,
f32 => F32,
f64 => F64,
String => String,
}
impl_st!([](), AlgebraicType::unit());
impl_st!([] str, AlgebraicType::String);
impl_st!([T] [T], ts => AlgebraicType::array(T::make_type(ts)));
impl_st!([T: ?Sized] &T, ts => T::make_type(ts));
impl_st!([T: ?Sized] Box<T>, ts => T::make_type(ts));
impl_st!([T: ?Sized] Rc<T>, ts => T::make_type(ts));
impl_st!([T: ?Sized] Arc<T>, ts => T::make_type(ts));
impl_st!([T] Vec<T>, ts => <[T]>::make_type(ts));
impl_st!([T, const N: usize] SmallVec<[T; N]>, ts => <[T]>::make_type(ts));
impl_st!([T] Option<T>, ts => AlgebraicType::option(T::make_type(ts)));
impl_st!([] spacetimedb_primitives::ArgId, AlgebraicType::U64);
impl_st!([] spacetimedb_primitives::ColId, AlgebraicType::U16);
impl_st!([] spacetimedb_primitives::TableId, AlgebraicType::U32);
impl_st!([] spacetimedb_primitives::ViewId, AlgebraicType::U32);
impl_st!([] spacetimedb_primitives::IndexId, AlgebraicType::U32);
impl_st!([] spacetimedb_primitives::SequenceId, AlgebraicType::U32);
impl_st!([] spacetimedb_primitives::ConstraintId, AlgebraicType::U32);
impl_st!([] spacetimedb_primitives::ScheduleId, AlgebraicType::U32);
impl_st!([] spacetimedb_primitives::ColList, ts => AlgebraicType::array(spacetimedb_primitives::ColId::make_type(ts)));
impl_st!([] spacetimedb_primitives::ColSet, ts => AlgebraicType::array(spacetimedb_primitives::ColId::make_type(ts)));
impl_st!([] bytes::Bytes, AlgebraicType::bytes());
#[cfg(feature = "bytestring")]
impl_st!([] bytestring::ByteString, AlgebraicType::String);
impl<T, E> SpacetimeType for Result<T, E>
where
T: SpacetimeType,
E: SpacetimeType,
{
fn make_type<S: TypespaceBuilder>(typespace: &mut S) -> AlgebraicType {
AlgebraicType::result(T::make_type(typespace), E::make_type(typespace))
}
}
#[cfg(test)]
mod tests {
use crate::proptest::generate_typespace_valid_for_codegen;
use proptest::prelude::*;
use super::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(512))]
#[test]
fn is_valid_for_client_code_generation(typespace in generate_typespace_valid_for_codegen(5)) {
prop_assert!(typespace.is_valid_for_client_code_generation());
}
}
#[test]
fn is_not_valid_for_client_code_generation() {
let bad_inner_1 = AlgebraicType::sum([("red", AlgebraicType::U8), ("green", AlgebraicType::U8)]);
let bad_inner_2 = AlgebraicType::product([("red", AlgebraicType::U8), ("green", AlgebraicType::U8)]);
fn assert_not_valid(ty: AlgebraicType) {
let typespace = Typespace::new(vec![ty.clone()]);
assert!(!typespace.is_valid_for_client_code_generation(), "{ty:?}");
}
assert_not_valid(AlgebraicType::product([AlgebraicType::U8, bad_inner_1.clone()]));
assert_not_valid(AlgebraicType::product([AlgebraicType::U8, bad_inner_2.clone()]));
assert_not_valid(AlgebraicType::sum([AlgebraicType::U8, bad_inner_1.clone()]));
assert_not_valid(AlgebraicType::sum([AlgebraicType::U8, bad_inner_2.clone()]));
assert_not_valid(AlgebraicType::array(bad_inner_1.clone()));
assert_not_valid(AlgebraicType::array(bad_inner_2.clone()));
assert_not_valid(AlgebraicType::option(bad_inner_1.clone()));
assert_not_valid(AlgebraicType::option(bad_inner_2.clone()));
assert_not_valid(AlgebraicType::option(AlgebraicType::array(AlgebraicType::option(
bad_inner_1.clone(),
))));
assert_not_valid(AlgebraicType::result(bad_inner_1.clone(), AlgebraicType::U8));
assert_not_valid(AlgebraicType::result(AlgebraicType::U8, bad_inner_2.clone()));
assert_not_valid(AlgebraicType::result(
AlgebraicType::array(AlgebraicType::result(bad_inner_1.clone(), AlgebraicType::U8)),
AlgebraicType::U8,
));
assert_not_valid(AlgebraicType::result(
AlgebraicType::U8,
AlgebraicType::array(AlgebraicType::result(AlgebraicType::U8, bad_inner_2.clone())),
));
}
}