この記事は はてなエンジニア Advent Calendar 2025 の 4 日目の記事です.
id:susisu です. TypeScript の never 型がたいへん奥ゆかしいので見ていってください.
基礎編
まずは never 型がどういったものなのかを見てみましょう.
値のない型として
例えば number 型に対しては 42, 3.14, NaN など , string 型に対しては "", "Hello" などのように, 一般的な型にはその型が付けられる値が存在します.
一方で never 型にはそういった値が存在しません (こういった型は一般にボトム型などと呼ばれたりします).
never 型の変数には何を代入しようとしてもエラーになります.
const x: never = 42; // エラー
undefined や null も「値が存在しない」と説明されることがありますが, これらはせいぜい目的に対して利用可能な値 (具体的な数値やオブジェクトなど) がないというだけで, undefined や null 自体は歴とした値です.
そのため当然 never 型の変数には代入できません.
const y: never = undefined; // エラー const z: never = null; // エラー
任意の型の部分型として
型 S の値を型 T の値として扱っても問題がないとき, 型システムが「型 S は型 T の部分型である」と判断してこのような扱いを許すことがあります.
例えば { id: string; name: string } 型の値は { id: string } 型の値として扱っても普通は問題ないので, TypeScript のコンパイラは前者を後者の部分型とみなします.
さて never 型の値はというと存在しないので, どう扱っても問題が起きません (そもそも扱うことがありません).
そのため TypeScript のコンパイラは任意の型 T に対して「never は型 T の部分型である」と判断します.
実際に never 型の値が存在すると仮定して declare で宣言してあげると, その値を任意の型の値として扱えることが確認できます.
declare const x: never; const y: number = x; // OK const z: string = x; // OK
空のユニオン型として
TypeScript には number | string のようなユニオン型が存在して, JavaScript 時代からありがちな「number もしくは string」といった曖昧な値もうまく扱うことができます.
ところで never 型には値が存在しないことから「空のユニオン型」とみなすことができて, 実際に TypeScript のコンパイラはそのような扱いをしてくれます.
またこれは型の演算 | の単位元ということでもあり, 任意の型 T に対して T | never = never | T = T です.
「number もしくは never の値」は要するに「number の値」であるということですね.
type X = number | never; // ^? type X = number
応用編
never 型が何であるかについて説明してきましたが, TypeScript や never 型に馴染みのない方にとっては, おそらくこれだけでは使い方の見当がつきづらいかと思います.
ということで具体的な使い方をいくつか紹介します.
絶対に値を返さない関数
例えば常に無限ループしたりエラーを throw したりする関数は値を返しません.
こういった関数の戻り値として never が使えます.
シグネチャから振る舞いが推測できて便利ですね.
function infiniteLoop(): never { while (true) {} } function throw_(error: Error): never { throw error; }
JavaScript や TypeScript の throw は式ではありませんが, 上記のような throw_ 関数を定義しておくと throw 式のようなものとして便利に使えます.
任意の型 T に対して T | never = T なこともあり, 下記の例の foo に対してはきちんと options.foo が存在する時の型が推論されます.
const foo = options?.foo ?? throw_(new Error("'foo' is required"));
また関数が絶対に値を返さないということは, その関数を呼び出した後のコードは絶対に実行されないということなので, コンパイラが気を利かせて型を絞り込んでくれたりします.
declare const x: number | string; if (typeof x === "number") { infiniteLoop(); // ここにはもう来ないはず } const y: string = x; // OK
条件分岐の網羅性チェック
上の例でもしれっと登場していますが, TypeScript にはフロー解析があって, if や switch などで変数の型を絞り込むような条件を書くと, 実際にその変数が絞り込まれた後の型として使えるようになります.
declare const x: number | string; if (typeof x === "number") { const y: number = x; // OK } else if (typeof x === "string") { const z: string = x; // OK } else { throw new Error("unexpected value");) }
ではこの最後の else の中で x の型はどうなっているでしょうか?
number でも string でもなく他に可能性がないということは, そう never ですね.
こういった if や switch の条件分岐が全てのパターンを網羅しているかをチェックするのに never が使えます.
例えば以下のように never 型の変数に x を代入するコードを書いてあげると, x に対する条件が網羅されて他の可能性が残っていないことを確認できます.
if (typeof x === "number") { // ... } else if (typeof x === "string") { // ... } else { const _: never = x; // OK throw new Error("unexpected value"); }
もし if が漏れているとこの代入文はコンパイルエラーになるので, 実行前に誤りに気がつけます.
if (typeof x === "number") { // ... } else { const _: never = x; // エラー throw new Error("unexpected value"); }
式の型を確認するための構文 satisfies を使っても同じことができます.
if (typeof x === "number") { // ... } else if (typeof x === "string") { // ... } else { x satisfies never; throw new Error("unexpected value"); }
絶対に呼び出せない関数
先ほどの絶対に値を返さない関数とは逆に, 関数の引数に never を使うことで, 絶対に呼び出せない関数が作れます.
function nonCallable(x: never): void {} notCallable(42); // エラー
これも使い所があって, 上記のように網羅性チェックをしたはずなのに万が一変な値が混入してしまった時のエラー処理を共通化するときなんかに便利です.
下記の unreachable は通常は絶対に呼び出せないはずですが, 変数が never 型ということになっている時に限っては呼び出すことができます.
ついでにこれは絶対に値を返さない関数でもありますね.
function unreachable(value: never): never { console.error("'unreachable' called", value); throw new Error("'unreachable' called"); } declare const x: number | string; if (typeof x === "number") { // ... } else if (typeof x === "string") { // ... } else { unreachable(x); }
また型レベルプログラミングと組み合わせると, 引数が特定の条件を満たす時のみ呼び出せる関数を作ることができます. 詳しくは以下の記事を参照.
絶対に特定のパターンにならない型
下記のような成功もしくは失敗を表す型 Result<T, E> があったとしましょう.
type Result<T, E> = | { isOk: true; value: T } | { isOk: false; error: E };
では常に成功する関数 alwaysSuccess() の型はどうするとよいでしょうか?
以下のようにジェネリクスを使っても良いのですが, デフォルトでエラーの型 E に unknown が推論されてしまったり, 一度変数に代入するなどして E が決まってしまうと変更できない (値としては常に成功なので関係ないはずなのに) など, 使い勝手はあまり良くありません.
function alwaysSuccess<E>(): Result<number, E> { return { isOk: true, value: 42 }; } const r = alwaysSuccess(); // ^? const r: Result<number, unknown> const s: Result<number, string> = r; // エラー
ということで never を使いましょう.
never の値は存在しないので, この場合 Result<T, never> の値は常に成功を表すことになります.
そして never はあらゆる型の部分型なので, 適当な型に対して自由にアップキャストができます.
function alwaysSuccess(): Result<number, never> { return { isOk: true, value: 42 }; } const r = alwaysSuccess(); // ^? const r: Result<number, never> const s: Result<number, string> = r; // OK
ちなみに成功時だけを表す型 Ok<T> が独立して定義されているようなケースであれば, never を使わずに以下のようにするのでも良いです.
type Ok<T> = { isOk: true; value: T }; function alwaysSuccess(): Ok<number> { return { isOk: true, value: 42 }; }
型レベルプログラミングにおけるエラー
TypeScript の型レベルプログラミングにおいては throw のようなエラーの機構はないので, なんらかの型を使ってエラーを表現することになります.
このエラーというのは典型的には「未定義」や「解なし」と捉えられるので, 空のユニオン型という点で意味的な相性の良い never が使われがちです.
type Head<XS extends unknown[]> = XS extends [infer Y, ...infer _YS] ? Y : never; type A = Head<[1, 2, 3]>; // ^? type A = 1 type B = Head<[]>; // ^? type B = never
ただし型レベルのコードに対応する実行時のコードがある場合は, never という型と実行時の挙動が矛盾しないように注意しましょう.
例えば実行時のコードでは「未定義」や「解なし」の場合に undefined や null も使われがちですが, これらは当然 never 型の値ではありません.
戻り値の型が never の関数に対応する実行時の挙動は, 上で紹介した通り値を返さない, つまり throw か無限ループです.
ところで型レベルプログラミングといえば, never 型かどうかを判定する型 IsNever<T> を書こうとした人は全員, なぜか never が返ってくるという失敗をしたことがあります (要出典).
never が空のユニオン型であるということと分配法則を思い出すと当然の挙動ではありますが, 初見だとびっくりしますよね.
type IsNever<T> = T extends never ? true : false; type A = IsNever<never>; // ^? type A = never
正しくはこう.
type IsNever<T> = [T] extends [never] ? true : false; type A = IsNever<never>; // ^? type A = true
never が大好き
皆さんも never 型が好きになりましたか?
