diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5187363302496..2523d3baaba55 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -40548,12 +40548,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } 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) { @@ -40565,25 +40563,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - 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 { node = skipOuterExpressions(node); switch (node.kind) { @@ -40601,15 +40580,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // List of operators that can produce null/undefined: // = ??= ?? || ||= && &&= switch ((node as BinaryExpression).operatorToken.kind) { - case SyntaxKind.EqualsToken: - case SyntaxKind.QuestionQuestionToken: - case SyntaxKind.QuestionQuestionEqualsToken: case SyntaxKind.BarBarToken: case SyntaxKind.BarBarEqualsToken: case SyntaxKind.AmpersandAmpersandToken: case SyntaxKind.AmpersandAmpersandEqualsToken: return PredicateSemantics.Sometimes; + // For these operator kinds, the right operand is effectively controlling case SyntaxKind.CommaToken: + case SyntaxKind.EqualsToken: + case SyntaxKind.QuestionQuestionToken: + case SyntaxKind.QuestionQuestionEqualsToken: return getSyntacticNullishnessSemantics((node as BinaryExpression).right); } return PredicateSemantics.Never; diff --git a/tests/baselines/reference/neverNullishThroughParentheses.errors.txt b/tests/baselines/reference/neverNullishThroughParentheses.errors.txt new file mode 100644 index 0000000000000..cbdc7e457557d --- /dev/null +++ b/tests/baselines/reference/neverNullishThroughParentheses.errors.txt @@ -0,0 +1,44 @@ +neverNullishThroughParentheses.ts(6,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +neverNullishThroughParentheses.ts(7,14): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +neverNullishThroughParentheses.ts(10,15): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +neverNullishThroughParentheses.ts(11,16): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +neverNullishThroughParentheses.ts(14,15): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +neverNullishThroughParentheses.ts(15,16): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +neverNullishThroughParentheses.ts(16,17): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +neverNullishThroughParentheses.ts(16,17): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. + + +==== neverNullishThroughParentheses.ts (8 errors) ==== + // Repro for issue where "never nullish" checks miss "never nullish" through parentheses + + const x: { y: string | undefined } | undefined = undefined as any; + + // Both should error - both expressions are guaranteed to be "oops" + const foo = x?.y ?? `oops` ?? ""; + ~~~~~~~~~~~~~~ +!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. + const bar = (x?.y ?? `oops`) ?? ""; + ~~~~~~~~~~~~~~ +!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. + + // Additional test cases with various levels of nesting + const baz = ((x?.y ?? `oops`)) ?? ""; + ~~~~~~~~~~~~~~ +!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. + const qux = (((x?.y ?? `oops`))) ?? ""; + ~~~~~~~~~~~~~~ +!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. + + // Test with different types + const str1 = ("literal") ?? "fallback"; + ~~~~~~~~~ +!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. + const str2 = (("nested")) ?? "fallback"; + ~~~~~~~~ +!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. + const nested = ("a" ?? "b") ?? "c"; + ~~~ +!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. + ~~~~~~~~~~ +!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. + \ No newline at end of file diff --git a/tests/baselines/reference/neverNullishThroughParentheses.js b/tests/baselines/reference/neverNullishThroughParentheses.js new file mode 100644 index 0000000000000..c2f9e37512a30 --- /dev/null +++ b/tests/baselines/reference/neverNullishThroughParentheses.js @@ -0,0 +1,36 @@ +//// [tests/cases/compiler/neverNullishThroughParentheses.ts] //// + +//// [neverNullishThroughParentheses.ts] +// Repro for issue where "never nullish" checks miss "never nullish" through parentheses + +const x: { y: string | undefined } | undefined = undefined as any; + +// Both should error - both expressions are guaranteed to be "oops" +const foo = x?.y ?? `oops` ?? ""; +const bar = (x?.y ?? `oops`) ?? ""; + +// Additional test cases with various levels of nesting +const baz = ((x?.y ?? `oops`)) ?? ""; +const qux = (((x?.y ?? `oops`))) ?? ""; + +// Test with different types +const str1 = ("literal") ?? "fallback"; +const str2 = (("nested")) ?? "fallback"; +const nested = ("a" ?? "b") ?? "c"; + + +//// [neverNullishThroughParentheses.js] +"use strict"; +// Repro for issue where "never nullish" checks miss "never nullish" through parentheses +var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; +var x = undefined; +// Both should error - both expressions are guaranteed to be "oops" +var foo = (_b = (_a = x === null || x === void 0 ? void 0 : x.y) !== null && _a !== void 0 ? _a : "oops") !== null && _b !== void 0 ? _b : ""; +var bar = (_d = ((_c = x === null || x === void 0 ? void 0 : x.y) !== null && _c !== void 0 ? _c : "oops")) !== null && _d !== void 0 ? _d : ""; +// Additional test cases with various levels of nesting +var baz = (_f = (((_e = x === null || x === void 0 ? void 0 : x.y) !== null && _e !== void 0 ? _e : "oops"))) !== null && _f !== void 0 ? _f : ""; +var qux = (_h = ((((_g = x === null || x === void 0 ? void 0 : x.y) !== null && _g !== void 0 ? _g : "oops")))) !== null && _h !== void 0 ? _h : ""; +// Test with different types +var str1 = (_j = ("literal")) !== null && _j !== void 0 ? _j : "fallback"; +var str2 = (_k = (("nested"))) !== null && _k !== void 0 ? _k : "fallback"; +var nested = (_l = ("a" !== null && "a" !== void 0 ? "a" : "b")) !== null && _l !== void 0 ? _l : "c"; diff --git a/tests/baselines/reference/neverNullishThroughParentheses.symbols b/tests/baselines/reference/neverNullishThroughParentheses.symbols new file mode 100644 index 0000000000000..2c509e730e29e --- /dev/null +++ b/tests/baselines/reference/neverNullishThroughParentheses.symbols @@ -0,0 +1,46 @@ +//// [tests/cases/compiler/neverNullishThroughParentheses.ts] //// + +=== neverNullishThroughParentheses.ts === +// Repro for issue where "never nullish" checks miss "never nullish" through parentheses + +const x: { y: string | undefined } | undefined = undefined as any; +>x : Symbol(x, Decl(neverNullishThroughParentheses.ts, 2, 5)) +>y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) +>undefined : Symbol(undefined) + +// Both should error - both expressions are guaranteed to be "oops" +const foo = x?.y ?? `oops` ?? ""; +>foo : Symbol(foo, Decl(neverNullishThroughParentheses.ts, 5, 5)) +>x?.y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) +>x : Symbol(x, Decl(neverNullishThroughParentheses.ts, 2, 5)) +>y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) + +const bar = (x?.y ?? `oops`) ?? ""; +>bar : Symbol(bar, Decl(neverNullishThroughParentheses.ts, 6, 5)) +>x?.y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) +>x : Symbol(x, Decl(neverNullishThroughParentheses.ts, 2, 5)) +>y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) + +// Additional test cases with various levels of nesting +const baz = ((x?.y ?? `oops`)) ?? ""; +>baz : Symbol(baz, Decl(neverNullishThroughParentheses.ts, 9, 5)) +>x?.y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) +>x : Symbol(x, Decl(neverNullishThroughParentheses.ts, 2, 5)) +>y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) + +const qux = (((x?.y ?? `oops`))) ?? ""; +>qux : Symbol(qux, Decl(neverNullishThroughParentheses.ts, 10, 5)) +>x?.y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) +>x : Symbol(x, Decl(neverNullishThroughParentheses.ts, 2, 5)) +>y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) + +// Test with different types +const str1 = ("literal") ?? "fallback"; +>str1 : Symbol(str1, Decl(neverNullishThroughParentheses.ts, 13, 5)) + +const str2 = (("nested")) ?? "fallback"; +>str2 : Symbol(str2, Decl(neverNullishThroughParentheses.ts, 14, 5)) + +const nested = ("a" ?? "b") ?? "c"; +>nested : Symbol(nested, Decl(neverNullishThroughParentheses.ts, 15, 5)) + diff --git a/tests/baselines/reference/neverNullishThroughParentheses.types b/tests/baselines/reference/neverNullishThroughParentheses.types new file mode 100644 index 0000000000000..e061aafd4289a --- /dev/null +++ b/tests/baselines/reference/neverNullishThroughParentheses.types @@ -0,0 +1,144 @@ +//// [tests/cases/compiler/neverNullishThroughParentheses.ts] //// + +=== neverNullishThroughParentheses.ts === +// Repro for issue where "never nullish" checks miss "never nullish" through parentheses + +const x: { y: string | undefined } | undefined = undefined as any; +>x : { y: string | undefined; } | undefined +> : ^^^^^ ^^^^^^^^^^^^^^^ +>y : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>undefined as any : any +> : ^^^ +>undefined : undefined +> : ^^^^^^^^^ + +// Both should error - both expressions are guaranteed to be "oops" +const foo = x?.y ?? `oops` ?? ""; +>foo : string +> : ^^^^^^ +>x?.y ?? `oops` ?? "" : string +> : ^^^^^^ +>x?.y ?? `oops` : string +> : ^^^^^^ +>x?.y : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>x : { y: string | undefined; } | undefined +> : ^^^^^ ^^^^^^^^^^^^^^^ +>y : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>`oops` : "oops" +> : ^^^^^^ +>"" : "" +> : ^^ + +const bar = (x?.y ?? `oops`) ?? ""; +>bar : string +> : ^^^^^^ +>(x?.y ?? `oops`) ?? "" : string +> : ^^^^^^ +>(x?.y ?? `oops`) : string +> : ^^^^^^ +>x?.y ?? `oops` : string +> : ^^^^^^ +>x?.y : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>x : { y: string | undefined; } | undefined +> : ^^^^^ ^^^^^^^^^^^^^^^ +>y : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>`oops` : "oops" +> : ^^^^^^ +>"" : "" +> : ^^ + +// Additional test cases with various levels of nesting +const baz = ((x?.y ?? `oops`)) ?? ""; +>baz : string +> : ^^^^^^ +>((x?.y ?? `oops`)) ?? "" : string +> : ^^^^^^ +>((x?.y ?? `oops`)) : string +> : ^^^^^^ +>(x?.y ?? `oops`) : string +> : ^^^^^^ +>x?.y ?? `oops` : string +> : ^^^^^^ +>x?.y : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>x : { y: string | undefined; } | undefined +> : ^^^^^ ^^^^^^^^^^^^^^^ +>y : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>`oops` : "oops" +> : ^^^^^^ +>"" : "" +> : ^^ + +const qux = (((x?.y ?? `oops`))) ?? ""; +>qux : string +> : ^^^^^^ +>(((x?.y ?? `oops`))) ?? "" : string +> : ^^^^^^ +>(((x?.y ?? `oops`))) : string +> : ^^^^^^ +>((x?.y ?? `oops`)) : string +> : ^^^^^^ +>(x?.y ?? `oops`) : string +> : ^^^^^^ +>x?.y ?? `oops` : string +> : ^^^^^^ +>x?.y : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>x : { y: string | undefined; } | undefined +> : ^^^^^ ^^^^^^^^^^^^^^^ +>y : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>`oops` : "oops" +> : ^^^^^^ +>"" : "" +> : ^^ + +// Test with different types +const str1 = ("literal") ?? "fallback"; +>str1 : "literal" +> : ^^^^^^^^^ +>("literal") ?? "fallback" : "literal" +> : ^^^^^^^^^ +>("literal") : "literal" +> : ^^^^^^^^^ +>"literal" : "literal" +> : ^^^^^^^^^ +>"fallback" : "fallback" +> : ^^^^^^^^^^ + +const str2 = (("nested")) ?? "fallback"; +>str2 : "nested" +> : ^^^^^^^^ +>(("nested")) ?? "fallback" : "nested" +> : ^^^^^^^^ +>(("nested")) : "nested" +> : ^^^^^^^^ +>("nested") : "nested" +> : ^^^^^^^^ +>"nested" : "nested" +> : ^^^^^^^^ +>"fallback" : "fallback" +> : ^^^^^^^^^^ + +const nested = ("a" ?? "b") ?? "c"; +>nested : "a" +> : ^^^ +>("a" ?? "b") ?? "c" : "a" +> : ^^^ +>("a" ?? "b") : "a" +> : ^^^ +>"a" ?? "b" : "a" +> : ^^^ +>"a" : "a" +> : ^^^ +>"b" : "b" +> : ^^^ +>"c" : "c" +> : ^^^ + diff --git a/tests/baselines/reference/predicateSemantics.errors.txt b/tests/baselines/reference/predicateSemantics.errors.txt index cf503611a4b88..9bc7e32be2280 100644 --- a/tests/baselines/reference/predicateSemantics.errors.txt +++ b/tests/baselines/reference/predicateSemantics.errors.txt @@ -7,26 +7,26 @@ 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(32,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(34,13): error TS2871: This expression is always nullish. +predicateSemantics.ts(36,13): error TS2871: This expression is always nullish. +predicateSemantics.ts(37,13): 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(40,21): error TS2871: This expression is always nullish. +predicateSemantics.ts(41,13): error TS2871: This expression is always nullish. +predicateSemantics.ts(42,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +predicateSemantics.ts(43,13): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. +predicateSemantics.ts(45,13): error TS2871: This expression is always nullish. +predicateSemantics.ts(45,13): error TS2871: This expression is always nullish. predicateSemantics.ts(45,13): error TS2871: This expression is always nullish. -predicateSemantics.ts(45,21): error TS2871: This expression is always nullish. -predicateSemantics.ts(45,29): error TS2871: This expression is always nullish. predicateSemantics.ts(46,13): error TS2871: This expression is always nullish. -predicateSemantics.ts(46,21): error TS2881: This expression is never nullish. +predicateSemantics.ts(46,13): error TS2869: Right operand of ?? is unreachable because the left operand 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(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. @@ -89,7 +89,7 @@ predicateSemantics.ts(90,1): error TS2869: Right operand of ?? is unreachable be const p07 = null ?? null ?? null; ~~~~ !!! error TS2871: This expression is always nullish. - ~~~~ + ~~~~~~~~~~~~ !!! error TS2871: This expression is always nullish. const p08 = null ?? opt ?? null; ~~~~ @@ -97,14 +97,14 @@ predicateSemantics.ts(90,1): error TS2869: Right operand of ?? is unreachable be 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); ~~~~ @@ -115,35 +115,35 @@ predicateSemantics.ts(90,1): error TS2869: Right operand of ?? is unreachable be 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. + ~~~~~~~~ +!!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. const p17 = opt ?? (opt ? 1 : 2) ?? 3; - ~~~~~~~~~~~ -!!! error TS2881: This expression is never nullish. + ~~~~~~~~~~~~~~~~~~~~ +!!! 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. const p22 = null ?? 1 ?? 1; ~~~~ !!! error TS2871: This expression is always nullish. - ~ -!!! error TS2881: 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 TS2871: This expression is always nullish. - ~~~~~~~~~~~ -!!! error TS2881: 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/cases/compiler/neverNullishThroughParentheses.ts b/tests/cases/compiler/neverNullishThroughParentheses.ts new file mode 100644 index 0000000000000..39a0d8f92938c --- /dev/null +++ b/tests/cases/compiler/neverNullishThroughParentheses.ts @@ -0,0 +1,18 @@ +// @strict: true + +// Repro for issue where "never nullish" checks miss "never nullish" through parentheses + +const x: { y: string | undefined } | undefined = undefined as any; + +// Both should error - both expressions are guaranteed to be "oops" +const foo = x?.y ?? `oops` ?? ""; +const bar = (x?.y ?? `oops`) ?? ""; + +// Additional test cases with various levels of nesting +const baz = ((x?.y ?? `oops`)) ?? ""; +const qux = (((x?.y ?? `oops`))) ?? ""; + +// Test with different types +const str1 = ("literal") ?? "fallback"; +const str2 = (("nested")) ?? "fallback"; +const nested = ("a" ?? "b") ?? "c";