1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TypeScriptでユニオン型の重複チェックを行う方法

Posted at

TypeScriptで複数のユニオン型を定義し、それらのユニオン型に重複がないかをチェックしたい場合があります。例えば、以下のように複数のユニオン型を定義したとします。

type SubUnion1 = "one" | "two";
type SubUnion2 = "two" | "three";
type SubUnion3 = "nine" | "ten";

これらのユニオン型を1つのユニオン型にまとめたいとき
次のように書くとts的にはエラーが出ないので重複に気づくことができません。

/// Initial type:"one" | "two" | "two" | "three" | "nine" | "ten" となり特にエラーは出ない
type Union = SubUnion1 | SubUnion2 | SubUnion3 

ユニオン型の重複チェック方法

まず、各ユニオン型をタプルにまとめます。

type UnionList = [SubUnion1,SubUnion2,SubUnion3]

次に、重複があるかどうかをチェックする型を定義します。この型は、重複がなければneverを返し、重複があれば重複情報を含むタプルのユニオン型を返します。

type Overlaps<T extends string[]> = {
    [K in keyof T]: {
        [L in keyof T]: L extends K
            ? never
            : T[K] & T[L] extends never
              ? never
              : ['次のインデックスに', K | L, '次の重複があります', T[K] & T[L]];
    }[number];
}[number];

この型は複雑なのでOverlaps<UnionList>と定義したものとして
一つずつ説明します。

  1. 外側のマッピング [K in keyof T]
    これは、タプル T の各要素に対してインデックス K を使ってマッピングを行います。
    K はタプルのインデックスを表します。
    (KUnionListのインデックスである0,1,2)
  2. 内側のマッピング [L in keyof T]
    内側のマッピングでは、同じタプル T の各要素に対してインデックス L を使ってマッピングを行います。
    (同様にTはUnionListのインデックスである0,1,2)
    これにより、二重ループのような構造が作られ、各要素の組み合わせ (KL) をチェックすることができます。
  3. インデックスが同じ場合の処理 L extends K ? never:
    インデックス LK と同じ場合(つまり、同じ要素同士の場合)は処理をスキップするために never を返します。
  4. 重複がない場合の処理 T[K] & T[L] extends never ? never
    T[K]T[L] の交差型が never である場合、つまり重複がない場合は never を返します。
    (SubUnion2SubUnion3の比較の時、交差型がneverなのでneverを返す)
  5. 重複がある場合の処理['次のインデックスに', K | L, '次の重複があります', T[K] & T[L]]
    T[K]T[L] の交差型が never でない場合、重複があるとみなします。
    重複がある場合、重複の詳細情報を含むタプルを生成します。ここでは、インデックス KL、および重複している値 T[K] & T[L] を含むタプルを作成します。
    (SubUnion1SubUnion2の比較の時、交差型がtwoなので['次のインデックスに', 0|1, '次の重複があります',twoを返す)
  6. 内側のマッピングの結果をユニオン型に変換 [number]
    内側のマッピングの結果をユニオン型に変換します。これにより、全てのインデックスの結果が1つのユニオン型にまとめられます。
  7. 外側のマッピングの結果をユニオン型に変換 [number]
    同じく、外側のマッピングの結果もユニオン型に変換します。

次に、重複がある場合にコンパイルエラーを発生させるための型関数を定義します。

type ExpectNever<T extends never> = void;

これらを組み合わせて、重複チェックを行います。

// エラーが発生する TS2344: Type ["次のインデックスに", "0" | "1", "次の重複があります", "two"] does not satisfy the constraint never
type EnsureNoOverlapsOfSubUnions = ExpectNever<Overlaps<SubUnions>>;

最後に、タプルの要素をユニオン型にまとめることで、元のユニオン型を取得できます。

type Union = UnionList[number];

まとめ

以上をまとめると次のようになります。

type UnionList = [SubUnion1,SubUnion2,SubUnion3]
type Union = UnionList[number]


type Overlaps<T extends string[]> = {
    [K in keyof T]: {
        [L in keyof T]: L extends K
            ? never
            : T[K] & T[L] extends never
              ? never
              : ['次のインデックスに', K | L, '次の重複があります', T[K] & T[L]];
    }[number];
}[number];

type ExpectNever<_ extends never> = void;
// 重複を確認するための型重複があった場合下記がエラーになり気づける
type EnsureNoOverlapsOfSubUnions = ExpectNever<Overlaps<UnionList>>;


1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?