TypeScriptの型推論詳説

TypeScriptはJavaScriptに静的型を追加した言語で、コンパイルエラーを検出することでJavaScript開発をさらに快適・効率的にしてくれるものです。

型システムを備えている言語は、多かれ少なかれ何らかの形で**型推論**を備えています。大ざっぱに言えば、これは型を明示的に書かなくてもコンパイラがいい感じに型を推測して理解してくれる機能です。型推論は静的型付き言語における花型機能のひとつと言ってもよく、色々な言語がそれぞれの特色を持っています。

この記事では**TypeScript**における型推論について詳説します。この記事に書いてあることは、TypeScriptを普段から書いている方ならなんとなく理解している内容が多いと思います。しかし、これらの内容がちゃんと言語化されている記事がいまいち見当たらなかったので今回記事を書くことにしました。

※ この記事の内容は執筆時点の最新版のTypeScript (3.5 RC) に基づいています。TypeScriptの型推論は破壊的変更が比較的起こりやすい部分であり、最新版のTypeScriptではこの記事に書いてある通りの動作をしない可能性があります。

※ 解説の内容は`--strict`オプションを指定した状態を前提としています。このオプションを指定していない場合の挙動はこの記事と異なるところが結構あります。

## TypeScriptの型推論の概要

TypeScriptにおける型推論の基本は**式の型の推論**です。それに付随して、**変数の型の推論**や**関数の型引数の推論**、また**オーバーロードの解決**などが発生します。推論という言葉を聞き慣れない方もいるかもしれませんが、これは必要な情報を何らかの方法で推測し決定することです。推論というのは普通は当てずっぽうではなく、ちゃんと何らかの論理的な方法で決定されます。TypeScriptがどういう方法で型推論を行うのかというのがこの記事のテーマですね。

前提として、JavaScript (TypeScript)は**文**と**式**という基本的な概念を持ちます。この2つの違いは、**式**は評価結果の値があるという点です。

TypeScriptでは（もっと言えば、ほとんどの静的型付き言語では）全ての**式**に対してその式の**型**が決定されます。この処理こそが**型推論**の本体であると言ってもよいでしょう。

非常に簡単な例で説明します。

```ts
const a = 2 + 3;
```

このプログラムにおいて、変数`a`の型はどうなるでしょうか。それは、`a`に代入されている`2 + 3`という**式**の型をTypeScriptがどう推論するかによって決められます。結論から言えば、`a`の型は`number`です。つまり、`2 + 3`という式の評価結果は`number`型の値、すなわち数値になるであろうとTypeScriptが推論したことになります。この推論は、「`2`や`3`は数値である」という事実と「`+`で数値同士を足した結果は数値である」という2つの事実から裏付けられます。

また、次の例ではどうでしょうか。

```ts
const a = x + 3;
```

変数`x`はあらかじめ定義されているとしましょう。この場合、`a`の型、つまり`x + 3`の型は`x`の中身によって決まります。もし`x`が`"123"`という文字列だった場合、JavaScriptの`+`演算子に文字列と数値を渡した場合は文字列の結合となりますから結果は`"1233"`となります。つまり、`x + 3`の型は`string`型です。一方で、もし`x`が`456`という数値だった場合、`+`は数値の加算として働きますから`x + 3`は`459`となります。よって、`x + 3`は`number`型です。

この例から分かることは、**式の型を推論するためにはその中に登場する変数の型が必要である**ということです。`x + 3`という式の型を推論するためにはまず`x`の型が分からないといけないのです。このことから、宣言された全ての変数に対してその型を推論する需要が発生します。型推論のそのほかの要素（型引数の推論など）も**その目的はすべて「式の型を推論する」ということに集約されます**。

では、TypeScriptの型推論の詳細に踏み入っていくことにしましょう。

## 式の型の推論 (1)

とりあえず、TypeScriptがどのように式の型を推論するのかを見ていきます。ここから先はいろいろな型が登場しますが、もしよく分からない型が出てきたら以下の記事を見てみるのがよいでしょう。

- [TypeScriptの型入門](https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a)

### プリミティブリテラルの型

JavaScript/TypeScriptプログラミングにおける最も基本的な要素は**リテラル**です。リテラルは何らかの値をプログラム上で表現する方法です。例えば`abcde`という文字列を表したい場合は`"abcde"`と書きますね。この`"abcde"`が**リテラル**（この場合は文字列リテラル）です。リテラルはプリミティブ値（真偽値、数値、文字列など）を作るリテラルとオブジェクトを作るリテラルに大別できますが、まず前者を見ていきます。

一般的なプログラミング言語では、`"abcde"`というリテラルは文字列を生み出すので、その型は文字列型（`string`型）に推論されるでしょう。同様に、`234`などの数値リテラルは`number`型、`true`や`false`は真偽値リテラルなので`boolean`型に推論されるというのが一般的な挙動です。

しかし、TypeScriptはそれとは少し違う挙動をします。その理由は**リテラル型**の存在です。

結論としては、TypeScriptでは**リテラルの型は対応するリテラル型に推論されます**。何を言っているのかよく分からない方のために、例を用いて解説していきます。

単純な例として、以下のプログラムで変数`a`の型はどうなるでしょうか。この問題はつまり、`"abcde"`というリテラルの型がどう推論されるかという問題と同じです。

```ts
const a = "abcde";
```

この場合は、`a`の型は**`"abcde"`型**になります。分かりにくいですが、`string`という名前の型があるのと同様に`"abcde"`という名前の型があるのです。この型は`"abcde"`という文字列のみが許される型であり、文字列型（`string`型）の部分型[^subtype]です。

[^subtype]: 部分型というのは、ざっくり言えばある型よりさらに条件が厳しい型のことです。`string`型は「文字列である」という条件を満たす値が許される型である一方、`"abcde"`型は「`"abcde"`である」という条件が必要です。`"abcde"`は文字列ですから、「`"abcde"`である」は「文字列である」の十分条件です。よって、`"abcde"`型の条件を満たす値は全て（といっても`"abcde"`だけですが）`string`型の条件を満たすことになります。つまり、`"abcde"`型の値は`string`型の値であると言うことができます。このとき、`"abcde"`は`string`の部分型であると言います。部分型関係に関しては、TypeScriptは**構造的部分型**を採用しているという点が特徴的です。

基本的に、型推論においてはより厳しい型が推論されるほうが（情報が増えるので）嬉しいです。`a`が`"abcde"`型を持つことによって、`a`は`"abcde"`という文字列であることが型システム上で保証されます。もし`a`が`string`型だった場合は「`a`は何らかの文字列である」という情報しか無いことにになり、余計な可能性が発生しているという点で情報が少ないと言えます。

これは文字列リテラルの例ですが、数値リテラル・真偽値リテラルについても同じです。例えば`3`というリテラルの型は`3`型であると推論され、`true`は`true`型であると推論されます。また、TypeScriptは`null`型（`null`という値のみが存在する型）も持っており、`null`というリテラルも`null`型に推論されます[^undefined]。

[^undefined]: `undefined`を忘れていると思う読者の方がいるかもしれませんが、実は`undefined`はリテラルではなく変数です。`undefined`型自体はちゃんとあります。

### 組み込み演算子の型推論 (1)

型推論の挙動というのは、言語に存在する各構文がどのようなルールで型を推論されるかによって決まります。ここまで解説したのはプリミティブリテラルに対するルールでしたね（オブジェクトを作るリテラルもあるのですが、都合上それはすこし後回しにします）。

JavaScriptには多くの演算子（特に数値計算関係）がありますので、それに対する型推論を見てみましょう。

#### 算術演算子

`-`や`*`などほとんどの数値演算は、まずそのオペランド（演算子の計算の対象となる値）が数値である、すなわち`number`型か`bigint`型である必要があります。そうでない場合は**型エラー**が発生します。ここまでの説明ではあまり強調してきませんでしたが、型がおかしい場合に型エラーを発生させるのはTypeScriptの型推論の目的のひとつでありたいへん重要です。

そして、オペランドが`number`型なら結果の型も`number`型となり、オペランドが`bigint`型なら結果の型も`bigint`型となります。これは`-`などの演算子の実際の挙動と一致していますね。まあ、型推論が実際の挙動と一致していなかったら型の意味がないのですが。

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

```ts
// a は number 型
const a = 123 - 45;
// b は bigint 型
const b = 56n - 789n;

// これは型エラー（オペランドが数値でないので）
const c = "1234" - {};
```

他に注意すべき点としては、オペランドの片方が`number`で片方が`bigint`の場合はエラーになるという点です。これは実際のJavaScriptで実行時エラーとなるためそれを防ぐ意味があります。

```ts
// これも型エラー
const d = 123n - 45;
```

また、たとえ結果があらかじめ分かっていたとしても**算術演算子の結果がリテラル型になることはありません**。上の変数`a`の例だと、`-`の左が`123`型で右が`45`型であることから`a`に入るのは`78`であるのは明らかですが、`a`の型は`78`型ではなく`number`型です。なお、`123`型や`45`型は`number`型の部分型であることに注意してください。これにより、上で説明した「両辺が`number`型の場合」が適用されるのです。

ここまでは`-`を用いて説明しましたが、他の算術演算子やビット演算もすべて同様です。

ちなみに、両辺が`any`型で実際の結果が`number`なのか`bigint`なのか分からない場合は、演算子の結果は`number`型に推論されます。もし`any`型の値が実際にはBigIntだった場合に実行時の値が型推論と矛盾してしまいますが、まあ`any`を使用した時点で型安全性は既に消し飛んでいるので大した問題ではありませんね。

#### `+`

一方、`+`だけは異なる挙動を示します。これは、`+`が数値の加算だけでなく文字列の連結にも使われることが理由です。`-`などと同様なのは、両オペランドが`number`型だと結果が`number`型となり、両辺が`bigint`型だと結果が`bigint`型になる点です。`number`と`bigint`型を組み合わせるのはやはりエラーです。

```ts
// a は number 型
const a = 123 + 456;
// b は bigint 型
const b = 1n + 2345n;
// これは型エラー
const c = 12 + 345n;
// こういうのも型エラー
const d = 123 + null;
```

その一方、**どちらか片方**のオペランドが`string`型だった場合は結果が`string`型になります。面白いのは、この場合はもう片方がどんな値だろうとエラーになりません（ただし`symbol`型のみ例外的にエラーとなります）。

