The issue at hand lies with the assignment:
const value: string | null = 0 as any
Since the compiler recognizes that value
is a constant, it understands that it can never be null.
Even if you use let
, the situation doesn't improve much:
let value: string | null = 'foo';
if (isNotNull(value)) {
// `value` is of type `string` in this block
} else {
// `value` is of type `never` in this block
}
Once again, TypeScript's static analysis knows that the value cannot be null.
However, if you assign a value where TypeScript cannot deduce a constant, then you will find your anticipated string
in the if
condition and null
in the else
:
function isNotNull<T> (arg: T): arg is Exclude<T, null> {
return arg !== null
}
let value: string | null = foo();
if (isNotNull(value)) {
// `value` is of type `string` here
} else {
// `value` is of type `null` here
}
function foo(): string | null {
return "foo"
}
Even in this scenario, it only functions when the strictNullChecks
compilation setting is enabled. Without strictNullChecks
, you cannot eliminate null
from the type
, and the if
branch defaults to being just string
which aligns with string | null
, hence resulting in never
for the else
section.
Edit:
The reason behind this not functioning without strictNullChecks
is intriguing. In such instances, the types string | null
and string
are considered identical. Consequently, value
fundamentally possesses the type string
, leading Exclude<string, null>
to essentially represent string
(thus still encompassing null
), leaving the else
portion with type never
for value
.