🌀

never型を返す関数で行う型の絞り込みに失敗するパターンがある

2024/11/20に公開1

概要

Next.js の App Router に notFound 関数というのがあります。
この関数を呼び出すことで即座に処理を中断し、404ページを表示させることができます。
notFound 関数は例外をスローすることで処理を終了させるので(戻り値が never 型の関数なので)、型の絞り込みに利用することもできます。

/app/users/[id]/page.tsx
export default async function Page({ params }) {
  const user = await fetchUserById(params.id); // undefined or User

  if (!user) notFound(); // user が undefined の場合はここで処理が終了

  // 以降 user は User 型として扱えるようになる

  return <UserProfile profile={user.profile} />;
}

ここで notFound 関数を拡張した myNotFound 関数を作成し、利用するとします。すると、user 変数の型の絞り込みが正常に行われず型エラーが発生するようになってしまいました。

/app/users/[id]/page.tsx
+const myNotFound = (): never => {
+  // 何らかの追加処理
+  notFound();
+};

export default async function Page({ params }) {
  const user = await fetchUserById(params.id); // undefined or User

-  if (!user) notFound(); // user が undefined の場合はここで処理が終了
+  if (!user) myNotFound(); // user が undefined の場合はここで処理が終了

  return <UserProfile profile={user.profile} />; // error TS18048: 'user' is possibly 'undefined'.
}

notFoundmyNotFound 関数はどちらも戻り値が never 型なので違いはないようにみえるのですが myNotFound 関数は型の絞り込みに失敗してしまいます。
これはなぜでしょうか?

詳細

これはパフォーマンス上の理由で TypeScript の if 文などの条件分岐を解析して型の推測を行う制御フロー分析(Control Flow Analysis: CFA)が明示的な変数の型注釈を必要とするからです。[1]

つまり先ほどの例だと、制御フロー分析では myNotFound 変数が never 型を返す関数だと認識されず、型の絞り込みに失敗してしまったのです。左辺の myNotFound 変数自体に型注釈を入れることで型の絞り込みが正常に行われるようになります。

/app/users/[id]/page.tsx
-const myNotFound = (): never => {
+const myNotFound: () => never = () => {
  // 何らかの追加処理
  notFound();
};

...

どの書き方だと型の絞り込みが正常に行われるのかそうでないのか以下にまとめてみました。

// 型の絞り込みに成功する書き方
const myNotFound: () => never = () => { ... };
const myNotFound: () => never = function () { ... };
function myNotFound(): never { ... }

// 型の絞り込みに失敗する書き方
const myNotFound = notFound;
const myNotFound = (): never => { ... };
const myNotFound = function (): never { ... };
function myNotFound() { notFound() }

今回紹介した例とは異なりますが、TypeScript の Playground に簡単なデモを用意してみました。こちらもご参考ください。

https://www.typescriptlang.org/play/?#code/PTAEDsFMDdIJ0NHqgkhkCvxhNBkEWpgHU0OYMgZCMDEGBQOwZA9X0CiGQHvjB-BmMDGGQToZAJhgCgWRQBabn3v-gYKHCh7MIF0GQDEMgPQZJxUABcAFnAD2AdwCicNXABi4UNmKBmhkCHDIGeGJoCKGQGUMTJIDRNQOoMgMwZAZlFvA8gyADBkCqDIAiDGwcJBQ0gNYMgAhGgPlKaIDRDIAfZoCyDICdpiwAxqrgAM4Kiioa2roGAFygABQAlKAAvAB8EDDwtRXV9aAA3vlq6qAAREWqcH2gAL4A3GKgmTl5yj2D+uBlVbUNULBwLQBmAK7g6QoAllmtnd0a-YsjE1N7B8en84U6QwZVZRvNXc+9A6-DMbBMChKjUcKARk1AOqm8WSaQ4M1yFy0AIMLQ+jU2a3OvyuAJukwRWSRv0WaJqoHuhxOhgxXy2PwKf2uY0JYCpj0MpNR4FWjJ6eN0NymIlFYvF4rYU1BNCQeACgH0GAKAEgUMsS8tAAIYAG12kDK+wAJpBtkcoIbQAAfUC5OBmgDmLQAsprlAA6OCa8CG1QAW1WAB5QAAGN0AVlAAH5QEaTWbIBayn1bQ6+pMplrdZBQIAZBhj3rj5tAxEALBqACBVAPYMgGUGKs4QBnioAwFxzgCFfQBhciwjtsKgBCTN66rc4q8yrpkJkMGQmFxcs1nCAGAYAOR9yAL0BHbKgAAOqmy2SOACNtQBPUAL2Om80Lt2gQCwDIAKhkAlwyAH4Yc4AvN0A+K4sZdu7WQcD25RJiAA

脚注
  1. https://github.com/microsoft/TypeScript/issues/51075#issuecomment-1268935895 ↩︎

Discussion

HikaruooHikaruoo

TypeScriptの制御フロー分析における型推論の仕組みが、パフォーマンス面での設計によって影響を受けることを具体的に説明している点が勉強になります。ありがとうございます。