TL;DR

  • never 型への代入を使うと、TypeScript で網羅性チェックができる。
  • 複数の分岐から return する関数では、戻り値に型アノテーションを付ける。それでも明示的な網羅性チェックが必要な場合はある。
  • 複数の値のすべての組み合わせを網羅しているかは、テンプレートリテラル型でチェックできる。

内容

never 型で網羅性をチェックする

条件分岐ですべての選択肢を処理すると、残った値の型は never(ボトム型、つまり空集合)になるので、TypeScript では never 型への代入を利用して網羅性をチェックできる。実装の仕方は 3 つある。

1 つ目は、never を受け取る関数を用意する方法である。型チェックのときだけでなく、実行時にも想定外の値から守れる。

const assertUnreachable = (value: never): never => {
  throw new Error(`Missed a case: ${value}`)
}
 
const drawShape = (shape: Shape, context: CanvasRenderingContext2D) => {
  switch (shape.type) {
    case "box":
    // ...
    case "circle":
    // ...
    // case 'line':
    //   ...
    default:
      assertUnreachable(shape)
  }
}

2 つ目は、never 型の変数に直接代入する方法である。

const drawShape = (shape: Shape, context: CanvasRenderingContext2D) => {
  switch (shape.type) {
    // ...
    default:
      const exhaustiveCheck: never = shape
      throw new Error(`Missed a case: ${exhaustiveCheck}`)
  }
}

3 つ目は、satisfies 演算子を使う方法である。

const drawShape = (shape: Shape, context: CanvasRenderingContext2D) => {
  switch (shape.type) {
    // ...
    default:
      shape satisfies never
      throw new Error(`Missed a case: ${shape}`)
  }
}

戻り値の型アノテーションで網羅性をチェックする

複数の分岐から return する関数では、戻り値に型アノテーションを付けると、ケースの漏れを防げる場合がある(項目18 推論可能な型でコードを乱雑にしない)。ただし、戻り値に undefined が含まれる場合などは、明示的な網羅性チェックが必要になる。

const getArea = (shape: Shape): number => {
  //                          ------ Function lacks ending return statement and
  //                                 return type does not include 'undefined'.
  switch (shape.type) {
    case "box":
      const [width, height] = shape.size
      return width * height
    case "circle":
      return Math.PI * shape.radius ** 2
  }
}

複数の値の組み合わせを網羅する

複数の値のすべての組み合わせを網羅しているかをチェックするには、テンプレートリテラル型を使えばよい(項目54 テンプレートリテラル型を使ってDSLや文字列間の関係をモデリングする)。

type Play = "rock" | "paper" | "scissors"
 
const shoot = (a: Play, b: Play) => {
  const pair = `${a},${b}` as `${Play},${Play}` // or as const
  //    ^? const pair: "rock,rock" | "rock,paper" | "rock,scissors" |
  //                   "paper,rock" | "paper,paper" | "paper,scissors" |
  //                   "scissors,rock" | "scissors,paper" | "scissors,scissors"
  switch (pair) {
    case "rock,rock":
    // ...
    default:
      assertUnreachable(pair)
  }
}

関連する typescript-eslint のルール

typescript-eslint の switch-exhaustiveness-check を有効にすると、網羅していない switch 文を検出できる。

キーワード