```ts
// e は string 型
const e = "123" + "456";
// f も string 型
const f = "1234" + null;
// g も string 型
const g = {foo: 123} + "456";
// h も string 型
const h = "123" + function foo() { return 456; };
```

両辺とも文字列でない場合はこういう変な値を足すことは当然できません。

```ts
// これは型エラー
const i = {} + function foo() { return 456; };
```

また、`+`のオペランドに`any`型が来ている場合は、もう一方が`string`型であることが判明している場合は`+`の結果が`string`となり、そうでない場合は`+`の結果も`any`となります。

#### 単項演算子

単項演算子は数値用（`+`, `-`, `~`）、真偽値用（`!`）、そしてインクリメント/デクリメント（`++`、`--`）の3種類に大別できます。

数値用の単項演算子は「`null`や`undefined`以外は何でも受け付ける」という特性を持ち、結果は`bigint`型（オペランドが`bigint`型の場合）か`number`型（それ以外の場合）になります。この何でも受け付けるという特性は`+`などが他の型から数値へ変換する目的で使われることがあるからでしょう。

```ts
// a は number 型
const a = +"123";
// b も number 型
const b = -"456";
// c も number 型
const c = -{};
// これは型エラー
const d = +null;
// e は number 型
const e = +(3n as any);
```

ちなみに、負の数のリテラル型に対応できるようにだと思いますが、`+`や`-`の結果はリテラル型になることがあります。

```ts
// f は 123 型
const f = +123;
// g は number 型
const g = +(+123);
// h は -456 型
const h = -456;
// i は number 型
const i = -(456);
```

次に`!`に関しては、オペランドとして`null`や`undefined`型、`unknown`型を含めてどんな値でも受け付けることができます（これは`if`文の条件部分など、真偽値が期待される場面における共通の挙動です）。例外は`void`型であり、この型の値が与えられたときのみ型エラーとなります。

`!`演算子の結果の値は基本的には常に`boolean`型ですが、型情報から結果が明らかな場合は`true`型か`false`型が生成されることがあります。

```ts
const num: number = 123;
// a は boolean 型
const a = !num;
// b は true 型
const b = !0;
// c は false 型
const c = !123;
// d は boolean 型
const d = !(123 as unknown);
// e も boolean 型
const e = !(0 as any);
// f は false 型
const f = !{foo: 123};
// これは型エラー
const g = !(()=>{}());
```

インクリメント・デクリメントは算術二項演算子と同じ感じの処理になります。

他にも`&&`や`||`や`? :`などの説明が残っていますが、説明の都合上後回しにします。

## 変数の型の推論

このあたりで**変数の型**の推論を解説します。JavaScriptではいくつかの方法で変数を作ることができますが、その中でも型推論が重要になるのが**変数宣言**（`var`, `let`, `const`）で変数を作る場合です。利便性のために変数宣言で変数を作るときに型註釈を省略できるようになっており、その場合はTypeScriptがその変数の型を推論して自動的に決めてくれます。

変数の型推論における基本的な原則は、**変数の型は宣言時に決まる**（変数が宣言後にどのように使われるかは型推論において考慮されない）こと、そして**一度決まった変数の型は変わらない**ということです。これが原則ですが、実はこのあといくつか例外が出てきます（後で詳説しますがフロー情報を用いた推論がされる場合です）。それでも、宣言時に型が決まるというのはTypeScriptの基本的な言語デザインですから、そう思っておくのは悪いことではありません。

### `const`の推論

`const`の場合が一番話が簡単なので、まずそれを解説します。一応説明しておくと、JavaScriptにおいて`const`で作られた変数は再代入が不可能となります。変数の中に入っているオブジェクトをいじることに関しては制限はありません。その性質から、`const`で宣言する場合は必ず`const 変数 = 式;`という形で変数の中身を書く必要があります。`const 変数;`のように宣言だけしておいてあとから代入することはできません。

話は簡単で、**その変数に代入されている式の型推論結果が変数の型となります**。

```ts
// a は 123 型
const a = 123;
// b は number 型
const b = a + 456;
```

この例では、変数`a`に代入されているのは`123`という式で、この式の型推論結果は`123`型です。よって、`a`の型は`123`型となります。また、変数`b`に代入されている`a + 456`という式の型推論結果は`number`型です。よって、`b`の型は`number`型となるのです。

#### 型註釈がある場合

TypeScriptでは、変数宣言はこのように変数の型を型註釈として明示的に宣言することができます。

```ts
// 変数 a の型は number 型になる
const a: number = 3;

// これはエラー（宣言に反する値を代入しているので）
const b: number = "foo" + "bar";
```

このように変数の型が宣言されている場合、TypeScriptはその変数に代入されている値が正しいかどうかを型検査します。すなわち、`=`の右の式の型を推論して、その型が変数の型に代入可能かを調べます。上の変数`a`の例では`=`の右は`3`という式であり、この式の型は`3`型と推論されます。`3`型は`number`型の部分型なので、`3`型の式は変数`a`に代入することができます。よって、変数`a`の宣言ではエラーは起きませんでした。一方の変数`b`の場合、式`"foo" + "bar"`の型を推論すると`string`型になります。`string`型は`number`型に代入可能ではありませんから、ここで型エラーが発生します。

また、変数の型は原則として型註釈がそのまま採用されます。というのも、上の例の変数`a`などは型註釈が`number`であるとはいえ実際は`3`であることが分かっているので、`3`型に変えてしまう手もありそうですね。しかし、TypeScriptが勝手にそのような変更を加えることはありません。

なお、もう慣れてきたかと思いますが、“原則として”ということはやはり例外もあります。それは型註釈がunion型の場合ですが、これについては詳しくは後述します。

### `var`や`let`の推論

`var`と`let`も、型推論上の基本的な扱いはほぼ同様です。この2つの特徴は`const`とは異なり**再代入**があることです。つまり、最初に宣言された後も他の値が変数に代入される可能性があります。

とはいえ、基本は`const`の場合と同じで、**宣言時に代入された式の型が変数の型となります**。とりあえずこの例を見てください。

```ts
// a の型は number 型
var a = 123 - 456;
// b の型は string 型
let b = "foo" + "bar";
```

`123 - 456`という式の型は`number`型なので、変数`a`の型は`number`型となります。同様に変数`b`の型は`string`型となります。これは`const`の場合と同じで分かりやすいですね。

#### リテラル型のwidening

お察しとは想いますが、上の例ではわざと簡単な式を避けていました。次のような場合、`var`や`let`の結果は`const`とは異なるものになります。

```ts
// a の型は number 型
var a = 123;
// b の型は string 型
let b = "foobar";

// a は number 型なので別の数値を代入可能
a = 456;
// b も同様
b = "aiu";
```

`const`とは異なり、`var`や`let`に代入された式がリテラル型の場合はそれらはリテラル型ではない型に変換されます。上の例では、`123`型は`number`型に、`"foobar"`型は`string`型に変換されました。このように、リテラル型からそれに対応するプリミティブ型に変換される挙動は**widening**（型の拡張？）と呼ばれます。

このwideningによって、変数に別の値を再代入することができるようになっています。もし変数`a`の型が`number`型ではなく`123`型に推論されていたら、`a = 456`は型エラーとなってしまい大変不便です（`456`型の値は`123`型に当てはまらないため）。

#### 型註釈がある場合

`var`や`let`の場合でも変数宣言に型註釈をつけることが可能です。この場合の挙動は`const`と同じで、変数の型はその型註釈が尊重されます。もちろんこれはwideningよりも優先されますから、次のようなことが可能です。

```ts
// 変数 a は 123 型
let a: 123 = 123;

a = 456; // 456 型の値を 123 型の変数に代入できないのでエラー
```

`var`や`let`に対する型註釈は、さらに広い型を変数につけたい場合も有用です。例えば数値も文字列も入れることができる変数を作りたい場合は、`number | string`型の変数を作るとよいでしょう。この`|`を用いた記法は**union型**の記法で、これは`number`型でも`string`型でもいいということになります。

```ts
let a: number | string = 456;

a = "foo";
```

#### 宣言時に初期化しない場合（型註釈あり）

JavaScriptでは、`var`や`let`で変数を宣言だけして宣言時に何も代入しないことも可能です。TypeScriptもこれをサポートしています。

```ts
// number 型の変数 a を宣言だけする
let a: number;

if (Math.random() < 0.5) {
  a = 123;
} else {
  a = 0;
}

console.log(a);
```

この場合、型註釈にある通り変数`a`の型は`number`型です。`a`は宣言された直後の`if`文の中で数値が代入されますから、実際`a`には数値が入っていることになりこれは正しいですね。

しかし、`a`に何も入っていない所が一瞬あってちょっと怪しい感じがします。JavaScriptでは宣言だけして何も代入していない変数は`undefined`が入っていますから、`if`文の前では`a`に`undefined`が入っていて型に従っていないような気がしますね。

TypeScriptはこの問題にちゃんと対処してくれる機構を持っています。それは、「宣言後に初期化されていない変数を使おうとしたらエラーを出す」という機構です。この例では`if`文の前に`a`を使おうとするとエラーになります。

```ts
// number 型の変数 a を宣言だけする
let a: number;

console.log(a); // これはエラー（a に値を代入する前に使用しているので）

if (Math.random() < 0.5) {
  a = 123;
} else {
  a = 0;
}

console.log(a);
```

また、`a`に値が代入されていない可能性があるだけでもやはりエラーです。

```ts
// number 型の変数 a を宣言だけする
let a: number;

if (Math.random() < 0.5) {
  a = 123;
}

// これはエラー（aに何も代入されていない可能性があるので）
console.log(a);
```

ちなみに、変数`a`の型を`undefined`を許すように宣言することで、何も代入しないで使うことができます。気がきく仕様ですね。

```ts
let a: number | undefined;

if (Math.random() < 0.5) {
    a = 123;
}

// エラーにならずにaを使える
console.log(a);
```

この機構により安心して変数を初期化無しで宣言することができます。コードの可読性の観点からは、初期化無しで変数を宣言するときは型を明示した方が良いですね。

しかし実は、型註釈も無し、初期化も無しという宣言も許されています。その場合、TypeScriptが急に本気を出します。それが次の話題です。

#### 宣言時に初期化しない場合（型註釈なし）

