Skip to content

Commit 6d538b6

Browse files
committed
Moved toplevel await check to transformAwaitExpression and removed superfluous try/catch
1 parent 73c4d83 commit 6d538b6

6 files changed

Lines changed: 22 additions & 46 deletions

File tree

src/lualib/Await.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,11 @@ function __TS__AsyncAwaiter(this: void, generator: (this: void) => void) {
2424
return value instanceof __TS__Promise ? value : Promise.resolve(value);
2525
}
2626
function fulfilled(value) {
27-
try {
28-
const [success, resultOrError] = coroutine.resume(asyncCoroutine, value);
29-
if (success) {
30-
step(resultOrError);
31-
} else {
32-
reject(resultOrError);
33-
}
34-
} catch (e) {
35-
reject(e);
27+
const [success, resultOrError] = coroutine.resume(asyncCoroutine, value);
28+
if (success) {
29+
step(resultOrError);
30+
} else {
31+
reject(resultOrError);
3632
}
3733
}
3834
function step(result: unknown) {

src/transformation/utils/diagnostics.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,6 @@ export const notAllowedOptionalAssignment = createErrorDiagnosticFactory(
148148
"The left-hand side of an assignment expression may not be an optional property access."
149149
);
150150

151-
export const notAllowedTopLevelAwait = createErrorDiagnosticFactory("Await can only be used inside async functions.");
151+
export const awaitMustBeInAsyncFunction = createErrorDiagnosticFactory(
152+
"Await can only be used inside async functions."
153+
);

src/transformation/utils/typescript/index.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -92,19 +92,3 @@ export function getFunctionTypeForCall(context: TransformationContext, node: ts.
9292
}
9393
return context.checker.getTypeFromTypeNode(typeDeclaration.type);
9494
}
95-
96-
// https://github.com/microsoft/TypeScript/blob/663b19fe4a7c4d4ddaa61aedadd28da06acd27b6/src/services/documentHighlights.ts#L435
97-
// Do not cross function/class/interface/module/type boundaries.
98-
export function traverseWithoutCrossingFunction(node: ts.Node, cb: (node: ts.Node) => void) {
99-
cb(node);
100-
if (
101-
!ts.isFunctionLike(node) &&
102-
!ts.isClassLike(node) &&
103-
!ts.isInterfaceDeclaration(node) &&
104-
!ts.isModuleDeclaration(node) &&
105-
!ts.isTypeAliasDeclaration(node) &&
106-
!ts.isTypeNode(node)
107-
) {
108-
ts.forEachChild(node, child => traverseWithoutCrossingFunction(child, cb));
109-
}
110-
}

src/transformation/visitors/async-await.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
import * as ts from "typescript";
22
import * as lua from "../../LuaAST";
33
import { FunctionVisitor, TransformationContext } from "../context";
4+
import { awaitMustBeInAsyncFunction } from "../utils/diagnostics";
45
import { importLuaLibFeature, LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
6+
import { findFirstNodeAbove } from "../utils/typescript";
57

68
export const transformAwaitExpression: FunctionVisitor<ts.AwaitExpression> = (node, context) => {
9+
// Check if await is inside an async function, it is not allowed at top level or in non-async functions
10+
const containingFunction = findFirstNodeAbove(node, ts.isFunctionLike);
11+
if (
12+
containingFunction === undefined ||
13+
!containingFunction.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword)
14+
) {
15+
context.diagnostics.push(awaitMustBeInAsyncFunction(node));
16+
}
17+
718
const expression = context.transformExpression(node.expression);
819
return transformLuaLibFunction(context, LuaLibFeature.Await, node, expression);
920
};

src/transformation/visitors/sourceFile.ts

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ import * as ts from "typescript";
22
import * as lua from "../../LuaAST";
33
import { assert } from "../../utils";
44
import { FunctionVisitor } from "../context";
5-
import { notAllowedTopLevelAwait } from "../utils/diagnostics";
65
import { createExportsIdentifier } from "../utils/lua-ast";
76
import { getUsedLuaLibFeatures } from "../utils/lualib";
87
import { performHoisting, popScope, pushScope, ScopeType } from "../utils/scope";
9-
import { hasExportEquals, traverseWithoutCrossingFunction } from "../utils/typescript";
8+
import { hasExportEquals } from "../utils/typescript";
109

1110
export const transformSourceFileNode: FunctionVisitor<ts.SourceFile> = (node, context) => {
1211
let statements: lua.Statement[] = [];
@@ -25,11 +24,6 @@ export const transformSourceFileNode: FunctionVisitor<ts.SourceFile> = (node, co
2524
} else {
2625
pushScope(context, ScopeType.File);
2726

28-
// await cannot be used outside of async functions due to it using yield which needs to be inside a coroutine
29-
for (const topLevelAwait of node.statements.filter(isTopLevelAwait)) {
30-
context.diagnostics.push(notAllowedTopLevelAwait(topLevelAwait));
31-
}
32-
3327
statements = performHoisting(context, context.transformStatements(node.statements));
3428
popScope(context);
3529

@@ -50,14 +44,3 @@ export const transformSourceFileNode: FunctionVisitor<ts.SourceFile> = (node, co
5044
const trivia = node.getFullText().match(/^#!.*\r?\n/)?.[0] ?? "";
5145
return lua.createFile(statements, getUsedLuaLibFeatures(context), trivia, node);
5246
};
53-
54-
function isTopLevelAwait(statement: ts.Statement) {
55-
// Check if expression contains an await child, without going into function declarations
56-
let containsAwait = false;
57-
traverseWithoutCrossingFunction(statement, node => {
58-
if (ts.isAwaitExpression(node)) {
59-
containsAwait = true;
60-
}
61-
});
62-
return containsAwait;
63-
}

test/unit/builtins/async-await.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ModuleKind, ScriptTarget } from "typescript";
2-
import { notAllowedTopLevelAwait } from "../../../src/transformation/utils/diagnostics";
2+
import { awaitMustBeInAsyncFunction } from "../../../src/transformation/utils/diagnostics";
33
import * as util from "../../util";
44

55
const promiseTestLib = `
@@ -331,5 +331,5 @@ test.each([
331331
export {} // Required to make TS happy, cannot await without import/exports
332332
`
333333
.setOptions({ module: ModuleKind.ESNext, target: ScriptTarget.ES2017 })
334-
.expectToHaveDiagnostics([notAllowedTopLevelAwait.code]);
334+
.expectToHaveDiagnostics([awaitMustBeInAsyncFunction.code]);
335335
});

0 commit comments

Comments
 (0)