コードがいろいろうまく行かなかったのでこのstackoverflowにたどり着きました。
問題
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.includes(searchElement: never, fromIndex: number | undefined): boolean
見事に引数がnever型になっていますね。
次にこのコードを見てください。1行変えただけです。
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.includes(searchElement: 5, fromIndex: number | undefined): boolean
[2, 3, 4, 5]と[5, 6, 7, 8]の両方に共通しているリテラルだけ抜き出しているのです。
Q. どうするか?
A. どうしようもない
https://github.com/microsoft/TypeScript/issues/14520
暫定策
as constを外す
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.includes(searchElement: number, fromIndex?: number | undefined): boolean
as const number[]をつける
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にキャストする
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にキャストしていい感じの型にキャストする
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);
なんとなく一番いいのはこれな気がします。