`var`や`let`が型註釈無し・初期値無しで宣言されている場合、その変数に対しては**代入されるとそれに応じて型が変わる**という挙動が有効になります。これは先に説明した「変数の型は宣言時に決まる」の**例外**となる挙動ですが、主にJSの型チェックにおいて必要となるため実装されています。

ただ、先に述べておきますが、この機能は微妙に危険なので**新たにTypeScriptコードを描く場合は利用を避けるべき**です。ちゃんと型註釈くらい書きましょう。

では例に移ります。

```ts
let a;

// ここでは a は undefined 型
console.log(a);
const u: undefined = a;

if (Math.random() < 0.5) {
  a = 123;
} else {
  a = 0;
}

// ここで a は number 型
const num: number = a;
console.log(a);
```

このコードでは変数`a`を宣言したあと、いきなり`a`を使用しています。これはエラーにはならず、ここでは`a`は`undefined`型の変数として扱われます。`a`は宣言された後に触られていないため、初期値の`undefined`が入っていると判断されて`undefined`型になるのです。

次がポイントです。次の`if`文では`a`に数値が代入されていますが、これによりその後で`a`が`number`型に変化しています。

このように、**型註釈無し・初期値無しで宣言された変数は現在の型と異なる値を代入することができ、それに合わせて変数の型が変わる**のです。

次の例のように、必要に応じてunion型が発生することもあります。

```ts
let a;

if (Math.random() < 0.5) {
    a = 123;
}

// a は number | undefined 型
console.log(a);
```

この挙動は便利に見えますが、変数の型がころころと変わる（＝変数が複数の意味を持つ）のはあまり良いことではありません。初期値をちゃんと指定するか、それができない場合は型を明示するのが良いでしょう。このどちらかをすることで、後から異なる型を代入するのは型エラーとなります。

さらに、この機能に関してはTypeScriptが推論できる範囲にも限界があります。特に**関数呼び出し**が関わる場合が問題で、この場合は**誤った推論結果**となる可能性があります。

```ts
let a;

// ここでは a は undefined 型
console.log(a);
const u: undefined = a;

change_a();

// ここで a は undefined 型
const num: undefined = a;
console.log(a); // "123" が表示される

function change_a() {
    a = "123";
}
```

`change_a()`関数は`let`で宣言された変数`a`に文字列を代入しています。しかし、`change_a()`を呼び出す側のコードではその事実が無視され、`change_a()`を呼び出した後も変数`a`の型は`undefined`型となっています。TypeScriptが関数呼び出しの中までは見に行かない上になぜかその場合も自信を失わないため、このような誤った推論が発生してしまいます。

これは安全性と利便性を天秤にかけた結果このような挙動になっているのだと思いますが、危険なことには変わりませんからあまり積極的に使うべきではありません。

まあ、実際のところ宣言される変数のほとんどは`const`であり、`let`で変数を宣言することは多くありません。そのレアケースを突破した上にさらにその変数を関数の中から書き換えるという**シェフの気まぐれパスタ**みたいなコードが生み出されることは稀でしょうから、頭の片隅に置いておけば大丈夫だと思います。

#### 余談：`--noImplicitAny`が無しの場合の挙動

上記の「型註釈も初期値もない変数」の型が代入された値に応じて変化するという挙動は、実は`--noImplicitAny`コンパイラオプションが有効のときの挙動です。もしこの`--noImplicitAny`オプションが無い場合は話はもっと単純で、**そのような変数の型は`any`型となります**。

どっちにせよ危険な挙動ですから、やらなければ問題ありませんね。

#### 余談2：型註釈がunion型の場合の挙動

型註釈ありで変数を宣言して、その型註釈がunion型だった場合の挙動は少し特殊です。個人的にはこの挙動は余計なお世話に思えますが、知らないとたまに困りそうなので解説しておきます。

変数の型註釈がunion型の場合で実際の中身がその中のどれに当てはまるのか明らかな場合、註釈を無視して変数の型がそれに固定されます。言葉ではいまいち伝わりにくいと思いますので例を見てください。

```ts
const stringOrNull: string | null = "abcde";

// stringOrNull は string | null 型ではなく string 型になる
// よって length プロパティを見たりしてもエラーが起きない
console.log(stringOrNull.length);
```

この例では、変数`stringOrNull`に`string | null`型という型註釈を付けたにも関わらず、実際の型は`string`型になっています。まあ確かに、変数の宣言を見れば実際は`stringOrNull`に`null`が入ったりしないのは一目瞭然ですから、この挙動は分からないでもありません。個人的には型註釈を勝手に無視しないで欲しいですが。

また、`let`（や`var`）で宣言した場合も同様の事象が発生します。

```ts
let stringOrNull: string | null = "abcde";

// ここでは stringOrNull は string 型
console.log(stringOrNull.length);
```

しかもややこしいことに、この場合は`string | null`という型註釈が完全に死んだわけではありません。`stringOrNull`は最初は`string`型ですが、これに`null`型の値や`string | null`型の値を代入したりできるのです。

```ts
let stringOrNull: string | null = "abcde";

// stringOrNull は string | null 型ではなく string 型になる
// よって length プロパティを見たりしてもエラーが起きない
console.log(stringOrNull.length);

// stringOrNull は string 型だが実は null を代入してもよい
stringOrNull = null;

// ここでは stringOrNull の型は null に変わっているのでこれはエラー
console.log(stringOrNull.length)

// 文字列を代入すると string 型に戻せる
stringOrNull = "foobar";

// これはOKになる
console.log(stringOrNull.length);

// string | null に当てはまらないものは代入できない
// のでこれはエラー
stringOrNull = 123;
```

このように変数に代入すると型が変わるという挙動は先程説明した「`let`で型註釈も初期値もない場合」と同じですね。今回は型註釈を何も付けない代わりにunion型がついており、その結果代入に制限がかかるようになりました。ある意味で、変数が「読み出し時の型」と「代入時の型」を2つを持っているようなイメージです。実は先程の場合は「代入時の型」が`any`であり何でも書き込み可能という扱いになっています。

そして、例は省略しますがこの場合も「関数を呼び出してその中で変数を書き換える場合は安全性が壊れる」という問題がそのまま残っています。前の場合は`let`に型註釈をつけないことが条件だったのでやる機会が少なそうでしたが、`let`の型註釈にunion型を書くというのはわりと機会がありそうです。そんなコードを見かけたら警戒レベルを1段階上げたほうがいいかもしれません。

## 式の型の推論 (2)

さて、結構長いこと説明してきましたが、ここまで登場した型は`string`型などのプリミティブ型や`"foo"`などのリテラル型だけでいまいち面白くないですね。ここからはやっと**オブジェクト型**が登場します。

一瞬だけ復習しましょう。オブジェクト型というのは次のような記法を持つ型です。

```ts
// number 型のプロパティ foo と string 型のプロパティ bar を持つオブジェクトの型
{
  foo: string;
  bar: number;
}
```

このようにオブジェクト型の中には`プロパティ名: 型`というペアを書くことができます。この他にもインデックスシグネチャや関数シグネチャなどもありますが詳細は[型入門](https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a)に譲ります。

JavaScript・TypeScriptにあまり馴染みがない方のためにちょっと補足しておくと、オブジェクト型において「型名」はあまり重要ではありません。これはクラスベースのオブジェクト指向言語とは対照的です。

クラスベースオブジェクト指向言語の多くは、オブジェクトは「クラス」から作られます。これに伴い、オブジェクトの型はクラスの名前によって表現されます。例えば`class Person {}`というクラスがあった場合、`new Person()`という式は`Person`型になります。

しかし、TypeScriptのオブジェクトはクラスとは毛色が違います。オブジェクト型という概念はクラスとは何の関係もなく、例えば`{ foo: string; bar: number; }`自体がオブジェクトの型となります[^typescript_class]。次の例を通してこのことを理解しましょう。

[^typescript_class]: 一応JavaScriptにもクラスという概念はありますから、TypeScriptもクラスをサポートしています。実は`class Person {}`のような定義を書いた場合、TypeScriptではクラスと同時に`Person`型という型を定義したことになります。ただしこれは基本的には後述のような単なるオブジェクト型に`Person`という名前を勝手に付けてくれるだけであり、それゆえ`new Person()`とする以外にも`Person`型のオブジェクトを作る方法はあります。

```ts
type Hoge = {
  foo: string;
  bar: number;
}
type Fuga = {
  bar: number;
  foo: string;
}

// obj は Hoge 型
const obj: Hoge = {
  foo: "uwaaaa",
  bar: -5,
};
// obj はFuga 型でもある
const obj2: Fuga = obj;
```

ここで登場した`type`文は型に別名をつけることができる文です。ここでは`Hoge`型は`{ foo: string; bar: number; }`型の別名となり、`Fuga`型は`{ bar: number; foo: string; }`型の別名となります。

ここで気づいていただきたいことは、**`Hoge`型と`Fuga`型は同じ型である**ということです。この2つはどちらも`{ foo: string; bar: number; }`型を指しています（プロパティの順番が違うのは関係ありません）。

`Hoge`と`Fuga`は全く同じ型です。型に別々の名前が付いているとかそういうことは全く関係ありません。よって、`Hoge`型の変数`obj`は当然`Fuga`型として扱うことができます（逆も同じ）。

これからオブジェクト型の話をするにあたって、**型にどんな名前が付いているかは関係ない**という事実はぜひ覚えておきましょう。当然ながら、このようにオブジェクト型に名前をつけずに使うことも可能です。この例では`{ foo: string; bar: number; }`型を変数`obj`の型註釈として用いています。

```ts
const obj: { foo: string; bar: number; } = {
  foo: "uwaaaaaaaa",
  bar: -8,
}:
```

### オブジェクトリテラルの型の推論

JavaScript・TypeScriptにおいてオブジェクトを作る主要な方法は**オブジェクトリテラル**を使うことです。オブジェクトリテラルも当然ひとつの式です。

```ts
const obj = {
  foo: "hello",
  bar: 123,
};
```

この例において変数`obj`の型はどうなるでしょうか。それはもちろん、`{ foo: "hello", bar: 123 }`という式の型がどのように推論されるかによって決まります。

実はこの場合、この式の型は`{ foo: string; bar: number }`型となります。このように、オブジェクトリテラルからはその形に基づいたオブジェクト型が推論されます。この推論は以下のルールで行われます。

