From f6d2e774e4c1675ec8ada40a27a110c1700b3fc8 Mon Sep 17 00:00:00 2001 From: Noa Date: Thu, 5 Sep 2024 12:02:39 -0500 Subject: [PATCH] Switch Rust codegen to use new ModuleDef --- crates/cli/examples/regen-csharp-moduledef.rs | 6 +- crates/cli/src/subcommands/call.rs | 138 +++- crates/cli/src/subcommands/generate/mod.rs | 217 +++--- crates/cli/src/subcommands/generate/rust.rs | 644 ++++++++---------- .../snapshots/codegen__codegen_rust.snap | 42 +- .../examples/get_ws_schema.rs | 3 +- crates/schema/src/def/validate/v8.rs | 19 +- crates/schema/src/type_for_generate.rs | 22 +- crates/schema/tests/validate_integration.rs | 7 +- 9 files changed, 582 insertions(+), 516 deletions(-) diff --git a/crates/cli/examples/regen-csharp-moduledef.rs b/crates/cli/examples/regen-csharp-moduledef.rs index e52a055b51f..19becad395e 100644 --- a/crates/cli/examples/regen-csharp-moduledef.rs +++ b/crates/cli/examples/regen-csharp-moduledef.rs @@ -21,7 +21,11 @@ fn main() -> anyhow::Result<()> { module.add_type::(); }); - let mut results = generate(module, Language::Csharp, "SpacetimeDB.Internal")?; + let mut results = generate( + RawModuleDef::V8BackCompat(module), + Language::Csharp, + "SpacetimeDB.Internal", + )?; // Someday we might replace custom BSATN types with autogenerated ones as well, // but for now they're not very large and our copies are somewhat more optimised. diff --git a/crates/cli/src/subcommands/call.rs b/crates/cli/src/subcommands/call.rs index d85f493f363..744948bff90 100644 --- a/crates/cli/src/subcommands/call.rs +++ b/crates/cli/src/subcommands/call.rs @@ -1,7 +1,6 @@ use crate::common_args; use crate::config::Config; use crate::edit_distance::{edit_distance, find_best_match_for_name}; -use crate::generate::rust::{write_arglist_no_delimiters, write_type}; use crate::util; use crate::util::{add_auth_header_opt, database_address, get_auth_header_only}; use anyhow::{bail, Context, Error}; @@ -191,10 +190,10 @@ fn reducer_signature(schema_json: Value, reducer_name: &str) -> Option { fn ctx(typespace: &Typespace, r: AlgebraicTypeRef) -> String { let ty = &typespace[r]; let mut ty_str = String::new(); - write_type(&|r| ctx(typespace, r), &mut ty_str, ty).unwrap(); + write_type::write_type(&|r| ctx(typespace, r), &mut ty_str, ty).unwrap(); ty_str } - write_arglist_no_delimiters(&|r| ctx(&typespace, r), &mut args, ¶ms, None).unwrap(); + write_type::write_arglist_no_delimiters(&|r| ctx(&typespace, r), &mut args, ¶ms, None).unwrap(); let args = args.trim().trim_end_matches(',').replace('\n', " "); // Print the full signature to `reducer_fmt`. @@ -322,3 +321,136 @@ fn typespace(value: &serde_json::Value) -> Option { let types = value.as_object()?.get("typespace")?; deserialize_from(types).map(Typespace::new).ok() } + +// this is an old version of code in generate::rust that got +// refactored, but reducer_signature() was using it +// TODO: port reducer_signature() to use AlgebraicTypeUse et al, somehow. +mod write_type { + use super::*; + use convert_case::{Case, Casing}; + use spacetimedb_lib::sats::ArrayType; + use spacetimedb_lib::ProductType; + use std::fmt; + use std::ops::Deref; + + pub fn write_type( + ctx: &impl Fn(AlgebraicTypeRef) -> String, + out: &mut W, + ty: &AlgebraicType, + ) -> fmt::Result { + match ty { + p if p.is_identity() => write!(out, "Identity")?, + p if p.is_address() => write!(out, "Address")?, + p if p.is_schedule_at() => write!(out, "ScheduleAt")?, + AlgebraicType::Sum(sum_type) => { + if let Some(inner_ty) = sum_type.as_option() { + write!(out, "Option<")?; + write_type(ctx, out, inner_ty)?; + write!(out, ">")?; + } else { + write!(out, "enum ")?; + print_comma_sep_braced(out, &sum_type.variants, |out: &mut W, elem: &_| { + if let Some(name) = &elem.name { + write!(out, "{name}: ")?; + } + write_type(ctx, out, &elem.algebraic_type) + })?; + } + } + AlgebraicType::Product(ProductType { elements }) => { + print_comma_sep_braced(out, elements, |out: &mut W, elem: &ProductTypeElement| { + if let Some(name) = &elem.name { + write!(out, "{name}: ")?; + } + write_type(ctx, out, &elem.algebraic_type) + })?; + } + AlgebraicType::Bool => write!(out, "bool")?, + AlgebraicType::I8 => write!(out, "i8")?, + AlgebraicType::U8 => write!(out, "u8")?, + AlgebraicType::I16 => write!(out, "i16")?, + AlgebraicType::U16 => write!(out, "u16")?, + AlgebraicType::I32 => write!(out, "i32")?, + AlgebraicType::U32 => write!(out, "u32")?, + AlgebraicType::I64 => write!(out, "i64")?, + AlgebraicType::U64 => write!(out, "u64")?, + AlgebraicType::I128 => write!(out, "i128")?, + AlgebraicType::U128 => write!(out, "u128")?, + AlgebraicType::I256 => write!(out, "i256")?, + AlgebraicType::U256 => write!(out, "u256")?, + AlgebraicType::F32 => write!(out, "f32")?, + AlgebraicType::F64 => write!(out, "f64")?, + AlgebraicType::String => write!(out, "String")?, + AlgebraicType::Array(ArrayType { elem_ty }) => { + write!(out, "Vec<")?; + write_type(ctx, out, elem_ty)?; + write!(out, ">")?; + } + AlgebraicType::Map(ty) => { + write!(out, "Map<")?; + write_type(ctx, out, &ty.key_ty)?; + write!(out, ", ")?; + write_type(ctx, out, &ty.ty)?; + write!(out, ">")?; + } + AlgebraicType::Ref(r) => { + write!(out, "{}", ctx(*r))?; + } + } + Ok(()) + } + + fn print_comma_sep_braced( + out: &mut W, + elems: &[T], + on: impl Fn(&mut W, &T) -> fmt::Result, + ) -> fmt::Result { + write!(out, "{{")?; + + let mut iter = elems.iter(); + + // First factor. + if let Some(elem) = iter.next() { + write!(out, " ")?; + on(out, elem)?; + } + // Other factors. + for elem in iter { + write!(out, ", ")?; + on(out, elem)?; + } + + if !elems.is_empty() { + write!(out, " ")?; + } + + write!(out, "}}")?; + + Ok(()) + } + + pub fn write_arglist_no_delimiters( + ctx: &impl Fn(AlgebraicTypeRef) -> String, + out: &mut impl Write, + elements: &[ProductTypeElement], + + // Written before each line. Useful for `pub`. + prefix: Option<&str>, + ) -> fmt::Result { + for elt in elements { + if let Some(prefix) = prefix { + write!(out, "{prefix} ")?; + } + + let Some(name) = &elt.name else { + panic!("Product type element has no name: {elt:?}"); + }; + let name = name.deref().to_case(Case::Snake); + + write!(out, "{name}: ")?; + write_type(ctx, out, &elt.algebraic_type)?; + writeln!(out, ",")?; + } + Ok(()) + } +} diff --git a/crates/cli/src/subcommands/generate/mod.rs b/crates/cli/src/subcommands/generate/mod.rs index 0215984c376..5d0b09a4286 100644 --- a/crates/cli/src/subcommands/generate/mod.rs +++ b/crates/cli/src/subcommands/generate/mod.rs @@ -5,13 +5,18 @@ use clap::ArgAction::SetTrue; use convert_case::{Case, Casing}; use core::mem; use duct::cmd; +use itertools::Itertools; use spacetimedb::host::wasmtime::{Mem, MemView, WasmPointee as _}; -use spacetimedb_lib::db::raw_def::RawColumnDefV8; +use spacetimedb_data_structures::map::HashSet; use spacetimedb_lib::de::serde::DeserializeWrapper; -use spacetimedb_lib::sats::{AlgebraicType, Typespace}; -use spacetimedb_lib::{bsatn, MiscModuleExport, RawModuleDefV8, ReducerDef, TableDesc, TypeAlias}; +use spacetimedb_lib::sats::{AlgebraicType, AlgebraicTypeRef, Typespace}; +use spacetimedb_lib::{bsatn, RawModuleDefV8, TableDesc, TypeAlias}; use spacetimedb_lib::{RawModuleDef, MODULE_ABI_MAJOR_VERSION}; use spacetimedb_primitives::errno; +use spacetimedb_schema; +use spacetimedb_schema::def::{ModuleDef, ReducerDef, ScopedTypeName, TableDef, TypeDef}; +use spacetimedb_schema::identifier::Identifier; +use spacetimedb_schema::schema::TableSchema; use std::fs; use std::io::Write; use std::ops::Deref; @@ -240,114 +245,132 @@ pub struct GenCtx { names: Vec>, } -pub fn generate(module: RawModuleDefV8, lang: Language, namespace: &str) -> anyhow::Result> { - let (ctx, items) = extract_from_moduledef(module); - let items: Vec = items.collect(); - let mut files: Vec<(String, String)> = items - .iter() - .filter_map(|item| item.generate(&ctx, lang, namespace)) - .collect(); - files.extend(generate_globals(&ctx, lang, namespace, &items)); - - Ok(files) +pub fn generate(module: RawModuleDef, lang: Language, namespace: &str) -> anyhow::Result> { + let module = match module { + RawModuleDef::V8BackCompat(def) => spacetimedb_schema::def::validate::v8::validate(def)?, + RawModuleDef::V9(def) => spacetimedb_schema::def::validate::v9::validate(def)?, + _ => unimplemented!(), + }; + Ok(match lang { + Language::Rust => generate_lang(&module, rust::Rust, namespace), + Language::Csharp | Language::TypeScript => { + let ctx = GenCtx { + typespace: module.typespace().clone(), + names: (0..module.typespace().types.len()) + .map(|r| { + module + .type_def_from_ref(AlgebraicTypeRef(r as _)) + .map(|(name, _)| name.name_segments().join(".")) + }) + .collect(), + }; + + let tableset = module.tables().map(|t| t.product_type_ref).collect::>(); + let tables = module + .tables() + .map(|table| TableDesc { + schema: TableSchema::from_module_def(table, 0.into()).into(), + data: table.product_type_ref, + }) + .sorted_by(|a, b| a.schema.table_name.cmp(&b.schema.table_name)); + + // HACK: Patch the fields to have the types that point to `AlgebraicTypeRef` because all generators depend on that + // `register_table` in rt.rs resolve the types early, but the generators do it late. This impact enums where + // the enum name is not preserved in the `AlgebraicType`. + // x.schema.columns = + // RawColumnDefV8::from_product_type(typespace[x.data].as_product().unwrap().clone()); + + let types = module.types().filter(|typ| !tableset.contains(&typ.ty)).map(|typ| { + GenItem::TypeAlias(TypeAlias { + name: typ.name.name_segments().join("."), + ty: typ.ty, + }) + }); + + let reducers = module + .reducers() + .map(|reducer| spacetimedb_lib::ReducerDef { + name: reducer.name.clone().into(), + args: reducer.params.elements.to_vec(), + }) + .sorted_by(|a, b| a.name.cmp(&b.name)); + + let items = itertools::chain!( + types, + tables.into_iter().map(GenItem::Table), + reducers + .filter(|r| !(r.name.starts_with("__") && r.name.ends_with("__"))) + .map(GenItem::Reducer), + ); + + let items: Vec = items.collect(); + let mut files: Vec<(String, String)> = items + .iter() + .filter_map(|item| item.generate(&ctx, lang, namespace)) + .collect(); + files.extend(generate_globals(&ctx, lang, namespace, &items)); + files + } + }) } -fn generate_globals(ctx: &GenCtx, lang: Language, namespace: &str, items: &[GenItem]) -> Vec<(String, String)> { - match lang { - Language::Csharp => csharp::autogen_csharp_globals(ctx, items, namespace), - Language::TypeScript => typescript::autogen_typescript_globals(ctx, items), - Language::Rust => rust::autogen_rust_globals(ctx, items), - } +fn generate_lang(module: &ModuleDef, lang: impl Lang, namespace: &str) -> Vec<(String, String)> { + let table_refs = module.tables().map(|tbl| tbl.product_type_ref).collect::>(); + itertools::chain!( + module.tables().map(|tbl| { + ( + lang.table_filename(module, tbl), + lang.generate_table(module, namespace, tbl), + ) + }), + module.types().filter(|typ| !table_refs.contains(&typ.ty)).map(|typ| { + ( + lang.type_filename(&typ.name), + lang.generate_type(module, namespace, typ), + ) + }), + module.reducers().filter(|r| r.lifecycle.is_none()).map(|reducer| { + ( + lang.reducer_filename(&reducer.name), + lang.generate_reducer(module, namespace, reducer), + ) + }), + lang.generate_globals(module, namespace), + ) + .collect() } -pub fn extract_from_moduledef(module: RawModuleDefV8) -> (GenCtx, impl Iterator) { - let RawModuleDefV8 { - typespace, - tables, - reducers, - misc_exports, - } = module; - // HACK: Patch the fields to have the types that point to `AlgebraicTypeRef` because all generators depend on that - // `register_table` in rt.rs resolve the types early, but the generators do it late. This impact enums where - // the enum name is not preserved in the `AlgebraicType`. - let tables: Vec<_> = tables - .into_iter() - .map(|mut x| { - x.schema.columns = RawColumnDefV8::from_product_type(typespace[x.data].as_product().unwrap().clone()); - x - }) - .collect(); - let tableset = tables - .iter() - .map(|t| t.data) - .collect::>(); - - let mut names = vec![None; typespace.types.len()]; - let name_info = itertools::chain!( - tables.iter().map(|t| (t.data, &*t.schema.table_name)), - misc_exports - .iter() - .map(|MiscModuleExport::TypeAlias(a)| (a.ty, &*a.name)), - ); - for (typeref, name) in name_info { - names[typeref.idx()] = Some(name.into()) - } - let ctx = GenCtx { typespace, names }; - let iter = itertools::chain!( - misc_exports - .into_iter() - .filter(move |MiscModuleExport::TypeAlias(a)| !tableset.contains(&a.ty)) - .map(GenItem::from_misc_export), - tables.into_iter().map(GenItem::Table), - reducers - .into_iter() - .filter(|r| !(r.name.starts_with("__") && r.name.ends_with("__"))) - .map(GenItem::Reducer), - ); - (ctx, iter) +trait Lang { + fn table_filename(&self, module: &ModuleDef, table: &TableDef) -> String; + fn type_filename(&self, type_name: &ScopedTypeName) -> String; + fn reducer_filename(&self, reducer_name: &Identifier) -> String; + + fn generate_table(&self, module: &ModuleDef, namespace: &str, tbl: &TableDef) -> String; + fn generate_type(&self, module: &ModuleDef, namespace: &str, typ: &TypeDef) -> String; + fn generate_reducer(&self, module: &ModuleDef, namespace: &str, reducer: &ReducerDef) -> String; + fn generate_globals(&self, module: &ModuleDef, namespace: &str) -> Vec<(String, String)>; } pub enum GenItem { Table(TableDesc), TypeAlias(TypeAlias), - Reducer(ReducerDef), + Reducer(spacetimedb_lib::ReducerDef), } -impl GenItem { - fn from_misc_export(exp: MiscModuleExport) -> Self { - match exp { - MiscModuleExport::TypeAlias(a) => Self::TypeAlias(a), - } +fn generate_globals(ctx: &GenCtx, lang: Language, namespace: &str, items: &[GenItem]) -> Vec<(String, String)> { + match lang { + Language::Csharp => csharp::autogen_csharp_globals(ctx, items, namespace), + Language::TypeScript => typescript::autogen_typescript_globals(ctx, items), + Language::Rust => unreachable!(), } +} +impl GenItem { fn generate(&self, ctx: &GenCtx, lang: Language, namespace: &str) -> Option<(String, String)> { match lang { Language::Csharp => self.generate_csharp(ctx, namespace), Language::TypeScript => self.generate_typescript(ctx), - Language::Rust => self.generate_rust(ctx), - } - } - - fn generate_rust(&self, ctx: &GenCtx) -> Option<(String, String)> { - match self { - GenItem::Table(table) => { - let code = rust::autogen_rust_table(ctx, table); - // TODO: this is not ideal (should use table name, not row type name) - let tyname = ctx.names[table.data.idx()].as_ref().unwrap(); - Some((rust::rust_type_file_name(tyname), code)) - } - GenItem::TypeAlias(TypeAlias { name, ty }) => { - let code = match &ctx.typespace[*ty] { - AlgebraicType::Sum(sum) => rust::autogen_rust_sum(ctx, name, sum), - AlgebraicType::Product(prod) => rust::autogen_rust_tuple(ctx, name, prod), - _ => todo!(), - }; - Some((rust::rust_type_file_name(name), code)) - } - GenItem::Reducer(reducer) => { - let code = rust::autogen_rust_reducer(ctx, reducer); - Some((rust::rust_reducer_file_name(&reducer.name), code)) - } + Language::Rust => unreachable!(), } } @@ -407,7 +430,7 @@ impl GenItem { } } -pub fn extract_descriptions(wasm_file: &Path) -> anyhow::Result { +pub fn extract_descriptions(wasm_file: &Path) -> anyhow::Result { let engine = wasmtime::Engine::default(); let t = std::time::Instant::now(); let module = wasmtime::Module::from_file(&engine, wasm_file)?; @@ -460,10 +483,6 @@ pub fn extract_descriptions(wasm_file: &Path) -> anyhow::Result // TODO: shouldn't we return an error here? None => RawModuleDef::V8BackCompat(RawModuleDefV8::default()), }; - let module = match module { - RawModuleDef::V8BackCompat(v8) => v8, - _ => anyhow::bail!("Unimplemented module definition version"), - }; Ok(module) } diff --git a/crates/cli/src/subcommands/generate/rust.rs b/crates/cli/src/subcommands/generate/rust.rs index 8eef1e9944d..89c0fffacf4 100644 --- a/crates/cli/src/subcommands/generate/rust.rs +++ b/crates/cli/src/subcommands/generate/rust.rs @@ -1,12 +1,14 @@ use super::code_indenter::CodeIndenter; -use super::{GenCtx, GenItem}; use convert_case::{Case, Casing}; -use spacetimedb_lib::sats::{ - AlgebraicType, AlgebraicTypeRef, ArrayType, ProductType, ProductTypeElement, SumType, SumTypeVariant, -}; -use spacetimedb_lib::{ReducerDef, TableDesc}; +use itertools::Itertools; +use spacetimedb_lib::sats::AlgebraicTypeRef; use spacetimedb_primitives::ColList; +use spacetimedb_schema::def::{ModuleDef, ReducerDef, ScopedTypeName, TableDef, TypeDef}; +use spacetimedb_schema::identifier::Identifier; use spacetimedb_schema::schema::TableSchema; +use spacetimedb_schema::type_for_generate::{ + AlgebraicTypeDef, AlgebraicTypeUse, PlainEnumTypeDef, PrimitiveType, ProductTypeDef, SumTypeDef, +}; use std::collections::BTreeSet; use std::fmt::{self, Write}; use std::ops::Deref; @@ -16,60 +18,88 @@ type Indenter = CodeIndenter; /// Pairs of (module_name, TypeName). type Imports = BTreeSet<(String, String)>; -fn write_type_ctx(ctx: &GenCtx, out: &mut Indenter, ty: &AlgebraicType) { - write_type(&|r| type_name(ctx, r), out, ty).unwrap() +pub struct Rust; + +impl super::Lang for Rust { + fn table_filename(&self, module: &ModuleDef, table: &TableDef) -> String { + let (name, _) = module.type_def_from_ref(table.product_type_ref).unwrap(); + collect_case(Case::Snake, name.name_segments()) + ".rs" + } + + fn type_filename(&self, type_name: &ScopedTypeName) -> String { + collect_case(Case::Snake, type_name.name_segments()) + ".rs" + } + + fn reducer_filename(&self, reducer_name: &Identifier) -> String { + reducer_name.deref().to_case(Case::Snake) + "_reducer.rs" + } + + fn generate_table(&self, module: &ModuleDef, _namespace: &str, table: &TableDef) -> String { + autogen_rust_table(module, table) + } + + fn generate_type(&self, module: &ModuleDef, _namespace: &str, typ: &TypeDef) -> String { + let name = &collect_case(Case::Pascal, typ.name.name_segments()); + match &module.typespace_for_generate()[typ.ty] { + AlgebraicTypeDef::Product(product) => autogen_rust_tuple(module, name, product), + AlgebraicTypeDef::Sum(sum_type) => autogen_rust_sum(module, name, sum_type), + AlgebraicTypeDef::PlainEnum(plain_enum) => autogen_rust_plain_enum(name, plain_enum), + } + } + + fn generate_reducer(&self, module: &ModuleDef, _namespace: &str, reducer: &ReducerDef) -> String { + autogen_rust_reducer(module, reducer) + } + + fn generate_globals(&self, module: &ModuleDef, _namespace: &str) -> Vec<(String, String)> { + autogen_rust_globals(module) + } +} + +fn collect_case<'a>(case: Case, segs: impl Iterator) -> String { + segs.map(|s| s.deref().to_case(case)).join(case.delim()) +} + +fn write_type(module: &ModuleDef, out: &mut Indenter, ty: &AlgebraicTypeUse) { + write_type_generic(module, out, ty).ok(); } -pub fn write_type(ctx: &impl Fn(AlgebraicTypeRef) -> String, out: &mut W, ty: &AlgebraicType) -> fmt::Result { +pub fn write_type_generic(module: &ModuleDef, out: &mut W, ty: &AlgebraicTypeUse) -> fmt::Result { match ty { - p if p.is_identity() => write!(out, "Identity")?, - p if p.is_address() => write!(out, "Address")?, - p if p.is_schedule_at() => write!(out, "ScheduleAt")?, - AlgebraicType::Sum(sum_type) => { - if let Some(inner_ty) = sum_type.as_option() { - write!(out, "Option::<")?; - write_type(ctx, out, inner_ty)?; - write!(out, ">")?; - } else { - write!(out, "enum ")?; - print_comma_sep_braced(out, &sum_type.variants, |out: &mut W, elem: &_| { - if let Some(name) = &elem.name { - write!(out, "{name}: ")?; - } - write_type(ctx, out, &elem.algebraic_type) - })?; - } - } - AlgebraicType::Product(ProductType { elements }) => { - print_comma_sep_braced(out, elements, |out: &mut W, elem: &ProductTypeElement| { - if let Some(name) = &elem.name { - write!(out, "{name}: ")?; - } - write_type(ctx, out, &elem.algebraic_type) - })?; + AlgebraicTypeUse::Unit => write!(out, "()")?, + AlgebraicTypeUse::Never => write!(out, "std::convert::Infallible")?, + AlgebraicTypeUse::Identity => write!(out, "Identity")?, + AlgebraicTypeUse::Address => write!(out, "Address")?, + AlgebraicTypeUse::ScheduleAt => write!(out, "ScheduleAt")?, + AlgebraicTypeUse::Option(inner_ty) => { + write!(out, "Option::<")?; + write_type_generic(module, out, inner_ty)?; + write!(out, ">")?; } - AlgebraicType::Bool => write!(out, "bool")?, - AlgebraicType::I8 => write!(out, "i8")?, - AlgebraicType::U8 => write!(out, "u8")?, - AlgebraicType::I16 => write!(out, "i16")?, - AlgebraicType::U16 => write!(out, "u16")?, - AlgebraicType::I32 => write!(out, "i32")?, - AlgebraicType::U32 => write!(out, "u32")?, - AlgebraicType::I64 => write!(out, "i64")?, - AlgebraicType::U64 => write!(out, "u64")?, - AlgebraicType::I128 => write!(out, "i128")?, - AlgebraicType::U128 => write!(out, "u128")?, - AlgebraicType::I256 => write!(out, "i256")?, - AlgebraicType::U256 => write!(out, "u256")?, - AlgebraicType::F32 => write!(out, "f32")?, - AlgebraicType::F64 => write!(out, "f64")?, - AlgebraicType::String => write!(out, "String")?, - AlgebraicType::Array(ArrayType { elem_ty }) => { + AlgebraicTypeUse::Primitive(prim) => match prim { + PrimitiveType::Bool => write!(out, "bool")?, + PrimitiveType::I8 => write!(out, "i8")?, + PrimitiveType::U8 => write!(out, "u8")?, + PrimitiveType::I16 => write!(out, "i16")?, + PrimitiveType::U16 => write!(out, "u16")?, + PrimitiveType::I32 => write!(out, "i32")?, + PrimitiveType::U32 => write!(out, "u32")?, + PrimitiveType::I64 => write!(out, "i64")?, + PrimitiveType::U64 => write!(out, "u64")?, + PrimitiveType::I128 => write!(out, "i128")?, + PrimitiveType::U128 => write!(out, "u128")?, + PrimitiveType::I256 => write!(out, "i256")?, + PrimitiveType::U256 => write!(out, "u256")?, + PrimitiveType::F32 => write!(out, "f32")?, + PrimitiveType::F64 => write!(out, "f64")?, + }, + AlgebraicTypeUse::String => write!(out, "String")?, + AlgebraicTypeUse::Array(elem_ty) => { write!(out, "Vec::<")?; - write_type(ctx, out, elem_ty)?; + write_type_generic(module, out, elem_ty)?; write!(out, ">")?; } - AlgebraicType::Map(ty) => { + AlgebraicTypeUse::Map { key, value } => { // TODO: Should `AlgebraicType::Map` translate to `HashMap`? This requires // that any map-key type implement `Hash`. We'll have to derive hash // on generated types, and notably, `HashMap` is not itself `Hash`, @@ -79,55 +109,24 @@ pub fn write_type(ctx: &impl Fn(AlgebraicTypeRef) -> String, out: &mut // This will require deriving `Ord` for generated types, // and is likely to be a big headache. write!(out, "HashMap::<")?; - write_type(ctx, out, &ty.key_ty)?; + write_type_generic(module, out, key)?; write!(out, ", ")?; - write_type(ctx, out, &ty.ty)?; + write_type_generic(module, out, value)?; write!(out, ">")?; } - AlgebraicType::Ref(r) => { - write!(out, "{}", ctx(*r))?; + AlgebraicTypeUse::Ref(r) => { + write!(out, "{}", type_name(module, *r))?; } } Ok(()) } -fn print_comma_sep_braced( - out: &mut W, - elems: &[T], - on: impl Fn(&mut W, &T) -> fmt::Result, -) -> fmt::Result { - write!(out, "{{")?; - - let mut iter = elems.iter(); - - // First factor. - if let Some(elem) = iter.next() { - write!(out, " ")?; - on(out, elem)?; - } - // Other factors. - for elem in iter { - write!(out, ", ")?; - on(out, elem)?; - } - - if !elems.is_empty() { - write!(out, " ")?; - } - - write!(out, "}}")?; - - Ok(()) -} - // This is (effectively) duplicated in [typescript.rs] as `typescript_typename` and in // [csharp.rs] as `csharp_typename`, and should probably be lifted to a shared utils // module. -fn type_name(ctx: &GenCtx, typeref: AlgebraicTypeRef) -> String { - ctx.names[typeref.idx()] - .as_deref() - .expect("TypeRefs should have names") - .to_case(Case::Pascal) +fn type_name(module: &ModuleDef, typeref: AlgebraicTypeRef) -> String { + let (name, _def) = module.type_def_from_ref(typeref).unwrap(); + collect_case(Case::Pascal, name.name_segments()) } fn print_lines(output: &mut Indenter, lines: &[&str]) { @@ -191,7 +190,7 @@ fn print_enum_derives(output: &mut Indenter) { } /// Generate a file which defines an `enum` corresponding to the `sum_type`. -pub fn autogen_rust_sum(ctx: &GenCtx, name: &str, sum_type: &SumType) -> String { +pub fn autogen_rust_sum(module: &ModuleDef, name: &str, sum_type: &SumTypeDef) -> String { let mut output = CodeIndenter::new(String::new()); let out = &mut output; @@ -207,7 +206,7 @@ pub fn autogen_rust_sum(ctx: &GenCtx, name: &str, sum_type: &SumType) -> String // For some reason, deref coercion doesn't work on `&sum_type.variants` here - rustc // wants to pass it as `&Vec<_>`, not `&[_]`. The slicing index `[..]` forces passing // as a slice. - gen_and_print_imports(ctx, out, &sum_type.variants[..], generate_imports_variants, this_file); + gen_and_print_imports(module, out, &sum_type.variants[..], this_file); out.newline(); @@ -218,8 +217,8 @@ pub fn autogen_rust_sum(ctx: &GenCtx, name: &str, sum_type: &SumType) -> String out.delimited_block( "{", |out| { - for variant in &*sum_type.variants { - write_enum_variant(ctx, out, variant); + for (name, ty) in &*sum_type.variants { + write_enum_variant(module, out, name, ty); out.newline(); } }, @@ -229,14 +228,11 @@ pub fn autogen_rust_sum(ctx: &GenCtx, name: &str, sum_type: &SumType) -> String output.into_inner() } -fn write_enum_variant(ctx: &GenCtx, out: &mut Indenter, variant: &SumTypeVariant) { - let Some(name) = &variant.name else { - panic!("Sum type variant has no name: {variant:?}"); - }; +fn write_enum_variant(module: &ModuleDef, out: &mut Indenter, name: &Identifier, ty: &AlgebraicTypeUse) { let name = name.deref().to_case(Case::Pascal); write!(out, "{name}"); - match &variant.algebraic_type { - AlgebraicType::Product(ProductType { elements }) if elements.is_empty() => { + match ty { + AlgebraicTypeUse::Unit => { // If the contained type is the unit type, i.e. this variant has no members, // write it without parens or braces, like // ``` @@ -248,97 +244,70 @@ fn write_enum_variant(ctx: &GenCtx, out: &mut Indenter, variant: &SumTypeVariant // If the contained type is not a product, i.e. this variant has a single // member, write it tuple-style, with parens. write!(out, "("); - write_type_ctx(ctx, out, otherwise); + write_type(module, out, otherwise); write!(out, "),"); } } } -fn write_struct_type_fields_in_braces( - ctx: &GenCtx, - out: &mut Indenter, - elements: &[ProductTypeElement], +/// Generate a file which defines an `enum` corresponding to the `sum_type`. +pub fn autogen_rust_plain_enum(name: &str, plain_enum: &PlainEnumTypeDef) -> String { + let mut output = CodeIndenter::new(String::new()); + let out = &mut output; - // Whether to print a `pub` qualifier on the fields. Necessary for `struct` defns, - // disallowed for `enum` defns. - pub_qualifier: bool, -) { - out.delimited_block( - "{", - |out| write_arglist_no_delimiters_ctx(ctx, out, elements, pub_qualifier.then_some("pub")), - "}", - ); -} + let sum_type_name = name.replace("r#", "").to_case(Case::Pascal); -fn write_arglist_no_delimiters_ctx( - ctx: &GenCtx, - out: &mut Indenter, - elements: &[ProductTypeElement], + print_file_header(out); + out.newline(); - // Written before each line. Useful for `pub`. - prefix: Option<&str>, -) { - write_arglist_no_delimiters(&|r| type_name(ctx, r), out, elements, prefix).unwrap() -} + print_enum_derives(out); -pub fn write_arglist_no_delimiters( - ctx: &impl Fn(AlgebraicTypeRef) -> String, - out: &mut impl Write, - elements: &[ProductTypeElement], - - // Written before each line. Useful for `pub`. - prefix: Option<&str>, -) -> fmt::Result { - for elt in elements { - if let Some(prefix) = prefix { - write!(out, "{prefix} ")?; - } + write!(out, "pub enum {sum_type_name} "); - let Some(name) = &elt.name else { - panic!("Product type element has no name: {elt:?}"); - }; - let name = name.deref().to_case(Case::Snake); + out.delimited_block( + "{", + |out| { + for name in &plain_enum.variants[..] { + writeln!(out, "{name},"); + } + }, + "}\n", + ); - write!(out, "{name}: ")?; - write_type(ctx, out, &elt.algebraic_type)?; - writeln!(out, ",")?; - } - Ok(()) + output.into_inner() } /// Generate a file which defines a `struct` corresponding to the `product` type. -pub fn autogen_rust_tuple(ctx: &GenCtx, name: &str, product: &ProductType) -> String { +pub fn autogen_rust_tuple(module: &ModuleDef, name: &str, product: &ProductTypeDef) -> String { let mut output = CodeIndenter::new(String::new()); let out = &mut output; let type_name = name.to_case(Case::Pascal); - begin_rust_struct_def_shared(ctx, out, &type_name, &product.elements); + begin_rust_struct_def_shared(module, out, &type_name, product); output.into_inner() } -fn find_product_type(ctx: &GenCtx, ty: AlgebraicTypeRef) -> &ProductType { - ctx.typespace[ty].as_product().unwrap() -} - /// Generate a file which defines a `struct` corresponding to the `table`'s `ProductType`, /// and implements `spacetimedb_sdk::table::TableType` for it. -#[allow(deprecated)] -pub fn autogen_rust_table(ctx: &GenCtx, table: &TableDesc) -> String { +pub fn autogen_rust_table(module: &ModuleDef, table: &TableDef) -> String { let mut output = CodeIndenter::new(String::new()); let out = &mut output; - let type_name = type_name(ctx, table.data); + let type_name = type_name(module, table.product_type_ref); - begin_rust_struct_def_shared(ctx, out, &type_name, &find_product_type(ctx, table.data).elements); + let product_def = module.typespace_for_generate()[table.product_type_ref] + .as_product() + .unwrap(); + begin_rust_struct_def_shared(module, out, &type_name, product_def); out.newline(); - let table = TableSchema::from_def(0.into(), table.schema.clone()) + let table = TableSchema::from_module_def(table, 0.into()) .validated() .expect("Failed to generate table due to validation errors"); - print_impl_tabletype(ctx, out, &type_name, &table); + print_impl_tabletype(module, out, &type_name, product_def, &table); output.into_inner() } @@ -368,7 +337,7 @@ fn print_struct_derives(output: &mut Indenter) { print_lines(output, STRUCT_DERIVES); } -fn begin_rust_struct_def_shared(ctx: &GenCtx, out: &mut Indenter, name: &str, elements: &[ProductTypeElement]) { +fn begin_rust_struct_def_shared(module: &ModuleDef, out: &mut Indenter, name: &str, def: &ProductTypeDef) { print_file_header(out); // Pass this file into `gen_and_print_imports` to avoid recursively importing self @@ -379,7 +348,7 @@ fn begin_rust_struct_def_shared(ctx: &GenCtx, out: &mut Indenter, name: &str, el let file_name = name.to_case(Case::Snake); let this_file = (file_name.as_str(), name); - gen_and_print_imports(ctx, out, elements, generate_imports_elements, this_file); + gen_and_print_imports(module, out, &def.elements, this_file); out.newline(); @@ -388,9 +357,16 @@ fn begin_rust_struct_def_shared(ctx: &GenCtx, out: &mut Indenter, name: &str, el write!(out, "pub struct {name} "); // TODO: if elements is empty, define a unit struct with no brace-delimited list of fields. - write_struct_type_fields_in_braces( - ctx, out, elements, // `pub`-qualify fields. - true, + out.delimited_block( + "{", + |out| { + for (name, ty) in def { + write!(out, "pub {}: ", name.deref().to_case(Case::Snake)); + write_type(module, out, ty); + writeln!(out, ","); + } + }, + "}", ); out.newline(); @@ -400,7 +376,13 @@ fn find_primary_key_column_index(table: &TableSchema) -> Option { table.pk().map(|x| x.col_pos.idx()) } -fn print_impl_tabletype(ctx: &GenCtx, out: &mut Indenter, type_name: &str, table: &TableSchema) { +fn print_impl_tabletype( + module: &ModuleDef, + out: &mut Indenter, + type_name: &str, + product_def: &ProductTypeDef, + table: &TableSchema, +) { write!(out, "impl TableType for {type_name} "); out.delimited_block( @@ -422,7 +404,7 @@ fn print_impl_tabletype(ctx: &GenCtx, out: &mut Indenter, type_name: &str, table "{", |out| { write!(out, "type PrimaryKey = "); - write_type_ctx(ctx, out, &pk_field.col_type); + write_type(module, out, &product_def.elements[pk_field.col_pos.idx()].1); writeln!(out, ";"); out.delimited_block( @@ -437,10 +419,16 @@ fn print_impl_tabletype(ctx: &GenCtx, out: &mut Indenter, type_name: &str, table out.newline(); - print_table_filter_methods(ctx, out, type_name, table); + print_table_filter_methods(module, out, type_name, product_def, table); } -fn print_table_filter_methods(ctx: &GenCtx, out: &mut Indenter, table_type_name: &str, table: &TableSchema) { +fn print_table_filter_methods( + module: &ModuleDef, + out: &mut Indenter, + table_type_name: &str, + product_def: &ProductTypeDef, + table: &TableSchema, +) { write!(out, "impl {table_type_name} "); let constraints = table.column_constraints(); out.delimited_block( @@ -448,15 +436,12 @@ fn print_table_filter_methods(ctx: &GenCtx, out: &mut Indenter, table_type_name: |out| { for field in table.columns() { let field_name = field.col_name.deref().to_case(Case::Snake); - match &field.col_type { - AlgebraicType::Product(prod) if prod.is_special() => {} - AlgebraicType::Product(_) - | AlgebraicType::Ref(_) - | AlgebraicType::Sum(_) - | AlgebraicType::Array(_) - | AlgebraicType::Map(_) => { - continue; - } + let col_ty = &product_def.elements[field.col_pos.idx()].1; + match col_ty { + AlgebraicTypeUse::Ref(_) + | AlgebraicTypeUse::Array(_) + | AlgebraicTypeUse::Map { .. } + | AlgebraicTypeUse::Option(_) => continue, _ => {} } writeln!(out, "{ALLOW_UNUSED}"); @@ -466,7 +451,7 @@ fn print_table_filter_methods(ctx: &GenCtx, out: &mut Indenter, table_type_name: // fields should take &[T]. Determine if integer types should be by // value. Is there a trait for this? // Look at `Borrow` or Deref or AsRef? - write_type_ctx(ctx, out, &field.col_type); + write_type(module, out, col_ty); write!(out, ") -> "); let ct = constraints[&ColList::new(field.col_pos)]; @@ -487,7 +472,7 @@ fn print_table_filter_methods(ctx: &GenCtx, out: &mut Indenter, table_type_name: if ct.has_unique() { writeln!(out, "{ALLOW_UNUSED}"); write!(out, "pub fn find_by_{field_name}({field_name}: "); - write_type_ctx(ctx, out, &field.col_type); + write_type(module, out, col_ty); write!(out, ") -> Option "); out.delimited_block( "{", @@ -521,26 +506,14 @@ fn reducer_function_name(reducer: &ReducerDef) -> String { reducer.name.deref().to_case(Case::Snake) } -fn iter_reducer_arg_names(reducer: &ReducerDef) -> impl Iterator> + '_ { - reducer - .args - .iter() - .map(|elt| elt.name.as_ref().map(|name| name.deref().to_case(Case::Snake))) -} - -fn iter_reducer_arg_types(reducer: &'_ ReducerDef) -> impl Iterator { - reducer.args.iter().map(|elt| &elt.algebraic_type) -} - fn print_reducer_struct_literal(out: &mut Indenter, reducer: &ReducerDef) { write!(out, "{} ", reducer_type_name(reducer)); // TODO: if reducer.args is empty, write a unit struct. out.delimited_block( "{", |out| { - for arg_name in iter_reducer_arg_names(reducer) { - let name = arg_name.unwrap(); - writeln!(out, "{name},"); + for (name, _) in &reducer.params_for_generate { + writeln!(out, "{},", name.deref().to_case(Case::Snake)); } }, "}", @@ -550,14 +523,14 @@ fn print_reducer_struct_literal(out: &mut Indenter, reducer: &ReducerDef) { /// Generate a file which defines a struct corresponding to the `reducer`'s arguments, /// implements `spacetimedb_sdk::table::Reducer` for it, and defines a helper /// function which invokes the reducer. -pub fn autogen_rust_reducer(ctx: &GenCtx, reducer: &ReducerDef) -> String { +pub fn autogen_rust_reducer(module: &ModuleDef, reducer: &ReducerDef) -> String { let func_name = reducer_function_name(reducer); let type_name = reducer_type_name(reducer); let mut output = CodeIndenter::new(String::new()); let out = &mut output; - begin_rust_struct_def_shared(ctx, out, &type_name, &reducer.args); + begin_rust_struct_def_shared(module, out, &type_name, &reducer.params_for_generate); out.newline(); @@ -580,7 +553,13 @@ pub fn autogen_rust_reducer(ctx: &GenCtx, reducer: &ReducerDef) -> String { // TODO: if reducer.args is empty, just write "()" with no newlines out.delimited_block( "(", - |out| write_arglist_no_delimiters_ctx(ctx, out, &reducer.args, None), + |out| { + for (name, ty) in &reducer.params_for_generate { + write!(out, "{}: ", name.deref().to_case(Case::Snake)); + write_type(module, out, ty); + writeln!(out, ","); + } + }, ") ", ); @@ -596,91 +575,54 @@ pub fn autogen_rust_reducer(ctx: &GenCtx, reducer: &ReducerDef) -> String { out.newline(); - // Function definition for convenient callback function, - // which takes a closure fromunpacked args, - // and wraps it in a closure from the args struct. - writeln!(out, "{ALLOW_UNUSED}"); - write!( - out, - "pub fn on_{func_name}(mut __callback: impl FnMut(&Identity, Option
, &Status" - ); - for arg_type in iter_reducer_arg_types(reducer) { - write!(out, ", &"); - write_type_ctx(ctx, out, arg_type); - } - writeln!(out, ") + Send + 'static) -> ReducerCallbackId<{type_name}> "); - out.delimited_block( - "{", - |out| { - write!(out, "{type_name}"); - out.delimited_block( - "::on_reducer(move |__identity, __addr, __status, __args| {", - |out| { - write!(out, "let "); - print_reducer_struct_literal(out, reducer); - writeln!(out, " = __args;"); - out.delimited_block( - "__callback(", - |out| { - writeln!(out, "__identity,"); - writeln!(out, "__addr,"); - writeln!(out, "__status,"); - for arg_name in iter_reducer_arg_names(reducer) { - writeln!(out, "{},", arg_name.unwrap()); - } - }, - ");\n", - ); - }, - "})\n", - ); - }, - "}\n", - ); - - out.newline(); + let mut on_func = |on_prefix, mut_, fn_kind| { + // Function definition for convenient callback function, + // which takes a closure fromunpacked args, + // and wraps it in a closure from the args struct. + writeln!(out, "{ALLOW_UNUSED}"); + write!( + out, + "pub fn {on_prefix}_{func_name}({mut_}__callback: impl {fn_kind}(&Identity, Option
, &Status" + ); + for (_, arg_type) in &reducer.params_for_generate { + write!(out, ", &"); + write_type(module, out, arg_type); + } + writeln!(out, ") + Send + 'static) -> ReducerCallbackId<{type_name}> "); + out.delimited_block( + "{", + |out| { + write!(out, "{type_name}::{on_prefix}_reducer"); + out.delimited_block( + "(move |__identity, __addr, __status, __args| {", + |out| { + write!(out, "let "); + print_reducer_struct_literal(out, reducer); + writeln!(out, " = __args;"); + out.delimited_block( + "__callback(", + |out| { + writeln!(out, "__identity,"); + writeln!(out, "__addr,"); + writeln!(out, "__status,"); + for (arg_name, _) in &reducer.params_for_generate { + writeln!(out, "{},", arg_name.deref().to_case(Case::Snake)); + } + }, + ");\n", + ); + }, + "})\n", + ); + }, + "}\n", + ); - // Function definition for convenient once_on callback function. - writeln!(out, "{ALLOW_UNUSED}"); - write!( - out, - "pub fn once_on_{func_name}(__callback: impl FnOnce(&Identity, Option
, &Status" - ); - for arg_type in iter_reducer_arg_types(reducer) { - write!(out, ", &"); - write_type_ctx(ctx, out, arg_type); - } - writeln!(out, ") + Send + 'static) -> ReducerCallbackId<{type_name}> "); - out.delimited_block( - "{", - |out| { - write!(out, "{type_name}"); - out.delimited_block( - "::once_on_reducer(move |__identity, __addr, __status, __args| {", - |out| { - write!(out, "let "); - print_reducer_struct_literal(out, reducer); - writeln!(out, " = __args;"); - out.delimited_block( - "__callback(", - |out| { - writeln!(out, "__identity,"); - writeln!(out, "__addr,"); - writeln!(out, "__status,"); - for arg_name in iter_reducer_arg_names(reducer) { - writeln!(out, "{},", arg_name.unwrap()); - } - }, - ");\n", - ); - }, - "})\n", - ) - }, - "}\n", - ); + out.newline(); + }; - out.newline(); + on_func("on", "mut ", "FnMut"); + on_func("once_on", "", "FnOnce"); // Function definition for callback-canceling `remove_on_{reducer}` function. writeln!(out, "{ALLOW_UNUSED}"); @@ -717,7 +659,7 @@ pub fn autogen_rust_reducer(ctx: &GenCtx, reducer: &ReducerDef) -> String { /// to connect to a remote database, and passes the `handle_row_update` /// and `handle_event` functions so the `BackgroundDbConnection` can spawn workers /// which use those functions to dispatch on the content of messages. -pub fn autogen_rust_globals(ctx: &GenCtx, items: &[GenItem]) -> Vec<(String, String)> { +pub fn autogen_rust_globals(module: &ModuleDef) -> Vec<(String, String)> { let mut output = CodeIndenter::new(String::new()); let out = &mut output; @@ -729,21 +671,21 @@ pub fn autogen_rust_globals(ctx: &GenCtx, items: &[GenItem]) -> Vec<(String, Str out.newline(); // Declare `pub mod` for each of the files generated. - print_module_decls(ctx, out, items); + print_module_decls(module, out); out.newline(); // Re-export all the modules for the generated files. - print_module_reexports(ctx, out, items); + print_module_reexports(module, out); out.newline(); // Define `enum ReducerEvent`. - print_reducer_event_defn(out, items); + print_reducer_event_defn(module, out); out.newline(); - print_spacetime_module_struct_defn(ctx, out, items); + print_spacetime_module_struct_defn(module, out); out.newline(); @@ -769,38 +711,39 @@ fn print_dispatch_imports(out: &mut Indenter) { print_lines(out, DISPATCH_IMPORTS); } -fn iter_reducer_items(items: &[GenItem]) -> impl Iterator { - items.iter().filter_map(|item| match item { - GenItem::Reducer(reducer) => Some(reducer), - _ => None, - }) +fn iter_module_names(module: &ModuleDef) -> impl Iterator + '_ { + dbg!(module.types().map(|ty| (&ty.name, ty.ty)).collect::>()); + itertools::chain!( + // iter_tables(module).map(|table| table.name.deref().to_case(Case::Snake)), + module + .types() + .sorted_by_key(|ty| &ty.name) + .map(|ty| collect_case(Case::Snake, ty.name.name_segments())), + iter_reducers(module).map(reducer_module_name), + ) } -fn iter_table_items(items: &[GenItem]) -> impl Iterator { - items.iter().filter_map(|item| match item { - GenItem::Table(table) => Some(table), - _ => None, - }) +fn iter_tables(module: &ModuleDef) -> impl Iterator { + module.tables().sorted_by_key(|tbl| &tbl.name) } -fn iter_module_names<'a>(ctx: &'a GenCtx, items: &'a [GenItem]) -> impl Iterator + 'a { - items.iter().map(|item| match item { - GenItem::Table(table) => type_name(ctx, table.data).to_case(Case::Snake), - GenItem::TypeAlias(ty) => ty.name.to_case(Case::Snake), - GenItem::Reducer(reducer) => reducer_module_name(reducer), - }) +fn iter_reducers(module: &ModuleDef) -> impl Iterator { + module + .reducers() + .filter(|r| r.lifecycle.is_none()) + .sorted_by_key(|r| &r.name) } /// Print `pub mod` declarations for all the files that will be generated for `items`. -fn print_module_decls(ctx: &GenCtx, out: &mut Indenter, items: &[GenItem]) { - for module_name in iter_module_names(ctx, items) { +fn print_module_decls(module: &ModuleDef, out: &mut Indenter) { + for module_name in iter_module_names(module) { writeln!(out, "pub mod {module_name};"); } } /// Print `pub use *` declarations for all the files that will be generated for `items`. -fn print_module_reexports(ctx: &GenCtx, out: &mut Indenter, items: &[GenItem]) { - for module_name in iter_module_names(ctx, items) { +fn print_module_reexports(module: &ModuleDef, out: &mut Indenter) { + for module_name in iter_module_names(module) { writeln!(out, "pub use {module_name}::*;"); } } @@ -825,7 +768,7 @@ fn print_module_reexports(ctx: &GenCtx, out: &mut Indenter, items: &[GenItem]) { /// /// - `handle_event`, which serves the same role as `handle_table_update`, but for /// reducers. -fn print_spacetime_module_struct_defn(ctx: &GenCtx, out: &mut Indenter, items: &[GenItem]) { +fn print_spacetime_module_struct_defn(module: &ModuleDef, out: &mut Indenter) { // Muffle unused warning for `Module`, which is not supposed to be visible to // users. It will be used if and only if `connect` is used, so that unused warning is // sufficient, and not as confusing. @@ -834,10 +777,10 @@ fn print_spacetime_module_struct_defn(ctx: &GenCtx, out: &mut Indenter, items: & out.delimited_block( "impl SpacetimeModule for Module {", |out| { - print_handle_table_update_defn(ctx, out, items); - print_invoke_row_callbacks_defn(ctx, out, items); - print_handle_event_defn(out, items); - print_handle_resubscribe_defn(ctx, out, items); + print_handle_table_update_defn(module, out); + print_invoke_row_callbacks_defn(module, out); + print_handle_event_defn(module, out); + print_handle_resubscribe_defn(module, out); }, "}\n", ); @@ -847,7 +790,7 @@ fn print_spacetime_module_struct_defn(ctx: &GenCtx, out: &mut Indenter, items: & /// which dispatches on the table name in a `TableUpdate` message /// to call an appropriate method on the `ClientCache`. #[allow(deprecated)] -fn print_handle_table_update_defn(ctx: &GenCtx, out: &mut Indenter, items: &[GenItem]) { +fn print_handle_table_update_defn(module: &ModuleDef, out: &mut Indenter) { out.delimited_block( "fn handle_table_update(&self, table_update: TableUpdate, client_cache: &mut ClientCache, callbacks: &mut RowCallbackReminders) {", |out| { @@ -855,8 +798,8 @@ fn print_handle_table_update_defn(ctx: &GenCtx, out: &mut Indenter, items: &[Gen out.delimited_block( "match table_name {", |out| { - for table_desc in iter_table_items(items) { - let table = TableSchema::from_def(0.into(), table_desc.schema.clone()).validated().unwrap(); + for table_desc in iter_tables(module) { + let table = TableSchema::from_module_def(table_desc, 0.into()).validated().unwrap(); writeln!( out, "{:?} => client_cache.{}::<{}::{}>(callbacks, table_update),", @@ -866,8 +809,8 @@ fn print_handle_table_update_defn(ctx: &GenCtx, out: &mut Indenter, items: &[Gen } else { "handle_table_update_no_primary_key" }, - type_name(ctx, table_desc.data).to_case(Case::Snake), - type_name(ctx, table_desc.data).to_case(Case::Pascal), + type_name(module, table_desc.product_type_ref).to_case(Case::Snake), + type_name(module, table_desc.product_type_ref).to_case(Case::Pascal), ); } writeln!( @@ -884,16 +827,16 @@ fn print_handle_table_update_defn(ctx: &GenCtx, out: &mut Indenter, items: &[Gen /// Define the `invoke_row_callbacks` function, /// which does `RowCallbackReminders::invoke_callbacks` on each table type defined in the `items`. -fn print_invoke_row_callbacks_defn(ctx: &GenCtx, out: &mut Indenter, items: &[GenItem]) { +fn print_invoke_row_callbacks_defn(module: &ModuleDef, out: &mut Indenter) { out.delimited_block( "fn invoke_row_callbacks(&self, reminders: &mut RowCallbackReminders, worker: &mut DbCallbacks, reducer_event: Option>, state: &Arc) {", |out| { - for table in iter_table_items(items) { + for table in iter_tables(module) { writeln!( out, "reminders.invoke_callbacks::<{}::{}>(worker, &reducer_event, state);", - type_name(ctx, table.data).to_case(Case::Snake), - type_name(ctx, table.data).to_case(Case::Pascal), + type_name(module, table.product_type_ref).to_case(Case::Snake), + type_name(module, table.product_type_ref).to_case(Case::Pascal), ); } }, @@ -904,7 +847,7 @@ fn print_invoke_row_callbacks_defn(ctx: &GenCtx, out: &mut Indenter, items: &[Ge /// Define the `handle_resubscribe` function, /// which dispatches on the table name in a `TableUpdate` /// to invoke `ClientCache::handle_resubscribe_for_type` with an appropriate type arg. -fn print_handle_resubscribe_defn(ctx: &GenCtx, out: &mut Indenter, items: &[GenItem]) { +fn print_handle_resubscribe_defn(module: &ModuleDef, out: &mut Indenter) { out.delimited_block( "fn handle_resubscribe(&self, new_subs: TableUpdate, client_cache: &mut ClientCache, callbacks: &mut RowCallbackReminders) {", |out| { @@ -912,13 +855,13 @@ fn print_handle_resubscribe_defn(ctx: &GenCtx, out: &mut Indenter, items: &[GenI out.delimited_block( "match table_name {", |out| { - for table in iter_table_items(items) { + for table in iter_tables(module) { writeln!( out, "{:?} => client_cache.handle_resubscribe_for_type::<{}::{}>(callbacks, new_subs),", - table.schema.table_name, - type_name(ctx, table.data).to_case(Case::Snake), - type_name(ctx, table.data).to_case(Case::Pascal), + table.name, + type_name(module, table.product_type_ref).to_case(Case::Snake), + type_name(module, table.product_type_ref).to_case(Case::Pascal), ); } writeln!( @@ -936,7 +879,7 @@ fn print_handle_resubscribe_defn(ctx: &GenCtx, out: &mut Indenter, items: &[GenI /// Define the `handle_event` function, /// which dispatches on the reducer name in an `Event` /// to `ReducerCallbacks::handle_event_of_type` with an appropriate type argument. -fn print_handle_event_defn(out: &mut Indenter, items: &[GenItem]) { +fn print_handle_event_defn(module: &ModuleDef, out: &mut Indenter) { out.delimited_block( "fn handle_event(&self, event: TransactionUpdate, _reducer_callbacks: &mut ReducerCallbacks, _state: Arc) -> Option> {", |out| { @@ -952,7 +895,7 @@ fn print_handle_event_defn(out: &mut Indenter, items: &[GenItem]) { out.delimited_block( "match &reducer_call.reducer_name[..] {", |out| { - for reducer in iter_reducer_items(items) { + for reducer in iter_reducers(module) { writeln!( out, "{:?} => _reducer_callbacks.handle_event_of_type::<{}::{}, ReducerEvent>(event, _state, ReducerEvent::{}),", @@ -1011,14 +954,14 @@ where ); } -fn print_reducer_event_defn(out: &mut Indenter, items: &[GenItem]) { +fn print_reducer_event_defn(module: &ModuleDef, out: &mut Indenter) { writeln!(out, "{ALLOW_UNUSED}"); print_enum_derives(out); out.delimited_block( "pub enum ReducerEvent {", |out| { - for reducer in iter_reducer_items(items) { + for reducer in iter_reducers(module) { writeln!( out, "{}({}::{}),", @@ -1032,42 +975,10 @@ fn print_reducer_event_defn(out: &mut Indenter, items: &[GenItem]) { ); } -fn generate_imports_variants(ctx: &GenCtx, imports: &mut Imports, variants: &[SumTypeVariant]) { - for variant in variants { - generate_imports(ctx, imports, &variant.algebraic_type); - } -} - -fn generate_imports_elements(ctx: &GenCtx, imports: &mut Imports, elements: &[ProductTypeElement]) { - for element in elements { - generate_imports(ctx, imports, &element.algebraic_type); - } -} - fn module_name(name: &str) -> String { name.to_case(Case::Snake) } -fn generate_imports(ctx: &GenCtx, imports: &mut Imports, ty: &AlgebraicType) { - match ty { - AlgebraicType::Array(ArrayType { elem_ty }) => generate_imports(ctx, imports, elem_ty), - AlgebraicType::Map(map_type) => { - generate_imports(ctx, imports, &map_type.key_ty); - generate_imports(ctx, imports, &map_type.ty); - } - AlgebraicType::Ref(r) => { - let type_name = type_name(ctx, *r); - let module_name = module_name(&type_name); - imports.insert((module_name, type_name)); - } - // Recurse into variants of anonymous sum types, e.g. for `Option`, import `T`. - AlgebraicType::Sum(s) => generate_imports_variants(ctx, imports, &s.variants), - // Products, scalars, and strings. - // Do we need to generate imports for fields of anonymous product types? - _ => {} - } -} - /// Print `use super::` imports for each of the `imports`, except `this_file`. /// /// `this_file` is passed and excluded for the case of recursive types: @@ -1086,17 +997,20 @@ fn print_imports(out: &mut Indenter, imports: Imports, this_file: (&str, &str)) /// `this_file` is passed and excluded for the case of recursive types: /// without it, the definition for a type like `struct Foo { foos: Vec }` /// would attempt to include `import super::foo::Foo`, which fails to compile. -fn gen_and_print_imports( - ctx: &GenCtx, +fn gen_and_print_imports( + module: &ModuleDef, out: &mut Indenter, - roots: Roots, - search_fn: SearchFn, + roots: &[(Identifier, AlgebraicTypeUse)], this_file: (&str, &str), -) where - SearchFn: FnOnce(&GenCtx, &mut Imports, Roots), -{ +) { let mut imports = BTreeSet::new(); - search_fn(ctx, &mut imports, roots); + for (_, ty) in roots { + ty.for_each_ref(|r| { + let type_name = type_name(module, r); + let module_name = module_name(&type_name); + imports.insert((module_name, type_name)); + }); + } print_imports(out, imports, this_file); } diff --git a/crates/cli/tests/snapshots/codegen__codegen_rust.snap b/crates/cli/tests/snapshots/codegen__codegen_rust.snap index dc85f775c1b..1d45c242cab 100644 --- a/crates/cli/tests/snapshots/codegen__codegen_rust.snap +++ b/crates/cli/tests/snapshots/codegen__codegen_rust.snap @@ -337,17 +337,17 @@ use spacetimedb_sdk::global_connection::with_connection_mut; use spacetimedb_sdk::spacetime_module::SpacetimeModule; use std::sync::Arc; -pub mod test_b; -pub mod namespace.test_c; -pub mod namespace.test_f; pub mod has_special_stuff; pub mod pk_multi_identity; pub mod point; pub mod private; pub mod repeating_test_arg; pub mod test_a; +pub mod test_b; pub mod test_d; pub mod test_e; +pub mod namespace_test_c; +pub mod namespace_test_f; pub mod add_player_reducer; pub mod add_private_reducer; pub mod delete_player_reducer; @@ -356,17 +356,17 @@ pub mod query_private_reducer; pub mod repeating_test_reducer; pub mod test_reducer; -pub use test_b::*; -pub use namespace.test_c::*; -pub use namespace.test_f::*; pub use has_special_stuff::*; pub use pk_multi_identity::*; pub use point::*; pub use private::*; pub use repeating_test_arg::*; pub use test_a::*; +pub use test_b::*; pub use test_d::*; pub use test_e::*; +pub use namespace_test_c::*; +pub use namespace_test_f::*; pub use add_player_reducer::*; pub use add_private_reducer::*; pub use delete_player_reducer::*; @@ -475,11 +475,9 @@ where }; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub enum Namespace.testC { +pub enum NamespaceTestC { Foo, - Bar, - } ''' "namespace_test_f.rs" = ''' @@ -497,7 +495,7 @@ pub enum Namespace.testC { }; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub enum Namespace.testF { +pub enum NamespaceTestF { Foo, Bar, @@ -732,6 +730,10 @@ impl RepeatingTestArg { #[allow(unused)] pub fn find_by_scheduled_id(scheduled_id: u64) -> Option { Self::find(|row| row.scheduled_id == scheduled_id) +} + #[allow(unused)] + pub fn filter_by_scheduled_at(scheduled_at: ScheduleAt) -> TableIter { + Self::filter(|row| row.scheduled_at == scheduled_at) } } ''' @@ -879,11 +881,11 @@ pub struct TestB { spacetimedb_lib, anyhow::{Result, anyhow}, }; -use super::namespace.test_c::Namespace.testC; +use super::namespace_test_c::NamespaceTestC; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct TestD { - pub test_c: Option::, + pub test_c: Option::, } impl TableType for TestD { @@ -955,8 +957,8 @@ impl TestE { spacetimedb_lib, anyhow::{Result, anyhow}, }; -use super::namespace.test_c::Namespace.testC; -use super::namespace.test_f::Namespace.testF; +use super::namespace_test_c::NamespaceTestC; +use super::namespace_test_f::NamespaceTestF; use super::test_a::TestA; use super::test_b::TestB; @@ -964,8 +966,8 @@ use super::test_b::TestB; pub struct TestArgs { pub arg: TestA, pub arg_2: TestB, - pub arg_3: Namespace.testC, - pub arg_4: Namespace.testF, + pub arg_3: NamespaceTestC, + pub arg_4: NamespaceTestF, } impl Reducer for TestArgs { @@ -976,8 +978,8 @@ impl Reducer for TestArgs { pub fn test( arg: TestA, arg_2: TestB, - arg_3: Namespace.testC, - arg_4: Namespace.testF, + arg_3: NamespaceTestC, + arg_4: NamespaceTestF, ) { TestArgs { arg, @@ -988,7 +990,7 @@ pub fn test( } #[allow(unused)] -pub fn on_test(mut __callback: impl FnMut(&Identity, Option
, &Status, &TestA, &TestB, &Namespace.testC, &Namespace.testF) + Send + 'static) -> ReducerCallbackId +pub fn on_test(mut __callback: impl FnMut(&Identity, Option
, &Status, &TestA, &TestB, &NamespaceTestC, &NamespaceTestF) + Send + 'static) -> ReducerCallbackId { TestArgs::on_reducer(move |__identity, __addr, __status, __args| { let TestArgs { @@ -1010,7 +1012,7 @@ __callback( } #[allow(unused)] -pub fn once_on_test(__callback: impl FnOnce(&Identity, Option
, &Status, &TestA, &TestB, &Namespace.testC, &Namespace.testF) + Send + 'static) -> ReducerCallbackId +pub fn once_on_test(__callback: impl FnOnce(&Identity, Option
, &Status, &TestA, &TestB, &NamespaceTestC, &NamespaceTestF) + Send + 'static) -> ReducerCallbackId { TestArgs::once_on_reducer(move |__identity, __addr, __status, __args| { let TestArgs { diff --git a/crates/client-api-messages/examples/get_ws_schema.rs b/crates/client-api-messages/examples/get_ws_schema.rs index dede6e0ce2c..7b50e80f941 100644 --- a/crates/client-api-messages/examples/get_ws_schema.rs +++ b/crates/client-api-messages/examples/get_ws_schema.rs @@ -1,12 +1,13 @@ use spacetimedb_client_api_messages::websocket::{ClientMessage, ServerMessage}; use spacetimedb_lib::ser::serde::SerializeWrapper; -use spacetimedb_lib::RawModuleDefV8; +use spacetimedb_lib::{RawModuleDef, RawModuleDefV8}; fn main() -> Result<(), serde_json::Error> { let module = RawModuleDefV8::with_builder(|module| { module.add_type::(); module.add_type::(); }); + let module = RawModuleDef::V8BackCompat(module); serde_json::to_writer(std::io::stdout().lock(), SerializeWrapper::from_ref(&module)) } diff --git a/crates/schema/src/def/validate/v8.rs b/crates/schema/src/def/validate/v8.rs index 1e2b8f962aa..c124e00fca9 100644 --- a/crates/schema/src/def/validate/v8.rs +++ b/crates/schema/src/def/validate/v8.rs @@ -50,11 +50,7 @@ fn upgrade_module(def: RawModuleDefV8, extra_errors: &mut Vec) let tables = convert_all(tables, |table| upgrade_table(table, &typespace, extra_errors)); let reducers = convert_all(reducers, upgrade_reducer); - let types = misc_exports - .into_iter() - .map(upgrade_misc_export_to_type) - .chain(tables.iter().map(type_def_for_table)) - .collect(); + let types = misc_exports.into_iter().map(upgrade_misc_export_to_type).collect(); RawModuleDefV9 { typespace, @@ -164,19 +160,6 @@ fn check_all_column_defs( } } -/// In `v8`, tables did NOT get a `MiscModuleExport` for their product type. -/// So, we generate these. -fn type_def_for_table(def: &RawTableDefV9) -> RawTypeDefV9 { - RawTypeDefV9 { - name: RawScopedTypeNameV9 { - scope: [].into(), - name: def.name.clone(), - }, - ty: def.product_type_ref, - custom_ordering: true, - } -} - /// Check a column definition. fn check_column( id: ColId, diff --git a/crates/schema/src/type_for_generate.rs b/crates/schema/src/type_for_generate.rs index b6976961d77..811050214e9 100644 --- a/crates/schema/src/type_for_generate.rs +++ b/crates/schema/src/type_for_generate.rs @@ -332,16 +332,24 @@ pub enum AlgebraicTypeUse { impl AlgebraicTypeUse { /// Extract all `AlgebraicTypeRef`s that are used in this type and add them to `buf`.` fn extract_refs(&self, buf: &mut HashSet) { + self.for_each_ref(|r| { + buf.insert(r); + }) + } + + pub fn for_each_ref(&self, mut f: impl FnMut(AlgebraicTypeRef)) { + self._for_each_ref(&mut f) + } + + fn _for_each_ref(&self, f: &mut impl FnMut(AlgebraicTypeRef)) { match self { - AlgebraicTypeUse::Ref(ref_) => { - buf.insert(*ref_); - } - AlgebraicTypeUse::Array(elem_ty) => elem_ty.extract_refs(buf), + AlgebraicTypeUse::Ref(ref_) => f(*ref_), + AlgebraicTypeUse::Array(elem_ty) => elem_ty._for_each_ref(f), AlgebraicTypeUse::Map { key, value } => { - key.extract_refs(buf); - value.extract_refs(buf); + key._for_each_ref(f); + value._for_each_ref(f); } - AlgebraicTypeUse::Option(elem_ty) => elem_ty.extract_refs(buf), + AlgebraicTypeUse::Option(elem_ty) => elem_ty._for_each_ref(f), _ => {} } } diff --git a/crates/schema/tests/validate_integration.rs b/crates/schema/tests/validate_integration.rs index 62d2c54901c..8f92caf3533 100644 --- a/crates/schema/tests/validate_integration.rs +++ b/crates/schema/tests/validate_integration.rs @@ -4,7 +4,7 @@ //! The name should refer to a path in the `modules` directory of this repo. use spacetimedb_cli::generate::extract_descriptions; -use spacetimedb_lib::{db::raw_def::v9::RawModuleDefV9, ser::serde::SerializeWrapper, RawModuleDefV8}; +use spacetimedb_lib::{db::raw_def::v9::RawModuleDefV9, ser::serde::SerializeWrapper, RawModuleDef}; use spacetimedb_primitives::TableId; use spacetimedb_schema::{ def::{ModuleDef, TableDef}, @@ -18,8 +18,11 @@ const TEST_TABLE_ID: TableId = TableId(1337); #[allow(clippy::disallowed_macros)] // LET ME PRINTLN >:( fn validate_module(module_name: &str) { let module = CompiledModule::compile(module_name, CompilationMode::Debug); - let raw_module_def: RawModuleDefV8 = + let raw_module_def: RawModuleDef = extract_descriptions(module.path()).expect("failed to extract module descriptions"); + let RawModuleDef::V8BackCompat(raw_module_def) = raw_module_def else { + panic!("no more v8") + }; // v8 -> ModuleDef let result = ModuleDef::try_from(raw_module_def.clone());