Skip to content

Commit 6b77d0e

Browse files
committed
Fixed nullish coalesce operator's precedence
1 parent c85e626 commit 6b77d0e

11 files changed

+93
-33
lines changed

src/compiler/checker.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39843,18 +39843,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3984339843
}
3984439844

3984539845
function checkNullishCoalesceOperands(node: BinaryExpression) {
39846-
const { left, operatorToken, right } = node;
39847-
if (operatorToken.kind === SyntaxKind.QuestionQuestionToken) {
39848-
if (isBinaryExpression(left) && (left.operatorToken.kind === SyntaxKind.BarBarToken || left.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) {
39849-
grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(left.operatorToken.kind), tokenToString(operatorToken.kind));
39850-
}
39851-
if (isBinaryExpression(right) && (right.operatorToken.kind === SyntaxKind.BarBarToken || right.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) {
39852-
grammarErrorOnNode(right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(right.operatorToken.kind), tokenToString(operatorToken.kind));
39846+
if (node.operatorToken.kind !== SyntaxKind.QuestionQuestionToken) {
39847+
return;
39848+
}
39849+
if (isBinaryExpression(node.parent)) {
39850+
const { left, operatorToken } = node.parent;
39851+
if (isBinaryExpression(left) && (operatorToken.kind === SyntaxKind.BarBarToken || operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) {
39852+
grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(SyntaxKind.QuestionQuestionToken), tokenToString(operatorToken.kind));
3985339853
}
39854-
39855-
checkNullishCoalesceOperandLeft(node);
39856-
checkNullishCoalesceOperandRight(node);
39854+
} else if (isBinaryExpression(node.left) && node.left.operatorToken.kind === SyntaxKind.BarBarToken) {
39855+
grammarErrorOnNode(node.left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(node.left.operatorToken.kind), tokenToString(SyntaxKind.QuestionQuestionToken));
39856+
} else if (isBinaryExpression(node.right) && node.right.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
39857+
grammarErrorOnNode(node.right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(SyntaxKind.QuestionQuestionToken), tokenToString(node.right.operatorToken.kind));
3985739858
}
39859+
checkNullishCoalesceOperandLeft(node);
39860+
checkNullishCoalesceOperandRight(node);
3985839861
}
3985939862

3986039863
function checkNullishCoalesceOperandLeft(node: BinaryExpression) {

src/compiler/utilities.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5658,17 +5658,17 @@ export const enum OperatorPrecedence {
56585658
// CoalesceExpression
56595659
Conditional,
56605660

5661+
// LogicalORExpression:
5662+
// LogicalANDExpression
5663+
// LogicalORExpression `||` LogicalANDExpression
5664+
LogicalOR,
5665+
56615666
// CoalesceExpression:
56625667
// CoalesceExpressionHead `??` BitwiseORExpression
56635668
// CoalesceExpressionHead:
56645669
// CoalesceExpression
56655670
// BitwiseORExpression
5666-
Coalesce = Conditional, // NOTE: This is wrong
5667-
5668-
// LogicalORExpression:
5669-
// LogicalANDExpression
5670-
// LogicalORExpression `||` LogicalANDExpression
5671-
LogicalOR,
5671+
Coalesce = LogicalOR,
56725672

56735673
// LogicalANDExpression:
56745674
// BitwiseORExpression

src/testRunner/unittests/printer.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,5 +366,62 @@ describe("unittests:: PrinterAPI", () => {
366366
ts.factory.createNoSubstitutionTemplateLiteral("\n"),
367367
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
368368
));
369+
370+
printsCorrectly("binaryBarBarExpressionWithLeftConditionalExpression", {}, printer =>
371+
printer.printNode(
372+
ts.EmitHint.Unspecified,
373+
ts.factory.createExpressionStatement(
374+
ts.factory.createBinaryExpression(
375+
ts.factory.createConditionalExpression(
376+
ts.factory.createIdentifier("a"),
377+
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
378+
ts.factory.createIdentifier("b"),
379+
ts.factory.createToken(ts.SyntaxKind.ColonToken),
380+
ts.factory.createIdentifier("c"),
381+
),
382+
ts.factory.createToken(ts.SyntaxKind.BarBarToken),
383+
ts.factory.createIdentifier("d"),
384+
),
385+
),
386+
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
387+
));
388+
389+
printsCorrectly("binaryAmpersandAmpersandExpressionWithLeftConditionalExpression", {}, printer =>
390+
printer.printNode(
391+
ts.EmitHint.Unspecified,
392+
ts.factory.createExpressionStatement(
393+
ts.factory.createBinaryExpression(
394+
ts.factory.createConditionalExpression(
395+
ts.factory.createIdentifier("a"),
396+
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
397+
ts.factory.createIdentifier("b"),
398+
ts.factory.createToken(ts.SyntaxKind.ColonToken),
399+
ts.factory.createIdentifier("c"),
400+
),
401+
ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),
402+
ts.factory.createIdentifier("d"),
403+
),
404+
),
405+
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
406+
));
407+
408+
printsCorrectly("binaryQuestionQuestionExpressionWithLeftConditionalExpression", {}, printer =>
409+
printer.printNode(
410+
ts.EmitHint.Unspecified,
411+
ts.factory.createExpressionStatement(
412+
ts.factory.createBinaryExpression(
413+
ts.factory.createConditionalExpression(
414+
ts.factory.createIdentifier("a"),
415+
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
416+
ts.factory.createIdentifier("b"),
417+
ts.factory.createToken(ts.SyntaxKind.ColonToken),
418+
ts.factory.createIdentifier("c"),
419+
),
420+
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
421+
ts.factory.createIdentifier("d"),
422+
),
423+
),
424+
ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext),
425+
));
369426
});
370427
});

