Reputation: 53
export type MyTuple = ["test", "othertest"];
type NotWorking = {
[K in keyof MyTuple]: { value: MyTuple[K]};
};
type NotWorkingLengthType = NotWorking["length"]; // { value: 2 }
type Working<T>= {
[P in keyof T]: { value: T[P] };
};
type MappedWorking = Working<MyTuple>;
type MappedWorkingLengthType = MappedWorking["length"]; // 2
Why is it behaving differently in that case ? That puzzles me.
Upvotes: 5
Views: 1572
Reputation: 328433
This actually looks like a bug, see microsoft/TypeScript#27995.
Generally mapped types of the form {[K in keyof T]: ...T[K]...}
are considered to be homomorphic mapped types, and the structure of the input type T
is preserved as much as possible. This happens with optional and readonly
keys (see microsoft/TypeScript#12563), and was also the intent when mapped tuples/arrays were implemented in microsoft/TypeScript#26063.
In order for that to work, it means the compiler must look at [K in keyof T]
and remember to keep T
around after it has evaluated keyof T
. This happens when T
is a generic type, and for optional/readonly
keys this also happens when T
is some concrete type:
type MyObj = { a?: string, readonly b: number };
type MyMappedObj = { [K in keyof MyObj]: { value: MyObj[K] } }
/* type MyMappedObj = {
a?: {
value: string | undefined;
} | undefined;
readonly b: {
value: number;
};
} */
Note that this only works when your mapped type is explicitly iterating over keys with exactly "in keyof
". If you calculate your keys some other way, or assign them to a type alias, or even just parenthesize the keyof
expression, the spell is broken and the mapped type is no longer homomorphic:
type MyBadMappedObj = { [K in (keyof MyObj)]: { value: MyObj[K] } }
/* type MyMappedObj = {
a: { // not optional
value: string | undefined;
}
b: { // not readonly
value: number;
};
} */
So, mapping over keys like {[K in keyof T]: ...}
should preserve the structure of T
in the output.
Unfortunately, when the mapped arrays/tuples feature was introduced, it looks like this was only implemented for when T
is a generic type parameter, and not for a specific concrete type. In this comment, the implementer says:
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). We should probably also do it for homomorphic mapped types with a
keyof T
whereT
is non-generic type.
and that's where it is for now. Maybe this will eventually be fixed. Until then, you should probably use an intermediate generic type like your Working
example as a workaround.
Upvotes: 4
Reputation: 249706
Tuples and Array types are only preserved for homomorphic mapped types. A homomorphic mapped type is one that maps over keyof T
, where T is a type parameter to the type, or K
where K
is a type parameter of the type, with K extends keyof T
. This behavior is described in the PR that added preserving tuples and arrays in mapped types.
Without being specially preserved, when mapping a tuple all properties are mapped, including length
and you get that less than useful result.
Upvotes: 0