Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ac74c29
Initial lualib promise class implementation
Perryvw Jul 5, 2021
7ca3030
First promise tests
Perryvw Jul 8, 2021
d388154
More promise tests
Perryvw Jul 11, 2021
46b82c1
Promise class implementation
Perryvw Jul 12, 2021
f0b325d
Implemented Promise.all
Perryvw Jul 14, 2021
d2db835
Promise.any
Perryvw Jul 16, 2021
7f662e6
Promise.race
Perryvw Jul 17, 2021
c0bbb42
Promise.allSettled
Perryvw Jul 17, 2021
fd8108f
Merge branch 'master' into promises
Perryvw Jul 17, 2021
8f800b9
fix prettier
Perryvw Jul 17, 2021
47f179e
Add promise example usage test
Perryvw Jul 20, 2021
5931c3a
Added missing lualib dependencies for PromiseConstructor functions
Perryvw Jul 20, 2021
95c5fe3
Immediately call then/catch/finally callbacks on promises that are al…
Perryvw Jul 22, 2021
cc74c3a
Transform all references to Promise to __TS__Promise
Perryvw Jul 30, 2021
3fad722
PR feedback
Perryvw Aug 14, 2021
40efc17
Merge branch 'master' into promises
Perryvw Aug 14, 2021
20660f0
Removed incorrect asyncs
Perryvw Aug 17, 2021
1b74429
Add test for direct chaining
Perryvw Aug 17, 2021
780abba
Add test for finally and correct wrong behavior it caught
Perryvw Aug 17, 2021
c0e82ef
Added test throwing in parallel and chained then onFulfilleds
Perryvw Aug 17, 2021
cca6949
Fixed pull request link in ArrayIsArray lualib comment
Perryvw Aug 17, 2021
e623271
Initial async await
Perryvw Jul 25, 2021
83dee1e
Disallow await in top-level scope
Perryvw Aug 14, 2021
c4310f9
Add await rejection test
Perryvw Aug 14, 2021
d43b331
Merge branch 'master' into async-await
Perryvw Aug 18, 2021
fa508b4
Give await the correct lualib dependencies
Perryvw Aug 18, 2021
eb04520
use coroutine.status instead of lastData
Perryvw Aug 18, 2021
b487ff1
Better top level await check
Perryvw Aug 18, 2021
73c4d83
Add tests for async lambdas and throws in async functions
Perryvw Aug 18, 2021
6d538b6
Moved toplevel await check to transformAwaitExpression and removed su…
Perryvw Aug 20, 2021
01a0fcb
fix for vararg access in async functions (#1096)
tomblind Aug 20, 2021
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
2 changes: 2 additions & 0 deletions src/LuaLib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export enum LuaLibFeature {
ArrayFlat = "ArrayFlat",
ArrayFlatMap = "ArrayFlatMap",
ArraySetLength = "ArraySetLength",
Await = "Await",
Class = "Class",
ClassExtends = "ClassExtends",
CloneDescriptor = "CloneDescriptor",
Expand Down Expand Up @@ -101,6 +102,7 @@ const luaLibDependencies: Partial<Record<LuaLibFeature, LuaLibFeature[]>> = {
ArrayConcat: [LuaLibFeature.ArrayIsArray],
ArrayFlat: [LuaLibFeature.ArrayConcat, LuaLibFeature.ArrayIsArray],
ArrayFlatMap: [LuaLibFeature.ArrayConcat, LuaLibFeature.ArrayIsArray],
Await: [LuaLibFeature.InstanceOf, LuaLibFeature.New],
Decorate: [LuaLibFeature.ObjectGetOwnPropertyDescriptor, LuaLibFeature.SetDescriptor, LuaLibFeature.ObjectAssign],
DelegatedYield: [LuaLibFeature.StringAccess],
Delete: [LuaLibFeature.ObjectGetOwnPropertyDescriptors],
Expand Down
52 changes: 52 additions & 0 deletions src/lualib/Await.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// The following is a translation of the TypeScript async awaiter which uses generators and yields.
// For Lua we use coroutines instead.
//
// Source:
//
// var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
// function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
// return new (P || (P = Promise))(function (resolve, reject) {
// function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
// function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
// function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
// step((generator = generator.apply(thisArg, _arguments || [])).next());
// });
// };
//

// eslint-disable-next-line @typescript-eslint/promise-function-async
function __TS__AsyncAwaiter(this: void, generator: (this: void) => void) {
return new Promise((resolve, reject) => {
const asyncCoroutine = coroutine.create(generator);

// eslint-disable-next-line @typescript-eslint/promise-function-async
function adopt(value: unknown) {
return value instanceof __TS__Promise ? value : Promise.resolve(value);
}
function fulfilled(value) {
const [success, resultOrError] = coroutine.resume(asyncCoroutine, value);
if (success) {
step(resultOrError);
} else {
reject(resultOrError);
}
}
function step(result: unknown) {
if (coroutine.status(asyncCoroutine) === "dead") {
resolve(result);
} else {
adopt(result).then(fulfilled, reason => reject(reason));
}
}
const [success, resultOrError] = coroutine.resume(asyncCoroutine);
if (success) {
step(resultOrError);
} else {
reject(resultOrError);
}
});
}

function __TS__Await(this: void, thing: unknown) {
return coroutine.yield(thing);
}
4 changes: 4 additions & 0 deletions src/transformation/utils/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,7 @@ export const annotationDeprecated = createWarningDiagnosticFactory(
export const notAllowedOptionalAssignment = createErrorDiagnosticFactory(
"The left-hand side of an assignment expression may not be an optional property access."
);

export const awaitMustBeInAsyncFunction = createErrorDiagnosticFactory(
"Await can only be used inside async functions."
);
36 changes: 36 additions & 0 deletions src/transformation/visitors/async-await.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as ts from "typescript";
import * as lua from "../../LuaAST";
import { FunctionVisitor, TransformationContext } from "../context";
import { awaitMustBeInAsyncFunction } from "../utils/diagnostics";
import { importLuaLibFeature, LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
import { findFirstNodeAbove } from "../utils/typescript";

export const transformAwaitExpression: FunctionVisitor<ts.AwaitExpression> = (node, context) => {
// Check if await is inside an async function, it is not allowed at top level or in non-async functions
const containingFunction = findFirstNodeAbove(node, ts.isFunctionLike);
if (
containingFunction === undefined ||
!containingFunction.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword)
) {
context.diagnostics.push(awaitMustBeInAsyncFunction(node));
}

const expression = context.transformExpression(node.expression);
return transformLuaLibFunction(context, LuaLibFeature.Await, node, expression);
};

export function isAsyncFunction(declaration: ts.FunctionLikeDeclaration): boolean {
return declaration.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;
}

export function wrapInAsyncAwaiter(context: TransformationContext, statements: lua.Statement[]): lua.Statement[] {
importLuaLibFeature(context, LuaLibFeature.Await);

return [
lua.createReturnStatement([
lua.createCallExpression(lua.createIdentifier("__TS__AsyncAwaiter"), [
lua.createFunctionExpression(lua.createBlock(statements)),
]),
]),
];
}
7 changes: 6 additions & 1 deletion src/transformation/visitors/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "../utils/lua-ast";
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
import { peekScope, performHoisting, popScope, pushScope, Scope, ScopeType } from "../utils/scope";
import { isAsyncFunction, wrapInAsyncAwaiter } from "./async-await";
import { transformIdentifier } from "./identifier";
import { transformExpressionBodyToReturnStatement } from "./return";
import { transformBindingPattern } from "./variable-declaration";
Expand Down Expand Up @@ -114,7 +115,10 @@ export function transformFunctionBody(
): [lua.Statement[], Scope] {
const scope = pushScope(context, ScopeType.Function);
scope.node = node;
const bodyStatements = transformFunctionBodyContent(context, body);
let bodyStatements = transformFunctionBodyContent(context, body);
if (node && isAsyncFunction(node)) {
bodyStatements = wrapInAsyncAwaiter(context, bodyStatements);
}
const headerStatements = transformFunctionBodyHeader(context, scope, parameters, spreadIdentifier);
popScope(context);
return [[...headerStatements, ...bodyStatements], scope];
Expand Down Expand Up @@ -195,6 +199,7 @@ export function transformFunctionToExpression(
spreadIdentifier,
node
);

const functionExpression = lua.createFunctionExpression(
lua.createBlock(transformedBody),
paramNames,
Expand Down
2 changes: 2 additions & 0 deletions src/transformation/visitors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { typescriptVisitors } from "./typescript";
import { transformPostfixUnaryExpression, transformPrefixUnaryExpression } from "./unary-expression";
import { transformVariableStatement } from "./variable-declaration";
import { jsxVisitors } from "./jsx/jsx";
import { transformAwaitExpression } from "./async-await";

const transformEmptyStatement: FunctionVisitor<ts.EmptyStatement> = () => undefined;
const transformParenthesizedExpression: FunctionVisitor<ts.ParenthesizedExpression> = (node, context) =>
Expand All @@ -51,6 +52,7 @@ export const standardVisitors: Visitors = {
...typescriptVisitors,
...jsxVisitors,
[ts.SyntaxKind.ArrowFunction]: transformFunctionLikeDeclaration,
[ts.SyntaxKind.AwaitExpression]: transformAwaitExpression,
[ts.SyntaxKind.BinaryExpression]: transformBinaryExpression,
[ts.SyntaxKind.Block]: transformBlock,
[ts.SyntaxKind.BreakStatement]: transformBreakStatement,
Expand Down
1 change: 1 addition & 0 deletions src/transformation/visitors/sourceFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const transformSourceFileNode: FunctionVisitor<ts.SourceFile> = (node, co
}
} else {
pushScope(context, ScopeType.File);

statements = performHoisting(context, context.transformStatements(node.statements));
popScope(context);

Expand Down
5 changes: 5 additions & 0 deletions src/transformation/visitors/spread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ export function isOptimizedVarArgSpread(context: TransformationContext, symbol:
return false;
}

// Scope cannot be an async function
if (scope.node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword)) {
return false;
}

// Identifier must be a vararg in the local function scope's parameters
const isSpreadParameter = (p: ts.ParameterDeclaration) =>
p.dotDotDotToken && ts.isIdentifier(p.name) && context.checker.getSymbolAtLocation(p.name) === symbol;
Expand Down
Loading