Skip to content

Commit 604c452

Browse files
committed
Implement SpacetimeType for Result<T, E>
1 parent 0a19208 commit 604c452

Some content is hidden

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

49 files changed

+2343
-56
lines changed

crates/bindings-csharp/BSATN.Codegen/Type.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ public static TypeUse Parse(ISymbol member, ITypeSymbol typeSymbol, DiagReporter
9090
),
9191
INamedTypeSymbol named => named.OriginalDefinition.ToString() switch
9292
{
93+
"SpacetimeDB.Result<T, E>" or "SpacetimeDB.Result<T,E>" => new ResultUse(
94+
type,
95+
typeInfo,
96+
Parse(member, named.TypeArguments[0], diag),
97+
Parse(member, named.TypeArguments[1], diag)
98+
),
9399
"System.Collections.Generic.List<T>" => new ListUse(
94100
type,
95101
typeInfo,
@@ -107,6 +113,14 @@ public static TypeUse Parse(ISymbol member, ITypeSymbol typeSymbol, DiagReporter
107113
};
108114
}
109115

116+
/// <summary>
117+
/// Get the name of the BSATN struct for this type.
118+
/// </summary>
119+
public virtual string ToBSATNString()
120+
{
121+
return this.BSATNName;
122+
}
123+
110124
/// <summary>
111125
/// Get a statement that declares outVar and assigns (inVar1 logically-equals inVar2) to it.
112126
/// logically-equals:
@@ -139,6 +153,40 @@ public abstract string EqualsStatement(
139153
public abstract string GetHashCodeStatement(string inVar, string outVar, int level = 0);
140154
}
141155

156+
/// <summary>
157+
/// A use of a Result&lt;T, E&gt; type.
158+
/// </summary>
159+
public sealed record ResultUse : TypeUse
160+
{
161+
public TypeUse Ok { get; }
162+
public TypeUse Err { get; }
163+
164+
public string TypeName { get; }
165+
166+
public ResultUse(string typeName, string typeInfo, TypeUse ok, TypeUse err)
167+
: base(typeName, typeInfo)
168+
{
169+
Ok = ok;
170+
Err = err;
171+
TypeName = typeName;
172+
}
173+
174+
public override string ToBSATNString()
175+
{
176+
return $"{TypeName}.BSATN<{Ok.BSATNName}, {Err.BSATNName}>";
177+
}
178+
179+
public override string EqualsStatement(
180+
string inVar1,
181+
string inVar2,
182+
string outVar,
183+
int level = 0
184+
) => $"var {outVar} = {inVar1} == {inVar2};";
185+
186+
public override string GetHashCodeStatement(string inVar, string outVar, int level = 0) =>
187+
$"var {outVar} = {inVar}.GetHashCode();";
188+
}
189+
142190
/// <summary>
143191
/// A use of an enum type.
144192
/// (This is a C# enum, not one of our tagged enums.)
@@ -354,7 +402,7 @@ IEnumerable<MemberDeclaration> members
354402
return string.Join(
355403
"\n ",
356404
members.Select(m =>
357-
$"{visStr} static readonly {m.Type.BSATNName} {m.Name}{TypeUse.BsatnFieldSuffix} = new();"
405+
$"{visStr} static readonly {m.Type.ToBSATNString()} {m.Name}{TypeUse.BsatnFieldSuffix} = new();"
358406
)
359407
);
360408
}

crates/bindings-csharp/BSATN.Runtime/BSATN/AlgebraicType.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,8 @@ Unit F64
4343
// Special AlgebraicType that can be recognised by the SpacetimeDB `generate` CLI as an Option<T>.
4444
internal static AlgebraicType MakeOption(AlgebraicType someType) =>
4545
new Sum([new("some", someType), new("none", Unit)]);
46+
47+
// Special AlgebraicType that can be recognised by the SpacetimeDB `generate` CLI as a Result<T, E>.
48+
internal static AlgebraicType MakeResult(AlgebraicType okType, AlgebraicType errType) =>
49+
new Sum([new("ok", okType), new("err", errType)]);
4650
}

