Skip to content

Commit 81ce254

Browse files
committed
Refactor logical assignment
1 parent cb2e71c commit 81ce254

13 files changed

+47
-117
lines changed

src/compiler/binder.ts

Lines changed: 23 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,24 +1047,9 @@ namespace ts {
10471047
}
10481048
}
10491049

1050-
function isLogicalAssignmentExpressioin(node: Node) {
1051-
while (true) {
1052-
if (isParenthesizedExpression(node)) {
1053-
node = node.expression;
1054-
}
1055-
else {
1056-
return isBinaryExpression(node) && isLogicalAssignmentOperator(node.operatorToken.kind);
1057-
}
1058-
}
1059-
}
1060-
1061-
function isTopLevelLogicalAssignmentExpression(node: Node): boolean {
1062-
while (isParenthesizedExpression(node.parent)) {
1063-
node = node.parent;
1064-
}
1065-
return !isStatementCondition(node) &&
1066-
!isLogicalAssignmentExpressioin(node.parent) &&
1067-
!(isOptionalChain(node.parent) && node.parent.expression === node);
1050+
function isLogicalAssignmentExpression(node: Node) {
1051+
node = skipParentheses(node);
1052+
return isBinaryExpression(node) && isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind);
10681053
}
10691054

10701055
function isTopLevelLogicalExpression(node: Node): boolean {
@@ -1073,6 +1058,7 @@ namespace ts {
10731058
node = node.parent;
10741059
}
10751060
return !isStatementCondition(node) &&
1061+
!isLogicalAssignmentExpression(node.parent) &&
10761062
!isLogicalExpression(node.parent) &&
10771063
!(isOptionalChain(node.parent) && node.parent.expression === node);
10781064
}
@@ -1089,7 +1075,7 @@ namespace ts {
10891075

10901076
function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) {
10911077
doWithConditionalBranches(bind, node, trueTarget, falseTarget);
1092-
if (!node || !isLogicalAssignmentExpressioin(node) && !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) {
1078+
if (!node || !isLogicalAssignmentExpression(node) && !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) {
10931079
addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node));
10941080
addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node));
10951081
}
@@ -1189,24 +1175,6 @@ namespace ts {
11891175
currentFlow = finishFlowLabel(postIfLabel);
11901176
}
11911177

1192-
function bindLogicalAssignmentExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
1193-
const preRightLabel = createBranchLabel();
1194-
if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandEqualsToken) {
1195-
bindCondition(node.left, preRightLabel, falseTarget);
1196-
}
1197-
else {
1198-
bindCondition(node.left, trueTarget, preRightLabel);
1199-
}
1200-
currentFlow = finishFlowLabel(preRightLabel);
1201-
bind(node.operatorToken);
1202-
1203-
doWithConditionalBranches(bind, node.right, trueTarget, falseTarget);
1204-
bindAssignmentTargetFlow(node.left);
1205-
1206-
addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node));
1207-
addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node));
1208-
}
1209-
12101178
function bindReturnOrThrow(node: ReturnStatement | ThrowStatement): void {
12111179
bind(node.expression);
12121180
if (node.kind === SyntaxKind.ReturnStatement) {
@@ -1448,17 +1416,27 @@ namespace ts {
14481416
}
14491417
}
14501418

1451-
function bindLogicalExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
1419+
function bindLogicalLikeExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
14521420
const preRightLabel = createBranchLabel();
1453-
if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
1421+
if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === SyntaxKind.AmpersandAmpersandEqualsToken) {
14541422
bindCondition(node.left, preRightLabel, falseTarget);
14551423
}
14561424
else {
14571425
bindCondition(node.left, trueTarget, preRightLabel);
14581426
}
14591427
currentFlow = finishFlowLabel(preRightLabel);
14601428
bind(node.operatorToken);
1461-
bindCondition(node.right, trueTarget, falseTarget);
1429+
1430+
if (isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind)) {
1431+
doWithConditionalBranches(bind, node.right, trueTarget, falseTarget);
1432+
bindAssignmentTargetFlow(node.left);
1433+
1434+
addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node));
1435+
addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node));
1436+
}
1437+
else {
1438+
bindCondition(node.right, trueTarget, falseTarget);
1439+
}
14621440
}
14631441

