Skip to content
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
7 changes: 3 additions & 4 deletions crates/bindings-csharp/Codegen.Tests/Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,16 +300,15 @@ public static async Task ViewInvalidReturnHighlightsReturnType()
Method = method,
})
)
.Single(entry => entry.Method.Identifier.Text == "ViewDefIEnumerableReturnFromIter");
.Single(entry => entry.Method.Identifier.Text == "ViewDefWrongReturn");

var returnTypeSpan = method.Method.ReturnType.Span;
var diagnostics = runResult
.Results.SelectMany(result => result.Diagnostics)
.Where(d => d.Id == "STDB0024")
.ToList();
var diagnostic = diagnostics.FirstOrDefault(d =>
d.GetMessage().Contains("ViewDefIEnumerableReturnFromIter")
&& d.Location.SourceTree == method.Tree
d.GetMessage().Contains("ViewDefWrongReturn") && d.Location.SourceTree == method.Tree
);

Assert.NotNull(diagnostic);
Expand All @@ -319,6 +318,6 @@ public static async Task ViewInvalidReturnHighlightsReturnType()
var returnTypeText = method
.Root.ToFullString()
.Substring(returnTypeSpan.Start, returnTypeSpan.Length);
Assert.Contains("IEnumerable", returnTypeText);
Assert.Contains("Player", returnTypeText);
}
}
4 changes: 2 additions & 2 deletions crates/bindings-csharp/Codegen.Tests/fixtures/diag/Lib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -569,14 +569,14 @@ public static Player ViewDefWrongReturn(ViewContext ctx)
return new Player { Identity = new() };
}

