Skip to content

Commit d8e6546

Browse files
authored
Preceding Statements (#1116)
* working on preceding statements implementation * fixed issues from tests and started adding new tests (which don't pass yet) * fixed issues with short-circuit compound operator expressions * execution order tests * fixes for remaining broken tests * switch test (currently broken) * refactor to expression list transformation * more refactoring and fixes for expression lists * refactored object literals a bit * refactorings, including removal of old iife stuff * snapshot updates * cleanup, fixes, and added some original nodes for source maps * comment update * fixed ifelse statements * more things to fix * working on fixes to assignments and creating more tests (most of which are broken) * more fixes. more broken things. * lots of fixes to call expressions and lots of new broken tests * more execution order fixes, more tests * working on fixes for destructuring exec order * additional object destructuring test * fixes for object destructuring * fixed switch statements * refactored expression list handling - removed optimization that broke certain situations - optimized out extra temp in common unary operator cases - removed duplicate functionality between transformOrderedExpressions and transformExpressionList * optimized out temps in many situations * fixed execution order of computed property names in object literals * a little refactoring * removed bad optimization and refactored call stuff a bit * added SparseArray lib functions to handle complex expression lists and fixed/refactored a few more things * addPrecedingStatements takes a single argument now * a little cleanup * fix for indirect property assignments * refactoring to expression-lists * fixes and refactors to tests * improvements to temp variable naming * treating temps like consts and more temp name adjustments * snapshot update * a couple of small refactorings * refactoring based on comments & suggestions * change void expressions to use preceding statements * better comments and variable names for preceding statement handling of loop conditions * addressed feedback * reverted change that didn't seem necessary * updated transformBinaryOperation to handle preceding statements * reverted switch handling to using lua expressions instead of manufactured ts expressions and handled preceding statements manually * constified compound assignment code * renamed createShortCircuitBinaryExpression * Merge from master Co-authored-by: Tom <tomblind@users.noreply.github.com>
1 parent 028e3f5 commit d8e6546

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2121
-729
lines changed

src/LuaAST.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,18 @@ export function createStringLiteral(value: string, tsOriginal?: ts.Node): String
566566
return expression;
567567
}
568568

569+
export function isLiteral(
570+
node: Node
571+
): node is NilLiteral | DotsLiteral | BooleanLiteral | NumericLiteral | StringLiteral {
572+
return (
573+
isNilLiteral(node) ||
574+
isDotsLiteral(node) ||
575+
isBooleanLiteral(node) ||
576+
isNumericLiteral(node) ||
577+
isStringLiteral(node)
578+
);
579+
}
580+
569581
export enum FunctionExpressionFlags {
570582
None = 1 << 0,
571583
Inline = 1 << 1, // Keep function body on same line

src/LuaLib.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ export enum LuaLibFeature {
7070
PromiseRace = "PromiseRace",
7171
Set = "Set",
7272
SetDescriptor = "SetDescriptor",
73+
SparseArrayNew = "SparseArrayNew",
74+
SparseArrayPush = "SparseArrayPush",
75+
SparseArraySpread = "SparseArraySpread",
7376
WeakMap = "WeakMap",
7477
WeakSet = "WeakSet",
7578
SourceMapTraceBack = "SourceMapTraceBack",

src/lualib/SparseArrayNew.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
type __TS__SparseArray<T> = T[] & { sparseLength: number };
2+
3+
function __TS__SparseArrayNew<T>(this: void, ...args: T[]): __TS__SparseArray<T> {
4+
const sparseArray = [...args] as __TS__SparseArray<T>;
5+
// select("#", ...) counts the number of args passed, including nils.
6+
// Note that we're depending on vararg optimization to occur here.
7+
sparseArray.sparseLength = select("#", ...args);
8+
return sparseArray;
9+
}

src/lualib/SparseArrayPush.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
function __TS__SparseArrayPush<T>(this: void, sparseArray: __TS__SparseArray<T>, ...args: T[]): void {
2+
const argsLen = select("#", ...args);
3+
const listLen = sparseArray.sparseLength;
4+
for (const i of $range(1, argsLen)) {
5+
sparseArray[listLen + i - 1] = args[i - 1];
6+
}
7+
sparseArray.sparseLength = listLen + argsLen;
8+
}

src/lualib/SparseArraySpread.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
function __TS__SparseArraySpread<T>(this: void, sparseArray: __TS__SparseArray<T>): LuaMultiReturn<T[]> {
2+
const _unpack = unpack ?? table.unpack;
3+
return _unpack(sparseArray, 1, sparseArray.sparseLength);
4+
}

src/transformation/builtins/array.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as lua from "../../LuaAST";
33
import { TransformationContext } from "../context";
44
import { unsupportedProperty } from "../utils/diagnostics";
55
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
6-
import { PropertyCallExpression, transformArguments } from "../visitors/call";
6+
import { PropertyCallExpression, transformArguments, transformCallAndArguments } from "../visitors/call";
77
import { isStringType, isNumberType } from "../utils/typescript";
88

99
export function transformArrayConstructorCall(
@@ -29,8 +29,7 @@ export function transformArrayPrototypeCall(
2929
): lua.CallExpression | undefined {
3030
const expression = node.expression;
3131
const signature = context.checker.getResolvedSignature(node);
32-
const params = transformArguments(context, node.arguments, signature);
33-
const caller = context.transformExpression(expression.expression);
32+
const [caller, params] = transformCallAndArguments(context, expression.expression, node.arguments, signature);
3433

3534
const expressionName = expression.name.text;
3635
switch (expressionName) {

src/transformation/builtins/function.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { unsupportedForTarget, unsupportedProperty, unsupportedSelfFunctionConve
66
import { ContextType, getFunctionContextType } from "../utils/function-context";
77
import { createUnpackCall } from "../utils/lua-ast";
88
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
9-
import { PropertyCallExpression, transformArguments } from "../visitors/call";
9+
import { PropertyCallExpression, transformCallAndArguments } from "../visitors/call";
1010

1111
export function transformFunctionPrototypeCall(
1212
context: TransformationContext,
@@ -19,8 +19,7 @@ export function transformFunctionPrototypeCall(
1919
}
2020

2121
const signature = context.checker.getResolvedSignature(node);
22-
const params = transformArguments(context, node.arguments, signature);
23-
const caller = context.transformExpression(expression.expression);
22+
const [caller, params] = transformCallAndArguments(context, expression.expression, node.arguments, signature);
2423
const expressionName = expression.name.text;
2524
switch (expressionName) {
2625
case "apply":

src/transformation/builtins/string.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { TransformationContext } from "../context";
44
import { unsupportedProperty } from "../utils/diagnostics";
55
import { addToNumericExpression, createNaN, getNumberLiteralValue } from "../utils/lua-ast";
66
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
7-
import { PropertyCallExpression, transformArguments } from "../visitors/call";
7+
import { PropertyCallExpression, transformArguments, transformCallAndArguments } from "../visitors/call";
88

99
function createStringCall(methodName: string, tsOriginal: ts.Node, ...params: lua.Expression[]): lua.CallExpression {
1010
const stringIdentifier = lua.createIdentifier("string");
@@ -21,8 +21,7 @@ export function transformStringPrototypeCall(
2121
): lua.Expression | undefined {
2222
const expression = node.expression;
2323
const signature = context.checker.getResolvedSignature(node);
24-
const params = transformArguments(context, node.arguments, signature);
25-
const caller = context.transformExpression(expression.expression);
24+
const [caller, params] = transformCallAndArguments(context, expression.expression, node.arguments, signature);
2625

2726
const expressionName = expression.name.text;
2827
switch (expressionName) {

src/transformation/context/context.ts

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import * as ts from "typescript";
22
import { CompilerOptions, LuaTarget } from "../../CompilerOptions";
33
import * as lua from "../../LuaAST";
4-
import { castArray } from "../../utils";
4+
import { assert, castArray } from "../../utils";
55
import { unsupportedNodeKind } from "../utils/diagnostics";
66
import { unwrapVisitorResult } from "../utils/lua-ast";
7+
import { createSafeName } from "../utils/safe-names";
78
import { ExpressionLikeNode, ObjectVisitor, StatementLikeNode, VisitorMap } from "./visitors";
89

10+
export const tempSymbolId = -1 as lua.SymbolId;
11+
912
export interface AllAccessorDeclarations {
1013
firstAccessor: ts.AccessorDeclaration;
1114
secondAccessor: ts.AccessorDeclaration | undefined;
@@ -29,6 +32,7 @@ export class TransformationContext {
2932
public readonly diagnostics: ts.Diagnostic[] = [];
3033
public readonly checker: DiagnosticsProducingTypeChecker = this.program.getDiagnosticsProducingTypeChecker();
3134
public readonly resolver: EmitResolver;
35+
public readonly precedingStatementsStack: lua.Statement[][] = [];
3236

3337
public readonly options: CompilerOptions = this.program.getCompilerOptions();
3438
public readonly luaTarget = this.options.luaTarget ?? LuaTarget.Universal;
@@ -46,6 +50,7 @@ export class TransformationContext {
4650
}
4751

4852
private currentNodeVisitors: Array<ObjectVisitor<ts.Node>> = [];
53+
private nextTempId = 0;
4954

5055
public transformNode(node: ts.Node): lua.Node[];
5156
/** @internal */
@@ -104,10 +109,107 @@ export class TransformationContext {
104109
}
105110

106111
public transformStatements(node: StatementLikeNode | readonly StatementLikeNode[]): lua.Statement[] {
107-
return castArray(node).flatMap(n => this.transformNode(n) as lua.Statement[]);
112+
return castArray(node).flatMap(n => {
113+
this.pushPrecedingStatements();
114+
const statements = this.transformNode(n) as lua.Statement[];
115+
statements.unshift(...this.popPrecedingStatements());
116+
return statements;
117+
});
108118
}
109119

110120
public superTransformStatements(node: StatementLikeNode | readonly StatementLikeNode[]): lua.Statement[] {
111-
return castArray(node).flatMap(n => this.superTransformNode(n) as lua.Statement[]);
121+
return castArray(node).flatMap(n => {
122+
this.pushPrecedingStatements();
123+
const statements = this.superTransformNode(n) as lua.Statement[];
124+
statements.unshift(...this.popPrecedingStatements());
125+
return statements;
126+
});
127+
}
128+
129+
public pushPrecedingStatements() {
130+
this.precedingStatementsStack.push([]);
131+
}
132+
133+
public popPrecedingStatements() {
134+
const precedingStatements = this.precedingStatementsStack.pop();
135+
assert(precedingStatements);
136+
return precedingStatements;
137+
}
138+
139+
public addPrecedingStatements(statements: lua.Statement | lua.Statement[]) {
140+
const precedingStatements = this.precedingStatementsStack[this.precedingStatementsStack.length - 1];
141+
assert(precedingStatements);
142+
if (!Array.isArray(statements)) {
143+
statements = [statements];
144+
}
145+
precedingStatements.push(...statements);
146+
}
147+
148+
public prependPrecedingStatements(statements: lua.Statement | lua.Statement[]) {
149+
const precedingStatements = this.precedingStatementsStack[this.precedingStatementsStack.length - 1];
150+
assert(precedingStatements);
151+
if (!Array.isArray(statements)) {
152+
statements = [statements];
153+
}
154+
precedingStatements.unshift(...statements);
155+
}
156+
157+
public createTempName(prefix = "temp") {
158+
prefix = prefix.replace(/^_*/, ""); // Strip leading underscores because createSafeName will add them again
159+
return createSafeName(`${prefix}_${this.nextTempId++}`);
160+
}
161+
162+
private getTempNameForLuaExpression(expression: lua.Expression): string | undefined {
163+
if (lua.isStringLiteral(expression)) {
164+
return expression.value;
165+
} else if (lua.isNumericLiteral(expression)) {
166+
return `_${expression.value.toString()}`;
167+
} else if (lua.isIdentifier(expression)) {
168+
return expression.text;
169+
} else if (lua.isCallExpression(expression)) {
170+
const name = this.getTempNameForLuaExpression(expression.expression);
171+
if (name) {
172+
return `${name}_result`;
173+
}
174+
} else if (lua.isTableIndexExpression(expression)) {
175+
const tableName = this.getTempNameForLuaExpression(expression.table);
176+
const indexName = this.getTempNameForLuaExpression(expression.index);
177+
if (tableName || indexName) {
178+
return `${tableName ?? "table"}_${indexName ?? "index"}`;
179+
}
180+
}
181+
}
182+
183+
public createTempNameForLuaExpression(expression: lua.Expression) {
184+
const name = this.getTempNameForLuaExpression(expression);
185+
const identifier = lua.createIdentifier(this.createTempName(name), undefined, tempSymbolId);
186+
lua.setNodePosition(identifier, lua.getOriginalPos(expression));
187+
return identifier;
188+
}
189+
190+
private getTempNameForNode(node: ts.Node): string | undefined {
191+
if (ts.isStringLiteral(node) || ts.isIdentifier(node) || ts.isMemberName(node)) {
192+
return node.text;
193+
} else if (ts.isNumericLiteral(node)) {
194+
return `_${node.text}`;
195+
} else if (ts.isCallExpression(node)) {
196+
const name = this.getTempNameForNode(node.expression);
197+
if (name) {
198+
return `${name}_result`;
199+
}
200+
} else if (ts.isElementAccessExpression(node) || ts.isPropertyAccessExpression(node)) {
201+
const tableName = this.getTempNameForNode(node.expression);
202+
const indexName = ts.isElementAccessExpression(node)
203+
? this.getTempNameForNode(node.argumentExpression)
204+
: node.name.text;
205+
if (tableName || indexName) {
206+
return `${tableName ?? "table"}_${indexName ?? "index"}`;
207+
}
208+
}
209+
}
210+
211+
public createTempNameForNode(node: ts.Node) {
212+
const name = this.getTempNameForNode(node);
213+
return lua.createIdentifier(this.createTempName(name), node, tempSymbolId);
112214
}
113215
}

src/transformation/utils/lua-ast.ts

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as lua from "../../LuaAST";
44
import { assert, castArray } from "../../utils";
55
import { TransformationContext } from "../context";
66
import { createExportedIdentifier, getIdentifierExportScope } from "./export";
7-
import { peekScope, ScopeType, Scope } from "./scope";
7+
import { peekScope, ScopeType, Scope, addScopeVariableDeclaration } from "./scope";
88
import { transformLuaLibFunction } from "./lualib";
99
import { LuaLibFeature } from "../../LuaLib";
1010

@@ -62,19 +62,6 @@ export function getNumberLiteralValue(expression?: lua.Expression) {
6262
return undefined;
6363
}
6464

65-
// Prefer use of transformToImmediatelyInvokedFunctionExpression to maintain correct scope. If you use this directly,
66-
// ensure you push/pop a function scope appropriately to avoid incorrect vararg optimization.
67-
export function createImmediatelyInvokedFunctionExpression(
68-
statements: lua.Statement[],
69-
result: lua.Expression | lua.Expression[],
70-
tsOriginal?: ts.Node
71-
): lua.CallExpression {
72-
const body = [...statements, lua.createReturnStatement(castArray(result))];
73-
const flags = statements.length === 0 ? lua.FunctionExpressionFlags.Inline : lua.FunctionExpressionFlags.None;
74-
const iife = lua.createFunctionExpression(lua.createBlock(body), undefined, undefined, flags);
75-
return lua.createCallExpression(iife, [], tsOriginal);
76-
}
77-
7865
export function createUnpackCall(
7966
context: TransformationContext,
8067
expression: lua.Expression,
@@ -119,12 +106,7 @@ export function createHoistableVariableDeclarationStatement(
119106
if (identifier.symbolId !== undefined) {
120107
const scope = peekScope(context);
121108
assert(scope.type !== ScopeType.Switch);
122-
123-
if (!scope.variableDeclarations) {
124-
scope.variableDeclarations = [];
125-
}
126-
127-
scope.variableDeclarations.push(declaration);
109+
addScopeVariableDeclaration(scope, declaration);
128110
}
129111

130112
return declaration;
@@ -176,22 +158,25 @@ export function createLocalOrExportedOrGlobalDeclaration(
176158

177159
if (context.isModule || !isTopLevelVariable) {
178160
if (!isFunctionDeclaration && hasMultipleReferences(scope, lhs)) {
179-
// Split declaration and assignment of identifiers that reference themselves in their declaration
180-
declaration = lua.createVariableDeclarationStatement(lhs, undefined, tsOriginal);
161+
// Split declaration and assignment of identifiers that reference themselves in their declaration.
162+
// Put declaration above preceding statements in case the identifier is referenced in those.
163+
const precedingDeclaration = lua.createVariableDeclarationStatement(lhs, undefined, tsOriginal);
164+
context.prependPrecedingStatements(precedingDeclaration);
181165
if (rhs) {
182166
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal);
183167
}
168+
169+
if (!isFunctionDeclaration) {
170+
// Remember local variable declarations for hoisting later
171+
addScopeVariableDeclaration(scope, precedingDeclaration);
172+
}
184173
} else {
185174
declaration = lua.createVariableDeclarationStatement(lhs, rhs, tsOriginal);
186-
}
187175

188-
if (!isFunctionDeclaration) {
189-
// Remember local variable declarations for hoisting later
190-
if (!scope.variableDeclarations) {
191-
scope.variableDeclarations = [];
176+
if (!isFunctionDeclaration) {
177+
// Remember local variable declarations for hoisting later
178+
addScopeVariableDeclaration(scope, declaration);
192179
}
193-
194-
scope.variableDeclarations.push(declaration);
195180
}
196181
} else if (rhs) {
197182
// global

0 commit comments

Comments
 (0)