Skip to content

Commit c39f7fa

Browse files
authored
Plain enums as index keys with specialized indices (#2506)
1 parent c8f0d8f commit c39f7fa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1692
-271
lines changed

crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs

+23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module.verified.txt

+1-18
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,6 @@
5252
}
5353
},
5454
{/*
55-
[PrimaryKey]
56-
public TestEnumWithExplicitValues PrimaryKeyField;
57-
^^^^^^^^^^^^^^^
58-
}
59-
*/
60-
Message: Field PrimaryKeyField is marked as Unique but it has a type TestEnumWithExplicitValues which is not an equatable primitive.,
61-
Severity: Error,
62-
Descriptor: {
63-
Id: STDB0003,
64-
Title: Unique fields must be equatable,
65-
MessageFormat: Field {0} is marked as Unique but it has a type {1} which is not an equatable primitive.,
66-
Category: SpacetimeDB,
67-
DefaultSeverity: Error,
68-
IsEnabledByDefault: true
69-
}
70-
},
71-
{/*
7255
[SpacetimeDB.Table]
7356
public partial record TestTableTaggedEnum : SpacetimeDB.TaggedEnum<(int X, int Y)> { }
7457
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -429,4 +412,4 @@ public partial struct TestScheduleIssues
429412
}
430413
}
431414
]
432-
}
415+
}

crates/bindings-csharp/Codegen/Module.cs

+23
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,32 @@ or SpecialType.System_Int64
9696
diag.Report(ErrorDescriptor.AutoIncNotInteger, field);
9797
}
9898

99+
// Check whether this is a sum type without a payload.
100+
var isAllUnitEnum = false;
101+
if (type.TypeKind == Microsoft.CodeAnalysis.TypeKind.Enum)
102+
{
103+
isAllUnitEnum = true;
104+
}
105+
else if (type.BaseType?.OriginalDefinition.ToString() == "SpacetimeDB.TaggedEnum<Variants>")
106+
{
107+
if (
108+
type.BaseType.TypeArguments.FirstOrDefault() is INamedTypeSymbol
109+
{
110+
IsTupleType: true,
111+
TupleElements: var taggedEnumVariants
112+
}
113+
)
114+
{
115+
isAllUnitEnum = taggedEnumVariants.All(
116+
(field) => field.Type.ToString() == "SpacetimeDB.Unit"
117+
);
118+
}
119+
}
120+
99121
IsEquatable =
100122
(
101123
isInteger
124+
|| isAllUnitEnum
102125
|| type.SpecialType switch
103126
{
104127
SpecialType.System_String or SpecialType.System_Boolean => true,

crates/bindings-csharp/Runtime/Internal/Bounds.cs

-11
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ public interface IBTreeIndexBounds
1818
}
1919

2020
public readonly struct Bound<T>(T min, T max)
21-
where T : IEquatable<T>
2221
{
2322
public T Min => min;
2423
public T Max => max;
@@ -29,7 +28,6 @@ public readonly struct Bound<T>(T min, T max)
2928
}
3029

3130
public readonly struct BTreeIndexBounds<T, TRW>(Bound<T> t) : IBTreeIndexBounds
32-
where T : IEquatable<T>
3331
where TRW : struct, IReadWrite<T>
3432
{
3533
public ushort PrefixElems => 0;
@@ -50,7 +48,6 @@ public void REnd(BinaryWriter w)
5048
}
5149

5250
public readonly struct BTreeIndexBounds<T, TRW, U, URW>((T t, Bound<U> u) b) : IBTreeIndexBounds
53-
where U : IEquatable<U>
5451
where TRW : struct, IReadWrite<T>
5552
where URW : struct, IReadWrite<U>
5653
{
@@ -76,7 +73,6 @@ public void REnd(BinaryWriter w)
7673

7774
public readonly struct BTreeIndexBounds<T, TRW, U, URW, V, VRW>((T t, U u, Bound<V> v) b)
7875
: IBTreeIndexBounds
79-
where V : IEquatable<V>
8076
where TRW : struct, IReadWrite<T>
8177
where URW : struct, IReadWrite<U>
8278
where VRW : struct, IReadWrite<V>
@@ -105,7 +101,6 @@ public void REnd(BinaryWriter w)
105101
public readonly struct BTreeIndexBounds<T, TRW, U, URW, V, VRW, W, WRW>(
106102
(T t, U u, V v, Bound<W> w) b
107103
) : IBTreeIndexBounds
108-
where W : IEquatable<W>
109104
where TRW : struct, IReadWrite<T>
110105
where URW : struct, IReadWrite<U>
111106
where VRW : struct, IReadWrite<V>
@@ -136,7 +131,6 @@ public void REnd(BinaryWriter w)
136131
public readonly struct BTreeIndexBounds<T, TRW, U, URW, V, VRW, W, WRW, X, XRW>(
137132
(T t, U u, V v, W w, Bound<X> x) b
138133
) : IBTreeIndexBounds
139-
where X : IEquatable<X>
140134
where TRW : struct, IReadWrite<T>
141135
where URW : struct, IReadWrite<U>
142136
where VRW : struct, IReadWrite<V>
@@ -169,7 +163,6 @@ public void REnd(BinaryWriter w)
169163
public readonly struct BTreeIndexBounds<T, TRW, U, URW, V, VRW, W, WRW, X, XRW, Y, YRW>(
170164
(T t, U u, V v, W w, X x, Bound<Y> y) b
171165
) : IBTreeIndexBounds
172-
where Y : IEquatable<Y>
173166
where TRW : struct, IReadWrite<T>
174167
where URW : struct, IReadWrite<U>
175168
where VRW : struct, IReadWrite<V>
@@ -204,7 +197,6 @@ public void REnd(BinaryWriter w)
204197
public readonly struct BTreeIndexBounds<T, TRW, U, URW, V, VRW, W, WRW, X, XRW, Y, YRW, Z, ZRW>(
205198
(T t, U u, V v, W w, X x, Y y, Bound<Z> z) b
206199
) : IBTreeIndexBounds
207-
where Z : IEquatable<Z>
208200
where TRW : struct, IReadWrite<T>
209201
where URW : struct, IReadWrite<U>
210202
where VRW : struct, IReadWrite<V>
@@ -256,7 +248,6 @@ public readonly struct BTreeIndexBounds<
256248
A,
257249
ARW
258250
>((T t, U u, V v, W w, X x, Y y, Z z, Bound<A> a) b) : IBTreeIndexBounds
259-
where A : IEquatable<A>
260251
where TRW : struct, IReadWrite<T>
261252
where URW : struct, IReadWrite<U>
262253
where VRW : struct, IReadWrite<V>
@@ -312,7 +303,6 @@ public readonly struct BTreeIndexBounds<
312303
B,
313304
BRW
314305
>((T t, U u, V v, W w, X x, Y y, Z z, A a, Bound<B> b) b) : IBTreeIndexBounds
315-
where B : IEquatable<B>
316306
where TRW : struct, IReadWrite<T>
317307
where URW : struct, IReadWrite<U>
318308
where VRW : struct, IReadWrite<V>
@@ -372,7 +362,6 @@ public readonly struct BTreeIndexBounds<
372362
C,
373363
CRW
374364
>((T t, U u, V v, W w, X x, Y y, Z z, A a, B b, Bound<C> c) b) : IBTreeIndexBounds
375-
where C : IEquatable<C>
376365
where TRW : struct, IReadWrite<T>
377366
where URW : struct, IReadWrite<U>
378367
where VRW : struct, IReadWrite<V>

crates/bindings-csharp/Runtime/Internal/IIndex.cs

-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ out handle
8585
public abstract class UniqueIndex<Handle, Row, T, RW>(string name) : IndexBase<Row>(name)
8686
where Handle : ITableView<Handle, Row>
8787
where Row : IStructuralReadWrite, new()
88-
where T : IEquatable<T>
8988
where RW : struct, BSATN.IReadWrite<T>
9089
{
9190
private static BTreeIndexBounds<T, RW> ToBounds(T key) => new(key);

crates/bindings-macro/src/sats.rs

+42-2
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ pub(crate) fn derive_satstype(ty: &SatsType<'_>) -> TokenStream {
143143
let name = &ty.ident;
144144
let krate = &ty.krate;
145145

146+
let mut add_impls_for_plain_enum = false;
146147
let typ = match &ty.data {
147148
SatsTypeData::Product(fields) => {
148149
let fields = fields.iter().map(|field| {
@@ -166,6 +167,10 @@ pub(crate) fn derive_satstype(ty: &SatsType<'_>) -> TokenStream {
166167
)
167168
}
168169
SatsTypeData::Sum(variants) => {
170+
// To allow an enum, with all-unit variants, as an index key type,
171+
// add derive `Filterable` for the enum.
172+
add_impls_for_plain_enum = variants.iter().all(|var| var.ty.is_none());
173+
169174
let unit = syn::Type::Tuple(syn::TypeTuple {
170175
paren_token: Default::default(),
171176
elems: Default::default(),
@@ -186,8 +191,7 @@ pub(crate) fn derive_satstype(ty: &SatsType<'_>) -> TokenStream {
186191
[#(#variants),*]
187192
)
188193
)
189-
// todo!()
190-
} // syn::Data::Union(u) => return Err(syn::Error::new(u.union_token.span, "unions not supported")),
194+
}
191195
};
192196

193197
let mut sats_generics = ty.generics.clone();
@@ -205,7 +209,43 @@ pub(crate) fn derive_satstype(ty: &SatsType<'_>) -> TokenStream {
205209
}
206210
let (_, typeid_ty_generics, _) = typeid_generics.split_for_impl();
207211

212+
let impl_plain_enum_extras = if add_impls_for_plain_enum {
213+
// These will mostly be empty as lifetime and type parameters must be constrained
214+
// but const parameters don't require constraining.
215+
let mut generics = ty.generics.clone();
216+
add_type_bounds(&mut generics, &quote!(#krate::FilterableValue));
217+
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
218+
// Assume that the type is `Copy`, as most all-unit enums will be.
219+
let filterable_impl = quote! {
220+
#[automatically_derived]
221+
impl #impl_generics #krate::FilterableValue for #name #ty_generics #where_clause {
222+
type Column = #name #ty_generics;
223+
}
224+
#[automatically_derived]
225+
impl #impl_generics #krate::FilterableValue for &#name #ty_generics #where_clause {
226+
type Column = #name #ty_generics;
227+
}
228+
};
229+
230+
let mut generics = ty.generics.clone();
231+
add_type_bounds(&mut generics, &quote!(#krate::DirectIndexKey));
232+
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
233+
let dik_impl = quote! {
234+
#[automatically_derived]
235+
impl #impl_generics #krate::DirectIndexKey for #name #ty_generics #where_clause {}
236+
};
237+
238+
Some(quote! {
239+
#filterable_impl
240+
#dik_impl
241+
})
242+
} else {
243+
None
244+
};
245+
208246
quote! {
247+
#impl_plain_enum_extras
248+
209249
#[automatically_derived]
210250
impl #impl_generics #krate::SpacetimeType for #name #ty_generics #where_clause {
211251
fn make_type<S: #krate::sats::typespace::TypespaceBuilder>(__typespace: &mut S) -> #krate::sats::AlgebraicType {

crates/bindings-macro/src/table.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ impl ValidatedIndex<'_> {
381381
let col_ty = col.ty;
382382
let typeck = quote_spanned!(col_ty.span()=>
383383
const _: () = {
384-
spacetimedb::rt::assert_column_type_valid_for_direct_index::<#col_ty>();
384+
spacetimedb::spacetimedb_lib::assert_column_type_valid_for_direct_index::<#col_ty>();
385385
};
386386
);
387387
(slice::from_ref(col), Some(typeck))

crates/bindings/src/lib.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ pub use spacetimedb_bindings_macro::client_visibility_filter;
139139
/// for popular_user in by_popularity.filter((100, "a"..)) {
140140
/// log::debug!("Popular user whose name starts with 'a': {:?}", popular_user);
141141
/// }
142-
///
142+
///
143143
/// // For every `#[unique]` or `#[primary_key]` field,
144144
/// // the table has an extra method that allows getting a
145145
/// // corresponding `spacetimedb::UniqueColumn`.
@@ -359,7 +359,7 @@ pub use spacetimedb_bindings_macro::client_visibility_filter;
359359
/// ```ignore
360360
/// ctx.db.cities().latitude()
361361
/// ```
362-
///
362+
///
363363
/// # Generated code
364364
///
365365
/// For each `[table(name = {name})]` annotation on a type `{T}`, generates a struct
@@ -402,7 +402,7 @@ pub use spacetimedb_bindings_macro::client_visibility_filter;
402402
/// impl {name}Handle {
403403
/// // For each `#[unique]` or `#[primary_key]` field `{field}` of type `{F}`:
404404
/// fn {field}(&self) -> UniqueColumn<_, {F}, _> { /* ... */ };
405-
///
405+
///
406406
/// // For each named index `{index}` on fields of type `{(F1, ..., FN)}`:
407407
/// fn {index}(&self) -> RangedIndex<_, {(F1, ..., FN)}, _>;
408408
/// }