- オブジェクトリテラルに対してはオブジェクト型（これを`R`とします）が推論されます。
- オブジェクトリテラルが持つ各プロパティ`prop: expr`に対して、推論結果のオブジェクト型`R`にもそのプロパティ`prop`が追加されます。
- `R`のプロパティ`prop`の型は式`expr`の型推論結果となります。ただし、この型は**wideningされます**。

先ほどの例に沿って確認しましょう。`{ foo: "hello", bar: 123 }`というオブジェクトリテラルは`foo`と`bar`という2つのプロパティを持ちますので、結果のオブジェクト型も`foo`と`bar`という2つのプロパティを持った型となります。

オブジェクトリテラルにおける`foo`プロパティの中身は`"hello"`という式であり、この式に対する型推論の結果は`"hello"`型です。これがwideningされて`string`型となるため結果のオブジェクト型の`foo`プロパティは`string`型となります。`bar`プロパティも同様に、`123`型がwideningされて`number`型となります。これにより、このオブジェクトリテラルに対する型推論の結果は`{ foo: string; bar: number; }`型となります。

プロパティの型がwideningされる理由は、`var`や`let`の場合と同じです。**後から中身を変更できるように**ということですね。JavaScriptのオブジェクトは（`Object.freeze`などを使って変更不可にしない限り）プロパティを後から書き換えることができますから、その需要に対応できるようにこの仕様になっています。wideningが嫌な場合は`let`のときと同様に型註釈を明示的に書くか、あるいは後述の`as const`を使いましょう。

### 配列リテラルの場合

JavaScriptでは配列を作るときに**配列リテラル**を使用できます。当然これに対しても型推論が働き、推論結果は**配列型**となります。配列型は`number[]`のように、要素の型が何かという情報を持っています。`number[]`は全ての要素が`number`型である配列です。

配列リテラルの型推論は、それに含まれる各要素に対して型推論を行い、それに対してwideningを行ったものが要素の型となります。例えば、下の例の配列`arr`は各要素に対する型推論の結果（`1`型、`234`型、`-56`型）をwideningした結果である`number`を要素とする配列の型、すなわち`number[]`型となります。

```ts
// arr の型は number[] 型
const arr = [1, 234, -56];

arr.push(789); // 数値を追加可能
arr.push("foobar"); // これはエラー
```

では、`[1, "foo", -56]`のように複数の型が入り混じった配列リテラルを書いた場合はどうなるでしょうか。この場合は、各要素の型のunion型が取られます。

```ts
// arr2 の型は (number | string)[] 型
const arr2 = [1, "foo", -56];
// 任意の数値や文字列を追加可能
arr2.push(789);
arr2.push("foobar");
```

例によって、これだと困る場合は型註釈の出番です。例えば、配列の要素として数値はOKだけど文字列は`"foo"`のみOKという場合はこんな型を書きましょう。

```ts
const arr: (number | "foo")[] = [1, "foo", -56];

arr.push(789); // これはOK
arr.push("foobar"); // これはエラー
```

### `as const`による推論の変化

TypeScript 3.4からは`as const`という構文が導入されています。これはリテラル（各種プリミティブのリテラル・オブジェクトリテラル・配列リテラル）に後置で付加することができるものです（他の構文にも付加可能ですが意味はありません）。`as const`が付加されたリテラルは、型推論の挙動が変化します。具体的には以下のような変化があります。

- リテラル型やオブジェクトのプロパティのwideningが発生しなくなります。
- オブジェクトリテラルに対する型推論の結果は各プロパティが`readonly`になります。
- 配列リテラルに対する型推論の結果は`readonly`タプル型となります。

また、この効果は`as const`が付加されたリテラルそのものだけでなく、その中にあるリテラルに対しても適用されます。つまり、ネストしたオブジェクト・配列リテラルに対して`as const`を付加した場合、その中の全てのリテラルが`as const`の適用対象となります。

`as const`の例はこんな感じです。

```ts
// 変数 a の型は string ではなく "foo"
let a = "foo" as const;

// 変数 b の型は { readonly foo: "foobar" }
const b = {
  foo: "foobar"
} as const;

// 変数 c の型は readonly ["foo", 123]
const c = ["foo", 123] as const;
```

基本的に`as const`を使用する主な目的はwideningを防ぐこととタプル型を簡単に作ることにあります。プロパティが`readonly`になる効果もありますが、そのために使う場面はそんなに多くないと思います。

### 論理演算子の型推論

`&&`と`||`は論理演算子として知られていますが、返り値が常に真偽値であるとは限らないという特徴を持ちます。これらの演算子は常に**オペランドのどちらかを返します**。`a && b`という式の場合、`a`を真偽値に変換して`true`の場合は`b`が、そうでない場合は`a`を返します。一方`a || b`という式の場合、`a`を真偽値に変換して`true`ならば`a`が、そうでない場合は`b`を返します。

これを踏まえて`a && b`や`a || b`の型推論の結果を考えてみましょう。ここで重要なのはこの式の評価結果として「`a`と`b`のどちらかが帰ってくる」という事実です。ここから考えると、これらの型の型推論結果は`(aの型) | (bの型)`というunion型になりそうですね。

これは基本的には正しいです。例えばこんな例で確かめられます。

```ts
const num: number = 123;

// or の型は number | "foobar" 型
const or = num || "foobar";
```

変数`num`が`number`型であることを踏まえて考えると、`num || "foobar"`は、`num`が`0`または`NaN`の場合は`"foobar"`を返し、それ以外の場合は`num`を返します。よって、`num || "foobar"`の型は`number | "foobar"`型となります。

余談ですが、`const`を`let`に変えると変数`or`の型は`number | string`に変わります。union型の中にもwideningが働くわけですね。

では、この場合も考えてみましょう。

```ts
const and = num && "foobar";
```

さっきと同様にこの場合の`and`の型は`number | "foobar"`となりそうですが、実は少し違います。この場合、`and`の型は`0 | "foobar"`型となります。この背景には先ほども述べたとおり`num && "foobar"`が`num`を返すのは`num`を真偽値に変換した結果が`false`になる場合のみであり、具体的には`0`と`NaN`しかないことがあります。`num`は`number`型といっても実際に数値が返ってくるのは`0`か`NaN`だけなのでTypeScriptは親切にも型を絞り込んでくれるのです。

残念なことに、**TypeScriptは`NaN`を知らない**（正確には`NaN`のリテラル型を持たない）という問題があります。今の説明だと型が`0 | NaN | "foobar"`などにならないといけませんが、実際は`NaN`の可能性は**闇に葬られています**[^nan_literal_type]。厳密な安全性を求めるならこれは危険な挙動なのですが、そもそも`NaN`がロジックに組み込まれている時点で異常事態なのでまあ大目に見てあげましょう。

