Hvorfor klager TypeScript ikke, når man forsøger at læse fra never i en unions type?


Jeg troede, at TypeScript ville klage, når man prøver at læse egenskaben length af noget, der kunne være et array eller never, og derefter, at jeg skulle indsnævre typen med en type predicates. Det er ikke tilfældet. Hvorfor? Hvordan kunne jeg ændre typerne, så TypeScript tvinger mig til at tjekke, at jeg ikke arbejder med et tomt objekt?

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

Svar #1

Nedenfor er et eksempel på trin-for-trin type-destruktion:

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

Og nedenfor er et eksempel på forventet adfærd, der måske passer til dit behov:

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-legeredet

Svar #2

Bliver aldrig fjernet fra unionstyper (f.eks. 0 + 5=5).

type Test1 = never | string // string

Never overskriver andre typer i snit-typer. (f.eks. 0 x 5=0)

type Test2 = never & string // never

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

Relaterede artikler