Skip to content

Commit

Permalink
C#: split table codegen logic from type codegen logic (#1573)
Browse files Browse the repository at this point in the history
Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
  • Loading branch information
RReverser and bfops authored Sep 5, 2024
1 parent 69e881c commit 68e3565
Show file tree
Hide file tree
Showing 19 changed files with 902 additions and 1,046 deletions.
507 changes: 233 additions & 274 deletions crates/bindings-csharp/BSATN.Codegen/Type.cs

Large diffs are not rendered by default.

151 changes: 68 additions & 83 deletions crates/bindings-csharp/BSATN.Codegen/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static string SymbolToName(ISymbol symbol)
}

public static void RegisterSourceOutputs(
this IncrementalValuesProvider<KeyValuePair<string, string>> methods,
this IncrementalValuesProvider<Scope.Extensions> methods,
IncrementalGeneratorInitializationContext context
)
{
Expand All @@ -49,12 +49,12 @@ IncrementalGeneratorInitializationContext context
(context, method) =>
{
context.AddSource(
$"{string.Join("_", method.Key.Split(Path.GetInvalidFileNameChars()))}.cs",
$"{string.Join("_", method.FullName.Split(Path.GetInvalidFileNameChars()))}.cs",
$"""
// <auto-generated />
#nullable enable
{method.Value}
{method}
"""
);
}
Expand Down Expand Up @@ -155,38 +155,24 @@ static string GetTypeInfoForNamedType(INamedTypeSymbol type)
}
}

public static IEnumerable<IFieldSymbol> GetFields(
TypeDeclarationSyntax typeSyntax,
INamedTypeSymbol type
// Polyfill for .NET methods from .NET Standard 2.1+:
private static StringBuilder AppendJoin<T>(
this StringBuilder sb,
string separator,
IEnumerable<T> values
)
{
// Note: we could use naively use `type.GetMembers()` to get all fields of the type,
// but some users add their own fields in extra partial declarations like this:
//
// ```csharp
// [SpacetimeDB.Type]
// partial class MyType
// {
// public int TableField;
// }
//
// partial class MyType
// {
// public int ExtraField;
// }
// ```
//
// In this scenario, only fields declared inside the declaration with the `[SpacetimeDB.Type]` attribute
// should be considered as BSATN fields, and others are expected to be ignored.
//
// To achieve this, we need to walk over the annotated type syntax node, collect the field names,
// and look up the resolved field symbols only for those fields.
return typeSyntax
.Members.OfType<FieldDeclarationSyntax>()
.SelectMany(f => f.Declaration.Variables)
.SelectMany(v => type.GetMembers(v.Identifier.Text))
.OfType<IFieldSymbol>()
.Where(f => !f.IsStatic);
var first = true;
foreach (var value in values)
{
if (!first)
{
sb.Append(separator);
}
first = false;
sb.Append(value);
}
return sb;
}

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

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

public string GenerateExtensions(
string contents,
string? interface_ = null,
string? extraAttrs = null
)
public sealed record Extensions(Scope Scope, string FullName)
{
var sb = new StringBuilder();
public readonly StringBuilder Contents = new();
public readonly List<string> BaseTypes = [];
public readonly List<string> ExtraAttrs = [];

// Join all namespaces into a single namespace statement, starting with the outermost.
if (namespaces.Length > 0)
public override string ToString()
{
sb.Append("namespace ");
var first = true;
foreach (var ns in namespaces.Reverse())
var sb = new StringBuilder();

// Join all namespaces into a single namespace statement, starting with the outermost.
if (Scope.namespaces.Length > 0)
{
if (!first)
{
sb.Append('.');
}
first = false;
sb.Append(ns);
sb.Append("namespace ")
.AppendJoin(".", Scope.namespaces.Reverse())
.AppendLine(" {");
}
sb.AppendLine(" {");
}

// Loop through the full parent type hiearchy, starting with the outermost.
foreach (var (i, typeScope) in typeScopes.Select((ts, i) => (i, ts)).Reverse())
{
if (i == 0 && extraAttrs is not null)
// Loop through the full parent type hiearchy, starting with the outermost.
foreach (
var (i, typeScope) in Scope.typeScopes.Select((ts, i) => (i, ts)).Reverse()
)
{
sb.AppendLine(extraAttrs);
}
if (i == 0)
{
foreach (var extraAttr in ExtraAttrs)
{
sb.AppendLine(extraAttr);
}
}

sb.Append("partial ")
.Append(typeScope.Keyword) // e.g. class/struct/record
.Append(' ')
.Append(typeScope.Name) // e.g. Outer/Generic<T>
.Append(' ');
sb.Append("partial ")
.Append(typeScope.Keyword) // e.g. class/struct/record
.Append(' ')
.Append(typeScope.Name) // e.g. Outer/Generic<T>
.Append(' ');

if (i == 0 && interface_ is not null)
{
sb.Append(" : ").Append(interface_);
if (i == 0 && BaseTypes.Count > 0)
{
sb.Append(" : ").AppendJoin(", ", BaseTypes);
}

sb.Append(typeScope.Constraints).AppendLine(" {");
}

sb.Append(typeScope.Constraints).AppendLine(" {");
}
sb.AppendLine();
sb.Append(Contents);
sb.AppendLine();

sb.AppendLine();
sb.Append(contents);
sb.AppendLine();
// We need to "close" each of the parent types, so write
// the required number of '}'
foreach (var typeScope in Scope.typeScopes)
{
sb.Append("} // ").AppendLine(typeScope.Name);
}

// We need to "close" each of the parent types, so write
// the required number of '}'
foreach (var typeScope in typeScopes)
{
sb.Append("} // ").AppendLine(typeScope.Name);
}
// Close the namespace, if we had one
if (Scope.namespaces.Length > 0)
{
sb.AppendLine("} // namespace");
}

// Close the namespace, if we had one
if (namespaces.Length > 0)
{
sb.AppendLine("} // namespace");
return sb.ToString();
}

return sb.ToString();
}
}
}
1 change: 0 additions & 1 deletion crates/bindings-csharp/BSATN.Runtime/BSATN/Runtime.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Text;
using SpacetimeDB;

namespace SpacetimeDB.BSATN;

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

2 comments on commit 68e3565

@github-actions
Copy link

@github-actions github-actions bot commented on 68e3565 Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmarking failed. Please check the workflow run for details.

@github-actions
Copy link

@github-actions github-actions bot commented on 68e3565 Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmarking failed. Please check the workflow run for details.

Please sign in to comment.