Skip to content

Fix fields named 'read' #2525

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
68 changes: 50 additions & 18 deletions crates/bindings-csharp/BSATN.Codegen/Type.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
namespace SpacetimeDB.Codegen;

// Generate code to implement serialization to the BSATN format (https://spacetimedb.com/docs/bsatn).
// C# doesn't support static methods in interfaces, so instead we declare a zero-sized `struct` type that implements
// the serialization interface (IReadWrite) for us.
//
// See BSATN.Runtime for the support code referenced by code generation,
// and see Codegen.Tests/fixtures/*/snapshots for examples of generated code.
// Also, if you set <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> in a csproj,
// you can find the generated code in obj/Debug/*/generated/SpacetimeDB.BSATN.Codegen.

using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand All @@ -13,6 +22,17 @@ public record MemberDeclaration(
bool IsNullableReferenceType
)
{
internal static string BSATN_FIELD_SUFFIX = "RW";

/// <summary>
/// The name of the static field containing an IReadWrite in the IReadWrite struct associated with this type.
/// We make sure this is different from the field name so that collisions cannot occur.
/// </summary>
public string BsatnFieldName
{
get => $"{Name}{BSATN_FIELD_SUFFIX}";
}

public MemberDeclaration(ISymbol member, ITypeSymbol type, DiagReporter diag)
: this(member.Name, SymbolToName(type), "", Utils.IsNullableReferenceType(type))
{
Expand Down Expand Up @@ -46,14 +66,20 @@ IEnumerable<MemberDeclaration> members
var visStr = SyntaxFacts.GetText(visibility);
return string.Join(
"\n ",
members.Select(m => $"{visStr} static readonly {m.TypeInfo} {m.Name} = new();")
members.Select(m =>
$"{visStr} static readonly {m.TypeInfo} {m.BsatnFieldName} = new();"
)
);
}

public static string GenerateDefs(IEnumerable<MemberDeclaration> members) =>
string.Join(
",\n ",
members.Select(m => $"new(nameof({m.Name}), {m.Name}.GetAlgebraicType(registrar))")
// we can't use nameof(m.BsatnFieldName) because the bsatn field name differs from the logical name
// assigned in the type.
members.Select(m =>
$"new(\"{m.Name}\", {m.BsatnFieldName}.GetAlgebraicType(registrar))"
)
);
}

Expand Down Expand Up @@ -166,27 +192,33 @@ public Scope.Extensions ToExtensions()
var extensions = new Scope.Extensions(Scope, FullName);

var bsatnDecls = Members.Cast<MemberDeclaration>();
var fieldNames = bsatnDecls.Select(m => m.Name);

extensions.BaseTypes.Add($"System.IEquatable<{ShortName}>");

