TypeScript5.5が正式リリースされたのでtype predicateの型推論をまとめてみる

# はじめに
こんにちは、H×Hのセンリツ大好きエンジニアです。（同担OKです😉）
2024年6月20日に正式リリースされたTypeScript5.5にて、**type predicateの型推論**が実装されました。

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html

今回はそれに関してご紹介します！
（情報が誤っている可能性がありますので、その場合は教えていただけると助かります🙇）

### 参考サイト
- [TypeScript 5.5からは関数からType predicatesの型推論が有効になるよ！](https://zenn.dev/bs_kansai/articles/40fef14a1ea98e)
- [TypeScript 5.5で型述語を推論できて最高。配列のfilterも型安全に](https://zenn.dev/ubie_dev/articles/ts-infer-type-predicates)
- [TS5.5のInferred Type Predicatesでちょこっと気にしておきたいなと思ったこと](https://bufferings.hatenablog.com/entry/2024/05/12/103723)

# type predicateとは
> A type predicate is a function whose return type is a boolean value that's used to narrow down types. They come handy when TypeScript can't automatically infer the type of a variable or when more sophisticated type checks are needed.

type predicateは**型述語**とも呼ばれており、**返り値がboolean型の関数で型を絞り込むために利用**されます。TypeScriptが変数の型を自動的に推測できない場合であったり、より高度な型チェックが必要な場合に便利です。

https://www.basedash.com/blog/what-are-type-predicates-in-typescript

。。。自分も言葉だけではあまり理解できませんが、コードを見ていただく方が早いので例を載せます。🥹
``` isString.ts
// string型
const data = "hello, world!"

// string型か判定する関数
function isString(x: string | undefined): x is string {
    return typeof x === "string"
}

// true
console.log(isString(data))
```

functionの返り値として`x is string`とありますが、これがtype predicateです。
type predicateにより、**ユーザ定義関数で型ガードが行える**ようになります！

> If the function returns true then x has the type T.
If the function returns false then x does not have type T.

今回の場合は、**関数が`true`を返せば`x`は`string`型、`false`を返せばそれ以外である**ことを明示的に表すことができるようになり、型を安全に使用することができます。

type predicateがない場合、TypeScriptが型を推論できないため返り値は`boolean`になります。

### TypeScript5.5以前では出来なかったこと
型ガードを行うことで、より安全にTypeScriptで型を扱えるようになりますが、今まではtype predicateを自分で定義する必要がありました。

そのため、以下のコードのような関数の処理内容と返り値のtype predicateの意味が異なるケースが発生してしまいます。🫣
``` isString.ts
function isString(x: string | undefined): x is undefined {
    return typeof x === 'string'
}
```

また、`string`か`undefined`の型を受け取れば良いため、コンパイルする際にもエラーが発生しません。
![スクリーンショット 2024-07-14 12.48.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653241/5a00e385-6654-e08d-20fd-16abc6b2fdbd.png)


ユーザが定義する場合、複雑になればなるほどヒューマンエラーのリスクが高まります。
そのため、他の方が見た時に「あれ、これどういう実装？🤔」となってしまいます。

# Typescript5.5で実装されたtype predicateの型推論とは

その名の通り、**type predicateを自動で推論してくれる機能**になります！！

これにより、先程ご紹介したようなヒューマンエラーが起きないようになります☺️

``` isString.ts
// string型
const data = "hello, world!"

// string型か判定する関数
// ユーザが改めて定義する必要がなく x: string が推論される
function isString(x: string | undefined) {
    return typeof x === "string"
}

// true
console.log(isString(data))
```
この機能により、安全に型ガードを使用できます。
実際の挙動も見てみましょう！
![スクリーンショット 2024-07-14 14.37.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653241/5186ce58-85ab-47e4-db43-c12f7602bd2c.png)

このように、**TypeScript5.5では明示的にtype predicateを記述しなくとも、自動で関数の実装内容から推論してくれるようになります**！
便利ですね😍

> This works because TypeScript now infers a type predicate for the filter function. You can see what’s going on more clearly by pulling it out into a standalone function:

このtype predicateによる型推論が行えるようになったのは、`filter`のtype predicateが機能したからのようです。👀

例として、`(string | undefined)[]`の配列から`string`型の値のみ取り出す`filter`を作成し、チェックしてみます！

``` filter.ts
// values: (string | undefined)[]
const values = ["hello", undefined, "world"];

// string型か判定する関数
function isString(x: string | undefined) {
    return typeof x === 'string'
}

// valuesからstring型の値のみ格納
const strings = values.filter(isString);

// stringの値を大文字に変更
strings.forEach(str => {
  console.log(str.toUpperCase())
});
```

このコードの出力としては、以下のものが予想できるかと思います。
``` zsh
"HELLO"
"WORLD"
```

しかし、TypeScript5.5以前ではコンパイルエラーになってしまいます。🥲
![スクリーンショット 2024-07-14 14.51.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653241/3e143b48-e305-0bce-5c82-b0b9c4a0597f.png)

実は、TypeScript5.5以前では型推論が行われないため、　`filter`を通したとしても型は変更されません。
``` filter.ts
// strings: (string | undefined)[]
const strings = values.filter(isString);

// 型にundefinedを含んでいるため、コンパイルエラー発生
strings.forEach(str => {
  console.log(str.toUpperCase())
});
```

この問題を、今回の型推論で解決することができます！
`filter`を通すことにより型推論が行われ、正しく`string[]`として型定義が行われるようになります🥳

![スクリーンショット 2024-07-14 14.55.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653241/f25b660a-41ab-676d-b2f7-6fb489664b22.png)

きちんとコンパイルエラーにならず、想定される出力を返してくれますね！
このように、TypeScript5.5で実装されたtype predicateの型推論は**開発者が特段型を意識せずに直感的な記述が行える**ようになります！

# type predicateの型推論が行われる条件
> TypeScript will infer that a function returns a type predicate if these conditions hold:
>1. The function does not have an explicit return type or type predicate annotation.
> 2. The function has a single return statement and no implicit returns.
> 3. The function does not mutate its parameter.
> 4. The function returns a boolean expression that’s tied to a refinement on the parameter.

公式によると、以下の４つの条件を満たした場合だそうです。
1. 関数の返り値の型を**明示的に指定していない**場合
1. 関数に**単一の`return`があり、かつ暗黙的な返り値がない**場合
1. 関数の引数が**変更されていない**場合
1. 関数の内容が「**引数の型を狭めることに関係するbooleanを返す**」ものである場合

### 1. 関数の返り値の型を明示的に指定していない場合
関数の返り値を以下のように指定されている場合は型推論が発動しなくなります。
``` isString.ts
function isString(x: string | undefined): x is undefined {
    return typeof x === 'string'
}
```

ですので、上記のようにエラーの元になるため、極力自分で定義しないように心掛けましょう。

### 2. 関数に単一の`return`があり、かつ暗黙的な返り値がない場合
**暗黙的な返り値**についてですが、こちらは真偽チェックが正しく行われているかが影響すると思います。
例えば、`isString`関数のreturnを`!!x`に変えてみましょう。
すると、コンパイルエラーとなります。
![スクリーンショット 2024-07-14 15.29.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653241/aba81ceb-c73d-2028-212e-7dee0bac6290.png)

原因としては、**`!!x`が`false`を返すものに`""`が含まれる**からです。（`falsy`な値）
これでは、**`string`型の一部が`false`になってしまうため、型推論が行えません**。

> The fix is to tell TypeScript the type that you want using an explicit type annotation:

解決方法としては、**明示的な型注釈を利用して必要な型をTypeScriptに伝えてあげること**です。
そのため、今回の場合は`undefined`を明確にフィルターする必要があります。
``` isString.ts
function isString(x: string | undefined) {
    return typeof x === 'string'
}
```

### 3. 関数の引数が**変更されていない**場合
こちらは、関数内で与えられた引数の型を変更していない場合になります。
例と挙げると、以下のコードが考えられます。
``` isNumber.ts
// string型をnumber型に変換し、number型か判定する関数
function isNumber(x: string | undefined) {
  const num = Number(x)
    return typeof x === 'number'
}
```

このように、**関数の引数が変更されてしまうとTypeScriptで型推論が行われなくなります**。
実際にTypeScriptの型定義を見ても分かる通り、`x is never`になってしまいます。
![スクリーンショット 2024-07-14 15.49.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653241/cf99d040-ad90-ad6c-e792-7cca5e5f5206.png)

なので、上記のように型変更して判定することが出来ませんので、**型を変更する関数と型の絞り込みを行う関数は分けてあげましょう**。😇

### 4. 関数の内容が「**引数の型を狭めることに関係するbooleanを返す**」ものである場合
type predicateの型推論なので、こちらも当然っちゃ当然ですね🫣

こちらは、先ほどから紹介している`filter`や`isString`関数のような、**2つ以上の型が引数で与えられた際に、返り値として最低1つは型を絞る**ような関数の場合に型推論は機能してくれます。

# おわりに
今回は先月正式リリースされたばかりの、TypeScript5.5の一機能についてまとめてみました！
参考サイトや公式サイトを見ながら自分なりに解釈しましたが、もし間違いがありましたら教えていただけると幸いです。🙇

最後までご覧いただき、ありがとうございました！