Skip to content

Support top level "for await of" #37424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 25, 2021
Merged
Show file tree
Hide file tree
Changes from 6 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
38 changes: 26 additions & 12 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40104,19 +40104,33 @@ namespace ts {
}

if (forInOrOfStatement.kind === SyntaxKind.ForOfStatement && forInOrOfStatement.awaitModifier) {
if ((forInOrOfStatement.flags & NodeFlags.AwaitContext) === NodeFlags.None) {
// use of 'for-await-of' in non-async function
if (!(forInOrOfStatement.flags & NodeFlags.AwaitContext)) {
const sourceFile = getSourceFileOfNode(forInOrOfStatement);
if (!hasParseDiagnostics(sourceFile)) {
const diagnostic = createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.A_for_await_of_statement_is_only_allowed_within_an_async_function_or_async_generator);
const func = getContainingFunction(forInOrOfStatement);
if (func && func.kind !== SyntaxKind.Constructor) {
Debug.assert((getFunctionFlags(func) & FunctionFlags.Async) === 0, "Enclosing function should never be an async function.");
const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async);
addRelatedInfo(diagnostic, relatedInfo);
}
diagnostics.add(diagnostic);
return true;
if (isInTopLevelContext(forInOrOfStatement)) {
if (!hasParseDiagnostics(sourceFile)) {
if (!isEffectiveExternalModule(sourceFile, compilerOptions)) {
diagnostics.add(createDiagnosticForNode(forInOrOfStatement.awaitModifier,
Diagnostics.await_expressions_are_only_allowed_at_the_top_level_of_a_file_when_that_file_is_a_module_but_this_file_has_no_imports_or_exports_Consider_adding_an_empty_export_to_make_this_file_a_module));
Copy link
Member

Choose a reason for hiding this comment

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

@DanielRosenwasser would we want to reuse the phrase "await expressions" here, or should we have a more specific message for for await of?

Copy link
Member

Choose a reason for hiding this comment

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

Create a new diagnostic so we don't seem janky and lazy

'for await' loops are only allowed at the top level of a file when that file is a module, but this file...

}
if ((moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System) || languageVersion < ScriptTarget.ES2017) {
diagnostics.add(createDiagnosticForNode(forInOrOfStatement.awaitModifier,
Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_esnext_or_system_and_the_target_option_is_set_to_es2017_or_higher));
}
}
}
else {
// use of 'for-await-of' in non-async function
if (!hasParseDiagnostics(sourceFile)) {
const diagnostic = createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.A_for_await_of_statement_is_only_allowed_within_an_async_function_or_async_generator);
const func = getContainingFunction(forInOrOfStatement);
if (func && func.kind !== SyntaxKind.Constructor) {
Debug.assert((getFunctionFlags(func) & FunctionFlags.Async) === 0, "Enclosing function should never be an async function.");
const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async);
addRelatedInfo(diagnostic, relatedInfo);
}
diagnostics.add(diagnostic);
return true;
}
}
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/awaitInNonAsyncFunction.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ tests/cases/compiler/awaitInNonAsyncFunction.ts(30,9): error TS1103: A 'for-awai
tests/cases/compiler/awaitInNonAsyncFunction.ts(31,5): error TS1308: 'await' expressions are only allowed within async functions and at the top levels of modules.
tests/cases/compiler/awaitInNonAsyncFunction.ts(34,7): error TS1103: A 'for-await-of' statement is only allowed within an async function or async generator.
tests/cases/compiler/awaitInNonAsyncFunction.ts(35,5): error TS1308: 'await' expressions are only allowed within async functions and at the top levels of modules.
tests/cases/compiler/awaitInNonAsyncFunction.ts(39,5): error TS1103: A 'for-await-of' statement is only allowed within an async function or async generator.
tests/cases/compiler/awaitInNonAsyncFunction.ts(39,5): error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.
tests/cases/compiler/awaitInNonAsyncFunction.ts(40,1): error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.


Expand Down Expand Up @@ -97,7 +97,7 @@ tests/cases/compiler/awaitInNonAsyncFunction.ts(40,1): error TS1378: Top-level '

