Skip to content

Commit 68e3565

Browse files
RReverserbfops
andauthored
C#: split table codegen logic from type codegen logic (#1573)
Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
1 parent 69e881c commit 68e3565

19 files changed

+902
-1046
lines changed

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

Lines changed: 233 additions & 274 deletions
Large diffs are not rendered by default.

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

Lines changed: 68 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static string SymbolToName(ISymbol symbol)
4040
}
4141

4242
public static void RegisterSourceOutputs(
43-
this IncrementalValuesProvider<KeyValuePair<string, string>> methods,
43+
this IncrementalValuesProvider<Scope.Extensions> methods,
4444
IncrementalGeneratorInitializationContext context
4545
)
4646
{
@@ -49,12 +49,12 @@ IncrementalGeneratorInitializationContext context
4949
(context, method) =>
5050
{
5151
context.AddSource(
52-
$"{string.Join("_", method.Key.Split(Path.GetInvalidFileNameChars()))}.cs",
52+
$"{string.Join("_", method.FullName.Split(Path.GetInvalidFileNameChars()))}.cs",
5353
$"""
5454
// <auto-generated />
5555
#nullable enable
5656
57-
{method.Value}
57+
{method}
5858
"""
5959
);
6060
}
@@ -155,38 +155,24 @@ static string GetTypeInfoForNamedType(INamedTypeSymbol type)
155155
}
156156
}
157157

