Skip to content

Commit e832e04

Browse files
Merge pull request #37727 from Kingwl/logical_assignment
Add logical assignment operator
2 parents f41398e + fed922a commit e832e04

File tree

108 files changed

+6397
-593
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

108 files changed

+6397
-593
lines changed

src/compiler/binder.ts

+30-7
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,9 @@ namespace ts {
905905
function isNarrowingBinaryExpression(expr: BinaryExpression) {
906906
switch (expr.operatorToken.kind) {
907907
case SyntaxKind.EqualsToken:
908+
case SyntaxKind.BarBarEqualsToken:
909+
case SyntaxKind.AmpersandAmpersandEqualsToken:
910+
case SyntaxKind.QuestionQuestionEqualsToken:
908911
return containsNarrowableReference(expr.left);
909912
case SyntaxKind.EqualsEqualsToken:
910913
case SyntaxKind.ExclamationEqualsToken:
@@ -1041,12 +1044,18 @@ namespace ts {
10411044
}
10421045
}
10431046

1047+
function isLogicalAssignmentExpression(node: Node) {
1048+
node = skipParentheses(node);
1049+
return isBinaryExpression(node) && isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind);
1050+
}
1051+
10441052
function isTopLevelLogicalExpression(node: Node): boolean {
10451053
while (isParenthesizedExpression(node.parent) ||
10461054
isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.ExclamationToken) {
10471055
node = node.parent;
10481056
}
10491057
return !isStatementCondition(node) &&
1058+
!isLogicalAssignmentExpression(node.parent) &&
10501059
!isLogicalExpression(node.parent) &&
10511060
!(isOptionalChain(node.parent) && node.parent.expression === node);
10521061
}
@@ -1063,7 +1072,7 @@ namespace ts {
10631072

10641073
function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) {
10651074
doWithConditionalBranches(bind, node, trueTarget, falseTarget);
1066-
if (!node || !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) {
1075+
if (!node || !isLogicalAssignmentExpression(node) && !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) {
10671076
addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node));
10681077
addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node));
10691078
}
@@ -1404,17 +1413,27 @@ namespace ts {
14041413
}
14051414
}
14061415

1407-
function bindLogicalExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
1416+
function bindLogicalLikeExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
14081417
const preRightLabel = createBranchLabel();
1409-
if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
1418+
if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === SyntaxKind.AmpersandAmpersandEqualsToken) {
14101419
bindCondition(node.left, preRightLabel, falseTarget);
14111420
}
14121421
else {
14131422
bindCondition(node.left, trueTarget, preRightLabel);
14141423
}
14151424
currentFlow = finishFlowLabel(preRightLabel);
14161425
bind(node.operatorToken);
1417-
bindCondition(node.right, trueTarget, falseTarget);
1426+
1427+
if (isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind)) {
1428+
doWithConditionalBranches(bind, node.right, trueTarget, falseTarget);
1429+
bindAssignmentTargetFlow(node.left);
1430+
1431+
addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node));
1432+
addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node));
1433+
}
1434+
else {
1435+
bindCondition(node.right, trueTarget, falseTarget);
1436+
}
14181437
}
14191438