tests/baselines/reference/nullishCoalescingOperator5.errors.txt

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1-
nullishCoalescingOperator5.ts(6,6): error TS5076: '||' and '??' operations cannot be mixed without parentheses.
1+
nullishCoalescingOperator5.ts(6,1): error TS5076: '??' and '||' operations cannot be mixed without parentheses.
22
nullishCoalescingOperator5.ts(9,1): error TS5076: '||' and '??' operations cannot be mixed without parentheses.
3-
nullishCoalescingOperator5.ts(12,6): error TS5076: '&&' and '??' operations cannot be mixed without parentheses.
4-
nullishCoalescingOperator5.ts(15,1): error TS5076: '&&' and '??' operations cannot be mixed without parentheses.
3+
nullishCoalescingOperator5.ts(12,6): error TS5076: '??' and '&&' operations cannot be mixed without parentheses.
54

65

7-
==== nullishCoalescingOperator5.ts (4 errors) ====
6+
==== nullishCoalescingOperator5.ts (3 errors) ====
87
declare const a: string | undefined
98
declare const b: string | undefined
109
declare const c: string | undefined
1110

1211
// should be a syntax error
1312
a ?? b || c;
14-
~~~~~~
15-
!!! error TS5076: '||' and '??' operations cannot be mixed without parentheses.
13+
~~~~~~
14+
!!! error TS5076: '??' and '||' operations cannot be mixed without parentheses.
1615

1716
// should be a syntax error
1817
a || b ?? c;
@@ -22,12 +21,10 @@ nullishCoalescingOperator5.ts(15,1): error TS5076: '&&' and '??' operations cann
2221
// should be a syntax error
2322
a ?? b && c;
2423
~~~~~~
25-
!!! error TS5076: '&&' and '??' operations cannot be mixed without parentheses.
24+
!!! error TS5076: '??' and '&&' operations cannot be mixed without parentheses.
2625

2726
// should be a syntax error
2827
a && b ?? c;
29-
~~~~~~
30-
!!! error TS5076: '&&' and '??' operations cannot be mixed without parentheses.
3128

3229
// Valid according to spec
3330
a ?? (b || c);

tests/baselines/reference/nullishCoalescingOperator5.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ a && (b ?? c);
4646
"use strict";
4747
var _a, _b, _c, _d;
4848
// should be a syntax error
49-
a !== null && a !== void 0 ? a : b || c;
49+
(a !== null && a !== void 0 ? a : b) || c;
5050
// should be a syntax error
5151
(_a = a || b) !== null && _a !== void 0 ? _a : c;
5252
// should be a syntax error

tests/baselines/reference/nullishCoalescingOperator5.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ declare const c: string | undefined
1717
a ?? b || c;
1818
>a ?? b || c : string | undefined
1919
> : ^^^^^^^^^^^^^^^^^^
20+
>a ?? b : string | undefined
21+
> : ^^^^^^^^^^^^^^^^^^
2022
>a : string | undefined
2123
> : ^^^^^^^^^^^^^^^^^^
22-
>b || c : string | undefined
23-
> : ^^^^^^^^^^^^^^^^^^
2424
>b : string | undefined
2525
> : ^^^^^^^^^^^^^^^^^^
2626
>c : string | undefined

tests/baselines/reference/plainJSGrammarErrors.errors.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ plainJSGrammarErrors.js(92,33): error TS2566: A rest element cannot have a prope
5959
plainJSGrammarErrors.js(93,42): error TS1186: A rest element cannot have an initializer.
6060
plainJSGrammarErrors.js(96,4): error TS1123: Variable declaration list cannot be empty.
6161
plainJSGrammarErrors.js(97,9): error TS5076: '||' and '??' operations cannot be mixed without parentheses.
62-
plainJSGrammarErrors.js(98,14): error TS5076: '||' and '??' operations cannot be mixed without parentheses.
62+
plainJSGrammarErrors.js(98,9): error TS5076: '??' and '||' operations cannot be mixed without parentheses.
6363
plainJSGrammarErrors.js(100,3): error TS1200: Line terminator not permitted before arrow.
6464
plainJSGrammarErrors.js(102,4): error TS1358: Tagged template expressions are not permitted in an optional chain.
6565
plainJSGrammarErrors.js(104,6): error TS1171: A comma expression is not allowed in a computed property name.
@@ -323,8 +323,8 @@ plainJSGrammarErrors.js(205,36): error TS1325: Argument of dynamic import cannot
323323
~~~~~~
324324
!!! error TS5076: '||' and '??' operations cannot be mixed without parentheses.
325325
var x = 2 ?? 3 || 4
326-
~~~~~~
327-
!!! error TS5076: '||' and '??' operations cannot be mixed without parentheses.
326+
~~~~~~
327+
!!! error TS5076: '??' and '||' operations cannot be mixed without parentheses.
328328
const arr = x
329329
=> x + 1
330330
~~

tests/baselines/reference/plainJSGrammarErrors.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,10 +421,10 @@ var x = 2 ?? 3 || 4
421421
> : ^^^^^^
422422
>2 ?? 3 || 4 : 2 | 3 | 4
423423
> : ^^^^^^^^^
424+
>2 ?? 3 : 2 | 3
425+
> : ^^^^^
424426
>2 : 2
425427
> : ^
426-
>3 || 4 : 3 | 4
427-
> : ^^^^^
428428
>3 : 3
429429
> : ^
430430
>4 : 4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(a ? b : c) && d;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(a ? b : c) || d;

0 commit comments

Comments
 (0)