diff --git a/crates/bindings-csharp/Codegen.Tests/Tests.cs b/crates/bindings-csharp/Codegen.Tests/Tests.cs index 8b617a161b2..9148af3f498 100644 --- a/crates/bindings-csharp/Codegen.Tests/Tests.cs +++ b/crates/bindings-csharp/Codegen.Tests/Tests.cs @@ -300,7 +300,7 @@ 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 @@ -308,8 +308,7 @@ public static async Task ViewInvalidReturnHighlightsReturnType() .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); @@ -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); } } diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/Lib.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/Lib.cs index 47d5cf291f5..4eac3d7645a 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/Lib.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/Lib.cs @@ -569,14 +569,14 @@ public static Player ViewDefWrongReturn(ViewContext ctx) return new Player { Identity = new() }; } - // Invalid: IEnumerable return type (from Iter()) is not List or T? + // Valid: IEnumerable return type (from Iter()) is supported [SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_iter", Public = true)] public static IEnumerable ViewDefIEnumerableReturnFromIter(ViewContext ctx) { return ctx.Db.Player.Iter(); } - // Invalid: IEnumerable return type (from Filter()) is not List or T? + // Valid: IEnumerable return type (from Filter()) is supported [SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_filter", Public = true)] public static IEnumerable ViewDefIEnumerableReturnFromFilter( ViewContext ctx diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt index 8e960c9f38a..c732156f772 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt @@ -256,7 +256,7 @@ public partial struct TestDefaultFieldValues var returnValue = Module.ViewDefWrongContext((SpacetimeDB.ViewContext)ctx); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - SpacetimeDB.BSATN.List returnRW = new(); + var listSerializer = new SpacetimeDB.BSATN.List(); */ Message: Argument 1: cannot convert from 'SpacetimeDB.ViewContext' to 'SpacetimeDB.ReducerContext', Severity: Error, @@ -325,7 +325,7 @@ SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_THI var returnValue = Module.ViewDefNoContext((SpacetimeDB.ViewContext)ctx); ^^^^^^^^^^^^^^^^ - SpacetimeDB.BSATN.List returnRW = new(); + var listSerializer = new SpacetimeDB.BSATN.List(); */ Message: No overload for method 'ViewDefNoContext' takes 1 arguments, Severity: Error, diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs index e69962b169c..679dbcb6c70 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs @@ -1819,9 +1819,10 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar IsPublic: true, IsAnonymous: false, Params: [], - ReturnType: new SpacetimeDB.BSATN.Unsupported>().GetAlgebraicType( - registrar - ) + ReturnType: new SpacetimeDB.BSATN.List< + TestScheduleIssues, + TestScheduleIssues.BSATN + >().GetAlgebraicType(registrar) ); public byte[] Invoke( @@ -1834,14 +1835,17 @@ public byte[] Invoke( var returnValue = Module.ViewDefIEnumerableReturnFromFilter( (SpacetimeDB.ViewContext)ctx ); - SpacetimeDB.BSATN.Unsupported> returnRW = - new(); + var listSerializer = new SpacetimeDB.BSATN.List< + TestScheduleIssues, + TestScheduleIssues.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) @@ -1866,7 +1870,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar IsPublic: true, IsAnonymous: false, Params: [], - ReturnType: new SpacetimeDB.BSATN.Unsupported>().GetAlgebraicType( + ReturnType: new SpacetimeDB.BSATN.List().GetAlgebraicType( registrar ) ); @@ -1879,14 +1883,14 @@ public byte[] Invoke( try { var returnValue = Module.ViewDefIEnumerableReturnFromIter((SpacetimeDB.ViewContext)ctx); - SpacetimeDB.BSATN.Unsupported> returnRW = - new(); + var listSerializer = new SpacetimeDB.BSATN.List(); + 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) @@ -1923,13 +1927,14 @@ public byte[] Invoke( try { var returnValue = Module.ViewDefNoContext((SpacetimeDB.ViewContext)ctx); - SpacetimeDB.BSATN.List returnRW = new(); + var listSerializer = new SpacetimeDB.BSATN.List(); + 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) @@ -1964,13 +1969,14 @@ public byte[] Invoke( try { var returnValue = Module.ViewDefNoPublic((SpacetimeDB.ViewContext)ctx); - SpacetimeDB.BSATN.List returnRW = new(); + var listSerializer = new SpacetimeDB.BSATN.List(); + 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) @@ -2005,13 +2011,14 @@ public byte[] Invoke( try { var returnValue = Module.ViewDefWrongContext((SpacetimeDB.ViewContext)ctx); - SpacetimeDB.BSATN.List returnRW = new(); + var listSerializer = new SpacetimeDB.BSATN.List(); + 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) diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module.verified.txt b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module.verified.txt index 492d1cf9db5..645e102d204 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module.verified.txt +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module.verified.txt @@ -335,80 +335,12 @@ public partial struct TestScheduleIssues ^^^^^^ { */ - Message: View 'ViewDefWrongReturn' must return T?, List, or IQuery, + Message: View 'ViewDefWrongReturn' must return T?, List, IQuery, or IEnumerable., Severity: Error, Descriptor: { Id: STDB0024, - Title: Views must return T?, List, or IQuery, - MessageFormat: View '{0}' must return T?, List, or IQuery, - Category: SpacetimeDB, - DefaultSeverity: Error, - IsEnabledByDefault: true - } - }, - {/* - [SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_iter", Public = true)] - public static IEnumerable ViewDefIEnumerableReturnFromIter(ViewContext ctx) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - { -*/ - Message: BSATN implementation for System.Collections.Generic.IEnumerable is not found: Unsupported system type System.Collections.Generic.IEnumerable, - 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 ViewDefIEnumerableReturnFromIter(ViewContext ctx) - ^^^^^^^^^^^^^^^^^^^ - { -*/ - Message: View 'ViewDefIEnumerableReturnFromIter' must return T?, List, or IQuery, - Severity: Error, - Descriptor: { - Id: STDB0024, - Title: Views must return T?, List, or IQuery, - MessageFormat: View '{0}' must return T?, List, or IQuery, - Category: SpacetimeDB, - DefaultSeverity: Error, - IsEnabledByDefault: true - } - }, - {/* - [SpacetimeDB.View(Accessor = "view_def_ienumerable_return_from_filter", Public = true)] - public static IEnumerable ViewDefIEnumerableReturnFromFilter( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ViewContext ctx -*/ - Message: BSATN implementation for System.Collections.Generic.IEnumerable is not found: Unsupported system type System.Collections.Generic.IEnumerable, - 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 ViewDefIEnumerableReturnFromFilter( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ViewContext ctx -*/ - Message: View 'ViewDefIEnumerableReturnFromFilter' must return T?, List, or IQuery, - Severity: Error, - Descriptor: { - Id: STDB0024, - Title: Views must return T?, List, or IQuery, - MessageFormat: View '{0}' must return T?, List, or IQuery, + Title: Views must return T?, List, IQuery, or IEnumerable, + MessageFormat: View '{0}' must return T?, List, IQuery, or IEnumerable., Category: SpacetimeDB, DefaultSeverity: Error, IsEnabledByDefault: true diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/explicitnames/snapshots/Module#FFI.verified.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/explicitnames/snapshots/Module#FFI.verified.cs index 82127603515..b620e8d37c9 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/explicitnames/snapshots/Module#FFI.verified.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/explicitnames/snapshots/Module#FFI.verified.cs @@ -366,13 +366,14 @@ public byte[] Invoke( try { var returnValue = Reducers.DemoView((SpacetimeDB.ViewContext)ctx); - SpacetimeDB.BSATN.List returnRW = new(); + var listSerializer = new SpacetimeDB.BSATN.List(); + 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) diff --git a/crates/bindings-csharp/Codegen/Diag.cs b/crates/bindings-csharp/Codegen/Diag.cs index c4916e378ca..1d2a77cd711 100644 --- a/crates/bindings-csharp/Codegen/Diag.cs +++ b/crates/bindings-csharp/Codegen/Diag.cs @@ -215,8 +215,9 @@ string typeName public static readonly ErrorDescriptor ViewInvalidReturn = new( group, - "Views must return T?, List, or IQuery", - method => $"View '{method.Identifier}' must return T?, List, or IQuery", + "Views must return T?, List, IQuery, or IEnumerable", + method => + $"View '{method.Identifier}' must return T?, List, IQuery, or IEnumerable.", method => method.ReturnType ); diff --git a/crates/bindings-csharp/Codegen/Module.cs b/crates/bindings-csharp/Codegen/Module.cs index db31d77e865..4dd95b82434 100644 --- a/crates/bindings-csharp/Codegen/Module.cs +++ b/crates/bindings-csharp/Codegen/Module.cs @@ -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 Parameters; public readonly Scope Scope; @@ -1155,6 +1156,7 @@ public ViewDeclaration(GeneratorAttributeSyntaxContext context, DiagReporter dia IsAnonymous = isAnonymousContext; ReturnsQuery = false; + ReturnsEnumerable = false; INamedTypeSymbol? iquery = null; if ( method.ReturnType is INamedTypeSymbol @@ -1192,9 +1194,40 @@ method.ReturnType is INamedTypeSymbol // Match Rust semantics: Query 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" + ) + { + 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" + ) + { + ReturnsEnumerable = true; + } } Scope = new Scope(methodSyntax.Parent as MemberDeclarationSyntax); @@ -1210,12 +1243,12 @@ method.ReturnType is INamedTypeSymbol diag.Report(ErrorDescriptor.ViewContextParam, methodSyntax); } - // Validate return type: must be List 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?, or IEnumerable + var isOption = + ReturnType.BSATNName.Contains("SpacetimeDB.BSATN.ValueOption") + || ReturnType.BSATNName.Contains("SpacetimeDB.BSATN.RefOption"); + + if (!ReturnsQuery && !ReturnsEnumerable && !isOption) { diag.Report(ErrorDescriptor.ViewInvalidReturn, methodSyntax); } @@ -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); diff --git a/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md b/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md index ec05ce1fe13..f8a3f2d814f 100644 --- a/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md +++ b/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md @@ -631,9 +631,9 @@ public static Player? MyPlayer(ViewContext ctx) // Return potentially multiple rows [SpacetimeDB.View(Accessor = "TopPlayers", Public = true)] -public static List TopPlayers(ViewContext ctx) +public static IEnumerable 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. diff --git a/docs/docs/00200-core-concepts/00200-functions/00500-views.md b/docs/docs/00200-core-concepts/00200-functions/00500-views.md index 869d63911bd..e2406ef5dd2 100644 --- a/docs/docs/00200-core-concepts/00200-functions/00500-views.md +++ b/docs/docs/00200-core-concepts/00200-functions/00500-views.md @@ -135,9 +135,9 @@ public static partial class Module return ctx.Db.Player.Identity.Find(ctx.Sender) as Player?; } - // Multiple rows: return a list + // Multiple rows: return a list or enumerable [SpacetimeDB.View(Accessor = "PlayersForLevel", Public = true)] - public static List PlayersForLevel(AnonymousViewContext ctx) + public static IEnumerable PlayersForLevel(AnonymousViewContext ctx) { var rows = new List(); foreach (var player in ctx.Db.PlayerLevel.Level.Filter(1)) @@ -159,7 +159,7 @@ public static partial class Module } ``` -Views must be static methods and can return either a single row (`T?`) or a list of rows (`List` or `T[]`) where `T` can be a table type or any product type. +Views must be static methods and can return either a single row (`T?`) or multiple rows (`IEnumerable`, `List`, or `T[]`) where `T` can be a table type or any product type.