diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7c5958106334a..7f18bbc1a1dec 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -39847,22 +39847,42 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { grammarErrorOnNode(right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(right.operatorToken.kind), tokenToString(operatorToken.kind)); } - const leftTarget = skipOuterExpressions(left, OuterExpressionKinds.All); - const nullishSemantics = getSyntacticNullishnessSemantics(leftTarget); - if (nullishSemantics !== PredicateSemantics.Sometimes) { - if (node.parent.kind === SyntaxKind.BinaryExpression) { - error(leftTarget, Diagnostics.This_binary_expression_is_never_nullish_Are_you_missing_parentheses); - } - else { - if (nullishSemantics === PredicateSemantics.Always) { - error(leftTarget, Diagnostics.This_expression_is_always_nullish); - } - else { - error(leftTarget, Diagnostics.Right_operand_of_is_unreachable_because_the_left_operand_is_never_nullish); - } - } + checkNullishCoalesceOperandLeft(node); + checkNullishCoalesceOperandRight(node); + } + } + + function checkNullishCoalesceOperandLeft(node: BinaryExpression) { + const leftTarget = skipOuterExpressions(node.left, OuterExpressionKinds.All); + + const nullishSemantics = getSyntacticNullishnessSemantics(leftTarget); + if (nullishSemantics !== PredicateSemantics.Sometimes) { + if (nullishSemantics === PredicateSemantics.Always) { + error(leftTarget, Diagnostics.This_expression_is_always_nullish); } + else { + error(leftTarget, Diagnostics.Right_operand_of_is_unreachable_because_the_left_operand_is_never_nullish); + } + } + } + + function checkNullishCoalesceOperandRight(node: BinaryExpression) { + const rightTarget = skipOuterExpressions(node.right, OuterExpressionKinds.All); + const nullishSemantics = getSyntacticNullishnessSemantics(rightTarget); + if (isNotWithinNullishCoalesceExpression(node)) { + return; } + + if (nullishSemantics === PredicateSemantics.Always) { + error(rightTarget, Diagnostics.This_expression_is_always_nullish); + } + else if (nullishSemantics === PredicateSemantics.Never) { + error(rightTarget, Diagnostics.This_expression_is_never_nullish); + } + } + + function isNotWithinNullishCoalesceExpression(node: BinaryExpression) { + return !isBinaryExpression(node.parent) || node.parent.operatorToken.kind !== SyntaxKind.QuestionQuestionToken; } function getSyntacticNullishnessSemantics(node: Node): PredicateSemantics { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 0d7e0bb39a8d4..be2fe3957b20a 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3987,6 +3987,10 @@ "category": "Error", "code": 2880 }, + "This expression is never nullish.": { + "category": "Error", + "code": 2881 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/tests/baselines/reference/predicateSemantics.errors.txt b/tests/baselines/reference/predicateSemantics.errors.txt index c80dd47d19357..cf503611a4b88 100644 --- a/tests/baselines/reference/predicateSemantics.errors.txt +++ b/tests/baselines/reference/predicateSemantics.errors.txt @@ -1,39 +1,61 @@ predicateSemantics.ts(7,16): error TS2871: This expression is always nullish. predicateSemantics.ts(10,16): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. -predicateSemantics.ts(26,12): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. -predicateSemantics.ts(27,12): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. -predicateSemantics.ts(28,12): error TS2871: This expression is always nullish. -predicateSemantics.ts(29,12): error TS2872: This kind of expression is always truthy. -predicateSemantics.ts(30,12): error TS2872: This kind of expression is always truthy. -predicateSemantics.ts(33,8): error TS2872: This kind of expression is always truthy. -predicateSemantics.ts(34,11): error TS2872: This kind of expression is always truthy. -predicateSemantics.ts(35,8): error TS2872: This kind of expression is always truthy. -predicateSemantics.ts(36,8): error TS2872: This kind of expression is always truthy. -predicateSemantics.ts(51,14): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. -predicateSemantics.ts(52,14): error TS2695: Left side of comma operator is unused and has no side effects. -predicateSemantics.ts(52,14): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. -predicateSemantics.ts(70,1): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. -predicateSemantics.ts(71,1): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +predicateSemantics.ts(26,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +predicateSemantics.ts(27,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +predicateSemantics.ts(28,13): error TS2871: This expression is always nullish. +predicateSemantics.ts(29,13): error TS2871: This expression is always nullish. +predicateSemantics.ts(30,13): error TS2872: This kind of expression is always truthy. +predicateSemantics.ts(31,13): error TS2872: This kind of expression is always truthy. +predicateSemantics.ts(32,13): error TS2871: This expression is always nullish. +predicateSemantics.ts(32,21): error TS2871: This expression is always nullish. +predicateSemantics.ts(33,13): error TS2871: This expression is always nullish. +predicateSemantics.ts(34,13): error TS2871: This expression is always nullish. +predicateSemantics.ts(34,22): error TS2871: This expression is always nullish. +predicateSemantics.ts(36,20): error TS2871: This expression is always nullish. +predicateSemantics.ts(37,20): error TS2871: This expression is always nullish. +predicateSemantics.ts(38,21): error TS2871: This expression is always nullish. +predicateSemantics.ts(39,21): error TS2871: This expression is always nullish. +predicateSemantics.ts(40,21): error TS2871: This expression is always nullish. +predicateSemantics.ts(40,29): error TS2871: This expression is always nullish. +predicateSemantics.ts(41,21): error TS2871: This expression is always nullish. +predicateSemantics.ts(42,20): error TS2881: This expression is never nullish. +predicateSemantics.ts(43,21): error TS2881: This expression is never nullish. +predicateSemantics.ts(45,13): error TS2871: This expression is always nullish. +predicateSemantics.ts(45,21): error TS2871: This expression is always nullish. +predicateSemantics.ts(45,29): error TS2871: This expression is always nullish. +predicateSemantics.ts(46,13): error TS2871: This expression is always nullish. +predicateSemantics.ts(46,21): error TS2881: This expression is never nullish. +predicateSemantics.ts(47,13): error TS2871: This expression is always nullish. +predicateSemantics.ts(47,22): error TS2881: This expression is never nullish. +predicateSemantics.ts(50,8): error TS2872: This kind of expression is always truthy. +predicateSemantics.ts(51,11): error TS2872: This kind of expression is always truthy. +predicateSemantics.ts(52,8): error TS2872: This kind of expression is always truthy. +predicateSemantics.ts(53,8): error TS2872: This kind of expression is always truthy. +predicateSemantics.ts(70,14): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +predicateSemantics.ts(71,14): error TS2695: Left side of comma operator is unused and has no side effects. +predicateSemantics.ts(71,14): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +predicateSemantics.ts(89,1): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +predicateSemantics.ts(90,1): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. -==== predicateSemantics.ts (16 errors) ==== - declare let cond: any; +==== predicateSemantics.ts (38 errors) ==== + declare let opt: number | undefined; // OK: One or other operand is possibly nullish - const test1 = (cond ? undefined : 32) ?? "possibly reached"; + const test1 = (opt ? undefined : 32) ?? "possibly reached"; // Not OK: Both operands nullish - const test2 = (cond ? undefined : null) ?? "always reached"; - ~~~~~~~~~~~~~~~~~~~~~~~ + const test2 = (opt ? undefined : null) ?? "always reached"; + ~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2871: This expression is always nullish. // Not OK: Both operands non-nullish - const test3 = (cond ? 132 : 17) ?? "unreachable"; - ~~~~~~~~~~~~~~~ + const test3 = (opt ? 132 : 17) ?? "unreachable"; + ~~~~~~~~~~~~~~ !!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. // Parens - const test4 = (cond ? (undefined) : (17)) ?? 42; + const test4 = (opt ? (undefined) : (17)) ?? 42; // Should be OK (special case) if (!!true) { @@ -46,21 +68,82 @@ predicateSemantics.ts(71,1): error TS2869: Right operand of ?? is unreachable be while (true) { } while (false) { } - const p5 = {} ?? null; - ~~ + const p01 = {} ?? null; + ~~ !!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. - const p6 = 0 > 1 ?? null; - ~~~~~ + const p02 = 0 > 1 ?? null; + ~~~~~ !!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. - const p7 = null ?? null; - ~~~~ + const p03 = null ?? 1; + ~~~~ +!!! error TS2871: This expression is always nullish. + const p04 = null ?? null; + ~~~~ !!! error TS2871: This expression is always nullish. - const p8 = (class foo { }) && null; - ~~~~~~~~~~~~~~~ + const p05 = (class foo { }) && null; + ~~~~~~~~~~~~~~~ !!! error TS2872: This kind of expression is always truthy. - const p9 = (class foo { }) || null; - ~~~~~~~~~~~~~~~ + const p06 = (class foo { }) || null; + ~~~~~~~~~~~~~~~ !!! error TS2872: This kind of expression is always truthy. + const p07 = null ?? null ?? null; + ~~~~ +!!! error TS2871: This expression is always nullish. + ~~~~ +!!! error TS2871: This expression is always nullish. + const p08 = null ?? opt ?? null; + ~~~~ +!!! error TS2871: This expression is always nullish. + const p09 = null ?? (opt ? null : undefined) ?? null; + ~~~~ +!!! error TS2871: This expression is always nullish. + ~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2871: This expression is always nullish. + + const p10 = opt ?? null ?? 1; + ~~~~ +!!! error TS2871: This expression is always nullish. + const p11 = opt ?? null ?? null; + ~~~~ +!!! error TS2871: This expression is always nullish. + const p12 = opt ?? (null ?? 1); + ~~~~ +!!! error TS2871: This expression is always nullish. + const p13 = opt ?? (null ?? null); + ~~~~ +!!! error TS2871: This expression is always nullish. + const p14 = opt ?? (null ?? null ?? null); + ~~~~ +!!! error TS2871: This expression is always nullish. + ~~~~ +!!! error TS2871: This expression is always nullish. + const p15 = opt ?? (opt ? null : undefined) ?? null; + ~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2871: This expression is always nullish. + const p16 = opt ?? 1 ?? 2; + ~ +!!! error TS2881: This expression is never nullish. + const p17 = opt ?? (opt ? 1 : 2) ?? 3; + ~~~~~~~~~~~ +!!! error TS2881: This expression is never nullish. + + const p21 = null ?? null ?? null ?? null; + ~~~~ +!!! error TS2871: This expression is always nullish. + ~~~~ +!!! error TS2871: This expression is always nullish. + ~~~~ +!!! error TS2871: This expression is always nullish. + const p22 = null ?? 1 ?? 1; + ~~~~ +!!! error TS2871: This expression is always nullish. + ~ +!!! error TS2881: This expression is never nullish. + const p23 = null ?? (opt ? 1 : 2) ?? 1; + ~~~~ +!!! error TS2871: This expression is always nullish. + ~~~~~~~~~~~ +!!! error TS2881: This expression is never nullish. // Outer expression tests while ({} as any) { } @@ -76,6 +159,8 @@ predicateSemantics.ts(71,1): error TS2869: Right operand of ?? is unreachable be ~~~~~~ !!! error TS2872: This kind of expression is always truthy. + declare let cond: any; + // Should be OK console.log((cond || undefined) && 1 / cond); diff --git a/tests/baselines/reference/predicateSemantics.js b/tests/baselines/reference/predicateSemantics.js index 30ada0d514d02..7bd7ca5aa3409 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,28 @@ 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 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 p21 = null ?? null ?? null ?? null; +const p22 = null ?? 1 ?? 1; +const p23 = null ?? (opt ? 1 : 2) ?? 1; // Outer expression tests while ({} as any) { } @@ -38,6 +55,8 @@ while ({} satisfies unknown) { } while ((({}))) { } while ((({}))) { } +declare let cond: any; + // Should be OK console.log((cond || undefined) && 1 / cond); @@ -79,15 +98,15 @@ var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cook if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; }; -var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; +var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z; // 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) { } @@ -96,19 +115,34 @@ 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 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 : ((_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 ({}) { } @@ -124,9 +158,9 @@ function foo() { { var maybe = null; var i = 0; - var d = (_g = (i++, maybe)) !== null && _g !== void 0 ? _g : true; // ok - var e = (_h = (i++, i++)) !== null && _h !== void 0 ? _h : true; // error - var f = (_j = (maybe, i++)) !== null && _j !== void 0 ? _j : true; // error + var d = (_v = (i++, maybe)) !== null && _v !== void 0 ? _v : true; // ok + var e = (_w = (i++, i++)) !== null && _w !== void 0 ? _w : true; // error + var f = (_x = (maybe, i++)) !== null && _x !== void 0 ? _x : true; // error } // https://github.com/microsoft/TypeScript/issues/60439 var X = /** @class */ (function () { @@ -137,6 +171,6 @@ var X = /** @class */ (function () { } return X; }()); -(_k = tag(__makeTemplateObject(["foo", ""], ["foo", ""]), 1)) !== null && _k !== void 0 ? _k : 32; // ok -(_l = "foo".concat(1)) !== null && _l !== void 0 ? _l : 32; // error +(_y = tag(__makeTemplateObject(["foo", ""], ["foo", ""]), 1)) !== null && _y !== void 0 ? _y : 32; // ok +(_z = "foo".concat(1)) !== null && _z !== void 0 ? _z : 32; // error "foo" !== null && "foo" !== void 0 ? "foo" : 32; // error diff --git a/tests/baselines/reference/predicateSemantics.symbols b/tests/baselines/reference/predicateSemantics.symbols index f8ddf0fe5db87..d2acb5c4cdb13 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,82 @@ 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 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, 35, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) + +const p11 = opt ?? null ?? null; +>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, 37, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) + +const p13 = opt ?? (null ?? null); +>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, 39, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) -const p6 = 0 > 1 ?? null; ->p6 : Symbol(p6, Decl(predicateSemantics.ts, 26, 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 p7 = null ?? null; ->p7 : Symbol(p7, Decl(predicateSemantics.ts, 27, 5)) +const p21 = null ?? null ?? null ?? null; +>p21 : Symbol(p21, Decl(predicateSemantics.ts, 44, 5)) -const p8 = (class foo { }) && null; ->p8 : Symbol(p8, Decl(predicateSemantics.ts, 28, 5)) ->foo : Symbol(foo, Decl(predicateSemantics.ts, 28, 12)) +const p22 = null ?? 1 ?? 1; +>p22 : Symbol(p22, Decl(predicateSemantics.ts, 45, 5)) -const p9 = (class foo { }) || null; ->p9 : Symbol(p9, Decl(predicateSemantics.ts, 29, 5)) ->foo : Symbol(foo, Decl(predicateSemantics.ts, 29, 12)) +const p23 = null ?? (opt ? 1 : 2) ?? 1; +>p23 : Symbol(p23, Decl(predicateSemantics.ts, 46, 5)) +>opt : Symbol(opt, Decl(predicateSemantics.ts, 0, 11)) // Outer expression tests while ({} as any) { } @@ -61,78 +121,81 @@ while ({} satisfies unknown) { } while ((({}))) { } while ((({}))) { } +declare let cond: any; +>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, 0, 11)) +>cond : Symbol(cond, Decl(predicateSemantics.ts, 54, 11)) >undefined : Symbol(undefined) ->cond : Symbol(cond, Decl(predicateSemantics.ts, 0, 11)) +>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 diff --git a/tests/baselines/reference/predicateSemantics.types b/tests/baselines/reference/predicateSemantics.types index b054c66ad43e7..e9f5112eb0620 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,198 @@ const p9 = (class foo { }) || null; >foo : typeof foo > : ^^^^^^^^^^ +const p07 = null ?? null ?? null; +>p07 : any +> : ^^^ +>null ?? 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 +> : ^^^^^^ +>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 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 +> : ^^^^ +>opt : number +> : ^^^^^^ +>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 +> : ^^^ +>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 +392,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 a22b34341e7d5..2d36fb200e4fd 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,28 @@ 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 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 p21 = null ?? null ?? null ?? null; +const p22 = null ?? 1 ?? 1; +const p23 = null ?? (opt ? 1 : 2) ?? 1; // Outer expression tests while ({} as any) { } @@ -35,6 +52,8 @@ while ({} satisfies unknown) { } while ((({}))) { } while ((({}))) { } +declare let cond: any; + // Should be OK console.log((cond || undefined) && 1 / cond);