From 5e35bdb7a8e7b72c9294687bf418702bfec9fd22 Mon Sep 17 00:00:00 2001 From: Noa Date: Fri, 3 May 2024 17:25:54 -0500 Subject: [PATCH] Improve diagnostics and add ui tests --- Cargo.lock | 25 +++ Cargo.toml | 1 + crates/bindings-macro/src/lib.rs | 72 ++++---- crates/bindings-macro/src/module.rs | 2 - crates/bindings/Cargo.toml | 1 + crates/bindings/src/rt.rs | 67 ++++++- crates/bindings/tests/ui.rs | 5 + crates/bindings/tests/ui/reducers.rs | 28 +++ crates/bindings/tests/ui/reducers.stderr | 221 +++++++++++++++++++++++ crates/bindings/tests/ui/tables.rs | 16 ++ crates/bindings/tests/ui/tables.stderr | 108 +++++++++++ crates/sats/src/typespace.rs | 4 + 12 files changed, 506 insertions(+), 44 deletions(-) create mode 100644 crates/bindings/tests/ui.rs create mode 100644 crates/bindings/tests/ui/reducers.rs create mode 100644 crates/bindings/tests/ui/reducers.stderr create mode 100644 crates/bindings/tests/ui/tables.rs create mode 100644 crates/bindings/tests/ui/tables.stderr diff --git a/Cargo.lock b/Cargo.lock index d63d715467c..7d451380c90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -337,6 +337,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "basic-toml" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6" +dependencies = [ + "serde", +] + [[package]] name = "benchmarks-module" version = "0.1.0" @@ -4258,6 +4267,7 @@ dependencies = [ "spacetimedb-bindings-sys", "spacetimedb-lib", "spacetimedb-primitives", + "trybuild", ] [[package]] @@ -5752,6 +5762,21 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "trybuild" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419ecd263363827c5730386f418715766f584e2f874d32c23c5b00bd9727e7e" +dependencies = [ + "basic-toml", + "glob", + "once_cell", + "serde", + "serde_derive", + "serde_json", + "termcolor", +] + [[package]] name = "tungstenite" version = "0.21.0" diff --git a/Cargo.toml b/Cargo.toml index 2de5800e6fb..5b4fc1c1374 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -240,6 +240,7 @@ tracing-core = "0.1.31" tracing-flame = "0.2.0" tracing-log = "0.1.3" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } +trybuild = "1" typed-arena = "2.0" unicode-normalization = "0.1.23" unicode-ident = "1.0.12" diff --git a/crates/bindings-macro/src/lib.rs b/crates/bindings-macro/src/lib.rs index dcab7835cac..3fc83ae5df9 100644 --- a/crates/bindings-macro/src/lib.rs +++ b/crates/bindings-macro/src/lib.rs @@ -17,7 +17,6 @@ use proc_macro::TokenStream as StdTokenStream; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; use std::borrow::Cow; -use std::collections::HashMap; use std::time::Duration; use syn::ext::IdentExt; use syn::meta::ParseNestedMeta; @@ -330,6 +329,15 @@ fn reducer_impl(args: ReducerArgs, original_function: &ItemFn) -> syn::Result {} + syn::GenericParam::Type(_) => return Err(err("type parameters are not allowed on reducers")), + syn::GenericParam::Const(_) => return Err(err("const parameters are not allowed on reducers")), + } + } + let lifecycle = args.lifecycle.iter().filter_map(|lc| lc.to_lifecycle_value()); // Extract all function parameters, except for `self` ones that aren't allowed. @@ -354,6 +362,8 @@ fn reducer_impl(args: ReducerArgs, original_function: &ItemFn) -> syn::Result>(); + let first_arg_ty = arg_tys.first().into_iter(); + let rest_arg_tys = arg_tys.iter().skip(1); // Extract the return type. let ret_ty = match &original_function.sig.output { @@ -364,13 +374,8 @@ fn reducer_impl(args: ReducerArgs, original_function: &ItemFn) -> syn::Result spacetimedb::ReducerResult { - #(spacetimedb::rt::assert_reducer_arg::<#arg_tys>();)* - #(spacetimedb::rt::assert_reducer_ret::<#ret_ty>();)* - spacetimedb::rt::invoke_reducer(#func_name, __ctx, __args) - } - }; + let lt_params = &original_function.sig.generics; + let lt_where_clause = <_params.where_clause; let generated_describe_function = quote! { #[export_name = #register_describer_symbol] @@ -385,14 +390,24 @@ fn reducer_impl(args: ReducerArgs, original_function: &ItemFn) -> syn::Result::_ITEM;)* + #(let _ = <#rest_arg_tys as spacetimedb::rt::ReducerArg>::_ITEM;)* + #(let _ = <#ret_ty as spacetimedb::rt::IntoReducerResult>::into_result;)* + } + }; + impl #func_name { + fn invoke(__ctx: spacetimedb::ReducerContext, __args: &[u8]) -> spacetimedb::ReducerResult { + spacetimedb::rt::invoke_reducer(#func_name, __ctx, __args) + } + } + #[automatically_derived] impl spacetimedb::rt::ReducerInfo for #func_name { const NAME: &'static str = #reducer_name; #(const LIFECYCLE: Option = Some(#lifecycle);)* const ARG_NAMES: &'static [Option<&'static str>] = &[#(#opt_arg_names),*]; - const INVOKE: spacetimedb::rt::ReducerFn = { - #generated_function - __reducer - }; + const INVOKE: spacetimedb::rt::ReducerFn = #func_name::invoke; } }) } @@ -887,6 +902,15 @@ fn table_impl(mut args: TableArgs, mut item: MutItem) -> syn:: return Err(syn::Error::new(Span::call_site(), "spacetimedb table must be a struct")); }; + for param in &item.generics.params { + let err = |msg| syn::Error::new_spanned(param, msg); + match param { + syn::GenericParam::Lifetime(_) => {} + syn::GenericParam::Type(_) => return Err(err("type parameters are not allowed on tables")), + syn::GenericParam::Const(_) => return Err(err("const parameters are not allowed on tables")), + } + } + let table_id_from_name_func = quote! { fn table_id() -> spacetimedb::TableId { static TABLE_ID: std::sync::OnceLock = std::sync::OnceLock::new(); @@ -1069,28 +1093,6 @@ fn table_impl(mut args: TableArgs, mut item: MutItem) -> syn:: })* }; - // Attempt to improve the compile error when a table field doesn't satisfy - // the supertraits of `TableType`. We make it so the field span indicates - // which fields are offenders, and error reporting stops if the field doesn't - // implement `SpacetimeType` (which happens to be the derive macro one is - // supposed to use). That is, the user doesn't see errors about `Serialize`, - // `Deserialize` not being satisfied, which they wouldn't know what to do - // about. - let assert_fields_are_spacetimetypes = { - let trait_ident = Ident::new("AssertSpacetimeFields", Span::call_site()); - let field_impls = fields - .iter() - .map(|field| (field.ty, field.span)) - .collect::>() - .into_iter() - .map(|(ty, span)| quote_spanned!(span=> impl #trait_ident for #ty {})); - - quote_spanned! {item.span()=> - trait #trait_ident: spacetimedb::SpacetimeType {} - #(#field_impls)* - } - }; - let row_type_to_table = quote!(<#row_type as spacetimedb::table::__MapRowTypeToTable>::Table); // Output all macro data @@ -1116,7 +1118,7 @@ fn table_impl(mut args: TableArgs, mut item: MutItem) -> syn:: let emission = quote! { const _: () = { - #assert_fields_are_spacetimetypes + #(let _ = <#field_types as spacetimedb::rt::TableColumn>::_ITEM;)* }; #trait_def diff --git a/crates/bindings-macro/src/module.rs b/crates/bindings-macro/src/module.rs index 4faf0793b6c..88057547595 100644 --- a/crates/bindings-macro/src/module.rs +++ b/crates/bindings-macro/src/module.rs @@ -32,7 +32,6 @@ pub(crate) struct SatsField<'a> { pub name: Option, pub ty: &'a syn::Type, pub original_attrs: &'a [syn::Attribute], - pub span: Span, } pub(crate) struct SatsVariant<'a> { @@ -57,7 +56,6 @@ pub(crate) fn sats_type_from_derive( name: field.ident.as_ref().map(syn::Ident::to_string), ty: &field.ty, original_attrs: &field.attrs, - span: field.span(), }); SatsTypeData::Product(fields.collect()) } diff --git a/crates/bindings/Cargo.toml b/crates/bindings/Cargo.toml index 823246a3018..7d725742fc5 100644 --- a/crates/bindings/Cargo.toml +++ b/crates/bindings/Cargo.toml @@ -37,3 +37,4 @@ getrandom = { workspace = true, optional = true, features = ["custom"] } [dev-dependencies] insta.workspace = true +trybuild.workspace = true diff --git a/crates/bindings/src/rt.rs b/crates/bindings/src/rt.rs index d55fe130b3c..1abac5aa3f0 100644 --- a/crates/bindings/src/rt.rs +++ b/crates/bindings/src/rt.rs @@ -32,6 +32,15 @@ pub fn invoke_reducer<'a, A: Args<'a>>( with_timestamp_set(ctx.timestamp, || reducer.invoke(&ctx, args)) } /// A trait for types representing the *execution logic* of a reducer. +#[diagnostic::on_unimplemented( + message = "invalid reducer signature", + label = "this reducer signature is not valid", + note = "", + note = "reducer signatures must match the following pattern:", + note = " `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]`", + note = "where each `Ti` type implements `SpacetimeType`.", + note = "" +)] pub trait Reducer<'de, A: Args<'de>> { fn invoke(&self, ctx: &ReducerContext, args: A) -> ReducerResult; } @@ -67,6 +76,10 @@ pub trait Args<'de>: Sized { } /// A trait of types representing the result of executing a reducer. +#[diagnostic::on_unimplemented( + message = "`{Self}` is not a valid reducer return type", + note = "reducers cannot return values -- you can only return `()` or `Result<(), impl Display>`" +)] pub trait IntoReducerResult { /// Convert the result into form where there is no value /// and the error message is a string. @@ -85,17 +98,57 @@ impl IntoReducerResult for Result<(), E> { } } +#[diagnostic::on_unimplemented( + message = "the first argument of a reducer must be `&ReducerContext`", + note = "all reducers must take `&ReducerContext` as their first argument" +)] +pub trait ReducerContextArg { + // a little hack used in the macro to make error messages nicer. it generates ::_ITEM + #[doc(hidden)] + const _ITEM: () = (); +} +impl ReducerContextArg for &ReducerContext {} + /// A trait of types that can be an argument of a reducer. -pub trait ReducerArg<'de> {} -impl<'de, T: Deserialize<'de>> ReducerArg<'de> for T {} -impl ReducerArg<'_> for &ReducerContext {} -/// Assert that `T: ReducerArg`. -pub fn assert_reducer_arg<'de, T: ReducerArg<'de>>() {} -/// Assert that `T: IntoReducerResult`. -pub fn assert_reducer_ret() {} +#[diagnostic::on_unimplemented( + message = "the reducer argument `{Self}` does not implement `SpacetimeType`", + note = "if you own the type, try adding `#[derive(SpacetimeType)]` to its definition" +)] +pub trait ReducerArg { + // a little hack used in the macro to make error messages nicer. it generates ::_ITEM + #[doc(hidden)] + const _ITEM: () = (); +} +impl ReducerArg for T {} + /// Assert that a reducer type-checks with a given type. pub const fn assert_reducer_typecheck<'de, A: Args<'de>>(_: impl Reducer<'de, A> + Copy) {} +// the macro generates ::make_type:: +pub struct DummyTypespace; +impl TypespaceBuilder for DummyTypespace { + fn add( + &mut self, + _: std::any::TypeId, + _: Option<&'static str>, + _: impl FnOnce(&mut Self) -> spacetimedb_lib::AlgebraicType, + ) -> spacetimedb_lib::AlgebraicType { + unreachable!() + } +} + +#[diagnostic::on_unimplemented( + message = "the column type `{Self}` does not implement `SpacetimeType`", + note = "table column types all must implement `SpacetimeType`", + note = "if you own the type, try adding `#[derive(SpacetimeType)]` to its definition" +)] +pub trait TableColumn { + // a little hack used in the macro to make error messages nicer. it generates ::_ITEM + #[doc(hidden)] + const _ITEM: () = (); +} +impl TableColumn for T {} + /// Used in the last type parameter of `Reducer` to indicate that the /// context argument *should* be passed to the reducer logic. pub struct ContextArg; diff --git a/crates/bindings/tests/ui.rs b/crates/bindings/tests/ui.rs new file mode 100644 index 00000000000..870c2f95ec1 --- /dev/null +++ b/crates/bindings/tests/ui.rs @@ -0,0 +1,5 @@ +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/*.rs"); +} diff --git a/crates/bindings/tests/ui/reducers.rs b/crates/bindings/tests/ui/reducers.rs new file mode 100644 index 00000000000..e46d2887a58 --- /dev/null +++ b/crates/bindings/tests/ui/reducers.rs @@ -0,0 +1,28 @@ +use spacetimedb::ReducerContext; + +struct Test; + +#[spacetimedb::reducer] +fn bad_type(_ctx: &ReducerContext, _a: Test) {} + +#[spacetimedb::reducer] +fn bad_return_type(_ctx: &ReducerContext) -> Test { + Test +} + +#[spacetimedb::reducer] +fn lifetime<'a>(_ctx: &ReducerContext, _a: &'a str) {} + +#[spacetimedb::reducer] +fn type_param() {} + +#[spacetimedb::reducer] +fn const_param() {} + +#[spacetimedb::reducer] +fn missing_ctx(_a: u8) {} + +#[spacetimedb::reducer] +fn ctx_by_val(_ctx: ReducerContext, _a: u8) {} + +fn main() {} diff --git a/crates/bindings/tests/ui/reducers.stderr b/crates/bindings/tests/ui/reducers.stderr new file mode 100644 index 00000000000..4211b9237c1 --- /dev/null +++ b/crates/bindings/tests/ui/reducers.stderr @@ -0,0 +1,221 @@ +error: type parameters are not allowed on reducers + --> tests/ui/reducers.rs:17:15 + | +17 | fn type_param() {} + | ^ + +error: const parameters are not allowed on reducers + --> tests/ui/reducers.rs:20:16 + | +20 | fn const_param() {} + | ^^^^^^^^^^^ + +error[E0277]: invalid reducer signature + --> tests/ui/reducers.rs:6:4 + | +5 | #[spacetimedb::reducer] + | ----------------------- required by a bound introduced by this call +6 | fn bad_type(_ctx: &ReducerContext, _a: Test) {} + | ^^^^^^^^ this reducer signature is not valid + | + = help: the trait `Reducer<'_, _>` is not implemented for fn item `for<'a> fn(&'a ReducerContext, Test) {bad_type}` + = note: + = note: reducer signatures must match the following pattern: + = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` + = note: where each `Ti` type implements `SpacetimeType`. + = note: +note: required by a bound in `register_reducer` + --> src/rt.rs + | + | pub fn register_reducer<'a, A: Args<'a>, I: ReducerInfo>(_: impl Reducer<'a, A>) { + | ^^^^^^^^^^^^^^ required by this bound in `register_reducer` + +error[E0277]: the reducer argument `Test` does not implement `SpacetimeType` + --> tests/ui/reducers.rs:6:40 + | +6 | fn bad_type(_ctx: &ReducerContext, _a: Test) {} + | ^^^^ the trait `SpacetimeType` is not implemented for `Test`, which is required by `Test: ReducerArg` + | + = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition + = note: required for `Test` to implement `ReducerArg` + +error[E0277]: invalid reducer signature + --> tests/ui/reducers.rs:6:4 + | +5 | #[spacetimedb::reducer] + | ----------------------- required by a bound introduced by this call +6 | fn bad_type(_ctx: &ReducerContext, _a: Test) {} + | ^^^^^^^^ this reducer signature is not valid + | + = help: the trait `Reducer<'_, _>` is not implemented for fn item `for<'a> fn(&'a ReducerContext, Test) {bad_type}` + = note: + = note: reducer signatures must match the following pattern: + = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` + = note: where each `Ti` type implements `SpacetimeType`. + = note: +note: required by a bound in `invoke_reducer` + --> src/rt.rs + | + | pub fn invoke_reducer<'a, A: Args<'a>>( + | -------------- required by a bound in this function + | reducer: impl Reducer<'a, A>, + | ^^^^^^^^^^^^^^ required by this bound in `invoke_reducer` + +error[E0277]: invalid reducer signature + --> tests/ui/reducers.rs:9:4 + | +8 | #[spacetimedb::reducer] + | ----------------------- required by a bound introduced by this call +9 | fn bad_return_type(_ctx: &ReducerContext) -> Test { + | ^^^^^^^^^^^^^^^ this reducer signature is not valid + | + = help: the trait `Reducer<'_, _>` is not implemented for fn item `for<'a> fn(&'a ReducerContext) -> Test {bad_return_type}` + = note: + = note: reducer signatures must match the following pattern: + = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` + = note: where each `Ti` type implements `SpacetimeType`. + = note: +note: required by a bound in `register_reducer` + --> src/rt.rs + | + | pub fn register_reducer<'a, A: Args<'a>, I: ReducerInfo>(_: impl Reducer<'a, A>) { + | ^^^^^^^^^^^^^^ required by this bound in `register_reducer` + +error[E0277]: `Test` is not a valid reducer return type + --> tests/ui/reducers.rs:9:46 + | +9 | fn bad_return_type(_ctx: &ReducerContext) -> Test { + | ^^^^ the trait `IntoReducerResult` is not implemented for `Test` + | + = note: reducers cannot return values -- you can only return `()` or `Result<(), impl Display>` + = help: the following other types implement trait `IntoReducerResult`: + Result<(), E> + () + +error[E0277]: invalid reducer signature + --> tests/ui/reducers.rs:9:4 + | +8 | #[spacetimedb::reducer] + | ----------------------- required by a bound introduced by this call +9 | fn bad_return_type(_ctx: &ReducerContext) -> Test { + | ^^^^^^^^^^^^^^^ this reducer signature is not valid + | + = help: the trait `Reducer<'_, _>` is not implemented for fn item `for<'a> fn(&'a ReducerContext) -> Test {bad_return_type}` + = note: + = note: reducer signatures must match the following pattern: + = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` + = note: where each `Ti` type implements `SpacetimeType`. + = note: +note: required by a bound in `invoke_reducer` + --> src/rt.rs + | + | pub fn invoke_reducer<'a, A: Args<'a>>( + | -------------- required by a bound in this function + | reducer: impl Reducer<'a, A>, + | ^^^^^^^^^^^^^^ required by this bound in `invoke_reducer` + +error[E0277]: invalid reducer signature + --> tests/ui/reducers.rs:23:4 + | +22 | #[spacetimedb::reducer] + | ----------------------- required by a bound introduced by this call +23 | fn missing_ctx(_a: u8) {} + | ^^^^^^^^^^^ this reducer signature is not valid + | + = help: the trait `Reducer<'_, _>` is not implemented for fn item `fn(u8) {missing_ctx}` + = note: + = note: reducer signatures must match the following pattern: + = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` + = note: where each `Ti` type implements `SpacetimeType`. + = note: +note: required by a bound in `register_reducer` + --> src/rt.rs + | + | pub fn register_reducer<'a, A: Args<'a>, I: ReducerInfo>(_: impl Reducer<'a, A>) { + | ^^^^^^^^^^^^^^ required by this bound in `register_reducer` + +error[E0277]: the first argument of a reducer must be `&ReducerContext` + --> tests/ui/reducers.rs:23:20 + | +23 | fn missing_ctx(_a: u8) {} + | ^^ the trait `ReducerContextArg` is not implemented for `u8` + | + = note: all reducers must take `&ReducerContext` as their first argument + = help: the trait `ReducerContextArg` is implemented for `&ReducerContext` + +error[E0277]: invalid reducer signature + --> tests/ui/reducers.rs:23:4 + | +22 | #[spacetimedb::reducer] + | ----------------------- required by a bound introduced by this call +23 | fn missing_ctx(_a: u8) {} + | ^^^^^^^^^^^ this reducer signature is not valid + | + = help: the trait `Reducer<'_, _>` is not implemented for fn item `fn(u8) {missing_ctx}` + = note: + = note: reducer signatures must match the following pattern: + = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` + = note: where each `Ti` type implements `SpacetimeType`. + = note: +note: required by a bound in `invoke_reducer` + --> src/rt.rs + | + | pub fn invoke_reducer<'a, A: Args<'a>>( + | -------------- required by a bound in this function + | reducer: impl Reducer<'a, A>, + | ^^^^^^^^^^^^^^ required by this bound in `invoke_reducer` + +error[E0277]: invalid reducer signature + --> tests/ui/reducers.rs:26:4 + | +25 | #[spacetimedb::reducer] + | ----------------------- required by a bound introduced by this call +26 | fn ctx_by_val(_ctx: ReducerContext, _a: u8) {} + | ^^^^^^^^^^ this reducer signature is not valid + | + = help: the trait `Reducer<'_, _>` is not implemented for fn item `fn(ReducerContext, u8) {ctx_by_val}` + = note: + = note: reducer signatures must match the following pattern: + = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` + = note: where each `Ti` type implements `SpacetimeType`. + = note: +note: required by a bound in `register_reducer` + --> src/rt.rs + | + | pub fn register_reducer<'a, A: Args<'a>, I: ReducerInfo>(_: impl Reducer<'a, A>) { + | ^^^^^^^^^^^^^^ required by this bound in `register_reducer` + +error[E0277]: the first argument of a reducer must be `&ReducerContext` + --> tests/ui/reducers.rs:26:21 + | +26 | fn ctx_by_val(_ctx: ReducerContext, _a: u8) {} + | ^^^^^^^^^^^^^^ + | | + | the trait `ReducerContextArg` is not implemented for `ReducerContext` + | the trait `ReducerContextArg` is not implemented for `ReducerContext` + | + = note: the trait bound `ReducerContext: ReducerContextArg` is not satisfied + = note: all reducers must take `&ReducerContext` as their first argument + = help: the trait `ReducerContextArg` is implemented for `&ReducerContext` + +error[E0277]: invalid reducer signature + --> tests/ui/reducers.rs:26:4 + | +25 | #[spacetimedb::reducer] + | ----------------------- required by a bound introduced by this call +26 | fn ctx_by_val(_ctx: ReducerContext, _a: u8) {} + | ^^^^^^^^^^ this reducer signature is not valid + | + = help: the trait `Reducer<'_, _>` is not implemented for fn item `fn(ReducerContext, u8) {ctx_by_val}` + = note: + = note: reducer signatures must match the following pattern: + = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` + = note: where each `Ti` type implements `SpacetimeType`. + = note: +note: required by a bound in `invoke_reducer` + --> src/rt.rs + | + | pub fn invoke_reducer<'a, A: Args<'a>>( + | -------------- required by a bound in this function + | reducer: impl Reducer<'a, A>, + | ^^^^^^^^^^^^^^ required by this bound in `invoke_reducer` diff --git a/crates/bindings/tests/ui/tables.rs b/crates/bindings/tests/ui/tables.rs new file mode 100644 index 00000000000..c698f74ce3d --- /dev/null +++ b/crates/bindings/tests/ui/tables.rs @@ -0,0 +1,16 @@ +struct Test; + +#[spacetimedb::table(name = table)] +struct Table { + x: Test, +} + +#[spacetimedb::table(name = type_param)] +struct TypeParam { + t: T, +} + +#[spacetimedb::table(name = const_param)] +struct ConstParam {} + +fn main() {} diff --git a/crates/bindings/tests/ui/tables.stderr b/crates/bindings/tests/ui/tables.stderr new file mode 100644 index 00000000000..0c4876c2306 --- /dev/null +++ b/crates/bindings/tests/ui/tables.stderr @@ -0,0 +1,108 @@ +error: type parameters are not allowed on tables + --> tests/ui/tables.rs:9:18 + | +9 | struct TypeParam { + | ^ + +error: const parameters are not allowed on tables + --> tests/ui/tables.rs:14:19 + | +14 | struct ConstParam {} + | ^^^^^^^^^^^ + +error[E0277]: the column type `Test` does not implement `SpacetimeType` + --> tests/ui/tables.rs:5:8 + | +5 | x: Test, + | ^^^^ the trait `SpacetimeType` is not implemented for `Test`, which is required by `Test: TableColumn` + | + = note: table column types all must implement `SpacetimeType` + = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition + = note: required for `Test` to implement `TableColumn` + +error[E0277]: the trait bound `Test: SpacetimeType` is not satisfied + --> tests/ui/tables.rs:5:8 + | +5 | x: Test, + | ^^^^ the trait `SpacetimeType` is not implemented for `Test` + | + = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition + = help: the following other types implement trait `SpacetimeType`: + bool + i8 + i16 + i32 + i64 + i128 + u8 + u16 + and $N others + +error[E0277]: the trait bound `Test: Deserialize<'de>` is not satisfied + --> tests/ui/tables.rs:5:8 + | +3 | #[spacetimedb::table(name = table)] + | ----------------------------------- required by a bound introduced by this call +4 | struct Table { +5 | x: Test, + | ^^^^ the trait `Deserialize<'de>` is not implemented for `Test` + | + = help: the following other types implement trait `Deserialize<'de>`: + bool + i8 + i16 + i32 + i64 + i128 + u8 + u16 + and $N others +note: required by a bound in `spacetimedb::spacetimedb_lib::de::SeqProductAccess::next_element` + --> $WORKSPACE/crates/sats/src/de.rs + | + | fn next_element>(&mut self) -> Result, Self::Error> { + | ^^^^^^^^^^^^^^^^ required by this bound in `SeqProductAccess::next_element` + +error[E0277]: the trait bound `Test: Deserialize<'_>` is not satisfied + --> tests/ui/tables.rs:5:8 + | +5 | x: Test, + | ^^^^ the trait `Deserialize<'_>` is not implemented for `Test` + | + = help: the following other types implement trait `Deserialize<'de>`: + bool + i8 + i16 + i32 + i64 + i128 + u8 + u16 + and $N others +note: required by a bound in `get_field_value` + --> $WORKSPACE/crates/sats/src/de.rs + | + | fn get_field_value>(&mut self) -> Result { + | ^^^^^^^^^^^^^^^^ required by this bound in `NamedProductAccess::get_field_value` + +error[E0277]: the trait bound `Test: Serialize` is not satisfied + --> tests/ui/tables.rs:5:8 + | +5 | x: Test, + | ^^^^ the trait `Serialize` is not implemented for `Test` + | + = help: the following other types implement trait `Serialize`: + bool + i8 + i16 + i32 + i64 + i128 + u8 + u16 + and $N others +note: required by a bound in `spacetimedb::spacetimedb_lib::ser::SerializeNamedProduct::serialize_element` + --> $WORKSPACE/crates/sats/src/ser.rs + | + | fn serialize_element(&mut self, name: Option<&str>, elem: &T) -> Result<(), Self::Error>; + | ^^^^^^^^^ required by this bound in `SerializeNamedProduct::serialize_element` diff --git a/crates/sats/src/typespace.rs b/crates/sats/src/typespace.rs index 6579643c7be..b0a8f03bf05 100644 --- a/crates/sats/src/typespace.rs +++ b/crates/sats/src/typespace.rs @@ -231,6 +231,9 @@ pub trait GroundSpacetimeType { /// A trait for Rust types that can be represented as an [`AlgebraicType`] /// provided a typing context `typespace`. +// 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. +// keep this note in sync with the ones on spacetimedb::rt::{ReducerArg, TableColumn} +#[diagnostic::on_unimplemented(note = "if you own the type, try adding `#[derive(SpacetimeType)]` to its definition")] pub trait SpacetimeType { /// Returns an `AlgebraicType` representing the type for `Self` in SATS /// and in the typing context in `typespace`. @@ -327,6 +330,7 @@ impl_primitives! { 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, ts => T::make_type(ts)); impl_st!([T: ?Sized] Rc, ts => T::make_type(ts)); impl_st!([T: ?Sized] Arc, ts => T::make_type(ts));