Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.

36 changes: 36 additions & 0 deletions tests/baselines/reference/neverNullishThroughParentheses.js
Original file line number Diff line number Diff line change
@@ -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";
46 changes: 46 additions & 0 deletions tests/baselines/reference/neverNullishThroughParentheses.symbols
Original file line number Diff line number Diff line change
@@ -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))

144 changes: 144 additions & 0 deletions tests/baselines/reference/neverNullishThroughParentheses.types
Original file line number Diff line number Diff line change
@@ -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"
> : ^^^

11 changes: 10 additions & 1 deletion tests/baselines/reference/predicateSemantics.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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;
~~~~~~~~~~~~~~~~~~~~~~
Expand Down
18 changes: 18 additions & 0 deletions tests/cases/compiler/neverNullishThroughParentheses.ts
Original file line number Diff line number Diff line change
@@ -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";