この記事はZOZO Advent Calendar 2023 シリーズ3、2日目の記事です。
rimrafが有名だったり最近ではtsimpが話題になったisaacs氏が先月、Xに以下のようなポストをしました。
TypeScriptクイズ!
調べずに、anyはneverを拡張しますか?
- はい
- いいえ
- はいもいいえも両方
- どちらもない
(ChatGPT訳)
TypeScriptを読み書きしていると、型表現のなかで A extends B といった定義を見かけることがあります。
ジェネリクスの制約や、Conditional Typesなどが主でしょう。
(JavaScriptのクラスの継承は今回の話の対象ではないので省略します)
さて、話は戻って上のクイズ、解けましたか?
クイズのように急に言われると、どのように考えればよいか戸惑う方も多いのではないでしょうか。
no の回答が最も多いようですが答えは both yes and no です。
この記事ではTypeScriptの extends をどのように考えれば良いかを考えてみます。
does `any` extend `never`?
TypeScriptにおける extends は型の大小関係を測るようなものです。
上の does `any` extend `never`? をコードに落とし込んでみましょう。
type AnyExtendNever = any extends never ? true : false;
この時点で AnyExtendNever の型が想像つく方は、この記事の内容は読まなくても大丈夫です。
このままではわからないので関数のジェネリクスの形で考えてみます。
function fn1<T extends never>(_: T) { ... }
function fn2<T extends never>(): T { ... }
さて二つに増えました。これは入出力それぞれで考える必要があるためです。
まずは fn1 のケースで考えます。
引数に any な値を渡して関数を呼べば does `any` extend `never`? の検証ができそうです。
function fn1<T extends never>(_: T) {}
declare const anyValue: any;
fn1(anyValue);
この時、 Argument of type 'any' is not assignable to parameter of type 'never'. のように型エラーになります。
does `any` extend `never`? に対して no と見ることができそうです。
次に fn2 のケースで考えます。
any な値をreturnすることで検証ができそうです。
declare const anyValue: any;
function fn2<T extends never>(): T {
return anyValue;
}
これはエラーになりません。
つまり does `any` extend `never`? に対して yes と見ることができそうです。
yes と no 、両方が出てきました。
つまり答えとしては does `any` extend `never`? は yesともnoとも言える both yes and no となるわけです。
Without looking it up
調べるなと言われていますが、今回はPlaygroundを用いつつ検証しました。
なので次は想像してみましょう。
does `never` extend `any`?
先の逆パターンです。
得票は no が最多ですが、ほんとうにそうでしょうか。
考え方は同じで関数の入出力を想像します。
function fn1<T extends any>(_: T) { ... }
function fn2<T extends any>(): T { ... }
fn1 には never な値を渡すことを考えてみます。
function fn1<T extends any>(_: T) {}
declare const neverValue: never;
fn1(neverValue);
このとき関数は function fn1(_: any) {} のように見ることもできます。
any な変数には never な値も代入できるので、これは型エラーにならなそうです。
つまり does `never` extend `any`? に対して yes と見ることができます。
fn2 では never な値をreturnすることを考えます。
declare const neverValue: never;
function fn2<T extends any>(): T {
return neverValue;
}
このとき関数は function fn2(): any { return neverValue; } のように見ることもできます。
any な値が返せるなら never な値も返せるので、これも型エラーにならなそうです。
つまり does `never` extend `any`? に対して yes と見ることができます。
どちらのパターンにおいても yes と言えるので、
does `never` extend `any`? に対して yes と答えられます。
終わりに
今回のように any と never を実務などで用いることは多くないと思います。
ですがTypeScriptの型と仲良くなるための一歩にはなると思います。
any や never の部分を unknown, number, 123, {a:1}, Record<string, never> など色々と変えてみて遊んでみてください。