Skip to content

Commit 0bd05ba

Browse files
Fix C# codegen keyword escaping (#4529)
1 parent e729c15 commit 0bd05ba

4 files changed

Lines changed: 73 additions & 30 deletions

File tree

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -424,13 +424,6 @@ public MemberDeclaration(IFieldSymbol field, DiagReporter diag)
424424

425425
public string Identifier => EscapeIdentifier(Name);
426426

427-
private static string EscapeIdentifier(string name)
428-
{
429-
var kind = SyntaxFacts.GetKeywordKind(name);
430-
var contextualKind = SyntaxFacts.GetContextualKeywordKind(name);
431-
return kind != SyntaxKind.None || contextualKind != SyntaxKind.None ? $"@{name}" : name;
432-
}
433-
434427
public static string GenerateBsatnFields(
435428
Accessibility visibility,
436429
IEnumerable<MemberDeclaration> members

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,26 @@ public readonly record struct EquatableArray<T>(ImmutableArray<T> Array) : IEnum
3333
.AddMemberOptions(SymbolDisplayMemberOptions.IncludeContainingType)
3434
.AddMiscellaneousOptions(
3535
SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier
36+
| SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers
3637
);
3738

3839
public static string SymbolToName(ISymbol symbol)
3940
{
4041
return symbol.ToDisplayString(SymbolFormat);
4142
}
4243

44+
public static string EscapeIdentifier(string name)
45+
{
46+
if (name.Length > 0 && name[0] == '@')
47+
{
48+
return name;
49+
}
50+
51+
var kind = SyntaxFacts.GetKeywordKind(name);
52+
var contextualKind = SyntaxFacts.GetContextualKeywordKind(name);
53+
return kind != SyntaxKind.None || contextualKind != SyntaxKind.None ? $"@{name}" : name;
54+
}
55+
4356
public static void RegisterSourceOutputs(
4457
this IncrementalValuesProvider<Scope.Extensions> methods,
4558
IncrementalGeneratorInitializationContext context

crates/bindings-csharp/Codegen.Tests/Tests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,21 @@ public partial struct KeywordTable
274274
public int @params;
275275
}
276276
277+
[SpacetimeDB.Table(Accessor = "class")]
278+
public partial struct AccessorKeywordTable
279+
{
280+
[SpacetimeDB.PrimaryKey]
281+
[SpacetimeDB.Index.BTree(Accessor = "class")]
282+
public int Id;
283+
}
284+
285+
[SpacetimeDB.Table]
286+
public partial struct @class
287+
{
288+
[SpacetimeDB.PrimaryKey]
289+
public int Id;
290+
}
291+
277292
public static partial class KeywordApis
278293
{
279294
[SpacetimeDB.Reducer]
@@ -283,11 +298,21 @@ public static void KeywordReducer(ReducerContext ctx, int @params, string @class
283298
_ = @class;
284299
}
285300
301+
[SpacetimeDB.Reducer]
302+
public static void @class(ReducerContext ctx)
303+
{
304+
}
305+
286306
[SpacetimeDB.Procedure]
287307
public static int KeywordProcedure(ProcedureContext ctx, int @params, int @class)
288308
{
289309
return @params + @class;
290310
}
311+
312+
[SpacetimeDB.Procedure]
313+
public static void @params(ProcedureContext ctx)
314+
{
315+
}
291316
}
292317
""";
293318

crates/bindings-csharp/Codegen/Module.cs

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ record TableAccessor
275275
public readonly bool IsEvent;
276276
public readonly Scheduled? Scheduled;
277277

278+
public string Identifier => EscapeIdentifier(Name);
279+
278280
public TableAccessor(TableDeclaration table, AttributeData data, DiagReporter diag)
279281
{
280282
var attr = data.ParseAs<TableAttribute>();
@@ -334,6 +336,8 @@ record TableIndex
334336
public readonly string? CanonicalName;
335337
public readonly TableIndexType Type;
336338

339+
public string AccessorIdentifier => EscapeIdentifier(AccessorName);
340+
337341
// See: bindings_sys::index_id_from_name for documentation of this format.
338342
// Guaranteed not to contain quotes, so does not need to be escaped when embedded in a string.
339343
private readonly string StandardNameSuffix;
@@ -569,7 +573,7 @@ public IEnumerable<string> GenerateTableAccessorFilters(TableAccessor tableAcces
569573
? $"public {globalName} Update({globalName} row) => DoUpdate(row);"
570574
: "";
571575
yield return $$"""
572-
{{vis}} sealed class {{f.Identifier}}UniqueIndex : {{uniqueIndexBase}}<{{tableAccessor.Name}}, {{globalName}}, {{f.Type.Name}}, {{f.Type.BSATNName}}> {
576+
{{vis}} sealed class {{f.Identifier}}UniqueIndex : {{uniqueIndexBase}}<{{tableAccessor.Identifier}}, {{globalName}}, {{f.Type.Name}}, {{f.Type.BSATNName}}> {
573577
internal {{f.Identifier}}UniqueIndex() : base("{{standardIndexName}}") {}
574578
// Important: don't move this to the base class.
575579
// C# generics don't play well with nullable types and can't accept both struct-type-based and class-type-based
@@ -584,6 +588,7 @@ public IEnumerable<string> GenerateTableAccessorFilters(TableAccessor tableAcces
584588
foreach (var index in GetIndexes(tableAccessor))
585589
{
586590
var name = index.AccessorName;
591+
var identifierName = index.AccessorIdentifier;
587592

588593
// Skip bad declarations. Empty name means no columns, which we have already reported with a meaningful error.
589594
// Emitting this will result in further compilation errors due to missing property name.
@@ -596,7 +601,7 @@ public IEnumerable<string> GenerateTableAccessorFilters(TableAccessor tableAcces
596601
var standardIndexName = index.StandardIndexName(tableAccessor);
597602

598603
yield return $$"""
599-
{{vis}} sealed class {{name}}Index() : SpacetimeDB.Internal.IndexBase<{{globalName}}>("{{standardIndexName}}") {
604+
{{vis}} sealed class {{identifierName}}Index() : SpacetimeDB.Internal.IndexBase<{{globalName}}>("{{standardIndexName}}") {
600605
""";
601606

602607
for (var n = 0; n < members.Length; n++)
@@ -639,7 +644,7 @@ public ulong Delete({{argsBounds}}) =>
639644
""";
640645
}
641646

642-
yield return $"}}\n {vis} {name}Index {name} => new();\n";
647+
yield return $"}}\n {vis} {identifierName}Index {identifierName} => new();\n";
643648
}
644649
}
645650

@@ -665,7 +670,7 @@ private IEnumerable<string> GenerateReadOnlyAccessorFilters(TableAccessor tableA
665670
yield return $$$"""
666671
public sealed class {{{f.Identifier}}}Index
667672
: {{{uniqueIndexBase}}}<
668-
global::SpacetimeDB.Internal.ViewHandles.{{{tableAccessor.Name}}}ReadOnly,
673+
global::SpacetimeDB.Internal.ViewHandles.{{{tableAccessor.Identifier}}}ReadOnly,
669674
{{{globalName}}},
670675
{{{f.Type.Name}}},
671676
{{{f.Type.BSATNName}}}>
@@ -778,12 +783,13 @@ public IEnumerable<GeneratedTableAccessor> GenerateTableAccessors()
778783
var autoIncFields = Members.Where(m => m.GetAttrs(v).HasFlag(ColumnAttrs.AutoInc));
779784

780785
var globalName = $"global::{FullName}";
781-
var iTable = $"global::SpacetimeDB.Internal.ITableView<{v.Name}, {globalName}>";
786+
var accessorIdentifier = v.Identifier;
787+
var iTable = $"global::SpacetimeDB.Internal.ITableView<{accessorIdentifier}, {globalName}>";
782788
yield return new(
783789
v.Name,
784790
globalName,
785791
$$$"""
786-
{{{SyntaxFacts.GetText(Visibility)}}} readonly struct {{{v.Name}}} : {{{iTable}}} {
792+
{{{SyntaxFacts.GetText(Visibility)}}} readonly struct {{{accessorIdentifier}}} : {{{iTable}}} {
787793
public static {{{globalName}}} ReadGenFields(System.IO.BinaryReader reader, {{{globalName}}} row) {
788794
{{{string.Join(
789795
"\n",
@@ -800,7 +806,7 @@ public IEnumerable<GeneratedTableAccessor> GenerateTableAccessors()
800806
}
801807
802808
public static SpacetimeDB.Internal.RawTableDefV10 MakeTableDesc(SpacetimeDB.BSATN.ITypeRegistrar registrar) => new (
803-
SourceName: nameof({{{v.Name}}}),
809+
SourceName: nameof({{{accessorIdentifier}}}),
804810
ProductTypeRef: (uint) new {{{globalName}}}.BSATN().GetAlgebraicType(registrar).Ref_,
805811
PrimaryKey: [{{{GetPrimaryKey(v)?.ToString() ?? ""}}}],
806812
Indexes: [
@@ -834,7 +840,7 @@ v.Scheduled is { } scheduled
834840
{{{string.Join("\n", GenerateTableAccessorFilters(v))}}}
835841
}
836842
""",
837-
$"{SyntaxFacts.GetText(Visibility)} global::SpacetimeDB.Internal.TableHandles.{v.Name} {v.Name} => new();"
843+
$"{SyntaxFacts.GetText(Visibility)} global::SpacetimeDB.Internal.TableHandles.{accessorIdentifier} {accessorIdentifier} => new();"
838844
);
839845
}
840846
}
@@ -856,24 +862,25 @@ public IEnumerable<GeneratedReadOnlyAccessor> GenerateReadOnlyAccessors()
856862
foreach (var accessor in TableAccessors)
857863
{
858864
var globalName = $"global::{FullName}";
865+
var accessorIdentifier = accessor.Identifier;
859866

860867
var readOnlyIndexDecls = string.Join("\n", GenerateReadOnlyAccessorFilters(accessor));
861868
var visibility = SyntaxFacts.GetText(Visibility);
862869
yield return new(
863870
accessor.Name,
864871
globalName,
865872
$$$"""
866-
{{{visibility}}} sealed class {{{accessor.Name}}}ReadOnly
873+
{{{visibility}}} sealed class {{{accessorIdentifier}}}ReadOnly
867874
: global::SpacetimeDB.Internal.ReadOnlyTableView<{{{globalName}}}>
868875
{
869-
internal {{{accessor.Name}}}ReadOnly() : base("{{{accessor.Name}}}") { }
876+
internal {{{accessorIdentifier}}}ReadOnly() : base("{{{accessor.Name}}}") { }
870877
871878
public ulong Count => DoCount();
872879
873880
{{{readOnlyIndexDecls}}}
874881
}
875882
""",
876-
$"{visibility} global::SpacetimeDB.Internal.ViewHandles.{accessor.Name}ReadOnly {accessor.Name} => new();"
883+
$"{visibility} global::SpacetimeDB.Internal.ViewHandles.{accessorIdentifier}ReadOnly {accessorIdentifier} => new();"
877884
);
878885
}
879886
}
@@ -890,9 +897,10 @@ public IEnumerable<string> GenerateQueryBuilderMembers()
890897

891898
foreach (var accessor in TableAccessors)
892899
{
900+
var accessorIdentifier = accessor.Identifier;
893901
var tableName = accessor.Name;
894-
var colsTypeName = $"{accessor.Name}Cols";
895-
var ixColsTypeName = $"{accessor.Name}IxCols";
902+
var colsTypeName = $"{accessorIdentifier}Cols";
903+
var ixColsTypeName = $"{accessorIdentifier}IxCols";
896904

897905
string ColDecl(ColumnDeclaration col)
898906
{
@@ -987,7 +995,7 @@ string IxColInit(ColumnDeclaration col)
987995
988996
public readonly partial struct QueryBuilder
989997
{
990-
{{vis}} global::SpacetimeDB.Table<{{globalRowName}}, {{colsTypeName}}, {{ixColsTypeName}}> {{accessor.Name}}() =>
998+
{{vis}} global::SpacetimeDB.Table<{{globalRowName}}, {{colsTypeName}}, {{ixColsTypeName}}> {{accessorIdentifier}}() =>
991999
new("{{tableName}}", new {{colsTypeName}}("{{tableName}}"), new {{ixColsTypeName}}("{{tableName}}"));
9921000
}
9931001
""";
@@ -1345,6 +1353,8 @@ record ReducerDeclaration
13451353
public readonly Scope Scope;
13461354
private readonly bool HasWrongSignature;
13471355

1356+
public string Identifier => EscapeIdentifier(Name);
1357+
13481358
public ReducerDeclaration(GeneratorAttributeSyntaxContext context, DiagReporter diag)
13491359
{
13501360
var methodSyntax = (MethodDeclarationSyntax)context.TargetNode;
@@ -1398,11 +1408,11 @@ public string GenerateClass()
13981408
)})";
13991409

14001410
return $$"""
1401-
class {{Name}}: SpacetimeDB.Internal.IReducer {
1411+
class {{Identifier}}: SpacetimeDB.Internal.IReducer {
14021412
{{MemberDeclaration.GenerateBsatnFields(Accessibility.Private, Args)}}
14031413
14041414
public SpacetimeDB.Internal.RawReducerDefV10 MakeReducerDef(SpacetimeDB.BSATN.ITypeRegistrar registrar) => new (
1405-
SourceName: nameof({{Name}}),
1415+
SourceName: nameof({{Identifier}}),
14061416
Params: [{{MemberDeclaration.GenerateDefs(Args)}}],
14071417
Visibility: SpacetimeDB.Internal.FunctionVisibility.ClientCallable,
14081418
OkReturnType: SpacetimeDB.BSATN.AlgebraicType.Unit,
@@ -1445,7 +1455,7 @@ public Scope.Extensions GenerateSchedule()
14451455
"\n",
14461456
Args.Select(a => $"new {a.Type.ToBSATNString()}().Write(writer, {a.Identifier});")
14471457
)}}
1448-
SpacetimeDB.Internal.IReducer.VolatileNonatomicScheduleImmediate(nameof({{Name}}), stream);
1458+
SpacetimeDB.Internal.IReducer.VolatileNonatomicScheduleImmediate(nameof({{Identifier}}), stream);
14491459
}
14501460
"""
14511461
);
@@ -1470,6 +1480,8 @@ record ProcedureDeclaration
14701480
private readonly TypeUse? TxPayloadType;
14711481
private readonly bool TxPayloadIsUnit;
14721482

1483+
public string Identifier => EscapeIdentifier(Name);
1484+
14731485
public ProcedureDeclaration(GeneratorAttributeSyntaxContext context, DiagReporter diag)
14741486
{
14751487
var methodSyntax = (MethodDeclarationSyntax)context.TargetNode;
@@ -1629,11 +1641,11 @@ public string GenerateClass()
16291641
}
16301642

16311643
return $$$"""
1632-
class {{{Name}}} : SpacetimeDB.Internal.IProcedure {
1644+
class {{{Identifier}}} : SpacetimeDB.Internal.IProcedure {
16331645
{{{classFields}}}
16341646
16351647
public SpacetimeDB.Internal.RawProcedureDefV10 MakeProcedureDef(SpacetimeDB.BSATN.ITypeRegistrar registrar) => new(
1636-
SourceName: nameof({{{Name}}}),
1648+
SourceName: nameof({{{Identifier}}}),
16371649
Params: [{{{MemberDeclaration.GenerateDefs(Args)}}}],
16381650
ReturnType: {{{returnTypeExpr}}},
16391651
Visibility: SpacetimeDB.Internal.FunctionVisibility.ClientCallable
@@ -1667,7 +1679,7 @@ public Scope.Extensions GenerateSchedule()
16671679
"\n",
16681680
Args.Select(a => $"new {a.Type.ToBSATNString()}().Write(writer, {a.Identifier});")
16691681
)}}
1670-
SpacetimeDB.Internal.ProcedureExtensions.VolatileNonatomicScheduleImmediate(nameof({{Name}}), stream);
1682+
SpacetimeDB.Internal.ProcedureExtensions.VolatileNonatomicScheduleImmediate(nameof({{Identifier}}), stream);
16711683
}
16721684
"""
16731685
);
@@ -2335,13 +2347,13 @@ public static void Main() {
23352347
{{string.Join(
23362348
"\n",
23372349
addReducers.Select(r =>
2338-
$"SpacetimeDB.Internal.Module.RegisterReducer<{r.Name}>();"
2350+
$"SpacetimeDB.Internal.Module.RegisterReducer<{EscapeIdentifier(r.Name)}>();"
23392351
)
23402352
)}}
23412353
{{string.Join(
23422354
"\n",
23432355
addProcedures.Select(r =>
2344-
$"SpacetimeDB.Internal.Module.RegisterProcedure<{r.Name}>();"
2356+
$"SpacetimeDB.Internal.Module.RegisterProcedure<{EscapeIdentifier(r.Name)}>();"
23452357
)
23462358
)}}
23472359
@@ -2359,7 +2371,7 @@ public static void Main() {
23592371
23602372
{{string.Join(
23612373
"\n",
2362-
tableAccessors.Select(t => $"SpacetimeDB.Internal.Module.RegisterTable<{t.tableName}, SpacetimeDB.Internal.TableHandles.{t.tableAccessorName}>();")
2374+
tableAccessors.Select(t => $"SpacetimeDB.Internal.Module.RegisterTable<{t.tableName}, SpacetimeDB.Internal.TableHandles.{EscapeIdentifier(t.tableAccessorName)}>();")
23632375
)}}
23642376
{{string.Join(
23652377
"\n",

0 commit comments

Comments
 (0)