Skip to content
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

Add top-level await for esnext and system modules #35813

Merged
merged 1 commit into from
Dec 22, 2019
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
7 changes: 5 additions & 2 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3898,10 +3898,13 @@ namespace ts {

switch (kind) {
case SyntaxKind.AsyncKeyword:
case SyntaxKind.AwaitExpression:
// async/await is ES2017 syntax, but may be ES2018 syntax (for async generators)
// async is ES2017 syntax, but may be ES2018 syntax (for async generators)
transformFlags |= TransformFlags.AssertES2018 | TransformFlags.AssertES2017;
break;
case SyntaxKind.AwaitExpression:
// await is ES2017 syntax, but may be ES2018 syntax (for async generators)
transformFlags |= TransformFlags.AssertES2018 | TransformFlags.AssertES2017 | TransformFlags.ContainsAwait;
break;

case SyntaxKind.TypeAssertionExpression:
case SyntaxKind.AsExpression:
Expand Down
40 changes: 30 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26422,21 +26422,41 @@ namespace ts {
return undefinedWideningType;
}

function isTopLevelAwait(node: AwaitExpression) {
const container = getThisContainer(node, /*includeArrowFunctions*/ true);
return isSourceFile(container);
}

function checkAwaitExpression(node: AwaitExpression): Type {
// Grammar checking
if (produceDiagnostics) {
if (!(node.flags & NodeFlags.AwaitContext)) {
// use of 'await' in non-async function
const sourceFile = getSourceFileOfNode(node);
if (!hasParseDiagnostics(sourceFile)) {
const span = getSpanOfTokenAtPosition(sourceFile, node.pos);
const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.await_expression_is_only_allowed_within_an_async_function);
const func = getContainingFunction(node);
if (func && func.kind !== SyntaxKind.Constructor && (getFunctionFlags(func) & FunctionFlags.Async) === 0) {
const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async);
addRelatedInfo(diagnostic, relatedInfo);
if (isTopLevelAwait(node)) {
const sourceFile = getSourceFileOfNode(node);
if ((moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System) ||
languageVersion < ScriptTarget.ES2017 ||
!isEffectiveExternalModule(sourceFile, compilerOptions)) {
if (!hasParseDiagnostics(sourceFile)) {
const span = getSpanOfTokenAtPosition(sourceFile, node.pos);
const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length,
Diagnostics.await_outside_of_an_async_function_is_only_allowed_at_the_top_level_of_a_module_when_module_is_esnext_or_system_and_target_is_es2017_or_higher);
diagnostics.add(diagnostic);
}
}
}
else {
// use of 'await' in non-async function
const sourceFile = getSourceFileOfNode(node);
if (!hasParseDiagnostics(sourceFile)) {
const span = getSpanOfTokenAtPosition(sourceFile, node.pos);
const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.await_expression_is_only_allowed_within_an_async_function);
const func = getContainingFunction(node);
if (func && func.kind !== SyntaxKind.Constructor && (getFunctionFlags(func) & FunctionFlags.Async) === 0) {
const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async);
addRelatedInfo(diagnostic, relatedInfo);
}
diagnostics.add(diagnostic);
}
diagnostics.add(diagnostic);
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,10 @@
"category": "Error",
"code": 1360
},
"'await' outside of an async function is only allowed at the top level of a module when '--module' is 'esnext' or 'system' and '--target' is 'es2017' or higher.": {
"category": "Error",
"code": 1361
},

"The types of '{0}' are incompatible between these types.": {
"category": "Error",
Expand Down
53 changes: 40 additions & 13 deletions src/compiler/transformers/es2017.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ namespace ts {
AsyncMethodsWithSuper = 1 << 0
}

const enum ContextFlags {
NonTopLevel = 1 << 0,
HasLexicalThis = 1 << 1
}

export function transformES2017(context: TransformationContext) {
const {
resumeLexicalEnvironment,
Expand Down Expand Up @@ -41,7 +46,7 @@ namespace ts {
/** A set of node IDs for generated super accessors (variable statements). */
const substitutedSuperAccessors: boolean[] = [];

let topLevel: boolean;
let contextFlags: ContextFlags = 0;

// Save the previous transformation hooks.
const previousOnEmitNode = context.onEmitNode;
Expand All @@ -58,17 +63,35 @@ namespace ts {
return node;
}

topLevel = isEffectiveStrictModeSourceFile(node, compilerOptions);
setContextFlag(ContextFlags.NonTopLevel, false);
setContextFlag(ContextFlags.HasLexicalThis, !isEffectiveStrictModeSourceFile(node, compilerOptions));
const visited = visitEachChild(node, visitor, context);
addEmitHelpers(visited, context.readEmitHelpers());
return visited;
}

function doOutsideOfTopLevel<T, U>(cb: (value: T) => U, value: T) {
if (topLevel) {
topLevel = false;
function setContextFlag(flag: ContextFlags, val: boolean) {
contextFlags = val ? contextFlags | flag : contextFlags & ~flag;
}

function inContext(flags: ContextFlags) {
return (contextFlags & flags) !== 0;
}

function inTopLevelContext() {
return !inContext(ContextFlags.NonTopLevel);
}

function inHasLexicalThisContext() {
return inContext(ContextFlags.HasLexicalThis);
}

function doWithContext<T, U>(flags: ContextFlags, cb: (value: T) => U, value: T) {
const contextFlagsToSet = flags & ~contextFlags;
if (contextFlagsToSet) {
setContextFlag(contextFlagsToSet, /*val*/ true);
const result = cb(value);
topLevel = true;
setContextFlag(contextFlagsToSet, /*val*/ false);
return result;
}
return cb(value);
Expand All @@ -91,16 +114,16 @@ namespace ts {
return visitAwaitExpression(<AwaitExpression>node);

case SyntaxKind.MethodDeclaration:
return doOutsideOfTopLevel(visitMethodDeclaration, <MethodDeclaration>node);
return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitMethodDeclaration, <MethodDeclaration>node);

case SyntaxKind.FunctionDeclaration:
return doOutsideOfTopLevel(visitFunctionDeclaration, <FunctionDeclaration>node);
return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitFunctionDeclaration, <FunctionDeclaration>node);

case SyntaxKind.FunctionExpression:
return doOutsideOfTopLevel(visitFunctionExpression, <FunctionExpression>node);
return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitFunctionExpression, <FunctionExpression>node);

case SyntaxKind.ArrowFunction:
return visitArrowFunction(<ArrowFunction>node);
return doWithContext(ContextFlags.NonTopLevel, visitArrowFunction, <ArrowFunction>node);

case SyntaxKind.PropertyAccessExpression:
if (capturedSuperProperties && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.SuperKeyword) {
Expand All @@ -119,7 +142,7 @@ namespace ts {
case SyntaxKind.Constructor:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
return doOutsideOfTopLevel(visitDefault, node);
return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitDefault, node);

default:
return visitEachChild(node, visitor, context);
Expand Down Expand Up @@ -237,6 +260,10 @@ namespace ts {
* @param node The node to visit.
*/
function visitAwaitExpression(node: AwaitExpression): Expression {
// do not downlevel a top-level await as it is module syntax...
if (inTopLevelContext()) {
return visitEachChild(node, visitor, context);
}
return setOriginalNode(
setTextRange(
createYield(
Expand Down Expand Up @@ -457,7 +484,7 @@ namespace ts {
createReturn(
createAwaiterHelper(
context,
!topLevel,
inHasLexicalThisContext(),
hasLexicalArguments,
promiseConstructor,
transformAsyncFunctionBodyWorker(<Block>node.body, statementOffset)
Expand Down Expand Up @@ -498,7 +525,7 @@ namespace ts {
else {
const expression = createAwaiterHelper(
context,
!topLevel,
inHasLexicalThisContext(),
hasLexicalArguments,
promiseConstructor,
transformAsyncFunctionBodyWorker(node.body!)
Expand Down
28 changes: 14 additions & 14 deletions src/compiler/transformers/es2018.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace ts {
let enabledSubstitutions: ESNextSubstitutionFlags;
let enclosingFunctionFlags: FunctionFlags;
let enclosingSuperContainerFlags: NodeCheckFlags = 0;
let topLevel: boolean;
let hasLexicalThis: boolean;

/** Keeps track of property names accessed on super (`super.x`) within async functions. */
let capturedSuperProperties: UnderscoreEscapedMap<true>;
Expand All @@ -43,7 +43,7 @@ namespace ts {
}

exportedVariableStatement = false;
topLevel = isEffectiveStrictModeSourceFile(node, compilerOptions);
hasLexicalThis = !isEffectiveStrictModeSourceFile(node, compilerOptions);
const visited = visitEachChild(node, visitor, context);
addEmitHelpers(visited, context.readEmitHelpers());
return visited;
Expand All @@ -64,11 +64,11 @@ namespace ts {
return node;
}

function doOutsideOfTopLevel<T, U>(cb: (value: T) => U, value: T) {
if (topLevel) {
topLevel = false;
function doWithLexicalThis<T, U>(cb: (value: T) => U, value: T) {
if (!hasLexicalThis) {
hasLexicalThis = true;
const result = cb(value);
topLevel = true;
hasLexicalThis = false;
return result;
}
return cb(value);
Expand Down Expand Up @@ -108,17 +108,17 @@ namespace ts {
case SyntaxKind.VoidExpression:
return visitVoidExpression(node as VoidExpression);
case SyntaxKind.Constructor:
return doOutsideOfTopLevel(visitConstructorDeclaration, node as ConstructorDeclaration);
return doWithLexicalThis(visitConstructorDeclaration, node as ConstructorDeclaration);
case SyntaxKind.MethodDeclaration:
return doOutsideOfTopLevel(visitMethodDeclaration, node as MethodDeclaration);
return doWithLexicalThis(visitMethodDeclaration, node as MethodDeclaration);
case SyntaxKind.GetAccessor:
return doOutsideOfTopLevel(visitGetAccessorDeclaration, node as GetAccessorDeclaration);
return doWithLexicalThis(visitGetAccessorDeclaration, node as GetAccessorDeclaration);
case SyntaxKind.SetAccessor:
return doOutsideOfTopLevel(visitSetAccessorDeclaration, node as SetAccessorDeclaration);
return doWithLexicalThis(visitSetAccessorDeclaration, node as SetAccessorDeclaration);
case SyntaxKind.FunctionDeclaration:
return doOutsideOfTopLevel(visitFunctionDeclaration, node as FunctionDeclaration);
return doWithLexicalThis(visitFunctionDeclaration, node as FunctionDeclaration);
case SyntaxKind.FunctionExpression:
return doOutsideOfTopLevel(visitFunctionExpression, node as FunctionExpression);
return doWithLexicalThis(visitFunctionExpression, node as FunctionExpression);
case SyntaxKind.ArrowFunction:
return visitArrowFunction(node as ArrowFunction);
case SyntaxKind.Parameter:
Expand All @@ -139,7 +139,7 @@ namespace ts {
return visitEachChild(node, visitor, context);
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
return doOutsideOfTopLevel(visitDefault, node);
return doWithLexicalThis(visitDefault, node);
default:
return visitEachChild(node, visitor, context);
}
Expand Down Expand Up @@ -774,7 +774,7 @@ namespace ts {
visitLexicalEnvironment(node.body!.statements, visitor, context, statementOffset)
)
),
!topLevel
hasLexicalThis
)
);

Expand Down
5 changes: 4 additions & 1 deletion src/compiler/transformers/module/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,13 +262,16 @@ namespace ts {
insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment());

const exportStarFunction = addExportStarIfNeeded(statements)!; // TODO: GH#18217
const modifiers = node.transformFlags & TransformFlags.ContainsAwait ?
createModifiersFromModifierFlags(ModifierFlags.Async) :
undefined;
const moduleObject = createObjectLiteral([
createPropertyAssignment("setters",
createSettersArray(exportStarFunction, dependencyGroups)
),
createPropertyAssignment("execute",
createFunctionExpression(
/*modifiers*/ undefined,
modifiers,
/*asteriskToken*/ undefined,
/*name*/ undefined,
/*typeParameters*/ undefined,
Expand Down
15 changes: 8 additions & 7 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5599,9 +5599,10 @@ namespace ts {
ContainsBlockScopedBinding = 1 << 15,
ContainsBindingPattern = 1 << 16,
ContainsYield = 1 << 17,
ContainsHoistedDeclarationOrCompletion = 1 << 18,
ContainsDynamicImport = 1 << 19,
ContainsClassFields = 1 << 20,
ContainsAwait = 1 << 18,
ContainsHoistedDeclarationOrCompletion = 1 << 19,
ContainsDynamicImport = 1 << 20,
ContainsClassFields = 1 << 21,

// Please leave this as 1 << 29.
// It is the maximum bit we can set before we outgrow the size of a v8 small integer (SMI) on an x86 system.
Expand All @@ -5627,10 +5628,10 @@ namespace ts {
OuterExpressionExcludes = HasComputedFlags,
PropertyAccessExcludes = OuterExpressionExcludes,
NodeExcludes = PropertyAccessExcludes,
ArrowFunctionExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread,
FunctionExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread,
ConstructorExcludes = NodeExcludes | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread,
MethodOrAccessorExcludes = NodeExcludes | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread,
ArrowFunctionExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsBlockScopedBinding | ContainsYield | ContainsAwait | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread,
FunctionExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsAwait | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread,
ConstructorExcludes = NodeExcludes | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsAwait | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread,
MethodOrAccessorExcludes = NodeExcludes | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsAwait | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread,
PropertyExcludes = NodeExcludes | ContainsLexicalThis,
ClassExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsComputedPropertyName,
ModuleExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsHoistedDeclarationOrCompletion,
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 @@ -13,7 +13,7 @@ tests/cases/compiler/awaitInNonAsyncFunction.ts(31,5): error TS1308: 'await' exp
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' expression is only allowed within an async function.
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(40,1): error TS1308: 'await' expression is only allowed within an async function.
tests/cases/compiler/awaitInNonAsyncFunction.ts(40,1): error TS1361: 'await' outside of an async function is only allowed at the top level of a module when '--module' is 'esnext' or 'system' and '--target' is 'es2017' or higher.


==== tests/cases/compiler/awaitInNonAsyncFunction.ts (16 errors) ====
Expand Down Expand Up @@ -100,4 +100,4 @@ tests/cases/compiler/awaitInNonAsyncFunction.ts(40,1): error TS1308: 'await' exp
!!! error TS1103: A 'for-await-of' statement is only allowed within an async function or async generator.
await null;
~~~~~
!!! error TS1308: 'await' expression is only allowed within an async function.
!!! error TS1361: 'await' outside of an async function is only allowed at the top level of a module when '--module' is 'esnext' or 'system' and '--target' is 'es2017' or higher.
Loading