Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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.
Comment thread
jakebailey marked this conversation as resolved.
Outdated
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";
Loading