Skip to content

Commit 9e80934

Browse files
committed
Merge remote-tracking branch 'upstream/master' into diagnostics
2 parents 03e9aa3 + 96c3f8d commit 9e80934

36 files changed

+563
-591
lines changed

src/LuaAST.ts

Lines changed: 71 additions & 170 deletions
Large diffs are not rendered by default.

src/LuaPrinter.ts

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -273,16 +273,24 @@ export class LuaPrinter {
273273

274274
protected printStatementArray(statements: lua.Statement[]): SourceChunk[] {
275275
const statementNodes: SourceNode[] = [];
276-
statements = this.removeDeadAndEmptyStatements(statements);
277-
statements.forEach((s, i) => {
278-
const node = this.printStatement(s);
276+
for (const [index, statement] of statements.entries()) {
277+
if (this.isStatementEmpty(statement)) continue;
279278

280-
if (i > 0 && this.statementMayRequireSemiColon(statements[i - 1]) && this.nodeStartsWithParenthesis(node)) {
281-
statementNodes[i - 1].add(";");
279+
const node = this.printStatement(statement);
280+
281+
if (
282+
index > 0 &&
283+
this.statementMayRequireSemiColon(statements[index - 1]) &&
284+
this.nodeStartsWithParenthesis(node)
285+
) {
286+
statementNodes[index - 1].add(";");
282287
}
283288

284289
statementNodes.push(node);
285-
});
290+
291+
if (lua.isReturnStatement(statement)) break;
292+
}
293+
286294
return statementNodes.length > 0 ? [...this.joinChunks("\n", statementNodes), "\n"] : [];
287295
}
288296

@@ -395,13 +403,10 @@ export class LuaPrinter {
395403
return this.createSourceNode(statement, chunks);
396404
}
397405

398-
public printIfStatement(statement: lua.IfStatement): SourceNode {
406+
public printIfStatement(statement: lua.IfStatement, isElseIf = false): SourceNode {
399407
const chunks: SourceChunk[] = [];
400408

401-
const isElseIf = statement.parent !== undefined && lua.isIfStatement(statement.parent);
402-
403409
const prefix = isElseIf ? "elseif" : "if";
404-
405410
chunks.push(this.indent(prefix + " "), this.printExpression(statement.condition), " then\n");
406411

407412
this.pushIndent();
@@ -410,7 +415,7 @@ export class LuaPrinter {
410415

411416
if (statement.elseBlock) {
412417
if (lua.isIfStatement(statement.elseBlock)) {
413-
chunks.push(this.printIfStatement(statement.elseBlock));
418+
chunks.push(this.printIfStatement(statement.elseBlock, true));
414419
} else {
415420
chunks.push(this.indent("else\n"));
416421
this.pushIndent();
@@ -773,19 +778,6 @@ export class LuaPrinter {
773778
return new SourceNode(null, null, this.sourceFile, LuaPrinter.operatorMap[kind]);
774779
}
775780

776-
protected removeDeadAndEmptyStatements(statements: lua.Statement[]): lua.Statement[] {
777-
const aliveStatements = [];
778-
for (const statement of statements) {
779-
if (!this.isStatementEmpty(statement)) {
780-
aliveStatements.push(statement);
781-
}
782-
if (lua.isReturnStatement(statement)) {
783-
break;
784-
}
785-
}
786-
return aliveStatements;
787-
}
788-
789781
protected isStatementEmpty(statement: lua.Statement): boolean {
790782
return lua.isDoStatement(statement) && (!statement.statements || statement.statements.length === 0);
791783
}

src/transformation/builtins/math.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as ts from "typescript";
22
import * as lua from "../../LuaAST";
3+
import { LuaTarget } from "../../CompilerOptions";
34
import { TransformationContext } from "../context";
45
import { unsupportedProperty } from "../utils/diagnostics";
56
import { PropertyCallExpression, transformArguments } from "../visitors/call";
@@ -39,12 +40,13 @@ export function transformMathCall(
3940

4041
const expressionName = expression.name.text;
4142
switch (expressionName) {
42-
// math.tan(x / y)
43+
// Lua 5.3: math.atan(y, x)
44+
// Otherwise: math.atan2(y, x)
4345
case "atan2": {
4446
const math = lua.createIdentifier("math");
45-
const atan = lua.createStringLiteral("atan");
46-
const div = lua.createBinaryExpression(params[0], params[1], lua.SyntaxKind.DivisionOperator);
47-
return lua.createCallExpression(lua.createTableIndexExpression(math, atan), [div], node);
47+
const methodName = context.options.luaTarget === LuaTarget.Lua53 ? "atan" : expressionName;
48+
const method = lua.createStringLiteral(methodName);
49+
return lua.createCallExpression(lua.createTableIndexExpression(math, method), params, node);
4850
}
4951

5052
// (math.log(x) / Math.LNe)

src/transformation/context/context.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as ts from "typescript";
22
import { CompilerOptions, LuaTarget } from "../../CompilerOptions";
33
import * as lua from "../../LuaAST";
44
import { unwrapVisitorResult } from "../utils/lua-ast";
5-
import { isFileModule } from "../utils/typescript";
65
import { ExpressionLikeNode, ObjectVisitor, StatementLikeNode, VisitorMap } from "./visitors";
76

87
export interface EmitResolver {
@@ -24,7 +23,7 @@ export class TransformationContext {
2423

2524
public readonly options: CompilerOptions = this.program.getCompilerOptions();
2625
public readonly luaTarget = this.options.luaTarget ?? LuaTarget.LuaJIT;
27-
public readonly isModule = isFileModule(this.sourceFile);
26+
public readonly isModule = ts.isExternalModule(this.sourceFile);
2827
public readonly isStrict =
2928
(this.options.alwaysStrict ?? this.options.strict) ||
3029
(this.isModule && this.options.target !== undefined && this.options.target >= ts.ScriptTarget.ES2015);

src/transformation/utils/diagnostics.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,7 @@ export const referencedBeforeDeclaration = createDiagnosticFactory(
138138
export const unresolvableRequirePath = createDiagnosticFactory(
139139
(path: string) => `Cannot create require path. Module '${path}' does not exist within --rootDir.`
140140
);
141+
142+
export const unsupportedVarDeclaration = createDiagnosticFactory(
143+
"`var` declarations are not supported. Use `let` or `const` instead."
144+
);

src/transformation/utils/export.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { TransformationContext } from "../context";
44
import { createModuleLocalNameIdentifier } from "../visitors/namespace";
55
import { createExportsIdentifier } from "./lua-ast";
66
import { getSymbolInfo } from "./symbols";
7-
import { findFirstNodeAbove, isFileModule } from "./typescript";
7+
import { findFirstNodeAbove } from "./typescript";
88

99
export function hasDefaultExportModifier(node: ts.Node): boolean {
1010
return (node.modifiers ?? []).some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword);
@@ -71,6 +71,29 @@ export function getSymbolExportScope(
7171
return scope;
7272
}
7373

74+
export function getExportedSymbolsFromScope(
75+
context: TransformationContext,
76+
scope: ts.SourceFile | ts.ModuleDeclaration
77+
): ts.Symbol[] {
78+
const scopeSymbol = context.checker.getSymbolAtLocation(ts.isSourceFile(scope) ? scope : scope.name);
79+
if (scopeSymbol?.exports === undefined) {
80+
return [];
81+
}
82+
83+
// ts.Iterator is not a ES6-compatible iterator, because TypeScript targets ES5
84+
const it: Iterable<ts.Symbol> = { [Symbol.iterator]: () => scopeSymbol!.exports!.values() };
85+
return [...it];
86+
}
87+
88+
export function getDependenciesOfSymbol(context: TransformationContext, originalSymbol: ts.Symbol): ts.Symbol[] {
89+
return getExportedSymbolsFromScope(context, context.sourceFile).filter(exportSymbol =>
90+
exportSymbol.declarations
91+
.filter(ts.isExportSpecifier)
92+
.map(context.checker.getExportSpecifierLocalTargetSymbol)
93+
.includes(originalSymbol)
94+
);
95+
}
96+
7497
export function isSymbolExported(context: TransformationContext, symbol: ts.Symbol): boolean {
7598
return (
7699
getExportedSymbolDeclaration(symbol) !== undefined ||
@@ -84,23 +107,7 @@ export function isSymbolExportedFromScope(
84107
symbol: ts.Symbol,
85108
scope: ts.SourceFile | ts.ModuleDeclaration
86109
): boolean {
87-
if (ts.isSourceFile(scope) && !isFileModule(scope)) {
88-
return false;
89-
}
90-
91-
let scopeSymbol = context.checker.getSymbolAtLocation(scope);
92-
if (scopeSymbol === undefined) {
93-
// TODO: Necessary?
94-
scopeSymbol = context.checker.getTypeAtLocation(scope).getSymbol();
95-
}
96-
97-
if (scopeSymbol === undefined || scopeSymbol.exports === undefined) {
98-
return false;
99-
}
100-
101-
// ts.Iterator is not a ES6-compatible iterator, because TypeScript targets ES5
102-
const it: Iterable<ts.Symbol> = { [Symbol.iterator]: () => scopeSymbol!.exports!.values() };
103-
return [...it].includes(symbol);
110+
return getExportedSymbolsFromScope(context, scope).includes(symbol);
104111
}
105112

106113
export function addExportToIdentifier(

src/transformation/utils/lua-ast.ts

Lines changed: 26 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import * as ts from "typescript";
22
import { LuaTarget } from "../../CompilerOptions";
33
import * as lua from "../../LuaAST";
4+
import { assert } from "../../utils";
45
import { TransformationContext } from "../context";
56
import { getCurrentNamespace } from "../visitors/namespace";
67
import { createExportedIdentifier, getIdentifierExportScope } from "./export";
78
import { findScope, peekScope, ScopeType } from "./scope";
8-
import { isFirstDeclaration, isFunctionType } from "./typescript";
9+
import { isFunctionType } from "./typescript";
910

1011
export type OneToManyVisitorResult<T extends lua.Node> = T | T[] | undefined;
1112
export function unwrapVisitorResult<T extends lua.Node>(result: OneToManyVisitorResult<T>): T[] {
@@ -26,22 +27,6 @@ export function createExportsIdentifier(): lua.Identifier {
2627
return lua.createIdentifier("____exports");
2728
}
2829

29-
export function replaceStatementInParent(oldNode: lua.Statement, newNode?: lua.Statement): void {
30-
if (!oldNode.parent) {
31-
throw new Error("node has not yet been assigned a parent");
32-
}
33-
34-
if (lua.isBlock(oldNode.parent) || lua.isDoStatement(oldNode.parent)) {
35-
if (newNode) {
36-
oldNode.parent.statements.splice(oldNode.parent.statements.indexOf(oldNode), 1, newNode);
37-
} else {
38-
oldNode.parent.statements.splice(oldNode.parent.statements.indexOf(oldNode), 1);
39-
}
40-
} else {
41-
throw new Error("unexpected parent type");
42-
}
43-
}
44-
4530
export function createExpressionPlusOne(expression: lua.Expression): lua.Expression {
4631
if (lua.isNumericLiteral(expression)) {
4732
const newNode = lua.cloneNode(expression);
@@ -110,12 +95,13 @@ export function createHoistableVariableDeclarationStatement(
11095
context: TransformationContext,
11196
identifier: lua.Identifier,
11297
initializer?: lua.Expression,
113-
tsOriginal?: ts.Node,
114-
parent?: lua.Node
98+
tsOriginal?: ts.Node
11599
): lua.AssignmentStatement | lua.VariableDeclarationStatement {
116-
const declaration = lua.createVariableDeclarationStatement(identifier, initializer, tsOriginal, parent);
100+
const declaration = lua.createVariableDeclarationStatement(identifier, initializer, tsOriginal);
117101
if (!context.options.noHoisting && identifier.symbolId) {
118102
const scope = peekScope(context);
103+
assert(scope.type !== ScopeType.Switch);
104+
119105
if (!scope.variableDeclarations) {
120106
scope.variableDeclarations = [];
121107
}
@@ -131,13 +117,13 @@ export function createLocalOrExportedOrGlobalDeclaration(
131117
lhs: lua.Identifier | lua.Identifier[],
132118
rhs?: lua.Expression | lua.Expression[],
133119
tsOriginal?: ts.Node,
134-
parent?: lua.Node,
135120
overrideExportScope?: ts.SourceFile | ts.ModuleDeclaration
136121
): lua.Statement[] {
137122
let declaration: lua.VariableDeclarationStatement | undefined;
138123
let assignment: lua.AssignmentStatement | undefined;
139124

140-
const functionDeclaration = tsOriginal && ts.isFunctionDeclaration(tsOriginal) ? tsOriginal : undefined;
125+
const isVariableDeclaration = tsOriginal !== undefined && ts.isVariableDeclaration(tsOriginal);
126+
const isFunctionDeclaration = tsOriginal !== undefined && ts.isFunctionDeclaration(tsOriginal);
141127

142128
const identifiers = Array.isArray(lhs) ? lhs : [lhs];
143129
if (identifiers.length === 0) {
@@ -153,62 +139,51 @@ export function createLocalOrExportedOrGlobalDeclaration(
153139
assignment = lua.createAssignmentStatement(
154140
identifiers.map(identifier => createExportedIdentifier(context, identifier, exportScope)),
155141
rhs,
156-
tsOriginal,
157-
parent
142+
tsOriginal
158143
);
159144
}
160145
} else {
161146
const insideFunction = findScope(context, ScopeType.Function) !== undefined;
162-
let isLetOrConst = false;
163-
let isVariableFirstDeclaration = true; // var can have multiple declarations for the same variable :/
164-
if (tsOriginal && ts.isVariableDeclaration(tsOriginal) && tsOriginal.parent) {
165-
isLetOrConst = (tsOriginal.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
166-
isVariableFirstDeclaration = isLetOrConst || isFirstDeclaration(context, tsOriginal);
167-
}
168147

169-
if (
170-
(context.isModule || getCurrentNamespace(context) || insideFunction || isLetOrConst) &&
171-
isVariableFirstDeclaration
172-
) {
173-
// local
148+
if (context.isModule || getCurrentNamespace(context) || insideFunction || isVariableDeclaration) {
149+
const scope = peekScope(context);
150+
174151
const isPossibleWrappedFunction =
175-
!functionDeclaration &&
152+
!isFunctionDeclaration &&
176153
tsOriginal &&
177154
ts.isVariableDeclaration(tsOriginal) &&
178155
tsOriginal.initializer &&
179156
isFunctionType(context, context.checker.getTypeAtLocation(tsOriginal.initializer));
180-
if (isPossibleWrappedFunction) {
157+
158+
if (isPossibleWrappedFunction || scope.type === ScopeType.Switch) {
181159
// Split declaration and assignment for wrapped function types to allow recursion
182-
declaration = lua.createVariableDeclarationStatement(lhs, undefined, tsOriginal, parent);
183-
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal, parent);
160+
declaration = lua.createVariableDeclarationStatement(lhs, undefined, tsOriginal);
161+
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal);
184162
} else {
185-
declaration = lua.createVariableDeclarationStatement(lhs, rhs, tsOriginal, parent);
163+
declaration = lua.createVariableDeclarationStatement(lhs, rhs, tsOriginal);
186164
}
187165

188166
if (!context.options.noHoisting) {
189167
// Remember local variable declarations for hoisting later
190-
const scope =
191-
isLetOrConst || functionDeclaration
192-
? peekScope(context)
193-
: findScope(context, ScopeType.Function | ScopeType.File);
168+
if (!scope.variableDeclarations) {
169+
scope.variableDeclarations = [];
170+
}
194171

195-
if (scope) {
196-
if (!scope.variableDeclarations) {
197-
scope.variableDeclarations = [];
198-
}
172+
scope.variableDeclarations.push(declaration);
199173

200-
scope.variableDeclarations.push(declaration);
174+
if (scope.type === ScopeType.Switch) {
175+
declaration = undefined;
201176
}
202177
}
203178
} else if (rhs) {
204179
// global
205-
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal, parent);
180+
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal);
206181
} else {
207182
return [];
208183
}
209184
}
210185

211-
if (!context.options.noHoisting && functionDeclaration) {
186+
if (!context.options.noHoisting && isFunctionDeclaration) {
212187
// Remember function definitions for hoisting later
213188
const functionSymbolId = (lhs as lua.Identifier).symbolId;
214189
const scope = peekScope(context);

src/transformation/utils/safe-names.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const luaKeywords: ReadonlySet<string> = new Set([
2525
"repeat",
2626
"return",
2727
"then",
28+
"true",
2829
"until",
2930
"while",
3031
]);

0 commit comments

Comments
 (0)