Skip to content

Commit 1d93db8

Browse files
committed
Add logical assignment operator
1 parent 33c3e9e commit 1d93db8

22 files changed

+1229
-569
lines changed

src/compiler/binder.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3601,6 +3601,9 @@ namespace ts {
36013601
if (operatorTokenKind === SyntaxKind.QuestionQuestionToken) {
36023602
transformFlags |= TransformFlags.AssertES2020;
36033603
}
3604+
else if (isLogicalAssignmentOperator(operatorTokenKind)) {
3605+
transformFlags |= TransformFlags.AssertESNext;
3606+
}
36043607
else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) {
36053608
// Destructuring object assignments with are ES2015 syntax
36063609
// and possibly ES2018 if they contain rest

src/compiler/checker.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28579,17 +28579,35 @@ namespace ts {
2857928579
case SyntaxKind.InKeyword:
2858028580
return checkInExpression(left, right, leftType, rightType);
2858128581
case SyntaxKind.AmpersandAmpersandToken:
28582-
return getTypeFacts(leftType) & TypeFacts.Truthy ?
28582+
case SyntaxKind.AmpersandAmpersandEqualsToken: {
28583+
const resultType = getTypeFacts(leftType) & TypeFacts.Truthy ?
2858328584
getUnionType([extractDefinitelyFalsyTypes(strictNullChecks ? leftType : getBaseTypeOfLiteralType(rightType)), rightType]) :
2858428585
leftType;
28586+
if (operator === SyntaxKind.AmpersandAmpersandEqualsToken) {
28587+
checkAssignmentOperator(resultType);
28588+
}
28589+
return resultType;
28590+
}
2858528591
case SyntaxKind.BarBarToken:
28586-
return getTypeFacts(leftType) & TypeFacts.Falsy ?
28592+
case SyntaxKind.BarBarEqualsToken: {
28593+
const resultType = getTypeFacts(leftType) & TypeFacts.Falsy ?
2858728594
getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) :
2858828595
leftType;
28596+
if (operator === SyntaxKind.BarBarEqualsToken) {
28597+
checkAssignmentOperator(resultType);
28598+
}
28599+
return resultType;
28600+
}
2858928601
case SyntaxKind.QuestionQuestionToken:
28590-
return getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ?
28602+
case SyntaxKind.QuestionQuestionEqualsToken: {
28603+
const resultType = getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ?
2859128604
getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) :
2859228605
leftType;
28606+
if (operator === SyntaxKind.QuestionQuestionEqualsToken) {
28607+
checkAssignmentOperator(resultType);
28608+
}
28609+
return resultType;
28610+
}
2859328611
case SyntaxKind.EqualsToken:
2859428612
const declKind = isBinaryExpression(left.parent) ? getAssignmentDeclarationKind(left.parent) : AssignmentDeclarationKind.None;
2859528613
checkAssignmentDeclaration(declKind, rightType);

src/compiler/scanner.ts

Lines changed: 17 additions & 7 deletions
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/esnext.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
/*@internal*/
22
namespace ts {
33
export function transformESNext(context: TransformationContext) {
4+
const {
5+
hoistVariableDeclaration
6+
} = context;
7+
48
return chainBundle(transformSourceFile);
59

610
function transformSourceFile(node: SourceFile) {
@@ -16,9 +20,42 @@ namespace ts {
1620
return node;
1721
}
1822
switch (node.kind) {
23+
case SyntaxKind.BinaryExpression:
24+
const binaryExpression = <BinaryExpression>node;
25+
if (isLogicalAssignmentOperator(binaryExpression.operatorToken.kind)) {
26+
return transformLogicalAssignmentOperators(binaryExpression);
27+
}
28+
// falls through
1929
default:
2030
return visitEachChild(node, visitor, context);
2131
}
2232
}
33+
34+
function transformLogicalAssignmentOperators(binaryExpression: BinaryExpression): VisitResult<Node> {
35+
const operator = binaryExpression.operatorToken;
36+
if (isCompoundAssignment(operator.kind) && isLogicalAssignmentOperator(operator.kind)) {
37+
const nonAssignmentOperator = getNonAssignmentOperatorForCompoundAssignment(operator.kind);
38+
const left = visitNode(binaryExpression.left, visitor, isExpression);
39+
const right = visitNode(binaryExpression.right, visitor, isExpression);
40+
let cond = left;
41+
if (shouldCaptureInTempVariable(left)) {
42+
const temp = createTempVariable(hoistVariableDeclaration);
43+
cond = createAssignment(temp, left);
44+
}
45+
46+
return createBinary(
47+
cond,
48+
nonAssignmentOperator,
49+
createParen(
50+
createAssignment(
51+
left,
52+
right
53+
)
54+
)
55+
)
56+
57+
}
58+
Debug.fail("unexpected operator: " + operator.kind);
59+
}
2360
}
2461
}

src/compiler/transformers/utilities.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ namespace ts {
259259
&& kind <= SyntaxKind.LastCompoundAssignment;
260260
}
261261

