diff --git a/crates/bindings-macro/src/lib.rs b/crates/bindings-macro/src/lib.rs index 8fa9705ca0e..110be923989 100644 --- a/crates/bindings-macro/src/lib.rs +++ b/crates/bindings-macro/src/lib.rs @@ -132,6 +132,7 @@ mod sym { symbol!(update); symbol!(default); symbol!(event); + symbol!(allow_discriminants); symbol!(u8); symbol!(i8); diff --git a/crates/bindings-macro/src/sats.rs b/crates/bindings-macro/src/sats.rs index 8bad39da837..eb334c09686 100644 --- a/crates/bindings-macro/src/sats.rs +++ b/crates/bindings-macro/src/sats.rs @@ -63,7 +63,39 @@ pub(crate) fn sats_type_from_derive( SatsTypeData::Product(fields.collect()) } syn::Data::Enum(enu) => { + // Check if #[sats(allow_discriminants)] is set, which opts out of the + // discriminant check. This is used by internal enums that set discriminant + // values for non-codegen reasons (e.g. for `as u8` casts). + let allow_discriminants = input.attrs.iter().any(|attr| { + if !attr.path().is_ident("sats") { + return false; + } + let mut found = false; + // Use parse_nested_meta with proper error handling for keyword paths + // like `crate = crate`. We consume unknown meta items gracefully. + let _ = attr.parse_nested_meta(|meta| { + if meta.path.is_ident("allow_discriminants") { + found = true; + } else if meta.input.peek(syn::Token![=]) { + // Consume `= value` for other keys (e.g. `crate = crate`). + meta.input.parse::()?; + meta.input.parse::()?; + } + Ok(()) + }); + found + }); let variants = enu.variants.iter().map(|var| { + if !allow_discriminants { + if let Some((eq, _)) = &var.discriminant { + return Err(syn::Error::new( + eq.span(), + "explicit discriminant values are not supported by SpacetimeDB; \ + SATS assigns variant tags by declaration order, so discriminant \ + values would be silently ignored", + )); + } + } let (member, ty) = variant_data(var)?.unzip(); Ok(SatsVariant { ident: &var.ident, @@ -118,6 +150,8 @@ pub(crate) fn extract_sats_type<'a>( let v = value.parse::()?; name = Some(v); } + // Parsed earlier in sats_type_from_derive; just accept and ignore here. + sym::allow_discriminants => {} }); Ok(()) })?; diff --git a/crates/bindings/tests/ui/enum_discriminants.rs b/crates/bindings/tests/ui/enum_discriminants.rs new file mode 100644 index 00000000000..f0128d89e73 --- /dev/null +++ b/crates/bindings/tests/ui/enum_discriminants.rs @@ -0,0 +1,23 @@ +// Enums with explicit discriminant values should produce a compile error, +// since SATS assigns variant tags by declaration order and ignores discriminants. + +#[derive(spacetimedb::SpacetimeType)] +enum ExplicitValues { + A = 1, + B = 2, +} + +#[derive(spacetimedb::SpacetimeType)] +enum MixedValues { + X, + Y = 5, +} + +// This should compile fine — no explicit discriminants. +#[derive(spacetimedb::SpacetimeType)] +enum NoValues { + Foo, + Bar, +} + +fn main() {} diff --git a/crates/bindings/tests/ui/enum_discriminants.stderr b/crates/bindings/tests/ui/enum_discriminants.stderr new file mode 100644 index 00000000000..e0322ebea2a --- /dev/null +++ b/crates/bindings/tests/ui/enum_discriminants.stderr @@ -0,0 +1,11 @@ +error: explicit discriminant values are not supported by SpacetimeDB; SATS assigns variant tags by declaration order, so discriminant values would be silently ignored + --> tests/ui/enum_discriminants.rs:6:7 + | +6 | A = 1, + | ^ + +error: explicit discriminant values are not supported by SpacetimeDB; SATS assigns variant tags by declaration order, so discriminant values would be silently ignored + --> tests/ui/enum_discriminants.rs:13:7 + | +13 | Y = 5, + | ^ diff --git a/crates/client-api-messages/src/websocket/v2.rs b/crates/client-api-messages/src/websocket/v2.rs index 734c28fdbe5..40f2203f059 100644 --- a/crates/client-api-messages/src/websocket/v2.rs +++ b/crates/client-api-messages/src/websocket/v2.rs @@ -84,7 +84,7 @@ pub struct Unsubscribe { } #[derive(Clone, Copy, Default, PartialEq, Eq, SpacetimeType)] -#[sats(crate = spacetimedb_lib)] +#[sats(crate = spacetimedb_lib, allow_discriminants)] pub enum UnsubscribeFlags { #[default] Default = 0, diff --git a/crates/lib/src/db/raw_def/v8.rs b/crates/lib/src/db/raw_def/v8.rs index d0266ba5ea7..edfaad0fa61 100644 --- a/crates/lib/src/db/raw_def/v8.rs +++ b/crates/lib/src/db/raw_def/v8.rs @@ -80,7 +80,7 @@ impl RawSequenceDefV8 { /// /// Currently only `IndexType::BTree` is allowed. #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Display, SpacetimeType)] -#[sats(crate = crate)] +#[sats(crate = crate, allow_discriminants)] pub enum IndexType { /// A BTree index. BTree = 0,