Skip to content
Merged
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
36 changes: 36 additions & 0 deletions SharpTS.Tests/SharedTests/AsyncGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using SharpTS.Tests.Infrastructure;
using SharpTS.TypeSystem.Exceptions;
using Xunit;

namespace SharpTS.Tests.SharedTests;
Expand Down Expand Up @@ -329,6 +330,41 @@ async function main() {
Assert.Equal("first: 1\nfirst: 2\nsecond: 1\nsecond: 2\n", output);
}

// #672: a top-level `for await...of` drives the async-iterator protocol, which requires an async
// context. SharpTS does not support top-level await, so — like a top-level `await` expression —
// it must be rejected by the type checker rather than silently degrading to a synchronous
// `for...of` (which then throws a misleading 'not iterable' runtime error in both modes).
[Theory]
[MemberData(nameof(ExecutionModes.All), MemberType = typeof(ExecutionModes))]
public void ForAwaitOf_TopLevel_RejectedByTypeChecker(ExecutionMode mode)
{
var source = """
async function* g() { yield 1; yield 2; }
for await (const x of g()) console.log("top", x);
""";

var ex = Assert.Throws<TypeCheckException>(() => TestHarness.Run(source, mode));
Assert.Contains("'await' is only valid inside an async function.", ex.Message);
}

// #672: `for await...of` inside a non-async function is likewise an await outside an async
// context and must be rejected (TS conformance), not run as a synchronous `for...of`.
[Theory]
[MemberData(nameof(ExecutionModes.All), MemberType = typeof(ExecutionModes))]
public void ForAwaitOf_InNonAsyncFunction_RejectedByTypeChecker(ExecutionMode mode)
{
var source = """
async function* g() { yield 1; yield 2; }
function notAsync() {
for await (const x of g()) console.log(x);
}
notAsync();
""";

var ex = Assert.Throws<TypeCheckException>(() => TestHarness.Run(source, mode));
Assert.Contains("'await' is only valid inside an async function.", ex.Message);
}

#endregion

#region Async Generator .return() and .throw() Tests
Expand Down
12 changes: 12 additions & 0 deletions TypeSystem/TypeChecker.Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,18 @@ internal VoidResult VisitFor(Stmt.For stmt)

internal VoidResult VisitForOf(Stmt.ForOf stmt)
{
// `for await...of` drives the async-iterator protocol, so — like an `await` expression or
// `await using` — it is only valid inside an async function. SharpTS does not support
// top-level await, so a top-level (or otherwise non-async-context) `for await` is rejected
// here rather than silently degrading to a synchronous `for...of`, which would then fail at
// runtime with a misleading 'not iterable' error (#672). Mirrors CheckAwait / CheckUsingDeclaration.
if (stmt.IsAsync && !_inAsyncFunction)
{
throw new TypeCheckException(
"'await' is only valid inside an async function.",
stmt.Variable.Line, tsCode: "TS1308");
}

TypeInfo iterableType = CheckExpr(stmt.Iterable);

// Now that generator yield-type inference draws from the `yield` / `yield*` operands (#548), the
Expand Down
Loading