for await (const _ of []);
~~~~~
!!! error TS1103: A 'for-await-of' statement is only allowed within an async function or async generator.
!!! error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.
await null;
~~~~~
!!! error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.
18 changes: 12 additions & 6 deletions tests/baselines/reference/parser.forAwait.es2018.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,30 @@ tests/cases/conformance/parser/ecmascript2018/forAwait/inFunctionDeclWithDeclIsE
tests/cases/conformance/parser/ecmascript2018/forAwait/inFunctionDeclWithExprIsError.ts(3,9): error TS1103: A 'for-await-of' statement is only allowed within an async function or async generator.
tests/cases/conformance/parser/ecmascript2018/forAwait/inGeneratorWithDeclIsError.ts(3,9): error TS1103: A 'for-await-of' statement is only allowed within an async function or async generator.
tests/cases/conformance/parser/ecmascript2018/forAwait/inGeneratorWithExprIsError.ts(3,9): error TS1103: A 'for-await-of' statement is only allowed within an async function or async generator.
tests/cases/conformance/parser/ecmascript2018/forAwait/topLevelWithDeclIsError.ts(1,5): error TS1103: A 'for-await-of' statement is only allowed within an async function or async generator.
tests/cases/conformance/parser/ecmascript2018/forAwait/topLevelWithDeclIsError.ts(1,5): error TS1375: 'await' expressions are only allowed at the top level of a file when that file is a module, but this file has no imports or exports. Consider adding an empty 'export {}' to make this file a module.
tests/cases/conformance/parser/ecmascript2018/forAwait/topLevelWithDeclIsError.ts(1,5): error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.
tests/cases/conformance/parser/ecmascript2018/forAwait/topLevelWithDeclIsError.ts(1,23): error TS2304: Cannot find name 'y'.
tests/cases/conformance/parser/ecmascript2018/forAwait/topLevelWithExprIsError.ts(1,5): error TS1103: A 'for-await-of' statement is only allowed within an async function or async generator.
tests/cases/conformance/parser/ecmascript2018/forAwait/topLevelWithExprIsError.ts(1,5): error TS1375: 'await' expressions are only allowed at the top level of a file when that file is a module, but this file has no imports or exports. Consider adding an empty 'export {}' to make this file a module.
tests/cases/conformance/parser/ecmascript2018/forAwait/topLevelWithExprIsError.ts(1,5): error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.
tests/cases/conformance/parser/ecmascript2018/forAwait/topLevelWithExprIsError.ts(1,12): error TS2304: Cannot find name 'x'.
tests/cases/conformance/parser/ecmascript2018/forAwait/topLevelWithExprIsError.ts(1,17): error TS2304: Cannot find name 'y'.


==== tests/cases/conformance/parser/ecmascript2018/forAwait/topLevelWithDeclIsError.ts (2 errors) ====
==== tests/cases/conformance/parser/ecmascript2018/forAwait/topLevelWithDeclIsError.ts (3 errors) ====
for await (const x of y) {
~~~~~
!!! error TS1103: A 'for-await-of' statement is only allowed within an async function or async generator.
!!! error TS1375: 'await' expressions are only allowed at the top level of a file when that file is a module, but this file has no imports or exports. Consider adding an empty 'export {}' to make this file a module.
~~~~~
!!! error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.
~
!!! error TS2304: Cannot find name 'y'.
}
==== tests/cases/conformance/parser/ecmascript2018/forAwait/topLevelWithExprIsError.ts (3 errors) ====
==== tests/cases/conformance/parser/ecmascript2018/forAwait/topLevelWithExprIsError.ts (4 errors) ====
for await (x of y) {
~~~~~
!!! error TS1103: A 'for-await-of' statement is only allowed within an async function or async generator.
!!! error TS1375: 'await' expressions are only allowed at the top level of a file when that file is a module, but this file has no imports or exports. Consider adding an empty 'export {}' to make this file a module.
~~~~~
!!! error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.
~
!!! error TS2304: Cannot find name 'x'.
~
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
tests/cases/conformance/externalModules/index.ts(2,1): error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.
tests/cases/conformance/externalModules/index.ts(46,3): error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.
tests/cases/conformance/externalModules/other.ts(9,5): error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.


==== tests/cases/conformance/externalModules/index.ts (2 errors) ====
Expand Down Expand Up @@ -72,8 +73,18 @@ tests/cases/conformance/externalModules/index.ts(46,3): error TS1378: Top-level
await
1;

==== tests/cases/conformance/externalModules/other.ts (0 errors) ====
==== tests/cases/conformance/externalModules/other.ts (1 errors) ====
const _await = 1;

// await allowed in aliased export
export { _await as await };
export { _await as await };

// for-await-of
const arr = [Promise.resolve()];

for await (const item of arr) {
~~~~~
!!! error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.
item;
}

Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,36 @@ await
const _await = 1;

// await allowed in aliased export
export { _await as await };
export { _await as await };

// for-await-of
const arr = [Promise.resolve()];

for await (const item of arr) {
item;
}


//// [other.js]
var e_1, _a;
const _await = 1;
// await allowed in aliased export
export { _await as await };
// for-await-of
const arr = [Promise.resolve()];
try {
for (var arr_1 = __asyncValues(arr), arr_1_1; arr_1_1 = await arr_1.next(), !arr_1_1.done;) {
const item = arr_1_1.value;
item;
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (arr_1_1 && !arr_1_1.done && (_a = arr_1.return)) await _a.call(arr_1);
}
finally { if (e_1) throw e_1.error; }
}
//// [index.js]
export const x = 1;
await x;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,18 @@ export { _await as await };
>_await : Symbol(_await, Decl(other.ts, 0, 5))
>await : Symbol(await, Decl(other.ts, 3, 8))

// for-await-of
const arr = [Promise.resolve()];
>arr : Symbol(arr, Decl(other.ts, 6, 5))
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))

