This is a known bug in TypeScript where the support (added in TS3.1) for mapped types over tuples and arrays does not exist inside the implementation of such mapped types; see microsoft/TypeScript#27995. It seems that according to the lead architect of TypeScript:
The issue here is that we only map to tuple and array types when we instantiate a generic homomorphic mapped type for a tuple or array (see #26063).
So from the outside, when you use a mapped type on an array or tuple, the mapping preserves the array-or-tupleness of the input and only maps over numeric properties:
declare const foo: Class<["a", "b", "c"]>;
// (property) prop: [Generic<"a">, Generic<"b">, Generic<"c">]
const zero = foo.prop[0]; // Generic<"a">;
const one = foo.prop[1]; // Generic<"b">;
but on the inside, the compiler still sees P in keyof T
as iterating over every key of T
, including any possibly non-numeric ones.
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T]: Generic<T[P]> } // error!
}
As you note, there are workarounds for this, and these workarounds are mentioned in microsoft/TypeScript#27995. I think the best one is essentially the same as your conditional type:
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T]: Generic<Extract<T[P], string>> }
}
The other ones in there either don't work for generic types like T
, or produce mapped types that are no longer true arrays or tuples (e.g., {0: Generic<"a">, 1: Generic<"b">, 2: Generic<"c">}
instead of [Generic<"a">, Generic<"b">, Generic<"c">]
... so I'll leave them out of this answer.
Playground link to code