Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion crates/bindings-csharp/BSATN.Codegen/Type.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@
),
INamedTypeSymbol named => named.OriginalDefinition.ToString() switch
{
"SpacetimeDB.Result<T, E>" or "SpacetimeDB.Result<T,E>" => new ResultUse(
type,
typeInfo,
Parse(member, named.TypeArguments[0], diag),
Parse(member, named.TypeArguments[1], diag)
),
"System.Collections.Generic.List<T>" => new ListUse(
type,
typeInfo,
Expand All @@ -101,6 +107,14 @@
};
}

/// <summary>
/// Get the name of the BSATN struct for this type.
/// </summary>
public virtual string to_bsatn_string()
{
return this.BSATNName;
}

/// <summary>
/// Get a statement that declares outVar and assigns (inVar1 logically-equals inVar2) to it.
/// logically-equals:
Expand Down Expand Up @@ -133,6 +147,40 @@
public abstract string GetHashCodeStatement(string inVar, string outVar, int level = 0);
}

/// <summary>
/// A use of a Result<T, E> type.

Check warning on line 151 in crates/bindings-csharp/BSATN.Codegen/Type.cs

View workflow job for this annotation

GitHub Actions / Test Suite

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check warning on line 151 in crates/bindings-csharp/BSATN.Codegen/Type.cs

View workflow job for this annotation

GitHub Actions / csharp-testsuite

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check warning on line 151 in crates/bindings-csharp/BSATN.Codegen/Type.cs

View workflow job for this annotation

GitHub Actions / unity-testsuite

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'
/// </summary>

Check warning on line 152 in crates/bindings-csharp/BSATN.Codegen/Type.cs

View workflow job for this annotation

GitHub Actions / Test Suite

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'T'.'

Check warning on line 152 in crates/bindings-csharp/BSATN.Codegen/Type.cs

View workflow job for this annotation

GitHub Actions / csharp-testsuite

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'T'.'

Check warning on line 152 in crates/bindings-csharp/BSATN.Codegen/Type.cs

View workflow job for this annotation

GitHub Actions / unity-testsuite

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'T'.'
public sealed record ResultUse : TypeUse

Check warning on line 153 in crates/bindings-csharp/BSATN.Codegen/Type.cs

View workflow job for this annotation

GitHub Actions / Test Suite

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check warning on line 153 in crates/bindings-csharp/BSATN.Codegen/Type.cs

View workflow job for this annotation

GitHub Actions / csharp-testsuite

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check warning on line 153 in crates/bindings-csharp/BSATN.Codegen/Type.cs

View workflow job for this annotation

GitHub Actions / unity-testsuite

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'
{
public TypeUse Ok { get; }
public TypeUse Err { get; }

public string TypeName { get; }

public ResultUse(string typeName, string typeInfo, TypeUse ok, TypeUse err)
: base(typeName, typeInfo)
{
Ok = ok;
Err = err;
TypeName = typeName;
}

public override string to_bsatn_string()
{
return $"{TypeName}.BSATN<{Ok.BSATNName}, {Err.BSATNName}>";
}

public override string EqualsStatement(
string inVar1,
string inVar2,
string outVar,
int level = 0
) => $"var {outVar} = {inVar1} == {inVar2};";

public override string GetHashCodeStatement(string inVar, string outVar, int level = 0) =>
$"var {outVar} = {inVar}.GetHashCode();";
}

/// <summary>
/// A use of an enum type.
/// (This is a C# enum, not one of our tagged enums.)
Expand Down Expand Up @@ -348,7 +396,7 @@
return string.Join(
"\n ",
members.Select(m =>
$"{visStr} static readonly {m.Type.BSATNName} {m.Name}{TypeUse.BsatnFieldSuffix} = new();"
$"{visStr} static readonly {m.Type.to_bsatn_string()} {m.Name}{TypeUse.BsatnFieldSuffix} = new();"
)
);
}
Expand Down
4 changes: 4 additions & 0 deletions crates/bindings-csharp/BSATN.Runtime/BSATN/AlgebraicType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,8 @@ Unit F64
// Special AlgebraicType that can be recognised by the SpacetimeDB `generate` CLI as an Option<T>.
internal static AlgebraicType MakeOption(AlgebraicType someType) =>
new Sum([new("some", someType), new("none", Unit)]);

