Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tweak scheduled_at macro syntax #2054

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/bindings-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ mod sym {
};
}

symbol!(at);
symbol!(auto_inc);
symbol!(btree);
symbol!(client_connected);
Expand All @@ -45,7 +46,6 @@ mod sym {
symbol!(public);
symbol!(sats);
symbol!(scheduled);
symbol!(scheduled_at);
symbol!(unique);
symbol!(update);

Expand Down Expand Up @@ -242,7 +242,7 @@ pub fn table(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {

/// Provides helper attributes for `#[spacetimedb::table]`, so that we don't get unknown attribute errors.
#[doc(hidden)]
#[proc_macro_derive(__TableHelper, attributes(sats, unique, auto_inc, primary_key, index, scheduled_at))]
#[proc_macro_derive(__TableHelper, attributes(sats, unique, auto_inc, primary_key, index))]
pub fn table_helper(_input: StdTokenStream) -> StdTokenStream {
Default::default()
}
Expand Down
143 changes: 84 additions & 59 deletions crates/bindings-macro/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use syn::{parse_quote, Ident, Path, Token};

pub(crate) struct TableArgs {
access: Option<TableAccess>,
scheduled: Option<Path>,
scheduled: Option<ScheduledArg>,
name: Ident,
indices: Vec<IndexArg>,
}
Expand All @@ -36,6 +36,12 @@ impl TableAccess {
}
}

struct ScheduledArg {
span: Span,
reducer: Path,
at: Option<Ident>,
}

struct IndexArg {
name: Ident,
kind: IndexType,
Expand Down Expand Up @@ -70,9 +76,7 @@ impl TableArgs {
sym::index => indices.push(IndexArg::parse_meta(meta)?),
sym::scheduled => {
check_duplicate(&scheduled, &meta)?;
let in_parens;
syn::parenthesized!(in_parens in meta.input);
scheduled = Some(in_parens.parse::<Path>()?);
scheduled = Some(ScheduledArg::parse_meta(meta)?);
}
});
Ok(())
Expand All @@ -94,6 +98,35 @@ impl TableArgs {
}
}

impl ScheduledArg {
fn parse_meta(meta: ParseNestedMeta) -> syn::Result<Self> {
let span = meta.path.span();
let mut reducer = None;
let mut at = None;

meta.parse_nested_meta(|meta| {
if meta.input.peek(syn::Token![=]) || meta.input.peek(syn::token::Paren) {
match_meta!(match meta {
sym::at => {
check_duplicate(&at, &meta)?;
let ident = meta.value()?.parse()?;
at = Some(ident);
}
})
} else {
check_duplicate_msg(&reducer, &meta, "can only specify one scheduled reducer")?;
reducer = Some(meta.path);
}
Ok(())
})?;

let reducer = reducer.ok_or_else(|| {
meta.error("must specify scheduled reducer associated with the table: scheduled(reducer_name)")
})?;
Ok(Self { span, reducer, at })
}
}

impl IndexArg {
fn parse_meta(meta: ParseNestedMeta) -> syn::Result<Self> {
let mut name = None;
Expand Down Expand Up @@ -161,11 +194,7 @@ impl IndexArg {
}

fn validate<'a>(&'a self, table_name: &str, cols: &'a [Column<'a>]) -> syn::Result<ValidatedIndex<'_>> {
let find_column = |ident| {
cols.iter()
.find(|col| col.field.ident == Some(ident))
.ok_or_else(|| syn::Error::new(ident.span(), "not a column of the table"))
};
let find_column = |ident| find_column(cols, ident);
let kind = match &self.kind {
IndexType::BTree { columns } => {
let cols = columns.iter().map(find_column).collect::<syn::Result<Vec<_>>>()?;
Expand Down Expand Up @@ -353,12 +382,23 @@ struct Column<'a> {
ty: &'a syn::Type,
}

fn try_find_column<'a, 'b, T: ?Sized>(cols: &'a [Column<'b>], name: &T) -> Option<&'a Column<'b>>
where
Ident: PartialEq<T>,
{
cols.iter()
.find(|col| col.field.ident.is_some_and(|ident| ident == name))
}

fn find_column<'a, 'b>(cols: &'a [Column<'b>], name: &Ident) -> syn::Result<&'a Column<'b>> {
try_find_column(cols, name).ok_or_else(|| syn::Error::new(name.span(), "not a column of the table"))
}

enum ColumnAttr {
Unique(Span),
AutoInc(Span),
PrimaryKey(Span),
Index(IndexArg),
ScheduledAt(Span),
}

impl ColumnAttr {
Expand All @@ -378,9 +418,6 @@ 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
})
Expand Down Expand Up @@ -427,7 +464,6 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R
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;
Expand All @@ -436,7 +472,6 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R
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;
Expand All @@ -455,10 +490,6 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R
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);
}
}
}

