From d3050d21e41aec4a19032f3f56713bbc1f601562 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 21 Jul 2019 19:26:24 +1000 Subject: [PATCH 01/10] Binding pattern assigment support --- src/LuaTransformer.ts | 320 +++++++++++++++++++++++++----- src/TSHelper.ts | 20 +- test/unit/bindingpatterns.spec.ts | 100 ++++++++++ 3 files changed, 381 insertions(+), 59 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 8ddaa24d3..8ac5880db 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2047,7 +2047,18 @@ export class LuaTransformer { } public transformExpressionStatement(statement: ts.ExpressionStatement | ts.Expression): StatementVisitResult { - const expression = ts.isExpressionStatement(statement) ? statement.expression : statement; + let expression: ts.Expression; + if (ts.isExpressionStatement(statement)) { + expression = statement.expression; + } else { + expression = statement; + } + + // Outer parenthesis have no effect on the expression within an ExpressionStatement + while (ts.isParenthesizedExpression(expression)) { + expression = expression.expression; + } + if (ts.isBinaryExpression(expression)) { const [isCompound, replacementOperator] = tsHelper.isBinaryAssignmentToken(expression.operatorToken.kind); if (isCompound && replacementOperator) { @@ -3024,7 +3035,18 @@ export class LuaTransformer { } } - protected transformAssignment(lhs: ts.Expression, right?: tstl.Expression): tstl.Statement { + protected transformAssignment(lhs: ts.Expression, right: tstl.Expression, parent?: ts.Expression): tstl.Statement { + if (tsHelper.isArrayLength(lhs, this.checker, this.program)) { + return tstl.createExpressionStatement( + this.transformLuaLibFunction( + LuaLibFeature.ArraySetLength, + parent, + this.transformExpression(lhs.expression), + right + ) + ); + } + return tstl.createAssignmentStatement( this.transformExpression(lhs) as tstl.AssignmentLeftHandSideExpression, right, @@ -3039,7 +3061,7 @@ export class LuaTransformer { this.validateFunctionAssignment(expression.right, rightType, leftType); this.validatePropertyAssignment(expression); - if (tsHelper.isArrayLengthAssignment(expression, this.checker, this.program)) { + if (tsHelper.isArrayLength(expression.left, this.checker, this.program)) { // array.length = x return tstl.createExpressionStatement( this.transformLuaLibFunction( @@ -3051,32 +3073,246 @@ export class LuaTransformer { ); } - if (ts.isArrayLiteralExpression(expression.left)) { + if (ts.isObjectLiteralExpression(expression.left) || ts.isArrayLiteralExpression(expression.left)) { // Destructuring assignment - const left = - expression.left.elements.length > 0 - ? expression.left.elements.map(e => this.transformArrayBindingElement(e)) - : [tstl.createAnonymousIdentifier(expression.left)]; - let right: tstl.Expression[]; - if (ts.isArrayLiteralExpression(expression.right)) { - if (expression.right.elements.length > 0) { - const visitResults = expression.right.elements.map(e => this.transformExpression(e)); - right = this.filterUndefined(visitResults); - } else { - right = [tstl.createNilLiteral()]; - } - } else if (tsHelper.isTupleReturnCall(expression.right, this.checker)) { - right = [this.transformExpression(expression.right)]; - } else { - right = [this.createUnpackCall(this.transformExpression(expression.right), expression.right)]; + const rootIdentifier = tstl.createAnonymousIdentifier(expression.left); + + let right = this.transformExpression(expression.right); + if (tsHelper.isTupleReturnCall(expression.right, this.checker)) { + right = this.wrapInTable(right); } - return tstl.createAssignmentStatement(left as tstl.AssignmentLeftHandSideExpression[], right, expression); + + const rootDeclaration = tstl.createVariableDeclarationStatement(rootIdentifier, right); + + const statements = this.transformDestructuringAssignment( + expression as ts.DestructuringAssignment, + rootIdentifier + ); + statements.unshift(rootDeclaration); + + return statements; } else { // Simple assignment return this.transformAssignment(expression.left, this.transformExpression(expression.right)); } } + protected transformDestructuringAssignment( + node: ts.DestructuringAssignment, + root: tstl.Expression + ): tstl.Statement[] { + switch (node.left.kind) { + case ts.SyntaxKind.ObjectLiteralExpression: + return this.transformObjectDestructuringAssignment(node as ts.ObjectDestructuringAssignment, root); + case ts.SyntaxKind.ArrayLiteralExpression: + return this.transformArrayDestructuringAssignment(node as ts.ArrayDestructuringAssignment, root); + } + } + + protected transformObjectDestructuringAssignment( + node: ts.ObjectDestructuringAssignment, + root: tstl.Expression + ): tstl.Statement[] { + return this.transformObjectLiteralAssignmentPattern(node.left, root); + } + + protected transformArrayDestructuringAssignment( + node: ts.ArrayDestructuringAssignment, + root: tstl.Expression + ): tstl.Statement[] { + return this.transformArrayLiteralAssignmentPattern(node.left, root); + } + + protected transformShorthandPropertyAssignment( + node: ts.ShorthandPropertyAssignment, + root: tstl.Expression + ): tstl.Statement[] { + const result: tstl.Statement[] = []; + const assignmentVariableName = this.transformIdentifier(node.name); + const extractionIndex = tstl.createStringLiteral(node.name.text); + const variableExtractionAssignmentStatement = tstl.createAssignmentStatement( + assignmentVariableName, + tstl.createTableIndexExpression(root, extractionIndex) + ); + + result.push(variableExtractionAssignmentStatement); + + const defaultInitializer = node.objectAssignmentInitializer + ? this.transformExpression(node.objectAssignmentInitializer) + : undefined; + + if (defaultInitializer) { + const nilCondition = tstl.createBinaryExpression( + assignmentVariableName, + tstl.createNilLiteral(), + tstl.SyntaxKind.EqualityOperator + ); + + const assignment = tstl.createAssignmentStatement(assignmentVariableName, defaultInitializer); + + const ifBlock = tstl.createBlock([assignment]); + + result.push(tstl.createIfStatement(nilCondition, ifBlock, undefined, node)); + } + + return result; + } + + protected transformObjectLiteralAssignmentPattern( + node: ts.ObjectLiteralExpression, + root: tstl.Expression + ): tstl.Statement[] { + const result: tstl.Statement[] = []; + + for (const property of node.properties) { + switch (property.kind) { + case ts.SyntaxKind.ShorthandPropertyAssignment: + result.push(...this.transformShorthandPropertyAssignment(property, root)); + break; + case ts.SyntaxKind.PropertyAssignment: + result.push(...this.transformPropertyAssignment(property, root)); + break; + case ts.SyntaxKind.SpreadAssignment: + throw TSTLErrors.ForbiddenEllipsisDestruction(property); + default: + throw TSTLErrors.UnsupportedKind("Object Destructure Property", property.kind, property); + } + } + + return result; + } + + protected transformArrayLiteralAssignmentPattern( + node: ts.ArrayLiteralExpression, + root: tstl.Expression + ): tstl.Statement[] { + const result: tstl.Statement[] = []; + + node.elements.forEach((element, index) => { + const indexedRoot = tstl.createTableIndexExpression( + root as tstl.Expression, + tstl.createNumericLiteral(index + 1), + element + ); + + switch (element.kind) { + case ts.SyntaxKind.ObjectLiteralExpression: + result.push( + ...this.transformObjectLiteralAssignmentPattern( + element as ts.ObjectLiteralExpression, + indexedRoot + ) + ); + break; + case ts.SyntaxKind.ArrayLiteralExpression: + result.push( + ...this.transformArrayLiteralAssignmentPattern( + element as ts.ArrayLiteralExpression, + indexedRoot + ) + ); + break; + case ts.SyntaxKind.BinaryExpression: + const assignedVariable = tstl.createIdentifier("____bindingAssignmentValue"); + + const assignedVariableDeclaration = tstl.createVariableDeclarationStatement( + assignedVariable, + indexedRoot + ); + + const nilCondition = tstl.createBinaryExpression( + assignedVariable, + tstl.createNilLiteral(), + tstl.SyntaxKind.EqualityOperator + ); + + const defaultAssignmentStatement = this.transformAssignment( + (element as ts.BinaryExpression).left, + this.transformExpression((element as ts.BinaryExpression).right) + ); + + const elseAssignmentStatement = this.transformAssignment( + (element as ts.BinaryExpression).left, + assignedVariable + ); + + const ifBlock = tstl.createBlock([defaultAssignmentStatement]); + + const elseBlock = tstl.createBlock([elseAssignmentStatement]); + + const ifStatement = tstl.createIfStatement(nilCondition, ifBlock, elseBlock, node); + + result.push(assignedVariableDeclaration); + result.push(ifStatement); + break; + case ts.SyntaxKind.Identifier: + case ts.SyntaxKind.PropertyAccessExpression: + case ts.SyntaxKind.ElementAccessExpression: + const assignmentStatement = this.transformAssignment(element, indexedRoot); + + result.push(assignmentStatement); + break; + case ts.SyntaxKind.OmittedExpression: + break; + default: + throw TSTLErrors.UnsupportedKind("Array Destructure Assignment Element", element.kind, element); + } + }); + + return result; + } + + protected transformPropertyAssignment(node: ts.PropertyAssignment, root: tstl.Expression): tstl.Statement[] { + const result: tstl.Statement[] = []; + + if (ts.isObjectLiteralExpression(node.initializer) || ts.isArrayLiteralExpression(node.initializer)) { + const propertyAccessString = this.transformPropertyName(node.name); + const newRootAccess = tstl.createTableIndexExpression(root, propertyAccessString); + + if (ts.isObjectLiteralExpression(node.initializer)) { + return this.transformObjectLiteralAssignmentPattern(node.initializer, newRootAccess); + } + + if (ts.isArrayLiteralExpression(node.initializer)) { + return this.transformArrayLiteralAssignmentPattern(node.initializer, newRootAccess); + } + } + + let leftExpression: ts.Expression; + if (ts.isBinaryExpression(node.initializer)) { + leftExpression = node.initializer.left; + } else { + leftExpression = node.initializer; + } + + const variableToExtract = this.transformPropertyName(node.name); + const extractingExpression = tstl.createTableIndexExpression(root, variableToExtract); + + const destructureAssignmentStatement = this.transformAssignment(leftExpression, extractingExpression); + + result.push(destructureAssignmentStatement); + + if (ts.isBinaryExpression(node.initializer)) { + const assignmentLeftHandSide = this.transformExpression(node.initializer.left); + + const nilCondition = tstl.createBinaryExpression( + assignmentLeftHandSide, + tstl.createNilLiteral(), + tstl.SyntaxKind.EqualityOperator + ); + + const assignmentStatements = this.statementVisitResultToArray( + this.transformAssignmentStatement(node.initializer) + ); + + const ifBlock = tstl.createBlock(assignmentStatements); + + result.push(tstl.createIfStatement(nilCondition, ifBlock, undefined, node)); + } + + return result; + } + protected transformAssignmentExpression( expression: ts.BinaryExpression ): tstl.CallExpression | tstl.MethodCallExpression { @@ -3085,7 +3321,7 @@ export class LuaTransformer { const leftType = this.checker.getTypeAtLocation(expression.left); this.validateFunctionAssignment(expression.right, rightType, leftType); - if (tsHelper.isArrayLengthAssignment(expression, this.checker, this.program)) { + if (tsHelper.isArrayLength(expression.left, this.checker, this.program)) { // array.length = x return this.transformLuaLibFunction( LuaLibFeature.ArraySetLength, @@ -3095,34 +3331,24 @@ export class LuaTransformer { ); } - if (ts.isArrayLiteralExpression(expression.left)) { + if (ts.isArrayLiteralExpression(expression.left) || ts.isObjectLiteralExpression(expression.left)) { // Destructuring assignment - // (function() local ${tmps} = ${right}; ${left} = ${tmps}; return {${tmps}} end)() - const left = - expression.left.elements.length > 0 - ? expression.left.elements.map(e => this.transformExpression(e)) - : [tstl.createAnonymousIdentifier(expression.left)]; - let right: tstl.Expression[]; - if (ts.isArrayLiteralExpression(expression.right)) { - right = - expression.right.elements.length > 0 - ? expression.right.elements.map(e => this.transformExpression(e)) - : [tstl.createNilLiteral()]; - } else if (tsHelper.isTupleReturnCall(expression.right, this.checker)) { - right = [this.transformExpression(expression.right)]; - } else { - right = [this.createUnpackCall(this.transformExpression(expression.right), expression.right)]; + const rootIdentifier = tstl.createAnonymousIdentifier(expression.left); + + let right = this.transformExpression(expression.right); + if (tsHelper.isTupleReturnCall(expression.right, this.checker)) { + right = this.wrapInTable(right); } - const tmps = left.map((_, i) => tstl.createIdentifier(`____tmp${i}`)); - const statements: tstl.Statement[] = [ - tstl.createVariableDeclarationStatement(tmps, right), - tstl.createAssignmentStatement(left as tstl.AssignmentLeftHandSideExpression[], tmps), - ]; - return this.createImmediatelyInvokedFunctionExpression( - statements, - tstl.createTableExpression(tmps.map(t => tstl.createTableFieldExpression(t))), - expression + + const rootDeclaration = tstl.createVariableDeclarationStatement(rootIdentifier, right); + + const statements = this.transformDestructuringAssignment( + expression as ts.DestructuringAssignment, + rootIdentifier ); + statements.unshift(rootDeclaration); + + return this.createImmediatelyInvokedFunctionExpression(statements, rootIdentifier, expression); } if (ts.isPropertyAccessExpression(expression.left) || ts.isElementAccessExpression(expression.left)) { diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 6a9dc5a32..56dbac5ff 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -851,27 +851,23 @@ export function moduleHasEmittedBody( return false; } -export function isArrayLengthAssignment( - expression: ts.BinaryExpression, +export function isArrayLength( + expression: ts.Expression, checker: ts.TypeChecker, program: ts.Program -): expression is ts.BinaryExpression & { left: ts.PropertyAccessExpression | ts.ElementAccessExpression } { - if (expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) { +): expression is ts.PropertyAccessExpression | ts.ElementAccessExpression { + if (!ts.isPropertyAccessExpression(expression) && !ts.isElementAccessExpression(expression)) { return false; } - if (!ts.isPropertyAccessExpression(expression.left) && !ts.isElementAccessExpression(expression.left)) { - return false; - } - - const type = checker.getTypeAtLocation(expression.left.expression); + const type = checker.getTypeAtLocation(expression.expression); if (!isArrayType(type, checker, program)) { return false; } - const name = ts.isPropertyAccessExpression(expression.left) - ? (expression.left.name.escapedText as string) - : ts.isStringLiteral(expression.left.argumentExpression) && expression.left.argumentExpression.text; + const name = ts.isPropertyAccessExpression(expression) + ? (expression.name.escapedText as string) + : ts.isStringLiteral(expression.argumentExpression) && expression.argumentExpression.text; return name === "length"; } diff --git a/test/unit/bindingpatterns.spec.ts b/test/unit/bindingpatterns.spec.ts index aad38661b..2a1e93bce 100644 --- a/test/unit/bindingpatterns.spec.ts +++ b/test/unit/bindingpatterns.spec.ts @@ -82,3 +82,103 @@ test.each([ ); expect(result).toBe(false); }); + +const assignmentBindingPatterns = [ + { bindingString: "{x: obj.prop}", objectString: "{x: true}", returnVariable: "obj.prop" }, + { + bindingString: "{x: obj.prop = true}", + objectString: "{x: undefined}", + returnVariable: "obj.prop", + }, + { bindingString: "[{x: obj.prop}]", objectString: "[{x: true}]", returnVariable: "obj.prop" }, + { + bindingString: "{obj: {prop: obj.prop}}", + objectString: "{obj: {prop: true}}", + returnVariable: "obj.prop", + }, + { bindingString: "{x = true}", objectString: "{}", returnVariable: "x" }, + { + bindingString: "{x: {[2 + 1]: y}}", + objectString: "{x: {[2 + 1]: true}}", + returnVariable: "y", + }, +]; + +test.each([...assignmentBindingPatterns, ...testCases])( + "Binding pattern expressions (%p)", + ({ bindingString, objectString, returnVariable }) => { + const result = util.transpileAndExecute(` + let x, y, z, foo, bar, obj: { prop: boolean }; + obj = { prop: false }; + (${bindingString} = ${objectString}) + return ${returnVariable}; + `); + expect(result).toBe(true); + } +); + +test.each([...assignmentBindingPatterns, ...testCases])( + "Binding patterns expressions pass conditional checks (%p)", + ({ bindingString, objectString, returnVariable }) => { + const result = util.transpileAndExecute(` + let x, y, z, foo, bar, obj: { prop: boolean }; + obj = { prop: false }; + if (${bindingString} = ${objectString}) { + return ${returnVariable}; + } + `); + expect(result).toBe(true); + } +); + +test.each([ + { bindingString: "{ x: x.prop = true } = {}", returnValue: "x.prop", expectedResult: true }, + { + bindingString: "{ x: x.prop = true } = {}", + returnValue: "typeof y === 'object'", + expectedResult: true, + }, +])("Binding pattern assignment pass-through (%p)", ({ bindingString, returnValue, expectedResult }) => { + const result = util.transpileAndExecute(` + let x: any = {}, y: any = {}; + y = ${bindingString}; + return ${returnValue}; + `); + expect(result).toBe(expectedResult); +}); + +test("Array binding pattern to assign array length (%p)", () => { + const result = util.transpileAndExecute(` + let x = [0, 1, 2]; + [x.length] = [0]; + return x.length; + `); + expect(result).toBe(0); +}); + +test("Nested array binding pattern to assign array length (%p)", () => { + const result = util.transpileAndExecute(` + let x = [0, 1, 2]; + [[x.length]] = [[0]]; + return x.length; + `); + expect(result).toBe(0); +}); + +test("Object binding pattern to assign array length (%p)", () => { + const result = util.transpileAndExecute(` + let x = [0, 1, 2]; + ({ x: x.length } = { x: 0 }); + return x.length; + `); + expect(result).toBe(0); +}); + +test("Nested object binding pattern to assign array length (%p)", () => { + const result = util.transpileAndExecute(` + let x = [0, 1, 2]; + ({ x: { x: x.length } } = { x: { x: 0 } }); + return x.length; + `); + expect(result).toBe(0); +}); From babd64f1c0b43fea97839fe0e0446d11ed7a4421 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 21 Jul 2019 20:27:43 +1000 Subject: [PATCH 02/10] Optimize destructuring for flat arrays --- src/LuaTransformer.ts | 40 +++++++++++++++++++++++++++++++++++++++- src/TSHelper.ts | 28 ++++++++++++++++++++++++++++ src/TSTLErrors.ts | 3 +++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 8ac5880db..933362057 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -3075,9 +3075,33 @@ export class LuaTransformer { if (ts.isObjectLiteralExpression(expression.left) || ts.isArrayLiteralExpression(expression.left)) { // Destructuring assignment + let right = this.transformExpression(expression.right); + + const flattenable = tsHelper.isValidFlattenableDestructuringAssignmentLeftHandSide( + expression as ts.DestructuringAssignment, + this.checker, + this.program + ); + + if (flattenable) { + const expressionType = this.checker.getTypeAtLocation(expression.right); + let right = this.transformExpression(expression.right); + + if ( + !tsHelper.isTupleReturnCall(expression.right, this.checker) && + tsHelper.isArrayType(expressionType, this.checker, this.program) + ) { + right = this.createUnpackCall(right, expression.right); + } + + return this.transformFlattenableDestructuringAssignment( + expression as ts.DestructuringAssignment, + right + ); + } + const rootIdentifier = tstl.createAnonymousIdentifier(expression.left); - let right = this.transformExpression(expression.right); if (tsHelper.isTupleReturnCall(expression.right, this.checker)) { right = this.wrapInTable(right); } @@ -3097,6 +3121,20 @@ export class LuaTransformer { } } + protected transformFlattenableDestructuringAssignment( + node: ts.DestructuringAssignment, + right: tstl.Expression | tstl.Expression[] + ): tstl.Statement { + if (ts.isArrayLiteralExpression(node.left)) { + const left: tstl.AssignmentLeftHandSideExpression[] = node.left.elements.map( + element => this.transformExpression(element) as tstl.AssignmentLeftHandSideExpression + ); + return tstl.createAssignmentStatement(left, right, node); + } + + throw TSTLErrors.NonFlattenableDestructure(node); + } + protected transformDestructuringAssignment( node: ts.DestructuringAssignment, root: tstl.Expression diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 56dbac5ff..68638c507 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -851,6 +851,34 @@ export function moduleHasEmittedBody( return false; } +export function isValidFlattenableDestructuringAssignmentLeftHandSide( + node: ts.DestructuringAssignment, + checker: ts.TypeChecker, + program: ts.Program +): boolean { + if (ts.isArrayLiteralExpression(node.left)) { + if (node.left.elements.length > 0) { + return !node.left.elements.some(element => { + switch (element.kind) { + case ts.SyntaxKind.Identifier: + case ts.SyntaxKind.PropertyAccessExpression: + if (isArrayLength(element, checker, program)) { + return true; + } + case ts.SyntaxKind.ElementAccessExpression: + // Can be on the left hand side of a Lua assignment statement + return false; + default: + // Cannot be + return true; + } + }); + } + } + + return false; +} + export function isArrayLength( expression: ts.Expression, checker: ts.TypeChecker, diff --git a/src/TSTLErrors.ts b/src/TSTLErrors.ts index cc351f2b7..054c89425 100644 --- a/src/TSTLErrors.ts +++ b/src/TSTLErrors.ts @@ -90,6 +90,9 @@ export const MissingFunctionName = (declaration: ts.FunctionLikeDeclaration) => export const MissingMetaExtension = (node: ts.Node) => new TranspileError(`@metaExtension requires the extension of the metatable class.`, node); +export const NonFlattenableDestructure = (node: ts.Node) => + new TranspileError(`This node cannot be destructured using a standard Lua assignment statement.`, node); + export const UndefinedFunctionDefinition = (functionSymbolId: number) => new Error(`Function definition for function symbol ${functionSymbolId} is undefined.`); From 863e620eed464bc2de90c56f394ebabb74bce74d Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 21 Jul 2019 20:35:32 +1000 Subject: [PATCH 03/10] Move assignment destructure rhs transform down --- src/LuaTransformer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 7008963b4..2e1060676 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -3075,8 +3075,6 @@ export class LuaTransformer { if (ts.isObjectLiteralExpression(expression.left) || ts.isArrayLiteralExpression(expression.left)) { // Destructuring assignment - let right = this.transformExpression(expression.right); - const flattenable = tsHelper.isValidFlattenableDestructuringAssignmentLeftHandSide( expression as ts.DestructuringAssignment, this.checker, @@ -3100,6 +3098,7 @@ export class LuaTransformer { ); } + let right = this.transformExpression(expression.right); const rootIdentifier = tstl.createAnonymousIdentifier(expression.left); if (tsHelper.isTupleReturnCall(expression.right, this.checker)) { From f7814c7901b7030a660ab578437e23ae96d5046a Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 23 Jul 2019 22:32:51 +1000 Subject: [PATCH 04/10] Remove unnessessary assignment array length check --- src/LuaTransformer.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 8e1ccfdb1..b97f9ede6 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -3061,18 +3061,6 @@ export class LuaTransformer { this.validateFunctionAssignment(expression.right, rightType, leftType); this.validatePropertyAssignment(expression); - if (tsHelper.isArrayLength(expression.left, this.checker, this.program)) { - // array.length = x - return tstl.createExpressionStatement( - this.transformLuaLibFunction( - LuaLibFeature.ArraySetLength, - expression, - this.transformExpression(expression.left.expression), - this.transformExpression(expression.right) - ) - ); - } - if (ts.isObjectLiteralExpression(expression.left) || ts.isArrayLiteralExpression(expression.left)) { // Destructuring assignment const flattenable = tsHelper.isValidFlattenableDestructuringAssignmentLeftHandSide( From 6d5fa6ae391df713077c40867811b70ef1ea0ed9 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 23 Jul 2019 22:41:16 +1000 Subject: [PATCH 05/10] Add default destructure array assignment test --- test/unit/bindingpatterns.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/bindingpatterns.spec.ts b/test/unit/bindingpatterns.spec.ts index 2a1e93bce..4e8ce1a7b 100644 --- a/test/unit/bindingpatterns.spec.ts +++ b/test/unit/bindingpatterns.spec.ts @@ -18,6 +18,7 @@ const testCases = [ const testCasesDefault = [ { bindingString: "{x = true}", objectString: "{}", returnVariable: "x" }, { bindingString: "{x, y = true}", objectString: "{x: false}", returnVariable: "y" }, + { bindingString: "[x = true, y = false]", objectString: "[undefined, undefined]", returnVariable: "x" }, ]; test.each([ From 6609d550396240c1ba9c6c688e2b918146abe682 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 24 Jul 2019 06:49:49 +1000 Subject: [PATCH 06/10] Add destructure non-default destructure array test --- test/unit/bindingpatterns.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/bindingpatterns.spec.ts b/test/unit/bindingpatterns.spec.ts index 4e8ce1a7b..09a30a582 100644 --- a/test/unit/bindingpatterns.spec.ts +++ b/test/unit/bindingpatterns.spec.ts @@ -19,6 +19,7 @@ const testCasesDefault = [ { bindingString: "{x = true}", objectString: "{}", returnVariable: "x" }, { bindingString: "{x, y = true}", objectString: "{x: false}", returnVariable: "y" }, { bindingString: "[x = true, y = false]", objectString: "[undefined, undefined]", returnVariable: "x" }, + { bindingString: "[x = false, y = false]", objectString: "[false, true]", returnVariable: "y" }, ]; test.each([ From efcb6d550197006f72430c212ac83203b9823576 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 24 Jul 2019 06:58:42 +1000 Subject: [PATCH 07/10] Add non-default destructure binary expression test --- test/unit/bindingpatterns.spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/unit/bindingpatterns.spec.ts b/test/unit/bindingpatterns.spec.ts index 09a30a582..39684ae50 100644 --- a/test/unit/bindingpatterns.spec.ts +++ b/test/unit/bindingpatterns.spec.ts @@ -140,6 +140,11 @@ test.each([ returnValue: "typeof y === 'object'", expectedResult: true, }, + { + bindingString: "{ x: x.prop = false } = { x: true }", + returnValue: "x.prop", + expectedResult: true, + }, ])("Binding pattern assignment pass-through (%p)", ({ bindingString, returnValue, expectedResult }) => { const result = util.transpileAndExecute(` let x: any = {}, y: any = {}; From e07ebf72bfa0df5ed7d1acdf5ff10491e4bd2a36 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sat, 27 Jul 2019 08:43:04 +1000 Subject: [PATCH 08/10] Add tsHelper.isAssignmentPattern --- src/LuaTransformer.ts | 6 +++--- src/TSHelper.ts | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index e0f7e4e90..7ed3dce3e 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -3065,7 +3065,7 @@ export class LuaTransformer { this.validateFunctionAssignment(expression.right, rightType, leftType); this.validatePropertyAssignment(expression); - if (ts.isObjectLiteralExpression(expression.left) || ts.isArrayLiteralExpression(expression.left)) { + if (tsHelper.isAssignmentPattern(expression.left)) { // Destructuring assignment const flattenable = tsHelper.isValidFlattenableDestructuringAssignmentLeftHandSide( expression as ts.DestructuringAssignment, @@ -3294,7 +3294,7 @@ export class LuaTransformer { protected transformPropertyAssignment(node: ts.PropertyAssignment, root: tstl.Expression): tstl.Statement[] { const result: tstl.Statement[] = []; - if (ts.isObjectLiteralExpression(node.initializer) || ts.isArrayLiteralExpression(node.initializer)) { + if (tsHelper.isAssignmentPattern(node.initializer)) { const propertyAccessString = this.transformPropertyName(node.name); const newRootAccess = tstl.createTableIndexExpression(root, propertyAccessString); @@ -3360,7 +3360,7 @@ export class LuaTransformer { ); } - if (ts.isArrayLiteralExpression(expression.left) || ts.isObjectLiteralExpression(expression.left)) { + if (tsHelper.isAssignmentPattern(expression.left)) { // Destructuring assignment const rootIdentifier = tstl.createAnonymousIdentifier(expression.left); diff --git a/src/TSHelper.ts b/src/TSHelper.ts index ea8a6f07c..a95b14d01 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -53,6 +53,10 @@ export function getExtendedType(node: ts.ClassLikeDeclarationBase, checker: ts.T return extendedTypeNode && checker.getTypeAtLocation(extendedTypeNode); } +export function isAssignmentPattern(node: ts.Node): node is ts.AssignmentPattern { + return ts.isObjectLiteralExpression(node) || ts.isArrayLiteralExpression(node); +} + export function isFileModule(sourceFile: ts.SourceFile): boolean { return sourceFile.statements.some(isStatementExported); } From eb6dd9ae0af6d92dd068854ff4c51e5228702fb6 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sat, 27 Jul 2019 09:25:59 +1000 Subject: [PATCH 09/10] Revert transformExpressionStatement change --- src/LuaTransformer.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 7ed3dce3e..32d097aa1 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2055,17 +2055,7 @@ export class LuaTransformer { } public transformExpressionStatement(statement: ts.ExpressionStatement | ts.Expression): StatementVisitResult { - let expression: ts.Expression; - if (ts.isExpressionStatement(statement)) { - expression = statement.expression; - } else { - expression = statement; - } - - // Outer parenthesis have no effect on the expression within an ExpressionStatement - while (ts.isParenthesizedExpression(expression)) { - expression = expression.expression; - } + const expression = ts.isExpressionStatement(statement) ? statement.expression : statement; if (ts.isBinaryExpression(expression)) { const [isCompound, replacementOperator] = tsHelper.isBinaryAssignmentToken(expression.operatorToken.kind); From f4950253d4823b07f04e7394aa84db924257de5f Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sat, 27 Jul 2019 09:28:12 +1000 Subject: [PATCH 10/10] Revert whitespace change in transformExpressionStatement --- src/LuaTransformer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 32d097aa1..a03700519 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2056,7 +2056,6 @@ export class LuaTransformer { public transformExpressionStatement(statement: ts.ExpressionStatement | ts.Expression): StatementVisitResult { const expression = ts.isExpressionStatement(statement) ? statement.expression : statement; - if (ts.isBinaryExpression(expression)) { const [isCompound, replacementOperator] = tsHelper.isBinaryAssignmentToken(expression.operatorToken.kind); if (isCompound && replacementOperator) {