for await (const item of arr) {
>item : Symbol(item, Decl(other.ts, 8, 16))
>arr : Symbol(arr, Decl(other.ts, 6, 5))

item;
>item : Symbol(item, Decl(other.ts, 8, 16))
}

Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,20 @@ export { _await as await };
>_await : 1
>await : 1

// for-await-of
const arr = [Promise.resolve()];
>arr : Promise<void>[]
>[Promise.resolve()] : Promise<void>[]
>Promise.resolve() : Promise<void>
>Promise.resolve : { (): Promise<void>; <T>(value: T | PromiseLike<T>): Promise<T>; }
>Promise : PromiseConstructor
>resolve : { (): Promise<void>; <T>(value: T | PromiseLike<T>): Promise<T>; }

for await (const item of arr) {
>item : void
>arr : Promise<void>[]

item;
>item : void
}

Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,36 @@ await
const _await = 1;

// await allowed in aliased export
export { _await as await };
export { _await as await };

// for-await-of
const arr = [Promise.resolve()];

for await (const item of arr) {
item;
}


//// [other.js]
var e_1, _a;
const _await = 1;
// await allowed in aliased export
export { _await as await };
// for-await-of
const arr = [Promise.resolve()];
try {
for (var arr_1 = __asyncValues(arr), arr_1_1; arr_1_1 = await arr_1.next(), !arr_1_1.done;) {
const item = arr_1_1.value;
item;
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (arr_1_1 && !arr_1_1.done && (_a = arr_1.return)) await _a.call(arr_1);
}
finally { if (e_1) throw e_1.error; }
}
//// [index.js]
export const x = 1;
await x;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,18 @@ export { _await as await };
>_await : Symbol(_await, Decl(other.ts, 0, 5))
>await : Symbol(await, Decl(other.ts, 3, 8))

// for-await-of
const arr = [Promise.resolve()];
>arr : Symbol(arr, Decl(other.ts, 6, 5))
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))

for await (const item of arr) {
>item : Symbol(item, Decl(other.ts, 8, 16))
>arr : Symbol(arr, Decl(other.ts, 6, 5))

item;
>item : Symbol(item, Decl(other.ts, 8, 16))
}

Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,20 @@ export { _await as await };
>_await : 1
>await : 1

// for-await-of
const arr = [Promise.resolve()];
>arr : Promise<void>[]
>[Promise.resolve()] : Promise<void>[]
>Promise.resolve() : Promise<void>
>Promise.resolve : { (): Promise<void>; <T>(value: T | PromiseLike<T>): Promise<T>; }
>Promise : PromiseConstructor
>resolve : { (): Promise<void>; <T>(value: T | PromiseLike<T>): Promise<T>; }

for await (const item of arr) {
>item : void
>arr : Promise<void>[]

item;
>item : void
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
tests/cases/conformance/externalModules/index.ts(2,1): error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.
tests/cases/conformance/externalModules/index.ts(46,3): error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.
tests/cases/conformance/externalModules/other.ts(9,5): error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.


==== tests/cases/conformance/externalModules/index.ts (2 errors) ====
Expand Down Expand Up @@ -72,8 +73,18 @@ tests/cases/conformance/externalModules/index.ts(46,3): error TS1378: Top-level
await
1;

==== tests/cases/conformance/externalModules/other.ts (0 errors) ====
==== tests/cases/conformance/externalModules/other.ts (1 errors) ====
const _await = 1;

// await allowed in aliased export
export { _await as await };
export { _await as await };

// for-await-of
const arr = [Promise.resolve()];

for await (const item of arr) {
~~~~~
!!! error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.
item;
}

Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,41 @@ await
const _await = 1;

// await allowed in aliased export
export { _await as await };
export { _await as await };

// for-await-of
const arr = [Promise.resolve()];

for await (const item of arr) {
item;
}


//// [other.js]
System.register([], function (exports_1, context_1) {
"use strict";
var _await;
var e_1, _a, _await, arr;
var __moduleName = context_1 && context_1.id;
return {
setters: [],
execute: function () {
execute: async function () {
_await = 1;
exports_1("await", _await);
// for-await-of
arr = [Promise.resolve()];
try {
for (var arr_1 = __asyncValues(arr), arr_1_1; arr_1_1 = await arr_1.next(), !arr_1_1.done;) {
const item = arr_1_1.value;
item;
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (arr_1_1 && !arr_1_1.done && (_a = arr_1.return)) await _a.call(arr_1);
}
finally { if (e_1) throw e_1.error; }
}
}
};
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,18 @@ export { _await as await };
>_await : Symbol(_await, Decl(other.ts, 0, 5))
>await : Symbol(await, Decl(other.ts, 3, 8))

// for-await-of
const arr = [Promise.resolve()];
>arr : Symbol(arr, Decl(other.ts, 6, 5))
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))

for await (const item of arr) {
>item : Symbol(item, Decl(other.ts, 8, 16))
>arr : Symbol(arr, Decl(other.ts, 6, 5))

item;
>item : Symbol(item, Decl(other.ts, 8, 16))
}

Loading