// Special AlgebraicType that can be recognised by the SpacetimeDB `generate` CLI as a Result<T, E>.
internal static AlgebraicType MakeResult(AlgebraicType okType, AlgebraicType errType) =>
new Sum([new("ok", okType), new("err", errType)]);
}
74 changes: 74 additions & 0 deletions crates/bindings-csharp/BSATN.Runtime/Builtins.cs
Original file line number Diff line number Diff line change
Expand Up @@ -605,3 +605,77 @@ public AlgebraicType GetAlgebraicType(ITypeRegistrar registrar) =>
// --- / customized ---
}
}

public partial record Result<T, E> : TaggedEnum<(T Ok, E Err)>
{
public static implicit operator Result<T, E>(T value) => new Ok(value);

public static implicit operator Result<T, E>(E error) => new Err(error);

public TResult Match<TResult>(Func<T, TResult> onOk, Func<E, TResult> onErr) =>
this switch
{
Ok(var v) => onOk(v),
Err(var e) => onErr(e),
_ => throw new InvalidOperationException("Unknown Result variant."),
};

// ----- auto-generated -----

private Result() { }

internal enum @enum : byte
{
Ok,
Err,
}

public sealed record Ok(T Value) : Result<T, E>;

public sealed record Err(E Error) : Result<T, E>;

private enum Variant : byte
{
Ok = 0,
Err = 1,
}

public readonly struct BSATN<OkRW, ErrRW> : IReadWrite<Result<T, E>>
where OkRW : struct, IReadWrite<T>
where ErrRW : struct, IReadWrite<E>
{
private static readonly SpacetimeDB.BSATN.Enum<@enum> __enumTag = new();
private static readonly OkRW okRW = new();
private static readonly ErrRW errRW = new();

public Result<T, E> Read(BinaryReader reader) =>
__enumTag.Read(reader) switch
{
@enum.Ok => new Ok(okRW.Read(reader)),
@enum.Err => new Err(errRW.Read(reader)),
_ => throw new InvalidOperationException(),
};

public void Write(BinaryWriter writer, Result<T, E> value)
{
switch (value)
{
case Ok(var v):
__enumTag.Write(writer, @enum.Ok);
okRW.Write(writer, v);
break;

case Err(var e):
__enumTag.Write(writer, @enum.Err);
errRW.Write(writer, e);
break;
}
}

public AlgebraicType GetAlgebraicType(ITypeRegistrar registrar) =>
AlgebraicType.MakeResult(
okRW.GetAlgebraicType(registrar),
errRW.GetAlgebraicType(registrar)
);
}
}
6 changes: 3 additions & 3 deletions crates/bindings-csharp/Codegen/Module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,7 @@ public Scope.Extensions GenerateSchedule()
using var writer = new BinaryWriter(stream);
{{string.Join(
"\n",
Args.Select(a => $"new {a.Type.BSATNName}().Write(writer, {a.Name});")
Args.Select(a => $"new {a.Type.to_bsatn_string()}().Write(writer, {a.Name});")
)}}
SpacetimeDB.Internal.IReducer.VolatileNonatomicScheduleImmediate(nameof({{Name}}), stream);
}
Expand Down Expand Up @@ -1239,7 +1239,7 @@ public string GenerateClass()
var result = {{FullName}}((SpacetimeDB.ProcedureContext)ctx{{invocationArgs}});
using var output = new MemoryStream();
using var writer = new BinaryWriter(output);
new {{ReturnType.BSATNName}}().Write(writer, result);
new {{ReturnType.to_bsatn_string()}}().Write(writer, result);
return output.ToArray();
""";

Expand Down Expand Up @@ -1283,7 +1283,7 @@ public Scope.Extensions GenerateSchedule()
using var writer = new BinaryWriter(stream);
{{string.Join(
"\n",
Args.Select(a => $"new {a.Type.BSATNName}().Write(writer, {a.Name});")
Args.Select(a => $"new {a.Type.to_bsatn_string()}().Write(writer, {a.Name});")
)}}
SpacetimeDB.Internal.IProcedure.VolatileNonatomicScheduleImmediate(nameof({{Name}}), stream);
}
Expand Down
1 change: 1 addition & 0 deletions crates/bindings-typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export * from './lib/timestamp';
export * from './lib/util';
export * from './lib/identity';
export * from './lib/option';
export * from './lib/result';
export * from './sdk';
52 changes: 52 additions & 0 deletions crates/bindings-typescript/src/lib/algebraic_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,36 @@ export const SumType = {
} else {
writer.writeByte(1);
}
} else if (
ty.variants.length == 2 &&
ty.variants[0].name === 'ok' &&
ty.variants[1].name === 'err'
) {
let variantName: 'ok' | 'err';
let innerValue: any;
let index: number;
if ('ok' in value) {
variantName = 'ok';
innerValue = value.ok;
index = 0;
} else {
variantName = 'err';
innerValue = value.err;
index = 1;
}

if (index < 0) {
throw `Result serialization error: variant '${variantName}' not found in ${JSON.stringify(ty)}`;
}

writer.writeU8(index);

AlgebraicType.serializeValue(
writer,
ty.variants[index].algebraicType,
innerValue,
typespace
);
} else {
const variant = value['tag'];
const index = ty.variants.findIndex(v => v.name === variant);
Expand Down Expand Up @@ -479,6 +509,28 @@ export const SumType = {
} else {
throw `Can't deserialize an option type, couldn't find ${tag} tag`;
}
} else if (
ty.variants.length == 2 &&
ty.variants[0].name === 'ok' &&
ty.variants[1].name === 'err'
) {
if (tag === 0) {
const value = AlgebraicType.deserializeValue(
reader,
ty.variants[0].algebraicType,
typespace
);
return { ok: value };
} else if (tag === 1) {
const value = AlgebraicType.deserializeValue(
reader,
ty.variants[1].algebraicType,
typespace
);
return { err: value };
} else {
throw `Can't deserialize a result type, couldn't find ${tag} tag`;
}
} else {
const variant = ty.variants[tag];
const value = AlgebraicType.deserializeValue(
Expand Down
36 changes: 36 additions & 0 deletions crates/bindings-typescript/src/lib/result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { AlgebraicType } from './algebraic_type';

export type ResultAlgebraicType<
T extends AlgebraicType = AlgebraicType,
E extends AlgebraicType = AlgebraicType,
> = {
tag: 'Sum';
value: {
variants: [
{ name: 'ok'; algebraicType: T },
{ name: 'err'; algebraicType: E },
];
};
};

export const Result: {
getAlgebraicType<
T extends AlgebraicType = AlgebraicType,
E extends AlgebraicType = AlgebraicType,
>(
okType: T,
errType: E
): ResultAlgebraicType<T, E>;
} = {
getAlgebraicType<
T extends AlgebraicType = AlgebraicType,
E extends AlgebraicType = AlgebraicType,
>(okType: T, errType: E): ResultAlgebraicType<T, E> {
return AlgebraicType.Sum({
variants: [
{ name: 'ok', algebraicType: okType },
{ name: 'err', algebraicType: errType },
],
});
},
};
6 changes: 6 additions & 0 deletions crates/bindings-typescript/src/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
type InferSpacetimeTypeOfTypeBuilder,
type RowObj,
type VariantsObj,
ResultBuilder,
} from './type_builders';
import type { UntypedTableDef } from './table';
import {
Expand Down Expand Up @@ -203,6 +204,11 @@ export function registerTypesRecursively<
return new OptionBuilder(
registerTypesRecursively(typeBuilder.value)
) as any;
} else if (typeBuilder instanceof ResultBuilder) {
return new ResultBuilder(
registerTypesRecursively(typeBuilder.ok),
registerTypesRecursively(typeBuilder.err)
) as any;
} else if (typeBuilder instanceof ArrayBuilder) {
return new ArrayBuilder(
registerTypesRecursively(typeBuilder.element)
Expand Down
Loading