Skip to content

Account for right operands & fix a weird error message for leftmost nullish literals in checkNullishCoalesceOperands #59569

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 34 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39847,22 +39847,42 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
grammarErrorOnNode(right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(right.operatorToken.kind), tokenToString(operatorToken.kind));
}

const leftTarget = skipOuterExpressions(left, OuterExpressionKinds.All);
const nullishSemantics = getSyntacticNullishnessSemantics(leftTarget);
if (nullishSemantics !== PredicateSemantics.Sometimes) {
if (node.parent.kind === SyntaxKind.BinaryExpression) {
error(leftTarget, Diagnostics.This_binary_expression_is_never_nullish_Are_you_missing_parentheses);
}
else {
if (nullishSemantics === PredicateSemantics.Always) {
error(leftTarget, Diagnostics.This_expression_is_always_nullish);
}
else {
error(leftTarget, Diagnostics.Right_operand_of_is_unreachable_because_the_left_operand_is_never_nullish);
}
}
checkNullishCoalesceOperandLeft(node);
checkNullishCoalesceOperandRight(node);
}
}

function checkNullishCoalesceOperandLeft(node: BinaryExpression) {
const leftTarget = skipOuterExpressions(node.left, OuterExpressionKinds.All);

const nullishSemantics = getSyntacticNullishnessSemantics(leftTarget);
if (nullishSemantics !== PredicateSemantics.Sometimes) {
if (nullishSemantics === PredicateSemantics.Always) {
error(leftTarget, Diagnostics.This_expression_is_always_nullish);
}
else {
error(leftTarget, Diagnostics.Right_operand_of_is_unreachable_because_the_left_operand_is_never_nullish);
}
}
}

function checkNullishCoalesceOperandRight(node: BinaryExpression) {
const rightTarget = skipOuterExpressions(node.right, OuterExpressionKinds.All);
const nullishSemantics = getSyntacticNullishnessSemantics(rightTarget);
if (isNotWithinNullishCoalesceExpression(node)) {
return;
}

if (nullishSemantics === PredicateSemantics.Always) {
error(rightTarget, Diagnostics.This_expression_is_always_nullish);
}
else if (nullishSemantics === PredicateSemantics.Never) {
error(rightTarget, Diagnostics.This_expression_is_never_nullish);
}
}

function isNotWithinNullishCoalesceExpression(node: BinaryExpression) {
return !isBinaryExpression(node.parent) || node.parent.operatorToken.kind !== SyntaxKind.QuestionQuestionToken;
}

