Jean-Baptiste Rudant
Jean-Baptiste Rudant

Reputation: 1066

Why TypeScript does not complain when trying to read from never in an union type?

I thought that TypeScript would complain when trying to read the property length of what could be an array or never, and then, that I would need to narrow down the type with a type predicate. This is not the case. Why ? How could I change the types so TypeScript force me to check that I am not dealing with an empty object ?

// strictNullChecks = true
type Empty = Record<string, never>;

type NonEmpty = {
  documents: Array<number>;
};

function isEmpty(x: Empty | NonEmpty): x is Empty {
  return !("documents" in x);
}

const count = (x: Empty | NonEmpty) => {
  // TypeScript does not complain about reading `length` property of `never`
  // if (isEmpty(x)) {
  //   return 0;
  // }
  return x.documents.length;
};

Upvotes: 3

Views: 325

Answers (2)

Edwin
Edwin

Reputation: 415

Below is a step by step type destruction example:

type Empty = Record<string, never>;

type NonEmpty = {
  documents: Array<number>;
};

function isEmpty(x: Empty | NonEmpty): x is Empty {
  return !("documents" in x);
}

const count = (x: Empty | NonEmpty) => {

  type D0 = typeof x.documents
  type D1 =  (Empty | NonEmpty)[`documents`]
  type D2 = Empty[`documents`] | NonEmpty[`documents`]
  type D3 =  never | NonEmpty[`documents`]
  type D4 =  NonEmpty[`documents`]
  type D5 =  Array<number>

  return x.documents.length; // x.documents is number[], that's why no error here
};

And below is the expected behaviour example that may fit your need:

type EmptyExpected = Record<string, undefined>;

const countExpected = (x: EmptyExpected | NonEmpty) => {

  type D0 = typeof x.documents
  type D1 =  (EmptyExpected | NonEmpty)[`documents`]
  type D2 = EmptyExpected[`documents`] | NonEmpty[`documents`]
  type D3 =  undefined | NonEmpty[`documents`]
  type D4 =  undefined | number[]

  return x.documents.length; // x.documents is number[] | undefined, that's why it is an error here
};

TypeScript Playground

Upvotes: 2

arcbjorn
arcbjorn

Reputation: 121

Never gets dropped from union types (e.g. 0 + 5 = 5).

type Test1 = never | string // string

Never overrides other types in intersection types. (e.g. 0 x 5 = 0)

type Test2 = never & string // never

Docs: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-never-type

Upvotes: 2

Related Questions