TypeScriptの特殊な型同士の型演算結果まとめ

TypeScriptの型定義(d.ts)を読んでいると、
いろいろな型パズルが出てきてわけわかんなくなってきたので、
anyやnullなどの特殊な型同士をいじくったときの結果をまとめた。

TypeScript3.4.3で確認。動作を見ただけで仕様を当たったわけではない点に注意。

[追記]
strictNullChecksの存在をすっかり忘れていたので追加。直感通りの素直な結果ではあった。

# Union Types

```ts
type Union<A, B> = A | B;
```

`"strictNullChecks": false`の場合

|A｜B|any|unknown|string|void|null|undefined|never|
|---|---|---|---|---|---|---|---|---|
|**any**|any|any|any|any|any|any|any|
|**unknown**||unknown|unknown|unknown|unknown|unknown|unknown|
|**string**|||string|string｜void|string|string|string|
|**void**||||void|void|void|void|
|**null**|||||null|null|null|
|**undefined**||||||undefined|undefined|
|**never**|||||||never|

`"strictNullChecks": true`の場合（異なる部分は太字）

|A｜B|any|unknown|string|void|null|undefined|never|
|---|---|---|---|---|---|---|---|---|
|**any**|any|any|any|any|any|any|any|
|**unknown**||unknown|unknown|unknown|unknown|unknown|unknown|
|**string**|||string|string｜void|**string｜null**|**string｜undefined**|string|
|**void**||||void|**void｜null**|**void｜undefined**|void|
|**null**|||||null|**null｜undefined**|null|
|**undefined**||||||undefined|undefined|
|**never**|||||||never|

`"strictNullChecks":true`や`"strict":true`(含strictNullChecks)によって以下のケースを除外できる。

```ts
let a: string = null; // strictNullChecks: trueではエラー
```

voidについて。
voidは関数の返り値が無いことを示すが、実際は値を返してもエラーにならない。
ただし、厳密にvoidではない場合はきちんとチェックされる。

```ts
type Returns<T> = () => T;

// 返り値型が void なら何返してもOK
const a: Returns<void> = () => 1; // OK

// 返り値型が string|void ならそれ以外はNG
const b: Returns<string|void> = () => 1; // NG

// void値は真偽値判定できないという違いもある
if (a()) { // エラー
  a() || true; // エラー
}

if (b()) { // OK
  b() || true; // OK
}
```

さきほどの表で`string|void`がそのまま残っているのはこのチェックのためだろう。

# Intersection types

```ts
type Intersection<A, B> = A & B;
```

`"strictNullChecks": false`の場合

|A & B|any|unknown|string|void|null|undefined|never|
|---|---|---|---|---|---|---|---|---|
|**any**|any|any|any|any|any|any|never|
|**unknown**||unknown|void|string|null|undefined|never|
|**string**|||string|string & void|null|undefined|never|
|**void**||||void|null|undefined|never|
|**null**|||||null|undefined|never|
|**undefined**||||||undefined|never|
|**never**|||||||never|

`"strictNullChecks": true`の場合（異なる部分は太字）

|A & B|any|unknown|string|void|null|undefined|never|
|---|---|---|---|---|---|---|---|---|
|**any**|any|any|any|any|any|any|never|
|**unknown**||unknown|void|string|null|undefined|never|
|**string**|||string|string & void|**string & null**|**string & undefined**|never|
|**void**||||void|**void & null**|**void & undefined**|never|
|**null**|||||null|**null & undefined**|never|
|**undefined**||||||undefined|never|
|**never**|||||||never|

さっきと逆になると思いきや、ここでもanyが強い。
ただ、anyとneverはneverが勝つ。

```ts
type a = any | any & never; // => any
type b = (any | any) & never; // => never
type c = any | (any & never); // => any
```

型演算は右結合のようだ。

# Extends

特殊な型同士のExtendsの関係性を見てみる。
継承というより代入可能性と考えたほうがわかりやすいかもしれない。

```ts
type Extends<A, B> = A extends B ? "○" : "×";
```

`"strictNullChecks": false`の場合

|A extends B|(B) any|unknown|string|void|null|undefined|never|
|---|---|---|---|---|---|---|---|---|
|**(A) any**|○|○|○｜×|○｜×|○｜×|○｜×|○｜×|
|**unknown**|○|○|×|×|×|×|×|
|**string**|○|○|○|×|×|×|×|
|**void**|○|○|×|○|×|×|×|
|**null**|○|○|○|○|○|○|×|
|**undefined**|○|○|○|○|○|○|×|
|**never**|never|never|never|never|never|never|never|

`"strictNullChecks": true`の場合（異なる部分は太字）

|A extends B|(B) any|unknown|string|void|null|undefined|never|
|---|---|---|---|---|---|---|---|---|
|**(A) any**|○|○|○｜×|○｜×|○｜×|○｜×|○｜×|
|**unknown**|○|○|×|×|×|×|×|
|**string**|○|○|○|×|×|×|×|
|**void**|○|○|×|○|×|×|×|
|**null**|○|○|**×**|**×**|○|**×**|×|
|**undefined**|○|○|**×**|○|**×**|○|×|
|**never**|never|never|never|never|never|never|never|

へんてこなのはanyとneverで、
anyはstringやvoidを継承している状態と継承していない状態の両方を併せ持っている。
neverはそもそも判定すらされず、全体がneverと評価されてしまう。

この挙動を利用して、例えばanyを判定する型を作ることができる。

```ts
// neverを継承するかの判定
// この型は、Tがanyのときtrue|false, neverのときnever, それ以外のときfalseになる
type ExtendsNever<T> = T extends never ? true : false;

// booleanはtrue|falseなので、booleanとマッチするかどうかでanyを取り出せる
// (ちなみにこの型はReactの型定義でも使われている)
type IsExactlyAny<T> = boolean extends ExtendsNever<T> ? true : false;

// 同じ要領でneverを判定
type IsExactlyNever<T> = false extends ExtendsNever<T> ? false : true;

// unknownを判定してみよう
```

ちなみに、union distributionを発生させないようにextendsを変えた場合、結果はまた違ったものになる。

```ts
type Extends<A, B> = A[] extends B[] ? "○" : "×";
```

|A[] extends B[]|(B) any|unknown|string|void|null|undefined|never|
|---|---|---|---|---|---|---|---|---|
|**(A) any**|○|○|○|○|○|○|○|
|**unknown**|○|○|×|×|×|×|×|
|**string**|○|○|○|×|×|×|×|
|**void**|○|○|×|○|×|×|×|
|**null**|○|○|**×**|**×**|○|**×**|×|
|**undefined**|○|○|**×**|○|**×**|○|×|
|**never**|○|○|○|○|○|○|○|

`never extends never ? true : false`はneverだけど、
`never[] extends never[] ? true : false`はtrueというわけ。ややこしいね。

型パズルで遊ぶのもほどほどに。