diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8e5c03560db3e..250dec144eb6c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -40567,7 +40567,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function isNotWithinNullishCoalesceExpression(node: BinaryExpression) { - return !isBinaryExpression(node.parent) || node.parent.operatorToken.kind !== SyntaxKind.QuestionQuestionToken; + const parent = walkUpOuterExpressions(node); + return !isBinaryExpression(parent) || parent.operatorToken.kind !== SyntaxKind.QuestionQuestionToken; } function getSyntacticNullishnessSemantics(node: Node): PredicateSemantics { diff --git a/tests/baselines/reference/neverNullishThroughParentheses.errors.txt b/tests/baselines/reference/neverNullishThroughParentheses.errors.txt new file mode 100644 index 0000000000000..2ccf534596d88 --- /dev/null +++ b/tests/baselines/reference/neverNullishThroughParentheses.errors.txt @@ -0,0 +1,44 @@ +neverNullishThroughParentheses.ts(6,21): error TS2881: This expression is never nullish. +neverNullishThroughParentheses.ts(7,22): error TS2881: This expression is never nullish. +neverNullishThroughParentheses.ts(10,23): error TS2881: This expression is never nullish. +neverNullishThroughParentheses.ts(11,24): error TS2881: This expression 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,24): error TS2881: This expression 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 TS2881: This expression is never nullish. + const bar = (x?.y ?? `oops`) ?? ""; + ~~~~~~ +!!! error TS2881: This expression is never nullish. + + // Additional test cases with various levels of nesting + const baz = ((x?.y ?? `oops`)) ?? ""; + ~~~~~~ +!!! error TS2881: This expression is never nullish. + const qux = (((x?.y ?? `oops`))) ?? ""; + ~~~~~~ +!!! error TS2881: This expression 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 TS2881: This expression 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..fb89eaf0b2f4e 100644 --- a/tests/baselines/reference/predicateSemantics.errors.txt +++ b/tests/baselines/reference/predicateSemantics.errors.txt @@ -14,9 +14,12 @@ 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(38,29): error TS2881: This expression is never nullish. predicateSemantics.ts(39,21): error TS2871: This expression is always nullish. +predicateSemantics.ts(39,29): 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(40,37): 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. @@ -38,7 +41,7 @@ predicateSemantics.ts(89,1): error TS2869: Right operand of ?? is unreachable be predicateSemantics.ts(90,1): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. -==== predicateSemantics.ts (38 errors) ==== +==== predicateSemantics.ts (41 errors) ==== declare let opt: number | undefined; // OK: One or other operand is possibly nullish @@ -109,13 +112,19 @@ predicateSemantics.ts(90,1): error TS2869: Right operand of ?? is unreachable be const p12 = opt ?? (null ?? 1); ~~~~ !!! error TS2871: This expression is always nullish. + ~ +!!! error TS2881: This expression is never nullish. const p13 = opt ?? (null ?? null); ~~~~ +!!! error TS2871: This expression is always nullish. + ~~~~ !!! error TS2871: This expression is always nullish. const p14 = opt ?? (null ?? null ?? null); ~~~~ !!! error TS2871: This expression is always nullish. ~~~~ +!!! error TS2871: This expression is always nullish. + ~~~~ !!! error TS2871: This expression is always nullish. const p15 = opt ?? (opt ? null : undefined) ?? null; ~~~~~~~~~~~~~~~~~~~~~~ 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";