function getSyntacticNullishnessSemantics(node: Node): PredicateSemantics {
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3987,6 +3987,10 @@
"category": "Error",
"code": 2880
},
"This expression is never nullish.": {
"category": "Error",
"code": 2881
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
149 changes: 117 additions & 32 deletions tests/baselines/reference/predicateSemantics.errors.txt
Original file line number Diff line number Diff line change
@@ -1,39 +1,61 @@
predicateSemantics.ts(7,16): error TS2871: This expression is always nullish.
predicateSemantics.ts(10,16): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(26,12): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(27,12): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(28,12): error TS2871: This expression is always nullish.
predicateSemantics.ts(29,12): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(30,12): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(33,8): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(34,11): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(35,8): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(36,8): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(51,14): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(52,14): error TS2695: Left side of comma operator is unused and has no side effects.
predicateSemantics.ts(52,14): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(70,1): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(71,1): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(26,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(27,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(28,13): error TS2871: This expression is always nullish.
predicateSemantics.ts(29,13): error TS2871: This expression is always nullish.
predicateSemantics.ts(30,13): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(31,13): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(32,13): error TS2871: This expression is always nullish.
predicateSemantics.ts(32,21): error TS2871: This expression is always nullish.
predicateSemantics.ts(33,13): error TS2871: This expression is always nullish.
predicateSemantics.ts(34,13): error TS2871: This expression is always nullish.
predicateSemantics.ts(34,22): error TS2871: This expression is always nullish.
predicateSemantics.ts(36,20): error TS2871: This expression is always nullish.
predicateSemantics.ts(37,20): error TS2871: This expression is always nullish.
predicateSemantics.ts(38,21): error TS2871: This expression is always nullish.
predicateSemantics.ts(39,21): error TS2871: This expression is always nullish.
predicateSemantics.ts(40,21): error TS2871: This expression is always nullish.
predicateSemantics.ts(40,29): error TS2871: This expression is always nullish.
predicateSemantics.ts(41,21): error TS2871: This expression is always nullish.
predicateSemantics.ts(42,20): error TS2881: This expression is never nullish.
predicateSemantics.ts(43,21): error TS2881: This expression is never nullish.
predicateSemantics.ts(45,13): error TS2871: This expression is always nullish.
predicateSemantics.ts(45,21): error TS2871: This expression is always nullish.
predicateSemantics.ts(45,29): error TS2871: This expression is always nullish.
predicateSemantics.ts(46,13): error TS2871: This expression is always nullish.
predicateSemantics.ts(46,21): error TS2881: This expression is never nullish.
predicateSemantics.ts(47,13): error TS2871: This expression is always nullish.
predicateSemantics.ts(47,22): error TS2881: This expression is never nullish.
predicateSemantics.ts(50,8): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(51,11): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(52,8): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(53,8): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(70,14): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(71,14): error TS2695: Left side of comma operator is unused and has no side effects.
predicateSemantics.ts(71,14): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(89,1): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(90,1): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.


==== predicateSemantics.ts (16 errors) ====
declare let cond: any;
==== predicateSemantics.ts (38 errors) ====
declare let opt: number | undefined;

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

// Not OK: Both operands nullish
const test2 = (cond ? undefined : null) ?? "always reached";
~~~~~~~~~~~~~~~~~~~~~~~
const test2 = (opt ? undefined : null) ?? "always reached";
~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2871: This expression is always nullish.

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

// Parens
const test4 = (cond ? (undefined) : (17)) ?? 42;
const test4 = (opt ? (undefined) : (17)) ?? 42;

// Should be OK (special case)
if (!!true) {
Expand All @@ -46,21 +68,82 @@ predicateSemantics.ts(71,1): error TS2869: Right operand of ?? is unreachable be
while (true) { }
while (false) { }

const p5 = {} ?? null;
~~
const p01 = {} ?? null;
~~
!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
const p6 = 0 > 1 ?? null;
~~~~~
const p02 = 0 > 1 ?? null;
~~~~~
!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
const p7 = null ?? null;
~~~~
const p03 = null ?? 1;
~~~~
!!! error TS2871: This expression is always nullish.
const p04 = null ?? null;
~~~~
!!! error TS2871: This expression is always nullish.
const p8 = (class foo { }) && null;
~~~~~~~~~~~~~~~
const p05 = (class foo { }) && null;
~~~~~~~~~~~~~~~
!!! error TS2872: This kind of expression is always truthy.
const p9 = (class foo { }) || null;
~~~~~~~~~~~~~~~
const p06 = (class foo { }) || null;
~~~~~~~~~~~~~~~
!!! error TS2872: This kind of expression is always truthy.
const p07 = null ?? null ?? null;
~~~~
!!! error TS2871: This expression is always nullish.
~~~~
!!! error TS2871: This expression is always nullish.
const p08 = null ?? opt ?? null;
~~~~
!!! error TS2871: This expression is always nullish.
const p09 = null ?? (opt ? null : undefined) ?? null;
~~~~
!!! error TS2871: This expression is always nullish.
~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2871: This expression is always nullish.

const p10 = opt ?? null ?? 1;
~~~~
!!! error TS2871: This expression is always nullish.
const p11 = opt ?? null ?? null;
~~~~
!!! error TS2871: This expression is always nullish.
const p12 = opt ?? (null ?? 1);
~~~~
!!! error TS2871: This expression is always nullish.
const p13 = opt ?? (null ?? null);
~~~~
!!! error TS2871: This expression is always nullish.
const p14 = opt ?? (null ?? null ?? null);
~~~~
!!! error TS2871: This expression is always nullish.
~~~~
!!! error TS2871: This expression is always nullish.
const p15 = opt ?? (opt ? null : undefined) ?? null;
~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2871: This expression is always nullish.
const p16 = opt ?? 1 ?? 2;
~
!!! error TS2881: This expression is never nullish.
const p17 = opt ?? (opt ? 1 : 2) ?? 3;
~~~~~~~~~~~
!!! error TS2881: This expression is never nullish.

const p21 = null ?? null ?? null ?? null;
~~~~
!!! error TS2871: This expression is always nullish.
~~~~
!!! error TS2871: This expression is always nullish.
~~~~
!!! error TS2871: This expression is always nullish.
const p22 = null ?? 1 ?? 1;
~~~~
!!! error TS2871: This expression is always nullish.
~
!!! error TS2881: This expression is never nullish.
const p23 = null ?? (opt ? 1 : 2) ?? 1;
~~~~
!!! error TS2871: This expression is always nullish.
~~~~~~~~~~~
!!! error TS2881: This expression is never nullish.

// Outer expression tests
while ({} as any) { }
Expand All @@ -76,6 +159,8 @@ predicateSemantics.ts(71,1): error TS2869: Right operand of ?? is unreachable be
~~~~~~
!!! error TS2872: This kind of expression is always truthy.

declare let cond: any;

// Should be OK
console.log((cond || undefined) && 1 / cond);

Expand Down
Loading