Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
68 changes: 54 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39667,24 +39667,64 @@ 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);
checkNullishCoalesceOperandLeft(node);
checkNullishCoalesceOperandRight(node);
}
}

function checkNullishCoalesceOperandLeft(node: BinaryExpression) {
const leftTarget = skipOuterExpressions(node.left, OuterExpressionKinds.All);
let nullishSemantics = getSyntacticNullishnessSemantics(leftTarget);
if (nullishSemantics & PredicateSemantics.Literal && isLeftmostNullishCoalesceOperand(node)) {
return;
}

nullishSemantics = nullishSemantics & ~PredicateSemantics.Literal;

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 {
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);
}
error(leftTarget, Diagnostics.Right_operand_of_is_unreachable_because_the_left_operand_is_never_nullish);
}
}
}
}

function checkNullishCoalesceOperandRight(node: BinaryExpression) {
if (isRightmostNullishCoalesceOperand(node)) {
return;
}

const rightTarget = skipOuterExpressions(node.right, OuterExpressionKinds.All);
const nullishSemantics = getSyntacticNullishnessSemantics(rightTarget) & ~PredicateSemantics.Literal;
if (nullishSemantics !== PredicateSemantics.Sometimes) {
if (nullishSemantics === PredicateSemantics.Always) {
error(rightTarget, Diagnostics.This_expression_is_always_nullish);
}
else {
error(rightTarget, Diagnostics.This_expression_is_never_nullish);
}
}
}

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

return !isBinaryExpression(node.parent) || node.parent.operatorToken.kind !== SyntaxKind.QuestionQuestionToken;
}

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