if (Kind is TypeKind.Sum)
{
var enumTag = new MemberDeclaration(
"__enumTag",
"@enum",
"SpacetimeDB.BSATN.Enum<@enum>",
false
);

extensions.Contents.Append(
$$"""
private {{ShortName}}() { }

internal enum @enum: byte
{
{{string.Join(",\n ", fieldNames)}}
{{string.Join(",\n ", bsatnDecls.Select(decl => decl.Name))}}
}

"""
);
extensions.Contents.Append(
string.Join(
"\n",
Members.Select(m =>
bsatnDecls.Select(m =>
// C# puts field names in the same namespace as records themselves, and will complain about clashes if they match.
// To avoid this, we append an underscore to the field name.
// In most cases the field name shouldn't matter anyway as you'll idiomatically use pattern matching to extract the value.
Expand All @@ -203,11 +235,11 @@ public override string ToString() =>
);

read = $$"""
__enumTag.Read(reader) switch {
{{enumTag.BsatnFieldName}}.Read(reader) switch {
{{string.Join(
"\n ",
fieldNames.Select(name =>
$"@enum.{name} => new {name}({name}.Read(reader)),"
bsatnDecls.Select(m =>
$"@enum.{m.Name} => new {m.Name}({m.BsatnFieldName}.Read(reader)),"
)
)}}
_ => throw new System.InvalidOperationException("Invalid tag value, this state should be unreachable.")
Expand All @@ -218,10 +250,10 @@ public override string ToString() =>
switch (value) {
{{string.Join(
"\n",
fieldNames.Select(name => $"""
case {name}(var inner):
__enumTag.Write(writer, @enum.{name});
{name}.Write(writer, inner);
bsatnDecls.Select(m => $"""
case {m.Name}(var inner):
{enumTag.BsatnFieldName}.Write(writer, @enum.{m.Name});
{m.BsatnFieldName}.Write(writer, inner);
break;
"""))}}
}
Expand Down Expand Up @@ -255,9 +287,9 @@ public override string ToString() =>
}
""";

bsatnDecls = bsatnDecls.Prepend(
new("__enumTag", "@enum", "SpacetimeDB.BSATN.Enum<@enum>", false)
);
// It's important that this happen here; only the stuff later in this method
// needs to see the enum tag as one of the bsatn declarations.
bsatnDecls = bsatnDecls.Prepend(enumTag);
}
else
{
Expand All @@ -268,14 +300,14 @@ public override string ToString() =>
public void ReadFields(System.IO.BinaryReader reader) {
{{string.Join(
"\n",
fieldNames.Select(name => $" {name} = BSATN.{name}.Read(reader);")
bsatnDecls.Select(m => $" {m.Name} = BSATN.{m.BsatnFieldName}.Read(reader);")
)}}
}

public void WriteFields(System.IO.BinaryWriter writer) {
{{string.Join(
"\n",
fieldNames.Select(name => $" BSATN.{name}.Write(writer, {name});")
bsatnDecls.Select(m => $" BSATN.{m.BsatnFieldName}.Write(writer, {m.Name});")
)}}
}

Expand All @@ -291,7 +323,7 @@ public void WriteFields(System.IO.BinaryWriter writer) {
public override string ToString() =>
$"{{ShortName}} {{start}} {{string.Join(
", ",
fieldNames.Select(name => $$"""{{name}} = {SpacetimeDB.BSATN.StringUtil.GenericToString({{name}})}""")
bsatnDecls.Select(m => $$"""{{m.Name}} = {SpacetimeDB.BSATN.StringUtil.GenericToString({{m.Name}})}""")
)}} {{end}}";
"""
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ partial struct CustomClass : System.IEquatable<CustomClass>, SpacetimeDB.BSATN.I
{
public void ReadFields(System.IO.BinaryReader reader)
{
IntField = BSATN.IntField.Read(reader);
StringField = BSATN.StringField.Read(reader);
IntField = BSATN.IntFieldRW.Read(reader);
StringField = BSATN.StringFieldRW.Read(reader);
}

public void WriteFields(System.IO.BinaryWriter writer)
{
BSATN.IntField.Write(writer, IntField);
BSATN.StringField.Write(writer, StringField);
BSATN.IntFieldRW.Write(writer, IntField);
BSATN.StringFieldRW.Write(writer, StringField);
}

public override string ToString() =>
$"CustomClass {{ IntField = {SpacetimeDB.BSATN.StringUtil.GenericToString(IntField)}, StringField = {SpacetimeDB.BSATN.StringUtil.GenericToString(StringField)} }}";

public readonly partial struct BSATN : SpacetimeDB.BSATN.IReadWrite<CustomClass>
{
internal static readonly SpacetimeDB.BSATN.I32 IntField = new();
internal static readonly SpacetimeDB.BSATN.String StringField = new();
internal static readonly SpacetimeDB.BSATN.I32 IntFieldRW = new();
internal static readonly SpacetimeDB.BSATN.String StringFieldRW = new();

public CustomClass Read(System.IO.BinaryReader reader) =>
SpacetimeDB.BSATN.IStructuralReadWrite.Read<CustomClass>(reader);
Expand All @@ -38,8 +38,8 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar
registrar.RegisterType<CustomClass>(_ => new SpacetimeDB.BSATN.AlgebraicType.Product(
new SpacetimeDB.BSATN.AggregateElement[]
{
new(nameof(IntField), IntField.GetAlgebraicType(registrar)),
new(nameof(StringField), StringField.GetAlgebraicType(registrar))
new("IntField", IntFieldRW.GetAlgebraicType(registrar)),
new("StringField", StringFieldRW.GetAlgebraicType(registrar))
}
));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@ partial struct CustomStruct
{
public void ReadFields(System.IO.BinaryReader reader)
{
IntField = BSATN.IntField.Read(reader);
StringField = BSATN.StringField.Read(reader);
IntField = BSATN.IntFieldRW.Read(reader);
StringField = BSATN.StringFieldRW.Read(reader);
}

public void WriteFields(System.IO.BinaryWriter writer)
{
BSATN.IntField.Write(writer, IntField);
BSATN.StringField.Write(writer, StringField);
BSATN.IntFieldRW.Write(writer, IntField);
BSATN.StringFieldRW.Write(writer, StringField);
}

public override string ToString() =>
$"CustomStruct {{ IntField = {SpacetimeDB.BSATN.StringUtil.GenericToString(IntField)}, StringField = {SpacetimeDB.BSATN.StringUtil.GenericToString(StringField)} }}";

public readonly partial struct BSATN : SpacetimeDB.BSATN.IReadWrite<CustomStruct>
{
internal static readonly SpacetimeDB.BSATN.I32 IntField = new();
internal static readonly SpacetimeDB.BSATN.String StringField = new();
internal static readonly SpacetimeDB.BSATN.I32 IntFieldRW = new();
internal static readonly SpacetimeDB.BSATN.String StringFieldRW = new();

public CustomStruct Read(System.IO.BinaryReader reader) =>
SpacetimeDB.BSATN.IStructuralReadWrite.Read<CustomStruct>(reader);
Expand All @@ -40,8 +40,8 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar
registrar.RegisterType<CustomStruct>(_ => new SpacetimeDB.BSATN.AlgebraicType.Product(
new SpacetimeDB.BSATN.AggregateElement[]
{
new(nameof(IntField), IntField.GetAlgebraicType(registrar)),
new(nameof(StringField), StringField.GetAlgebraicType(registrar))
new("IntField", IntFieldRW.GetAlgebraicType(registrar)),
new("StringField", StringFieldRW.GetAlgebraicType(registrar))
}
));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ public override string ToString() =>

public readonly partial struct BSATN : SpacetimeDB.BSATN.IReadWrite<CustomTaggedEnum>
{
internal static readonly SpacetimeDB.BSATN.Enum<@enum> __enumTag = new();
internal static readonly SpacetimeDB.BSATN.I32 IntVariant = new();
internal static readonly SpacetimeDB.BSATN.String StringVariant = new();
internal static readonly SpacetimeDB.BSATN.Enum<@enum> __enumTagRW = new();
internal static readonly SpacetimeDB.BSATN.I32 IntVariantRW = new();
internal static readonly SpacetimeDB.BSATN.String StringVariantRW = new();

public CustomTaggedEnum Read(System.IO.BinaryReader reader) =>
__enumTag.Read(reader) switch
__enumTagRW.Read(reader) switch
{
@enum.IntVariant => new IntVariant(IntVariant.Read(reader)),
@enum.StringVariant => new StringVariant(StringVariant.Read(reader)),
@enum.IntVariant => new IntVariant(IntVariantRW.Read(reader)),
@enum.StringVariant => new StringVariant(StringVariantRW.Read(reader)),
_
=> throw new System.InvalidOperationException(
"Invalid tag value, this state should be unreachable."
Expand All @@ -46,12 +46,12 @@ public void Write(System.IO.BinaryWriter writer, CustomTaggedEnum value)
switch (value)
{
case IntVariant(var inner):
__enumTag.Write(writer, @enum.IntVariant);
IntVariant.Write(writer, inner);
__enumTagRW.Write(writer, @enum.IntVariant);
IntVariantRW.Write(writer, inner);
break;
case StringVariant(var inner):
__enumTag.Write(writer, @enum.StringVariant);
StringVariant.Write(writer, inner);
__enumTagRW.Write(writer, @enum.StringVariant);
StringVariantRW.Write(writer, inner);
break;
}
}
Expand All @@ -62,8 +62,8 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar
registrar.RegisterType<CustomTaggedEnum>(_ => new SpacetimeDB.BSATN.AlgebraicType.Sum(
new SpacetimeDB.BSATN.AggregateElement[]
{
new(nameof(IntVariant), IntVariant.GetAlgebraicType(registrar)),
new(nameof(StringVariant), StringVariant.GetAlgebraicType(registrar))
new("IntVariant", IntVariantRW.GetAlgebraicType(registrar)),
new("StringVariant", StringVariantRW.GetAlgebraicType(registrar))
}
));

Expand Down
Loading
Loading