| 76 | |
| 77 | impl TableArgs { |
| 78 | pub(crate) fn parse(input: TokenStream, struct_ident: &Ident) -> syn::Result<Self> { |
| 79 | let mut access = None; |
| 80 | let mut scheduled = None; |
| 81 | let mut accessor = None; |
| 82 | let mut name: Option<LitStr> = None; |
| 83 | let mut indices = Vec::new(); |
| 84 | let mut event = None; |
| 85 | syn::meta::parser(|meta| { |
| 86 | match_meta!(match meta { |
| 87 | sym::public => { |
| 88 | check_duplicate_msg(&access, &meta, "already specified access level")?; |
| 89 | access = Some(TableAccess::Public(meta.path.span())); |
| 90 | } |
| 91 | sym::private => { |
| 92 | check_duplicate_msg(&access, &meta, "already specified access level")?; |
| 93 | access = Some(TableAccess::Private(meta.path.span())); |
| 94 | } |
| 95 | sym::accessor => { |
| 96 | check_duplicate(&accessor, &meta)?; |
| 97 | let value = meta.value()?; |
| 98 | accessor = Some(value.parse()?); |
| 99 | } |
| 100 | sym::name => { |
| 101 | check_duplicate(&name, &meta)?; |
| 102 | let value = meta.value()?; |
| 103 | // `fork` as a way to do lookahead `peek`, so that below when we parse as the `LitStr` we actually want, |
| 104 | // it works. |
| 105 | if let Ok(sym) = value.fork().parse::<Ident>() { |
| 106 | // The update from SpacetimeDB 1.* to 2.* changes `name =` to `accessor =`, |
| 107 | // and uses `name =` for a different thing. Now, only `accessor =` is mandatory, |
| 108 | // and `name =` accepts a string literal rather than an identifier. |
| 109 | // Detect the specific case where the user specifies a 1.*-style `name = ident`, |
| 110 | // and offer a diagnostic with a simple migration path. |
| 111 | // Unfortunately, we can't hook in to rustc's system for providing quick fixes in compiler errors, |
| 112 | // until [this ancient issue](https://github.com/rust-lang/rust/issues/54140) gets stabilized. |
| 113 | return Err( |
| 114 | if accessor.is_some() { |
| 115 | // If we've already encountered an `accessor`, |
| 116 | // then probably the user is actually trying to overwrite the `name`, |
| 117 | // so tell them to use a string literal instead of an ident. |
| 118 | // This is a best-effort check, and we won't hit it if the user specifies `name` first, |
| 119 | // but we're prioritizing the migration UX here. |
| 120 | meta.error(format_args!( |
| 121 | "Expected a string literal for `name`, but found an identifier. |
| 122 | |
| 123 | To overwrite the canonical name of the table, replace `name = {sym}` with `name = \"{sym}\"`." |
| 124 | )) |
| 125 | } else { |
| 126 | // FIXME: Ideally, this error span would point to the full pair `name = my_table`, |
| 127 | // but I (pgoldman 2026-02-18) can only figure out how to get it at either `name` or `my_table`. |
| 128 | // This version points at `name`, which, :shrug:. |
| 129 | // Note that, if the user specifies `name = {ident}` followed by `accessor = {ident}`, |
| 130 | // we'll hit this branch, even though the diagnostic doesn't apply and we'd prefer not to. |
| 131 | // I (pgoldman 2026-02-18) don't see a good way to distinguish this case |
| 132 | // without making our parsing dramatically more complicated, |
| 133 | // and it seems unlikely to occur. |
| 134 | meta.error(format_args!( |
| 135 | "Expected a string literal for `name`, but found an identifier. Did you mean to specify an `accessor`? |