Skip to content

Commit

Permalink
Add top-level await for esnext and system modules
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Dec 21, 2019
1 parent 2f0d07c commit e3e8b0c
Show file tree
Hide file tree
Showing 28 changed files with 274 additions and 49 deletions.
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: Top-level 'await' is only allowed when the '--module' flag is set to '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: Top-level 'await' is only allowed when the '--module' flag is set to 'esnext' or 'system' and '--target' is 'es2017' or higher.
Loading

0 comments on commit e3e8b0c

Please sign in to comment.