Lazar Ljubenović
Lazar Ljubenović

Reputation: 19764

A "not null" type guard resolves to "never" in the else-branch

I created a type guard which checks if the argument is exactly null:

function isNotNull<T> (arg: T): arg is Exclude<T, null> {
  return arg !== null
}

When I test it, the then-branch works correctly: it strips away the null from the type.

const value: string | null = 0 as any
if (isNotNull(value)) {
  // value is of type `string` here
}

However, it becomes never in the else-branch.

const value: string | null = 0 as any
if (isNotNull(value)) {
  // `value` is of type `string` here
} else {
  // `value` is of type `never` here
}

I'd like it to resolve to null in the else-branch.

How can I achieve this?

Upvotes: 4

Views: 7879

Answers (1)

Duncan
Duncan

Reputation: 95732

The problem here is the assignment:

const value: string | null = 0 as any

The compiler knows that value is a constant, so it can never be null.

It doesn't get much better if you use let:

let value: string | null = 'foo';
if (isNotNull(value)) {
  // `value` is of type `string` here
} else {
  // `value` is of type `never` here
}

Again the typescript compiler knows that the value is not null.

But if assign something where Typescript can't infer a constant value then you will have your expected string in the if branch 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"
}

Also, even this last example only works if you have the strictNullChecks compiler option set true. If you haven't enabled strictNullChecks you cannot exclude null from type and the if branch will be string which is the same as string | null so that leaves only never for the else.

Edit: The reason why this doesn't work when strictNullChecks is off is quite interesting. In that case the types string | null and string are identical. That means value actually has the type string and Exclude<string, null> is simply string (and therefore still includes null), so the else clause is left with type never for value.

Upvotes: 3

Related Questions