Skip to content

Commit 908db36

Browse files
authored
Using ipairs for for...of loops (#595)
Extracted from #585 Also includes: - Throwing error on attempts to use object destructuring in for...of loops - Fixed issue with sourcemaps in lua for...in loops
1 parent 3d7833e commit 908db36

File tree

6 files changed

+74
-42
lines changed

6 files changed

+74
-42
lines changed

src/LuaPrinter.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,12 +390,12 @@ export class LuaPrinter {
390390
}
391391

392392
public printForInStatement(statement: tstl.ForInStatement): SourceNode {
393-
const names = statement.names.map(i => this.printIdentifier(i)).join(", ");
394-
const expressions = statement.expressions.map(e => this.printExpression(e)).join(", ");
393+
const names = this.joinChunks(", ", statement.names.map(i => this.printIdentifier(i)));
394+
const expressions = this.joinChunks(", ", statement.expressions.map(e => this.printExpression(e)));
395395

396396
const chunks: SourceChunk[] = [];
397397

398-
chunks.push(this.indent("for "), names, " in ", expressions, " do\n");
398+
chunks.push(this.indent("for "), ...names, " in ", ...expressions, " do\n");
399399

400400
this.pushIndent();
401401
chunks.push(this.printBlock(statement.body));

src/LuaTransformer.ts

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2284,6 +2284,9 @@ export class LuaTransformer {
22842284
const variableDeclarations = this.transformVariableDeclaration(initializer.declarations[0]);
22852285
if (ts.isArrayBindingPattern(initializer.declarations[0].name)) {
22862286
expression = this.createUnpackCall(expression, initializer);
2287+
2288+
} else if (ts.isObjectBindingPattern(initializer.declarations[0].name)) {
2289+
throw TSTLErrors.UnsupportedObjectDestructuringInForOf(initializer);
22872290
}
22882291

22892292
const variableStatements = this.statementVisitResultToArray(variableDeclarations);
@@ -2302,6 +2305,10 @@ export class LuaTransformer {
23022305
expression = this.createUnpackCall(expression, initializer);
23032306
variables = initializer.elements
23042307
.map(e => this.transformExpression(e)) as tstl.AssignmentLeftHandSideExpression[];
2308+
2309+
} else if (ts.isObjectLiteralExpression(initializer)) {
2310+
throw TSTLErrors.UnsupportedObjectDestructuringInForOf(initializer);
2311+
23052312
} else {
23062313
variables = this.transformExpression(initializer) as tstl.AssignmentLeftHandSideExpression;
23072314
}
@@ -2336,43 +2343,35 @@ export class LuaTransformer {
23362343
}
23372344

23382345
private transformForOfArrayStatement(statement: ts.ForOfStatement, block: tstl.Block): StatementVisitResult {
2339-
const arrayExpression = this.transformExpression(statement.expression);
2340-
2341-
// Arrays use numeric for loop (performs better than ipairs)
2342-
const indexVariable = tstl.createIdentifier("____TS_index");
2343-
if (!ts.isIdentifier(statement.expression)) {
2344-
// Cache iterable expression if it's not a simple identifier
2345-
// local ____TS_array = ${iterable};
2346-
// for ____TS_index = 1, #____TS_array do
2347-
// local ${initializer} = ____TS_array[____TS_index]
2348-
const arrayVariable = tstl.createIdentifier("____TS_array", statement.expression);
2349-
const arrayAccess = tstl.createTableIndexExpression(arrayVariable, indexVariable);
2350-
const initializer = this.transformForOfInitializer(statement.initializer, arrayAccess);
2351-
block.statements.splice(0, 0, initializer);
2352-
return [
2353-
tstl.createVariableDeclarationStatement(arrayVariable, arrayExpression),
2354-
tstl.createForStatement(
2355-
block,
2356-
indexVariable,
2357-
tstl.createNumericLiteral(1),
2358-
tstl.createUnaryExpression(arrayVariable, tstl.SyntaxKind.LengthOperator)
2359-
),
2360-
];
2346+
let valueVariable: tstl.Identifier;
2347+
if (ts.isVariableDeclarationList(statement.initializer)) {
2348+
// Declaration of new variable
2349+
const variables = statement.initializer.declarations[0].name;
2350+
if (ts.isArrayBindingPattern(variables) || ts.isObjectBindingPattern(variables)) {
2351+
valueVariable = tstl.createIdentifier("____TS_values");
2352+
block.statements.unshift(this.transformForOfInitializer(statement.initializer, valueVariable));
2353+
2354+
} else {
2355+
valueVariable = this.transformIdentifier(variables);
2356+
}
23612357

23622358
} else {
2363-
// Simple identifier version
2364-
// for ____TS_index = 1, #${iterable} do
2365-
// local ${initializer} = ${iterable}[____TS_index]
2366-
const iterableAccess = tstl.createTableIndexExpression(arrayExpression, indexVariable);
2367-
const initializer = this.transformForOfInitializer(statement.initializer, iterableAccess);
2368-
block.statements.splice(0, 0, initializer);
2369-
return tstl.createForStatement(
2370-
block,
2371-
indexVariable,
2372-
tstl.createNumericLiteral(1),
2373-
tstl.createUnaryExpression(arrayExpression, tstl.SyntaxKind.LengthOperator)
2374-
);
2359+
// Assignment to existing variable
2360+
valueVariable = tstl.createIdentifier("____TS_value");
2361+
block.statements.unshift(this.transformForOfInitializer(statement.initializer, valueVariable));
23752362
}
2363+
2364+
const ipairsCall = tstl.createCallExpression(
2365+
tstl.createIdentifier("ipairs"),
2366+
[this.transformExpression(statement.expression)]
2367+
);
2368+
2369+
return tstl.createForInStatement(
2370+
block,
2371+
[tstl.createAnonymousIdentifier(), valueVariable],
2372+
[ipairsCall],
2373+
statement
2374+
);
23762375
}
23772376

23782377
private transformForOfLuaIteratorStatement(statement: ts.ForOfStatement, block: tstl.Block): StatementVisitResult {

src/TSTLErrors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ export class TSTLErrors {
191191
);
192192
};
193193

194+
public static UnsupportedObjectDestructuringInForOf = (node: ts.Node) => {
195+
return new TranspileError(`Unsupported object destructuring in for...of statement.`, node);
196+
};
197+
194198
public static InvalidAmbientLuaKeywordIdentifier = (node: ts.Identifier) => {
195199
return new TranspileError(
196200
`Invalid use of lua keyword "${node.text}" as ambient identifier name.`,

test/translation/__snapshots__/transformation.spec.ts.snap

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ end"
238238
`;
239239

240240
exports[`Transformation (forOf) 1`] = `
241-
"local ____TS_array = {
241+
"for ____, i in ipairs({
242242
1,
243243
2,
244244
3,
@@ -249,9 +249,7 @@ exports[`Transformation (forOf) 1`] = `
249249
8,
250250
9,
251251
10,
252-
}
253-
for ____TS_index = 1, #____TS_array do
254-
local i = ____TS_array[____TS_index]
252+
}) do
255253
end"
256254
`;
257255

test/unit/loops.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,37 @@ test("forof destructuring with iterator and existing variables", () => {
504504
expect(result).toBe("0a1b2c");
505505
});
506506

507+
test("forof array which modifies length", () => {
508+
const code = `
509+
const arr = ["a", "b", "c"];
510+
let result = "";
511+
for (const v of arr) {
512+
if (v === "a") {
513+
arr.push("d");
514+
}
515+
result += v;
516+
}
517+
return result;`;
518+
519+
expect(util.transpileAndExecute(code)).toBe("abcd");
520+
});
521+
522+
test.each([
523+
{ initializer: "const {a, b}", vars: "" },
524+
{ initializer: "const {a: x, b: y}", vars: "" },
525+
{ initializer: "{a, b}", vars: "let a: string, b: string;" },
526+
{ initializer: "{a: c, b: d}", vars: "let c: string, d: string;" },
527+
])("forof object destructuring (%p)", ({ initializer, vars }) => {
528+
const code = `
529+
declare const arr: {a: string, b: string}[];
530+
${vars}
531+
for (${initializer} of arr) {}`;
532+
533+
expect(() => util.transpileString(code)).toThrow(
534+
TSTLErrors.UnsupportedObjectDestructuringInForOf(ts.createEmptyStatement()).message,
535+
);
536+
});
537+
507538
test("forof with array typed as iterable", () => {
508539
const code = `
509540
function foo(): Iterable<string> {

test/unit/sourcemaps.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ test.each([
102102
`,
103103

104104
assertPatterns: [
105+
{ luaPattern: "for", typeScriptPattern: "for" },
105106
{ luaPattern: "getArr()", typeScriptPattern: "getArr()" },
106-
{ luaPattern: "____TS_array", typeScriptPattern: "getArr()" },
107107
{ luaPattern: "element", typeScriptPattern: "element" },
108108
],
109109
},

0 commit comments

Comments
 (0)