diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index e060b6ad3..3c9fb2c89 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -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); @@ -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 { diff --git a/src/TSTLErrors.ts b/src/TSTLErrors.ts index 7318b9cd0..89867e01d 100644 --- a/src/TSTLErrors.ts +++ b/src/TSTLErrors.ts @@ -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); diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index 34e92998a..4d50dca20 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -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; + `.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(); +});