14201439
function bindPrefixUnaryExpressionFlow(node: PrefixUnaryExpression) {
@@ -1500,14 +1519,15 @@ namespace ts {
15001519
// TODO: bindLogicalExpression is recursive - if we want to handle deeply nested `&&` expressions
15011520
// we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too
15021521
// For now, though, since the common cases are chained `+`, leaving it recursive is fine
1503-
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
1522+
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken ||
1523+
isLogicalOrCoalescingAssignmentOperator(operator)) {
15041524
if (isTopLevelLogicalExpression(node)) {
15051525
const postExpressionLabel = createBranchLabel();
1506-
bindLogicalExpression(node, postExpressionLabel, postExpressionLabel);
1526+
bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel);
15071527
currentFlow = finishFlowLabel(postExpressionLabel);
15081528
}
15091529
else {
1510-
bindLogicalExpression(node, currentTrueTarget!, currentFalseTarget!);
1530+
bindLogicalLikeExpression(node, currentTrueTarget!, currentFalseTarget!);
15111531
}
15121532
completeNode();
15131533
}
@@ -3607,6 +3627,9 @@ namespace ts {
36073627
if (operatorTokenKind === SyntaxKind.QuestionQuestionToken) {
36083628
transformFlags |= TransformFlags.AssertES2020;
36093629
}
3630+
else if (isLogicalOrCoalescingAssignmentOperator(operatorTokenKind)) {
3631+
transformFlags |= TransformFlags.AssertESNext;
3632+
}
36103633
else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) {
36113634
// Destructuring object assignments with are ES2015 syntax
36123635
// and possibly ES2018 if they contain rest

src/compiler/checker.ts

+30-3
Original file line numberDiff line numberDiff line change
@@ -19864,6 +19864,9 @@ namespace ts {
1986419864
case SyntaxKind.BinaryExpression:
1986519865
switch ((<BinaryExpression>node).operatorToken.kind) {
1986619866
case SyntaxKind.EqualsToken:
19867+
case SyntaxKind.BarBarEqualsToken:
19868+
case SyntaxKind.AmpersandAmpersandEqualsToken:
19869+
case SyntaxKind.QuestionQuestionEqualsToken:
1986719870
return getReferenceCandidate((<BinaryExpression>node).left);
1986819871
case SyntaxKind.CommaToken:
1986919872
return getReferenceCandidate((<BinaryExpression>node).right);
@@ -20838,6 +20841,9 @@ namespace ts {
2083820841
function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
2083920842
switch (expr.operatorToken.kind) {
2084020843
case SyntaxKind.EqualsToken:
20844+
case SyntaxKind.BarBarEqualsToken:
20845+
case SyntaxKind.AmpersandAmpersandEqualsToken:
20846+
case SyntaxKind.QuestionQuestionEqualsToken:
2084120847
return narrowTypeByTruthiness(narrowType(type, expr.right, assumeTrue), expr.left, assumeTrue);
2084220848
case SyntaxKind.EqualsEqualsToken:
2084320849
case SyntaxKind.ExclamationEqualsToken:
@@ -22462,6 +22468,9 @@ namespace ts {
2246222468
const { left, operatorToken, right } = binaryExpression;
2246322469
switch (operatorToken.kind) {
2246422470
case SyntaxKind.EqualsToken:
22471+
case SyntaxKind.AmpersandAmpersandEqualsToken:
22472+
case SyntaxKind.BarBarEqualsToken:
22473+
case SyntaxKind.QuestionQuestionEqualsToken:
2246522474
if (node !== right) {
2246622475
return undefined;
2246722476
}
@@ -28758,17 +28767,35 @@ namespace ts {
2875828767
case SyntaxKind.InKeyword:
2875928768
return checkInExpression(left, right, leftType, rightType);
2876028769
case SyntaxKind.AmpersandAmpersandToken:
28761-
return getTypeFacts(leftType) & TypeFacts.Truthy ?
28770+
case SyntaxKind.AmpersandAmpersandEqualsToken: {
28771+
const resultType = getTypeFacts(leftType) & TypeFacts.Truthy ?
2876228772
getUnionType([extractDefinitelyFalsyTypes(strictNullChecks ? leftType : getBaseTypeOfLiteralType(rightType)), rightType]) :
2876328773
leftType;
28774+
if (operator === SyntaxKind.AmpersandAmpersandEqualsToken) {
28775+
checkAssignmentOperator(rightType);
28776+
}
28777+
return resultType;
28778+
}
2876428779
case SyntaxKind.BarBarToken:
28765-
return getTypeFacts(leftType) & TypeFacts.Falsy ?
28780+
case SyntaxKind.BarBarEqualsToken: {
28781+
const resultType = getTypeFacts(leftType) & TypeFacts.Falsy ?
2876628782
getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) :
2876728783
leftType;
28784+
if (operator === SyntaxKind.BarBarEqualsToken) {
28785+
checkAssignmentOperator(rightType);
28786+
}
28787+
return resultType;
28788+
}
2876828789
case SyntaxKind.QuestionQuestionToken:
28769-
return getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ?
28790+
case SyntaxKind.QuestionQuestionEqualsToken: {
28791+
const resultType = getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ?
2877028792
getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) :
2877128793
leftType;
28794+
if (operator === SyntaxKind.QuestionQuestionEqualsToken) {
28795+
checkAssignmentOperator(rightType);
28796+
}
28797+
return resultType;
28798+
}
2877228799
case SyntaxKind.EqualsToken:
2877328800
const declKind = isBinaryExpression(left.parent) ? getAssignmentDeclarationKind(left.parent) : AssignmentDeclarationKind.None;
2877428801
checkAssignmentDeclaration(declKind, rightType);

src/compiler/scanner.ts

+17-7
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ namespace ts {
208208
"&=": SyntaxKind.AmpersandEqualsToken,
209209
"|=": SyntaxKind.BarEqualsToken,
210210
"^=": SyntaxKind.CaretEqualsToken,
211+
"||=": SyntaxKind.BarBarEqualsToken,
212+
"&&=": SyntaxKind.AmpersandAmpersandEqualsToken,
213+
"??=": SyntaxKind.QuestionQuestionEqualsToken,
211214
"@": SyntaxKind.AtToken,
212215
"`": SyntaxKind.BacktickToken
213216
});
@@ -1667,6 +1670,9 @@ namespace ts {
16671670
return token = SyntaxKind.PercentToken;
16681671
case CharacterCodes.ampersand:
16691672
if (text.charCodeAt(pos + 1) === CharacterCodes.ampersand) {
1673+
if (text.charCodeAt(pos + 2) === CharacterCodes.equals) {
1674+
return pos += 3, token = SyntaxKind.AmpersandAmpersandEqualsToken;
1675+
}
16701676
return pos += 2, token = SyntaxKind.AmpersandAmpersandToken;
16711677
}
16721678
if (text.charCodeAt(pos + 1) === CharacterCodes.equals) {
@@ -1928,15 +1934,16 @@ namespace ts {
19281934
pos++;
19291935
return token = SyntaxKind.GreaterThanToken;
19301936
case CharacterCodes.question:
1931-
pos++;
1932-
if (text.charCodeAt(pos) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 1))) {
1933-
pos++;
1934-
return token = SyntaxKind.QuestionDotToken;
1937+
if (text.charCodeAt(pos + 1) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 2))) {
1938+
return pos += 2, token = SyntaxKind.QuestionDotToken;
19351939
}
1936-
if (text.charCodeAt(pos) === CharacterCodes.question) {
1937-
pos++;
1938-
return token = SyntaxKind.QuestionQuestionToken;
1940+
if (text.charCodeAt(pos + 1) === CharacterCodes.question) {
1941+
if (text.charCodeAt(pos + 2) === CharacterCodes.equals) {
1942+
return pos += 3, token = SyntaxKind.QuestionQuestionEqualsToken;
1943+
}
1944+
return pos += 2, token = SyntaxKind.QuestionQuestionToken;
19391945
}
1946+
pos++;
19401947
return token = SyntaxKind.QuestionToken;
19411948
case CharacterCodes.openBracket:
19421949
pos++;
@@ -1965,6 +1972,9 @@ namespace ts {
19651972
}
19661973

19671974
if (text.charCodeAt(pos + 1) === CharacterCodes.bar) {
1975+
if (text.charCodeAt(pos + 2) === CharacterCodes.equals) {
1976+
return pos += 3, token = SyntaxKind.BarBarEqualsToken;
1977+
}
19681978
return pos += 2, token = SyntaxKind.BarBarToken;
19691979
}
19701980
if (text.charCodeAt(pos + 1) === CharacterCodes.equals) {

src/compiler/transformers/es2020.ts

+4-12
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ namespace ts {
7373

7474
let thisArg: Expression | undefined;
7575
if (captureThisArg) {
76-
if (shouldCaptureInTempVariable(expression)) {
76+
if (!isSimpleCopiableExpression(expression)) {
7777
thisArg = createTempVariable(hoistVariableDeclaration);
7878
expression = createAssignment(thisArg, expression);
7979
// if (inParameterInitializer) tempVariableInParameter = true;
@@ -113,7 +113,7 @@ namespace ts {
113113
const leftThisArg = isSyntheticReference(left) ? left.thisArg : undefined;
114114
let leftExpression = isSyntheticReference(left) ? left.expression : left;
115115
let capturedLeft: Expression = leftExpression;
116-
if (shouldCaptureInTempVariable(leftExpression)) {
116+
if (!isSimpleCopiableExpression(leftExpression)) {
117117
capturedLeft = createTempVariable(hoistVariableDeclaration);
118118
leftExpression = createAssignment(capturedLeft, leftExpression);
119119
// if (inParameterInitializer) tempVariableInParameter = true;
@@ -126,7 +126,7 @@ namespace ts {
126126
case SyntaxKind.PropertyAccessExpression:
127127
case SyntaxKind.ElementAccessExpression:
128128
if (i === chain.length - 1 && captureThisArg) {
129-
if (shouldCaptureInTempVariable(rightExpression)) {
129+
if (!isSimpleCopiableExpression(rightExpression)) {
130130
thisArg = createTempVariable(hoistVariableDeclaration);
131131
rightExpression = createAssignment(thisArg, rightExpression);
132132
// if (inParameterInitializer) tempVariableInParameter = true;
@@ -184,7 +184,7 @@ namespace ts {
184184
function transformNullishCoalescingExpression(node: BinaryExpression) {
185185
let left = visitNode(node.left, visitor, isExpression);
186186
let right = left;
187-
if (shouldCaptureInTempVariable(left)) {
187+
if (!isSimpleCopiableExpression(left)) {
188188
right = createTempVariable(hoistVariableDeclaration);
189189
left = createAssignment(right, left);
190190
// if (inParameterInitializer) tempVariableInParameter = true;
@@ -196,14 +196,6 @@ namespace ts {
196196
);
197197
}
198198

199-
function shouldCaptureInTempVariable(expression: Expression): boolean {
200-
// don't capture identifiers and `this` in a temporary variable
201-
// `super` cannot be captured as it's no real variable
202-
return !isIdentifier(expression) &&
203-
expression.kind !== SyntaxKind.ThisKeyword &&
204-
expression.kind !== SyntaxKind.SuperKeyword;
205-
}
206-
207199
function visitDeleteExpression(node: DeleteExpression) {
208200
return isOptionalChain(skipParentheses(node.expression))
209201
? setOriginalNode(visitNonOptionalExpression(node.expression, /*captureThisArg*/ false, /*isDelete*/ true), node)

src/compiler/transformers/esnext.ts

+57
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
/*@internal*/
22
namespace ts {
33
export function transformESNext(context: TransformationContext) {
4+
const {
5+
hoistVariableDeclaration
6+
} = context;
47
return chainBundle(transformSourceFile);
58

69
function transformSourceFile(node: SourceFile) {
@@ -16,9 +19,63 @@ namespace ts {
1619
return node;
1720
}
1821
switch (node.kind) {
22+
case SyntaxKind.BinaryExpression:
23+
const binaryExpression = <BinaryExpression>node;
24+
if (isLogicalOrCoalescingAssignmentExpression(binaryExpression)) {
25+
return transformLogicalAssignment(binaryExpression);
26+
}
27+
// falls through
1928
default:
2029
return visitEachChild(node, visitor, context);
2130
}
2231
}
32+
33+
function transformLogicalAssignment(binaryExpression: AssignmentExpression<Token<LogicalOrCoalescingAssignmentOperator>>): VisitResult<Node> {
34+
const operator = binaryExpression.operatorToken;
35+
const nonAssignmentOperator = getNonAssignmentOperatorForCompoundAssignment(operator.kind);
36+
let left = skipParentheses(visitNode(binaryExpression.left, visitor, isLeftHandSideExpression));
37+
let assignmentTarget = left;
38+
const right = skipParentheses(visitNode(binaryExpression.right, visitor, isExpression));
39+
if (isAccessExpression(left)) {
40+
const tempVariable = createTempVariable(hoistVariableDeclaration);
41+
if (isPropertyAccessExpression(left)) {
42+
assignmentTarget = createPropertyAccess(
43+
tempVariable,
44+
left.name
45+
);
46+
left = createPropertyAccess(
47+
createAssignment(
48+
tempVariable,
49+
left.expression
50+
),
51+
left.name
52+
);
53+
}
54+
else {
55+
assignmentTarget = createElementAccess(
56+
tempVariable,
57+
left.argumentExpression
58+
);
59+
left = createElementAccess(
60+
createAssignment(
61+
tempVariable,
62+
left.expression
63+
),
64+
left.argumentExpression
65+
);
66+
}
67+
}
68+
69+
return createBinary(
70+
left,
71+
nonAssignmentOperator,
72+
createParen(
73+
createAssignment(
74+
assignmentTarget,
75+
right
76+
)
77+
)
78+
);
79+
}
2380
}
2481
}

0 commit comments

Comments
 (0)