Files
Ryan f9ccf4c1c4 Move Bound struct out of SpacetimeDB.Internal to SpacetimeDB and Local out of SpacetiemDB.Runtime (#3996)
# Description of Changes
This PR fixes a C# SDK regression where using `Bound` in index filters
could trigger an ambiguous reference compiler error for Local after
upgrading to `v1.11.2`, as reported in
[#3995](https://github.com/clockworklabs/SpacetimeDB/issues/3995).
It also fixes a related warning-spam regression (`CS0436`) where user
projects could see `Local` type conflicts between generated module code
and the `SpacetimeDB.Runtime` assembly.

* Introduced a public `SpacetimeDB.Bound` type so users no longer need
to import `SpacetimeDB.Internal` to use bounds in index filters.
* Kept `SpacetimeDB.Internal.Bound` for compatibility, but added
implicit conversions between `SpacetimeDB.Internal.Bound` and
`SpacetimeDB.Bound`.
* Updated the C# code generator to emit fully-qualified
`global::SpacetimeDB.Bound` in generated index filter signatures,
avoiding `SpacetimeDB.Internal` in public-facing APIs and preventing
name collisions (e.g., `Local`).
* Updated internal runtime bounds helpers (`BTreeIndexBounds<...>`) to
explicitly use `SpacetimeDB.Bound` when constructing range-scan
arguments.
* Updated Codegen snapshot fixtures to match the new generated output
(type name + formatting).
* Fixed codegen output for `ITableView` `static abstract` member
implementations to generate `public static` methods (required for the
generated code to compile).
It also fixes a related warning-spam regression (CS0436) where user
projects could see Local type conflicts between generated module code
and the SpacetimeDB.Runtime assembly.

Additional fix (related to the `Local` reports):
* Removed the runtime assembly’s ownership of `SpacetimeDB.Local`
(introduced more recently than the generated module `Local`) to prevent
`CS0436` duplicate-type warnings. Basically, the runtime’s concrete
`Local`/`ProcedureTxContext` helpers were renamed and made internal so
the code generator remains the sole owner of module-level
`SpacetimeDB.Local`.

Regression coverage:
* Added generator regression assertions to ensure generated code does
not reference `global::SpacetimeDB.Internal.Bound<...>` and does
reference `global::SpacetimeDB.Bound<...>`.
* Added a runtime API regression assertion that `SpacetimeDB.Bound`
exists and is public in the runtime reference.
* Added a regression assertion that `SpacetimeDB.Runtime` does not
define codegen-owned types (e.g. `SpacetimeDB.Local`,
`ProcedureContext`, etc.) to prevent future `CS0436` conflicts.
* Added a “simulated downstream user file” compile check ensuring no
`CS0436` diagnostics occur when user code references
`SpacetimeDB.Local`.
# API and ABI breaking changes
None.
* No schema or wire-format changes.
* The changes are limited to C# type exposure / naming and codegen
output.
* `SpacetimeDB.Internal.Bound` remains usable via implicit conversions
(backwards compatible for existing code).
# Expected complexity level and risk
2 - Low
* Changes are isolated to C# runtime type exposure, codegen type
references, and snapshot updates.
* No runtime behavior changes to index scan encoding/decoding; only
avoids requiring SpacetimeDB.Internal in user code.
# Testing
- [X] Ran:`dotnet test
crates/bindings-csharp/Codegen.Tests/Codegen.Tests.csproj`
- [X] Ran regression tests locally.
2026-01-14 22:42:30 +00:00

426 lines
11 KiB
C#

using System.IO;
using SpacetimeDB.BSATN;
namespace SpacetimeDB
{
public readonly struct Bound<T>(T min, T max)
{
public T Min => min;
public T Max => max;
public static implicit operator Bound<T>(T value) => new(value, value);
public static implicit operator Bound<T>((T min, T max) span) => new(span.min, span.max);
}
}
namespace SpacetimeDB.Internal
{
enum BoundVariant : byte
{
Inclusive,
Exclusive,
Unbounded,
}
public interface IBTreeIndexBounds
{
ushort PrefixElems { get; }
void Prefix(BinaryWriter w);
void RStart(BinaryWriter w);
void REnd(BinaryWriter w);
}
public readonly struct Bound<T>(T min, T max)
{
public T Min => min;
public T Max => max;
public static implicit operator Bound<T>(T value) => new(value, value);
public static implicit operator Bound<T>((T min, T max) span) => new(span.min, span.max);
public static implicit operator SpacetimeDB.Bound<T>(Bound<T> value) =>
new(value.Min, value.Max);
public static implicit operator Bound<T>(SpacetimeDB.Bound<T> value) =>
new(value.Min, value.Max);
}
public readonly struct BTreeIndexBounds<T, TRW>(SpacetimeDB.Bound<T> t) : IBTreeIndexBounds
where TRW : struct, IReadWrite<T>
{
public ushort PrefixElems => 0;
public void Prefix(BinaryWriter _) { }
public void RStart(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new TRW().Write(w, t.Min);
}
public void REnd(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new TRW().Write(w, t.Max);
}
}
public readonly struct BTreeIndexBounds<T, TRW, U, URW>((T t, SpacetimeDB.Bound<U> u) b)
: IBTreeIndexBounds
where TRW : struct, IReadWrite<T>
where URW : struct, IReadWrite<U>
{
public ushort PrefixElems => 1;
public void Prefix(BinaryWriter w)
{
new TRW().Write(w, b.t);
}
public void RStart(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new URW().Write(w, b.u.Min);
}
public void REnd(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new URW().Write(w, b.u.Max);
}
}
public readonly struct BTreeIndexBounds<T, TRW, U, URW, V, VRW>(
(T t, U u, SpacetimeDB.Bound<V> v) b
) : IBTreeIndexBounds
where TRW : struct, IReadWrite<T>
where URW : struct, IReadWrite<U>
where VRW : struct, IReadWrite<V>
{
public ushort PrefixElems => 2;
public void Prefix(BinaryWriter w)
{
new TRW().Write(w, b.t);
new URW().Write(w, b.u);
}
public void RStart(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new VRW().Write(w, b.v.Min);
}
public void REnd(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new VRW().Write(w, b.v.Max);
}
}
public readonly struct BTreeIndexBounds<T, TRW, U, URW, V, VRW, W, WRW>(
(T t, U u, V v, SpacetimeDB.Bound<W> w) b
) : IBTreeIndexBounds
where TRW : struct, IReadWrite<T>
where URW : struct, IReadWrite<U>
where VRW : struct, IReadWrite<V>
where WRW : struct, IReadWrite<W>
{
public ushort PrefixElems => 3;
public void Prefix(BinaryWriter w)
{
new TRW().Write(w, b.t);
new URW().Write(w, b.u);
new VRW().Write(w, b.v);
}
public void RStart(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new WRW().Write(w, b.w.Min);
}
public void REnd(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new WRW().Write(w, b.w.Max);
}
}
public readonly struct BTreeIndexBounds<T, TRW, U, URW, V, VRW, W, WRW, X, XRW>(
(T t, U u, V v, W w, SpacetimeDB.Bound<X> x) b
) : IBTreeIndexBounds
where TRW : struct, IReadWrite<T>
where URW : struct, IReadWrite<U>
where VRW : struct, IReadWrite<V>
where WRW : struct, IReadWrite<W>
where XRW : struct, IReadWrite<X>
{
public ushort PrefixElems => 4;
public void Prefix(BinaryWriter w)
{
new TRW().Write(w, b.t);
new URW().Write(w, b.u);
new VRW().Write(w, b.v);
new WRW().Write(w, b.w);
}
public void RStart(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new XRW().Write(w, b.x.Min);
}
public void REnd(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new XRW().Write(w, b.x.Max);
}
}
public readonly struct BTreeIndexBounds<T, TRW, U, URW, V, VRW, W, WRW, X, XRW, Y, YRW>(
(T t, U u, V v, W w, X x, SpacetimeDB.Bound<Y> y) b
) : IBTreeIndexBounds
where TRW : struct, IReadWrite<T>
where URW : struct, IReadWrite<U>
where VRW : struct, IReadWrite<V>
where WRW : struct, IReadWrite<W>
where XRW : struct, IReadWrite<X>
where YRW : struct, IReadWrite<Y>
{
public ushort PrefixElems => 5;
public void Prefix(BinaryWriter w)
{
new TRW().Write(w, b.t);
new URW().Write(w, b.u);
new VRW().Write(w, b.v);
new WRW().Write(w, b.w);
new XRW().Write(w, b.x);
}
public void RStart(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new YRW().Write(w, b.y.Min);
}
public void REnd(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new YRW().Write(w, b.y.Max);
}
}
public readonly struct BTreeIndexBounds<T, TRW, U, URW, V, VRW, W, WRW, X, XRW, Y, YRW, Z, ZRW>(
(T t, U u, V v, W w, X x, Y y, SpacetimeDB.Bound<Z> z) b
) : IBTreeIndexBounds
where TRW : struct, IReadWrite<T>
where URW : struct, IReadWrite<U>
where VRW : struct, IReadWrite<V>
where WRW : struct, IReadWrite<W>
where XRW : struct, IReadWrite<X>
where YRW : struct, IReadWrite<Y>
where ZRW : struct, IReadWrite<Z>
{
public ushort PrefixElems => 6;
public void Prefix(BinaryWriter w)
{
new TRW().Write(w, b.t);
new URW().Write(w, b.u);
new VRW().Write(w, b.v);
new WRW().Write(w, b.w);
new XRW().Write(w, b.x);
new YRW().Write(w, b.y);
}
public void RStart(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new ZRW().Write(w, b.z.Min);
}
public void REnd(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new ZRW().Write(w, b.z.Max);
}
}
public readonly struct BTreeIndexBounds<
T,
TRW,
U,
URW,
V,
VRW,
W,
WRW,
X,
XRW,
Y,
YRW,
Z,
ZRW,
A,
ARW
>((T t, U u, V v, W w, X x, Y y, Z z, SpacetimeDB.Bound<A> a) b) : IBTreeIndexBounds
where TRW : struct, IReadWrite<T>
where URW : struct, IReadWrite<U>
where VRW : struct, IReadWrite<V>
where WRW : struct, IReadWrite<W>
where XRW : struct, IReadWrite<X>
where YRW : struct, IReadWrite<Y>
where ZRW : struct, IReadWrite<Z>
where ARW : struct, IReadWrite<A>
{
public ushort PrefixElems => 7;
public void Prefix(BinaryWriter w)
{
new TRW().Write(w, b.t);
new URW().Write(w, b.u);
new VRW().Write(w, b.v);
new WRW().Write(w, b.w);
new XRW().Write(w, b.x);
new YRW().Write(w, b.y);
new ZRW().Write(w, b.z);
}
public void RStart(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new ARW().Write(w, b.a.Min);
}
public void REnd(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new ARW().Write(w, b.a.Max);
}
}
public readonly struct BTreeIndexBounds<
T,
TRW,
U,
URW,
V,
VRW,
W,
WRW,
X,
XRW,
Y,
YRW,
Z,
ZRW,
A,
ARW,
B,
BRW
>((T t, U u, V v, W w, X x, Y y, Z z, A a, SpacetimeDB.Bound<B> b) b) : IBTreeIndexBounds
where TRW : struct, IReadWrite<T>
where URW : struct, IReadWrite<U>
where VRW : struct, IReadWrite<V>
where WRW : struct, IReadWrite<W>
where XRW : struct, IReadWrite<X>
where YRW : struct, IReadWrite<Y>
where ZRW : struct, IReadWrite<Z>
where ARW : struct, IReadWrite<A>
where BRW : struct, IReadWrite<B>
{
public ushort PrefixElems => 8;
public void Prefix(BinaryWriter w)
{
new TRW().Write(w, b.t);
new URW().Write(w, b.u);
new VRW().Write(w, b.v);
new WRW().Write(w, b.w);
new XRW().Write(w, b.x);
new YRW().Write(w, b.y);
new ZRW().Write(w, b.z);
new ARW().Write(w, b.a);
}
public void RStart(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new BRW().Write(w, b.b.Min);
}
public void REnd(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new BRW().Write(w, b.b.Max);
}
}
public readonly struct BTreeIndexBounds<
T,
TRW,
U,
URW,
V,
VRW,
W,
WRW,
X,
XRW,
Y,
YRW,
Z,
ZRW,
A,
ARW,
B,
BRW,
C,
CRW
>((T t, U u, V v, W w, X x, Y y, Z z, A a, B b, SpacetimeDB.Bound<C> c) b) : IBTreeIndexBounds
where TRW : struct, IReadWrite<T>
where URW : struct, IReadWrite<U>
where VRW : struct, IReadWrite<V>
where WRW : struct, IReadWrite<W>
where XRW : struct, IReadWrite<X>
where YRW : struct, IReadWrite<Y>
where ZRW : struct, IReadWrite<Z>
where ARW : struct, IReadWrite<A>
where BRW : struct, IReadWrite<B>
where CRW : struct, IReadWrite<C>
{
public ushort PrefixElems => 9;
public void Prefix(BinaryWriter w)
{
new TRW().Write(w, b.t);
new URW().Write(w, b.u);
new VRW().Write(w, b.v);
new WRW().Write(w, b.w);
new XRW().Write(w, b.x);
new YRW().Write(w, b.y);
new ZRW().Write(w, b.z);
new ARW().Write(w, b.a);
new BRW().Write(w, b.b);
}
public void RStart(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new CRW().Write(w, b.c.Min);
}
public void REnd(BinaryWriter w)
{
w.Write((byte)BoundVariant.Inclusive);
new CRW().Write(w, b.c.Max);
}
}
}