Skip to content

Commit 17b858c

Browse files
committed
Refactor logical assignment
1 parent a6e0086 commit 17b858c

12 files changed

+47
-111
lines changed

Diff for: src/compiler/binder.ts

+23-55
Original file line numberDiff line numberDiff line change
@@ -1057,24 +1057,9 @@ namespace ts {
10571057
}
10581058
}
10591059

1060-
function isLogicalAssignmentExpressioin(node: Node) {
1061-
while (true) {
1062-
if (isParenthesizedExpression(node)) {
1063-
node = node.expression;
1064-
}
1065-
else {
1066-
return isBinaryExpression(node) && isLogicalAssignmentOperator(node.operatorToken.kind);
1067-
}
1068-
}
1069-
}
1070-
1071-
function isTopLevelLogicalAssignmentExpression(node: Node): boolean {
1072-
while (isParenthesizedExpression(node.parent)) {
1073-
node = node.parent;
1074-
}
1075-
return !isStatementCondition(node) &&
1076-
!isLogicalAssignmentExpressioin(node.parent) &&
1077-
!(isOptionalChain(node.parent) && node.parent.expression === node);
1060+
function isLogicalAssignmentExpression(node: Node) {
1061+
node = skipParentheses(node);
1062+
return isBinaryExpression(node) && isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind);
10781063
}
10791064

10801065
function isTopLevelLogicalExpression(node: Node): boolean {
@@ -1083,6 +1068,7 @@ namespace ts {
10831068
node = node.parent;
10841069
}
10851070
return !isStatementCondition(node) &&
1071+
!isLogicalAssignmentExpression(node.parent) &&
10861072
!isLogicalExpression(node.parent) &&
10871073
!(isOptionalChain(node.parent) && node.parent.expression === node);
10881074
}
@@ -1099,7 +1085,7 @@ namespace ts {
10991085

11001086
function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) {
11011087
doWithConditionalBranches(bind, node, trueTarget, falseTarget);
1102-
if (!node || !isLogicalAssignmentExpressioin(node) && !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) {
1088+
if (!node || !isLogicalAssignmentExpression(node) && !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) {
11031089
addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node));
11041090
addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node));
11051091
}
@@ -1199,24 +1185,6 @@ namespace ts {
11991185
currentFlow = finishFlowLabel(postIfLabel);
12001186
}
12011187

1202-
function bindLogicalAssignmentExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
1203-
const preRightLabel = createBranchLabel();
1204-
if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandEqualsToken) {
1205-
bindCondition(node.left, preRightLabel, falseTarget);
1206-
}
1207-
else {
1208-
bindCondition(node.left, trueTarget, preRightLabel);
1209-
}
1210-
currentFlow = finishFlowLabel(preRightLabel);
1211-
bind(node.operatorToken);
1212-
1213-
doWithConditionalBranches(bind, node.right, trueTarget, falseTarget);
1214-
bindAssignmentTargetFlow(node.left);
1215-
1216-
addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node));
1217-
addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node));
1218-
}
1219-
12201188
function bindReturnOrThrow(node: ReturnStatement | ThrowStatement): void {
12211189
bind(node.expression);
12221190
if (node.kind === SyntaxKind.ReturnStatement) {
@@ -1458,17 +1426,27 @@ namespace ts {
14581426
}
14591427
}
14601428

1461-
function bindLogicalExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
1429+
function bindLogicalLikeExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
14621430
const preRightLabel = createBranchLabel();
1463-
if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
1431+
if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === SyntaxKind.AmpersandAmpersandEqualsToken) {
14641432
bindCondition(node.left, preRightLabel, falseTarget);
14651433
}
14661434
else {
14671435
bindCondition(node.left, trueTarget, preRightLabel);
14681436
}
14691437
currentFlow = finishFlowLabel(preRightLabel);
14701438
bind(node.operatorToken);
1471-
bindCondition(node.right, trueTarget, falseTarget);
1439+
1440+
if (isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind)) {
1441+
doWithConditionalBranches(bind, node.right, trueTarget, falseTarget);
1442+
bindAssignmentTargetFlow(node.left);
1443+
1444+
addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node));
1445+
addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node));
1446+
}
1447+
else {
1448+
bindCondition(node.right, trueTarget, falseTarget);
1449+
}
14721450
}
14731451

