Skip to content

Commit 716a382

Browse files
committed
Account for right operands & leftmost nullish literals in checkNullishCoalesceOperands
1 parent 278cb94 commit 716a382

File tree

8 files changed

+459
-150
lines changed

8 files changed

+459
-150
lines changed

src/compiler/checker.ts

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39667,23 +39667,63 @@ 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-
const leftTarget = skipOuterExpressions(left, OuterExpressionKinds.All);
39671-
const nullishSemantics = getSyntacticNullishnessSemantics(leftTarget);
39672-
if (nullishSemantics !== PredicateSemantics.Sometimes) {
39673-
if (node.parent.kind === SyntaxKind.BinaryExpression) {
39674-
error(leftTarget, Diagnostics.This_binary_expression_is_never_nullish_Are_you_missing_parentheses);
39670+
checkNullishCoalesceOperandLeft(node);
39671+
checkNullishCoalesceOperandRight(node);
39672+
}
39673+
}
39674+
39675+
function checkNullishCoalesceOperandLeft(node: BinaryExpression) {
39676+
const leftTarget = skipOuterExpressions(node.left, OuterExpressionKinds.All);
39677+
let nullishSemantics = getSyntacticNullishnessSemantics(leftTarget);
39678+
if (nullishSemantics & PredicateSemantics.Literal && isLeftmostNullishCoalesceOperand(node)) {
39679+
return;
39680+
}
39681+
39682+
nullishSemantics = nullishSemantics & ~PredicateSemantics.Literal;
39683+
39684+
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);
39687+
}
39688+
else {
39689+
if (nullishSemantics === PredicateSemantics.Always) {
39690+
error(leftTarget, Diagnostics.This_expression_is_always_nullish);
3967539691
}
3967639692
else {
39677-
if (nullishSemantics === PredicateSemantics.Always) {
39678-
error(leftTarget, Diagnostics.This_expression_is_always_nullish);
39679-
}
39680-
else {
39681-
error(leftTarget, Diagnostics.Right_operand_of_is_unreachable_because_the_left_operand_is_never_nullish);
39682-
}
39693+
error(leftTarget, Diagnostics.Right_operand_of_is_unreachable_because_the_left_operand_is_never_nullish);
3968339694
}
3968439695
}
3968539696
}
3968639697
}
39698+
39699+
function checkNullishCoalesceOperandRight(node: BinaryExpression) {
39700+
if (isRightmostNullishCoalesceOperand(node)) {
39701+
return;
39702+
}
39703+
39704+
const rightTarget = skipOuterExpressions(node.right, OuterExpressionKinds.All);
39705+
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+
}
39713+
}
39714+
}
39715+
39716+
function isLeftmostNullishCoalesceOperand(node: BinaryExpression) {
39717+
while (isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && node.parent.left === node) {
39718+
node = node.parent;
39719+
}
39720+
39721+
return !isBinaryExpression(node.parent) || node.parent.operatorToken.kind !== SyntaxKind.QuestionQuestionToken;
39722+
}
39723+
39724+
function isRightmostNullishCoalesceOperand(node: BinaryExpression) {
39725+
return !isBinaryExpression(node.parent) || node.parent.operatorToken.kind !== SyntaxKind.QuestionQuestionToken;
39726+
}
3968739727

