From cab00093fe8b82af5727bff5697b649ed48c9a7e Mon Sep 17 00:00:00 2001 From: Chiri Vulpes Date: Fri, 9 Aug 2024 13:45:53 +1200 Subject: [PATCH 1/4] Account for right operands & leftmost nullish literals in checkNullishCoalesceOperands --- src/compiler/checker.ts | 68 ++++- src/compiler/diagnosticMessages.json | 4 + src/compiler/types.ts | 1 + .../reference/predicateSemantics.errors.txt | 95 +++++-- .../baselines/reference/predicateSemantics.js | 66 +++-- .../reference/predicateSemantics.symbols | 92 +++++-- .../reference/predicateSemantics.types | 248 ++++++++++++++---- tests/cases/compiler/predicateSemantics.ts | 35 ++- 8 files changed, 459 insertions(+), 150 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9ca49d3cdee54..417761418ef09 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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) { @@ -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; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 850d5ca1022af..6847c175fbb26 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -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 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3f5306cec0383..842624bdb2ab5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -928,6 +928,7 @@ export const enum PredicateSemantics { None = 0, Always = 1 << 0, Never = 1 << 1, + Literal = 1 << 2, Sometimes = Always | Never, } diff --git a/tests/baselines/reference/predicateSemantics.errors.txt b/tests/baselines/reference/predicateSemantics.errors.txt index 883ce4570983b..92e4132e8661e 100644 --- a/tests/baselines/reference/predicateSemantics.errors.txt +++ b/tests/baselines/reference/predicateSemantics.errors.txt @@ -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) { @@ -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) { } @@ -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); \ No newline at end of file diff --git a/tests/baselines/reference/predicateSemantics.js b/tests/baselines/reference/predicateSemantics.js index 4ed294189332c..d82b26d2ce4bd 100644 --- a/tests/baselines/reference/predicateSemantics.js +++ b/tests/baselines/reference/predicateSemantics.js @@ -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) { @@ -26,11 +26,24 @@ 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) { } @@ -38,20 +51,22 @@ while ({} satisfies unknown) { } while ((({}))) { } 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) { } @@ -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 ({}) { } diff --git a/tests/baselines/reference/predicateSemantics.symbols b/tests/baselines/reference/predicateSemantics.symbols index 39ce291427a74..c8dc55fda4c77 100644 --- a/tests/baselines/reference/predicateSemantics.symbols +++ b/tests/baselines/reference/predicateSemantics.symbols @@ -1,30 +1,30 @@ //// [tests/cases/compiler/predicateSemantics.ts] //// === predicateSemantics.ts === -declare let cond: any; ->cond : Symbol(cond, Decl(predicateSemantics.ts, 0, 11)) +declare let opt: number | undefined; +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) // OK: One or other operand is possibly nullish -const test1 = (cond ? undefined : 32) ?? "possibly reached"; +const test1 = (opt ? undefined : 32) ?? "possibly reached"; >test1 : Symbol(test1, Decl(predicateSemantics.ts, 3, 5)) ->cond : Symbol(cond, Decl(predicateSemantics.ts, 0, 11)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) >undefined : Symbol(undefined) // Not OK: Both operands nullish -const test2 = (cond ? undefined : null) ?? "always reached"; +const test2 = (opt ? undefined : null) ?? "always reached"; >test2 : Symbol(test2, Decl(predicateSemantics.ts, 6, 5)) ->cond : Symbol(cond, Decl(predicateSemantics.ts, 0, 11)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) >undefined : Symbol(undefined) // Not OK: Both operands non-nullish -const test3 = (cond ? 132 : 17) ?? "unreachable"; +const test3 = (opt ? 132 : 17) ?? "unreachable"; >test3 : Symbol(test3, Decl(predicateSemantics.ts, 9, 5)) ->cond : Symbol(cond, Decl(predicateSemantics.ts, 0, 11)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) // Parens -const test4 = (cond ? (undefined) : (17)) ?? 42; +const test4 = (opt ? (undefined) : (17)) ?? 42; >test4 : Symbol(test4, Decl(predicateSemantics.ts, 12, 5)) ->cond : Symbol(cond, Decl(predicateSemantics.ts, 0, 11)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) >undefined : Symbol(undefined) // Should be OK (special case) @@ -38,22 +38,63 @@ while (1) { } while (true) { } while (false) { } -const p5 = {} ?? null; ->p5 : Symbol(p5, Decl(predicateSemantics.ts, 25, 5)) +const p01 = {} ?? null; +>p01 : Symbol(p01, Decl(predicateSemantics.ts, 25, 5)) + +const p02 = 0 > 1 ?? null; +>p02 : Symbol(p02, Decl(predicateSemantics.ts, 26, 5)) + +const p03 = null ?? 1; +>p03 : Symbol(p03, Decl(predicateSemantics.ts, 27, 5)) + +const p04 = null ?? null; +>p04 : Symbol(p04, Decl(predicateSemantics.ts, 28, 5)) + +const p05 = (class foo { }) && null; +>p05 : Symbol(p05, Decl(predicateSemantics.ts, 29, 5)) +>foo : Symbol(foo, Decl(predicateSemantics.ts, 29, 13)) + +const p06 = (class foo { }) || null; +>p06 : Symbol(p06, Decl(predicateSemantics.ts, 30, 5)) +>foo : Symbol(foo, Decl(predicateSemantics.ts, 30, 13)) + +const p07 = null ?? null ?? null; +>p07 : Symbol(p07, Decl(predicateSemantics.ts, 31, 5)) + +const p10 = opt ?? null ?? 1; +>p10 : Symbol(p10, Decl(predicateSemantics.ts, 33, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) -const p6 = 0 > 1 ?? null; ->p6 : Symbol(p6, Decl(predicateSemantics.ts, 26, 5)) +const p11 = opt ?? null ?? null; +>p11 : Symbol(p11, Decl(predicateSemantics.ts, 34, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) -const p7 = null ?? null; ->p7 : Symbol(p7, Decl(predicateSemantics.ts, 27, 5)) +const p12 = opt ?? (null ?? 1); +>p12 : Symbol(p12, Decl(predicateSemantics.ts, 35, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) -const p8 = (class foo { }) && null; ->p8 : Symbol(p8, Decl(predicateSemantics.ts, 28, 5)) ->foo : Symbol(foo, Decl(predicateSemantics.ts, 28, 12)) +const p13 = opt ?? (null ?? null); +>p13 : Symbol(p13, Decl(predicateSemantics.ts, 36, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) -const p9 = (class foo { }) || null; ->p9 : Symbol(p9, Decl(predicateSemantics.ts, 29, 5)) ->foo : Symbol(foo, Decl(predicateSemantics.ts, 29, 12)) +const p14 = opt ?? (null ?? null ?? null); +>p14 : Symbol(p14, Decl(predicateSemantics.ts, 37, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) + +const p20 = null ?? (opt ? null : undefined) ?? null; +>p20 : Symbol(p20, Decl(predicateSemantics.ts, 39, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) +>undefined : Symbol(undefined) + +const p21 = null ?? null ?? null ?? null; +>p21 : Symbol(p21, Decl(predicateSemantics.ts, 40, 5)) + +const p22 = null ?? 1 ?? 1; +>p22 : Symbol(p22, Decl(predicateSemantics.ts, 41, 5)) + +const p23 = null ?? (opt ? 1 : 2) ?? 1; +>p23 : Symbol(p23, Decl(predicateSemantics.ts, 42, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) // Outer expression tests while ({} as any) { } @@ -61,12 +102,15 @@ while ({} satisfies unknown) { } while ((({}))) { } while ((({}))) { } +declare let cond: any; +>cond : Symbol(cond, Decl(predicateSemantics.ts, 50, 11)) + // Should be OK console.log((cond || undefined) && 1 / cond); >console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) >console : Symbol(console, Decl(lib.dom.d.ts, --, --)) >log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) ->cond : Symbol(cond, Decl(predicateSemantics.ts, 0, 11)) +>cond : Symbol(cond, Decl(predicateSemantics.ts, 50, 11)) >undefined : Symbol(undefined) ->cond : Symbol(cond, Decl(predicateSemantics.ts, 0, 11)) +>cond : Symbol(cond, Decl(predicateSemantics.ts, 50, 11)) diff --git a/tests/baselines/reference/predicateSemantics.types b/tests/baselines/reference/predicateSemantics.types index b4f418ce33943..d61059969eb24 100644 --- a/tests/baselines/reference/predicateSemantics.types +++ b/tests/baselines/reference/predicateSemantics.types @@ -1,22 +1,22 @@ //// [tests/cases/compiler/predicateSemantics.ts] //// === predicateSemantics.ts === -declare let cond: any; ->cond : any -> : ^^^ +declare let opt: number | undefined; +>opt : number +> : ^^^^^^ // OK: One or other operand is possibly nullish -const test1 = (cond ? undefined : 32) ?? "possibly reached"; +const test1 = (opt ? undefined : 32) ?? "possibly reached"; >test1 : 32 | "possibly reached" > : ^^^^^^^^^^^^^^^^^^^^^^^ ->(cond ? undefined : 32) ?? "possibly reached" : 32 | "possibly reached" -> : ^^^^^^^^^^^^^^^^^^^^^^^ ->(cond ? undefined : 32) : 32 -> : ^^ ->cond ? undefined : 32 : 32 -> : ^^ ->cond : any -> : ^^^ +>(opt ? undefined : 32) ?? "possibly reached" : 32 | "possibly reached" +> : ^^^^^^^^^^^^^^^^^^^^^^^ +>(opt ? undefined : 32) : 32 +> : ^^ +>opt ? undefined : 32 : 32 +> : ^^ +>opt : number +> : ^^^^^^ >undefined : undefined > : ^^^^^^^^^ >32 : 32 @@ -25,34 +25,34 @@ const test1 = (cond ? undefined : 32) ?? "possibly reached"; > : ^^^^^^^^^^^^^^^^^^ // Not OK: Both operands nullish -const test2 = (cond ? undefined : null) ?? "always reached"; +const test2 = (opt ? undefined : null) ?? "always reached"; >test2 : "always reached" > : ^^^^^^^^^^^^^^^^ ->(cond ? undefined : null) ?? "always reached" : "always reached" -> : ^^^^^^^^^^^^^^^^ ->(cond ? undefined : null) : null -> : ^^^^ ->cond ? undefined : null : null -> : ^^^^ ->cond : any -> : ^^^ +>(opt ? undefined : null) ?? "always reached" : "always reached" +> : ^^^^^^^^^^^^^^^^ +>(opt ? undefined : null) : null +> : ^^^^ +>opt ? undefined : null : null +> : ^^^^ +>opt : number +> : ^^^^^^ >undefined : undefined > : ^^^^^^^^^ >"always reached" : "always reached" > : ^^^^^^^^^^^^^^^^ // Not OK: Both operands non-nullish -const test3 = (cond ? 132 : 17) ?? "unreachable"; +const test3 = (opt ? 132 : 17) ?? "unreachable"; >test3 : 132 | 17 | "unreachable" > : ^^^^^^^^^^^^^^^^^^^^^^^^ ->(cond ? 132 : 17) ?? "unreachable" : 132 | 17 | "unreachable" -> : ^^^^^^^^^^^^^^^^^^^^^^^^ ->(cond ? 132 : 17) : 132 | 17 -> : ^^^^^^^^ ->cond ? 132 : 17 : 132 | 17 -> : ^^^^^^^^ ->cond : any -> : ^^^ +>(opt ? 132 : 17) ?? "unreachable" : 132 | 17 | "unreachable" +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>(opt ? 132 : 17) : 132 | 17 +> : ^^^^^^^^ +>opt ? 132 : 17 : 132 | 17 +> : ^^^^^^^^ +>opt : number +> : ^^^^^^ >132 : 132 > : ^^^ >17 : 17 @@ -61,17 +61,17 @@ const test3 = (cond ? 132 : 17) ?? "unreachable"; > : ^^^^^^^^^^^^^ // Parens -const test4 = (cond ? (undefined) : (17)) ?? 42; +const test4 = (opt ? (undefined) : (17)) ?? 42; >test4 : 17 | 42 > : ^^^^^^^ ->(cond ? (undefined) : (17)) ?? 42 : 17 | 42 -> : ^^^^^^^ ->(cond ? (undefined) : (17)) : 17 -> : ^^ ->cond ? (undefined) : (17) : 17 -> : ^^ ->cond : any -> : ^^^ +>(opt ? (undefined) : (17)) ?? 42 : 17 | 42 +> : ^^^^^^^ +>(opt ? (undefined) : (17)) : 17 +> : ^^ +>opt ? (undefined) : (17) : 17 +> : ^^ +>opt : number +> : ^^^^^^ >(undefined) : undefined > : ^^^^^^^^^ >undefined : undefined @@ -111,17 +111,17 @@ while (false) { } >false : false > : ^^^^^ -const p5 = {} ?? null; ->p5 : {} -> : ^^ +const p01 = {} ?? null; +>p01 : {} +> : ^^ >{} ?? null : {} > : ^^ >{} : {} > : ^^ -const p6 = 0 > 1 ?? null; ->p6 : boolean -> : ^^^^^^^ +const p02 = 0 > 1 ?? null; +>p02 : boolean +> : ^^^^^^^ >0 > 1 ?? null : boolean > : ^^^^^^^ >0 > 1 : boolean @@ -131,15 +131,23 @@ const p6 = 0 > 1 ?? null; >1 : 1 > : ^ -const p7 = null ?? null; ->p7 : any -> : ^^^ +const p03 = null ?? 1; +>p03 : 1 +> : ^ +>null ?? 1 : 1 +> : ^ +>1 : 1 +> : ^ + +const p04 = null ?? null; +>p04 : any +> : ^^^ >null ?? null : null > : ^^^^ -const p8 = (class foo { }) && null; ->p8 : any -> : ^^^ +const p05 = (class foo { }) && null; +>p05 : any +> : ^^^ >(class foo { }) && null : null > : ^^^^ >(class foo { }) : typeof foo @@ -149,9 +157,9 @@ const p8 = (class foo { }) && null; >foo : typeof foo > : ^^^^^^^^^^ -const p9 = (class foo { }) || null; ->p9 : typeof foo -> : ^^^^^^^^^^ +const p06 = (class foo { }) || null; +>p06 : typeof foo +> : ^^^^^^^^^^ >(class foo { }) || null : typeof foo > : ^^^^^^^^^^ >(class foo { }) : typeof foo @@ -161,6 +169,134 @@ const p9 = (class foo { }) || null; >foo : typeof foo > : ^^^^^^^^^^ +const p07 = null ?? null ?? null; +>p07 : any +> : ^^^ +>null ?? null ?? null : null +> : ^^^^ +>null ?? null : null +> : ^^^^ + +const p10 = opt ?? null ?? 1; +>p10 : number +> : ^^^^^^ +>opt ?? null ?? 1 : number +> : ^^^^^^ +>opt ?? null : number +> : ^^^^^^ +>opt : number +> : ^^^^^^ +>1 : 1 +> : ^ + +const p11 = opt ?? null ?? null; +>p11 : number +> : ^^^^^^ +>opt ?? null ?? null : number +> : ^^^^^^ +>opt ?? null : number +> : ^^^^^^ +>opt : number +> : ^^^^^^ + +const p12 = opt ?? (null ?? 1); +>p12 : number +> : ^^^^^^ +>opt ?? (null ?? 1) : number +> : ^^^^^^ +>opt : number +> : ^^^^^^ +>(null ?? 1) : 1 +> : ^ +>null ?? 1 : 1 +> : ^ +>1 : 1 +> : ^ + +const p13 = opt ?? (null ?? null); +>p13 : number +> : ^^^^^^ +>opt ?? (null ?? null) : number +> : ^^^^^^ +>opt : number +> : ^^^^^^ +>(null ?? null) : null +> : ^^^^ +>null ?? null : null +> : ^^^^ + +const p14 = opt ?? (null ?? null ?? null); +>p14 : number +> : ^^^^^^ +>opt ?? (null ?? null ?? null) : number +> : ^^^^^^ +>opt : number +> : ^^^^^^ +>(null ?? null ?? null) : null +> : ^^^^ +>null ?? null ?? null : null +> : ^^^^ +>null ?? null : null +> : ^^^^ + +const p20 = null ?? (opt ? null : undefined) ?? null; +>p20 : any +> : ^^^ +>null ?? (opt ? null : undefined) ?? null : null +> : ^^^^ +>null ?? (opt ? null : undefined) : null +> : ^^^^ +>(opt ? null : undefined) : null +> : ^^^^ +>opt ? null : undefined : null +> : ^^^^ +>opt : number +> : ^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + +const p21 = null ?? null ?? null ?? null; +>p21 : any +> : ^^^ +>null ?? null ?? null ?? null : null +> : ^^^^ +>null ?? null ?? null : null +> : ^^^^ +>null ?? null : null +> : ^^^^ + +const p22 = null ?? 1 ?? 1; +>p22 : 1 +> : ^ +>null ?? 1 ?? 1 : 1 +> : ^ +>null ?? 1 : 1 +> : ^ +>1 : 1 +> : ^ +>1 : 1 +> : ^ + +const p23 = null ?? (opt ? 1 : 2) ?? 1; +>p23 : 1 | 2 +> : ^^^^^ +>null ?? (opt ? 1 : 2) ?? 1 : 1 | 2 +> : ^^^^^ +>null ?? (opt ? 1 : 2) : 1 | 2 +> : ^^^^^ +>(opt ? 1 : 2) : 1 | 2 +> : ^^^^^ +>opt ? 1 : 2 : 1 | 2 +> : ^^^^^ +>opt : number +> : ^^^^^^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ +>1 : 1 +> : ^ + // Outer expression tests while ({} as any) { } >{} as any : any @@ -192,6 +328,10 @@ while ((({}))) { } >{} : {} > : ^^ +declare let cond: any; +>cond : any +> : ^^^ + // Should be OK console.log((cond || undefined) && 1 / cond); >console.log((cond || undefined) && 1 / cond) : void diff --git a/tests/cases/compiler/predicateSemantics.ts b/tests/cases/compiler/predicateSemantics.ts index 4069c1da6eec7..5ec2180fa9751 100644 --- a/tests/cases/compiler/predicateSemantics.ts +++ b/tests/cases/compiler/predicateSemantics.ts @@ -1,16 +1,16 @@ -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) { @@ -23,11 +23,24 @@ 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) { } @@ -35,5 +48,7 @@ while ({} satisfies unknown) { } while ((({}))) { } while ((({}))) { } +declare let cond: any; + // Should be OK console.log((cond || undefined) && 1 / cond); From df5f29748bbaa0ff68a545f8dcff88972965c19c Mon Sep 17 00:00:00 2001 From: Chiri Vulpes Date: Sat, 10 Aug 2024 13:13:03 +1200 Subject: [PATCH 2/4] Also error on nullish coalesce expressions where both operands are always nullish --- src/compiler/checker.ts | 74 +++++++++------- .../reference/predicateSemantics.errors.txt | 84 ++++++++++++++----- .../baselines/reference/predicateSemantics.js | 26 ++++-- .../reference/predicateSemantics.symbols | 45 +++++++--- .../reference/predicateSemantics.types | 78 +++++++++++++++-- tests/cases/compiler/predicateSemantics.ts | 6 +- 6 files changed, 234 insertions(+), 79 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 417761418ef09..c8fa256aba23d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -39667,50 +39667,53 @@ 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)); } - checkNullishCoalesceOperandLeft(node); - checkNullishCoalesceOperandRight(node); + 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); - if (nullishSemantics & PredicateSemantics.Literal && isLeftmostNullishCoalesceOperand(node)) { - return; - } + let nullishSemantics = getSyntacticNullishnessSemantics(leftTarget); + const isLiteral = nullishSemantics & PredicateSemantics.Literal; nullishSemantics = nullishSemantics & ~PredicateSemantics.Literal; + if (isLiteral && isLeftmostNullishCoalesceOperand(node)) { + return nullishSemantics; + } + if (nullishSemantics !== PredicateSemantics.Sometimes) { - if (node.parent.kind === SyntaxKind.BinaryExpression) { - error(leftTarget, Diagnostics.This_binary_expression_is_never_nullish_Are_you_missing_parentheses); + 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); } } + + return nullishSemantics; } 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); - } + if (isNotWithinNullishCoalesceExpression(node)) { + return nullishSemantics; } + + if (nullishSemantics === PredicateSemantics.Always) { + error(rightTarget, Diagnostics.This_expression_is_always_nullish); + } + + return nullishSemantics; } function isLeftmostNullishCoalesceOperand(node: BinaryExpression) { @@ -39718,10 +39721,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { node = node.parent; } - return !isBinaryExpression(node.parent) || node.parent.operatorToken.kind !== SyntaxKind.QuestionQuestionToken; + return isNotWithinNullishCoalesceExpression(node); } - function isRightmostNullishCoalesceOperand(node: BinaryExpression) { + function isNotWithinNullishCoalesceExpression(node: BinaryExpression) { return !isBinaryExpression(node.parent) || node.parent.operatorToken.kind !== SyntaxKind.QuestionQuestionToken; } @@ -39738,15 +39741,26 @@ 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: diff --git a/tests/baselines/reference/predicateSemantics.errors.txt b/tests/baselines/reference/predicateSemantics.errors.txt index 92e4132e8661e..a6af2c64ababd 100644 --- a/tests/baselines/reference/predicateSemantics.errors.txt +++ b/tests/baselines/reference/predicateSemantics.errors.txt @@ -2,24 +2,38 @@ 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,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,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(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(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(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 (19 errors) ==== +==== predicateSemantics.ts (33 errors) ==== declare let opt: number | undefined; // OK: One or other operand is possibly nullish @@ -57,6 +71,8 @@ predicateSemantics.ts(49,8): error TS2872: This kind of expression is always tru !!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. const p03 = null ?? 1; const p04 = null ?? null; + ~~~~~~~~~~~~ +!!! error TS2871: This expression is always nullish. const p05 = (class foo { }) && null; ~~~~~~~~~~~~~~~ !!! error TS2872: This kind of expression is always truthy. @@ -64,8 +80,20 @@ predicateSemantics.ts(49,8): error TS2872: This kind of expression is always tru ~~~~~~~~~~~~~~~ !!! 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; ~~~~ @@ -75,24 +103,42 @@ predicateSemantics.ts(49,8): error TS2872: This kind of expression is always tru !!! 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 p20 = null ?? (opt ? null : undefined) ?? null; - ~~~~~~~~~~~~~~~~~~~~~~ + 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 TS2874: This expression is never nullish. + ~~~~~~~~~ +!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. const p23 = null ?? (opt ? 1 : 2) ?? 1; - ~~~~~~~~~~~ -!!! error TS2874: This expression is never nullish. + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. // Outer expression tests while ({} as any) { } diff --git a/tests/baselines/reference/predicateSemantics.js b/tests/baselines/reference/predicateSemantics.js index d82b26d2ce4bd..666b35bb10cf5 100644 --- a/tests/baselines/reference/predicateSemantics.js +++ b/tests/baselines/reference/predicateSemantics.js @@ -33,14 +33,18 @@ const p04 = null ?? null; const p05 = (class foo { }) && null; const p06 = (class foo { }) || null; const p07 = null ?? null ?? null; +const p08 = null ?? opt ?? null; +const p09 = null ?? (opt ? null : undefined) ?? 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 p15 = opt ?? (opt ? null : undefined) ?? null; +const p16 = opt ?? 1 ?? 2; +const p17 = opt ?? (opt ? 1 : 2) ?? 3; -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; @@ -58,7 +62,7 @@ console.log((cond || undefined) && 1 / cond); //// [predicateSemantics.js] -var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q; +var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u; // OK: One or other operand is possibly nullish var test1 = (_a = (opt ? undefined : 32)) !== null && _a !== void 0 ? _a : "possibly reached"; // Not OK: Both operands nullish @@ -90,15 +94,19 @@ var p06 = (/** @class */ (function () { 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 p08 = (_h = null !== null && null !== void 0 ? null : opt) !== null && _h !== void 0 ? _h : null; +var p09 = (_j = null !== null && null !== void 0 ? null : (opt ? null : undefined)) !== null && _j !== void 0 ? _j : null; +var p10 = (_k = opt !== null && opt !== void 0 ? opt : null) !== null && _k !== void 0 ? _k : 1; +var p11 = (_l = opt !== null && opt !== void 0 ? opt : null) !== null && _l !== void 0 ? _l : 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; +var p14 = opt !== null && opt !== void 0 ? opt : ((_m = null !== null && null !== void 0 ? null : null) !== null && _m !== void 0 ? _m : null); +var p15 = (_o = opt !== null && opt !== void 0 ? opt : (opt ? null : undefined)) !== null && _o !== void 0 ? _o : null; +var p16 = (_p = opt !== null && opt !== void 0 ? opt : 1) !== null && _p !== void 0 ? _p : 2; +var p17 = (_q = opt !== null && opt !== void 0 ? opt : (opt ? 1 : 2)) !== null && _q !== void 0 ? _q : 3; +var p21 = (_s = (_r = null !== null && null !== void 0 ? null : null) !== null && _r !== void 0 ? _r : null) !== null && _s !== void 0 ? _s : null; +var p22 = (_t = null !== null && null !== void 0 ? null : 1) !== null && _t !== void 0 ? _t : 1; +var p23 = (_u = null !== null && null !== void 0 ? null : (opt ? 1 : 2)) !== null && _u !== void 0 ? _u : 1; // Outer expression tests while ({}) { } while ({}) { } diff --git a/tests/baselines/reference/predicateSemantics.symbols b/tests/baselines/reference/predicateSemantics.symbols index c8dc55fda4c77..8bbec51a58243 100644 --- a/tests/baselines/reference/predicateSemantics.symbols +++ b/tests/baselines/reference/predicateSemantics.symbols @@ -61,39 +61,58 @@ const p06 = (class foo { }) || null; const p07 = null ?? null ?? null; >p07 : Symbol(p07, Decl(predicateSemantics.ts, 31, 5)) +const p08 = null ?? opt ?? null; +>p08 : Symbol(p08, Decl(predicateSemantics.ts, 32, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) + +const p09 = null ?? (opt ? null : undefined) ?? null; +>p09 : Symbol(p09, Decl(predicateSemantics.ts, 33, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) +>undefined : Symbol(undefined) + const p10 = opt ?? null ?? 1; ->p10 : Symbol(p10, Decl(predicateSemantics.ts, 33, 5)) +>p10 : Symbol(p10, Decl(predicateSemantics.ts, 35, 5)) >opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) const p11 = opt ?? null ?? null; ->p11 : Symbol(p11, Decl(predicateSemantics.ts, 34, 5)) +>p11 : Symbol(p11, Decl(predicateSemantics.ts, 36, 5)) >opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) const p12 = opt ?? (null ?? 1); ->p12 : Symbol(p12, Decl(predicateSemantics.ts, 35, 5)) +>p12 : Symbol(p12, Decl(predicateSemantics.ts, 37, 5)) >opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) const p13 = opt ?? (null ?? null); ->p13 : Symbol(p13, Decl(predicateSemantics.ts, 36, 5)) +>p13 : Symbol(p13, Decl(predicateSemantics.ts, 38, 5)) >opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) const p14 = opt ?? (null ?? null ?? null); ->p14 : Symbol(p14, Decl(predicateSemantics.ts, 37, 5)) +>p14 : Symbol(p14, Decl(predicateSemantics.ts, 39, 5)) >opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) -const p20 = null ?? (opt ? null : undefined) ?? null; ->p20 : Symbol(p20, Decl(predicateSemantics.ts, 39, 5)) +const p15 = opt ?? (opt ? null : undefined) ?? null; +>p15 : Symbol(p15, Decl(predicateSemantics.ts, 40, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) >opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) >undefined : Symbol(undefined) +const p16 = opt ?? 1 ?? 2; +>p16 : Symbol(p16, Decl(predicateSemantics.ts, 41, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) + +const p17 = opt ?? (opt ? 1 : 2) ?? 3; +>p17 : Symbol(p17, Decl(predicateSemantics.ts, 42, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) + const p21 = null ?? null ?? null ?? null; ->p21 : Symbol(p21, Decl(predicateSemantics.ts, 40, 5)) +>p21 : Symbol(p21, Decl(predicateSemantics.ts, 44, 5)) const p22 = null ?? 1 ?? 1; ->p22 : Symbol(p22, Decl(predicateSemantics.ts, 41, 5)) +>p22 : Symbol(p22, Decl(predicateSemantics.ts, 45, 5)) const p23 = null ?? (opt ? 1 : 2) ?? 1; ->p23 : Symbol(p23, Decl(predicateSemantics.ts, 42, 5)) +>p23 : Symbol(p23, Decl(predicateSemantics.ts, 46, 5)) >opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) // Outer expression tests @@ -103,14 +122,14 @@ while ((({}))) { } while ((({}))) { } declare let cond: any; ->cond : Symbol(cond, Decl(predicateSemantics.ts, 50, 11)) +>cond : Symbol(cond, Decl(predicateSemantics.ts, 54, 11)) // Should be OK console.log((cond || undefined) && 1 / cond); >console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) >console : Symbol(console, Decl(lib.dom.d.ts, --, --)) >log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) ->cond : Symbol(cond, Decl(predicateSemantics.ts, 50, 11)) +>cond : Symbol(cond, Decl(predicateSemantics.ts, 54, 11)) >undefined : Symbol(undefined) ->cond : Symbol(cond, Decl(predicateSemantics.ts, 50, 11)) +>cond : Symbol(cond, Decl(predicateSemantics.ts, 54, 11)) diff --git a/tests/baselines/reference/predicateSemantics.types b/tests/baselines/reference/predicateSemantics.types index d61059969eb24..165b237ff38db 100644 --- a/tests/baselines/reference/predicateSemantics.types +++ b/tests/baselines/reference/predicateSemantics.types @@ -177,6 +177,32 @@ const p07 = null ?? null ?? null; >null ?? null : null > : ^^^^ +const p08 = null ?? opt ?? null; +>p08 : number +> : ^^^^^^ +>null ?? opt ?? null : number +> : ^^^^^^ +>null ?? opt : number +> : ^^^^^^ +>opt : number +> : ^^^^^^ + +const p09 = null ?? (opt ? null : undefined) ?? null; +>p09 : any +> : ^^^ +>null ?? (opt ? null : undefined) ?? null : null +> : ^^^^ +>null ?? (opt ? null : undefined) : null +> : ^^^^ +>(opt ? null : undefined) : null +> : ^^^^ +>opt ? null : undefined : null +> : ^^^^ +>opt : number +> : ^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + const p10 = opt ?? null ?? 1; >p10 : number > : ^^^^^^ @@ -239,13 +265,15 @@ const p14 = opt ?? (null ?? null ?? null); >null ?? null : null > : ^^^^ -const p20 = null ?? (opt ? null : undefined) ?? null; ->p20 : any -> : ^^^ ->null ?? (opt ? null : undefined) ?? null : null -> : ^^^^ ->null ?? (opt ? null : undefined) : null -> : ^^^^ +const p15 = opt ?? (opt ? null : undefined) ?? null; +>p15 : number +> : ^^^^^^ +>opt ?? (opt ? null : undefined) ?? null : number +> : ^^^^^^ +>opt ?? (opt ? null : undefined) : number +> : ^^^^^^ +>opt : number +> : ^^^^^^ >(opt ? null : undefined) : null > : ^^^^ >opt ? null : undefined : null @@ -255,6 +283,42 @@ const p20 = null ?? (opt ? null : undefined) ?? null; >undefined : undefined > : ^^^^^^^^^ +const p16 = opt ?? 1 ?? 2; +>p16 : number +> : ^^^^^^ +>opt ?? 1 ?? 2 : number +> : ^^^^^^ +>opt ?? 1 : number +> : ^^^^^^ +>opt : number +> : ^^^^^^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ + +const p17 = opt ?? (opt ? 1 : 2) ?? 3; +>p17 : number +> : ^^^^^^ +>opt ?? (opt ? 1 : 2) ?? 3 : number +> : ^^^^^^ +>opt ?? (opt ? 1 : 2) : number +> : ^^^^^^ +>opt : number +> : ^^^^^^ +>(opt ? 1 : 2) : 1 | 2 +> : ^^^^^ +>opt ? 1 : 2 : 1 | 2 +> : ^^^^^ +>opt : number +> : ^^^^^^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ +>3 : 3 +> : ^ + const p21 = null ?? null ?? null ?? null; >p21 : any > : ^^^ diff --git a/tests/cases/compiler/predicateSemantics.ts b/tests/cases/compiler/predicateSemantics.ts index 5ec2180fa9751..19a56c932197c 100644 --- a/tests/cases/compiler/predicateSemantics.ts +++ b/tests/cases/compiler/predicateSemantics.ts @@ -30,14 +30,18 @@ const p04 = null ?? null; const p05 = (class foo { }) && null; const p06 = (class foo { }) || null; const p07 = null ?? null ?? null; +const p08 = null ?? opt ?? null; +const p09 = null ?? (opt ? null : undefined) ?? 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 p15 = opt ?? (opt ? null : undefined) ?? null; +const p16 = opt ?? 1 ?? 2; +const p17 = opt ?? (opt ? 1 : 2) ?? 3; -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; From 2d84410d3bf2f39a2ca56b191f09da7d90557b0d Mon Sep 17 00:00:00 2001 From: Chiri Vulpes Date: Tue, 13 Aug 2024 22:58:53 +1200 Subject: [PATCH 3/4] Pare down the PR to only fix the error message and check right operands --- src/compiler/checker.ts | 60 ++++------------- src/compiler/types.ts | 1 - .../reference/predicateSemantics.errors.txt | 66 +++++++++---------- 3 files changed, 46 insertions(+), 81 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c8fa256aba23d..304af96789096 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -39667,29 +39667,15 @@ 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 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); - } - } + checkNullishCoalesceOperandLeft(node); + checkNullishCoalesceOperandRight(node); } } 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; - } - + const nullishSemantics = getSyntacticNullishnessSemantics(leftTarget); if (nullishSemantics !== PredicateSemantics.Sometimes) { if (nullishSemantics === PredicateSemantics.Always) { error(leftTarget, Diagnostics.This_expression_is_always_nullish); @@ -39698,30 +39684,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { 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; + const nullishSemantics = getSyntacticNullishnessSemantics(rightTarget); if (isNotWithinNullishCoalesceExpression(node)) { - return nullishSemantics; + return; } 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; + else if (nullishSemantics === PredicateSemantics.Never) { + error(rightTarget, Diagnostics.This_expression_is_never_nullish); } - - return isNotWithinNullishCoalesceExpression(node); } function isNotWithinNullishCoalesceExpression(node: BinaryExpression) { @@ -39741,35 +39718,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.BinaryExpression: // List of operators that can produce null/undefined: // = ??= ?? || ||= && &&= - 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) { + switch ((node as BinaryExpression).operatorToken.kind) { + case SyntaxKind.EqualsToken: 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 leftSemantics === PredicateSemantics.Never && rightSemantics === PredicateSemantics.Never ? PredicateSemantics.Never : - leftSemantics === PredicateSemantics.Sometimes && rightSemantics === PredicateSemantics.Sometimes ? PredicateSemantics.Sometimes : - PredicateSemantics.Always; + return PredicateSemantics.Sometimes; } return PredicateSemantics.Never; case SyntaxKind.ConditionalExpression: - return (getSyntacticNullishnessSemantics((node as ConditionalExpression).whenTrue) | getSyntacticNullishnessSemantics((node as ConditionalExpression).whenFalse)) & ~PredicateSemantics.Literal; + return getSyntacticNullishnessSemantics((node as ConditionalExpression).whenTrue) | getSyntacticNullishnessSemantics((node as ConditionalExpression).whenFalse); case SyntaxKind.NullKeyword: - return PredicateSemantics.Always | PredicateSemantics.Literal; + return PredicateSemantics.Always; case SyntaxKind.Identifier: if (getResolvedSymbol(node as Identifier) === undefinedSymbol) { - return PredicateSemantics.Always | PredicateSemantics.Literal; + return PredicateSemantics.Always; } return PredicateSemantics.Sometimes; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 842624bdb2ab5..3f5306cec0383 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -928,7 +928,6 @@ export const enum PredicateSemantics { None = 0, Always = 1 << 0, Never = 1 << 1, - Literal = 1 << 2, Sometimes = Always | Never, } diff --git a/tests/baselines/reference/predicateSemantics.errors.txt b/tests/baselines/reference/predicateSemantics.errors.txt index a6af2c64ababd..2990ac35033cc 100644 --- a/tests/baselines/reference/predicateSemantics.errors.txt +++ b/tests/baselines/reference/predicateSemantics.errors.txt @@ -2,31 +2,31 @@ 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,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,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(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,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(42,20): error TS2874: This expression is never nullish. +predicateSemantics.ts(43,21): error TS2874: 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 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(46,13): error TS2871: This expression is always nullish. +predicateSemantics.ts(46,21): error TS2874: This expression is never nullish. +predicateSemantics.ts(47,13): error TS2871: This expression is always nullish. +predicateSemantics.ts(47,22): error TS2874: 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. @@ -70,8 +70,10 @@ predicateSemantics.ts(53,8): error TS2872: This kind of expression is always tru ~~~~~ !!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. const p03 = null ?? 1; + ~~~~ +!!! error TS2871: This expression is always nullish. const p04 = null ?? null; - ~~~~~~~~~~~~ + ~~~~ !!! error TS2871: This expression is always nullish. const p05 = (class foo { }) && null; ~~~~~~~~~~~~~~~ @@ -80,17 +82,15 @@ predicateSemantics.ts(53,8): error TS2872: This kind of expression is always tru ~~~~~~~~~~~~~~~ !!! 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. - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + const p09 = null ?? (opt ? null : undefined) ?? null; + ~~~~ !!! error TS2871: This expression is always nullish. ~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2871: This expression is always nullish. @@ -102,13 +102,13 @@ predicateSemantics.ts(53,8): error TS2872: This kind of expression is always tru ~~~~ !!! 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. ~~~~ !!! error TS2871: This expression is always nullish. @@ -116,29 +116,29 @@ predicateSemantics.ts(53,8): error TS2872: This kind of expression is always tru ~~~~~~~~~~~~~~~~~~~~~~ !!! 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. + ~ +!!! error TS2874: This expression is never nullish. const p17 = opt ?? (opt ? 1 : 2) ?? 3; - ~~~~~~~~~~~~~~~~~~~~ -!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. + ~~~~~~~~~~~ +!!! error TS2874: 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. ~~~~ !!! 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. + ~~~~ +!!! error TS2871: This expression is always nullish. + ~ +!!! error TS2874: This expression is never nullish. const p23 = null ?? (opt ? 1 : 2) ?? 1; - ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. + ~~~~ +!!! error TS2871: This expression is always nullish. + ~~~~~~~~~~~ +!!! error TS2874: This expression is never nullish. // Outer expression tests while ({} as any) { } From 50da01a597e16c734f7c09bcb0881b35b3e028ee Mon Sep 17 00:00:00 2001 From: Chiri Vulpes Date: Fri, 21 Feb 2025 21:56:17 +1300 Subject: [PATCH 4/4] Include predicateSemantics.symbols baselines update --- .../reference/predicateSemantics.symbols | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/baselines/reference/predicateSemantics.symbols b/tests/baselines/reference/predicateSemantics.symbols index dfb1e42ff35bb..d2acb5c4cdb13 100644 --- a/tests/baselines/reference/predicateSemantics.symbols +++ b/tests/baselines/reference/predicateSemantics.symbols @@ -134,68 +134,68 @@ console.log((cond || undefined) && 1 / cond); >cond : Symbol(cond, Decl(predicateSemantics.ts, 54, 11)) function foo(this: Object | undefined) { ->foo : Symbol(foo, Decl(predicateSemantics.ts, 38, 45)) ->this : Symbol(this, Decl(predicateSemantics.ts, 40, 13)) +>foo : Symbol(foo, Decl(predicateSemantics.ts, 57, 45)) +>this : Symbol(this, Decl(predicateSemantics.ts, 59, 13)) >Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) // Should be OK return this ?? 0; ->this : Symbol(this, Decl(predicateSemantics.ts, 40, 13)) +>this : Symbol(this, Decl(predicateSemantics.ts, 59, 13)) } // https://github.com/microsoft/TypeScript/issues/60401 { const maybe = null as true | null; ->maybe : Symbol(maybe, Decl(predicateSemantics.ts, 47, 7)) +>maybe : Symbol(maybe, Decl(predicateSemantics.ts, 66, 7)) let i = 0; ->i : Symbol(i, Decl(predicateSemantics.ts, 48, 5)) +>i : Symbol(i, Decl(predicateSemantics.ts, 67, 5)) const d = (i++, maybe) ?? true; // ok ->d : Symbol(d, Decl(predicateSemantics.ts, 49, 7)) ->i : Symbol(i, Decl(predicateSemantics.ts, 48, 5)) ->maybe : Symbol(maybe, Decl(predicateSemantics.ts, 47, 7)) +>d : Symbol(d, Decl(predicateSemantics.ts, 68, 7)) +>i : Symbol(i, Decl(predicateSemantics.ts, 67, 5)) +>maybe : Symbol(maybe, Decl(predicateSemantics.ts, 66, 7)) const e = (i++, i++) ?? true; // error ->e : Symbol(e, Decl(predicateSemantics.ts, 50, 7)) ->i : Symbol(i, Decl(predicateSemantics.ts, 48, 5)) ->i : Symbol(i, Decl(predicateSemantics.ts, 48, 5)) +>e : Symbol(e, Decl(predicateSemantics.ts, 69, 7)) +>i : Symbol(i, Decl(predicateSemantics.ts, 67, 5)) +>i : Symbol(i, Decl(predicateSemantics.ts, 67, 5)) const f = (maybe, i++) ?? true; // error ->f : Symbol(f, Decl(predicateSemantics.ts, 51, 7)) ->maybe : Symbol(maybe, Decl(predicateSemantics.ts, 47, 7)) ->i : Symbol(i, Decl(predicateSemantics.ts, 48, 5)) +>f : Symbol(f, Decl(predicateSemantics.ts, 70, 7)) +>maybe : Symbol(maybe, Decl(predicateSemantics.ts, 66, 7)) +>i : Symbol(i, Decl(predicateSemantics.ts, 67, 5)) } // https://github.com/microsoft/TypeScript/issues/60439 class X { ->X : Symbol(X, Decl(predicateSemantics.ts, 52, 1)) +>X : Symbol(X, Decl(predicateSemantics.ts, 71, 1)) constructor() { const p = new.target ?? 32; ->p : Symbol(p, Decl(predicateSemantics.ts, 57, 9)) ->new.target : Symbol(X, Decl(predicateSemantics.ts, 52, 1)) ->target : Symbol(X, Decl(predicateSemantics.ts, 52, 1)) +>p : Symbol(p, Decl(predicateSemantics.ts, 76, 9)) +>new.target : Symbol(X, Decl(predicateSemantics.ts, 71, 1)) +>target : Symbol(X, Decl(predicateSemantics.ts, 71, 1)) } } // https://github.com/microsoft/TypeScript/issues/60614 declare function tag( ->tag : Symbol(tag, Decl(predicateSemantics.ts, 59, 1)) ->T : Symbol(T, Decl(predicateSemantics.ts, 62, 21)) +>tag : Symbol(tag, Decl(predicateSemantics.ts, 78, 1)) +>T : Symbol(T, Decl(predicateSemantics.ts, 81, 21)) strings: TemplateStringsArray, ->strings : Symbol(strings, Decl(predicateSemantics.ts, 62, 24)) +>strings : Symbol(strings, Decl(predicateSemantics.ts, 81, 24)) >TemplateStringsArray : Symbol(TemplateStringsArray, Decl(lib.es5.d.ts, --, --)) ...values: number[] ->values : Symbol(values, Decl(predicateSemantics.ts, 63, 32)) +>values : Symbol(values, Decl(predicateSemantics.ts, 82, 32)) ): T | null; ->T : Symbol(T, Decl(predicateSemantics.ts, 62, 21)) +>T : Symbol(T, Decl(predicateSemantics.ts, 81, 21)) tag`foo${1}` ?? 32; // ok ->tag : Symbol(tag, Decl(predicateSemantics.ts, 59, 1)) +>tag : Symbol(tag, Decl(predicateSemantics.ts, 78, 1)) `foo${1}` ?? 32; // error `foo` ?? 32; // error