crates/bindings-csharp/BSATN.Runtime/Builtins.cs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,3 +605,109 @@ public AlgebraicType GetAlgebraicType(ITypeRegistrar registrar) =>
605605
// --- / customized ---
606606
}
607607
}
608+
609+
public partial record Result<T, E> : TaggedEnum<(T Ok, E Err)>
610+
{
611+
public static implicit operator Result<T, E>(T value) => new OkR(value);
612+
613+
public static implicit operator Result<T, E>(E error) => new ErrR(error);
614+
615+
public TResult Match<TResult>(Func<T, TResult> onOk, Func<E, TResult> onErr) =>
616+
this switch
617+
{
618+
OkR(var v) => onOk(v),
619+
ErrR(var e) => onErr(e),
620+
_ => throw new InvalidOperationException("Unknown Result variant."),
621+
};
622+
623+
public static Result<T, E> Ok(T value) => new OkR(value);
624+
625+
public static Result<T, E> Err(E error) => new ErrR(error);
626+
627+
public T UnwrapOrThrow()
628+
{
629+
return this switch
630+
{
631+
OkR(var v) => v,
632+
ErrR(var e) when e is not null => throw new Exception(e.ToString()),
633+
ErrR(_) => throw new InvalidOperationException(
634+
"Result failed without an error object."
635+
),
636+
_ => throw new InvalidOperationException("Unknown Result variant."),
637+
};
638+
}
639+
640+
public T UnwrapOr(T defaultValue) =>
641+
this switch
642+
{
643+
OkR(var v) => v,
644+
_ => defaultValue,
645+
};
646+
647+
public T UnwrapOrElse(Func<E, T> f) =>
648+
this switch
649+
{
650+
OkR(var v) => v,
651+
ErrR(var e) => f(e),
652+
_ => throw new InvalidOperationException("Unknown Result variant."),
653+
};
654+
655+
// ----- auto-generated -----
656+
657+
private Result() { }
658+
659+
internal enum @enum : byte
660+
{
661+
Ok,
662+
Err,
663+
}
664+
665+
public sealed record OkR(T Value) : Result<T, E>;
666+
667+
public sealed record ErrR(E Error) : Result<T, E>;
668+
669+
private enum Variant : byte
670+
{
671+
Ok = 0,
672+
Err = 1,
673+
}
674+
675+
public readonly struct BSATN<OkRW, ErrRW> : IReadWrite<Result<T, E>>
676+
where OkRW : struct, IReadWrite<T>
677+
where ErrRW : struct, IReadWrite<E>
678+
{
679+
private static readonly SpacetimeDB.BSATN.Enum<@enum> __enumTag = new();
680+
private static readonly OkRW okRW = new();
681+
private static readonly ErrRW errRW = new();
682+
683+
public Result<T, E> Read(BinaryReader reader) =>
684+
__enumTag.Read(reader) switch
685+
{
686+
@enum.Ok => new OkR(okRW.Read(reader)),
687+
@enum.Err => new ErrR(errRW.Read(reader)),
688+
_ => throw new InvalidOperationException(),
689+
};
690+
691+
public void Write(BinaryWriter writer, Result<T, E> value)
692+
{
693+
switch (value)
694+
{
695+
case OkR(var v):
696+
__enumTag.Write(writer, @enum.Ok);
697+
okRW.Write(writer, v);
698+
break;
699+
700+
case ErrR(var e):
701+
__enumTag.Write(writer, @enum.Err);
702+
errRW.Write(writer, e);
703+
break;
704+
}
705+
}
706+
707+
public AlgebraicType GetAlgebraicType(ITypeRegistrar registrar) =>
708+
AlgebraicType.MakeResult(
709+
okRW.GetAlgebraicType(registrar),
710+
errRW.GetAlgebraicType(registrar)
711+
);
712+
}
713+
}