14641442
function bindPrefixUnaryExpressionFlow(node: PrefixUnaryExpression) {
@@ -1544,25 +1522,15 @@ namespace ts {
15441522
// TODO: bindLogicalExpression is recursive - if we want to handle deeply nested `&&` expressions
15451523
// we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too
15461524
// For now, though, since the common cases are chained `+`, leaving it recursive is fine
1547-
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
1525+
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken ||
1526+
isLogicalOrCoalescingAssignmentOperator(operator)) {
15481527
if (isTopLevelLogicalExpression(node)) {
15491528
const postExpressionLabel = createBranchLabel();
1550-
bindLogicalExpression(node, postExpressionLabel, postExpressionLabel);
1551-
currentFlow = finishFlowLabel(postExpressionLabel);
1552-
}
1553-
else {
1554-
bindLogicalExpression(node, currentTrueTarget!, currentFalseTarget!);
1555-
}
1556-
completeNode();
1557-
}
1558-
else if(isLogicalAssignmentOperator(operator)) {
1559-
if (isTopLevelLogicalAssignmentExpression(node)) {
1560-
const postExpressionLabel = createBranchLabel();
1561-
bindLogicalAssignmentExpression(node, postExpressionLabel, postExpressionLabel);
1529+
bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel);
15621530
currentFlow = finishFlowLabel(postExpressionLabel);
15631531
}
15641532
else {
1565-
bindLogicalAssignmentExpression(node, currentTrueTarget!, currentFalseTarget!);
1533+
bindLogicalLikeExpression(node, currentTrueTarget!, currentFalseTarget!);
15661534
}
15671535
completeNode();
15681536
}
@@ -3656,7 +3624,7 @@ namespace ts {
36563624
if (operatorTokenKind === SyntaxKind.QuestionQuestionToken) {
36573625
transformFlags |= TransformFlags.AssertES2020;
36583626
}
3659-
else if (isLogicalAssignmentOperator(operatorTokenKind)) {
3627+
else if (isLogicalOrCoalescingAssignmentOperator(operatorTokenKind)) {
36603628
transformFlags |= TransformFlags.AssertESNext;
36613629
}
36623630
else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) {

src/compiler/checker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28596,7 +28596,7 @@ namespace ts {
2859628596
getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) :
2859728597
leftType;
2859828598
if (operator === SyntaxKind.BarBarEqualsToken) {
28599-
checkAssignmentOperator(resultType);
28599+
checkAssignmentOperator(rightType);
2860028600
}
2860128601
return resultType;
2860228602
}
@@ -28606,7 +28606,7 @@ namespace ts {
2860628606
getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) :
2860728607
leftType;
2860828608
if (operator === SyntaxKind.QuestionQuestionEqualsToken) {
28609-
checkAssignmentOperator(resultType);
28609+
checkAssignmentOperator(rightType);
2861028610
}
2861128611
return resultType;
2861228612
}

src/compiler/transformers/esnext.ts

Lines changed: 2 additions & 2 deletions
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);

src/compiler/utilities.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2594,7 +2594,7 @@ namespace ts {
25942594
switch (parent.kind) {
25952595
case SyntaxKind.BinaryExpression:
25962596
const binaryOperator = (<BinaryExpression>parent).operatorToken.kind;
2597-
return isAssignmentOperator(binaryOperator) && !isLogicalAssignmentOperator(binaryOperator) && (<BinaryExpression>parent).left === node ?
2597+
return isAssignmentOperator(binaryOperator) && !isLogicalOrCoalescingAssignmentOperator(binaryOperator) && (<BinaryExpression>parent).left === node ?
25982598
binaryOperator === SyntaxKind.EqualsToken ? AssignmentKind.Definite : AssignmentKind.Compound :
25992599
AssignmentKind.None;
26002600
case SyntaxKind.PrefixUnaryExpression:
@@ -3378,10 +3378,6 @@ namespace ts {
33783378
return 14;
33793379
case SyntaxKind.AsteriskAsteriskToken:
33803380
return 15;
3381-
case SyntaxKind.BarBarEqualsToken:
3382-
case SyntaxKind.AmpersandAmpersandEqualsToken:
3383-
case SyntaxKind.QuestionQuestionEqualsToken:
3384-
return 16;
33853381
}
33863382

33873383
// -1 is lower than all other precedences. Returning it will cause binary expression
@@ -4454,7 +4450,7 @@ namespace ts {
44544450
|| token === SyntaxKind.ExclamationToken;
44554451
}
44564452

4457-
export function isLogicalAssignmentOperator(token: SyntaxKind): boolean {
4453+
export function isLogicalOrCoalescingAssignmentOperator(token: SyntaxKind): boolean {
44584454
return token === SyntaxKind.BarBarEqualsToken
44594455
|| token === SyntaxKind.AmpersandAmpersandEqualsToken
44604456
|| token === SyntaxKind.QuestionQuestionEqualsToken;

src/services/codefixes/inferFromUsage.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -785,12 +785,6 @@ namespace ts.codefix {
785785
}
786786
break;
787787

788-
case SyntaxKind.BarBarEqualsToken:
789-
case SyntaxKind.QuestionQuestionEqualsToken:
790-
case SyntaxKind.AmpersandAmpersandEqualsToken:
791-
// TODO: infer here
792-
break;
793-
794788
case SyntaxKind.AmpersandAmpersandToken:
795789
case SyntaxKind.CommaToken:
796790
case SyntaxKind.InstanceOfKeyword:
Lines changed: 1 addition & 10 deletions
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
}

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

Lines changed: 3 additions & 4 deletions
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
}

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

Lines changed: 3 additions & 3 deletions
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
Lines changed: 1 addition & 10 deletions
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
}

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

Lines changed: 3 additions & 3 deletions
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)