14741452
function bindPrefixUnaryExpressionFlow(node: PrefixUnaryExpression) {
@@ -1554,25 +1532,15 @@ namespace ts {
15541532
// TODO: bindLogicalExpression is recursive - if we want to handle deeply nested `&&` expressions
15551533
// we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too
15561534
// For now, though, since the common cases are chained `+`, leaving it recursive is fine
1557-
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
1535+
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken ||
1536+
isLogicalOrCoalescingAssignmentOperator(operator)) {
15581537
if (isTopLevelLogicalExpression(node)) {
15591538
const postExpressionLabel = createBranchLabel();
1560-
bindLogicalExpression(node, postExpressionLabel, postExpressionLabel);
1561-
currentFlow = finishFlowLabel(postExpressionLabel);
1562-
}
1563-
else {
1564-
bindLogicalExpression(node, currentTrueTarget!, currentFalseTarget!);
1565-
}
1566-
completeNode();
1567-
}
1568-
else if(isLogicalAssignmentOperator(operator)) {
1569-
if (isTopLevelLogicalAssignmentExpression(node)) {
1570-
const postExpressionLabel = createBranchLabel();
1571-
bindLogicalAssignmentExpression(node, postExpressionLabel, postExpressionLabel);
1539+
bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel);
15721540
currentFlow = finishFlowLabel(postExpressionLabel);
15731541
}
15741542
else {
1575-
bindLogicalAssignmentExpression(node, currentTrueTarget!, currentFalseTarget!);
1543+
bindLogicalLikeExpression(node, currentTrueTarget!, currentFalseTarget!);
15761544
}
15771545
completeNode();
15781546
}
@@ -3667,7 +3635,7 @@ namespace ts {
36673635
if (operatorTokenKind === SyntaxKind.QuestionQuestionToken) {
36683636
transformFlags |= TransformFlags.AssertES2020;
36693637
}
3670-
else if (isLogicalAssignmentOperator(operatorTokenKind)) {
3638+
else if (isLogicalOrCoalescingAssignmentOperator(operatorTokenKind)) {
36713639
transformFlags |= TransformFlags.AssertESNext;
36723640
}
36733641
else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) {

Diff for: src/compiler/checker.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -28216,7 +28216,7 @@ namespace ts {
2821628216
getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) :
2821728217
leftType;
2821828218
if (operator === SyntaxKind.BarBarEqualsToken) {
28219-
checkAssignmentOperator(resultType);
28219+
checkAssignmentOperator(rightType);
2822028220
}
2822128221
return resultType;
2822228222
}
@@ -28226,7 +28226,7 @@ namespace ts {
2822628226
getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) :
2822728227
leftType;
2822828228
if (operator === SyntaxKind.QuestionQuestionEqualsToken) {
28229-
checkAssignmentOperator(resultType);
28229+
checkAssignmentOperator(rightType);
2823028230
}
2823128231
return resultType;
2823228232
}

