型が同じであることをチェックする型

TypeScript で以下のようなコードを作成する機会がありました。

```ts
// 以下のコードは、Code という型と CodeList という型の配列を定義し、
// setupCodeList という関数に CodeList の値を渡して呼び出す例です。

const CODES = {
    Foo: "001",
    Bar: "002",
} as const;
type Code = (typeof CODES)[keyof typeof CODES];
type CodeList = { name: string; code: Code }[];

// setupCodeList は、CodeList 型の引数 value を受け取り、
// value を使った処理を行う関数です。
function setupCodeList(value: CodeList) {
    // ...
}

const list = [
    {
        name: "Foo",
        code: CODES.Foo,
    },
    {
        name: "Bar",
        code: CODES.Bar,
    },
];

setupCodeList(list);
```

ここで、`list` 中に `code` の値の設定漏れがあったときに、型でエラーになるようにしたいです。

```ts
const list = [
    {
        name: "Foo",
        code: CODES.Foo,
    },
];

// TODO: `code: "002"` が含まれていないため、型エラーにしたい
```

### 試したこと 1

以下のようなコードを試しました。

```ts
// 漏れがあったときに型エラーにしたい
type Equal<A, B> = A extends B ? (B extends A ? true : false) : false;
let check: Equal<Code, (typeof list)[number]["code"]> = true;
```

試しに Code の部分を別の型に変えてみると、エラーになってくれました。

```ts
// 漏れがあったときに型エラーにしたい
type Equal<A, B> = A extends B ? (B extends A ? true : false) : false;
let check: Equal<string, (typeof list)[number]["code"]> = true;
//-> 以下のエラーが発生した
// Type 'true' is not assignable to type 'false'.(2322)
```

これでよさそうと思ったのですが、以下を試したところなぜか期待通り動きませんでした...

```ts
const list = [
    {
        name: "Foo",
        code: CODES.Foo,
    },
];

// 漏れがあったときに型エラーにしたい
type Equal<A, B> = A extends B ? (B extends A ? true : false) : false;
let check: Equal<Code, (typeof list)[number]["code"]> = true;
//-> `code: CODES.Bar` が含まれていないのでエラーになってほしかったが、エラーにならなかった...
```

#### うまくいかなかった理由

以下の記事に詳細が書かれていました。

https://zenn.dev/yumemi_inc/articles/ff981be751d26c#tl%3Bdr

> コンパイラは Conditional Types (T1 extends U1 ? X1 : Y1) のうち T1 が型パラメータ単体だった場合は必ず遅延評価する。この処理は [Distributive Conditional Types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types) のために存在する。

今回はこの T1 部分に型パラメータ単体を指定しており、`Equal<"001", "001" | "002">` が `Equal<"001", "001"> | Equal<"001", "002">` のような感じで評価された（？）ためにエラーにならなかったようです。

### 試したこと 2

上の記事にも書かれていた [type-challenges のソースコード](https://github.com/type-challenges/type-challenges/blob/fbd74f4067fb43ebbf020f864ef404e99deb585f/utils/index.d.ts#L1) を使って書き替えてみました。

コードの意図もより明確になったように感じます。

```ts
// 以下のコードは、Code という型と CodeList という型の配列を定義し、
// setupCodeList という関数に CodeList の値を渡して呼び出す例です。

const CODES = {
    Foo: "001",
    Bar: "002",
} as const;
type Code = (typeof CODES)[keyof typeof CODES];
type CodeList = { name: string; code: Code }[];

// setupCodeList は、CodeList 型の引数 value を受け取り、
// value を使った処理を行う関数です。
function setupCodeList(value: CodeList) {
    // ...
}

const list = [
    {
        name: "Foo",
        code: CODES.Foo,
    },
    {
        name: "Bar",
        code: CODES.Bar,
    },
];

// type-challenges のコードを追加した
// https://github.com/type-challenges/type-challenges/blob/fbd74f4067fb43ebbf020f864ef404e99deb585f/utils/index.d.ts#L1
type Expect<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y
    ? 1
    : 2
    ? true
    : false;
type Check = Expect<Equal<Code, (typeof list)[number]["code"]>>;

setupCodeList(list);
```

list から CODES.Bar を消してみるとエラーになります。

```ts
const list = [
    {
        name: "Foo",
        code: CODES.Foo,
    },
];

// type-challenges のコードを追加した
// https://github.com/type-challenges/type-challenges/blob/fbd74f4067fb43ebbf020f864ef404e99deb585f/utils/index.d.ts#L1
type Expect<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y
    ? 1
    : 2
    ? true
    : false;
type Check = Expect<Equal<Code, (typeof list)[number]["code"]>>;
//-> Type 'false' does not satisfy the constraint 'true'.
```

このコードがうまく機能する理由を知るにはコンパイラのソースを読む必要がありそうです...