158-
public static IEnumerable<IFieldSymbol> GetFields(
159-
TypeDeclarationSyntax typeSyntax,
160-
INamedTypeSymbol type
158+
// Polyfill for .NET methods from .NET Standard 2.1+:
159+
private static StringBuilder AppendJoin<T>(
160+
this StringBuilder sb,
161+
string separator,
162+
IEnumerable<T> values
161163
)
162164
{
163-
// Note: we could use naively use `type.GetMembers()` to get all fields of the type,
164-
// but some users add their own fields in extra partial declarations like this:
165-
//
166-
// ```csharp
167-
// [SpacetimeDB.Type]
168-
// partial class MyType
169-
// {
170-
// public int TableField;
171-
// }
172-
//
173-
// partial class MyType
174-
// {
175-
// public int ExtraField;
176-
// }
177-
// ```
178-
//
179-
// In this scenario, only fields declared inside the declaration with the `[SpacetimeDB.Type]` attribute
180-
// should be considered as BSATN fields, and others are expected to be ignored.
181-
//
182-
// To achieve this, we need to walk over the annotated type syntax node, collect the field names,
183-
// and look up the resolved field symbols only for those fields.
184-
return typeSyntax
185-
.Members.OfType<FieldDeclarationSyntax>()
186-
.SelectMany(f => f.Declaration.Variables)
187-
.SelectMany(v => type.GetMembers(v.Identifier.Text))
188-
.OfType<IFieldSymbol>()
189-
.Where(f => !f.IsStatic);
165+
var first = true;
166+
foreach (var value in values)
167+
{
168+
if (!first)
169+
{
170+
sb.Append(separator);
171+
}
172+
first = false;
173+
sb.Append(value);
174+
}
175+
return sb;
190176
}
191177

192178
// Borrowed & modified code for generating in-place extensions for partial structs/classes/etc. Source:
@@ -232,71 +218,70 @@ public Scope(MemberDeclarationSyntax? node)
232218

233219
public readonly record struct TypeScope(string Keyword, string Name, string Constraints);
234220

235-
public string GenerateExtensions(
236-
string contents,
237-
string? interface_ = null,
238-
string? extraAttrs = null
239-
)
221+
public sealed record Extensions(Scope Scope, string FullName)
240222
{
241-
var sb = new StringBuilder();
223+
public readonly StringBuilder Contents = new();
224+
public readonly List<string> BaseTypes = [];
225+
public readonly List<string> ExtraAttrs = [];
242226

243-
// Join all namespaces into a single namespace statement, starting with the outermost.
244-
if (namespaces.Length > 0)
227+
public override string ToString()
245228
{
246-
sb.Append("namespace ");
247-
var first = true;
248-
foreach (var ns in namespaces.Reverse())
229+
var sb = new StringBuilder();
230+
231+
// Join all namespaces into a single namespace statement, starting with the outermost.
232+
if (Scope.namespaces.Length > 0)
249233
{
250-
if (!first)
251-
{
252-
sb.Append('.');
253-
}
254-
first = false;
255-
sb.Append(ns);
234+
sb.Append("namespace ")
235+
.AppendJoin(".", Scope.namespaces.Reverse())
236+
.AppendLine(" {");
256237
}
257-
sb.AppendLine(" {");
258-
}
259238

260-
// Loop through the full parent type hiearchy, starting with the outermost.
261-
foreach (var (i, typeScope) in typeScopes.Select((ts, i) => (i, ts)).Reverse())
262-
{
263-
if (i == 0 && extraAttrs is not null)
239+
// Loop through the full parent type hiearchy, starting with the outermost.
240+
foreach (
241+
var (i, typeScope) in Scope.typeScopes.Select((ts, i) => (i, ts)).Reverse()
242+
)
264243
{
265-
sb.AppendLine(extraAttrs);
266-
}
244+
if (i == 0)
245+
{
246+
foreach (var extraAttr in ExtraAttrs)
247+
{
248+
sb.AppendLine(extraAttr);
249+
}
250+
}
267251

268-
sb.Append("partial ")
269-
.Append(typeScope.Keyword) // e.g. class/struct/record
270-
.Append(' ')
271-
.Append(typeScope.Name) // e.g. Outer/Generic<T>
272-
.Append(' ');
252+
sb.Append("partial ")
253+
.Append(typeScope.Keyword) // e.g. class/struct/record
254+
.Append(' ')
255+
.Append(typeScope.Name) // e.g. Outer/Generic<T>
256+
.Append(' ');
273257

274-
if (i == 0 && interface_ is not null)
275-
{
276-
sb.Append(" : ").Append(interface_);
258+
if (i == 0 && BaseTypes.Count > 0)
259+
{
260+
sb.Append(" : ").AppendJoin(", ", BaseTypes);
261+
}
262+
263+
sb.Append(typeScope.Constraints).AppendLine(" {");
277264
}
278265

279-
sb.Append(typeScope.Constraints).AppendLine(" {");
280-
}
266+
sb.AppendLine();
267+
sb.Append(Contents);
268+
sb.AppendLine();
281269

282-
sb.AppendLine();
283-
sb.Append(contents);
284-
sb.AppendLine();
270+
// We need to "close" each of the parent types, so write
271+
// the required number of '}'
272+
foreach (var typeScope in Scope.typeScopes)
273+
{
274+
sb.Append("} // ").AppendLine(typeScope.Name);
275+
}
285276

286-
// We need to "close" each of the parent types, so write
287-
// the required number of '}'
288-
foreach (var typeScope in typeScopes)
289-
{
290-
sb.Append("} // ").AppendLine(typeScope.Name);
291-
}
277+
// Close the namespace, if we had one
278+
if (Scope.namespaces.Length > 0)
279+
{
280+
sb.AppendLine("} // namespace");
281+
}
292282

293-
// Close the namespace, if we had one
294-
if (namespaces.Length > 0)
295-
{
296-
sb.AppendLine("} // namespace");
283+
return sb.ToString();
297284
}
298-
299-
return sb.ToString();
300285
}
301286
}
302287
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Text;
2-
using SpacetimeDB;
32

43
namespace SpacetimeDB.BSATN;
54

crates/bindings-csharp/Codegen.Tests/fixtures/client/snapshots/Type#CustomClass.verified.cs

Lines changed: 7 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bindings-csharp/Codegen.Tests/fixtures/client/snapshots/Type#CustomStruct.verified.cs

Lines changed: 7 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bindings-csharp/Codegen.Tests/fixtures/client/snapshots/Type#CustomTaggedEnum.verified.cs

Lines changed: 7 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bindings-csharp/Codegen.Tests/fixtures/client/snapshots/Type#PublicTable.verified.cs

Lines changed: 38 additions & 49 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)