function getSyntacticNullishnessSemantics(node: Node): PredicateSemantics {
node = skipOuterExpressions(node);
switch (node.kind) {
Expand All @@ -39710,12 +39750,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
return PredicateSemantics.Never;
case SyntaxKind.ConditionalExpression:
return getSyntacticNullishnessSemantics((node as ConditionalExpression).whenTrue) | getSyntacticNullishnessSemantics((node as ConditionalExpression).whenFalse);
return (getSyntacticNullishnessSemantics((node as ConditionalExpression).whenTrue) | getSyntacticNullishnessSemantics((node as ConditionalExpression).whenFalse)) & ~PredicateSemantics.Literal;
case SyntaxKind.NullKeyword:
return PredicateSemantics.Always;
return PredicateSemantics.Always | PredicateSemantics.Literal;
case SyntaxKind.Identifier:
if (getResolvedSymbol(node as Identifier) === undefinedSymbol) {
return PredicateSemantics.Always;
return PredicateSemantics.Always | PredicateSemantics.Literal;
}
return PredicateSemantics.Sometimes;
}
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3940,6 +3940,10 @@
"category": "Error",
"code": 2873
},
"This expression is never nullish.": {
"category": "Error",
"code": 2874
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
"code": 4000
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,7 @@ export const enum PredicateSemantics {
None = 0,
Always = 1 << 0,
Never = 1 << 1,
Literal = 1 << 2,
Sometimes = Always | Never,
}

Expand Down
95 changes: 67 additions & 28 deletions tests/baselines/reference/predicateSemantics.errors.txt
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
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(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(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,21): error TS2871: This expression is always nullish.
predicateSemantics.ts(34,20): error TS2871: This expression is always nullish.
predicateSemantics.ts(35,20): error TS2871: This expression is always nullish.
predicateSemantics.ts(38,29): error TS2871: This expression is always nullish.
predicateSemantics.ts(40,22): error TS2871: This expression is always nullish.
predicateSemantics.ts(41,21): error TS2871: This expression is always nullish.
predicateSemantics.ts(41,29): error TS2871: This expression is always nullish.
predicateSemantics.ts(42,21): error TS2874: This expression is never nullish.
predicateSemantics.ts(43,22): error TS2874: This expression is never nullish.
predicateSemantics.ts(46,8): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(47,11): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(48,8): error TS2872: This kind of expression is always truthy.
predicateSemantics.ts(49,8): error TS2872: This kind of expression is always truthy.


==== predicateSemantics.ts (11 errors) ====
declare let cond: any;
==== predicateSemantics.ts (19 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 @@ -41,21 +49,50 @@ predicateSemantics.ts(36,8): error TS2872: This kind of expression is always tru
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;
~~~~
!!! error TS2871: This expression is always nullish.
const p8 = (class foo { }) && null;
~~~~~~~~~~~~~~~
const p03 = null ?? 1;
const p04 = null ?? 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.

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);
const p13 = opt ?? (null ?? null);
const p14 = opt ?? (null ?? null ?? null);
~~~~
!!! error TS2871: This expression is always nullish.

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

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

declare let cond: any;

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

66 changes: 46 additions & 20 deletions tests/baselines/reference/predicateSemantics.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
//// [tests/cases/compiler/predicateSemantics.ts] ////

//// [predicateSemantics.ts]
declare let cond: any;
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";

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

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

// Should be OK (special case)
if (!!true) {
Expand All @@ -26,32 +26,47 @@ while (1) { }
while (true) { }
while (false) { }

const p5 = {} ?? null;
const p6 = 0 > 1 ?? null;
const p7 = null ?? null;
const p8 = (class foo { }) && null;
const p9 = (class foo { }) || null;
const p01 = {} ?? null;
const p02 = 0 > 1 ?? null;
const p03 = null ?? 1;
const p04 = null ?? null;
const p05 = (class foo { }) && null;
const p06 = (class foo { }) || null;
const p07 = null ?? null ?? null;

const p10 = opt ?? null ?? 1;
const p11 = opt ?? null ?? null;
const p12 = opt ?? (null ?? 1);
const p13 = opt ?? (null ?? null);
const p14 = opt ?? (null ?? null ?? null);

const p20 = null ?? (opt ? null : undefined) ?? null;
const p21 = null ?? null ?? null ?? null;
const p22 = null ?? 1 ?? 1;
const p23 = null ?? (opt ? 1 : 2) ?? 1;

// Outer expression tests
while ({} as any) { }
while ({} satisfies unknown) { }
while ((<any>({}))) { }
while ((({}))) { }

declare let cond: any;

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


//// [predicateSemantics.js]
var _a, _b, _c, _d, _e, _f;
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
// OK: One or other operand is possibly nullish
var test1 = (_a = (cond ? undefined : 32)) !== null && _a !== void 0 ? _a : "possibly reached";
var test1 = (_a = (opt ? undefined : 32)) !== null && _a !== void 0 ? _a : "possibly reached";
// Not OK: Both operands nullish
var test2 = (_b = (cond ? undefined : null)) !== null && _b !== void 0 ? _b : "always reached";
var test2 = (_b = (opt ? undefined : null)) !== null && _b !== void 0 ? _b : "always reached";
// Not OK: Both operands non-nullish
var test3 = (_c = (cond ? 132 : 17)) !== null && _c !== void 0 ? _c : "unreachable";
var test3 = (_c = (opt ? 132 : 17)) !== null && _c !== void 0 ? _c : "unreachable";
// Parens
var test4 = (_d = (cond ? (undefined) : (17))) !== null && _d !== void 0 ? _d : 42;
var test4 = (_d = (opt ? (undefined) : (17))) !== null && _d !== void 0 ? _d : 42;
// Should be OK (special case)
if (!!true) {
}
Expand All @@ -60,19 +75,30 @@ while (0) { }
while (1) { }
while (true) { }
while (false) { }
var p5 = (_e = {}) !== null && _e !== void 0 ? _e : null;
var p6 = (_f = 0 > 1) !== null && _f !== void 0 ? _f : null;
var p7 = null !== null && null !== void 0 ? null : null;
var p8 = (/** @class */ (function () {
var p01 = (_e = {}) !== null && _e !== void 0 ? _e : null;
var p02 = (_f = 0 > 1) !== null && _f !== void 0 ? _f : null;
var p03 = null !== null && null !== void 0 ? null : 1;
var p04 = null !== null && null !== void 0 ? null : null;
var p05 = (/** @class */ (function () {
function foo() {
}
return foo;
}())) && null;
var p9 = (/** @class */ (function () {
var p06 = (/** @class */ (function () {
function foo() {
}
return foo;
}())) || null;
var p07 = (_g = null !== null && null !== void 0 ? null : null) !== null && _g !== void 0 ? _g : null;
var p10 = (_h = opt !== null && opt !== void 0 ? opt : null) !== null && _h !== void 0 ? _h : 1;
var p11 = (_j = opt !== null && opt !== void 0 ? opt : null) !== null && _j !== void 0 ? _j : null;
var p12 = opt !== null && opt !== void 0 ? opt : (null !== null && null !== void 0 ? null : 1);
var p13 = opt !== null && opt !== void 0 ? opt : (null !== null && null !== void 0 ? null : null);
var p14 = opt !== null && opt !== void 0 ? opt : ((_k = null !== null && null !== void 0 ? null : null) !== null && _k !== void 0 ? _k : null);
var p20 = (_l = null !== null && null !== void 0 ? null : (opt ? null : undefined)) !== null && _l !== void 0 ? _l : null;
var p21 = (_o = (_m = null !== null && null !== void 0 ? null : null) !== null && _m !== void 0 ? _m : null) !== null && _o !== void 0 ? _o : null;
var p22 = (_p = null !== null && null !== void 0 ? null : 1) !== null && _p !== void 0 ? _p : 1;
var p23 = (_q = null !== null && null !== void 0 ? null : (opt ? 1 : 2)) !== null && _q !== void 0 ? _q : 1;
// Outer expression tests
while ({}) { }
while ({}) { }
Expand Down
Loading