Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

keyof array type in TS is strange

interface PPP {
  1: number
  2: string
  4: boolean
}

type Map1<T extends any[]> = {[K in keyof T]: T[K] extends keyof PPP ? PPP[T[K]] : never}
type Map2<T extends any[], U extends keyof T> = {[K in U]: T[K] extends keyof PPP ? PPP[T[K]] : never}

type Y1 = Map1<[1, 2]> // type Y1 = [number, string]
type Y2 = Map2<[1, 2], keyof [1, 2]> // type Y2 is not same as Y1
// type Y2 = {
//     [x: number]: string | number;
//     0: number;
//     1: string;
//     length: string;
//     toString: never;
//     toLocaleString: never;
//     pop: never;
//     push: never;
//     concat: never;
//     join: never;
//     reverse: never;
//     shift: never;
//     slice: never;
//     sort: never;
//     ... 20 more ...;
//     flat: never;
// }

As I know about TS, I would think Map1 is equivalent to Map2, however Y1 is not same as Y2, so there must be something that I don't know.

Question: what is the reason that causes this difference.

like image 920
LiuXiMin Avatar asked Sep 19 '25 02:09

LiuXiMin


1 Answers

Typescript runs the type checking at build time, not runtime. Consider the following:

const a: (1 | 2 | 4)[] = [1, 1, 1, 1, 1]
const b: (1 | 2 | 4)[] = [1, 2, 4]

Both a and b are valid (since they are arrays that contain items that are either 1, 2 or 4.

keyof T on typescript lists all the properties of the type T. For an array, you can also run array.toString() or array.push(). Hence, toString and push appears as valid times.

Typescript will never be able to list, at compile time, the items on the array as your type.


Update

Indeed, there is a bug on typescript regarding the different behaviour between keyof T coming from type argument to the keyof T in the type value. Bug link

You can, though, bypass this issue with an Exclude

Link for the playground

interface PPP {
  1: number
  2: string
  4: boolean
}

type Map1 < T extends any[] > = {
  [K in keyof T]: T[K] extends keyof PPP ? PPP[T[K]] : never
}
type Map2 < T extends any[], U extends keyof T > = {
  [K in U]: T[K] extends keyof PPP ? PPP[T[K]] : never
}

type Map3 < T extends any[], U extends keyof T > = {
  [K in Exclude < U, keyof any[] > ]: T[K] extends keyof PPP ? PPP[T[K]] : never
}




type Y1 = Map1 < [1, 2] > // type Y1 = [number, string]
  type Y2 = Map2 < [1, 2], keyof[1, 2] > // type Y2 is not same as Y1
  type Y3 = Map3 < [1, 2], keyof[1, 2] > // Same as Y3
like image 157
Guilhermevrs Avatar answered Sep 23 '25 11:09

Guilhermevrs