// Invalid: IEnumerable<T> return type (from Iter()) is not List<T> or T?
// Valid: IEnumerable<T> return type (from Iter()) is supported
[SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_iter", Public = true)]
public static IEnumerable<Player> ViewDefIEnumerableReturnFromIter(ViewContext ctx)
{
return ctx.Db.Player.Iter();
}

// Invalid: IEnumerable<T> return type (from Filter()) is not List<T> or T?
// Valid: IEnumerable<T> return type (from Filter()) is supported
[SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_filter", Public = true)]
public static IEnumerable<TestScheduleIssues> ViewDefIEnumerableReturnFromFilter(
ViewContext ctx
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ public partial struct TestDefaultFieldValues

var returnValue = Module.ViewDefWrongContext((SpacetimeDB.ViewContext)ctx);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SpacetimeDB.BSATN.List<Player, Player.BSATN> returnRW = new();
var listSerializer = new SpacetimeDB.BSATN.List<Player, Player.BSATN>();
*/
Message: Argument 1: cannot convert from 'SpacetimeDB.ViewContext' to 'SpacetimeDB.ReducerContext',
Severity: Error,
Expand Down Expand Up @@ -325,7 +325,7 @@ SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_THI

var returnValue = Module.ViewDefNoContext((SpacetimeDB.ViewContext)ctx);
^^^^^^^^^^^^^^^^
SpacetimeDB.BSATN.List<Player, Player.BSATN> returnRW = new();
var listSerializer = new SpacetimeDB.BSATN.List<Player, Player.BSATN>();
*/
Message: No overload for method 'ViewDefNoContext' takes 1 arguments,
Severity: Error,
Expand Down

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

Original file line number Diff line number Diff line change
Expand Up @@ -335,80 +335,12 @@ public partial struct TestScheduleIssues
^^^^^^
{
*/
Message: View 'ViewDefWrongReturn' must return T?, List<T>, or IQuery<T>,
Message: View 'ViewDefWrongReturn' must return T?, List<T>, IQuery<T>, or IEnumerable<T>.,
Severity: Error,
Descriptor: {
Id: STDB0024,
Title: Views must return T?, List<T>, or IQuery<T>,
MessageFormat: View '{0}' must return T?, List<T>, or IQuery<T>,
Category: SpacetimeDB,
DefaultSeverity: Error,
IsEnabledByDefault: true
}
},
{/*
[SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_iter", Public = true)]
public static IEnumerable<Player> ViewDefIEnumerableReturnFromIter(ViewContext ctx)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
{
*/
Message: BSATN implementation for System.Collections.Generic.IEnumerable<Player> is not found: Unsupported system type System.Collections.Generic.IEnumerable<T>,
Severity: Error,
Descriptor: {
Id: BSATN0001,
Title: Unsupported type,
MessageFormat: BSATN implementation for {0} is not found: {1},
Category: SpacetimeDB.BSATN,
DefaultSeverity: Error,
IsEnabledByDefault: true
}
},
{/*
[SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_iter", Public = true)]
public static IEnumerable<Player> ViewDefIEnumerableReturnFromIter(ViewContext ctx)
^^^^^^^^^^^^^^^^^^^
{
*/
Message: View 'ViewDefIEnumerableReturnFromIter' must return T?, List<T>, or IQuery<T>,
Severity: Error,
Descriptor: {
Id: STDB0024,
Title: Views must return T?, List<T>, or IQuery<T>,
MessageFormat: View '{0}' must return T?, List<T>, or IQuery<T>,
Category: SpacetimeDB,
DefaultSeverity: Error,
IsEnabledByDefault: true
}
},
{/*
[SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_filter", Public = true)]
public static IEnumerable<TestScheduleIssues> ViewDefIEnumerableReturnFromFilter(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ViewContext ctx
*/
Message: BSATN implementation for System.Collections.Generic.IEnumerable<TestScheduleIssues> is not found: Unsupported system type System.Collections.Generic.IEnumerable<T>,
Severity: Error,
Descriptor: {
Id: BSATN0001,
Title: Unsupported type,
MessageFormat: BSATN implementation for {0} is not found: {1},
Category: SpacetimeDB.BSATN,
DefaultSeverity: Error,
IsEnabledByDefault: true
}
},
{/*
[SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_filter", Public = true)]
public static IEnumerable<TestScheduleIssues> ViewDefIEnumerableReturnFromFilter(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ViewContext ctx
*/
Message: View 'ViewDefIEnumerableReturnFromFilter' must return T?, List<T>, or IQuery<T>,
Severity: Error,
Descriptor: {
Id: STDB0024,
Title: Views must return T?, List<T>, or IQuery<T>,
MessageFormat: View '{0}' must return T?, List<T>, or IQuery<T>,
Title: Views must return T?, List<T>, IQuery<T>, or IEnumerable<T>,
MessageFormat: View '{0}' must return T?, List<T>, IQuery<T>, or IEnumerable<T>.,
Category: SpacetimeDB,
DefaultSeverity: Error,
IsEnabledByDefault: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,13 +366,14 @@ public byte[] Invoke(
try
{
var returnValue = Reducers.DemoView((SpacetimeDB.ViewContext)ctx);
SpacetimeDB.BSATN.List<DemoTable, DemoTable.BSATN> returnRW = new();
var listSerializer = new SpacetimeDB.BSATN.List<DemoTable, DemoTable.BSATN>();
var listValue = global::System.Linq.Enumerable.ToList(returnValue);
var header = new global::SpacetimeDB.Internal.ViewResultHeader.RowData(default);
var headerRW = new global::SpacetimeDB.Internal.ViewResultHeader.BSATN();
using var output = new System.IO.MemoryStream();
using var writer = new System.IO.BinaryWriter(output);
headerRW.Write(writer, header);
returnRW.Write(writer, returnValue);
listSerializer.Write(writer, listValue);
return output.ToArray();
}
catch (System.Exception e)
Expand Down
5 changes: 3 additions & 2 deletions crates/bindings-csharp/Codegen/Diag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,9 @@ string typeName
public static readonly ErrorDescriptor<MethodDeclarationSyntax> ViewInvalidReturn =
new(
group,
"Views must return T?, List<T>, or IQuery<T>",
method => $"View '{method.Identifier}' must return T?, List<T>, or IQuery<T>",
"Views must return T?, List<T>, IQuery<T>, or IEnumerable<T>",
method =>
$"View '{method.Identifier}' must return T?, List<T>, IQuery<T>, or IEnumerable<T>.",
method => method.ReturnType
);

Expand Down
57 changes: 51 additions & 6 deletions crates/bindings-csharp/Codegen/Module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,7 @@ record ViewDeclaration
public readonly bool IsAnonymous;
public readonly bool IsPublic;
public readonly bool ReturnsQuery;
public readonly bool ReturnsEnumerable;
public readonly TypeUse ReturnType;
public readonly EquatableArray<MemberDeclaration> Parameters;
public readonly Scope Scope;
Expand Down Expand Up @@ -1155,6 +1156,7 @@ public ViewDeclaration(GeneratorAttributeSyntaxContext context, DiagReporter dia
IsAnonymous = isAnonymousContext;

ReturnsQuery = false;
ReturnsEnumerable = false;
INamedTypeSymbol? iquery = null;
if (
method.ReturnType is INamedTypeSymbol
Expand Down Expand Up @@ -1192,9 +1194,40 @@ method.ReturnType is INamedTypeSymbol
// Match Rust semantics: Query<T> is described as a nullable row (T?).
ReturnType = new ReferenceUse(opt, opt);
}
else if (
method.ReturnType
is INamedTypeSymbol
{
OriginalDefinition: var originalDefinition,
TypeArguments: [var enumerableElementType]
}
&& originalDefinition.ToString() == "System.Collections.Generic.IEnumerable<T>"
)
{
ReturnsEnumerable = true;
var elementType = TypeUse.Parse(method, enumerableElementType, diag);
var elementTypeName = SymbolToName(enumerableElementType);
var listTypeName = $"System.Collections.Generic.List<{elementTypeName}>";
var listTypeInfo =
$"SpacetimeDB.BSATN.List<{elementTypeName}, {elementType.BSATNName}>";
ReturnType = new ListUse(listTypeName, listTypeInfo, elementType);
}
else
{
ReturnType = TypeUse.Parse(method, method.ReturnType, diag);

if (
method.ReturnType
is INamedTypeSymbol
{
OriginalDefinition: var listDefinition,
TypeArguments.Length: 1,
}
&& listDefinition.ToString() == "System.Collections.Generic.List<T>"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we still need this check or does IEnumerable make this one obsolete?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we still need it, because the List<T> is a valid return type. If we don't have that as a valid return type, any places where List<T> is returned, it will report that as an error. Even if there is a conversion between List<T> and IEnumerable, we accept both at this point.

)
{
ReturnsEnumerable = true;
}
}
Scope = new Scope(methodSyntax.Parent as MemberDeclarationSyntax);

Expand All @@ -1210,12 +1243,12 @@ method.ReturnType is INamedTypeSymbol
diag.Report(ErrorDescriptor.ViewContextParam, methodSyntax);
}

// Validate return type: must be List<T> or T?
if (
!ReturnType.BSATNName.Contains("SpacetimeDB.BSATN.ValueOption")
&& !ReturnType.BSATNName.Contains("SpacetimeDB.BSATN.RefOption")
&& !ReturnType.BSATNName.Contains("SpacetimeDB.BSATN.List")
)
// Validate return type: must be List<T>, T?, or IEnumerable<T>
var isOption =
ReturnType.BSATNName.Contains("SpacetimeDB.BSATN.ValueOption")
|| ReturnType.BSATNName.Contains("SpacetimeDB.BSATN.RefOption");

if (!ReturnsQuery && !ReturnsEnumerable && !isOption)
{
diag.Report(ErrorDescriptor.ViewInvalidReturn, methodSyntax);
}
Expand Down Expand Up @@ -1292,6 +1325,18 @@ public string GenerateDispatcherClass(uint index)
listSerializer.Write(writer, listValue);
return output.ToArray();
"""
: ReturnsEnumerable
? $$$"""
var listSerializer = new {{{ReturnType.BSATNName}}}();
var listValue = global::System.Linq.Enumerable.ToList(returnValue);
var header = new global::SpacetimeDB.Internal.ViewResultHeader.RowData(default);
var headerRW = new global::SpacetimeDB.Internal.ViewResultHeader.BSATN();
using var output = new System.IO.MemoryStream();
using var writer = new System.IO.BinaryWriter(output);
headerRW.Write(writer, header);
listSerializer.Write(writer, listValue);
return output.ToArray();
"""
: $$$"""
{{{ReturnType.BSATNName}}} returnRW = new();
var header = new global::SpacetimeDB.Internal.ViewResultHeader.RowData(default);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -631,9 +631,9 @@ public static Player? MyPlayer(ViewContext ctx)

// Return potentially multiple rows
[SpacetimeDB.View(Accessor = "TopPlayers", Public = true)]
public static List<Player> TopPlayers(ViewContext ctx)
public static IEnumerable<Player> TopPlayers(ViewContext ctx)
{
return ctx.Db.Player.Score.Filter(1000).ToList();
return ctx.Db.Player.Score.Filter(1000);
}

// Perform a generic filter using the query builder.
Expand Down
Loading
Loading