Diff for: src/compiler/transformers/esnext.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace ts {
1818
switch (node.kind) {
1919
case SyntaxKind.BinaryExpression:
2020
const binaryExpression = <BinaryExpression>node;
21-
if (isLogicalAssignmentOperator(binaryExpression.operatorToken.kind)) {
21+
if (isLogicalOrCoalescingAssignmentOperator(binaryExpression.operatorToken.kind)) {
2222
return transformLogicalAssignmentOperators(binaryExpression);
2323
}
2424
// falls through
@@ -29,7 +29,7 @@ namespace ts {
2929

3030
function transformLogicalAssignmentOperators(binaryExpression: BinaryExpression): VisitResult<Node> {
3131
const operator = binaryExpression.operatorToken;
32-
if (isCompoundAssignment(operator.kind) && isLogicalAssignmentOperator(operator.kind)) {
32+
if (isCompoundAssignment(operator.kind) && isLogicalOrCoalescingAssignmentOperator(operator.kind)) {
3333
const nonAssignmentOperator = getNonAssignmentOperatorForCompoundAssignment(operator.kind);
3434
const left = visitNode(binaryExpression.left, visitor, isExpression);
3535
const right = visitNode(binaryExpression.right, visitor, isExpression);

Diff for: src/compiler/utilities.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -2559,7 +2559,7 @@ namespace ts {
25592559
switch (parent.kind) {
25602560
case SyntaxKind.BinaryExpression:
25612561
const binaryOperator = (<BinaryExpression>parent).operatorToken.kind;
2562-
return isAssignmentOperator(binaryOperator) && !isLogicalAssignmentOperator(binaryOperator) && (<BinaryExpression>parent).left === node ?
2562+
return isAssignmentOperator(binaryOperator) && !isLogicalOrCoalescingAssignmentOperator(binaryOperator) && (<BinaryExpression>parent).left === node ?
25632563
binaryOperator === SyntaxKind.EqualsToken ? AssignmentKind.Definite : AssignmentKind.Compound :
25642564
AssignmentKind.None;
25652565
case SyntaxKind.PrefixUnaryExpression:
@@ -3343,10 +3343,6 @@ namespace ts {
33433343
return 14;
33443344
case SyntaxKind.AsteriskAsteriskToken:
33453345
return 15;
3346-
case SyntaxKind.BarBarEqualsToken:
3347-
case SyntaxKind.AmpersandAmpersandEqualsToken:
3348-
case SyntaxKind.QuestionQuestionEqualsToken:
3349-
return 16;
33503346
}
33513347

33523348
// -1 is lower than all other precedences. Returning it will cause binary expression
@@ -4368,7 +4364,7 @@ namespace ts {
43684364
|| token === SyntaxKind.ExclamationToken;
43694365
}
43704366

4371-
export function isLogicalAssignmentOperator(token: SyntaxKind): boolean {
4367+
export function isLogicalOrCoalescingAssignmentOperator(token: SyntaxKind): boolean {
43724368
return token === SyntaxKind.BarBarEqualsToken
43734369
|| token === SyntaxKind.AmpersandAmpersandEqualsToken
43744370
|| token === SyntaxKind.QuestionQuestionEqualsToken;
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,20 @@
1-
tests/cases/conformance/esnext/logicalAssignment/logicalAssignment7.ts(2,6): error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
2-
tests/cases/conformance/esnext/logicalAssignment/logicalAssignment7.ts(6,6): error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
31
tests/cases/conformance/esnext/logicalAssignment/logicalAssignment7.ts(10,5): error TS2532: Object is possibly 'undefined'.
4-
tests/cases/conformance/esnext/logicalAssignment/logicalAssignment7.ts(10,6): error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
52
tests/cases/conformance/esnext/logicalAssignment/logicalAssignment7.ts(10,40): error TS2345: Argument of type '100' is not assignable to parameter of type 'never'.
63

74

8-
==== tests/cases/conformance/esnext/logicalAssignment/logicalAssignment7.ts (5 errors) ====
5+
==== tests/cases/conformance/esnext/logicalAssignment/logicalAssignment7.ts (2 errors) ====
96
function foo1(results: number[] | undefined, results1: number[] | undefined) {
107
(results ||= results1 ||= []).push(100);
11-
~~~~~~~~~~~~~~~~~~~~
12-
!!! error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
138
}
149

1510
function foo2(results: number[] | undefined, results1: number[] | undefined) {
1611
(results ??= results1 ??= []).push(100);
17-
~~~~~~~~~~~~~~~~~~~~
18-
!!! error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
1912
}
2013

2114
function foo3(results: number[] | undefined, results1: number[] | undefined) {
2215
(results &&= results1 &&= []).push(100);
2316
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2417
!!! error TS2532: Object is possibly 'undefined'.
25-
~~~~~~~~~~~~~~~~~~~~
26-
!!! error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
2718
~~~
2819
!!! error TS2345: Argument of type '100' is not assignable to parameter of type 'never'.
2920
}

Diff for: tests/baselines/reference/logicalAssignment7(target=es2015).js

+3-4
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ function foo3(results: number[] | undefined, results1: number[] | undefined) {
1414
//// [logicalAssignment7.js]
1515
"use strict";
1616
function foo1(results, results1) {
17-
(results || (results = results1) || (results || (results = results1) = [])).push(100);
17+
(results || (results = results1 || (results1 = []))).push(100);
1818
}
1919
function foo2(results, results1) {
20-
var _a;
21-
((_a = results !== null && results !== void 0 ? results : (results = results1)) !== null && _a !== void 0 ? _a : (results !== null && results !== void 0 ? results : (results = results1) = [])).push(100);
20+
(results !== null && results !== void 0 ? results : (results = results1 !== null && results1 !== void 0 ? results1 : (results1 = []))).push(100);
2221
}
2322
function foo3(results, results1) {
24-
(results && (results = results1) && (results && (results = results1) = [])).push(100);
23+
(results && (results = results1 && (results1 = []))).push(100);
2524
}

Diff for: tests/baselines/reference/logicalAssignment7(target=es2015).types

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ function foo1(results: number[] | undefined, results1: number[] | undefined) {
99
>(results ||= results1 ||= []).push : (...items: number[]) => number
1010
>(results ||= results1 ||= []) : number[]
1111
>results ||= results1 ||= [] : number[]
12-
>results ||= results1 : number[] | undefined
1312
>results : number[] | undefined
13+
>results1 ||= [] : number[]
1414
>results1 : number[] | undefined
1515
>[] : never[]
1616
>push : (...items: number[]) => number
@@ -27,8 +27,8 @@ function foo2(results: number[] | undefined, results1: number[] | undefined) {
2727
>(results ??= results1 ??= []).push : (...items: number[]) => number
2828
>(results ??= results1 ??= []) : number[]
2929
>results ??= results1 ??= [] : number[]
30-
>results ??= results1 : number[] | undefined
3130
>results : number[] | undefined
31+
>results1 ??= [] : number[]
3232
>results1 : number[] | undefined
3333
>[] : never[]
3434
>push : (...items: number[]) => number
@@ -45,8 +45,8 @@ function foo3(results: number[] | undefined, results1: number[] | undefined) {
4545
>(results &&= results1 &&= []).push : (...items: never[]) => number
4646
>(results &&= results1 &&= []) : never[] | undefined
4747
>results &&= results1 &&= [] : never[] | undefined
48-
>results &&= results1 : number[] | undefined
4948
>results : number[] | undefined
49+
>results1 &&= [] : never[] | undefined
5050
>results1 : number[] | undefined
5151
>[] : never[]
5252
>push : (...items: never[]) => number
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,20 @@
1-
tests/cases/conformance/esnext/logicalAssignment/logicalAssignment7.ts(2,6): error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
2-
tests/cases/conformance/esnext/logicalAssignment/logicalAssignment7.ts(6,6): error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
31
tests/cases/conformance/esnext/logicalAssignment/logicalAssignment7.ts(10,5): error TS2532: Object is possibly 'undefined'.
4-
tests/cases/conformance/esnext/logicalAssignment/logicalAssignment7.ts(10,6): error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
52
tests/cases/conformance/esnext/logicalAssignment/logicalAssignment7.ts(10,40): error TS2345: Argument of type '100' is not assignable to parameter of type 'never'.
63

74

8-
==== tests/cases/conformance/esnext/logicalAssignment/logicalAssignment7.ts (5 errors) ====
5+
==== tests/cases/conformance/esnext/logicalAssignment/logicalAssignment7.ts (2 errors) ====
96
function foo1(results: number[] | undefined, results1: number[] | undefined) {
107
(results ||= results1 ||= []).push(100);
11-
~~~~~~~~~~~~~~~~~~~~
12-
!!! error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
138
}
149

1510
function foo2(results: number[] | undefined, results1: number[] | undefined) {
1611
(results ??= results1 ??= []).push(100);
17-
~~~~~~~~~~~~~~~~~~~~
18-
!!! error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
1912
}
2013

2114
function foo3(results: number[] | undefined, results1: number[] | undefined) {
2215
(results &&= results1 &&= []).push(100);
2316
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2417
!!! error TS2532: Object is possibly 'undefined'.
25-
~~~~~~~~~~~~~~~~~~~~
26-
!!! error TS2364: The left-hand side of an assignment expression must be a variable or a property access.
2718
~~~
2819
!!! error TS2345: Argument of type '100' is not assignable to parameter of type 'never'.
2920
}

Diff for: tests/baselines/reference/logicalAssignment7(target=es2020).js

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ function foo3(results: number[] | undefined, results1: number[] | undefined) {
1414
//// [logicalAssignment7.js]
1515
"use strict";
1616
function foo1(results, results1) {
17-
(results || (results = results1) || (results || (results = results1) = [])).push(100);
17+
(results || (results = results1 || (results1 = []))).push(100);
1818
}
1919
function foo2(results, results1) {
20-
(results ?? (results = results1) ?? (results ?? (results = results1) = [])).push(100);
20+
(results ?? (results = results1 ?? (results1 = []))).push(100);
2121
}
2222
function foo3(results, results1) {
23-
(results && (results = results1) && (results && (results = results1) = [])).push(100);
23+
(results && (results = results1 && (results1 = []))).push(100);
2424
}

0 commit comments

Comments
 (0)