Type ChallengeのIsNeverをみる

Type Challenge の IsNever をやっていたときの学びのメモ。

IsNever はTを渡した時にそのTが never 型か否かを判定し、never 型であれば true を、そうでなければ false を返すという問題。

ぱっと考えると以下のように実装すればよさそうにも思える。

が、(難易度が medium なので当然といえば当然なのだが)これだと上手くいかない。

type IsNever<T> = T extends never ? true : false;

type A = IsNever<never>;
// => type A = never;

#Distributive Conditional Types とは

これは期待される動作らしい。

以下の Issue にも解説がある。

https://github.com/microsoft/TypeScript/issues/31751#issuecomment-498526919

Conditional Types が Generic type に適用されるとき、それは Distributive Conditional Types と呼ばれるになるらしい。

公式ドキュメントには以下に記載されている。

https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types

When conditional types act on a generic type, they become distributive when given a union type.

つまり、以下のような動作になる。(公式の例を拝借)

type ToArray<Type> = Type extends any ? Type[] : never;

type Foo = ToArray<string | number>;
// => type Foo = string[] | number[] のようになる
// (type Foo = (string | number)[] ではない)

また、never は空の Union type として定義されているらしい。

これは以下のように never と他の型 の union をつくると never 型が消えてしまうことからも、確かにそれっぽい挙動をしている。

type Bar = string | never;
// => type Bar = stringとなる
// => type Bar = neverなどにはならない

この前提をもとに、IsNever の挙動を見る。

IsNever に never が代入された場合、never は空の union で分配する中身がないため never として判定される、ということのよう。(多分)

#IsNever の実装

Distributive Conditional Types の挙動を避ける方法もあり、それは[]で extends のオペランドを囲えばよい。

つまり、IsNever は以下のように実装すると想定通りの挙動になる。

type IsNever<T> = [T] extends [never] ? true : false;

type A = IsNever<never>;
// => type A = true;