Skip to content

Commit 4324cd7

Browse files
committed
Implement SpacetimeType for Result<T, E>
1 parent b5f3ce8 commit 4324cd7

Some content is hidden

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

48 files changed

+2318
-15
lines changed

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

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ public static TypeUse Parse(ISymbol member, ITypeSymbol typeSymbol, DiagReporter
8484
),
8585
INamedTypeSymbol named => named.OriginalDefinition.ToString() switch
8686
{
87+
"SpacetimeDB.Result<T, E>" or "SpacetimeDB.Result<T,E>" => new ResultUse(
88+
type,
89+
typeInfo,
90+
Parse(member, named.TypeArguments[0], diag),
91+
Parse(member, named.TypeArguments[1], diag)
92+
),
8793
"System.Collections.Generic.List<T>" => new ListUse(
8894
type,
8995
typeInfo,
@@ -101,6 +107,14 @@ public static TypeUse Parse(ISymbol member, ITypeSymbol typeSymbol, DiagReporter
101107
};
102108
}
103109

110+
/// <summary>
111+
/// Get the name of the BSATN struct for this type.
112+
/// </summary>
113+
public virtual string to_bsatn_string()
114+
{
115+
return this.BSATNName;
116+
}
117+
104118
/// <summary>
105119
/// Get a statement that declares outVar and assigns (inVar1 logically-equals inVar2) to it.
106120
/// logically-equals:
@@ -133,6 +147,40 @@ public abstract string EqualsStatement(
133147
public abstract string GetHashCodeStatement(string inVar, string outVar, int level = 0);
134148
}
135149

150+
/// <summary>
151+
/// A use of a Result<T, E> type.
152+
/// </summary>
153+
public sealed record ResultUse : TypeUse
154+
{
155+
public TypeUse Ok { get; }
156+
public TypeUse Err { get; }
157+
158+
public string TypeName { get; }
159+
160+
public ResultUse(string typeName, string typeInfo, TypeUse ok, TypeUse err)
161+
: base(typeName, typeInfo)
162+
{
163+
Ok = ok;
164+
Err = err;
165+
TypeName = typeName;
166+
}
167+
168+
public override string to_bsatn_string()
169+
{
170+
return $"{TypeName}.BSATN<{Ok.BSATNName}, {Err.BSATNName}>";
171+
}
172+
173+
public override string EqualsStatement(
174+
string inVar1,
175+
string inVar2,
176+
string outVar,
177+
int level = 0
178+
) => $"var {outVar} = {inVar1} == {inVar2};";
179+
180+
public override string GetHashCodeStatement(string inVar, string outVar, int level = 0) =>
181+
$"var {outVar} = {inVar}.GetHashCode();";
182+
}
183+
136184
/// <summary>
137185
/// A use of an enum type.
138186
/// (This is a C# enum, not one of our tagged enums.)
@@ -348,7 +396,7 @@ IEnumerable<MemberDeclaration> members
348396
return string.Join(
349397
"\n ",
350398
members.Select(m =>
351-
$"{visStr} static readonly {m.Type.BSATNName} {m.Name}{TypeUse.BsatnFieldSuffix} = new();"
399+
$"{visStr} static readonly {m.Type.to_bsatn_string()} {m.Name}{TypeUse.BsatnFieldSuffix} = new();"
352400
)
353401
);
354402
}

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

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

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,3 +605,77 @@ 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 Ok(value);
612+
613+
public static implicit operator Result<T, E>(E error) => new Err(error);
614+
615+
public TResult Match<TResult>(Func<T, TResult> onOk, Func<E, TResult> onErr) =>
616+
this switch
617+
{
618+
Ok(var v) => onOk(v),
619+
Err(var e) => onErr(e),
620+
_ => throw new InvalidOperationException("Unknown Result variant."),
621+
};
622+
623+
// ----- auto-generated -----
624+
625+
private Result() { }
626+
627+
internal enum @enum : byte
628+
{
629+
Ok,
630+
Err,
631+
}
632+
633+
public sealed record Ok(T Value) : Result<T, E>;
634+
635+
public sealed record Err(E Error) : Result<T, E>;
636+
637+
private enum Variant : byte
638+
{
639+
Ok = 0,
640+
Err = 1,
641+
}
642+
643+
public readonly struct BSATN<OkRW, ErrRW> : IReadWrite<Result<T, E>>
644+
where OkRW : struct, IReadWrite<T>
645+
where ErrRW : struct, IReadWrite<E>
646+
{
647+
private static readonly SpacetimeDB.BSATN.Enum<@enum> __enumTag = new();
648+
private static readonly OkRW okRW = new();
649+
private static readonly ErrRW errRW = new();
650+
651+
public Result<T, E> Read(BinaryReader reader) =>
652+
__enumTag.Read(reader) switch
653+
{
654+
@enum.Ok => new Ok(okRW.Read(reader)),
655+
@enum.Err => new Err(errRW.Read(reader)),
656+
_ => throw new InvalidOperationException(),
657+
};
658+
659+
public void Write(BinaryWriter writer, Result<T, E> value)
660+
{
661+
switch (value)
662+
{
663+
case Ok(var v):
664+
__enumTag.Write(writer, @enum.Ok);
665+
okRW.Write(writer, v);
666+
break;
667+
668+
case Err(var e):
669+
__enumTag.Write(writer, @enum.Err);
670+
errRW.Write(writer, e);
671+
break;
672+
}
673+
}
674+
675+
public AlgebraicType GetAlgebraicType(ITypeRegistrar registrar) =>
676+
AlgebraicType.MakeResult(
677+
okRW.GetAlgebraicType(registrar),
678+
errRW.GetAlgebraicType(registrar)
679+
);
680+
}
681+
}

