Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
92 changes: 73 additions & 19 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39667,24 +39667,67 @@ 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);
}
const leftNullishSemantics = checkNullishCoalesceOperandLeft(node);
const rightNullishSemantics = checkNullishCoalesceOperandRight(node);

// check self if not checked by left operand of parent nullish coalesce expression
if (leftNullishSemantics === PredicateSemantics.Always && isNotWithinNullishCoalesceExpression(node)) {
if (rightNullishSemantics === PredicateSemantics.Always) {
error(node, Diagnostics.This_expression_is_always_nullish);
}
}
}
}

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

let nullishSemantics = getSyntacticNullishnessSemantics(leftTarget);
const isLiteral = nullishSemantics & PredicateSemantics.Literal;
nullishSemantics = nullishSemantics & ~PredicateSemantics.Literal;

if (isLiteral && isLeftmostNullishCoalesceOperand(node)) {
return nullishSemantics;
}

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);
}
}

return nullishSemantics;
}

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

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

return nullishSemantics;
}

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

return isNotWithinNullishCoalesceExpression(node);
}

function isNotWithinNullishCoalesceExpression(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 @@ -39698,24 +39741,35 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.BinaryExpression:
// List of operators that can produce null/undefined:
// = ??= ?? || ||= && &&=
switch ((node as BinaryExpression).operatorToken.kind) {
case SyntaxKind.EqualsToken:
const binaryExpression = node as BinaryExpression;
if (binaryExpression.operatorToken.kind === SyntaxKind.EqualsToken) {
return getSyntacticNullishnessSemantics(binaryExpression.right) & ~PredicateSemantics.Literal;
}

const leftSemantics = getSyntacticNullishnessSemantics(binaryExpression.left) & ~PredicateSemantics.Literal;
const rightSemantics = getSyntacticNullishnessSemantics(binaryExpression.right) & ~PredicateSemantics.Literal;
switch (binaryExpression.operatorToken.kind) {
case SyntaxKind.QuestionQuestionToken:
case SyntaxKind.QuestionQuestionEqualsToken:
case SyntaxKind.BarBarToken:
case SyntaxKind.BarBarEqualsToken:
return leftSemantics === PredicateSemantics.Never || rightSemantics === PredicateSemantics.Never ? PredicateSemantics.Never :
leftSemantics === PredicateSemantics.Sometimes || rightSemantics === PredicateSemantics.Sometimes ? PredicateSemantics.Sometimes :
PredicateSemantics.Always;
case SyntaxKind.AmpersandAmpersandToken:
case SyntaxKind.AmpersandAmpersandEqualsToken:
return PredicateSemantics.Sometimes;
return leftSemantics === PredicateSemantics.Never && rightSemantics === PredicateSemantics.Never ? PredicateSemantics.Never :
leftSemantics === PredicateSemantics.Sometimes && rightSemantics === PredicateSemantics.Sometimes ? PredicateSemantics.Sometimes :
PredicateSemantics.Always;
}
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
139 changes: 112 additions & 27 deletions tests/baselines/reference/predicateSemantics.errors.txt
Original file line number Diff line number Diff line change
@@ -1,34 +1,56 @@
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(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,13): error TS2871: This expression is always nullish.
predicateSemantics.ts(32,21): error TS2871: This expression is always nullish.
predicateSemantics.ts(34,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(39,21): error TS2871: This expression is always nullish.
predicateSemantics.ts(40,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,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(43,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(45,13): error TS2871: This expression is always nullish.
predicateSemantics.ts(45,13): error TS2871: This expression is always 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 TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
predicateSemantics.ts(47,13): error TS2869: Right operand of ?? is unreachable because the left operand 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 (11 errors) ====
declare let cond: any;
==== predicateSemantics.ts (33 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 +63,82 @@ 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;
~~~~
const p03 = null ?? 1;
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.
~~~~
!!! error TS2871: This expression is always nullish.
const p08 = null ?? opt ?? null;
const p09 = null ?? (opt ? null : undefined) ?? null;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2871: This expression is always nullish.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! 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);
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.
~~~~
!!! 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 TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
const p17 = opt ?? (opt ? 1 : 2) ?? 3;
~~~~~~~~~~~~~~~~~~~~
!!! error TS2869: Right operand of ?? is unreachable because the left operand 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.
~~~~
!!! error TS2871: This expression is always nullish.
~~~~
!!! error TS2871: This expression is always nullish.
const p22 = null ?? 1 ?? 1;
~~~~~~~~~
!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.
const p23 = null ?? (opt ? 1 : 2) ?? 1;
~~~~~~~~~~~~~~~~~~~~~
!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish.

// Outer expression tests
while ({} as any) { }
Expand All @@ -71,6 +154,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);

Loading