In TypeScript this is not compiling:
export interface Generic<T extends string> {
}
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T]: Generic<T[P]> }
}
Particularly, the Generic<T[P]>
fails with Type 'T[P]' does not satisfy the constraint 'string'.
. However, since T extends string[]
, it can be certain, that T[P] extends string
for any P in keyof T
.
What am I doing wrong here?
I know I can fix the problem with a conditional type:
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T]: T[P] extends string ? Generic<T[P]> : never }
}
But I don't see, why this should be necessary.
If you look at the full error, the third line has a big clue:
Type 'T[P]' does not satisfy the constraint 'string'.
Type 'T[keyof T]' is not assignable to type 'string'.
Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'string'.
Type 'T[string]' is not assignable to type 'string'.(2344)
The problem is that keyof
any array type (or tuple type) will be more like string | number | symbol
. And an array also has more than it's member type on those keys. For instance:
// (...items: string[]) => number
type PushFunction = string[]['push']
See this snippet. There's a lot more than just numbers in array keys:
// number | "0" | "1" | "2" | "length" | "toString"
// | "toLocaleString" | "pop" | "push" | "concat"
// | "join" | "reverse" | "shift" | "slice" | "sort"
// | "splice" | "unshift" | "indexOf"
// | ... 15 more ... | "includes"
type ArrayKeys = keyof [1,2,3]
And Generic<T>
requires T
to be a string, but, as shown, not all values of all keys of an array are strings.
You can fix the mapped type very simply by intersecting the array keys with number
, informing typescript that you only care about the number keys (which are the array indices):
export interface Generic<T extends string> {
}
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T & number]: Generic<T[P]> }
}