TypeScript の型のエラーはどんなときに発生するのか？～部分型と変性で学ぶ TypeScript の型システム～

# TypeScript の型のエラーはどんなときに発生するのか？

TypeScript は静的型付け言語です。
型のチェックをコンパイルやエディタの拡張機能などを通じて行い、実行前に型のエラーを示してくれます。

では、型のエラーはどんなときに発生するのでしょうか？
その手掛かりとして、TypeScript の実際の型エラーを見てみましょう。

```typescript
const x: number = "number"
//    ^
// Type 'string' is not assignable to type 'number'.
```

> `string` 型は `number` 型に **代入** できません。

つまり、「右辺の値の型」が「左辺の変数の型」に代入できない場合に、エラーが発生するようです。
では、どんな場合に代入できて、どんな場合に代入できないのでしょうか？

型が同じ場合はもちろん代入できます。しかしそれだけではありません。

答えを言ってしまうと、「右辺の値の型」が「左辺の変数の型」の **部分型** である場合に代入できます。
そして、その場合に型のチェックを通過することができます。

この記事では **部分型** やそれに関連する **変性** そして、TypeScript 4.7 で追加された  **Optional Variance Annotations**  について説明します。

:::note
TypeScript の部分型は正確には **構造的部分型** (structural subtyping)です。
詳細はこちらの記事が参考になります: [構造的部分型 (structural subtyping) _ TypeScript入門『サバイバルTypeScript』](https://typescriptbook.jp/reference/values-types-variables/structural-subtyping)
:::

# 部分型

型を集合で説明することがよくあるので、ここでもそれに倣い集合で部分型の説明をするなら、部分型は部分集合です。

例えば、`number | string` のようなユニオン型は、`33` や `-4` などの数値 と、`"Hello World"` や `"TypeScript"` などの文字列を値として持つ集合です。このとき、`number` は `number | string` の部分集合ですよね？

つまり、`number` 型は `number | string` 型の部分型であり、代入可能です！

```typescript
const x: number = 1
const y: number | string = x
// => OK!
```

もう一つ例を見てみましょう。次はオブジェクト型です。
以下の2つのオブジェクト型があったとします。`Hoge` と `HogeFuga` どちらがどちらの部分型でしょうか？

```typescript
type Hoge = { hoge: number }
type HogeFuga = { hoge: number, fuga: string }
```

見た目だけだと `Hoge` は `HogeFuga` の 部分型っぽいですが、実際はその逆で、 `HogeFuga` が `Hoge` の部分型です。
これは、使う場面を想像してみるとわかりやすいかと思います。

`Hoge` 型を使うとき、その値からプロパティ `hoge` プロパティを取り出す場面が訪れるでしょう。
`HogeFuga` 型のプロパティは `hoge` だけでなく `fuga` も必要です。

もし、`HogeFuga` の値を使おうとしたときに実際の中身が `Hoge` だったら、プロパティ `fuga` が存在しなくてエラーが発生してしまいます。逆に、 `Hoge` の中身が `HogeFuga` だったとしても、`Hoge` はプロパティ `hoge` しか使わないので問題なく動作します。

実際に、それぞれ代入してみると、前者はエラーになり、後者は問題ありません。

```typescript
const h : Hoge     = { hoge: 1 }
const hf: HogeFuga = { hoge: 1, fuga: "a" }

const x: HogeFuga = h  // => Property 'fuga' is missing in type 'Hoge' but required in type 'HogeFuga'.
const y: Hoge     = hf // => OK
```

あらためて、 `Hoge` と `HogeFuga` をまとめると次のようになります。

- `Hoge`: プロパティ `hoge` を持っているオブジェクトの型 (他にもプロパティを持っていてもOK！)
- `HogeFuga`: プロパティ `hoge` と `fuga` の両方持っているオブジェクトの型
 
集合的に考えても `Hoge` の方が `HogeFuga` よりも広いの集合ですので、`HogeFuga` は `Hoge` の部分型です。

このように、型を集合のように考え、部分集合かどうかで部分型を判断すれば、ほとんどの場合型チェックが通ります。
次の章でもう少し複雑な場合を説明します。

# 型変数と変性

TypeScript は `Array<T>` のように型変数を持つ型があります。(`Array<T>` の場合、`T` が型変数)
このような型の場合、型変数の部分型になっているかどうかが重要になってきます。

例えば、`Array<number | string>` と `Array<number>`  はどうでしょうか？
`Array<number>` に `Array<number | string>` は代入できません。

```typescript
const arrNS: Array<number | string> = [1, "two", 3]
const arrN: Array<number> = arrNS // => Error
//    ^^^^
// Type '(string | number)[]' is not assignable to type 'number[]'.
//   Type 'string | number' is not assignable to type 'number'.
//     Type 'string' is not assignable to type 'number'.
```

一方、`Array<number | string>` に `Array<number>` は代入できました。

```typescript
const arrN: Array<number> = [1,2,3]
const arrNS: Array<number | string> = arrN // => OK
```

このように、`A` が `B` の部分型とき、`Array<A>` は `Array<B>` の部分型になりました。
ある型の型変数が部分型のとき、その型も必ず部分型になるのでしょうか？

答えは No です。もう一つの例を見てみましょう。

型変数として、関数の引数の型が与えられる `Func<T>`  型を考えてみましょう。

```typescript
type Func<T> = (x: T) => number
``` 

`Func<number | string>` に `Func<number>` を代入してみましょう。

```typescript
const funcN: Func<number> = (x: number) => 1
const funcNS: Func<number | string> = funcN
//    ^^^^^^
// Type 'Func<number>' is not assignable to type 'Func<string | number>'.
//   Type 'string | number' is not assignable to type 'number'.
//     Type 'string' is not assignable to type 'number'.
```

エラーになりました。
では逆に、`Func<number>` に `Func<number | string>` を代入してみましょう。

```typescript
const funcNS: Func<number | string> = (x: number | string) => 1
const funcN: Func<number> = funcNS // OK
```

代入できました！
`number` は `number | string` の部分型ですが、逆に `Func<number | string>` が `Func<number>` の部分型のようです。

ここまでをまとめると、次のようになります。

- `A` が `B` の 部分型のとき、`Array<A>` は `Array<B>` の部分型
- `A` が `B` の 部分型のとき、`Func<B>` は `Func<A>` の部分型

このように、型変数の使われ方によってその方の部分型関係が変わる性質のことを **変性**  (variance)と呼びます。
変性の種類は以下の4種類です。

- **共変** (covariant)
    - `A` が `B` の 部分型のとき、`Type<A>` は `Type<B>` の部分型
- **反変** (contravariant)
    - `B` が `A` の 部分型のとき、`Type<A>` は `Type<B>` の部分型
- **双変** (bivariant)
    - `A` が `B` の 部分型または `B` が `A` の 部分型のとき、`Type<A>` は `Type<B>` の部分型
- **不変** , **非変** (invariant, nonvariant)[^1]
    - `A` と `B` が同じ型のときのみ、`Type<A>` は `Type<B>` の部分型

`Array<T>` は共変、 `Func<T>` は反変と表現することができます。
TypeScript では基本的に共変の場合が多く、関数の引数のような場合で反変です。

## strictFunctionTypes

関数の引数は反変と説明しましたが、実は TypeScript のデフォルトでは、関数の引数は  **双変** です。
TypeScript 2.6 で追加されたオプションの `strictFunctionTypes` によって、反変になります。
`strict: true` で `strictFunctionTypes` もオンになるので、この記事を読んでいるような型に関心のある方なら問題ないと思いますが、注意が必要です。

もし、関数の引数は双変だったらどうなるでしょうか？

```typescript
const funcN: Func<number> = (x: number) => Math.abs(x)
const funcNS: Func<number | string> = funcN
funcNS("apple")
```

双変により、上記のような代入がエラーにならないため、`Math.abs("apple")` を実行してしまいます。
このように、関数の引数が反変でないと実行時にエラーが発生してしまう可能性があるため、`strictFunctionTypes` をオンにしておくことをオススメします。

# Optional Variance Annotations

最後に、TypeScript 4.7 で追加された構文 Optional Variance Annotations を紹介します。
名前の通り、変性(variance)に関する注釈が可能となります。

以下のように関数の引数のように反変な場合は `in` を、共変の場合は `out` を記載します。

```typescript
type Func<in T, out S> = (x: T) => S
```

Optional のため、あってもなくても型は変わりませんが、このように記載することで変性が明確になります。
また、誤った注釈をした場合にエラーを出してくれます。

```typescript
type Func<out T, in S> = (x: T) => S // => Error
//        ^^^^^  ^^^^
// Type 'Func<sub-T, S>' is not assignable to type 'Func<super-T, S>' as implied by variance annotation.
//   Types of parameters 'x' and 'x' are incompatible.
//      Type 'super-T' is not assignable to type 'sub-T'.
// Type 'Func<T, super-S>' is not assignable to type 'Func<T, sub-S>' as implied by variance annotation.
//   Type 'super-S' is not assignable to type 'sub-S'.
```


# さいごに

最後まで読んでいただきありがとうございます！
さらに詳しい内容を聞きたい方は、Devトークで直接お話しましょう！

https://jobs.qiita.com/employers/qiita-inc/dev_talks/444

# 参考文献

- 部分型
    - [構造的部分型 (structural subtyping) _ TypeScript入門『サバイバルTypeScript』](https://typescriptbook.jp/reference/values-types-variables/structural-subtyping)
    - [型システム入門 第15章 部分型付け](https://www.ohmsha.co.jp/book/9784274069116/)
- 変性
    - [TypeScript における変性（variance）について - 30歳からのプログラミング](https://numb86-tech.hatenablog.com/entry/2020/07/04/095737)
    - [配列の共変性 (covariance) _ TypeScript入門『サバイバルTypeScript』](https://typescriptbook.jp/reference/values-types-variables/array/array-type-is-covariant)
    - [型の互換性 - TypeScript Deep Dive 日本語版](https://typescript-jp.gitbook.io/deep-dive/type-system/type-compatibility#bariansuvariance)
    - [共変性と反変性 (計算機科学) - Wikipedia](https://ja.wikipedia.org/wiki/%E5%85%B1%E5%A4%89%E6%80%A7%E3%81%A8%E5%8F%8D%E5%A4%89%E6%80%A7_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%A6))
- strictFunctionTypes
    - [TypeScript_ TSConfig Reference - Docs on every TSConfig option](https://www.typescriptlang.org/tsconfig#strictFunctionTypes)
    - [strictFunctionTypes _ TypeScript入門『サバイバルTypeScript』](https://typescriptbook.jp/reference/tsconfig/strictfunctiontypes)
- Optional Variance Annotations
    - [Announcing TypeScript 4.7 - TypeScript](https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#optional-variance-annotations-for-type-parameters)
    - [『プロを目指す人のためのTypeScript入門』読者が最新情報にキャッチアップできる記事](https://zenn.dev/uhyo/articles/blueberry-book-catchup#%E5%9E%8B%E5%BC%95%E6%95%B0%E3%81%AB%E5%AF%BE%E3%81%99%E3%82%8B%E5%A4%89%E6%80%A7%E3%82%A2%E3%83%8E%E3%83%86%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)
    - [Optional Variance Annotations の挙動メモ - Object.create(null)](https://susisu.hatenablog.com/entry/2022/06/02/024923)

[^1]: 不変, 非変は同じ性質を指す単語だと思うのですが、サイトで表記異なるため両方書きました。参考: https://togetter.com/li/66427