crates/bindings/src/rt.rs

-20
Original file line numberDiff line numberDiff line change
@@ -346,26 +346,6 @@ pub fn register_table<T: Table>() {
346346
})
347347
}
348348

349-
mod sealed_direct_index {
350-
pub trait Sealed {}
351-
}
352-
#[diagnostic::on_unimplemented(
353-
message = "column type must be a one of: `u8`, `u16`, `u32`, or `u64`",
354-
label = "should be `u8`, `u16`, `u32`, or `u64`, not `{Self}`"
355-
)]
356-
pub trait DirectIndexKey: sealed_direct_index::Sealed {}
357-
impl sealed_direct_index::Sealed for u8 {}
358-
impl DirectIndexKey for u8 {}
359-
impl sealed_direct_index::Sealed for u16 {}
360-
impl DirectIndexKey for u16 {}
361-
impl sealed_direct_index::Sealed for u32 {}
362-
impl DirectIndexKey for u32 {}
363-
impl sealed_direct_index::Sealed for u64 {}
364-
impl DirectIndexKey for u64 {}
365-
366-
/// Assert that `T` is a valid column to use direct index on.
367-
pub const fn assert_column_type_valid_for_direct_index<T: DirectIndexKey>() {}
368-
369349
impl From<IndexAlgo<'_>> for RawIndexAlgorithm {
370350
fn from(algo: IndexAlgo<'_>) -> RawIndexAlgorithm {
371351
match algo {

0 commit comments

Comments
 (0)