Skip to content

Commit 8f11ac8

Browse files
Make name a required parameter of view macro (#3480)
# Description of Changes <!-- Please describe your change, mention any related tickets, and so on here. --> Makes `name` a required parameter of the `#[view]` macro. This is to be consistent with the `#[table]` macro since views should expose the same interface as tables. # API and ABI breaking changes <!-- If this is an API or ABI breaking change, please apply the corresponding GitHub label. --> None # Expected complexity level and risk <!-- How complicated do you think these changes are? Grade on a scale from 1 to 5, where 1 is a trivial change, and 5 is a deep-reaching and complex change. This complexity rating applies not only to the complexity apparent in the diff, but also to its interactions with existing and future code. If you answered more than a 2, explain what is complex about the PR, and what other components it interacts with in potentially concerning ways. --> 1 # Testing <!-- Describe any testing you've done, and any testing you'd like your reviewers to do, so that you're confident that all the changes work as expected! --> - [x] Added new compile tests - [x] Updated existing compile tests
1 parent 5aa80bf commit 8f11ac8

7 files changed

Lines changed: 140 additions & 129 deletions

File tree

crates/bindings-macro/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ pub fn reducer(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
125125
#[proc_macro_attribute]
126126
pub fn view(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
127127
cvt_attr::<ItemFn>(args, item, quote!(), |args, original_function| {
128-
let args = view::ViewArgs::parse(args)?;
128+
let args = view::ViewArgs::parse(args, &original_function.sig.ident)?;
129129
view::view_impl(args, original_function)
130130
})
131131
}

crates/bindings-macro/src/view.rs

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,56 @@
1-
use proc_macro2::{Span, TokenStream};
1+
use heck::ToSnakeCase;
2+
use proc_macro2::{Ident, Span, TokenStream};
23
use quote::quote;
4+
use syn::ext::IdentExt;
35
use syn::parse::Parser;
46
use syn::{FnArg, ItemFn};
57

68
use crate::sym;
7-
use crate::util::{ident_to_litstr, match_meta};
9+
use crate::util::{check_duplicate_msg, match_meta};
810

911
pub(crate) struct ViewArgs {
12+
name: Ident,
1013
#[allow(unused)]
1114
public: bool,
1215
}
1316

1417
impl ViewArgs {
15-
/// Parse `#[view(public)]` where `public` is required.
16-
pub(crate) fn parse(input: TokenStream) -> syn::Result<Self> {
17-
if input.is_empty() {
18-
return Err(syn::Error::new(
19-
Span::call_site(),
20-
"views must be declared as `#[view(public)]`; `public` is required",
21-
));
22-
}
23-
let mut public = false;
18+
/// Parse `#[view(name = ..., public)]` where both `name` and `public` are required.
19+
pub(crate) fn parse(input: TokenStream, func_ident: &Ident) -> syn::Result<Self> {
20+
let mut name = None;
21+
let mut public = None;
2422
syn::meta::parser(|meta| {
2523
match_meta!(match meta {
24+
sym::name => {
25+
check_duplicate_msg(&name, &meta, "`name` already specified")?;
26+
name = Some(meta.value()?.parse()?);
27+
}
2628
sym::public => {
27-
if public {
28-
return Err(syn::Error::new(
29-
Span::call_site(),
30-
"duplicate attribute argument: `public`",
31-
));
32-
}
33-
public = true;
29+
check_duplicate_msg(&public, &meta, "`public` already specified")?;
30+
public = Some(());
3431
}
3532
});
3633
Ok(())
3734
})
3835
.parse2(input)?;
39-
if !public {
40-
return Err(syn::Error::new(
36+
let name = name.ok_or_else(|| {
37+
let view = func_ident.to_string().to_snake_case();
38+
syn::Error::new(
4139
Span::call_site(),
42-
"views must be declared as `#[view(public)]`; `public` is required",
43-
));
44-
}
45-
Ok(Self { public })
40+
format_args!("must specify view name, e.g. `#[spacetimedb::view(name = {view})]"),
41+
)
42+
})?;
43+
let () = public
44+
.ok_or_else(|| syn::Error::new(Span::call_site(), "views must be `public`, e.g. `#[view(public)]`"))?;
45+
Ok(Self { name, public: true })
4646
}
4747
}
4848

49-
pub(crate) fn view_impl(_args: ViewArgs, original_function: &ItemFn) -> syn::Result<TokenStream> {
50-
let func_name = &original_function.sig.ident;
51-
let view_name = ident_to_litstr(func_name);
49+
pub(crate) fn view_impl(args: ViewArgs, original_function: &ItemFn) -> syn::Result<TokenStream> {
5250
let vis = &original_function.vis;
51+
let func_name = &original_function.sig.ident;
52+
let view_ident = args.name;
53+
let view_name = view_ident.unraw().to_string();
5354

5455
for param in &original_function.sig.generics.params {
5556
let err = |msg| syn::Error::new_spanned(param, msg);
@@ -116,7 +117,7 @@ pub(crate) fn view_impl(_args: ViewArgs, original_function: &ItemFn) -> syn::Res
116117
}
117118
};
118119

119-
let register_describer_symbol = format!("__preinit__20_register_describer_{}", view_name.value());
120+
let register_describer_symbol = format!("__preinit__20_register_describer_{}", view_name);
120121

121122
let lt_params = &original_function.sig.generics;
122123
let lt_where_clause = &lt_params.where_clause;

crates/bindings/src/lib.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -820,25 +820,25 @@ pub use spacetimedb_bindings_macro::procedure;
820820
/// }
821821
///
822822
/// // A view that selects at most one row from a table
823-
/// #[view(public)]
823+
/// #[view(name = my_player, public)]
824824
/// fn my_player(ctx: &ViewContext) -> Option<Player> {
825825
/// ctx.db.player().identity().find(ctx.sender)
826826
/// }
827827
///
828828
/// // An example of column projection
829-
/// #[view(public)]
829+
/// #[view(name = my_player_id, public)]
830830
/// fn my_player_id(ctx: &ViewContext) -> Option<PlayerId> {
831831
/// ctx.db.player().identity().find(ctx.sender).map(|Player { id, .. }| PlayerId { id })
832832
/// }
833833
///
834834
/// // An example of a parameterized view
835-
/// #[view(public)]
835+
/// #[view(name = players_at_level, public)]
836836
/// fn players_at_level(ctx: &AnonymousViewContext, level: u32) -> Vec<Player> {
837837
/// ctx.db.player().level().filter(level).collect()
838838
/// }
839839
///
840840
/// // An example that is analogous to a semijoin in sql
841-
/// #[view(public)]
841+
/// #[view(name = players_at_coordinates, public)]
842842
/// fn players_at_coordinates(ctx: &AnonymousViewContext, x: u64, y: u64) -> Vec<Player> {
843843
/// ctx
844844
/// .db
@@ -850,7 +850,7 @@ pub use spacetimedb_bindings_macro::procedure;
850850
/// }
851851
///
852852
/// // An example of a join that combines fields from two different tables
853-
/// #[view(public)]
853+
/// #[view(name = players_with_coordinates, public)]
854854
/// fn players_with_coordinates(ctx: &AnonymousViewContext, x: u64, y: u64) -> Vec<PlayerAndLocation> {
855855
/// ctx
856856
/// .db

crates/bindings/tests/ui/views.rs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,62 +72,68 @@ struct Player {
7272

7373
struct NotSpacetimeType {}
7474

75-
/// Private views not allowed; must be `#[view(public)]`
76-
#[view]
75+
/// Private views not allowed; must be `#[view(public, ...)]`
76+
#[view(name = view_def_no_public)]
7777
fn view_def_no_public(_: &ViewContext) -> Vec<Player> {
7878
vec![]
7979
}
8080

8181
/// Duplicate `public`
82-
#[view(public, public)]
83-
fn view_def_duplicate_attribute_arg() -> Vec<Player> {
82+
#[view(name = view_def_dup_public, public, public)]
83+
fn view_def_dup_public() -> Vec<Player> {
84+
vec![]
85+
}
86+
87+
/// Duplicate `name`
88+
#[view(name = view_def_dup_name, name = view_def_dup_name, public)]
89+
fn view_def_dup_name() -> Vec<Player> {
8490
vec![]
8591
}
8692

8793
/// Unsupported attribute arg
88-
#[view(public, anonymous)]
89-
fn view_def_unsupported_attribute_arg() -> Vec<Player> {
94+
#[view(name = view_def_unsupported_arg, public, anonymous)]
95+
fn view_def_unsupported_arg() -> Vec<Player> {
9096
vec![]
9197
}
9298

9399
/// A `ViewContext` is required
94-
#[view(public)]
100+
#[view(name = view_def_no_context, public)]
95101
fn view_def_no_context() -> Vec<Player> {
96102
vec![]
97103
}
98104

99105
/// A `ViewContext` is required
100-
#[view(public)]
106+
#[view(name = view_def_wrong_context, public)]
101107
fn view_def_wrong_context(_: &ReducerContext) -> Vec<Player> {
102108
vec![]
103109
}
104110

105111
/// Must pass the `ViewContext` by ref
106-
#[view(public)]
112+
#[view(name = view_def_pass_context_by_value, public)]
107113
fn view_def_pass_context_by_value(_: ViewContext) -> Vec<Player> {
108114
vec![]
109115
}
110116

111117
/// The view context must be the first parameter
112-
#[view(public)]
118+
#[view(name = view_def_wrong_context_position, public)]
113119
fn view_def_wrong_context_position(_: &u32, _: &ViewContext) -> Vec<Player> {
114120
vec![]
115121
}
116122

117123
/// Must return `Vec<T>` or `Option<T>` where `T` is a SpacetimeType
118-
#[view(public)]
124+
#[view(name = view_def_no_return, public)]
119125
fn view_def_no_return(_: &ViewContext) {}
120126

121127
/// Must return `Vec<T>` or `Option<T>` where `T` is a SpacetimeType
122-
#[view(public)]
128+
#[view(name = view_def_wrong_return, public)]
123129
fn view_def_wrong_return(_: &ViewContext) -> Player {
124130
Player {
125131
identity: Identity::ZERO,
126132
}
127133
}
128134

129135
/// Must return `Vec<T>` or `Option<T>` where `T` is a SpacetimeType
130-
#[view(public)]
136+
#[view(name = view_def_returns_not_a_spacetime_type, public)]
131137
fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option<NotSpacetimeType> {
132138
None
133139
}
@@ -144,7 +150,7 @@ struct ScheduledTable {
144150
}
145151

146152
/// Cannot use a view as a scheduled function
147-
#[view(public)]
153+
#[view(name = scheduled_table_view, public)]
148154
fn scheduled_table_view(_: &ViewContext, _args: ScheduledTable) -> Vec<Player> {
149155
vec![]
150156
}

0 commit comments

Comments
 (0)