Skip to content

Commit d7f76fb

Browse files
hazzard993Perryvw
authored andcommitted
Assignment binding patterns (#677)
* Binding pattern assigment support * Optimize destructuring for flat arrays * Move assignment destructure rhs transform down * Remove unnessessary assignment array length check * Add default destructure array assignment test * Add destructure non-default destructure array test * Add non-default destructure binary expression test * Add tsHelper.isAssignmentPattern * Revert transformExpressionStatement change * Revert whitespace change in transformExpressionStatement
1 parent ef7e027 commit d7f76fb

File tree

4 files changed

+443
-65
lines changed

4 files changed

+443
-65
lines changed

src/LuaTransformer.ts

Lines changed: 295 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3032,7 +3032,18 @@ export class LuaTransformer {
30323032
}
30333033
}
30343034

3035-
protected transformAssignment(lhs: ts.Expression, right?: tstl.Expression): tstl.Statement {
3035+
protected transformAssignment(lhs: ts.Expression, right: tstl.Expression, parent?: ts.Expression): tstl.Statement {
3036+
if (tsHelper.isArrayLength(lhs, this.checker, this.program)) {
3037+
return tstl.createExpressionStatement(
3038+
this.transformLuaLibFunction(
3039+
LuaLibFeature.ArraySetLength,
3040+
parent,
3041+
this.transformExpression(lhs.expression),
3042+
right
3043+
)
3044+
);
3045+
}
3046+
30363047
return tstl.createAssignmentStatement(
30373048
this.transformExpression(lhs) as tstl.AssignmentLeftHandSideExpression,
30383049
right,
@@ -3047,44 +3058,283 @@ export class LuaTransformer {
30473058
this.validateFunctionAssignment(expression.right, rightType, leftType);
30483059
this.validatePropertyAssignment(expression);
30493060

3050-
if (tsHelper.isArrayLengthAssignment(expression, this.checker, this.program)) {
3051-
// array.length = x
3052-
return tstl.createExpressionStatement(
3053-
this.transformLuaLibFunction(
3054-
LuaLibFeature.ArraySetLength,
3055-
expression,
3056-
this.transformExpression(expression.left.expression),
3057-
this.transformExpression(expression.right)
3058-
)
3061+
if (tsHelper.isAssignmentPattern(expression.left)) {
3062+
// Destructuring assignment
3063+
const flattenable = tsHelper.isValidFlattenableDestructuringAssignmentLeftHandSide(
3064+
expression as ts.DestructuringAssignment,
3065+
this.checker,
3066+
this.program
30593067
);
3060-
}
30613068

3062-
if (ts.isArrayLiteralExpression(expression.left)) {
3063-
// Destructuring assignment
3064-
const left =
3065-
expression.left.elements.length > 0
3066-
? expression.left.elements.map(e => this.transformArrayBindingElement(e))
3067-
: [tstl.createAnonymousIdentifier(expression.left)];
3068-
let right: tstl.Expression[];
3069-
if (ts.isArrayLiteralExpression(expression.right)) {
3070-
if (expression.right.elements.length > 0) {
3071-
const visitResults = expression.right.elements.map(e => this.transformExpression(e));
3072-
right = this.filterUndefined(visitResults);
3073-
} else {
3074-
right = [tstl.createNilLiteral()];
3069+
if (flattenable) {
3070+
const expressionType = this.checker.getTypeAtLocation(expression.right);
3071+
let right = this.transformExpression(expression.right);
3072+
3073+
if (
3074+
!tsHelper.isTupleReturnCall(expression.right, this.checker) &&
3075+
tsHelper.isArrayType(expressionType, this.checker, this.program)
3076+
) {
3077+
right = this.createUnpackCall(right, expression.right);
30753078
}
3076-
} else if (tsHelper.isTupleReturnCall(expression.right, this.checker)) {
3077-
right = [this.transformExpression(expression.right)];
3078-
} else {
3079-
right = [this.createUnpackCall(this.transformExpression(expression.right), expression.right)];
3079+
3080+
return this.transformFlattenableDestructuringAssignment(
3081+
expression as ts.DestructuringAssignment,
3082+
right
3083+
);
3084+
}
3085+
3086+
let right = this.transformExpression(expression.right);
3087+
const rootIdentifier = tstl.createAnonymousIdentifier(expression.left);
3088+
3089+
if (tsHelper.isTupleReturnCall(expression.right, this.checker)) {
3090+
right = this.wrapInTable(right);
30803091
}
3081-
return tstl.createAssignmentStatement(left as tstl.AssignmentLeftHandSideExpression[], right, expression);
3092+
3093+
const rootDeclaration = tstl.createVariableDeclarationStatement(rootIdentifier, right);
3094+
3095+
const statements = this.transformDestructuringAssignment(
3096+
expression as ts.DestructuringAssignment,
3097+
rootIdentifier
3098+
);
3099+
statements.unshift(rootDeclaration);
3100+
3101+
return statements;
30823102
} else {
30833103
// Simple assignment
30843104
return this.transformAssignment(expression.left, this.transformExpression(expression.right));
30853105
}
30863106
}
30873107

3108+
protected transformFlattenableDestructuringAssignment(
3109+
node: ts.DestructuringAssignment,
3110+
right: tstl.Expression | tstl.Expression[]
3111+
): tstl.Statement {
3112+
if (ts.isArrayLiteralExpression(node.left)) {
3113+
const left: tstl.AssignmentLeftHandSideExpression[] = node.left.elements.map(
3114+
element => this.transformExpression(element) as tstl.AssignmentLeftHandSideExpression
3115+
);
3116+
return tstl.createAssignmentStatement(left, right, node);
3117+
}
3118+
3119+
throw TSTLErrors.NonFlattenableDestructure(node);
3120+
}
3121+
3122+
protected transformDestructuringAssignment(
3123+
node: ts.DestructuringAssignment,
3124+
root: tstl.Expression
3125+
): tstl.Statement[] {
3126+
switch (node.left.kind) {
3127+
case ts.SyntaxKind.ObjectLiteralExpression:
3128+
return this.transformObjectDestructuringAssignment(node as ts.ObjectDestructuringAssignment, root);
3129+
case ts.SyntaxKind.ArrayLiteralExpression:
3130+
return this.transformArrayDestructuringAssignment(node as ts.ArrayDestructuringAssignment, root);
3131+
}
3132+
}
3133+
3134+
protected transformObjectDestructuringAssignment(
3135+
node: ts.ObjectDestructuringAssignment,
3136+
root: tstl.Expression
3137+
): tstl.Statement[] {
3138+
return this.transformObjectLiteralAssignmentPattern(node.left, root);
3139+
}
3140+
3141+
protected transformArrayDestructuringAssignment(
3142+
node: ts.ArrayDestructuringAssignment,
3143+
root: tstl.Expression
3144+
): tstl.Statement[] {
3145+
return this.transformArrayLiteralAssignmentPattern(node.left, root);
3146+
}
3147+
3148+
protected transformShorthandPropertyAssignment(
3149+
node: ts.ShorthandPropertyAssignment,
3150+
root: tstl.Expression
3151+
): tstl.Statement[] {
3152+
const result: tstl.Statement[] = [];
3153+
const assignmentVariableName = this.transformIdentifier(node.name);
3154+
const extractionIndex = tstl.createStringLiteral(node.name.text);
3155+
const variableExtractionAssignmentStatement = tstl.createAssignmentStatement(
3156+
assignmentVariableName,
3157+
tstl.createTableIndexExpression(root, extractionIndex)
3158+
);
3159+
3160+
result.push(variableExtractionAssignmentStatement);
3161+
3162+
const defaultInitializer = node.objectAssignmentInitializer
3163+
? this.transformExpression(node.objectAssignmentInitializer)
3164+
: undefined;
3165+
3166+
if (defaultInitializer) {
3167+
const nilCondition = tstl.createBinaryExpression(
3168+
assignmentVariableName,
3169+
tstl.createNilLiteral(),
3170+
tstl.SyntaxKind.EqualityOperator
3171+
);
3172+
3173+
const assignment = tstl.createAssignmentStatement(assignmentVariableName, defaultInitializer);
3174+
3175+
const ifBlock = tstl.createBlock([assignment]);
3176+
3177+
result.push(tstl.createIfStatement(nilCondition, ifBlock, undefined, node));
3178+
}
3179+
3180+
return result;
3181+
}
3182+
3183+
protected transformObjectLiteralAssignmentPattern(
3184+
node: ts.ObjectLiteralExpression,
3185+
root: tstl.Expression
3186+
): tstl.Statement[] {
3187+
const result: tstl.Statement[] = [];
3188+
3189+
for (const property of node.properties) {
3190+
switch (property.kind) {
3191+
case ts.SyntaxKind.ShorthandPropertyAssignment:
3192+
result.push(...this.transformShorthandPropertyAssignment(property, root));
3193+
break;
3194+
case ts.SyntaxKind.PropertyAssignment:
3195+
result.push(...this.transformPropertyAssignment(property, root));
3196+
break;
3197+
case ts.SyntaxKind.SpreadAssignment:
3198+
throw TSTLErrors.ForbiddenEllipsisDestruction(property);
3199+
default:
3200+
throw TSTLErrors.UnsupportedKind("Object Destructure Property", property.kind, property);
3201+
}
3202+
}
3203+
3204+
return result;
3205+
}
3206+
3207+
protected transformArrayLiteralAssignmentPattern(
3208+
node: ts.ArrayLiteralExpression,
3209+
root: tstl.Expression
3210+
): tstl.Statement[] {
3211+
const result: tstl.Statement[] = [];
3212+
3213+
node.elements.forEach((element, index) => {
3214+
const indexedRoot = tstl.createTableIndexExpression(
3215+
root as tstl.Expression,
3216+
tstl.createNumericLiteral(index + 1),
3217+
element
3218+
);
3219+
3220+
switch (element.kind) {
3221+
case ts.SyntaxKind.ObjectLiteralExpression:
3222+
result.push(
3223+
...this.transformObjectLiteralAssignmentPattern(
3224+
element as ts.ObjectLiteralExpression,
3225+
indexedRoot
3226+
)
3227+
);
3228+
break;
3229+
case ts.SyntaxKind.ArrayLiteralExpression:
3230+
result.push(
3231+
...this.transformArrayLiteralAssignmentPattern(
3232+
element as ts.ArrayLiteralExpression,
3233+
indexedRoot
3234+
)
3235+
);
3236+
break;
3237+
case ts.SyntaxKind.BinaryExpression:
3238+
const assignedVariable = tstl.createIdentifier("____bindingAssignmentValue");
3239+
3240+
const assignedVariableDeclaration = tstl.createVariableDeclarationStatement(
3241+
assignedVariable,
3242+
indexedRoot
3243+
);
3244+
3245+
const nilCondition = tstl.createBinaryExpression(
3246+
assignedVariable,
3247+
tstl.createNilLiteral(),
3248+
tstl.SyntaxKind.EqualityOperator
3249+
);
3250+
3251+
const defaultAssignmentStatement = this.transformAssignment(
3252+
(element as ts.BinaryExpression).left,
3253+
this.transformExpression((element as ts.BinaryExpression).right)
3254+
);
3255+
3256+
const elseAssignmentStatement = this.transformAssignment(
3257+
(element as ts.BinaryExpression).left,
3258+
assignedVariable
3259+
);
3260+
3261+
const ifBlock = tstl.createBlock([defaultAssignmentStatement]);
3262+
3263+
const elseBlock = tstl.createBlock([elseAssignmentStatement]);
3264+
3265+
const ifStatement = tstl.createIfStatement(nilCondition, ifBlock, elseBlock, node);
3266+
3267+
result.push(assignedVariableDeclaration);
3268+
result.push(ifStatement);
3269+
break;
3270+
case ts.SyntaxKind.Identifier:
3271+
case ts.SyntaxKind.PropertyAccessExpression:
3272+
case ts.SyntaxKind.ElementAccessExpression:
3273+
const assignmentStatement = this.transformAssignment(element, indexedRoot);
3274+
3275+
result.push(assignmentStatement);
3276+
break;
3277+
case ts.SyntaxKind.OmittedExpression:
3278+
break;
3279+
default:
3280+
throw TSTLErrors.UnsupportedKind("Array Destructure Assignment Element", element.kind, element);
3281+
}
3282+
});
3283+
3284+
return result;
3285+
}
3286+
3287+
protected transformPropertyAssignment(node: ts.PropertyAssignment, root: tstl.Expression): tstl.Statement[] {
3288+
const result: tstl.Statement[] = [];
3289+
3290+
if (tsHelper.isAssignmentPattern(node.initializer)) {
3291+
const propertyAccessString = this.transformPropertyName(node.name);
3292+
const newRootAccess = tstl.createTableIndexExpression(root, propertyAccessString);
3293+
3294+
if (ts.isObjectLiteralExpression(node.initializer)) {
3295+
return this.transformObjectLiteralAssignmentPattern(node.initializer, newRootAccess);
3296+
}
3297+
3298+
if (ts.isArrayLiteralExpression(node.initializer)) {
3299+
return this.transformArrayLiteralAssignmentPattern(node.initializer, newRootAccess);
3300+
}
3301+
}
3302+
3303+
let leftExpression: ts.Expression;
3304+
if (ts.isBinaryExpression(node.initializer)) {
3305+
leftExpression = node.initializer.left;
3306+
} else {
3307+
leftExpression = node.initializer;
3308+
}
3309+
3310+
const variableToExtract = this.transformPropertyName(node.name);
3311+
const extractingExpression = tstl.createTableIndexExpression(root, variableToExtract);
3312+
3313+
const destructureAssignmentStatement = this.transformAssignment(leftExpression, extractingExpression);
3314+
3315+
result.push(destructureAssignmentStatement);
3316+
3317+
if (ts.isBinaryExpression(node.initializer)) {
3318+
const assignmentLeftHandSide = this.transformExpression(node.initializer.left);
3319+
3320+
const nilCondition = tstl.createBinaryExpression(
3321+
assignmentLeftHandSide,
3322+
tstl.createNilLiteral(),
3323+
tstl.SyntaxKind.EqualityOperator
3324+
);
3325+
3326+
const assignmentStatements = this.statementVisitResultToArray(
3327+
this.transformAssignmentStatement(node.initializer)
3328+
);
3329+
3330+
const ifBlock = tstl.createBlock(assignmentStatements);
3331+
3332+
result.push(tstl.createIfStatement(nilCondition, ifBlock, undefined, node));
3333+
}
3334+
3335+
return result;
3336+
}
3337+
30883338
protected transformAssignmentExpression(
30893339
expression: ts.BinaryExpression
30903340
): tstl.CallExpression | tstl.MethodCallExpression {
@@ -3093,7 +3343,7 @@ export class LuaTransformer {
30933343
const leftType = this.checker.getTypeAtLocation(expression.left);
30943344
this.validateFunctionAssignment(expression.right, rightType, leftType);
30953345

3096-
if (tsHelper.isArrayLengthAssignment(expression, this.checker, this.program)) {
3346+
if (tsHelper.isArrayLength(expression.left, this.checker, this.program)) {
30973347
// array.length = x
30983348
return this.transformLuaLibFunction(
30993349
LuaLibFeature.ArraySetLength,
@@ -3103,34 +3353,24 @@ export class LuaTransformer {
31033353
);
31043354
}
31053355

3106-
if (ts.isArrayLiteralExpression(expression.left)) {
3356+
if (tsHelper.isAssignmentPattern(expression.left)) {
31073357
// Destructuring assignment
3108-
// (function() local ${tmps} = ${right}; ${left} = ${tmps}; return {${tmps}} end)()
3109-
const left =
3110-
expression.left.elements.length > 0
3111-
? expression.left.elements.map(e => this.transformExpression(e))
3112-
: [tstl.createAnonymousIdentifier(expression.left)];
3113-
let right: tstl.Expression[];
3114-
if (ts.isArrayLiteralExpression(expression.right)) {
3115-
right =
3116-
expression.right.elements.length > 0
3117-
? expression.right.elements.map(e => this.transformExpression(e))
3118-
: [tstl.createNilLiteral()];
3119-
} else if (tsHelper.isTupleReturnCall(expression.right, this.checker)) {
3120-
right = [this.transformExpression(expression.right)];
3121-
} else {
3122-
right = [this.createUnpackCall(this.transformExpression(expression.right), expression.right)];
3358+
const rootIdentifier = tstl.createAnonymousIdentifier(expression.left);
3359+
3360+
let right = this.transformExpression(expression.right);
3361+
if (tsHelper.isTupleReturnCall(expression.right, this.checker)) {
3362+
right = this.wrapInTable(right);
31233363
}
3124-
const tmps = left.map((_, i) => tstl.createIdentifier(`____tmp${i}`));
3125-
const statements: tstl.Statement[] = [
3126-
tstl.createVariableDeclarationStatement(tmps, right),
3127-
tstl.createAssignmentStatement(left as tstl.AssignmentLeftHandSideExpression[], tmps),
3128-
];
3129-
return this.createImmediatelyInvokedFunctionExpression(
3130-
statements,
3131-
tstl.createTableExpression(tmps.map(t => tstl.createTableFieldExpression(t))),
3132-
expression
3364+
3365+
const rootDeclaration = tstl.createVariableDeclarationStatement(rootIdentifier, right);
3366+
3367+
const statements = this.transformDestructuringAssignment(
3368+
expression as ts.DestructuringAssignment,
3369+
rootIdentifier
31333370
);
3371+
statements.unshift(rootDeclaration);
3372+
3373+
return this.createImmediatelyInvokedFunctionExpression(statements, rootIdentifier, expression);
31343374
}
31353375

31363376
if (ts.isPropertyAccessExpression(expression.left) || ts.isElementAccessExpression(expression.left)) {

0 commit comments

Comments
 (0)