readonlyの配列でincludesやindexOfをするとneverになってどうしようもなくなる


コードがいろいろうまく行かなかったのでこのstackoverflowにたどり着きました。

https://stackoverflow.com/questions/69368524/readonly-array-union-cannot-be-searched


# 問題

```typescript
const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

areas[area].adjacencies.includes(5);
                                 ^
```
というコードがあります。このincludes(5)でエラーが出ます。
> Argument of type 'number' is not assignable to parameter of type 'never'

ここでのincludesの定義はこうです。
> (method) ReadonlyArray<T>.includes(searchElement: never, fromIndex: number | undefined): boolean

見事に引数がnever型になっていますね。
次にこのコードを見てください。1行変えただけです。

```typescript
const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [5, 6, 7, 8]　//5を足しただけ
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

areas[area].adjacencies.includes(5);
```

コンパイルエラーは起きません。includesの型定義はこうです。引数を5のみ受け付けています。
> (method) ReadonlyArray<T>.includes(searchElement: 5, fromIndex: number | undefined): boolean

[2, 3, 4, 5]と[5, 6, 7, 8]の両方に共通しているリテラルだけ抜き出しているのです。

# Q. どうするか？
A. <b>どうしようもない</b>
https://github.com/microsoft/TypeScript/issues/14520

# 暫定策

## as constを外す

```typescript
const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
};

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

areas[area].adjacencies.includes(5);

```

こうすることで引数がnumberになります。
> (method) Array<number>.includes(searchElement: number, fromIndex?: number | undefined): boolean

## as const number[]をつける

```typescript
const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5] as const number[]
  },
  area2: {
    adjacencies: [6, 7, 8] as const number[]
  }
};

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

areas[area].adjacencies.includes(5);

```

こうすることで引数がreadonly number[]になります。

## anyにキャストする
```typescript
const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

(areas[area].adjacencies as any).includes(5);
```
万能の方法です。一時しのぎだけならこれで十分。

## unknownにキャストしていい感じの型にキャストする

```typescript
const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

(areas[area].adjacencies as unknown as ElementUnion[]).includes(5);
```

なんとなく一番いいのはこれな気がします。