Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
4c41fc9
working on preceding statements implementation
tomblind Aug 29, 2021
69390bf
fixed issues from tests and started adding new tests (which don't pas…
tomblind Aug 30, 2021
c30fa75
fixed issues with short-circuit compound operator expressions
tomblind Aug 31, 2021
af010aa
execution order tests
tomblind Aug 31, 2021
8888089
fixes for remaining broken tests
tomblind Sep 1, 2021
4076afd
switch test (currently broken)
tomblind Sep 2, 2021
7f45420
refactor to expression list transformation
tomblind Sep 2, 2021
52714ef
more refactoring and fixes for expression lists
tomblind Sep 3, 2021
306a5b9
refactored object literals a bit
tomblind Sep 3, 2021
6945c7e
refactorings, including removal of old iife stuff
tomblind Sep 3, 2021
70d463b
snapshot updates
tomblind Sep 3, 2021
0bbf30e
cleanup, fixes, and added some original nodes for source maps
tomblind Sep 3, 2021
c788432
comment update
tomblind Sep 3, 2021
2b3f517
fixed ifelse statements
tomblind Sep 3, 2021
00d6bdb
more things to fix
tomblind Sep 3, 2021
e7ba530
working on fixes to assignments and creating more tests (most of whic…
tomblind Sep 4, 2021
d60b6fd
more fixes. more broken things.
tomblind Sep 4, 2021
ed8bc68
lots of fixes to call expressions and lots of new broken tests
tomblind Sep 5, 2021
73f179b
Merge branch 'master' into preceding-statements
tomblind Sep 6, 2021
54f61e6
more execution order fixes, more tests
tomblind Sep 6, 2021
e579ee2
working on fixes for destructuring exec order
tomblind Sep 8, 2021
175f85a
additional object destructuring test
tomblind Sep 10, 2021
a537be9
Merge branch 'master' into preceding-statements
tomblind Sep 13, 2021
f16841c
fixes for object destructuring
tomblind Sep 15, 2021
6bd5a6f
fixed switch statements
tomblind Sep 16, 2021
4c154cd
refactored expression list handling
tomblind Sep 17, 2021
895e589
optimized out temps in many situations
tomblind Sep 18, 2021
13c6d1a
fixed execution order of computed property names in object literals
tomblind Sep 18, 2021
65c6131
a little refactoring
tomblind Sep 19, 2021
35771e8
removed bad optimization and refactored call stuff a bit
tomblind Sep 24, 2021
0896dd5
added SparseArray lib functions to handle complex expression lists an…
tomblind Sep 28, 2021
c936a05
addPrecedingStatements takes a single argument now
tomblind Sep 28, 2021
92afa7c
a little cleanup
tomblind Sep 28, 2021
a7d7b3e
fix for indirect property assignments
tomblind Sep 28, 2021
40c24a8
refactoring to expression-lists
tomblind Sep 29, 2021
ccb1c26
fixes and refactors to tests
tomblind Sep 29, 2021
09ae80b
improvements to temp variable naming
tomblind Sep 29, 2021
309f90a
treating temps like consts and more temp name adjustments
tomblind Sep 29, 2021
6aae347
snapshot update
tomblind Sep 29, 2021
ca0a27d
a couple of small refactorings
tomblind Sep 30, 2021
8a7d589
refactoring based on comments & suggestions
tomblind Nov 7, 2021
b1c56bd
Merge branch 'master' into preceding-statements
tomblind Nov 7, 2021
93923d4
change void expressions to use preceding statements
tomblind Nov 7, 2021
d25377a
better comments and variable names for preceding statement handling o…
tomblind Nov 7, 2021
6964927
addressed feedback
tomblind Nov 14, 2021
1f2d638
reverted change that didn't seem necessary
tomblind Nov 14, 2021
738836f
updated transformBinaryOperation to handle preceding statements
tomblind Nov 15, 2021
4264479
reverted switch handling to using lua expressions instead of manufact…
tomblind Nov 16, 2021
d8eacdd
constified compound assignment code
tomblind Nov 16, 2021
551e951
renamed createShortCircuitBinaryExpression
tomblind Nov 16, 2021
ffd2666
Merge from master
tomblind Nov 16, 2021
3861ec8
Merge branch 'master' into preceding-statements
tomblind Nov 16, 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
12 changes: 12 additions & 0 deletions src/LuaAST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,18 @@ export function createStringLiteral(value: string, tsOriginal?: ts.Node): String
return expression;
}

export function isLiteral(
node: Node
): node is NilLiteral | DotsLiteral | BooleanLiteral | NumericLiteral | StringLiteral {
return (
isNilLiteral(node) ||
isDotsLiteral(node) ||
isBooleanLiteral(node) ||
isNumericLiteral(node) ||
isStringLiteral(node)
);
}

export enum FunctionExpressionFlags {
None = 1 << 0,
Inline = 1 << 1, // Keep function body on same line
Expand Down
3 changes: 3 additions & 0 deletions src/LuaLib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ export enum LuaLibFeature {
PromiseRace = "PromiseRace",
Set = "Set",
SetDescriptor = "SetDescriptor",
SparseArrayNew = "SparseArrayNew",
SparseArrayPush = "SparseArrayPush",
SparseArraySpread = "SparseArraySpread",
WeakMap = "WeakMap",
WeakSet = "WeakSet",
SourceMapTraceBack = "SourceMapTraceBack",
Expand Down
9 changes: 9 additions & 0 deletions src/lualib/SparseArrayNew.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type __TS__SparseArray<T> = T[] & { sparseLength: number };

function __TS__SparseArrayNew<T>(this: void, ...args: T[]): __TS__SparseArray<T> {
const sparseArray = [...args] as __TS__SparseArray<T>;
// select("#", ...) counts the number of args passed, including nils.
// Note that we're depending on vararg optimization to occur here.
sparseArray.sparseLength = select("#", ...args);
return sparseArray;
}
8 changes: 8 additions & 0 deletions src/lualib/SparseArrayPush.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
function __TS__SparseArrayPush<T>(this: void, sparseArray: __TS__SparseArray<T>, ...args: T[]): void {
const argsLen = select("#", ...args);
const listLen = sparseArray.sparseLength;
for (const i of $range(1, argsLen)) {
sparseArray[listLen + i - 1] = args[i - 1];
}
sparseArray.sparseLength = listLen + argsLen;
}
4 changes: 4 additions & 0 deletions src/lualib/SparseArraySpread.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function __TS__SparseArraySpread<T>(this: void, sparseArray: __TS__SparseArray<T>): LuaMultiReturn<T[]> {
const _unpack = unpack ?? table.unpack;
return _unpack(sparseArray, 1, sparseArray.sparseLength);
}
5 changes: 2 additions & 3 deletions src/transformation/builtins/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as lua from "../../LuaAST";
import { TransformationContext } from "../context";
import { unsupportedProperty } from "../utils/diagnostics";
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
import { PropertyCallExpression, transformArguments } from "../visitors/call";
import { PropertyCallExpression, transformArguments, transformCallAndArguments } from "../visitors/call";
import { isStringType, isNumberType } from "../utils/typescript";

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

const expressionName = expression.name.text;
switch (expressionName) {
Expand Down
5 changes: 2 additions & 3 deletions src/transformation/builtins/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { unsupportedForTarget, unsupportedProperty, unsupportedSelfFunctionConve
import { ContextType, getFunctionContextType } from "../utils/function-context";
import { createUnpackCall } from "../utils/lua-ast";
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
import { PropertyCallExpression, transformArguments } from "../visitors/call";
import { PropertyCallExpression, transformCallAndArguments } from "../visitors/call";

export function transformFunctionPrototypeCall(
context: TransformationContext,
Expand All @@ -19,8 +19,7 @@ export function transformFunctionPrototypeCall(
}

const signature = context.checker.getResolvedSignature(node);
const params = transformArguments(context, node.arguments, signature);
const caller = context.transformExpression(expression.expression);
const [caller, params] = transformCallAndArguments(context, expression.expression, node.arguments, signature);
const expressionName = expression.name.text;
switch (expressionName) {
case "apply":
Expand Down
5 changes: 2 additions & 3 deletions src/transformation/builtins/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TransformationContext } from "../context";
import { unsupportedProperty } from "../utils/diagnostics";
import { addToNumericExpression, createNaN, getNumberLiteralValue } from "../utils/lua-ast";
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
import { PropertyCallExpression, transformArguments } from "../visitors/call";
import { PropertyCallExpression, transformArguments, transformCallAndArguments } from "../visitors/call";

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

const expressionName = expression.name.text;
switch (expressionName) {
Expand Down
108 changes: 105 additions & 3 deletions src/transformation/context/context.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import * as ts from "typescript";
import { CompilerOptions, LuaTarget } from "../../CompilerOptions";
import * as lua from "../../LuaAST";
import { castArray } from "../../utils";
import { assert, castArray } from "../../utils";
import { unsupportedNodeKind } from "../utils/diagnostics";
import { unwrapVisitorResult } from "../utils/lua-ast";
import { createSafeName } from "../utils/safe-names";
import { ExpressionLikeNode, ObjectVisitor, StatementLikeNode, VisitorMap } from "./visitors";

export const tempSymbolId = -1 as lua.SymbolId;

export interface AllAccessorDeclarations {
firstAccessor: ts.AccessorDeclaration;
secondAccessor: ts.AccessorDeclaration | undefined;
Expand All @@ -29,6 +32,7 @@ export class TransformationContext {
public readonly diagnostics: ts.Diagnostic[] = [];
public readonly checker: DiagnosticsProducingTypeChecker = this.program.getDiagnosticsProducingTypeChecker();
public readonly resolver: EmitResolver;
public readonly precedingStatementsStack: lua.Statement[][] = [];

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

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

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

public transformStatements(node: StatementLikeNode | readonly StatementLikeNode[]): lua.Statement[] {
return castArray(node).flatMap(n => this.transformNode(n) as lua.Statement[]);
return castArray(node).flatMap(n => {
this.pushPrecedingStatements();
const statements = this.transformNode(n) as lua.Statement[];
statements.unshift(...this.popPrecedingStatements());
return statements;
});
}

public superTransformStatements(node: StatementLikeNode | readonly StatementLikeNode[]): lua.Statement[] {
return castArray(node).flatMap(n => this.superTransformNode(n) as lua.Statement[]);
return castArray(node).flatMap(n => {
this.pushPrecedingStatements();
const statements = this.superTransformNode(n) as lua.Statement[];
statements.unshift(...this.popPrecedingStatements());
return statements;
});
}

public pushPrecedingStatements() {
this.precedingStatementsStack.push([]);
}

public popPrecedingStatements() {
const precedingStatements = this.precedingStatementsStack.pop();
assert(precedingStatements);
return precedingStatements;
}

public addPrecedingStatements(statements: lua.Statement | lua.Statement[]) {
const precedingStatements = this.precedingStatementsStack[this.precedingStatementsStack.length - 1];
assert(precedingStatements);
if (!Array.isArray(statements)) {
statements = [statements];
}
precedingStatements.push(...statements);
}

public prependPrecedingStatements(statements: lua.Statement | lua.Statement[]) {
const precedingStatements = this.precedingStatementsStack[this.precedingStatementsStack.length - 1];
assert(precedingStatements);
if (!Array.isArray(statements)) {
statements = [statements];
}
precedingStatements.unshift(...statements);
}

public createTempName(prefix = "temp") {
prefix = prefix.replace(/^_*/, ""); // Strip leading underscores because createSafeName will add them again
return createSafeName(`${prefix}_${this.nextTempId++}`);
}

private getTempNameForLuaExpression(expression: lua.Expression): string | undefined {
if (lua.isStringLiteral(expression)) {
return expression.value;
} else if (lua.isNumericLiteral(expression)) {
return `_${expression.value.toString()}`;
} else if (lua.isIdentifier(expression)) {
return expression.text;
} else if (lua.isCallExpression(expression)) {
const name = this.getTempNameForLuaExpression(expression.expression);
if (name) {
return `${name}_result`;
}
} else if (lua.isTableIndexExpression(expression)) {
const tableName = this.getTempNameForLuaExpression(expression.table);
const indexName = this.getTempNameForLuaExpression(expression.index);
if (tableName || indexName) {
return `${tableName ?? "table"}_${indexName ?? "index"}`;
}
}
}

public createTempNameForLuaExpression(expression: lua.Expression) {
const name = this.getTempNameForLuaExpression(expression);
const identifier = lua.createIdentifier(this.createTempName(name), undefined, tempSymbolId);
lua.setNodePosition(identifier, lua.getOriginalPos(expression));
return identifier;
}

private getTempNameForNode(node: ts.Node): string | undefined {
if (ts.isStringLiteral(node) || ts.isIdentifier(node) || ts.isMemberName(node)) {
return node.text;
} else if (ts.isNumericLiteral(node)) {
return `_${node.text}`;
} else if (ts.isCallExpression(node)) {
const name = this.getTempNameForNode(node.expression);
if (name) {
return `${name}_result`;
}
} else if (ts.isElementAccessExpression(node) || ts.isPropertyAccessExpression(node)) {
const tableName = this.getTempNameForNode(node.expression);
const indexName = ts.isElementAccessExpression(node)
? this.getTempNameForNode(node.argumentExpression)
: node.name.text;
if (tableName || indexName) {
return `${tableName ?? "table"}_${indexName ?? "index"}`;
}
}
}

public createTempNameForNode(node: ts.Node) {
const name = this.getTempNameForNode(node);
return lua.createIdentifier(this.createTempName(name), node, tempSymbolId);
}
}
43 changes: 14 additions & 29 deletions src/transformation/utils/lua-ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as lua from "../../LuaAST";
import { assert, castArray } from "../../utils";
import { TransformationContext } from "../context";
import { createExportedIdentifier, getIdentifierExportScope } from "./export";
import { peekScope, ScopeType, Scope } from "./scope";
import { peekScope, ScopeType, Scope, addScopeVariableDeclaration } from "./scope";
import { transformLuaLibFunction } from "./lualib";
import { LuaLibFeature } from "../../LuaLib";

Expand Down Expand Up @@ -62,19 +62,6 @@ export function getNumberLiteralValue(expression?: lua.Expression) {
return undefined;
}

// Prefer use of transformToImmediatelyInvokedFunctionExpression to maintain correct scope. If you use this directly,
// ensure you push/pop a function scope appropriately to avoid incorrect vararg optimization.
export function createImmediatelyInvokedFunctionExpression(
statements: lua.Statement[],
result: lua.Expression | lua.Expression[],
tsOriginal?: ts.Node
): lua.CallExpression {
const body = [...statements, lua.createReturnStatement(castArray(result))];
const flags = statements.length === 0 ? lua.FunctionExpressionFlags.Inline : lua.FunctionExpressionFlags.None;
const iife = lua.createFunctionExpression(lua.createBlock(body), undefined, undefined, flags);
return lua.createCallExpression(iife, [], tsOriginal);
}

export function createUnpackCall(
context: TransformationContext,
expression: lua.Expression,
Expand Down Expand Up @@ -119,12 +106,7 @@ export function createHoistableVariableDeclarationStatement(
if (identifier.symbolId !== undefined) {
const scope = peekScope(context);
assert(scope.type !== ScopeType.Switch);

if (!scope.variableDeclarations) {
scope.variableDeclarations = [];
}

scope.variableDeclarations.push(declaration);
addScopeVariableDeclaration(scope, declaration);
}

return declaration;
Expand Down Expand Up @@ -176,22 +158,25 @@ export function createLocalOrExportedOrGlobalDeclaration(

if (context.isModule || !isTopLevelVariable) {
if (!isFunctionDeclaration && hasMultipleReferences(scope, lhs)) {
// Split declaration and assignment of identifiers that reference themselves in their declaration
declaration = lua.createVariableDeclarationStatement(lhs, undefined, tsOriginal);
// Split declaration and assignment of identifiers that reference themselves in their declaration.
// Put declaration above preceding statements in case the identifier is referenced in those.
const precedingDeclaration = lua.createVariableDeclarationStatement(lhs, undefined, tsOriginal);
context.prependPrecedingStatements(precedingDeclaration);
if (rhs) {
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal);
}

if (!isFunctionDeclaration) {
// Remember local variable declarations for hoisting later
addScopeVariableDeclaration(scope, precedingDeclaration);
}
} else {
declaration = lua.createVariableDeclarationStatement(lhs, rhs, tsOriginal);
}

if (!isFunctionDeclaration) {
// Remember local variable declarations for hoisting later
if (!scope.variableDeclarations) {
scope.variableDeclarations = [];
if (!isFunctionDeclaration) {
// Remember local variable declarations for hoisting later
addScopeVariableDeclaration(scope, declaration);
}

scope.variableDeclarations.push(declaration);
}
} else if (rhs) {
// global
Expand Down
11 changes: 11 additions & 0 deletions src/transformation/utils/preceding-statements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as lua from "../../LuaAST";
import { TransformationContext } from "../context";

export function transformInPrecedingStatementScope<
TReturn extends lua.Statement | lua.Statement[] | lua.Expression | lua.Expression[]
>(context: TransformationContext, transformer: () => TReturn): [lua.Statement[], TReturn] {
context.pushPrecedingStatements();
const statementOrStatements = transformer();
const precedingStatements = context.popPrecedingStatements();
return [precedingStatements, statementOrStatements];
}
7 changes: 7 additions & 0 deletions src/transformation/utils/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ export function popScope(context: TransformationContext): Scope {
return scope;
}

export function addScopeVariableDeclaration(scope: Scope, declaration: lua.VariableDeclarationStatement) {
if (!scope.variableDeclarations) {
scope.variableDeclarations = [];
}
scope.variableDeclarations.push(declaration);
}

function isHoistableFunctionDeclaredInScope(symbol: ts.Symbol, scopeNode: ts.Node) {
return symbol?.declarations?.some(
d => ts.isFunctionDeclaration(d) && findFirstNodeAbove(d, (n): n is ts.Node => n === scopeNode)
Expand Down
22 changes: 0 additions & 22 deletions src/transformation/utils/transform.ts

This file was deleted.

17 changes: 17 additions & 0 deletions src/transformation/utils/typescript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,20 @@ export function getFunctionTypeForCall(context: TransformationContext, node: ts.
}
return context.checker.getTypeFromTypeNode(typeDeclaration.type);
}

export function isConstIdentifier(context: TransformationContext, node: ts.Node) {
let identifier = node;
if (ts.isComputedPropertyName(identifier)) {
identifier = identifier.expression;
}
if (!ts.isIdentifier(identifier)) {
return false;
}
const symbol = context.checker.getSymbolAtLocation(identifier);
if (!symbol || !symbol.declarations) {
return false;
}
return symbol.declarations.some(
d => ts.isVariableDeclarationList(d.parent) && (d.parent.flags & ts.NodeFlags.Const) !== 0
);
}
Loading