Skip to content

Commit 084e228

Browse files
ark120202Perryvw
authored andcommitted
Drop var declarations support (#762)
* Drop `var` declarations support * Fix `Function using global as this` test * Error on vars in loop variable declarations * Move `transformLoopBody` to the new utils file * Add test for #761 * Revert unrelated test change
1 parent 0284f91 commit 084e228

File tree

18 files changed

+221
-359
lines changed

18 files changed

+221
-359
lines changed

src/LuaAST.ts

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

src/LuaPrinter.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -395,13 +395,10 @@ export class LuaPrinter {
395395
return this.createSourceNode(statement, chunks);
396396
}
397397

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

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

407404
this.pushIndent();
@@ -410,7 +407,7 @@ export class LuaPrinter {
410407

411408
if (statement.elseBlock) {
412409
if (lua.isIfStatement(statement.elseBlock)) {
413-
chunks.push(this.printIfStatement(statement.elseBlock));
410+
chunks.push(this.printIfStatement(statement.elseBlock, true));
414411
} else {
415412
chunks.push(this.indent("else\n"));
416413
this.pushIndent();

src/transformation/utils/errors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,6 @@ export const InvalidAmbientIdentifierName = (node: ts.Identifier) =>
148148

149149
export const InvalidForRangeCall = (node: ts.Node, message: string) =>
150150
new TranspileError(`Invalid @forRange call: ${message}`, node);
151+
152+
export const UnsupportedVarDeclaration = (node: ts.Node) =>
153+
new TranspileError("`var` declarations are not supported. Use `let` or `const` instead.", node);

src/transformation/utils/lua-ast.ts

Lines changed: 14 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ import { LuaTarget } from "../../CompilerOptions";
33
import * as lua from "../../LuaAST";
44
import { TransformationContext } from "../context";
55
import { getCurrentNamespace } from "../visitors/namespace";
6-
import { UndefinedScope } from "./errors";
76
import { createExportedIdentifier, getIdentifierExportScope } from "./export";
87
import { findScope, peekScope, ScopeType } from "./scope";
9-
import { isFirstDeclaration, isFunctionType } from "./typescript";
8+
import { isFunctionType } from "./typescript";
109

1110
export type OneToManyVisitorResult<T extends lua.Node> = T | T[] | undefined;
1211
export function unwrapVisitorResult<T extends lua.Node>(result: OneToManyVisitorResult<T>): T[] {
@@ -27,22 +26,6 @@ export function createExportsIdentifier(): lua.Identifier {
2726
return lua.createIdentifier("____exports");
2827
}
2928

30-
export function replaceStatementInParent(oldNode: lua.Statement, newNode?: lua.Statement): void {
31-
if (!oldNode.parent) {
32-
throw new Error("node has not yet been assigned a parent");
33-
}
34-
35-
if (lua.isBlock(oldNode.parent) || lua.isDoStatement(oldNode.parent)) {
36-
if (newNode) {
37-
oldNode.parent.statements.splice(oldNode.parent.statements.indexOf(oldNode), 1, newNode);
38-
} else {
39-
oldNode.parent.statements.splice(oldNode.parent.statements.indexOf(oldNode), 1);
40-
}
41-
} else {
42-
throw new Error("unexpected parent type");
43-
}
44-
}
45-
4629
export function createExpressionPlusOne(expression: lua.Expression): lua.Expression {
4730
if (lua.isNumericLiteral(expression)) {
4831
const newNode = lua.cloneNode(expression);
@@ -111,10 +94,9 @@ export function createHoistableVariableDeclarationStatement(
11194
context: TransformationContext,
11295
identifier: lua.Identifier,
11396
initializer?: lua.Expression,
114-
tsOriginal?: ts.Node,
115-
parent?: lua.Node
97+
tsOriginal?: ts.Node
11698
): lua.AssignmentStatement | lua.VariableDeclarationStatement {
117-
const declaration = lua.createVariableDeclarationStatement(identifier, initializer, tsOriginal, parent);
99+
const declaration = lua.createVariableDeclarationStatement(identifier, initializer, tsOriginal);
118100
if (!context.options.noHoisting && identifier.symbolId) {
119101
const scope = peekScope(context);
120102
if (!scope.variableDeclarations) {
@@ -132,13 +114,13 @@ export function createLocalOrExportedOrGlobalDeclaration(
132114
lhs: lua.Identifier | lua.Identifier[],
133115
rhs?: lua.Expression | lua.Expression[],
134116
tsOriginal?: ts.Node,
135-
parent?: lua.Node,
136117
overrideExportScope?: ts.SourceFile | ts.ModuleDeclaration
137118
): lua.Statement[] {
138119
let declaration: lua.VariableDeclarationStatement | undefined;
139120
let assignment: lua.AssignmentStatement | undefined;
140121

141-
const functionDeclaration = tsOriginal && ts.isFunctionDeclaration(tsOriginal) ? tsOriginal : undefined;
122+
const isVariableDeclaration = tsOriginal !== undefined && ts.isVariableDeclaration(tsOriginal);
123+
const isFunctionDeclaration = tsOriginal !== undefined && ts.isFunctionDeclaration(tsOriginal);
142124

143125
const identifiers = Array.isArray(lhs) ? lhs : [lhs];
144126
if (identifiers.length === 0) {
@@ -154,48 +136,31 @@ export function createLocalOrExportedOrGlobalDeclaration(
154136
assignment = lua.createAssignmentStatement(
155137
identifiers.map(identifier => createExportedIdentifier(context, identifier, exportScope)),
156138
rhs,
157-
tsOriginal,
158-
parent
139+
tsOriginal
159140
);
160141
}
161142
} else {
162143
const insideFunction = findScope(context, ScopeType.Function) !== undefined;
163-
let isLetOrConst = false;
164-
let isVariableFirstDeclaration = true; // var can have multiple declarations for the same variable :/
165-
if (tsOriginal && ts.isVariableDeclaration(tsOriginal) && tsOriginal.parent) {
166-
isLetOrConst = (tsOriginal.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
167-
isVariableFirstDeclaration = isLetOrConst || isFirstDeclaration(context, tsOriginal);
168-
}
169144

170-
if (
171-
(context.isModule || getCurrentNamespace(context) || insideFunction || isLetOrConst) &&
172-
isVariableFirstDeclaration
173-
) {
145+
if (context.isModule || getCurrentNamespace(context) || insideFunction || isVariableDeclaration) {
174146
// local
175147
const isPossibleWrappedFunction =
176-
!functionDeclaration &&
148+
!isFunctionDeclaration &&
177149
tsOriginal &&
178150
ts.isVariableDeclaration(tsOriginal) &&
179151
tsOriginal.initializer &&
180152
isFunctionType(context, context.checker.getTypeAtLocation(tsOriginal.initializer));
181153
if (isPossibleWrappedFunction) {
182154
// Split declaration and assignment for wrapped function types to allow recursion
183-
declaration = lua.createVariableDeclarationStatement(lhs, undefined, tsOriginal, parent);
184-
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal, parent);
155+
declaration = lua.createVariableDeclarationStatement(lhs, undefined, tsOriginal);
156+
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal);
185157
} else {
186-
declaration = lua.createVariableDeclarationStatement(lhs, rhs, tsOriginal, parent);
158+
declaration = lua.createVariableDeclarationStatement(lhs, rhs, tsOriginal);
187159
}
188160

189161
if (!context.options.noHoisting) {
190162
// Remember local variable declarations for hoisting later
191-
const scope =
192-
isLetOrConst || functionDeclaration
193-
? peekScope(context)
194-
: findScope(context, ScopeType.Function | ScopeType.File);
195-
196-
if (scope === undefined) {
197-
throw UndefinedScope();
198-
}
163+
const scope = peekScope(context);
199164

200165
if (!scope.variableDeclarations) {
201166
scope.variableDeclarations = [];
@@ -205,13 +170,13 @@ export function createLocalOrExportedOrGlobalDeclaration(
205170
}
206171
} else if (rhs) {
207172
// global
208-
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal, parent);
173+
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal);
209174
} else {
210175
return [];
211176
}
212177
}
213178

214-
if (!context.options.noHoisting && functionDeclaration) {
179+
if (!context.options.noHoisting && isFunctionDeclaration) {
215180
// Remember function definitions for hoisting later
216181
const functionSymbolId = (lhs as lua.Identifier).symbolId;
217182
const scope = peekScope(context);

src/transformation/utils/scope.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import * as assert from "assert";
12
import * as ts from "typescript";
23
import * as lua from "../../LuaAST";
34
import { getOrUpdate, isNonNull } from "../../utils";
45
import { TransformationContext } from "../context";
56
import { UndefinedFunctionDefinition, UndefinedScope } from "./errors";
6-
import { replaceStatementInParent } from "./lua-ast";
77
import { getSymbolInfo } from "./symbols";
88
import { getFirstDeclarationInFile } from "./typescript";
99

@@ -168,15 +168,11 @@ function hoistVariableDeclarations(
168168
}
169169

170170
const index = result.indexOf(declaration);
171-
if (index >= 0) {
172-
if (assignment) {
173-
result.splice(index, 1, assignment);
174-
} else {
175-
result.splice(index, 1);
176-
}
171+
assert(index > -1);
172+
if (assignment) {
173+
result.splice(index, 1, assignment);
177174
} else {
178-
// Special case for 'var's declared in child scopes
179-
replaceStatementInParent(declaration, assignment);
175+
result.splice(index, 1);
180176
}
181177

182178
hoistedLocals.push(...declaration.left);

src/transformation/visitors/enum.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ export const transformEnumDeclaration: FunctionVisitor<ts.EnumDeclaration> = (no
7575
: lua.createIdentifier(member.name.getText(), member.name),
7676
valueExpression,
7777
node,
78-
undefined,
7978
exportScope
8079
)
8180
);

src/transformation/visitors/loops/do-while.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as ts from "typescript";
22
import * as lua from "../../../LuaAST";
33
import { FunctionVisitor } from "../../context";
4-
import { transformLoopBody } from "./body";
4+
import { transformLoopBody } from "./utils";
55

66
export const transformWhileStatement: FunctionVisitor<ts.WhileStatement> = (statement, context) => {
77
return lua.createWhileStatement(

src/transformation/visitors/loops/for-in.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { FunctionVisitor } from "../../context";
44
import { ForbiddenForIn, UnsupportedForInVariable } from "../../utils/errors";
55
import { isArrayType } from "../../utils/typescript";
66
import { transformIdentifier } from "../identifier";
7-
import { transformLoopBody } from "./body";
7+
import { getVariableDeclarationBinding, transformLoopBody } from "./utils";
88

99
export const transformForInStatement: FunctionVisitor<ts.ForInStatement> = (statement, context) => {
1010
if (isArrayType(context, context.checker.getTypeAtLocation(statement.expression))) {
@@ -22,11 +22,13 @@ export const transformForInStatement: FunctionVisitor<ts.ForInStatement> = (stat
2222
// TODO: After the transformation pipeline refactor we should look at refactoring this together with the
2323
// for-of initializer transformation.
2424
let iterationVariable: lua.Identifier;
25-
if (
26-
ts.isVariableDeclarationList(statement.initializer) &&
27-
ts.isIdentifier(statement.initializer.declarations[0].name)
28-
) {
29-
iterationVariable = transformIdentifier(context, statement.initializer.declarations[0].name);
25+
if (ts.isVariableDeclarationList(statement.initializer)) {
26+
const binding = getVariableDeclarationBinding(statement.initializer);
27+
if (!ts.isIdentifier(binding)) {
28+
throw UnsupportedForInVariable(statement.initializer);
29+
}
30+
31+
iterationVariable = transformIdentifier(context, binding);
3032
} else if (ts.isIdentifier(statement.initializer)) {
3133
// Iteration variable becomes ____key
3234
iterationVariable = lua.createIdentifier("____key");

src/transformation/visitors/loops/for-of.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,24 @@ import { isArrayType, isNumberType } from "../../utils/typescript";
1515
import { transformArguments } from "../call";
1616
import { transformIdentifier } from "../identifier";
1717
import { transformArrayBindingElement, transformVariableDeclaration } from "../variable-declaration";
18-
import { transformLoopBody } from "./body";
18+
import { getVariableDeclarationBinding, transformLoopBody } from "./utils";
1919

2020
function transformForOfInitializer(
2121
context: TransformationContext,
2222
initializer: ts.ForInitializer,
2323
expression: lua.Expression
2424
): lua.Statement | undefined {
2525
if (ts.isVariableDeclarationList(initializer)) {
26+
const binding = getVariableDeclarationBinding(initializer);
2627
// Declaration of new variable
27-
if (ts.isArrayBindingPattern(initializer.declarations[0].name)) {
28-
if (initializer.declarations[0].name.elements.length === 0) {
28+
if (ts.isArrayBindingPattern(binding)) {
29+
if (binding.elements.length === 0) {
2930
// Ignore empty destructuring assignment
3031
return undefined;
3132
}
3233

3334
expression = createUnpackCall(context, expression, initializer);
34-
} else if (ts.isObjectBindingPattern(initializer.declarations[0].name)) {
35+
} else if (ts.isObjectBindingPattern(binding)) {
3536
throw UnsupportedObjectDestructuringInForOf(initializer);
3637
}
3738

@@ -90,19 +91,19 @@ function transformForRangeStatement(
9091
throw InvalidForRangeCall(statement.initializer, "@forRange loop must declare its own control variable.");
9192
}
9293

93-
const controlDeclaration = statement.initializer.declarations[0];
94-
if (!ts.isIdentifier(controlDeclaration.name)) {
94+
const binding = getVariableDeclarationBinding(statement.initializer);
95+
if (!ts.isIdentifier(binding)) {
9596
throw InvalidForRangeCall(statement.initializer, "@forRange loop cannot use destructuring.");
9697
}
9798

98-
if (!isNumberType(context, context.checker.getTypeAtLocation(controlDeclaration))) {
99+
if (!isNumberType(context, context.checker.getTypeAtLocation(binding))) {
99100
throw InvalidForRangeCall(
100101
statement.expression,
101102
"@forRange function must return Iterable<number> or Array<number>."
102103
);
103104
}
104105

105-
const control = transformIdentifier(context, controlDeclaration.name);
106+
const control = transformIdentifier(context, binding);
106107
const signature = context.checker.getResolvedSignature(statement.expression);
107108
const [start, limit, step] = transformArguments(context, statement.expression.arguments, signature);
108109
return lua.createForStatement(block, control, start, limit, step, statement);
@@ -121,12 +122,9 @@ function transformForOfLuaIteratorStatement(
121122
if (ts.isVariableDeclarationList(statement.initializer)) {
122123
// Variables declared in for loop
123124
// for ${initializer} in ${iterable} do
124-
const initializerVariable = statement.initializer.declarations[0].name;
125-
if (ts.isArrayBindingPattern(initializerVariable)) {
126-
const identifiers = castEach(
127-
initializerVariable.elements.map(e => transformArrayBindingElement(context, e)),
128-
lua.isIdentifier
129-
);
125+
const binding = getVariableDeclarationBinding(statement.initializer);
126+
if (ts.isArrayBindingPattern(binding)) {
127+
const identifiers = binding.elements.map(e => transformArrayBindingElement(context, e));
130128
if (identifiers.length === 0) {
131129
identifiers.push(lua.createAnonymousIdentifier());
132130
}
@@ -163,6 +161,7 @@ function transformForOfLuaIteratorStatement(
163161
// LuaIterator (no TupleReturn)
164162
if (
165163
ts.isVariableDeclarationList(statement.initializer) &&
164+
statement.initializer.declarations.length > 0 &&
166165
ts.isIdentifier(statement.initializer.declarations[0].name)
167166
) {
168167
// Single variable declared in for loop
@@ -194,15 +193,15 @@ function transformForOfArrayStatement(
194193
let valueVariable: lua.Identifier;
195194
if (ts.isVariableDeclarationList(statement.initializer)) {
196195
// Declaration of new variable
197-
const variables = statement.initializer.declarations[0].name;
198-
if (ts.isArrayBindingPattern(variables) || ts.isObjectBindingPattern(variables)) {
196+
const binding = getVariableDeclarationBinding(statement.initializer);
197+
if (ts.isArrayBindingPattern(binding) || ts.isObjectBindingPattern(binding)) {
199198
valueVariable = lua.createIdentifier("____values");
200199
const initializer = transformForOfInitializer(context, statement.initializer, valueVariable);
201200
if (initializer) {
202201
block.statements.unshift(initializer);
203202
}
204203
} else {
205-
valueVariable = transformIdentifier(context, variables);
204+
valueVariable = transformIdentifier(context, binding);
206205
}
207206
} else {
208207
// Assignment to existing variable
@@ -228,13 +227,14 @@ function transformForOfIteratorStatement(
228227
const iterable = context.transformExpression(statement.expression);
229228
if (
230229
ts.isVariableDeclarationList(statement.initializer) &&
230+
statement.initializer.declarations.length > 0 &&
231231
ts.isIdentifier(statement.initializer.declarations[0].name)
232232
) {
233233
// Single variable declared in for loop
234234
// for ${initializer} in __TS__iterator(${iterator}) do
235235
return lua.createForInStatement(
236236
block,
237-
[transformIdentifier(context, statement.initializer.declarations[0].name as ts.Identifier)],
237+
[transformIdentifier(context, statement.initializer.declarations[0].name)],
238238
[transformLuaLibFunction(context, LuaLibFeature.Iterator, statement.expression, iterable)]
239239
);
240240
} else {

src/transformation/visitors/loops/for.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import * as ts from "typescript";
22
import * as lua from "../../../LuaAST";
33
import { FunctionVisitor } from "../../context";
4-
import { transformVariableDeclaration } from "../variable-declaration";
5-
import { transformLoopBody } from "./body";
4+
import { checkVariableDeclarationList, transformVariableDeclaration } from "../variable-declaration";
5+
import { transformLoopBody } from "./utils";
66

77
export const transformForStatement: FunctionVisitor<ts.ForStatement> = (statement, context) => {
88
const result: lua.Statement[] = [];
99

1010
if (statement.initializer) {
1111
if (ts.isVariableDeclarationList(statement.initializer)) {
12+
checkVariableDeclarationList(statement.initializer);
1213
// local initializer = value
1314
result.push(...statement.initializer.declarations.flatMap(d => transformVariableDeclaration(context, d)));
1415
} else {

0 commit comments

Comments
 (0)