crates/bindings-csharp/Codegen/Module.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,7 +1168,7 @@ public Scope.Extensions GenerateSchedule()
11681168
using var writer = new BinaryWriter(stream);
11691169
{{string.Join(
11701170
"\n",
1171-
Args.Select(a => $"new {a.Type.BSATNName}().Write(writer, {a.Name});")
1171+
Args.Select(a => $"new {a.Type.to_bsatn_string()}().Write(writer, {a.Name});")
11721172
)}}
11731173
SpacetimeDB.Internal.IReducer.VolatileNonatomicScheduleImmediate(nameof({{Name}}), stream);
11741174
}
@@ -1239,7 +1239,7 @@ public string GenerateClass()
12391239
var result = {{FullName}}((SpacetimeDB.ProcedureContext)ctx{{invocationArgs}});
12401240
using var output = new MemoryStream();
12411241
using var writer = new BinaryWriter(output);
1242-
new {{ReturnType.BSATNName}}().Write(writer, result);
1242+
new {{ReturnType.to_bsatn_string()}}().Write(writer, result);
12431243
return output.ToArray();
12441244
""";
12451245

@@ -1283,7 +1283,7 @@ public Scope.Extensions GenerateSchedule()
12831283
using var writer = new BinaryWriter(stream);
12841284
{{string.Join(
12851285
"\n",
1286-
Args.Select(a => $"new {a.Type.BSATNName}().Write(writer, {a.Name});")
1286+
Args.Select(a => $"new {a.Type.to_bsatn_string()}().Write(writer, {a.Name});")
12871287
)}}
12881288
SpacetimeDB.Internal.IProcedure.VolatileNonatomicScheduleImmediate(nameof({{Name}}), stream);
12891289
}

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(
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { AlgebraicType } from './algebraic_type';
2+
3+
export type ResultAlgebraicType<
4+
T extends AlgebraicType = AlgebraicType,
5+
E extends AlgebraicType = AlgebraicType,
6+
> = {
7+
tag: 'Sum';
8+
value: {
9+
variants: [
10+
{ name: 'ok'; algebraicType: T },
11+
{ name: 'err'; algebraicType: E },
12+
];
13+
};
14+
};
15+
16+
export const Result: {
17+
getAlgebraicType<
18+
T extends AlgebraicType = AlgebraicType,
19+
E extends AlgebraicType = AlgebraicType,
20+
>(
21+
okType: T,
22+
errType: E
23+
): ResultAlgebraicType<T, E>;
24+
} = {
25+
getAlgebraicType<
26+
T extends AlgebraicType = AlgebraicType,
27+
E extends AlgebraicType = AlgebraicType,
28+
>(okType: T, errType: E): ResultAlgebraicType<T, E> {
29+
return AlgebraicType.Sum({
30+
variants: [
31+
{ name: 'ok', algebraicType: okType },
32+
{ name: 'err', algebraicType: errType },
33+
],
34+
});
35+
},
36+
};

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
type InferSpacetimeTypeOfTypeBuilder,
1717
type RowObj,
1818
type VariantsObj,
19+
ResultBuilder,
1920
} from './type_builders';
2021
import type { UntypedTableDef } from './table';
2122
import {
@@ -203,6 +204,11 @@ export function registerTypesRecursively<
203204
return new OptionBuilder(
204205
registerTypesRecursively(typeBuilder.value)
205206
) as any;
207+
} else if (typeBuilder instanceof ResultBuilder) {
208+
return new ResultBuilder(
209+
registerTypesRecursively(typeBuilder.ok),
210+
registerTypesRecursively(typeBuilder.err)
211+
) as any;
206212
} else if (typeBuilder instanceof ArrayBuilder) {
207213
return new ArrayBuilder(
208214
registerTypesRecursively(typeBuilder.element)

0 commit comments

Comments
 (0)