LoginSignup
16
8

More than 3 years have passed since last update.

TypeScriptの特殊な型同士の型演算結果まとめ

Last updated at Posted at 2019-04-20

TypeScriptの型定義(d.ts)を読んでいると、
いろいろな型パズルが出てきてわけわかんなくなってきたので、
anyやnullなどの特殊な型同士をいじくったときの結果をまとめた。

TypeScript3.4.3で確認。動作を見ただけで仕様を当たったわけではない点に注意。

[追記]
strictNullChecksの存在をすっかり忘れていたので追加。直感通りの素直な結果ではあった。

Union Types

type Union<A, B> = A | B;

"strictNullChecks": falseの場合

A|B any unknown string void null undefined never
any any any any any any any any
unknown unknown unknown unknown unknown unknown unknown
string string string|void string string string
void void void void void
null null null null
undefined undefined undefined
never never

"strictNullChecks": trueの場合(異なる部分は太字)

A|B any unknown string void null undefined never
any any any any any any any any
unknown unknown unknown unknown unknown unknown unknown
string string string|void string|null string|undefined string
void void void|null void|undefined void
null null null|undefined null
undefined undefined undefined
never never

"strictNullChecks":true"strict":true(含strictNullChecks)によって以下のケースを除外できる。

let a: string = null; // strictNullChecks: trueではエラー

voidについて。
voidは関数の返り値が無いことを示すが、実際は値を返してもエラーにならない。
ただし、厳密にvoidではない場合はきちんとチェックされる。

type Returns<T> = () => T;

// 返り値型が void なら何返してもOK
const a: Returns<void> = () => 1; // OK

// 返り値型が string|void ならそれ以外はNG
const b: Returns<string|void> = () => 1; // NG

// void値は真偽値判定できないという違いもある
if (a()) { // エラー
  a() || true; // エラー
}

if (b()) { // OK
  b() || true; // OK
}

さきほどの表でstring|voidがそのまま残っているのはこのチェックのためだろう。

Intersection types

type Intersection<A, B> = A & B;

"strictNullChecks": falseの場合

A & B any unknown string void null undefined never
any any any any any any any never
unknown unknown void string null undefined never
string string string & void null undefined never
void void null undefined never
null null undefined never
undefined undefined never
never never

"strictNullChecks": trueの場合(異なる部分は太字)

A & B any unknown string void null undefined never
any any any any any any any never
unknown unknown void string null undefined never
string string string & void string & null string & undefined never
void void void & null void & undefined never
null null null & undefined never
undefined undefined never
never never

さっきと逆になると思いきや、ここでもanyが強い。
ただ、anyとneverはneverが勝つ。

type a = any | any & never; // => any
type b = (any | any) & never; // => never
type c = any | (any & never); // => any

型演算は右結合のようだ。

Extends

特殊な型同士のExtendsの関係性を見てみる。
継承というより代入可能性と考えたほうがわかりやすいかもしれない。

type Extends<A, B> = A extends B ? "" : "×";

"strictNullChecks": falseの場合

A extends B (B) any unknown string void null undefined never
(A) any ○|× ○|× ○|× ○|× ○|×
unknown × × × × ×
string × × × ×
void × × × ×
null ×
undefined ×
never never never never never never never never

"strictNullChecks": trueの場合(異なる部分は太字)

A extends B (B) any unknown string void null undefined never
(A) any ○|× ○|× ○|× ○|× ○|×
unknown × × × × ×
string × × × ×
void × × × ×
null × × × ×
undefined × × ×
never never never never never never never never

へんてこなのはanyとneverで、
anyはstringやvoidを継承している状態と継承していない状態の両方を併せ持っている。
neverはそもそも判定すらされず、全体がneverと評価されてしまう。

この挙動を利用して、例えばanyを判定する型を作ることができる。

// neverを継承するかの判定
// この型は、Tがanyのときtrue|false, neverのときnever, それ以外のときfalseになる
type ExtendsNever<T> = T extends never ? true : false;

// booleanはtrue|falseなので、booleanとマッチするかどうかでanyを取り出せる
// (ちなみにこの型はReactの型定義でも使われている)
type IsExactlyAny<T> = boolean extends ExtendsNever<T> ? true : false;

// 同じ要領でneverを判定
type IsExactlyNever<T> = false extends ExtendsNever<T> ? false : true;

// unknownを判定してみよう

ちなみに、union distributionを発生させないようにextendsを変えた場合、結果はまた違ったものになる。

type Extends<A, B> = A[] extends B[] ? "" : "×";
A[] extends B[] (B) any unknown string void null undefined never
(A) any
unknown × × × × ×
string × × × ×
void × × × ×
null × × × ×
undefined × × ×
never

never extends never ? true : falseはneverだけど、
never[] extends never[] ? true : falseはtrueというわけ。ややこしいね。

型パズルで遊ぶのもほどほどに。

16
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
8