TypeScript の never型

TypeScriptの`never`型についてまとめます。
今まではいまいち使い所が分かっていないな〜という感じでしたが、今回理解できた気がします。

**動作確認環境**
- Mac
- [TypeScript Playground](https://www.typescriptlang.org/play) v５.１.３


# never型とは

TypeScriptの型の一つです。
「値を持たない」ことを表します。

## never型の特徴

関数の戻り値型で考えてみます。何も値を返さない関数の場合、戻り値型が`never`となります。
例えば終了しない関数や、必ず例外が発生する関数は戻り値の型が`never`となります。

終了しない関数
```typescript
const fn = (): never => {
    while (true) {}; // 無限ループ
}
```

必ず例外が発生する関数
```typescript
const fn = (): never => {
    throw new Error();
}
```

`never`型の変数には`never`型の値のみ代入できます。その他の値は代入できません。
```typescript
const foo: never = 1; //　　Error : Type 'number' is not assignable to type 'never'.

const fn = (): never => {
    throw new Error();
}
const bar: never = fn();
```

`never`型はどんな型にも代入できます。
```typescript
const fn = (): never => {
    throw new Error();
}
const num: number = fn();
const str: string = fn();
const flag: boolean = fn();
```

また、「作り得ない型」も`never`型となります。
```typescript
// numberとstringを同時に満たす値は作り得ない。NumberStringはneverとなる
type NumberString = number & string; 
```


### never と void の違い

`void` は値を返さない関数の戻り値として使われます。
※正確には、関数が何も返さないことを強制するものではなはなく、他の値を返すことができますが無視されます。詳しくは[こちら](https://www.typescriptlang.org/docs/handbook/2/functions.html#return-type-void)を参照してください。

言い換えると、値を何も返さない関数は`void`を返します。returnを返すことがない(または常にスローする)関数は`never`を返します

次のような場合は`void`を返すのが正しいので、TypeScriptはコンパイルエラーを出します。
```typescript
// Error : A function returning 'never' cannot have a reachable end point.
const fn = (value: string): never => { 
  console.log(value)
}
```

# never型の使い所　網羅チェック

`never`型を使うことで、網羅チェックを行い、網羅できていない場合に(実行時エラーではなく)コンパイルエラーを出すことができます。

以下のコードを例にします。

```typescript
type FavoriteFruit = "Apple" | "Lemon";

const getMainColor = (fruit: FavoriteFruit) => {
  switch (fruit) {
    case "Apple":
      return "red";
    case "Lemon":
      return "yellow";
    default:
      throw new Error(`${fruit} is not in FavoriteFruit.`);
  }
} 

console.log(getMainColor("Apple")); // "red"
```

`FavoriteFruit` に　`Grape`「葡萄」を追加することにしました。

```typescript
type FavoriteFruit = "Apple" | "Lemon" | "Grape";
```

関数`getMainColor`も`Grape`に対応する値を返すように修正する必要があります。
が、`getMainColor`への修正を忘れてしまったとします。

`getMainColor`への修正をしない状態で`getMainColor("Grape")`というコードを書いてもコンパイルエラーにはなりません。
実行することによって　` Grape is not in FavoriteFruit. ` のようにエラーが発生します。

```typescript
type FavoriteFruit = "Apple" | "Lemon" | "Grape";

const getMainColor = (fruit: FavoriteFruit) => {
  switch (fruit) {
    case "Apple":
      return "red";
    case "Lemon":
      return "yellow";
    default:
      throw new Error(`${fruit} is not in FavoriteFruit.`);
  }
} 

console.log(getMainColor("Grape")); // Error : Grape is not in FavoriteFruit. 
```

`getMainColor`で`never`型を使う実装を行うことによって、これをコンパイルエラーとして検知することができるようになります。

```typescript
const getMainColor = (fruit: FavoriteFruit) => {
  switch (fruit) {
    case "Apple":
      return "red";
    case "Lemon":
      return "yellow";
    default:
      const _check: never = fruit; // Type 'string' is not assignable to type 'never'.
      return _check
  }
} 
```

`never`型の変数には`never`型以外の値を代入することができません。
`fruit`変数の値が`Grape`だった場合、`default`分岐に入ってくるため、それを推測したTypeScriptがコンパイルエラーを出しています。コンパイルエラーによって、実行前にコードを修正することができます。

以下が修正したコードです。

```typescript
type FavoriteFruit = "Apple" | "Lemon" | "Grape";

const getMainColor = (fruit: FavoriteFruit) => {
  switch (fruit) {
    case "Apple":
      return "red";
    case "Lemon":
      return "yellow";
    case "Grape":
      return "violet";
    default:
      const _check: never = fruit;
      return _check
  }
} 
　
console.log(getMainColor("Grape")); // "violet"
```


# 参考

- https://www.typescriptlang.org/docs/handbook/2/functions.html#never
- https://typescript-jp.gitbook.io/deep-dive/type-system/never
- https://typescript-jp.gitbook.io/deep-dive/type-system/discriminated-unions
- https://typescriptbook.jp/reference/statements/never