I am trying to create a mapped tuple that drills down to the type of a member:
export type Argument<T = unknown> = {
value: T;
};
export type TypesOf<T extends Argument[]> = {[Index in keyof T]: T[Index]["value"]};
So, TypesOf<[{value: number}, {value: string}]>
should yield [number, string]
.
However, I get this error:
Type '"value"' cannot be used to index type 'T[Index]'.
EDIT:
Bonus question after applying @jcalz's solution:
const tuple = [{value: 1}, {value:"foo"}] as const;
type V = TypesOf<typeof tuple>;
I get error:
Type 'readonly [{ readonly value: 1; }, { readonly value: "foo"; }]' does not satisfy the constraint 'Argument<unknown>[]'.
The type 'readonly [{ readonly value: 1; }, { readonly value: "foo"; }]' is 'readonly' and cannot be assigned to the mutable type 'Argument<unknown>[]'.
CodePudding user response:
The TypesOf
implementation you present works, in that TypesOf<[{value: number}, {value: string}]>
does indeed evaluate to [number, string]
. Mapped types on arrays an tuples result in arrays and tuples.
But there is an issue, reported at microsoft/TypeScript#27995 where the compiler does not realize that inside the mapped type implementation {[I in keyof T]: ...T[I]...}
that I
will only end up being the numeric-like indices. It thinks that maybe I
will be things like "push"
and "pop"
, and so T[I]
cannot be assumed to be of type Argument
:
export type TypesOf<T extends Argument[]> =
{ [I in keyof T]: T[I]["value"] };
// -----------------> ~~~~~~~~~~~~~
// Type '"value"' cannot be used to index type 'T[I]'.
Presumably this is why you asked this question in the first place. The GitHub issue is listed as a bug, but it's been open for a long time and it's not clear if anything will happen here.
In cases like these I tend to use the Extract<T, U>
utility type to help convince the compiler that some type will be assignable to another. If you have the type T
that you know will be assignable to U
, but the compiler does not, you can use Extract<T, U>
in place of T
. When T
is specified later, Extract<T, U>
will evaluate to just T
if you are right about the assignability. And Extract<T, U>
will be seen by the compiler to be assignable to both T
and U
.
In our case, we know T[I]
will be assignable to Argument
but the compiler doesn't. So the following workaround using Extract<T[I], Argument>
will suppress the compiler error without affecting the output of TypesOf
:
export type TypesOf<T extends Argument[]> =
{ [I in keyof T]: Extract<T[I], Argument>["value"] }; // no error
type Z = TypesOf<[{ value: number }, { value: string }]>;
// type Z = [number, string]
Looks good!