diff --git a/src/transformation/visitors/binary-expression/assignments.ts b/src/transformation/visitors/binary-expression/assignments.ts index aec1ee96c..efd7180cb 100644 --- a/src/transformation/visitors/binary-expression/assignments.ts +++ b/src/transformation/visitors/binary-expression/assignments.ts @@ -107,8 +107,6 @@ function transformDestructuredAssignmentExpression( context: TransformationContext, expression: ts.DestructuringAssignment ) { - const rootIdentifier = context.createTempNameForNode(expression.right); - let [rightPrecedingStatements, right] = transformInPrecedingStatementScope(context, () => context.transformExpression(expression.right) ); @@ -117,12 +115,15 @@ function transformDestructuredAssignmentExpression( right = wrapInTable(right); } - const statements = [ - lua.createVariableDeclarationStatement(rootIdentifier, right), - ...transformDestructuringAssignment(context, expression, rootIdentifier, rightPrecedingStatements.length > 0), - ]; + const rightExpr = moveToPrecedingTemp(context, right, expression.right); + const statements = transformDestructuringAssignment( + context, + expression, + rightExpr, + rightPrecedingStatements.length > 0 + ); - return { statements, result: rootIdentifier }; + return { statements, result: rightExpr }; } export function transformAssignmentExpression( @@ -152,7 +153,6 @@ export function transformAssignmentExpression( } if (ts.isPropertyAccessExpression(expression.left) || ts.isElementAccessExpression(expression.left)) { - const tempVar = context.createTempNameForNode(expression.right); const [precedingStatements, right] = transformInPrecedingStatementScope(context, () => context.transformExpression(expression.right) ); @@ -163,12 +163,10 @@ export function transformAssignmentExpression( precedingStatements.length > 0 ); - context.addPrecedingStatements([ - ...precedingStatements, - lua.createVariableDeclarationStatement(tempVar, right, expression.right), - lua.createAssignmentStatement(left, lua.cloneIdentifier(tempVar), expression.left), - ]); - return lua.cloneIdentifier(tempVar); + context.addPrecedingStatements(precedingStatements); + const rightExpr = moveToPrecedingTemp(context, right, expression.right); + context.addPrecedingStatements(lua.createAssignmentStatement(left, rightExpr, expression.left)); + return rightExpr; } else { // Simple assignment // ${left} = ${right}; return ${left} @@ -232,24 +230,8 @@ export function transformAssignmentStatement( return [lua.createAssignmentStatement(left, right, expression)]; } - let [rightPrecedingStatements, right] = transformInPrecedingStatementScope(context, () => - context.transformExpression(expression.right) - ); - context.addPrecedingStatements(rightPrecedingStatements); - if (isMultiReturnCall(context, expression.right)) { - right = wrapInTable(right); - } - - const rootIdentifier = context.createTempNameForNode(expression.left); - return [ - lua.createVariableDeclarationStatement(rootIdentifier, right), - ...transformDestructuringAssignment( - context, - expression, - rootIdentifier, - rightPrecedingStatements.length > 0 - ), - ]; + const { statements } = transformDestructuredAssignmentExpression(context, expression); + return statements; } else { const [precedingStatements, right] = transformInPrecedingStatementScope(context, () => context.transformExpression(expression.right) diff --git a/src/transformation/visitors/expression-list.ts b/src/transformation/visitors/expression-list.ts index 67019dcf1..475108922 100644 --- a/src/transformation/visitors/expression-list.ts +++ b/src/transformation/visitors/expression-list.ts @@ -11,7 +11,12 @@ export function shouldMoveToTemp(context: TransformationContext, expression: lua return ( !lua.isLiteral(expression) && !(lua.isIdentifier(expression) && expression.symbolId === tempSymbolId) && // Treat generated temps as consts - !(tsOriginal && (isConstIdentifier(context, tsOriginal) || isOptionalContinuation(tsOriginal))) + !( + tsOriginal && + (isConstIdentifier(context, tsOriginal) || + isOptionalContinuation(tsOriginal) || + tsOriginal.kind === ts.SyntaxKind.ThisKeyword) + ) ); } diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index 9fd0f1f42..9cae749ce 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -12,6 +12,7 @@ import { createCallableTable, isFunctionTypeWithProperties } from "./function"; import { transformIdentifier } from "./identifier"; import { isMultiReturnCall } from "./language-extensions/multi"; import { transformPropertyName } from "./literal"; +import { moveToPrecedingTemp } from "./expression-list"; export function transformArrayBindingElement( context: TransformationContext, @@ -38,7 +39,7 @@ export function transformArrayBindingElement( export function transformBindingPattern( context: TransformationContext, pattern: ts.BindingPattern, - table: lua.Identifier, + table: lua.Expression, propertyAccessStack: ts.PropertyName[] = [] ): lua.Statement[] { const result: lua.Statement[] = []; @@ -165,21 +166,20 @@ export function transformBindingVariableDeclaration( ts.isBindingElement(e) && (!ts.isIdentifier(e.name) || e.dotDotDotToken); if (ts.isObjectBindingPattern(bindingPattern) || bindingPattern.elements.some(isComplexBindingElement)) { - let table: lua.Identifier; - if (initializer !== undefined && ts.isIdentifier(initializer)) { - table = transformIdentifier(context, initializer); - } else { + let table: lua.Expression; + if (initializer) { // Contain the expression in a temporary variable - if (initializer) { - table = context.createTempNameForNode(initializer); - let expression = context.transformExpression(initializer); - if (isMultiReturnCall(context, initializer)) { - expression = wrapInTable(expression); - } - statements.push(lua.createVariableDeclarationStatement(table, expression)); - } else { - table = lua.createAnonymousIdentifier(); + let expression = context.transformExpression(initializer); + if (isMultiReturnCall(context, initializer)) { + expression = wrapInTable(expression); } + const [moveStatements, movedExpr] = transformInPrecedingStatementScope(context, () => + moveToPrecedingTemp(context, expression, initializer) + ); + statements.push(...moveStatements); + table = movedExpr; + } else { + table = lua.createAnonymousIdentifier(); } statements.push(...transformBindingPattern(context, bindingPattern, table)); return statements; diff --git a/test/translation/__snapshots__/transformation.spec.ts.snap b/test/translation/__snapshots__/transformation.spec.ts.snap index 32f48994b..05bb647b8 100644 --- a/test/translation/__snapshots__/transformation.spec.ts.snap +++ b/test/translation/__snapshots__/transformation.spec.ts.snap @@ -269,7 +269,8 @@ value1 = obj.value1 value2 = obj.value2 obj2 = {value3 = 1, value4 = 2} value3 = obj2.value3 -value4 = obj2.value4 +local ____obj2_0 = obj2 +value4 = ____obj2_0.value4 function fun1(self) end fun2 = function() diff --git a/test/unit/destructuring.spec.ts b/test/unit/destructuring.spec.ts index d8fe47973..e44f8f0c3 100644 --- a/test/unit/destructuring.spec.ts +++ b/test/unit/destructuring.spec.ts @@ -74,6 +74,28 @@ test.each(testCases)("in variable declaration (%p)", ({ binding, value }) => { `.expectToMatchJsResult(); }); +test.each(testCases)("in variable declaration from const variable (%p)", ({ binding, value }) => { + util.testFunction` + let ${allBindings}; + { + const v: any = ${value}; + const ${binding} = v; + return { ${allBindings} }; + } + `.expectToMatchJsResult(); +}); + +test.each(testCases)("in variable declaration from this (%p)", ({ binding, value }) => { + util.testFunction` + let ${allBindings}; + function test(this: any) { + const ${binding} = this; + return { ${allBindings} }; + } + return test.call(${value}); + `.expectToMatchJsResult(); +}); + test.each(testCases)("in exported variable declaration (%p)", ({ binding, value }) => { util.testModule` export const ${binding} = ${value}; @@ -101,6 +123,28 @@ test.each(assignmentTestCases)("in assignment expression (%p)", ({ binding, valu `.expectToMatchJsResult(); }); +test.each(assignmentTestCases)("in assignment expression from const variable (%p)", ({ binding, value }) => { + util.testFunction` + let ${allBindings}; + const obj = { prop: false }; + const v: any = ${value}; + const expressionResult = (${binding} = v); + return { ${allBindings}, expressionResult }; + `.expectToMatchJsResult(); +}); + +test.each(assignmentTestCases)("in assignment expression from this (%p)", ({ binding, value }) => { + util.testFunction` + let ${allBindings}; + const obj = { prop: false }; + function test(this: any) { + const expressionResult = (${binding} = this); + return { ${allBindings}, obj, expressionResult }; + } + return test.call(${value}); + `.expectToMatchJsResult(); +}); + test.each(["[]", "{}"])("empty binding pattern", bindingPattern => { util.testFunction` let i = 1; diff --git a/test/unit/language-extensions/__snapshots__/multi.spec.ts.snap b/test/unit/language-extensions/__snapshots__/multi.spec.ts.snap index 6bd3922eb..98678b857 100644 --- a/test/unit/language-extensions/__snapshots__/multi.spec.ts.snap +++ b/test/unit/language-extensions/__snapshots__/multi.spec.ts.snap @@ -61,7 +61,7 @@ end" exports[`invalid $multi call (const [a = 0] = $multi()): diagnostics 1`] = `"main.ts(2,25): error TSTL: The $multi function must be called in a return statement."`; -exports[`invalid $multi call (const {} = $multi();): code 1`] = `"local _____24multi_result_0 = {{____(_G)}}"`; +exports[`invalid $multi call (const {} = $multi();): code 1`] = `"local ____temp_0 = {{____(_G)}}"`; exports[`invalid $multi call (const {} = $multi();): diagnostics 1`] = `"main.ts(2,20): error TSTL: The $multi function must be called in a return statement."`; @@ -219,10 +219,10 @@ local function multi(self, ...) return ... end local a -local _____24multi_result_0 = {____(nil, 1)} -a = _____24multi_result_0[1] +local ____temp_0 = {____(nil, 1)} +a = ____temp_0[1] ____exports.a = a -if _____24multi_result_0 then +if ____temp_0 then a = a + 1 ____exports.a = a end