262-
export function getNonAssignmentOperatorForCompoundAssignment(kind: CompoundAssignmentOperator): BitwiseOperatorOrHigher {
262+
export function getNonAssignmentOperatorForCompoundAssignment(kind: CompoundAssignmentOperator): LogicalOperatorOrHigher | SyntaxKind.QuestionQuestionToken {
263263
switch (kind) {
264264
case SyntaxKind.PlusEqualsToken: return SyntaxKind.PlusToken;
265265
case SyntaxKind.MinusEqualsToken: return SyntaxKind.MinusToken;
@@ -273,9 +273,21 @@ namespace ts {
273273
case SyntaxKind.AmpersandEqualsToken: return SyntaxKind.AmpersandToken;
274274
case SyntaxKind.BarEqualsToken: return SyntaxKind.BarToken;
275275
case SyntaxKind.CaretEqualsToken: return SyntaxKind.CaretToken;
276+
case SyntaxKind.BarBarEqualsToken: return SyntaxKind.BarBarToken;
277+
case SyntaxKind.AmpersandAmpersandEqualsToken: return SyntaxKind.AmpersandAmpersandToken;
278+
case SyntaxKind.QuestionQuestionEqualsToken: return SyntaxKind.QuestionQuestionToken;
279+
276280
}
277281
}
278282

283+
export function shouldCaptureInTempVariable(expression: Expression): boolean {
284+
// don't capture identifiers and `this` in a temporary variable
285+
// `super` cannot be captured as it's no real variable
286+
return !isIdentifier(expression) &&
287+
expression.kind !== SyntaxKind.ThisKeyword &&
288+
expression.kind !== SyntaxKind.SuperKeyword;
289+
}
290+
279291
/**
280292
* Adds super call and preceding prologue directives into the list of statements.
281293
*

src/compiler/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ namespace ts {
203203
GreaterThanGreaterThanGreaterThanEqualsToken,
204204
AmpersandEqualsToken,
205205
BarEqualsToken,
206+
BarBarEqualsToken,
207+
AmpersandAmpersandEqualsToken,
208+
QuestionQuestionEqualsToken,
206209
CaretEqualsToken,
207210
// Identifiers and PrivateIdentifiers
208211
Identifier,
@@ -1597,6 +1600,9 @@ namespace ts {
15971600
| SyntaxKind.LessThanLessThanEqualsToken
15981601
| SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken
15991602
| SyntaxKind.GreaterThanGreaterThanEqualsToken
1603+
| SyntaxKind.BarBarEqualsToken
1604+
| SyntaxKind.AmpersandAmpersandEqualsToken
1605+
| SyntaxKind.QuestionQuestionEqualsToken
16001606
;
16011607

16021608
// see: https://tc39.github.io/ecma262/#prod-AssignmentExpression

src/compiler/utilities.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3220,6 +3220,9 @@ namespace ts {
32203220
case SyntaxKind.AmpersandEqualsToken:
32213221
case SyntaxKind.CaretEqualsToken:
32223222
case SyntaxKind.BarEqualsToken:
3223+
case SyntaxKind.BarBarEqualsToken:
3224+
case SyntaxKind.AmpersandAmpersandEqualsToken:
3225+
case SyntaxKind.QuestionQuestionEqualsToken:
32233226
return Associativity.Right;
32243227
}
32253228
}
@@ -3276,6 +3279,9 @@ namespace ts {
32763279
case SyntaxKind.AmpersandEqualsToken:
32773280
case SyntaxKind.CaretEqualsToken:
32783281
case SyntaxKind.BarEqualsToken:
3282+
case SyntaxKind.BarBarEqualsToken:
3283+
case SyntaxKind.AmpersandAmpersandEqualsToken:
3284+
case SyntaxKind.QuestionQuestionEqualsToken:
32793285
return 3;
32803286

32813287
default:
@@ -3372,6 +3378,10 @@ namespace ts {
33723378
return 14;
33733379
case SyntaxKind.AsteriskAsteriskToken:
33743380
return 15;
3381+
case SyntaxKind.BarBarEqualsToken:
3382+
case SyntaxKind.AmpersandAmpersandEqualsToken:
3383+
case SyntaxKind.QuestionQuestionEqualsToken:
3384+
return 16;
33753385
}
33763386

33773387
// -1 is lower than all other precedences. Returning it will cause binary expression
@@ -4444,6 +4454,12 @@ namespace ts {
44444454
|| token === SyntaxKind.ExclamationToken;
44454455
}
44464456

4457+
export function isLogicalAssignmentOperator(token: SyntaxKind): boolean {
4458+
return token === SyntaxKind.BarBarEqualsToken
4459+
|| token === SyntaxKind.AmpersandAmpersandEqualsToken
4460+
|| token === SyntaxKind.QuestionQuestionEqualsToken;
4461+
}
4462+
44474463
export function isAssignmentOperator(token: SyntaxKind): boolean {
44484464
return token >= SyntaxKind.FirstAssignment && token <= SyntaxKind.LastAssignment;
44494465
}

src/services/classifier.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,9 @@ namespace ts {
392392
case SyntaxKind.EqualsToken:
393393
case SyntaxKind.CommaToken:
394394
case SyntaxKind.QuestionQuestionToken:
395+
case SyntaxKind.BarBarEqualsToken:
396+
case SyntaxKind.AmpersandAmpersandEqualsToken:
397+
case SyntaxKind.QuestionQuestionEqualsToken:
395398
return true;
396399
default:
397400
return false;

src/services/codefixes/inferFromUsage.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,12 @@ 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+
788794
case SyntaxKind.AmpersandAmpersandToken:
789795
case SyntaxKind.CommaToken:
790796
case SyntaxKind.InstanceOfKeyword:

src/testRunner/unittests/factory.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ namespace ts {
7777
checkRhs(SyntaxKind.AmpersandAmpersandToken, /*expectParens*/ true);
7878
checkRhs(SyntaxKind.QuestionQuestionToken, /*expectParens*/ true);
7979
checkRhs(SyntaxKind.EqualsEqualsToken, /*expectParens*/ true);
80+
checkRhs(SyntaxKind.BarBarEqualsToken, /*expectParens*/ false);
81+
checkRhs(SyntaxKind.AmpersandAmpersandEqualsToken, /*expectParens*/ false);
82+
checkRhs(SyntaxKind.QuestionQuestionEqualsToken, /*expectParens*/ false);
8083
});
8184
});
8285
});

0 commit comments

Comments
 (0)