1
1
namespace SpacetimeDB . Codegen ;
2
2
3
+ // Generate code to implement serialization to the BSATN format (https://spacetimedb.com/docs/bsatn).
4
+ // C# doesn't support static methods in interfaces, so instead we declare a zero-sized `struct` type that implements
5
+ // the serialization interface (IReadWrite) for us.
6
+ //
7
+ // See BSATN.Runtime for the support code referenced by code generation,
8
+ // and see Codegen.Tests/fixtures/*/snapshots for examples of generated code.
9
+ // Also, if you set <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> in a csproj,
10
+ // you can find the generated code in obj/Debug/*/generated/SpacetimeDB.BSATN.Codegen.
11
+
3
12
using System . Collections . Immutable ;
4
13
using Microsoft . CodeAnalysis ;
5
14
using Microsoft . CodeAnalysis . CSharp ;
@@ -13,6 +22,17 @@ public record MemberDeclaration(
13
22
bool IsNullableReferenceType
14
23
)
15
24
{
25
+ internal static string BSATN_FIELD_SUFFIX = "RW" ;
26
+
27
+ /// <summary>
28
+ /// The name of the static field containing an IReadWrite in the IReadWrite struct associated with this type.
29
+ /// We make sure this is different from the field name so that collisions cannot occur.
30
+ /// </summary>
31
+ public string BsatnFieldName
32
+ {
33
+ get => $ "{ Name } { BSATN_FIELD_SUFFIX } ";
34
+ }
35
+
16
36
public MemberDeclaration ( ISymbol member , ITypeSymbol type , DiagReporter diag )
17
37
: this ( member . Name , SymbolToName ( type ) , "" , Utils . IsNullableReferenceType ( type ) )
18
38
{
@@ -46,14 +66,20 @@ IEnumerable<MemberDeclaration> members
46
66
var visStr = SyntaxFacts . GetText ( visibility ) ;
47
67
return string . Join (
48
68
"\n " ,
49
- members . Select ( m => $ "{ visStr } static readonly { m . TypeInfo } { m . Name } = new();")
69
+ members . Select ( m =>
70
+ $ "{ visStr } static readonly { m . TypeInfo } { m . BsatnFieldName } = new();"
71
+ )
50
72
) ;
51
73
}
52
74
53
75
public static string GenerateDefs ( IEnumerable < MemberDeclaration > members ) =>
54
76
string . Join (
55
77
",\n " ,
56
- members . Select ( m => $ "new(nameof({ m . Name } ), { m . Name } .GetAlgebraicType(registrar))")
78
+ // we can't use nameof(m.BsatnFieldName) because the bsatn field name differs from the logical name
79
+ // assigned in the type.
80
+ members . Select ( m =>
81
+ $ "new(\" { m . Name } \" , { m . BsatnFieldName } .GetAlgebraicType(registrar))"
82
+ )
57
83
) ;
58
84
}
59
85
@@ -166,27 +192,33 @@ public Scope.Extensions ToExtensions()
166
192
var extensions = new Scope . Extensions ( Scope , FullName ) ;
167
193
168
194
var bsatnDecls = Members . Cast < MemberDeclaration > ( ) ;
169
- var fieldNames = bsatnDecls . Select ( m => m . Name ) ;
170
195
171
196
extensions . BaseTypes . Add ( $ "System.IEquatable<{ ShortName } >") ;
172
197
173
198
if ( Kind is TypeKind . Sum )
174
199
{
200
+ var enumTag = new MemberDeclaration (
201
+ "__enumTag" ,
202
+ "@enum" ,
203
+ "SpacetimeDB.BSATN.Enum<@enum>" ,
204
+ false
205
+ ) ;
206
+
175
207
extensions . Contents . Append (
176
208
$$ """
177
209
private {{ ShortName }} () { }
178
210
179
211
internal enum @enum: byte
180
212
{
181
- {{ string . Join ( ",\n " , fieldNames ) }}
213
+ {{ string . Join ( ",\n " , bsatnDecls . Select ( decl => decl . Name ) ) }}
182
214
}
183
215
184
216
"""
185
217
) ;
186
218
extensions . Contents . Append (
187
219
string . Join (
188
220
"\n " ,
189
- Members . Select ( m =>
221
+ bsatnDecls . Select ( m =>
190
222
// C# puts field names in the same namespace as records themselves, and will complain about clashes if they match.
191
223
// To avoid this, we append an underscore to the field name.
192
224
// In most cases the field name shouldn't matter anyway as you'll idiomatically use pattern matching to extract the value.
@@ -203,11 +235,11 @@ public override string ToString() =>
203
235
) ;
204
236
205
237
read = $$ """
206
- __enumTag .Read(reader) switch {
238
+ {{ enumTag . BsatnFieldName }} .Read(reader) switch {
207
239
{{ string . Join (
208
240
"\n " ,
209
- fieldNames . Select ( name =>
210
- $ "@enum.{ name } => new { name } ({ name } .Read(reader)),"
241
+ bsatnDecls . Select ( m =>
242
+ $ "@enum.{ m . Name } => new { m . Name } ({ m . BsatnFieldName } .Read(reader)),"
211
243
)
212
244
) }}
213
245
_ => throw new System.InvalidOperationException("Invalid tag value, this state should be unreachable.")
@@ -218,10 +250,10 @@ public override string ToString() =>
218
250
switch (value) {
219
251
{{ string . Join (
220
252
"\n " ,
221
- fieldNames . Select ( name => $ """
222
- case { name } (var inner):
223
- __enumTag. Write(writer, @enum.{ name } );
224
- { name } .Write(writer, inner);
253
+ bsatnDecls . Select ( m => $ """
254
+ case { m . Name } (var inner):
255
+ { enumTag . BsatnFieldName } . Write(writer, @enum.{ m . Name } );
256
+ { m . BsatnFieldName } .Write(writer, inner);
225
257
break;
226
258
""" ) ) }}
227
259
}
@@ -255,9 +287,9 @@ public override string ToString() =>
255
287
}
256
288
""" ;
257
289
258
- bsatnDecls = bsatnDecls . Prepend (
259
- new ( "__enumTag" , "@ enum" , "SpacetimeDB.BSATN.Enum<@enum>" , false )
260
- ) ;
290
+ // It's important that this happen here; only the stuff later in this method
291
+ // needs to see the enum tag as one of the bsatn declarations.
292
+ bsatnDecls = bsatnDecls . Prepend ( enumTag ) ;
261
293
}
262
294
else
263
295
{
@@ -268,14 +300,14 @@ public override string ToString() =>
268
300
public void ReadFields(System.IO.BinaryReader reader) {
269
301
{{ string . Join (
270
302
"\n " ,
271
- fieldNames . Select ( name => $ " { name } = BSATN.{ name } .Read(reader);")
303
+ bsatnDecls . Select ( m => $ " { m . Name } = BSATN.{ m . BsatnFieldName } .Read(reader);")
272
304
) }}
273
305
}
274
306
275
307
public void WriteFields(System.IO.BinaryWriter writer) {
276
308
{{ string . Join (
277
309
"\n " ,
278
- fieldNames . Select ( name => $ " BSATN.{ name } .Write(writer, { name } );")
310
+ bsatnDecls . Select ( m => $ " BSATN.{ m . BsatnFieldName } .Write(writer, { m . Name } );")
279
311
) }}
280
312
}
281
313
@@ -291,7 +323,7 @@ public void WriteFields(System.IO.BinaryWriter writer) {
291
323
public override string ToString() =>
292
324
$"{{ ShortName }} {{ start }} {{ string . Join (
293
325
", " ,
294
- fieldNames . Select ( name => $$ """ {{ name }} = {SpacetimeDB.BSATN.StringUtil.GenericToString({{ name }} )}""" )
326
+ bsatnDecls . Select ( m => $$ """ {{ m . Name }} = {SpacetimeDB.BSATN.StringUtil.GenericToString({{ m . Name }} )}""" )
295
327
) }} {{ end }} ";
296
328
"""
297
329
) ;
0 commit comments