Files
Ryan 6f91cfd524 Enable RefOption returns from Views to support Views returning a single class (#3964)
# Description of Changes

This PR adds a regression test covering nullable reference-type view
returns in C# modules (e.g. Account? where Account is a class), as
reported in
[#3962](https://github.com/clockworklabs/SpacetimeDB/issues/3962).

```csharp
public static Account? MyAccount(ViewContext ctx)
```
* Updated the C# regression-test server module to include a
reference-type table and views that exercise the RefOption path:
* A new public reference-type table row:
```csharp
[SpacetimeDB.Table(Name = "account", Public = true)]
public partial class Account { ... }
```
* A public at-most-one view that returns a nullable reference type
(Account?) via Find(...):
```csharp
[SpacetimeDB.View(Name = "my_account", Public = true)]
public static Account? MyAccount(ViewContext ctx)
{
    return ctx.Db.account.Identity.Find(ctx.Sender) as Account;
}
```
* A second public view that returns null to ensure the “empty result”
case is exercised:
```csharp
[SpacetimeDB.View(Name = "my_account_missing", Public = true)]
public static Account? MyAccountMissing(ViewContext ctx) => null;
```
* Updated ClientConnected to ensure an Account row exists for the
connecting identity so the “one row” case is deterministic.
* Updated the C# regression-test client to:
  * Subscribe to the new views:
    * `SELECT * FROM my_account`
    * `SELECT * FROM my_account_missing`
  * Assert correct semantics for nullable reference-type view returns:
    * MyAccount.Count == 1
    * MyAccountMissing.Count == 0
* Updated the regression-test server project to use local C#
runtime/codegen project references so the regression module exercises
the in-repo generator/runtime behavior (instead of the published
SpacetimeDB.Runtime package).

# API and ABI breaking changes

None.
* No changes to public module schema/wire format semantics beyond adding
regression-test-only tables/views.
* No behavior changes outside the C# regression test module + harness.

# Expected complexity level and risk

2 - Low

* Changes are isolated to regression tests and project wiring.
* The scenario specifically guards the nullable reference-type
“Option-like view return” path against regressions.

# 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] Ran C# regression tests with no failures in new View tests

Signed-off-by: Ryan <r.ekhoff@clockworklabs.io>
2026-01-15 00:21:08 +00:00
..
2026-01-08 18:55:30 +00:00

SpacetimeDB.Codegen

This project contains Roslyn incremental source generators that augment types and tables with static methods for self-describing and registration. They look for different attributes to know which types to augment:

  • [SpacetimeDB.Type] - generates a GetSatsTypeInfo() static method that registers this type with the runtime and returns a TypeInfo object. It supports only structs for now to explicitly forbid infinitely recursive types and to make the implementation simpler, as it doesn't need to deal with type references - each table is registered as an entirely self-contained type together with its nested structs if any. This is unlikely to be a problem in common scenarios, but it will be optimised in the future.

    All the nested fields will be added to the product type. Because it's not possible to implement static extension methods on 3rd-party types (including built-ins) in C#, the codegen is responsible for manually routing different types to their TypeInfo descriptors. See various static TypeInfo properties and helper methods on SpacetimeDB.BSATN.AlgebraicType (Runtime/AlgebraicType.cs) and routing logic in Utils.GetTypeInfo (Codegen/Utils.cs) for more details.

    Also, for the same reason - absence of static extension methods in C# - the codegen expects that your struct, as well as any of its parents, is partial so methods can be added from extra source files generated by the codegen.

  • [SpacetimeDB.Type] - also supports emulation of tagged enums in C#. For that, the struct needs to inherit a marker interface SpacetimeDB.TaggedEnum<Variants> where Variants is a named tuple of all possible variants, e.g.:

    [SpacetimeDB.Type]
    partial record Option<T> : SpacetimeDB.TaggedEnum<(T Some, Unit None)>;
    

    will generate inherited records Option.Some(T Some_) and Option.None(Unit None_). It allows you to use tagged enums in C# in a similar way to Rust enums by leveraging C# pattern-matching on any instance of Option<T>.

  • [SpacetimeDB.Table] - generates code to register this table in the FFI upon startup so that they can be enumerated by the __describe_module__ FFI API. It implies [SpacetimeDB.Type], so you must not specify both attributes on the same struct.

    The fields can be marked with [SpacetimeDB.ColumnAttrs] and those will be detected by the codegen and passed on to the runtime as well. Example:

    [SpacetimeDB.Table]
    public partial struct Person
    {
        [SpacetimeDB.Column(ColumnAttrs.Identity)]
        public int Id;
        public string Name;
    }
    
  • [SpacetimeDB.Reducer] - generates code to register a static function as a SpacetimeDB reducer in the FFI upon startup and creates a wrapper that will parse SATS binary blob into individual arguments and invoke the underlying function for the __call_reducer__ FFI API.