Conditional Types
(X extends Y ? A : B
)で
unknown
や any
が絡んでハマってしまったので
その際に調べた内容をメモとして残しておきます。
調べたこと
X extends Y
において X
や Y
にany
, unknown
が登場したときに
どのような判定になるかを調べてみました。
あとついでにnever
もいれてみた
TypeScript
のバージョンは3.9.7
で検証しました。
型推論の結果確認は VSCode 上でフォーカスをあてて確認しました。
コード
判定用のコードです。
type spu1 = unknown extends string ? "〇" : never;
type spu2 = unknown extends number ? "〇" : never;
type spu3 = unknown extends unknown ? "〇" : never;
type spu4 = unknown extends any ? "〇" : never;
type spu5 = unknown extends never ? "〇" : never;
type spu6 = unknown extends null ? "〇" : never;
type spu7 = unknown extends undefined ? "〇" : never;
type spu8 = unknown extends symbol ? "〇" : never;
type spu9 = unknown extends {} ? "〇" : never;
type sbu1 = string extends unknown ? "〇" : never;
type sbu2 = number extends unknown ? "〇" : never;
type sbu3 = unknown extends unknown ? "〇" : never;
type sbu4 = any extends unknown ? "〇" : never;
type sbu5 = never extends unknown ? "〇" : never;
type sbu6 = null extends unknown ? "〇" : never;
type sbu7 = undefined extends unknown ? "〇" : never;
type sbu8 = symbol extends unknown ? "〇" : never;
type sbu9 = {} extends unknown ? "〇" : never;
//---
type spa1 = any extends string ? "〇" : never;
type spa2 = any extends number ? "〇" : never;
type spa3 = any extends unknown ? "〇" : never;
type spa4 = any extends any ? "〇" : never;
type spa5 = any extends never ? "〇" : never;
type spa6 = any extends null ? "〇" : never;
type spa7 = any extends undefined ? "〇" : never;
type spa8 = any extends symbol ? "〇" : never;
type spa9 = any extends {} ? "〇" : never;
type sba1 = string extends any ? "〇" : never;
type sba2 = number extends any ? "〇" : never;
type sba3 = unknown extends any ? "〇" : never;
type sba4 = any extends any ? "〇" : never;
type sba5 = never extends any ? "〇" : never;
type sba6 = null extends any ? "〇" : never;
type sba7 = undefined extends any ? "〇" : never;
type sba8 = symbol extends any ? "〇" : never;
type sba9 = {} extends any ? "〇" : never;
//---
type spn1 = never extends string ? "〇" : never;
type spn2 = never extends number ? "〇" : never;
type spn3 = never extends unknown ? "〇" : never;
type spn4 = never extends any ? "〇" : never;
type spn5 = never extends never ? "〇" : never;
type spn6 = never extends null ? "〇" : never;
type spn7 = never extends undefined ? "〇" : never;
type spn8 = never extends symbol ? "〇" : never;
type spn9 = never extends {} ? "〇" : never;
type sbn1 = string extends never ? "〇" : never;
type sbn2 = number extends never ? "〇" : never;
type sbn3 = unknown extends never ? "〇" : never;
type sbn4 = any extends never ? "〇" : never;
type sbn5 = never extends never ? "〇" : never;
type sbn6 = null extends never ? "〇" : never;
type sbn7 = undefined extends never ? "〇" : never;
type sbn8 = symbol extends never ? "〇" : never;
type sbn9 = {} extends never ? "〇" : never;
結果
以下のような結果になりました。
unknown
type | unknown extends T | T extends unknown |
---|---|---|
string | 〇 | |
number | 〇 | |
unknown | 〇 | 〇 |
any | 〇 | 〇 |
never | 〇 | |
null | 〇 | |
undefined | 〇 | |
symbol | 〇 | |
{} | 〇 |
any
type | any extends T | T extends any |
---|---|---|
string | 〇 | 〇 |
number | 〇 | 〇 |
unknown | 〇 | 〇 |
any | 〇 | 〇 |
never | 〇 | 〇 |
null | 〇 | 〇 |
undefined | 〇 | 〇 |
symbol | 〇 | 〇 |
{} | 〇 | 〇 |
never
type | never extends T | T extends never |
---|---|---|
string | 〇 | |
number | 〇 | |
unknown | 〇 | |
any | 〇 | 〇 |
never | 〇 | |
null | 〇 | |
undefined | 〇 | |
symbol | 〇 | |
{} | 〇 |
上記のような結果になりました。
めでたしめでたし 😀
ジェネリックを使うとnever
の部分で結果が変わる
上記の調査では型をハードコーディングしていますが、
最初はジェネリックを使って以下のようなコードで検証していました。
type IsSuperUnknown<T> = unknown extends T ? "〇" : never;
type IsSubUnknown<T> = T extends unknown ? "〇" : never;
type IsSuperAny<T> = any extends T ? "〇" : never;
type IsSubAny<T> = T extends any ? "〇" : never;
type IsSuperNever<T> = never extends T ? "〇" : never;
type IsSubNever<T> = T extends never ? "〇" : never;
//検証コード
type spu1 = IsSuperUnknown<string>;
type spu2 = IsSuperUnknown<number>;
//以下省略
ところがこのコードだと 以下のような現象が起きました。
//never extends unknown
type sbu5 = IsSubUnknown<never>; //never
type spn3 = IsSuperNever<unknown>; //〇
//never extends any
type sba5 = IsSubAny<never>; //never
type spn4 = IsSuperNever<any>; //〇
試しに変数に代入してみます
let vsbu5: sbu5 = "〇"; //コンパイルエラー
let vspn3: spn3 = "〇";
let vsba5: sba5 = "〇"; //コンパイルエラー
let vspn4: spn4 = "〇";
この挙動は何なんでしょう...
仕様なのかな?🤔
おわりに
可視化をすることで理解を深めることができました。
こういった検証も大事なんだなとあらためて実感しました😀
あとジェネリック使った時の結果は何なんでしょうか...