Expand All @@ -484,27 +515,10 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R
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
Expand Down Expand Up @@ -552,47 +566,60 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R
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
let (schedule, schedule_typecheck) = args
.scheduled
.as_ref()
.map(|reducer| {
.map(|sched| {
let scheduled_at_column = match &sched.at {
Some(at) => Some(find_column(&columns, at)?),
None => try_find_column(&columns, "scheduled_at"),
};
// better error message when both are missing
if scheduled_at_column.is_none() && primary_key_column.is_none() {
return Err(syn::Error::new(
original_struct_ident.span(),
sched.span,
"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(
original_struct_ident.span(),
"scheduled tables must have a `#[scheduled_at] scheduled_at: spacetimedb::ScheduleAt` column.",
sched.span,
"scheduled tables must have a `scheduled_at: spacetimedb::ScheduleAt` column. \
if the column has a name besides `scheduled_at`, you can specify it with \
`scheduled(my_reducer, at = custom_scheduled_at)`",
)
})?;
let primary_key_column = primary_key_column.ok_or_else(|| {
syn::Error::new(
sched.span,
"scheduled tables must have a `#[primary_key] #[auto_inc] scheduled_id: u64` column",
)
})?;

let reducer = &sched.reducer;
let scheduled_at_id = scheduled_at_column.index;
if primary_key_column.is_none() {
return Err(syn::Error::new(
original_struct_ident.span(),
"scheduled tables should have a `#[primary_key] #[auto_inc] scheduled_id: u64` column",
));
}
Ok(quote!(spacetimedb::table::ScheduleDesc {
let desc = quote!(spacetimedb::table::ScheduleDesc {
reducer_name: <#reducer as spacetimedb::rt::ReducerInfo>::NAME,
scheduled_at_column: #scheduled_at_id,
}))
});

let primary_key_ty = primary_key_column.ty;
let scheduled_at_ty = scheduled_at_column.ty;
let typecheck = quote! {
spacetimedb::rt::scheduled_reducer_typecheck::<#original_struct_ident>(#reducer);
spacetimedb::rt::assert_scheduled_table_primary_key::<#primary_key_ty>();
let _ = |x: #scheduled_at_ty| { let _: spacetimedb::ScheduleAt = x; };
};

Ok((desc, typecheck))
})
.transpose()?
.into_iter();
.unzip();
let schedule = schedule.into_iter();

let unique_err = if !unique_columns.is_empty() {
quote!(spacetimedb::UniqueConstraintViolation)
Expand Down Expand Up @@ -665,9 +692,7 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R
let emission = quote! {
const _: () = {
#(let _ = <#field_types as spacetimedb::rt::TableColumn>::_ITEM;)*
#scheduled_reducer_type_check
#scheduled_at_typecheck
#scheduled_id_typecheck
#schedule_typecheck
};

#trait_def
Expand Down
1 change: 0 additions & 1 deletion crates/bindings/tests/ui/reducers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ struct ScheduledTable {
#[primary_key]
#[auto_inc]
scheduled_id: u64,
#[scheduled_at]
scheduled_at: spacetimedb::ScheduleAt,
x: u8,
y: u8,
Expand Down
9 changes: 4 additions & 5 deletions crates/bindings/tests/ui/reducers.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ error: scheduled table missing required columns; add these to your struct:
#[primary_key]
#[auto_inc]
scheduled_id: u64,
#[scheduled_at]
scheduled_at: spacetimedb::ScheduleAt,
--> tests/ui/reducers.rs:29:8
--> tests/ui/reducers.rs:28:59
|
29 | struct ScheduledTableMissingRows {
| ^^^^^^^^^^^^^^^^^^^^^^^^^
28 | #[spacetimedb::table(name = scheduled_table_missing_rows, scheduled(scheduled_table_missing_rows_reducer))]
| ^^^^^^^^^

error[E0593]: function is expected to take 2 arguments, but it takes 3 arguments
--> tests/ui/reducers.rs:37:56
Expand All @@ -30,7 +29,7 @@ error[E0593]: function is expected to take 2 arguments, but it takes 3 arguments
| | expected function that takes 2 arguments
| required by a bound introduced by this call
...
49 | fn scheduled_table_reducer(_ctx: &ReducerContext, _x: u8, _y: u8) {}
48 | fn scheduled_table_reducer(_ctx: &ReducerContext, _x: u8, _y: u8) {}
| ----------------------------------------------------------------- takes 3 arguments
|
= note: required for `for<'a> fn(&'a ReducerContext, u8, u8) {scheduled_table_reducer}` to implement `Reducer<'_, (ScheduledTable,)>`
Expand Down
1 change: 0 additions & 1 deletion modules/rust-wasm-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ pub struct RepeatingTestArg {
#[primary_key]
#[auto_inc]
scheduled_id: u64,
#[scheduled_at]
scheduled_at: spacetimedb::ScheduleAt,
prev_time: Timestamp,
}
Expand Down
1 change: 0 additions & 1 deletion modules/sdk-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,6 @@ pub struct ScheduledTable {
#[primary_key]
#[auto_inc]
scheduled_id: u64,
#[scheduled_at]
scheduled_at: spacetimedb::ScheduleAt,
text: String,
}
Expand Down
1 change: 0 additions & 1 deletion smoketests/tests/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ class UploadModule2(Smoketest):
#[primary_key]
#[auto_inc]
scheduled_id: u64,
#[scheduled_at]
scheduled_at: spacetimedb::ScheduleAt,
prev: Timestamp,
}
Expand Down
Loading
Loading