From 39e89e92a2543580f329654bfa7d93ed92cf620c Mon Sep 17 00:00:00 2001 From: Noa Date: Wed, 23 Oct 2024 13:03:05 -0500 Subject: [PATCH] Don't autogen schedule fields --- crates/bindings-macro/src/lib.rs | 147 +++++++++++++-------------- crates/bindings/src/table.rs | 13 --- modules/rust-wasm-test/src/lib.rs | 5 + smoketests/tests/modules.py | 5 + smoketests/tests/schedule_reducer.py | 10 ++ 5 files changed, 91 insertions(+), 89 deletions(-) diff --git a/crates/bindings-macro/src/lib.rs b/crates/bindings-macro/src/lib.rs index 0adca26ca0d..2a2ce21a93b 100644 --- a/crates/bindings-macro/src/lib.rs +++ b/crates/bindings-macro/src/lib.rs @@ -57,6 +57,7 @@ mod sym { symbol!(public); symbol!(sats); symbol!(scheduled); + symbol!(scheduled_at); symbol!(unique); symbol!(update); @@ -95,28 +96,18 @@ mod sym { /// Parses `item`, passing it and `args` to `f`, /// which should return only whats newly added, excluding the `item`. /// Returns the full token stream `extra_attr item newly_added`. -fn cvt_attr( +fn cvt_attr( args: StdTokenStream, item: StdTokenStream, extra_attr: TokenStream, - f: impl FnOnce(TokenStream, MutItem<'_, Item>) -> syn::Result, + f: impl FnOnce(TokenStream, &Item) -> syn::Result, ) -> StdTokenStream { let item: TokenStream = item.into(); - let mut parsed_item = match syn::parse2::(item.clone()) { + let parsed_item = match syn::parse2::(item.clone()) { Ok(i) => i, Err(e) => return TokenStream::from_iter([item, e.into_compile_error()]).into(), }; - let mut modified = false; - let mut_item = MutItem { - val: &mut parsed_item, - modified: &mut modified, - }; - let generated = f(args.into(), mut_item).unwrap_or_else(syn::Error::into_compile_error); - let item = if modified { - parsed_item.into_token_stream() - } else { - item - }; + let generated = f(args.into(), &parsed_item).unwrap_or_else(syn::Error::into_compile_error); TokenStream::from_iter([extra_attr, item, generated]).into() } @@ -124,23 +115,6 @@ fn ident_to_litstr(ident: &Ident) -> syn::LitStr { syn::LitStr::new(&ident.to_string(), ident.span()) } -struct MutItem<'a, T> { - val: &'a mut T, - modified: &'a mut bool, -} -impl std::ops::Deref for MutItem<'_, T> { - type Target = T; - fn deref(&self) -> &Self::Target { - self.val - } -} -impl std::ops::DerefMut for MutItem<'_, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - *self.modified = true; - self.val - } -} - /// Convert the `dur`ation to a `TokenStream` corresponding to it. fn duration_totokens(dur: Duration) -> TokenStream { let (secs, nanos) = (dur.as_secs(), dur.subsec_nanos()); @@ -306,7 +280,7 @@ impl ReducerArgs { pub fn reducer(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream { cvt_attr::(args, item, quote!(), |args, original_function| { let args = ReducerArgs::parse(args)?; - reducer_impl(args, &original_function) + reducer_impl(args, original_function) }) } @@ -437,21 +411,6 @@ impl TableAccess { } } -// add scheduled_id and scheduled_at fields to the struct -fn add_scheduled_fields(item: &mut syn::DeriveInput) { - if let syn::Data::Struct(struct_data) = &mut item.data { - if let syn::Fields::Named(fields) = &mut struct_data.fields { - let extra_fields: syn::FieldsNamed = parse_quote!({ - #[primary_key] - #[auto_inc] - pub scheduled_id: u64, - pub scheduled_at: spacetimedb::spacetimedb_lib::ScheduleAt, - }); - fields.named.extend(extra_fields.named); - } - } -} - struct IndexArg { name: Ident, kind: IndexType, @@ -836,7 +795,7 @@ pub fn table(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream { } #[doc(hidden)] -#[proc_macro_derive(__TableHelper, attributes(sats, unique, auto_inc, primary_key, index))] +#[proc_macro_derive(__TableHelper, attributes(sats, unique, auto_inc, primary_key, index, scheduled_at))] pub fn table_helper(_input: StdTokenStream) -> StdTokenStream { Default::default() } @@ -853,6 +812,7 @@ enum ColumnAttr { AutoInc(Span), PrimaryKey(Span), Index(IndexArg), + ScheduledAt(Span), } impl ColumnAttr { @@ -872,21 +832,16 @@ impl ColumnAttr { } else if ident == sym::primary_key { attr.meta.require_path_only()?; Some(ColumnAttr::PrimaryKey(ident.span())) + } else if ident == sym::scheduled_at { + attr.meta.require_path_only()?; + Some(ColumnAttr::ScheduledAt(ident.span())) } else { None }) } } -fn table_impl(mut args: TableArgs, mut item: MutItem) -> syn::Result { - let scheduled_reducer_type_check = args.scheduled.as_ref().map(|reducer| { - add_scheduled_fields(&mut item); - let struct_name = &item.ident; - quote! { - const _: () = spacetimedb::rt::scheduled_reducer_typecheck::<#struct_name>(#reducer); - } - }); - +fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::Result { let vis = &item.vis; let sats_ty = module::sats_type_from_derive(&item, quote!(spacetimedb::spacetimedb_lib))?; @@ -926,6 +881,7 @@ fn table_impl(mut args: TableArgs, mut item: MutItem) -> syn:: let mut unique_columns = vec![]; let mut sequenced_columns = vec![]; let mut primary_key_column = None; + let mut scheduled_at_column = None; for (i, field) in fields.iter().enumerate() { let col_num = i as u16; @@ -934,6 +890,7 @@ fn table_impl(mut args: TableArgs, mut item: MutItem) -> syn:: let mut unique = None; let mut auto_inc = None; let mut primary_key = None; + let mut scheduled_at = None; for attr in field.original_attrs { let Some(attr) = ColumnAttr::parse(attr, field_ident)? else { continue; @@ -952,6 +909,10 @@ fn table_impl(mut args: TableArgs, mut item: MutItem) -> syn:: primary_key = Some(span); } ColumnAttr::Index(index_arg) => args.indices.push(index_arg), + ColumnAttr::ScheduledAt(span) => { + check_duplicate(&scheduled_at, span)?; + scheduled_at = Some(span); + } } } @@ -977,10 +938,27 @@ fn table_impl(mut args: TableArgs, mut item: MutItem) -> syn:: check_duplicate_msg(&primary_key_column, span, "can only have one primary key per table")?; primary_key_column = Some(column); } + if let Some(span) = scheduled_at { + check_duplicate_msg( + &scheduled_at_column, + span, + "can only have one scheduled_at column per table", + )?; + scheduled_at_column = Some(column); + } columns.push(column); } + let scheduled_at_typecheck = scheduled_at_column.map(|col| { + let ty = col.ty; + quote!(let _ = |x: #ty| { let _: spacetimedb::ScheduleAt = x; };) + }); + let scheduled_id_typecheck = primary_key_column.filter(|_| args.scheduled.is_some()).map(|col| { + let ty = col.ty; + quote!(spacetimedb::rt::assert_scheduled_table_primary_key::<#ty>();) + }); + let row_type = quote!(#original_struct_ident); let mut indices = args @@ -1031,17 +1009,46 @@ fn table_impl(mut args: TableArgs, mut item: MutItem) -> syn:: let primary_col_id = primary_key_column.iter().map(|col| col.index); let sequence_col_ids = sequenced_columns.iter().map(|col| col.index); + let scheduled_reducer_type_check = args.scheduled.as_ref().map(|reducer| { + quote! { + spacetimedb::rt::scheduled_reducer_typecheck::<#original_struct_ident>(#reducer); + } + }); let schedule = args .scheduled .as_ref() .map(|reducer| { - // scheduled_at was inserted as the last field - let scheduled_at_id = (fields.len() - 1) as u16; - quote!(spacetimedb::table::ScheduleDesc { + // better error message when both are missing + if scheduled_at_column.is_none() && primary_key_column.is_none() { + return Err(syn::Error::new( + Span::call_site(), + "scheduled table missing required columns; add these to your struct:\n\ + #[primary_key]\n\ + #[auto_inc]\n\ + scheduled_id: u64,\n\ + #[scheduled_at]\n\ + scheduled_at: spacetimedb::ScheduleAt,", + )); + } + let scheduled_at_column = scheduled_at_column.ok_or_else(|| { + syn::Error::new( + Span::call_site(), + "scheduled tables must have a `#[scheduled_at] scheduled_at: spacetimedb::ScheduleAt` column.", + ) + })?; + let scheduled_at_id = scheduled_at_column.index; + if primary_key_column.is_none() { + return Err(syn::Error::new( + Span::call_site(), + "scheduled tables should have a `#[primary_key] #[auto_inc] scheduled_id: u64` column", + )); + } + Ok(quote!(spacetimedb::table::ScheduleDesc { reducer_name: <#reducer as spacetimedb::rt::ReducerInfo>::NAME, scheduled_at_column: #scheduled_at_id, - }) + })) }) + .transpose()? .into_iter(); let unique_err = if !unique_columns.is_empty() { @@ -1055,7 +1062,6 @@ fn table_impl(mut args: TableArgs, mut item: MutItem) -> syn:: quote!(::core::convert::Infallible) }; - let field_names = fields.iter().map(|f| f.ident.unwrap()).collect::>(); let field_types = fields.iter().map(|f| f.ty).collect::>(); let tabletype_impl = quote! { @@ -1090,16 +1096,6 @@ fn table_impl(mut args: TableArgs, mut item: MutItem) -> syn:: } }; - let col_num = 0u16..; - let field_access_impls = quote! { - #(impl spacetimedb::table::FieldAccess<#col_num> for #original_struct_ident { - type Field = #field_types; - fn get_field(&self) -> &Self::Field { - &self.#field_names - } - })* - }; - let row_type_to_table = quote!(<#row_type as spacetimedb::table::__MapRowTypeToTable>::Table); // Output all macro data @@ -1126,6 +1122,9 @@ fn table_impl(mut args: TableArgs, mut item: MutItem) -> syn:: let emission = quote! { const _: () = { #(let _ = <#field_types as spacetimedb::rt::TableColumn>::_ITEM;)* + #scheduled_reducer_type_check + #scheduled_at_typecheck + #scheduled_id_typecheck }; #trait_def @@ -1160,10 +1159,6 @@ fn table_impl(mut args: TableArgs, mut item: MutItem) -> syn:: #schema_impl #deserialize_impl #serialize_impl - - #field_access_impls - - #scheduled_reducer_type_check }; if std::env::var("PROC_MACRO_DEBUG").is_ok() { diff --git a/crates/bindings/src/table.rs b/crates/bindings/src/table.rs index 2e8a1256a72..0ae6355d574 100644 --- a/crates/bindings/src/table.rs +++ b/crates/bindings/src/table.rs @@ -262,19 +262,6 @@ impl MaybeError for AutoIncOverflow { } } -/// A trait for types exposing an operation to access their `N`th field. -/// -/// In other words, a type implementing `FieldAccess` allows -/// shared projection from `self` to its `N`th field. -#[doc(hidden)] -pub trait FieldAccess { - /// The type of the field at the `N`th position. - type Field; - - /// Project to the value of the field at position `N`. - fn get_field(&self) -> &Self::Field; -} - pub trait Column { type Row; type ColType; diff --git a/modules/rust-wasm-test/src/lib.rs b/modules/rust-wasm-test/src/lib.rs index cfc73fc7b23..e80b2aa8336 100644 --- a/modules/rust-wasm-test/src/lib.rs +++ b/modules/rust-wasm-test/src/lib.rs @@ -104,6 +104,11 @@ pub type TestAlias = TestA; #[spacetimedb::table(name = repeating_test_arg, scheduled(repeating_test))] pub struct RepeatingTestArg { + #[primary_key] + #[auto_inc] + scheduled_id: u64, + #[scheduled_at] + scheduled_at: spacetimedb::ScheduleAt, prev_time: Timestamp, } diff --git a/smoketests/tests/modules.py b/smoketests/tests/modules.py index e9a7e5f3025..203a6e2a16c 100644 --- a/smoketests/tests/modules.py +++ b/smoketests/tests/modules.py @@ -144,6 +144,11 @@ class UploadModule2(Smoketest): #[spacetimedb::table(name = scheduled_message, public, scheduled(my_repeating_reducer))] pub struct ScheduledMessage { + #[primary_key] + #[auto_inc] + scheduled_id: u64, + #[scheduled_at] + scheduled_at: spacetimedb::ScheduleAt, prev: Timestamp, } diff --git a/smoketests/tests/schedule_reducer.py b/smoketests/tests/schedule_reducer.py index 0b80d0f5c4e..01f6e57efdf 100644 --- a/smoketests/tests/schedule_reducer.py +++ b/smoketests/tests/schedule_reducer.py @@ -25,6 +25,11 @@ class CancelReducer(Smoketest): #[spacetimedb::table(name = scheduled_reducer_args, public, scheduled(reducer))] pub struct ScheduledReducerArgs { + #[primary_key] + #[auto_inc] + scheduled_id: u64, + #[scheduled_at] + scheduled_at: spacetimedb::ScheduleAt, num: i32, } @@ -53,6 +58,11 @@ class SubscribeScheduledTable(Smoketest): #[spacetimedb::table(name = scheduled_table, public, scheduled(my_reducer))] pub struct ScheduledTable { + #[primary_key] + #[auto_inc] + scheduled_id: u64, + #[scheduled_at] + scheduled_at: spacetimedb::ScheduleAt, prev: Timestamp, }