From ac391cdb36fadb0a47293e82e0a79c46513d3f19 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Wed, 28 Aug 2019 14:32:24 +0200 Subject: [PATCH 1/3] Fix busted for-in declaration --- src/LuaTransformer.ts | 19 +++++++++++++++---- src/TSTLErrors.ts | 3 +++ test/unit/loops.spec.ts | 13 +++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index e060b6ad3..ce4393f2b 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2628,9 +2628,20 @@ 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; + // Transform iteration variable + let iterationVariable: tstl.Identifier; + if ( + ts.isVariableDeclarationList(statement.initializer) && + !ts.isArrayBindingPattern(statement.initializer.declarations[0].name) && + !ts.isObjectBindingPattern(statement.initializer.declarations[0].name) + ) { + iterationVariable = this.transformIdentifier(statement.initializer.declarations[0].name); + } else if (ts.isIdentifier(statement.initializer)) { + iterationVariable = this.transformIdentifier(statement.initializer); + } else { + // This should never occur + throw TSTLErrors.UnsupportedForInVariable(statement.initializer); + } // Transpile expression const pairsIdentifier = tstl.createIdentifier("pairs"); @@ -2643,7 +2654,7 @@ export class LuaTransformer { const body = tstl.createBlock(this.transformLoopBody(statement)); - return tstl.createForInStatement(body, [this.transformIdentifier(identifier)], [pairsCall], statement); + 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..a9a0c7200 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -630,3 +630,16 @@ 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(); +}); From be68c942aa96e5ac9b3fed963ec88dcfcb206a3b Mon Sep 17 00:00:00 2001 From: Perryvw Date: Wed, 28 Aug 2019 22:05:10 +0200 Subject: [PATCH 2/3] Fixed scoping issue once the for-in loop is finished --- src/LuaTransformer.ts | 36 ++++++++++++++++++++++-------------- test/unit/loops.spec.ts | 11 +++++++++++ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index ce4393f2b..1745083e4 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2628,32 +2628,40 @@ export class LuaTransformer { } public transformForInStatement(statement: ts.ForInStatement): StatementVisitResult { + // Transpile expression + const pairsIdentifier = tstl.createIdentifier("pairs"); + const expression = this.transformExpression(statement.expression); + const pairsCall = tstl.createCallExpression(pairsIdentifier, [expression]); + + if (tsHelper.isArrayType(this.checker.getTypeAtLocation(statement.expression), this.checker, this.program)) { + throw TSTLErrors.ForbiddenForIn(statement); + } + + const body = tstl.createBlock(this.transformLoopBody(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.isArrayBindingPattern(statement.initializer.declarations[0].name) && - !ts.isObjectBindingPattern(statement.initializer.declarations[0].name) + ts.isIdentifier(statement.initializer.declarations[0].name) ) { iterationVariable = this.transformIdentifier(statement.initializer.declarations[0].name); } else if (ts.isIdentifier(statement.initializer)) { - iterationVariable = this.transformIdentifier(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.splice(0, 0, initializer); } else { // This should never occur throw TSTLErrors.UnsupportedForInVariable(statement.initializer); } - // Transpile expression - const pairsIdentifier = tstl.createIdentifier("pairs"); - const expression = this.transformExpression(statement.expression); - const pairsCall = tstl.createCallExpression(pairsIdentifier, [expression]); - - if (tsHelper.isArrayType(this.checker.getTypeAtLocation(statement.expression), this.checker, this.program)) { - throw TSTLErrors.ForbiddenForIn(statement); - } - - const body = tstl.createBlock(this.transformLoopBody(statement)); - return tstl.createForInStatement(body, [iterationVariable], [pairsCall], statement); } diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index a9a0c7200..4d50dca20 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -643,3 +643,14 @@ test("for...in with pre-defined variable", () => { 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(); +}); From 6dc456892c7abafd137cba825348f09594397f5a Mon Sep 17 00:00:00 2001 From: Perry van Wesel Date: Wed, 28 Aug 2019 22:10:09 +0200 Subject: [PATCH 3/3] replaced splice with unshift Co-Authored-By: ark120202 --- src/LuaTransformer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 1745083e4..3c9fb2c89 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2656,7 +2656,7 @@ export class LuaTransformer { this.transformIdentifier(statement.initializer), iterationVariable ); - body.statements.splice(0, 0, initializer); + body.statements.unshift(initializer); } else { // This should never occur throw TSTLErrors.UnsupportedForInVariable(statement.initializer);