Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 24 additions & 5 deletions src/LuaTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2628,10 +2628,6 @@ export class LuaTransformer {
}

public transformForInStatement(statement: ts.ForInStatement): StatementVisitResult {
// Get variable identifier
const variable = (statement.initializer as ts.VariableDeclarationList).declarations[0];
const identifier = variable.name as ts.Identifier;

// Transpile expression
const pairsIdentifier = tstl.createIdentifier("pairs");
const expression = this.transformExpression(statement.expression);
Expand All @@ -2643,7 +2639,30 @@ export class LuaTransformer {

const body = tstl.createBlock(this.transformLoopBody(statement));

return tstl.createForInStatement(body, [this.transformIdentifier(identifier)], [pairsCall], statement);
// Transform iteration variable
// TODO: After the transformation pipeline refactor we should look at refactoring this together with the
// for-of initializer transformation.
let iterationVariable: tstl.Identifier;
if (
ts.isVariableDeclarationList(statement.initializer) &&
ts.isIdentifier(statement.initializer.declarations[0].name)
) {
iterationVariable = this.transformIdentifier(statement.initializer.declarations[0].name);
} else if (ts.isIdentifier(statement.initializer)) {
// Iteration variable becomes ____key
iterationVariable = tstl.createIdentifier("____key");
// Push variable = ____key to the start of the loop body to match TS scoping
const initializer = tstl.createAssignmentStatement(
this.transformIdentifier(statement.initializer),
iterationVariable
);
body.statements.unshift(initializer);
} else {
// This should never occur
throw TSTLErrors.UnsupportedForInVariable(statement.initializer);
}

return tstl.createForInStatement(body, [iterationVariable], [pairsCall], statement);
}

public transformSwitchStatement(statement: ts.SwitchStatement): StatementVisitResult {
Expand Down
3 changes: 3 additions & 0 deletions src/TSTLErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ export const NonFlattenableDestructure = (node: ts.Node) =>
export const UndefinedFunctionDefinition = (functionSymbolId: number) =>
new Error(`Function definition for function symbol ${functionSymbolId} is undefined.`);

export const UnsupportedForInVariable = (initializer: ts.Node) =>
new TranspileError(`Unsuppored for-in variable kind.`, initializer);

export const UndefinedScope = () => new Error("Expected to pop a scope, but found undefined.");

export const UndefinedTypeNode = (node: ts.Node) => new TranspileError("Failed to resolve required type node.", node);
Expand Down
24 changes: 24 additions & 0 deletions test/unit/loops.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,3 +630,27 @@ test("do...while double-negation", () => {
expect(util.transpileString(code)).not.toMatch("not");
expect(util.transpileAndExecute(code)).toBe(2);
});

test("for...in with pre-defined variable", () => {
util.testFunction`
const obj = { x: "y", foo: "bar" };

let x = "";
let result = [];
for (x in obj) {
result.push(x);
}
return result;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since objects are compared deeply these 2 tests may be merged to return { x, result }

`.expectToMatchJsResult();
});

test("for...in with pre-defined variable keeps last value", () => {
util.testFunction`
const obj = { x: "y", foo: "bar" };

let x = "";
for (x in obj) {
}
return x;
`.expectToMatchJsResult();
});