3968839728
function getSyntacticNullishnessSemantics(node: Node): PredicateSemantics {
3968939729
node = skipOuterExpressions(node);
@@ -39710,12 +39750,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3971039750
}
3971139751
return PredicateSemantics.Never;
3971239752
case SyntaxKind.ConditionalExpression:
39713-
return getSyntacticNullishnessSemantics((node as ConditionalExpression).whenTrue) | getSyntacticNullishnessSemantics((node as ConditionalExpression).whenFalse);
39753+
return (getSyntacticNullishnessSemantics((node as ConditionalExpression).whenTrue) | getSyntacticNullishnessSemantics((node as ConditionalExpression).whenFalse)) & ~PredicateSemantics.Literal;
3971439754
case SyntaxKind.NullKeyword:
39715-
return PredicateSemantics.Always;
39755+
return PredicateSemantics.Always | PredicateSemantics.Literal;
3971639756
case SyntaxKind.Identifier:
3971739757
if (getResolvedSymbol(node as Identifier) === undefinedSymbol) {
39718-
return PredicateSemantics.Always;
39758+
return PredicateSemantics.Always | PredicateSemantics.Literal;
3971939759
}
3972039760
return PredicateSemantics.Sometimes;
3972139761
}

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3940,6 +3940,10 @@
39403940
"category": "Error",
39413941
"code": 2873
39423942
},
3943+
"This expression is never nullish.": {
3944+
"category": "Error",
3945+
"code": 2874
3946+
},
39433947
"Import declaration '{0}' is using private name '{1}'.": {
39443948
"category": "Error",
39453949
"code": 4000

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,7 @@ export const enum PredicateSemantics {
928928
None = 0,
929929
Always = 1 << 0,
930930
Never = 1 << 1,
931+
Literal = 1 << 2,
931932
Sometimes = Always | Never,
932933
}
933934

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,42 @@
11
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.
3-
predicateSemantics.ts(26,12): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
4-
predicateSemantics.ts(27,12): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
5-
predicateSemantics.ts(28,12): error TS2871: This expression is always nullish.
6-
predicateSemantics.ts(29,12): error TS2872: This kind of expression is always truthy.
7-
predicateSemantics.ts(30,12): error TS2872: This kind of expression is always truthy.
8-
predicateSemantics.ts(33,8): error TS2872: This kind of expression is always truthy.
9-
predicateSemantics.ts(34,11): error TS2872: This kind of expression is always truthy.
10-
predicateSemantics.ts(35,8): error TS2872: This kind of expression is always truthy.
11-
predicateSemantics.ts(36,8): error TS2872: This kind of expression is always truthy.
3+
predicateSemantics.ts(26,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
4+
predicateSemantics.ts(27,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
5+
predicateSemantics.ts(30,13): error TS2872: This kind of expression is always truthy.
6+
predicateSemantics.ts(31,13): error TS2872: This kind of expression is always truthy.
7+
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.
12+
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.
1220

1321

14-
==== predicateSemantics.ts (11 errors) ====
15-
declare let cond: any;
22+
==== predicateSemantics.ts (19 errors) ====
23+
declare let opt: number | undefined;
1624

1725
// OK: One or other operand is possibly nullish
18-
const test1 = (cond ? undefined : 32) ?? "possibly reached";
26+
const test1 = (opt ? undefined : 32) ?? "possibly reached";
1927

2028
// Not OK: Both operands nullish
21-
const test2 = (cond ? undefined : null) ?? "always reached";
22-
~~~~~~~~~~~~~~~~~~~~~~~
29+
const test2 = (opt ? undefined : null) ?? "always reached";
30+
~~~~~~~~~~~~~~~~~~~~~~
2331
!!! error TS2871: This expression is always nullish.
2432

2533
// Not OK: Both operands non-nullish
26-
const test3 = (cond ? 132 : 17) ?? "unreachable";
27-
~~~~~~~~~~~~~~~
34+
const test3 = (opt ? 132 : 17) ?? "unreachable";
35+
~~~~~~~~~~~~~~
2836
!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
2937

3038
// Parens
31-
const test4 = (cond ? (undefined) : (17)) ?? 42;
39+
const test4 = (opt ? (undefined) : (17)) ?? 42;
3240

3341
// Should be OK (special case)
3442
if (!!true) {
@@ -41,21 +49,50 @@ predicateSemantics.ts(36,8): error TS2872: This kind of expression is always tru
4149
while (true) { }
4250
while (false) { }
4351

44-
const p5 = {} ?? null;
45-
~~
52+
const p01 = {} ?? null;
53+
~~
4654
!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
47-
const p6 = 0 > 1 ?? null;
48-
~~~~~
55+
const p02 = 0 > 1 ?? null;
56+
~~~~~
4957
!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
50-
const p7 = null ?? null;
51-
~~~~
52-
!!! error TS2871: This expression is always nullish.
53-
const p8 = (class foo { }) && null;
54-
~~~~~~~~~~~~~~~
58+
const p03 = null ?? 1;
59+
const p04 = null ?? null;
60+
const p05 = (class foo { }) && null;
61+
~~~~~~~~~~~~~~~
5562
!!! error TS2872: This kind of expression is always truthy.
56-
const p9 = (class foo { }) || null;
57-
~~~~~~~~~~~~~~~
63+
const p06 = (class foo { }) || null;
64+
~~~~~~~~~~~~~~~
5865
!!! error TS2872: This kind of expression is always truthy.
66+
const p07 = null ?? null ?? null;
67+
~~~~
68+
!!! error TS2871: This expression is always nullish.
69+
70+
const p10 = opt ?? null ?? 1;
71+
~~~~
72+
!!! error TS2871: This expression is always nullish.
73+
const p11 = opt ?? null ?? null;
74+
~~~~
75+
!!! error TS2871: This expression is always nullish.
76+
const p12 = opt ?? (null ?? 1);
77+
const p13 = opt ?? (null ?? null);
78+
const p14 = opt ?? (null ?? null ?? null);
79+
~~~~
80+
!!! error TS2871: This expression is always nullish.
81+
82+
const p20 = null ?? (opt ? null : undefined) ?? null;
83+
~~~~~~~~~~~~~~~~~~~~~~
84+
!!! error TS2871: This expression is always nullish.
85+
const p21 = null ?? null ?? null ?? null;
86+
~~~~
87+
!!! error TS2871: This expression is always nullish.
88+
~~~~
89+
!!! error TS2871: This expression is always nullish.
90+
const p22 = null ?? 1 ?? 1;
91+
~
92+
!!! error TS2874: This expression is never nullish.
93+
const p23 = null ?? (opt ? 1 : 2) ?? 1;
94+
~~~~~~~~~~~
95+
!!! error TS2874: This expression is never nullish.
5996

6097
// Outer expression tests
6198
while ({} as any) { }
@@ -71,6 +108,8 @@ predicateSemantics.ts(36,8): error TS2872: This kind of expression is always tru
71108
~~~~~~
72109
!!! error TS2872: This kind of expression is always truthy.
73110

111+
declare let cond: any;
112+
74113
// Should be OK
75114
console.log((cond || undefined) && 1 / cond);
76115

tests/baselines/reference/predicateSemantics.js

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
//// [tests/cases/compiler/predicateSemantics.ts] ////
22

33
//// [predicateSemantics.ts]
4-
declare let cond: any;
4+
declare let opt: number | undefined;
55

66
// OK: One or other operand is possibly nullish
7-
const test1 = (cond ? undefined : 32) ?? "possibly reached";
7+
const test1 = (opt ? undefined : 32) ?? "possibly reached";
88

99
// Not OK: Both operands nullish
10-
const test2 = (cond ? undefined : null) ?? "always reached";
10+
const test2 = (opt ? undefined : null) ?? "always reached";
1111

1212
// Not OK: Both operands non-nullish
13-
const test3 = (cond ? 132 : 17) ?? "unreachable";
13+
const test3 = (opt ? 132 : 17) ?? "unreachable";
1414

1515
// Parens
16-
const test4 = (cond ? (undefined) : (17)) ?? 42;
16+
const test4 = (opt ? (undefined) : (17)) ?? 42;
1717

1818
// Should be OK (special case)
1919
if (!!true) {
@@ -26,32 +26,47 @@ while (1) { }
2626
while (true) { }
2727
while (false) { }
2828

29-
const p5 = {} ?? null;
30-
const p6 = 0 > 1 ?? null;
31-
const p7 = null ?? null;
32-
const p8 = (class foo { }) && null;
33-
const p9 = (class foo { }) || null;
29+
const p01 = {} ?? null;
30+
const p02 = 0 > 1 ?? null;
31+
const p03 = null ?? 1;
32+
const p04 = null ?? null;
33+
const p05 = (class foo { }) && null;
34+
const p06 = (class foo { }) || null;
35+
const p07 = null ?? null ?? null;
36+
37+
const p10 = opt ?? null ?? 1;
38+
const p11 = opt ?? null ?? null;
39+
const p12 = opt ?? (null ?? 1);
40+
const p13 = opt ?? (null ?? null);
41+
const p14 = opt ?? (null ?? null ?? null);
42+
43+
const p20 = null ?? (opt ? null : undefined) ?? null;
44+
const p21 = null ?? null ?? null ?? null;
45+
const p22 = null ?? 1 ?? 1;
46+
const p23 = null ?? (opt ? 1 : 2) ?? 1;
3447

3548
// Outer expression tests
3649
while ({} as any) { }
3750
while ({} satisfies unknown) { }
3851
while ((<any>({}))) { }
3952
while ((({}))) { }
4053

54+
declare let cond: any;
55+
4156
// Should be OK
4257
console.log((cond || undefined) && 1 / cond);
4358

4459

4560
//// [predicateSemantics.js]
46-
var _a, _b, _c, _d, _e, _f;
61+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
4762
// OK: One or other operand is possibly nullish
48-
var test1 = (_a = (cond ? undefined : 32)) !== null && _a !== void 0 ? _a : "possibly reached";
63+
var test1 = (_a = (opt ? undefined : 32)) !== null && _a !== void 0 ? _a : "possibly reached";
4964
// Not OK: Both operands nullish
50-
var test2 = (_b = (cond ? undefined : null)) !== null && _b !== void 0 ? _b : "always reached";
65+
var test2 = (_b = (opt ? undefined : null)) !== null && _b !== void 0 ? _b : "always reached";
5166
// Not OK: Both operands non-nullish
52-
var test3 = (_c = (cond ? 132 : 17)) !== null && _c !== void 0 ? _c : "unreachable";
67+
var test3 = (_c = (opt ? 132 : 17)) !== null && _c !== void 0 ? _c : "unreachable";
5368
// Parens
54-
var test4 = (_d = (cond ? (undefined) : (17))) !== null && _d !== void 0 ? _d : 42;
69+
var test4 = (_d = (opt ? (undefined) : (17))) !== null && _d !== void 0 ? _d : 42;
5570
// Should be OK (special case)
5671
if (!!true) {
5772
}
@@ -60,19 +75,30 @@ while (0) { }
6075
while (1) { }
6176
while (true) { }
6277
while (false) { }
63-
var p5 = (_e = {}) !== null && _e !== void 0 ? _e : null;
64-
var p6 = (_f = 0 > 1) !== null && _f !== void 0 ? _f : null;
65-
var p7 = null !== null && null !== void 0 ? null : null;
66-
var p8 = (/** @class */ (function () {
78+
var p01 = (_e = {}) !== null && _e !== void 0 ? _e : null;
79+
var p02 = (_f = 0 > 1) !== null && _f !== void 0 ? _f : null;
80+
var p03 = null !== null && null !== void 0 ? null : 1;
81+
var p04 = null !== null && null !== void 0 ? null : null;
82+
var p05 = (/** @class */ (function () {
6783
function foo() {
6884
}
6985
return foo;
7086
}())) && null;
71-
var p9 = (/** @class */ (function () {
87+
var p06 = (/** @class */ (function () {
7288
function foo() {
7389
}
7490
return foo;
7591
}())) || null;
92+
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;
95+
var p12 = opt !== null && opt !== void 0 ? opt : (null !== null && null !== void 0 ? null : 1);
96+
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;
76102
// Outer expression tests
77103
while ({}) { }
78104
while ({}) { }

0 commit comments

Comments
 (0)