crates/bindings-csharp/Codegen/Module.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,7 +1178,7 @@ public Scope.Extensions GenerateSchedule()
11781178
using var writer = new BinaryWriter(stream);
11791179
{{string.Join(
11801180
"\n",
1181-
Args.Select(a => $"new {a.Type.BSATNName}().Write(writer, {a.Name});")
1181+
Args.Select(a => $"new {a.Type.ToBSATNString()}().Write(writer, {a.Name});")
11821182
)}}
11831183
SpacetimeDB.Internal.IReducer.VolatileNonatomicScheduleImmediate(nameof({{Name}}), stream);
11841184
}
@@ -1295,7 +1295,7 @@ public string GenerateClass()
12951295
}
12961296
else
12971297
{
1298-
var serializer = $"new {ReturnType.BSATNName}()";
1298+
var serializer = $"new {ReturnType.ToBSATNString()}()";
12991299
bodyLines = new[]
13001300
{
13011301
$"var result = {invocation};",
@@ -1372,7 +1372,7 @@ public Scope.Extensions GenerateSchedule()
13721372
using var writer = new BinaryWriter(stream);
13731373
{{string.Join(
13741374
"\n",
1375-
Args.Select(a => $"new {a.Type.BSATNName}().Write(writer, {a.Name});")
1375+
Args.Select(a => $"new {a.Type.ToBSATNString()}().Write(writer, {a.Name});")
13761376
)}}
13771377
SpacetimeDB.Internal.ProcedureExtensions.VolatileNonatomicScheduleImmediate(nameof({{Name}}), stream);
13781378
}

crates/bindings-csharp/Runtime/ProcedureContext.cs

Lines changed: 10 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,6 @@ namespace SpacetimeDB;
33
using System.Diagnostics.CodeAnalysis;
44
using Internal;
55

6-
public readonly struct Result<T, E>(bool isSuccess, T? value, E? error)
7-
where E : Exception
8-
{
9-
public bool IsSuccess { get; } = isSuccess;
10-
public T? Value { get; } = value;
11-
public E? Error { get; } = error;
12-
13-
public static Result<T, E> Ok(T value) => new(true, value, null);
14-
15-
public static Result<T, E> Err(E error) => new(false, default, error);
16-
17-
public T UnwrapOrThrow()
18-
{
19-
if (IsSuccess)
20-
{
21-
return Value!;
22-
}
23-
24-
if (Error is not null)
25-
{
26-
throw Error;
27-
}
28-
29-
throw new InvalidOperationException("Result failed without an error object.");
30-
}
31-
32-
public T UnwrapOr(T defaultValue) => IsSuccess ? Value! : defaultValue;
33-
34-
public T UnwrapOrElse(Func<E, T> f) => IsSuccess ? Value! : f(Error!);
35-
36-
public TResult Match<TResult>(Func<T, TResult> onOk, Func<E, TResult> onErr) =>
37-
IsSuccess ? onOk(Value!) : onErr(Error!);
38-
}
39-
406
#pragma warning disable STDB_UNSTABLE
417
public abstract class ProcedureContextBase(
428
Identity sender,
@@ -122,9 +88,13 @@ Func<ProcedureTxContextBase, Result<TResult, TError>> body
12288
try
12389
{
12490
var result = RunWithRetry(body);
125-
return result.IsSuccess
126-
? TxOutcome<TResult>.Success(result.Value!)
127-
: TxOutcome<TResult>.Failure(result.Error!);
91+
92+
return result switch
93+
{
94+
Result<TResult, TError>.OkR(var value) => TxOutcome<TResult>.Success(value),
95+
Result<TResult, TError>.ErrR(var error) => TxOutcome<TResult>.Failure(error),
96+
_ => throw new InvalidOperationException("Unknown Result variant."),
97+
};
12898
}
12999
catch (Exception ex)
130100
{
@@ -181,15 +151,15 @@ Func<ProcedureTxContextBase, Result<TResult, TError>> body
181151
where TError : Exception
182152
{
183153
var result = RunOnce(body);
184-
if (!result.IsSuccess)
154+
if (result is Result<TResult, TError>.ErrR)
185155
{
186156
return result;
187157
}
188158

189159
bool Retry()
190160
{
191161
result = RunOnce(body);
192-
return result.IsSuccess;
162+
return result is Result<TResult, TError>.OkR;
193163
}
194164

195165
if (!CommitMutTxWithRetry(Retry))
@@ -220,7 +190,7 @@ Func<ProcedureTxContextBase, Result<TResult, TError>> body
220190
throw;
221191
}
222192

223-
if (result.IsSuccess)
193+
if (result is Result<TResult, TError>.OkR)
224194
{
225195
guard.Disarm();
226196
return result;

crates/bindings-typescript/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ export * from './lib/timestamp';
99
export * from './lib/util';
1010
export * from './lib/identity';
1111
export * from './lib/option';
12+
export * from './lib/result';
1213
export * from './sdk';

crates/bindings-typescript/src/lib/algebraic_type.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,36 @@ export const SumType = {
439439
} else {
440440
writer.writeByte(1);
441441
}
442+
} else if (
443+
ty.variants.length == 2 &&
444+
ty.variants[0].name === 'ok' &&
445+
ty.variants[1].name === 'err'
446+
) {
447+
let variantName: 'ok' | 'err';
448+
let innerValue: any;
449+
let index: number;
450+
if ('ok' in value) {
451+
variantName = 'ok';
452+
innerValue = value.ok;
453+
index = 0;
454+
} else {
455+
variantName = 'err';
456+
innerValue = value.err;
457+
index = 1;
458+
}
459+
460+
if (index < 0) {
461+
throw `Result serialization error: variant '${variantName}' not found in ${JSON.stringify(ty)}`;
462+
}
463+
464+
writer.writeU8(index);
465+
466+
AlgebraicType.serializeValue(
467+
writer,
468+
ty.variants[index].algebraicType,
469+
innerValue,
470+
typespace
471+
);
442472
} else {
443473
const variant = value['tag'];
444474
const index = ty.variants.findIndex(v => v.name === variant);
@@ -479,6 +509,28 @@ export const SumType = {
479509
} else {
480510
throw `Can't deserialize an option type, couldn't find ${tag} tag`;
481511
}
512+
} else if (
513+
ty.variants.length == 2 &&
514+
ty.variants[0].name === 'ok' &&
515+
ty.variants[1].name === 'err'
516+
) {
517+
if (tag === 0) {
518+
const value = AlgebraicType.deserializeValue(
519+
reader,
520+
ty.variants[0].algebraicType,
521+
typespace
522+
);
523+
return { ok: value };
524+
} else if (tag === 1) {
525+
const value = AlgebraicType.deserializeValue(
526+
reader,
527+
ty.variants[1].algebraicType,
528+
typespace
529+
);
530+
return { err: value };
531+
} else {
532+
throw `Can't deserialize a result type, couldn't find ${tag} tag`;
533+
}
482534
} else {
483535
const variant = ty.variants[tag];
484536
const value = AlgebraicType.deserializeValue(

0 commit comments

Comments
 (0)