[^nan_literal_type]: 一応TypeScriptチームの名誉のために述べておくと、これは[意図的な挙動](https://github.com/microsoft/TypeScript/pull/9407#issuecomment-229721835)のようです。

とにかく、`NaN`の可能性を忘れるならばこれは良い挙動ですね。上の例の変数`and`に入る可能性がある数値は`0`<font color="#ccc">（とNaN）</font>だけであることが型情報に現れています。

ほかに論理演算子がよく使われる場面は`null`かもしれない値を扱う場合です。ある変数にオブジェクトが入っているかもしれないし`null`が入っているかもしれないというような場合に`||`を使うことで`null`だった場合のデフォルト値を指定できます。この場合も型がいい感じに推論されます。

```ts
type MyObj = { foo: string };
const objOrNull: MyObj | null = null;

// obj は MyObj 型
const obj = objOrNull || { foo: "" };
```

この例で`||`の左辺は`objOrNull`、すなわち`MyObj | null`型の値ですが、もし`null`が入っている場合は`||`の右側の値が返る（`objOrNull`が返らない）ため、型推論の結果から`null`が排除されています。

このように、`||`や`&&`は結構直感通りに型推論を行ってくれて便利です。

その上、実はこいつらはさらに強力な推論能力を秘めています。それが次の話題です。

### 条件分岐による型の絞り込み

たまには天下り的に例を示してみます。次の例を見てください。

```ts
type MyObj = { foo: string; };

let objOrNull: MyObj | null;
if (Math.random() < 0.5) {
    objOrNull = { foo: "123" };
} else {
    objOrNull = null;
}

// fooの型は string | null
const foo = objOrNull && objOrNull.foo;
```

ポイントは一番下の`objOrNull && objOrNull.foo`という式です。`objOrNull`が`MyObj | null`型であることを鑑みるといきなり`objOrNull.foo`とするのは型エラーとなるのですが（`null`の可能性がある変数に対してプロパティアクセスすることはできないため）、`objOrNull && objOrNull.foo`とすればエラーは起きません。この理由は、`&&`の右側の式を型推論するときは`objOrNull`の型が`MyObj | null`から`MyObj`に変化しているからです。

このように、TypeScriptは特定の条件において変数の型が変化する挙動を持っています。この挙動は元の型よりも厳しい型（元の型の部分型）になることから**型の絞り込み** (*type narrowing*) と呼ばれています。

型の絞り込みが起きるのは**条件分岐**が発生した場合です。`&&`は「左側の値を真偽値に変換したら`true`のときのみ右側を評価する」ということですから、これもある種の条件分岐であると見なせます。`&&`の右側では、「左側の値を真偽値に変換したら`true`だった」という情報を使って良いわけです。

今回`&&`の左側は`objOrNull`でその型は`MyObj | null`でした。`null`は真偽値に変換すると`false`になる値ですから、`&&`の右側では「`objOrNull`が`null`である」という可能性は排除されています。ゆえに、`&&`の右側では`objOrNull`の型から`null`を除いて`MyObj`となるのです。

このような挙動は`&&`以外にも発生します。もちろん`||`も可能ですし、条件演算子`? :`も型の絞り込みをサポートしています。

さらに、条件分岐といえば`if`文ですから、`if`文も当然型の絞り込みができます。

```ts
type MyObj = { foo: string; };

let objOrNull: MyObj | null;
if (Math.random() < 0.5) {
    objOrNull = { foo: "123" };
} else {
    objOrNull = null;
}

if (objOrNull != null) {
  // この中では objOrNull は MyObj
  // なのでプロパティ foo を参照できる
  console.log(objOrNull.foo);
}
```

また、条件についても色々なものがサポートされています。直前の例では`objOrNull != null`という判定によって`objOrNull`が`null`である可能性を排除しました。他の例としては、`typeof value === "string"`という判定によって変数`value`の型が`string`以外である可能性を排除するといったことが可能です。

このような「`if`文の中でだけ型が変わる」のような挙動はフロー解析と呼ばれることもあります。TypeScriptのフロー解析は`return`文もサポートしているため、次のようなコードを書くこともできます。

```ts
type MyObj = { foo: string; };
function useMyObj(obj: MyObj | null) {
  if (obj == null) {
    // objがnullの場合は先にreturnする
    return;
  }
  // ここではobjはMyObj型なのでfooを見ることができる
  console.log(obj.foo);
}
```

TypeScriptではこのようなフロー解析を交えた型推論が便利で、特にunion型との相性が良い機能です。union型は型の表現力を大きく上げてくれるものですから積極的に使っていきましょう。

## 関数型の推論とcontextual types

この記事をここまで読んだ皆さんはだいぶ疲れているかと思いますが、まだ折り返し地点です。この記事で伝えたいことはまだまだあるのです。続きを読むのは明日とかでも大丈夫ですよ。（でもここまで読んでいいねと思ったらとりあえずいいねボタンを押していただけるとありがたいです。）

次のテーマは**関数**です。TypeScriptプログラミングにおいて関数は無くてはならない非常に頻出の概念であり、関数と型推論にまつわる色々な話題があります。

### 関数の型註釈・関数型

基本的な事実として、TypeScriptでは**関数宣言時に引数と返り値の型の型註釈を書くことができます**。型註釈を全部書いた場合はこんな感じの見た目になります。

```ts
function lengthOrDefault(str: string | null, defaultLength: number): number {
  return str != null ? str.length : defaultLength;
}
```

ここで宣言されている`lengthOrDefault`関数は引数が2つあり、1つ目の引数`str`の型が`string | null`であること、2つ目の引数`defaultLength`の型が`number`であること、そして返り値が`number`であることが型註釈によって明記されています。

また、関数宣言は**変数**を作ります。上の例の関数宣言は、関数オブジェクトが入った`lengthOrDefault`関数を作っていることになりますね。このような変数の型は**関数型**となります。具体的には、`lengthOrDefault`の型は`(str: string | null, defaultLength: number) => number`型ですね。ここまでは型推論とはそんなに関係ないただの関数型の復習でした。

なお、ここでは`function`による関数宣言の場合を示しましたが、`function`式やアロー関数式なども同じです。

### 戻り値の型の推論

実は、関数の**戻り値**の型註釈を省略した場合は戻り値の型が**推論されます**。この推論は、当然ながら関数から`return`されている式の型を推論することで行われます。複数の`return`文がある場合は、どの`return`文からも値が返される可能性があるということで、それぞれの返り値の型のunion型をとったものになります。具体例はこんな感じです。

```ts
// foo は (num: number) => "hoge" | "fuga" 型
function foo(num: number) {
  if (num >= 0) {
    return "hoge";
  } else {
    return "fuga";
  }
}
```

この例では`return`文が2つあり、一方では返り値の型が`"hoge"`型、もう一方では`"fuga"`型ということで、それらのunionを取った`"hoge" | "fuga"`型が`foo`の返り値の型となります。

また、`return`文が無い関数の場合は返り値の型として`void`型が推論されます。一方、「`return`文はあるけど`return`せずに関数が終了する場合がある」という場合は`return`しない部分は`undefined`型を返すものとして推論されます。下の例の関数は、`num >= 0`のときは`"foo"`を返しますがそうでない場合は何も`return`しません。JavaScriptでは`return`しない関数は`undefined`を返した扱いになりますので、TypeScriptもそれに合わせる形で「この関数は`undefined`を返す可能性がある」という推論を行い、それにより`undefined`型とのunion型が生成されます。

```ts
// fooの返り値は "hoge" | undefined 型
function foo(num: number) {
    if (num >= 0) {
        return "hoge";
    }
}
```

こういう時は勝手に`undefined`を推論するんじゃなくてエラーにしてほしいという場合は`--noImplicitReturns`オプションを使いましょう。これは`--strict`に含まれていませんので別途指定が必要です。

### 引数の型の推論

このように関数の返り値の型は推論できましたが、**引数**の型についてはそうもいきません。次のように関数の引数の型註釈を省略した場合はエラーとなります（もし`--noImplicitAny`を使用していない場合はエラーにはなりませんが、その場合は引数の型が`any`に推論されます）。

```ts
// これは num に型註釈が無いのでエラー
function foo(num) {
  if (num >= 0) {
    return "hoge";
  }
}

foo(123);
```

人間が見れば`num`の型はきっと`number`型だろうなあという気持ちになりますが、TypeScriptはそういう推論はしてくれません。これは言語設計上の意思決定であり、TypeScriptの型推論アルゴリズムを簡単に（それでも型システム自体が複雑なせいでアルゴリズムも結構こんがらがっていますが）するためにこのような制限がかかっています。

まず、関数の中で引数がどう使われているかは見られません。今回は`num >= 0`とあるので`num`が`number`型のような気がしますが、その情報は使われません。これはもちろん、関数の中まで見て引数の型を決めると型推論が複雑化するし遅くなるからです。逆に、関数型言語では関数の中まで見て引数の型を推論するという挙動が多く見られますから、そういう挙動が好きな方は関数型言語をやるとよいでしょう。

また、関数の外にも`foo(123)`という呼び出しがありますが、これを見て引数`num`の型が推論されるようなことも**ありません**。これは「変数の型は宣言時に決まる」という原則により説明できます。それに、いちいちプログラム全体を探索して`foo`の呼び出しを探してから`num`の型を決定するというような挙動はパフォーマンス的に無理があります。TypeScriptでは基本的に“上から下”[^top_to_bottom]にワンパスで型推論を行い前から順番に型を決めていくのです。

[^top_to_bottom]: 関数宣言はJavaScriptの仕様でhoistingされるので先に処理されたりしますが。

余談ですが、引数の型が推論されないというのは型推論時の話であり、TypeScriptが提供するquickfix（型エラーの修正をサジェストする機能）では実は「関数の使われ方を見て引数の型を自動で決める」という機能があります。例えばVSCodeではこれは以下のように使います。

引数の型エラーの部分でquickfixから「使用状況からパラメータの型を推論する」を選択します。

[![スクリーンショット：型エラーの部分でquickfixから「使用状況からパラメータの型を推論する」を選択](https://gyazo.com/553f06951080cc6a8132e8358081ebc5/raw)](https://gyazo.com/553f06951080cc6a8132e8358081ebc5)

すると、`foo(123)`から`num`の型が類推されて`number`になりました。

[![スクリーンショット：quickfix使用後](https://gyazo.com/2b51d79e59f30d2a500c483f08336868/raw)](https://gyazo.com/2b51d79e59f30d2a500c483f08336868)

これは、型検査に組み込むほど堅牢ではないけどエラーの修正を補助するには便利なので提供されていると考えられます。

### contextual type による引数型の推論

話を戻しますが、とにかく関数の引数はちゃんと型註釈で型を宣言しなければならないというのがTypeScriptの基本的な考え方です。特に、関数の宣言そのものだけから型を決める必要があり、関数の中身とか関数の呼び出し側などそれ以外の部分の情報は使わないようになっています。

これを逆の視点から見ると、**関数宣言そのものだけから型が決まりさえすれば無理に引数に型註釈を書かなくて良い**ということになります。これが、「関数の引数は型註釈を書かなければいけない」の例外となります。では、引数の型註釈がなくても引数の型が分かる場合というのはどういう場合でしょうか。

それは、**contextual type**がある場合です。contextual typeを日本語で説明すると「文脈から分かる型」という感じでしょうか。今回も例で説明します。

```ts
type Func = (arg: number) => number;

const double: Func = function(num) {
  // 引数 num の型は number 型と推論されている
  return num * 2;
};
```

これは変数`double`に代入されている`function`式の引数`num`に型註釈が無いにも関わらず、エラーになりません。その理由は、変数`double`に型註釈`Func`があるからです。この型註釈によって、「`double`に代入される値は`Func`型でなければならない」という情報が生まれます。`double`に代入されるのは`=`の右側の値、すなわち`function(num) { return num * 2; }`ですから、この式を型推論するときは`function(num) { return num * 2; }`は`Func`型でなければならないという条件が与えられていることになります。このように、型推論の時点で期待される型があらかじめ分かっている場合を**contextual type**があると言います。

contextual typeがある場合は式の型推論の結果がcontextual typeと矛盾しないかチェックすることになりますが、contextual typeの役目はそれだけではありません。そう、**関数式の引数の型を推論するときにcontextual typeを使用することができる**のです。

今回の場合、`function(num) { return num * 2; }`の型は`Func`であるというcontextual typeがあります。`Func`型というのは引数が`number`型の関数ですから、それに合致させるためには当然引数`num`は`number`型でなければいけません。よって、引数`num`の型が`number`型と推論されるのです。

contextual typeはいくつかの場合に発生します。今しがた見たのは、変数の型註釈によって型推論中の式の型があらかじめ分かっている場合でした。これの応用として、ネストしたオブジェクトでも大丈夫です。

```ts
interface MyObj {
  foo: (arg: number) => void;
}
const obj: MyObj = {
  // ↓この num の型は number 型に推論される
  foo: num => console.log(num * 2),
}
```

この場合は、`obj`に代入されている`{ foo: num => console.log(num * 2) }`のcontextual typeが`MyObj`型です。よって、その`foo`プロパティである`num => console.log(num * 2)`という式のcontextual typeは`MyObj`の`foo`プロパティの型、すなわち`(arg: number) => void`となるのです。

#### 関数引数のcontextual type

もう一つcontextual typeが発生する重要な例は、**関数引数の場合**です。

```ts
function callWith100(callback: (arg: number) => void) {
  callback(100);
}

callWith100(num => {
  // ここでは num は number 型
  console.log(num * 10);
});
```

`callWith100`は引数として`(arg: number) => void`型の値を受け取る関数です。ということは、`callWith100`を呼び出すときの引数の型は当然この型でなければいけません。ここでcontextual typeが発生しています。

つまり、`num => { console.log(num * 10); }`は`(arg: number) => void`というcontextual typeを伴って型推論されるため、引数`num`が`number`型と推論できるのです。

この挙動は便利ですが、一つ注意しなければいけない点がありますね。それは、一度コールバック関数を別の変数に入れようとすると上手くいかないということです。

```ts
function callWith100(callback: (arg: number) => void) {
  callback(100);
}
// ↓これは num の型を推論できないのでエラー
const func = num => {
  // ここでは num は number 型
  console.log(num * 10);
};

callWith100(func);
```

この例では`callWith100`の引数を一度変数`func`に入れるように変更しただけですが、エラーが発生しています。これは、`num => { console.log(num * 10); };`が作られた瞬間には変数に入れられるだけでその後`func`がどう使われるか分からないためcontextual typeが無いということで説明できます。それくらい推論してくれてもいいじゃんと思わないでもないですが、[過去にあったそのような提案は却下されています](https://github.com/microsoft/TypeScript/issues/30017)。

#### 関数返り値のcontextual type

他にcontextual typeが発生する場面として、関数の返り値が型註釈で指定されている場合です。例はこういう感じですね。

```ts
type Func = (arg: number) => number;

function makeFunc(val: number): Func {
  // ↓引数numはnumber型に推論される
  return num => num + val;
}
```

この例では、関数`makeFunc`の返り値の型が`Func`とされています。よって、`return`文で返される式にはcontextual typeが働きます。これにより返される関数の引数`num`の型を書かなくても良くなっているのです。

この3種類がとりあえず思いつくcontextual typeの出現ポイントです。他にもあるかもしれませんが、思いつかないので省略します。原則としては、まあ面倒な推論をしなくてもその場で型が明らかに分かりそうなときにcontextual typeがつきます。

## 関数呼び出しの型推論

TypeScriptの型推論において中々ややこしいことが起こりがちなのが**関数呼び出し**です。

とはいえ、普通の関数呼び出しは特に難しいことはありません。例えば下の例では関数`func`を引数`123`で呼び出しています。

```ts
const func = (num: number) => String(num * 2);

const str = func(123);
```

TypeScriptの「原則として関数の型は宣言時に決まる」という性質から、`func(123)`という式を型推論する段階では`func`の型は判明済みのはずです。それゆえ、やるべきことはまず`func`がちゃんと関数型かどうかをチェックし、引数の式（`123`）の型を（contextual type付きで）型推論して、成功したら`func(123)`という式の結果をすでに判明済みの`func`の型の返り値部分にすればいいわけです。

しかし、話がそう簡単には進まない場合があります。ひとつは、**型引数がある関数の場合**です。

### 型引数がある関数の場合

型引数というのは、要するに**ジェネリクス**のことです。次の例を使って説明することにします。

```ts
function apply<T, R>(value: T, func: (arg: T) => R): R {
  return func(value);
}

// res は string 型
const res = apply(100, num => String(num ** 2));
```

関数`apply`は2つの型引数`T`, `R`を持つものとして宣言されています。関数宣言時には`T`や`R`はどんな型でもOK（`extends`で制限を付けることできますが）なものとして扱われます。つまり、`T`や`R`が具体的にどんな型かは、`apply`を呼び出すたびにその場で決められます。

#### 型引数を明示する場合

`T`や`R`が何かを明示するために、呼び出し時に`<>`で型引数を明示することができます。上の例の型引数を省略せずに書くと`apply<number, string>(100, num => String(num ** 2))`となります。`T`として`number`型を、`R`として`string`型を選んだということになります。型引数が明示されている場合は`apply`の型が完全に判明しているので特に型推論で難しい点はありません。

#### 型引数の推論

問題は、関数呼び出し時に型引数の指定を省略できる点です。この場合は、**`T`や`R`といった型引数も一緒に推論されます**。これがどのように行なわれるかというと、**引数から推論されます**。

例えば、`apply`の型引数`T`はどのように推論されるでしょうか。この場合は第1引数にヒントがあります。第1引数`value`の型は`T`ですが、呼び出し側ではこの引数に`100`という値を渡しています。これにより、`T`は`100`を受け付ける型であることが分かり、`number`型が推論されます。

ポイントは、**関数の型を推論するのに引数の型が使用されている**ということです。引数の`100`という式に対して先に型推論を行い、それによって型引数`T`の値を決定することによって関数の型が判明するのです。

#### 型引数の推論とcontextual typing

ところで、`apply`の第2引数`num => String(num ** 2)`は引数`num`の型註釈を書いていませんが、型情報を見ると`num`の型は`number`型と正しく推論されています。これはもちろんcontextual typingのおかげです。

しかし、よく考えるとおかしいのではないでしょうか。今回の場合、`num`が`number`型であると推論されるためには先に型引数`T`の型が`number`であると判明している必要があります。そのためには引数の型推論が既に終わっている必要があります。しかし、第2引数にはcontextual typeが必要であり、そのためには引数の型推論よりも先に型変数の推論ができていないといけません。引数の型推論と型引数の推論、一体どちらが先なのでしょうか。

この問題を解決するためにTypeScriptはひと工夫しています。それは**contextual typingが必要な引数だけ後回しにする**というものです。つまり、`apply(100, num => String(num ** 2))`という式の場合TypeScriptは次のステップを踏んで型推論を行います。

1. contextual typingが不要な引数を先に型推論する。ここでは、引数`100`に対して型推論を行って`number`型を得る。
2. 得られた情報から型引数を推論する。型変数`T`が`number`に推論される。
3. contextual typingが必要な引数を型推論する。引数`num => String(num ** 2)`に対して型推論を行って`(num: number) => string`型を得る。このときcontextual typeは`(arg: T) => R`型だが、`T`はすでに判明しているので`(arg: number) => R`となる。
4. 再び型引数を推論する。型引数`R`が`string`に推論される。

このようなステップで型引数の推論とcontextual typingによる引数の型推論を両立しています。型引数`T`と`R`の推論結果が決まるタイミングが異なることに注目してください。

より正確に言えば、型引数の推論結果は、**その型引数が使われた時点で確定します**。よって、`T`が最終的に確定する瞬間は実はステップ3内です。上記のようにcontextual typeは`(arg: T) => R`ですが、これを使うと第2引数の`num => String(num ** 2)`の部分で`num`の引数が`T`と推論されます。この時点で`T`の型が具体的に何なのか知る必要が発生したため、`T`の型が確定されます（これまでの情報から`number`になります）。

一方、第2引数の型推論において型引数`R`の情報は使われていません。それどころか、`R`は**推論される側**ですね。ここでは`num => String(num ** 2)`の返り値が`string`型であることから`R`は`string`型という情報が得られています。この情報は得られていますが、`R`の型を具体的に確定させる必要はこのタイミングではまだ発生していません。

`R`の型が確定するのは、`apply(100, num => String(num ** 2)`の返り値の型を決めるときです。宣言によればこの返り値の型は`R`ですが、ここで初めて`R`の型を具体的に知る必要が発生し、やっと`R`が`string`に決定するのです。

#### 型引数の推論が失敗する場合

以上の説明のように引数の型推論は2段階に分けて行われることがあります。しかし、これが3段階以上になったりすることはありません。したがって、次のような場合は型引数の推論に失敗します。

```ts
function apply<T, U, R>(
  value: T,
  func1: (arg: U) => R,
  func2: (arg: T) => U,
): R {
  return func1(func2(value));
}

function id<T>(value: T): T {
  return value;
}

// 意図はT = number, U = string, R = boolean だが、
// 実際の型引数の推論結果は T = number, U = unknown, R = boolean となる
// その結果、str が unknown 型扱いになり str.length でエラーが発生
const res = apply(100, str => str.length > 0, num => String(num ** 2));
```

この例では`apply`に渡す関数が2つに増えています。なぜ型推論が失敗するのか詳しく見てみましょう。なお、引数の型推論は前から順番に行われます。

1. contextual typingが不要な引数を先に推論する。`100`の型推論が行なわれて`T = number`という情報が得られる。
2. contextual typingが必要な引数を次に推論する。まず第2引数`str => str.length > 0`が推論される。このときcontextual typeとして`(arg: U) => R`が使われるので`str`の型は`U`となる。まだ`U`の情報が無い状態で`U`を具体化する必要が生じたので、`U`は`unknown`型になる。よって、関数内部の型推論中に`str.length`でエラーが発生（`unknown`型の値に対してプロパティアクセスはできないので）。なお、エラーが発生しても一応型推論が続けられて`str => str.length > 0`の型は`(str: unknown) => boolean`型になる。これにより`R = boolean`という情報が得られる。
3. 次に第3引数`num => String(num ** 2)`の型推論が行なわれる。今回のcontextual typeは`(arg: T) => U`であり、`num`の型は`T`となる。ここで`T`が確定して`number`になる。

ポイントは、`U`の正しい型が判明するのは第3引数の型推論を行った後なのに、それより前の段階で`U`が必要になったためやむなく`unknown`型に固定されているという点です。TypeScriptは原則としてワンパスで型推論を行うため、第3引数の推論で正しい`U`が判明したからといって第2引数の推論をやり直すとかそういう挙動をすることはありません。

ちなみに、引数の順番を入れ替えて次のようにすると型推論が成功します。TypeScriptの型推論結果は処理の順番に依存するということですね。型引数の推論がうまくいかない場合はこのあたりの挙動を頭に入れておくと役に立つでしょう。

```ts
function apply2<T, U, R>(
  value: T,
  func1: (arg: T) => U,
  func2: (arg: U) => R,
): R {
  return func2(func1(value));
}

function id<T>(value: T): T {
  return value;
}

// res は boolean 型
const res = apply2(100, num => String(num ** 2), str => str.length > 0);
```

### オーバーロードされた関数の型推論

引数の型の推論結果が関数の型に影響する場面はもうひとつあります。それは関数が**オーバーロード**されている場合です。TypeScriptでは関数のオーバーロードという機構があり、これはひとつの関数が複数の関数型を同時に持つという機能です。

```ts
function func(arg1: number, arg2: number): number;
function func(arg1: string): string;
function func(arg1: number | string, arg2?: number): number | string {
  if (typeof arg1 === "number") {
    return arg1 + arg2!;
  } else {
    return arg1;
  }
}

// res1 は number 型
const res1 = func(123, 456);
// res2 は string 型
const res2 = func("foo");
```

この例では`function func`という宣言が3つも並んでいて気持ち悪いですが、よく見ると最初の2つは関数の中身が書いていません。これがオーバーロードシグネチャの宣言です。これが2つあるので、関数`func`は2種類の関数型を持っているという意味になります。3つ目が関数の実体の宣言です。

このように宣言された`func`を呼び出す側が今回のポイントです。2つの数値を引数に渡した場合は`func`の1つ目のシグネチャ（`(arg1: number, arg2: number) => number`）が採用されて返り値が`number`型と推論される一方で、1つの文字列を引数に渡した場合はもう1つのシグネチャ`(arg1: string) => string`が採用されて返り値は`string`型になります。

これも型引数の場合と理屈は同じで、contextual typingが不要な引数の型を先に推論して`func`のどのシグネチャを採用するか決定、その後でcontextual typingが必要な引数の推論を行うというのが基本的な流れです。

#### オーバーロードの解決が失敗する例

これを踏まえると、やはり型推論が失敗する例を作ることができます。例えばこんな感じです。

```ts
function func(callback1: (arg: number) => number, callback2: (arg: number) => string): string;
function func(callback1: (arg: string) => string, callback2: (arg: string) => number): number;
function func(callback1: any, callback2: any): string | number {
    return callback2(callback1(100));
}

// 返り値の x で Type 'number' is not assignable to type 'string'. というエラーが発生
func(x => x, str => Number(str));
```

この関数`func`は、2つ目のオーバーロードを選択すれば正しく型が付けられるはずです。型註釈`x`も`str`も`string`型ということにすれば辻褄が合いますね。ところが、これらはどちらもcontextual typingが必要なので`func`のオーバーロードの解決が失敗し、`x`と`str`は`number`型に推論されます。ただその後のエラーを見ると、`x => x`の返り値が`string`型であることが要求されており、どうやらオーバーロードの再解決が行われているようにも見えます。すみませんが、このあたりの挙動の詳細は筆者にもよくわかっておらず調査中です。

とにかく、実際にこんなオーバーロードがされた関数を書くことはあまり無いとは思いますが、オーバーロードと型引数を組み合わせたりしてややこしいことをすると実際のコードでもたまに型推論が失敗することがあります。基本的にはcontextual typingに頼っていることによる情報不足が原因ですので、適当に型註釈を増やしてやれば大丈夫なことが多いです。

まあ、筆者はそもそもオーバーロードがあまり好きではないのですが（関数定義の内部でだいたい[敗北](https://qiita.com/uhyo/items/aae57ba0734e36ee846a)が発生するので）。

### ジェネリクスとカリー化された関数に関する注意

関数型言語のバックグランドを持つ人は関数を**カリー化**したがることがあります。下の例のように、2つの引数を持つ関数をカリー化した場合は1引数関数を返す1引数関数になります。

```ts
// 元々の add 関数
const add_original = (left: number, right: number) => left + right;
// カリー化された add 関数
const add = (left: number) => (right: number) => left + right;

// add 関数の使い方
const eight = add(3)(5);

// 「3を足す関数」を作ったりできる
const add3 = add(3);

console.log(add3(10));    // 13
console.log(add3(10000)); // 10003
```

しかし、型引数を持つ関数をカリー化する場合は注意が必要です。次のようにするとうまくいきません。

```ts
// getは 配列 array の index 番目を返す関数
const get_original = <T>(index: number, array: T[]) => array[index];
const get = <T>(index: number) => (array: T[]) => array[index];

const numArr = [1, 2, 3];

// num1 は number 型
const num1 = get_original(1, numArr);
// num2 は number 型ではなく unknown 型になっている
const num2 = get(1)(numArr);
```

問題は、最後の`num2`の型が`unknown`型になることです。これは、`get`の型引数`T`の推論結果が`number`ではなく`unknown`型になっているということを意味しています。つまり、`T`の推論に失敗しているわけですね。

これは、**型変数の推論はその関数が呼ばれた瞬間にのみ起こる**ことを意味しています。関数`get`というのは型引数`T`を持ち、`index`というひとつの引数を受け取って何らかの関数を返すものでした。ということは、型引数`T`の推論は`index`というひとつの引数のみを使って行なわれます。端的に言えば、`get_original`では`index`と`array`という2つの引数を使って`T`を推論できたのに、`get`では`<T>`が直接ついている引数が`index`だけなので`index`しか利用できないのです。

`index`は`T`とは無関係の引数なので`T`の情報は得られません。ということは、`get`の返り値の型（`(array: T[]) => T`）を具体化する段階で`T`の情報がないため、ここで`T`が`unknown`型に確定します。よって、`get(1)`の型は`(array: unknown[]) => unknown`となります。これが`num2`、すなわち`get(1)(numArr)`の型が`unknown`となった理由です。

今回の場合は`get`を次のように変更すると意図した挙動に近くなるでしょう。型引数`T`のつく位置をずらして`get`が型引数のある関数を返すように変更しました。これにより、`T`の推論は`array`が渡されるまで遅延されます。

```ts
const get = (index: number) => <T>(array: T[]) => array[index];
// num2 に正しく number 型がつく
const num2 = get(1)(numArr);
```

## その他の細かい挙動

ここまでで、TypeScriptの型推論のルール的なものはおおよそ説明し終わりました。実はまだいくつかの特殊ルールがあるので、ざっと解説していきます。

### 配列の型推論

配列を構築する場合、以下のようにまず`[]`を代入してから`push`などを使って要素を追加していくことがありますね。実はこのとき、**変数の型が宣言時に決まるというルールを逸脱した挙動**が発生します。

```ts
const arr = [];
for (let i=0; i<10; i++) {
  arr.push(String(i));
}
// ここでは arr の型は string[] 型

arr.push(123);
// ここでは arr の型は (string | number)[] 型
```

このように、`arr`に対して行った操作に応じて`arr`の型が変わっていくという、`let`とかあの辺で見たような挙動をします。原理は同じです。

注意すべき点は、`[]`に大して何も要素を追加せずに使用しようとすると`arr`が`any[]`扱いになることです（`--noImplicitAny`下ではエラーとなります）。

```ts
const arr = [];

arr[0]; // ここでエラー

const arr2 = arr.slice(); //これもエラー
```

これが原因のエラーに遭遇することは滅多にありませんが、もしあったら「TypeScriptがバグった」などとは思わずに冷静に`arr`に型註釈を追加しましょう。

```ts
const arr: string[] = [];

arr[0]; // OK

const arr2 = arr.slice(); // OK
```

### 高階関数の返り値がジェネリクスになる場合（型引数の受け継ぎ）

TypeScriptの原則の一つとして「型註釈が尊重される」というものがあります。すでにそれが尊重されない例外も紹介していますが、ここではもう一つそんな例を紹介します。それは「関数を受け取って関数を返す」ような関数に対する型推論の場合です。

まずは普通の例です。

```ts
function twice<T>(func: (arg: T) => T): (arg: T) => T {
  return arg => func(func(arg));
}

const add1 = (arg: number) => arg + 1;

// add2 の型は (arg: number) => number
const add2 = twice(add1);
```

関数`twice`は、`T`型を受け取って`T`型を返す関数`func`を受け取って、`func`を2回適用する新しい関数を返します。上の例では、受け取った値に1を足して返す関数`add1`を`twice`に渡すことで、受け取った値に2を足して返す関数`add2`を得ています。

この`twice`呼び出しにおいて型推論はどのように行われているでしょうか。いきなり答えを言ってしまうと、`T`が`number`型に推論されます。これは、`add1`に対して型推論が行われて`(arg: number) => number`型を得て、それを`twice`の引数`(arg: T) => T`と比較することで`T`が`number`であると結論しています。

#### 型引数の受け継ぎが発生する例

では本題です。上の例を踏まえて次の例を見てください。

```ts
function twice<T>(func: (arg: T) => T): (arg: T) => T {
  return arg => func(func(arg));
}

const id = <V>(arg: V) => arg;

// idid の型は <V>(arg: V) => V
const idid = twice(id);
```

今度は関数`id`を`twice`に渡しています。先ほどとの違いは`id`が型引数を持つことです。これに対応して`twice(id)`の型も`<V>(arg: V) => V`となっていますね。

実はこの**`twice(id)`の返り値の型こそが原則からの逸脱になっています**。というのも、`twice`の関数宣言における返り値の型は`(arg: T) => T`なのであって、型引数`<V>`なんてどこにも書いていませんね。それにも関わらず`twice`に`id`を渡したときの実際の返り値の型は`<V>(arg: V) => V`になっています。

このような挙動は、特定条件下で**引数として渡した関数が持つ型引数が返り値の関数に受け継がれる**ものとして説明できます。

その条件とは、**その型引数の具体的な中身の推論が発生しなかった**ことです。また、他にも返り値の関数型がそれ自身ですでに型引数を持っている場合はだめです。

では詳細を見ていきましょう。`twice(id)`という関数呼び出しに対する型推論を考えます。`id`は関数なので、例によってcontextual typeありの型推論が行われます。今回のcontextual typeは`(arg: T) => T`です。一方で、`id`の型は宣言の通り`<V>(arg: V) => V`です。これの辻褄を合わせるには`V = T`とすれば良さそうですね。

ここがポイントです。渡された引数`id`が持っていた型引数`V`は、`T = V`という情報こそ得られたもののその具体的な型が何かは結局何も情報がないままです。これが「**その型引数の具体的な中身の推論が発生しなかった**」に相当します。

`twice(id)`の返り値の型については、宣言では`(arg: T) => T`でしたね。型引数の推論から`T = V`が分かっているのでこれは`(arg: V) => V`になります。さらに、上記の条件を全て満たしているので**型引数の受け継ぎ**が発生し、最終的にこれは`<V>(arg: V) => V`型となります。

ちなみに、この「型引数の受け継ぎ」はTypeScript 3.4で実装されました。TypeScript 3.3以前ではどうなっていたかというと、返り値の型が`(arg: V) => V`となるところまでは同じですが、最終的に`V`が具体化されます。`V`については情報が無かったので`V`は`{}`に置き換えられて、`twice(id)`の返り値の型は`(arg: {}) => {}`となっていました。

#### 他の例

型引数の受け継ぎが発生する例をもう一つ見ましょう。今度はより変な例です。

```ts
function makeDouble<T>(func: (arg: T) => T): (arg: number) => number {
  return num => num * 2;
}

const id = <V>(arg: V) => arg;

// fn の型は <V>(arg: number) => number
const fn = makeDouble(id);
```

`makeDouble`は、引数でもらった関数は使わずに`(arg: number) => number`型の変数を返すという謎の関数です。

これに対しても先ほどと同様の法則が適用された結果、`V`が返り値の関数の型に受け継がれて`<V>(arg: number) => number`型になります。面白いですね。

### 関数にプロパティを生やす時の型推論

「変数の型は宣言時に決まる」という原則の例外となる挙動をさらにひとつ紹介します。それは**関数に後からプロパティを追加できる**という挙動です。ちょっと長いですが例を出します。

```ts
// 関数を宣言
const func = (num: number) => num ** 2;

// 関数にプロパティを追加できる
func.foo = 123;
// メソッドも追加できる
func.method = (arg: number) => func(arg * 10);
/* func の型は
   {
     (num: number): number;
     foo: number;
     method(arg: number): number;
   }
 */
useObj(func);

// もちろん関数として使える
func(100);

type MyObj = { foo: number };

function useObj(obj: MyObj) {
  console.log(obj.foo);
}
```

この例では、関数`func`を作った後に`foo`とか`method`とかいうプロパティを`func`に追加しています。普通はオブジェクトの型に存在しないプロパティを勝手に追加するのは型エラーですから、これは関数に独特の挙動ですね。

何でこんな変な機能があるのかとお思いかもしれませんが、JavaScriptでは意外と「関数にプロパティを生やして拡張する」ということをするコードが多いのです（特にCommonJSで関数をエクスポートしたいけど他の機能も提供したい場合など）。そのようなコードに対して昔は問答無用でエラーを吐いていて簡単な解決策もありませんでしたが、頻出なので切り捨てるわけにもいかないという判断からこうなったと考えられます。

#### 機能の危険性

ただ、この機能にはひとつとても残念な点があります。関数の型を変化させるにあたって、`let`とかとは違ってフロー情報を見ないのです。よって、実際にプロパティが追加される前から型上はプロパティが存在する扱いとなり、このようなプログラムをエラー無く書けてしまいます。

```ts
// 関数を宣言
const func = (num: number) => num ** 2;

// func にプロパティを追加する前に使用することができる！！！（危険）
useObj(func);

func.foo = 123;
func.method = (arg: number) => func(arg * 10);

type MyObj = { foo: number };

function useObj(obj: MyObj) {
  console.log(obj.foo);
}
```

なので、安全性を気にする方はこの機能を使っているのを見たら黄色信号です。

#### 安全な代替案

実は、型安全に同じことができる方法があります。それは次のように`Object.assign`を使う方法です。

```ts
const func = Object.assign(
  (num: number) => num ** 2,
  {
    foo: 123,
    method: (arg: number) => func(arg * 10)
  }
);

useObj(func);
type MyObj = { foo: number };

function useObj(obj: MyObj) {
  console.log(obj.foo);
}
```

この場合は`func`は`((num: number) => number) & { foo: number; method: (arg: number) => number }`型となり、型の見た目が違いますがまあ同じ意味です。この方法を使う場合は中途半端な状態の`func`が露出しないので安全というわけです。

#### 機能の有効化条件

この機能は「関数型なら何でもプロパティを追加できる」というものではありません。この機能が有効になるには一定の条件があります。

まず、この機能で拡張できる関数は`function`宣言で作られた関数か、`const`で変数に代入された関数でなければいけません。つまり、**`let`とかはだめ**です。

```ts
let func = (num: number) => num * 2;
// これはエラー（funcがletで宣言されているので）
func.foo = 123;

const func2 = (num: number) => num * 2;
// これはOK（funcはconstで宣言されているので）
func2.foo = 1234;
// これもOK（func3はfunction宣言で宣言されているので）
func3.foo = 123;
function func3(num: number) { return num * 100; }
```

なお、`const`に関しては変数に“直に”代入された関数でなくてはいけません。つまり、`const 変数 = 関数式;`の形でないとだめだということです。次の例は`func`に入るのは確かに関数であるものの、直に関数式を入れるのではなく配列から取り出しているので`func`は拡張不可能になります。この“直に”という概念は[関数のnameプロパティの話](https://qiita.com/uhyo/items/2a2d67abd56419ea21b8)を思い出しますね。

```ts
const funcArr = [(num: number) => num * 10];
const func = funcArr[0];
// これはエラー（変数funcに直に関数式が代入されていないので）
func.foo = 123;
```

また、関数を`const`で宣言した場合でも、**型註釈があるとだめ**です。次の例は`func`に型註釈が付いているので拡張不可となります。

```ts
type F = (num: number) => number;
const func: F = num => num * 2;
// これはエラー（funcに型註釈があるので）
func.foo = 123;
```

`let`の場合といい、変数に型註釈を付けないと特殊なフラグが立つのはちょっと気持ちが悪いような気がしないでもないですね。

### 型引数のwidening

ジェネリクスの説明に出てきたこの例を思い出してください。

```ts
function apply<T, R>(value: T, func: (arg: T) => R): R {
  return func(value);
}

// res は string 型
const res = apply(100, num => String(num ** 2));
```

ここでは`T`型の引数に`100`が渡されていたので`T`が`number`型に推論されていました。よく考えると、ここで`T`の型に対して**widening**が発生していますね。`100`という式の型は`100`型なのにいつのまにか`number`型にされています。

実は、型引数がいつでもwideningされるわけではありません。次の例では`T`は`number`型ではなく`100`型になります。つまり、こっちの例では型引数がwideningされていないのです。

```ts
function id<T>(value: T): T {
  return value;
}

// num は 100 型
const num = id(100);
```

では、型引数はいつwideningされていつwideningされないのでしょうか。`T`という型変数がある場合を考えましょう。実は、以下の条件のどちらかを満たせば型引数`T`はwideningされません[^no_widening_source]。

[^no_widening_source]: これはTypeScriptコンパイラの`src/compiler/checker.ts`内の`getCovariantInference`関数のコメントに書いてあります。今回解説していない内容が関わってくる部分は省いていますが。

1. `T`に`extends string`のようなプリミティブ型による制約（または`extends 1 | 2 | 3`のようなリテラル型による制約）がついている。
2. 型推論中に`T`の具体的な型が確定しておらず、関数の返り値の型に`T`がトップレベルで出現する。 

トップレベルで出現するというのは、関数の返り値の型が`T`自身である（`T[]`とかではない）か、または`T`を含むunion型かintersection型であることを言います。

上記の`id`関数の場合、2を満たしていますね。`id(100)`という呼び出しでは`T = 100`という情報が得られましたが、他の引数で特に`T`は使われていないため、戻り値の型を決めるときまで`T`は確定していません。また、`id`の返り値の型は`T`なので`T`がトップレベルに出現しています。

試しに「型推論中に`T`の具体的な型が確定しておらず」という条件を壊してみましょう。

```ts
function id<T>(value: T, callback: (arg: T) => void) {
    return value;
}

// n の型は number 型
const n = id(100, num => {})
```

`id`に特に使われない第2引数を追加しました。こうすると、`T`は`100`ではなく`number`型に推論されるようになります。その理由は`id`の第2引数`num => {}`の型推論時にあります。この関数の型推論はcontextual typeがあり、それは`id`の定義に従えば`(arg: T) => void`です。よって、`num`の型が`T`となり、ここで`T`の具体的な型が必要となるため`T`が確定させられてしまいます。よって「`T`の具体的な型が確定していない」の条件を満たさなくなったため`T`に対してwideningが発生し、`T`は`number`型になるのです。

もうひとつの「関数の返り値の型に`T`がトップレベルで出現する」のほうを壊すのは簡単です。次のように返り値の型を`T[]`とかにすると`T`の推論結果が`number`に変わります。

```ts
function id<T>(value: T): T[] {
    return [value];
}

// n の型は number[] 型
const n = id(100)
````

一方、`T | null`とかは`T`がトップレベルに出現している判定になりセーフです。

```ts
function id<T>(value: T): T | null {
    return value
}

// n の型は 100 | null 型
const n = id(100)
```

最後にwideningが抑制されるもうひとつの条件「`T`に`extends string`のようなプリミティブ型による制約（または`extends 1 | 2 | 3`のようなリテラル型による制約）がついている」を試してみましょう。次の例は`T`に`T extends number`という制約を付けました。

```ts
function id<T extends number>(value: T): T[] {
    return [value]
}

// n の型は 100[] 型
const n = id(100)
```

これは返り値の型が`T[]`なので2つ目の条件は満たしていませんが、`T extends number`があることで`T`がwideningされずに`100`型に推論されています。この挙動の理由は、`T extends number`と書いた時点で`T`としてリテラル型を期待しているのは明らかであるということからでしょう。どんな数値を渡しても全部`T`が`number`型にwideningされてしまうのでは、わざわざ型引数`T`を作った意味がありません。

リテラル型が欲しくて型引数を作ったけどうまく推論してくれないという場面にこれから先出会うかもしれませんが、そのときはこの挙動を思い出しましょう。

## まとめ

お疲れ様でした。長かった割に、TypeScriptを使いこなしている人にとっては当たり前の内容が多かったかもしれません。この記事で初めて知ったことが多かったという方は、きっとTypeScript力がついたことでしょう。

この記事全体を通して重要なことは、**変数や関数の型は宣言された瞬間に決まるという原則**です。これにより、TypeScriptの型推論は基本的にワンパスで、つまりプログラムを1度上から下、左から右に見ていくだけで行えるようになっています。これにより推論が失敗してしまう場合もありますが、これはTypeScriptの制限というよりはそういう言語デザインであると考えられます。この制限を克服するためには関数型言語などで採用されている類の本格的な型推論アルゴリズムを導入する必要がありますが、TypeScriptの複雑な型システムの上でそれをやるのは非常に難しいです。

関数の引数の型を（contextual typeにより推論できる場合を除いて）明示しないといけないというのも同じ原則から言えることです。宣言だけ見て関数の型が決まらないのはだめということですね。

ただ、例外もいくつか記事内で紹介しました。`let`で宣言された関数やフロー解析による絞り込みがそれにあたります。

TypeScriptが型をうまく推論してくれない場合、この記事を理解してTypeScriptの型推論器の気持ちになればその理由が分かるかもしれません。機会があればぜひじっくりと考えてみてください。

最後になりますが、「このよく分からん挙動も解説してほしい」という要望があればぜひコメント欄に書いてください。応えられるかもしれません。

### あわせて読みたい

**TypeScriptの型シリーズ**

- [TypeScriptの型入門](https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a)
- [TypeScriptの型初級](https://qiita.com/uhyo/items/da21e2b3c10c8a03952f)
- [TypeScriptの型演習](https://qiita.com/uhyo/items/e4f54ef3b87afdd65546)