Skip to content

Commit 72ea8bd

Browse files
committed
Also error on nullish coalesce expressions where both operands are always nullish
1 parent cab0009 commit 72ea8bd

File tree

6 files changed

+234
-79
lines changed

6 files changed

+234
-79
lines changed

src/compiler/checker.ts

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -39667,61 +39667,64 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3966739667
grammarErrorOnNode(right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(right.operatorToken.kind), tokenToString(operatorToken.kind));
3966839668
}
3966939669

39670-
checkNullishCoalesceOperandLeft(node);
39671-
checkNullishCoalesceOperandRight(node);
39670+
const leftNullishSemantics = checkNullishCoalesceOperandLeft(node);
39671+
const rightNullishSemantics = checkNullishCoalesceOperandRight(node);
39672+
39673+
// check self if not checked by left operand of parent nullish coalesce expression
39674+
if (leftNullishSemantics === PredicateSemantics.Always && isNotWithinNullishCoalesceExpression(node)) {
39675+
if (rightNullishSemantics === PredicateSemantics.Always) {
39676+
error(node, Diagnostics.This_expression_is_always_nullish);
39677+
}
39678+
}
3967239679
}
3967339680
}
3967439681

3967539682
function checkNullishCoalesceOperandLeft(node: BinaryExpression) {
3967639683
const leftTarget = skipOuterExpressions(node.left, OuterExpressionKinds.All);
39677-
let nullishSemantics = getSyntacticNullishnessSemantics(leftTarget);
39678-
if (nullishSemantics & PredicateSemantics.Literal && isLeftmostNullishCoalesceOperand(node)) {
39679-
return;
39680-
}
3968139684

39685+
let nullishSemantics = getSyntacticNullishnessSemantics(leftTarget);
39686+
const isLiteral = nullishSemantics & PredicateSemantics.Literal;
3968239687
nullishSemantics = nullishSemantics & ~PredicateSemantics.Literal;
3968339688

39689+
if (isLiteral && isLeftmostNullishCoalesceOperand(node)) {
39690+
return nullishSemantics;
39691+
}
39692+
3968439693
if (nullishSemantics !== PredicateSemantics.Sometimes) {
39685-
if (node.parent.kind === SyntaxKind.BinaryExpression) {
39686-
error(leftTarget, Diagnostics.This_binary_expression_is_never_nullish_Are_you_missing_parentheses);
39694+
if (nullishSemantics === PredicateSemantics.Always) {
39695+
error(leftTarget, Diagnostics.This_expression_is_always_nullish);
3968739696
}
3968839697
else {
39689-
if (nullishSemantics === PredicateSemantics.Always) {
39690-
error(leftTarget, Diagnostics.This_expression_is_always_nullish);
39691-
}
39692-
else {
39693-
error(leftTarget, Diagnostics.Right_operand_of_is_unreachable_because_the_left_operand_is_never_nullish);
39694-
}
39698+
error(leftTarget, Diagnostics.Right_operand_of_is_unreachable_because_the_left_operand_is_never_nullish);
3969539699
}
3969639700
}
39701+
39702+
return nullishSemantics;
3969739703
}
3969839704

3969939705
function checkNullishCoalesceOperandRight(node: BinaryExpression) {
39700-
if (isRightmostNullishCoalesceOperand(node)) {
39701-
return;
39702-
}
39703-
3970439706
const rightTarget = skipOuterExpressions(node.right, OuterExpressionKinds.All);
3970539707
const nullishSemantics = getSyntacticNullishnessSemantics(rightTarget) & ~PredicateSemantics.Literal;
39706-
if (nullishSemantics !== PredicateSemantics.Sometimes) {
39707-
if (nullishSemantics === PredicateSemantics.Always) {
39708-
error(rightTarget, Diagnostics.This_expression_is_always_nullish);
39709-
}
39710-
else {
39711-
error(rightTarget, Diagnostics.This_expression_is_never_nullish);
39712-
}
39708+
if (isNotWithinNullishCoalesceExpression(node)) {
39709+
return nullishSemantics;
3971339710
}
39711+
39712+
if (nullishSemantics === PredicateSemantics.Always) {
39713+
error(rightTarget, Diagnostics.This_expression_is_always_nullish);
39714+
}
39715+
39716+
return nullishSemantics;
3971439717
}
3971539718

3971639719
function isLeftmostNullishCoalesceOperand(node: BinaryExpression) {
3971739720
while (isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && node.parent.left === node) {
3971839721
node = node.parent;
3971939722
}
3972039723

39721-
return !isBinaryExpression(node.parent) || node.parent.operatorToken.kind !== SyntaxKind.QuestionQuestionToken;
39724+
return isNotWithinNullishCoalesceExpression(node);
3972239725
}
3972339726

39724-
function isRightmostNullishCoalesceOperand(node: BinaryExpression) {
39727+
function isNotWithinNullishCoalesceExpression(node: BinaryExpression) {
3972539728
return !isBinaryExpression(node.parent) || node.parent.operatorToken.kind !== SyntaxKind.QuestionQuestionToken;
3972639729
}
3972739730

@@ -39738,15 +39741,26 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3973839741
case SyntaxKind.BinaryExpression:
3973939742
// List of operators that can produce null/undefined:
3974039743
// = ??= ?? || ||= && &&=
39741-
switch ((node as BinaryExpression).operatorToken.kind) {
39742-
case SyntaxKind.EqualsToken:
39744+
const binaryExpression = node as BinaryExpression;
39745+
if (binaryExpression.operatorToken.kind === SyntaxKind.EqualsToken) {
39746+
return getSyntacticNullishnessSemantics(binaryExpression.right) & ~PredicateSemantics.Literal;
39747+
}
39748+
39749+
const leftSemantics = getSyntacticNullishnessSemantics(binaryExpression.left) & ~PredicateSemantics.Literal;
39750+
const rightSemantics = getSyntacticNullishnessSemantics(binaryExpression.right) & ~PredicateSemantics.Literal;
39751+
switch (binaryExpression.operatorToken.kind) {
3974339752
case SyntaxKind.QuestionQuestionToken:
3974439753
case SyntaxKind.QuestionQuestionEqualsToken:
3974539754
case SyntaxKind.BarBarToken:
3974639755
case SyntaxKind.BarBarEqualsToken:
39756+
return leftSemantics === PredicateSemantics.Never || rightSemantics === PredicateSemantics.Never ? PredicateSemantics.Never :
39757+
leftSemantics === PredicateSemantics.Sometimes || rightSemantics === PredicateSemantics.Sometimes ? PredicateSemantics.Sometimes :
39758+
PredicateSemantics.Always;
3974739759
case SyntaxKind.AmpersandAmpersandToken:
3974839760
case SyntaxKind.AmpersandAmpersandEqualsToken:
39749-
return PredicateSemantics.Sometimes;
39761+
return leftSemantics === PredicateSemantics.Never && rightSemantics === PredicateSemantics.Never ? PredicateSemantics.Never :
39762+
leftSemantics === PredicateSemantics.Sometimes && rightSemantics === PredicateSemantics.Sometimes ? PredicateSemantics.Sometimes :
39763+
PredicateSemantics.Always;
3975039764
}
3975139765
return PredicateSemantics.Never;
3975239766
case SyntaxKind.ConditionalExpression:

tests/baselines/reference/predicateSemantics.errors.txt

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,38 @@ predicateSemantics.ts(7,16): error TS2871: This expression is always nullish.
22
predicateSemantics.ts(10,16): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
33
predicateSemantics.ts(26,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
44
predicateSemantics.ts(27,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
5+
predicateSemantics.ts(29,13): error TS2871: This expression is always nullish.
56
predicateSemantics.ts(30,13): error TS2872: This kind of expression is always truthy.
67
predicateSemantics.ts(31,13): error TS2872: This kind of expression is always truthy.
8+
predicateSemantics.ts(32,13): error TS2871: This expression is always nullish.
9+
predicateSemantics.ts(32,13): error TS2871: This expression is always nullish.
710
predicateSemantics.ts(32,21): error TS2871: This expression is always nullish.
8-
predicateSemantics.ts(34,20): error TS2871: This expression is always nullish.
9-
predicateSemantics.ts(35,20): error TS2871: This expression is always nullish.
10-
predicateSemantics.ts(38,29): error TS2871: This expression is always nullish.
11-
predicateSemantics.ts(40,22): error TS2871: This expression is always nullish.
11+
predicateSemantics.ts(34,13): error TS2871: This expression is always nullish.
12+
predicateSemantics.ts(34,13): error TS2871: This expression is always nullish.
13+
predicateSemantics.ts(34,22): error TS2871: This expression is always nullish.
14+
predicateSemantics.ts(36,20): error TS2871: This expression is always nullish.
15+
predicateSemantics.ts(37,20): error TS2871: This expression is always nullish.
16+
predicateSemantics.ts(39,21): error TS2871: This expression is always nullish.
17+
predicateSemantics.ts(40,21): error TS2871: This expression is always nullish.
18+
predicateSemantics.ts(40,21): error TS2871: This expression is always nullish.
19+
predicateSemantics.ts(40,29): error TS2871: This expression is always nullish.
1220
predicateSemantics.ts(41,21): error TS2871: This expression is always nullish.
13-
predicateSemantics.ts(41,29): error TS2871: This expression is always nullish.
14-
predicateSemantics.ts(42,21): error TS2874: This expression is never nullish.
15-
predicateSemantics.ts(43,22): error TS2874: This expression is never nullish.
16-
predicateSemantics.ts(46,8): error TS2872: This kind of expression is always truthy.
17-
predicateSemantics.ts(47,11): error TS2872: This kind of expression is always truthy.
18-
predicateSemantics.ts(48,8): error TS2872: This kind of expression is always truthy.
19-
predicateSemantics.ts(49,8): error TS2872: This kind of expression is always truthy.
21+
predicateSemantics.ts(42,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
22+
predicateSemantics.ts(43,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
23+
predicateSemantics.ts(45,13): error TS2871: This expression is always nullish.
24+
predicateSemantics.ts(45,13): error TS2871: This expression is always nullish.
25+
predicateSemantics.ts(45,13): error TS2871: This expression is always nullish.
26+
predicateSemantics.ts(45,21): error TS2871: This expression is always nullish.
27+
predicateSemantics.ts(45,29): error TS2871: This expression is always nullish.
28+
predicateSemantics.ts(46,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
29+
predicateSemantics.ts(47,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
30+
predicateSemantics.ts(50,8): error TS2872: This kind of expression is always truthy.
31+
predicateSemantics.ts(51,11): error TS2872: This kind of expression is always truthy.
32+
predicateSemantics.ts(52,8): error TS2872: This kind of expression is always truthy.
33+
predicateSemantics.ts(53,8): error TS2872: This kind of expression is always truthy.
2034

2135

22-
==== predicateSemantics.ts (19 errors) ====
36+
==== predicateSemantics.ts (33 errors) ====
2337
declare let opt: number | undefined;
2438

2539
// OK: One or other operand is possibly nullish
@@ -57,15 +71,29 @@ predicateSemantics.ts(49,8): error TS2872: This kind of expression is always tru
5771
!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
5872
const p03 = null ?? 1;
5973
const p04 = null ?? null;
74+
~~~~~~~~~~~~
75+
!!! error TS2871: This expression is always nullish.
6076
const p05 = (class foo { }) && null;
6177
~~~~~~~~~~~~~~~
6278
!!! error TS2872: This kind of expression is always truthy.
6379
const p06 = (class foo { }) || null;
6480
~~~~~~~~~~~~~~~
6581
!!! error TS2872: This kind of expression is always truthy.
6682
const p07 = null ?? null ?? null;
83+
~~~~~~~~~~~~
84+
!!! error TS2871: This expression is always nullish.
85+
~~~~~~~~~~~~~~~~~~~~
86+
!!! error TS2871: This expression is always nullish.
6787
~~~~
6888
!!! error TS2871: This expression is always nullish.
89+
const p08 = null ?? opt ?? null;
90+
const p09 = null ?? (opt ? null : undefined) ?? null;
91+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
92+
!!! error TS2871: This expression is always nullish.
93+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
94+
!!! error TS2871: This expression is always nullish.
95+
~~~~~~~~~~~~~~~~~~~~~~
96+
!!! error TS2871: This expression is always nullish.
6997

7098
const p10 = opt ?? null ?? 1;
7199
~~~~
@@ -75,24 +103,42 @@ predicateSemantics.ts(49,8): error TS2872: This kind of expression is always tru
75103
!!! error TS2871: This expression is always nullish.
76104
const p12 = opt ?? (null ?? 1);
77105
const p13 = opt ?? (null ?? null);
106+
~~~~~~~~~~~~
107+
!!! error TS2871: This expression is always nullish.
78108
const p14 = opt ?? (null ?? null ?? null);
109+
~~~~~~~~~~~~
110+
!!! error TS2871: This expression is always nullish.
111+
~~~~~~~~~~~~~~~~~~~~
112+
!!! error TS2871: This expression is always nullish.
79113
~~~~
80114
!!! error TS2871: This expression is always nullish.
81-
82-
const p20 = null ?? (opt ? null : undefined) ?? null;
83-
~~~~~~~~~~~~~~~~~~~~~~
115+
const p15 = opt ?? (opt ? null : undefined) ?? null;
116+
~~~~~~~~~~~~~~~~~~~~~~
84117
!!! error TS2871: This expression is always nullish.
118+
const p16 = opt ?? 1 ?? 2;
119+
~~~~~~~~
120+
!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
121+
const p17 = opt ?? (opt ? 1 : 2) ?? 3;
122+
~~~~~~~~~~~~~~~~~~~~
123+
!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
124+
85125
const p21 = null ?? null ?? null ?? null;
126+
~~~~~~~~~~~~
127+
!!! error TS2871: This expression is always nullish.
128+
~~~~~~~~~~~~~~~~~~~~
129+
!!! error TS2871: This expression is always nullish.
130+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
131+
!!! error TS2871: This expression is always nullish.
86132
~~~~
87133
!!! error TS2871: This expression is always nullish.
88134
~~~~
89135
!!! error TS2871: This expression is always nullish.
90136
const p22 = null ?? 1 ?? 1;
91-
~
92-
!!! error TS2874: This expression is never nullish.
137+
~~~~~~~~~
138+
!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
93139
const p23 = null ?? (opt ? 1 : 2) ?? 1;
94-
~~~~~~~~~~~
95-
!!! error TS2874: This expression is never nullish.
140+
~~~~~~~~~~~~~~~~~~~~~
141+
!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
96142

97143
// Outer expression tests
98144
while ({} as any) { }

tests/baselines/reference/predicateSemantics.js

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,18 @@ const p04 = null ?? null;
3333
const p05 = (class foo { }) && null;
3434
const p06 = (class foo { }) || null;
3535
const p07 = null ?? null ?? null;
36+
const p08 = null ?? opt ?? null;
37+
const p09 = null ?? (opt ? null : undefined) ?? null;
3638

3739
const p10 = opt ?? null ?? 1;
3840
const p11 = opt ?? null ?? null;
3941
const p12 = opt ?? (null ?? 1);
4042
const p13 = opt ?? (null ?? null);
4143
const p14 = opt ?? (null ?? null ?? null);
44+
const p15 = opt ?? (opt ? null : undefined) ?? null;
45+
const p16 = opt ?? 1 ?? 2;
46+
const p17 = opt ?? (opt ? 1 : 2) ?? 3;
4247

43-
const p20 = null ?? (opt ? null : undefined) ?? null;
4448
const p21 = null ?? null ?? null ?? null;
4549
const p22 = null ?? 1 ?? 1;
4650
const p23 = null ?? (opt ? 1 : 2) ?? 1;
@@ -58,7 +62,7 @@ console.log((cond || undefined) && 1 / cond);
5862

5963

6064
//// [predicateSemantics.js]
61-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
65+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
6266
// OK: One or other operand is possibly nullish
6367
var test1 = (_a = (opt ? undefined : 32)) !== null && _a !== void 0 ? _a : "possibly reached";
6468
// Not OK: Both operands nullish
@@ -90,15 +94,19 @@ var p06 = (/** @class */ (function () {
9094
return foo;
9195
}())) || null;
9296
var p07 = (_g = null !== null && null !== void 0 ? null : null) !== null && _g !== void 0 ? _g : null;
93-
var p10 = (_h = opt !== null && opt !== void 0 ? opt : null) !== null && _h !== void 0 ? _h : 1;
94-
var p11 = (_j = opt !== null && opt !== void 0 ? opt : null) !== null && _j !== void 0 ? _j : null;
97+
var p08 = (_h = null !== null && null !== void 0 ? null : opt) !== null && _h !== void 0 ? _h : null;
98+
var p09 = (_j = null !== null && null !== void 0 ? null : (opt ? null : undefined)) !== null && _j !== void 0 ? _j : null;
99+
var p10 = (_k = opt !== null && opt !== void 0 ? opt : null) !== null && _k !== void 0 ? _k : 1;
100+
var p11 = (_l = opt !== null && opt !== void 0 ? opt : null) !== null && _l !== void 0 ? _l : null;
95101
var p12 = opt !== null && opt !== void 0 ? opt : (null !== null && null !== void 0 ? null : 1);
96102
var p13 = opt !== null && opt !== void 0 ? opt : (null !== null && null !== void 0 ? null : null);
97-
var p14 = opt !== null && opt !== void 0 ? opt : ((_k = null !== null && null !== void 0 ? null : null) !== null && _k !== void 0 ? _k : null);
98-
var p20 = (_l = null !== null && null !== void 0 ? null : (opt ? null : undefined)) !== null && _l !== void 0 ? _l : null;
99-
var p21 = (_o = (_m = null !== null && null !== void 0 ? null : null) !== null && _m !== void 0 ? _m : null) !== null && _o !== void 0 ? _o : null;
100-
var p22 = (_p = null !== null && null !== void 0 ? null : 1) !== null && _p !== void 0 ? _p : 1;
101-
var p23 = (_q = null !== null && null !== void 0 ? null : (opt ? 1 : 2)) !== null && _q !== void 0 ? _q : 1;
103+
var p14 = opt !== null && opt !== void 0 ? opt : ((_m = null !== null && null !== void 0 ? null : null) !== null && _m !== void 0 ? _m : null);
104+
var p15 = (_o = opt !== null && opt !== void 0 ? opt : (opt ? null : undefined)) !== null && _o !== void 0 ? _o : null;
105+
var p16 = (_p = opt !== null && opt !== void 0 ? opt : 1) !== null && _p !== void 0 ? _p : 2;
106+
var p17 = (_q = opt !== null && opt !== void 0 ? opt : (opt ? 1 : 2)) !== null && _q !== void 0 ? _q : 3;
107+
var p21 = (_s = (_r = null !== null && null !== void 0 ? null : null) !== null && _r !== void 0 ? _r : null) !== null && _s !== void 0 ? _s : null;
108+
var p22 = (_t = null !== null && null !== void 0 ? null : 1) !== null && _t !== void 0 ? _t : 1;
109+
var p23 = (_u = null !== null && null !== void 0 ? null : (opt ? 1 : 2)) !== null && _u !== void 0 ? _u : 1;
102110
// Outer expression tests
103111
while ({}) { }
104112
while ({}) { }

0 commit comments

Comments
 (0)