Home > Back-end >  Why TypeScript does not complain when trying to read from never in an union type?
Why TypeScript does not complain when trying to read from never in an union type?

Time:05-05

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;
};

CodePudding user response:

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

CodePudding user response:

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

  • Related