Type tuples no longer inferred properly from rest arguments with mapped generic type tuples.
See original GitHub issueI recently updated the dependencies for a TypeScript-based project and unfortunately ended up with some unexpected breakage.
The TypeScript version was changed from 4.6 to 4.7 and type tuples are no longer inferred properly from rest arguments with mapped generic type tuples.
type MyMappedType<Primitive extends any> = {
primitive: Primitive;
};
The project uses several helper types to map type tuples to other type tuples as shown below.
type TupleMapperOld<Tuple extends any[]> = {
[Key in keyof Tuple]: Tuple[Key] extends Tuple[number] ? MyMappedType<Tuple[Key]> : never;
};
After updating to TypeScript 4.7 it is still possible to use the helper directly. TypeScript will correctly infer the type as expected.
// Type should be [MyMappedType<string>, MyMappedType<number>] in TypeScript 4.6 and TypeScript 4.7.
type MyMappedTupleOld = TupleMapperOld<[string, number]>;
However, type inference breaks when using the mapper to map the rest arguments of a generic function as shown below.
function extractPrimitivesOld<Tuple extends any[]>(...mappedTypes: TupleMapperOld<Tuple>): Tuple {
return mappedTypes.map((mappedType) => mappedType.primitive) as Tuple;
}
// Inferred type should be [string, number] but becomes [unknown, unknown] in TypeScript 4.7.
const myPrimitiveTupleOld = extractPrimitivesOld({ primitive: "" }, { primitive: 0 });
The problem can be mitigated by explicitly specifying the generic type tuple argument. It can also be solved by removing the mapping constraints in the mapper as shown below.
type TupleMapperNew<Tuple extends any[]> = {
[Key in keyof Tuple]: MyMappedType<Tuple[Key]>;
};
I decided to open this issue and let you decide if this is something worth investigating since I couldn’t find any information about the difference in behaviour in the release notes.
🔎 Search Terms
tuple mapping type inference rest arguments generics
🕗 Version & Regression Information
This changed between versions 4.6 and 4.7.
⏯ Playground Link
💻 Code
type MyMappedType<Primitive extends any> = {
primitive: Primitive;
};
type TupleMapperOld<Tuple extends any[]> = {
[Key in keyof Tuple]: Tuple[Key] extends Tuple[number] ? MyMappedType<Tuple[Key]> : never;
};
// Type should be [MyMappedType<string>, MyMappedType<number>] in TypeScript 4.6 and TypeScript 4.7.
type MyMappedTupleOld = TupleMapperOld<[string, number]>;
function extractPrimitivesOld<Tuple extends any[]>(...mappedTypes: TupleMapperOld<Tuple>): Tuple {
return mappedTypes.map((mappedType) => mappedType.primitive) as Tuple;
}
// Inferred type should be [string, number] but becomes [unknown, unknown] in TypeScript 4.7.
const myPrimitiveTupleOld = extractPrimitivesOld({ primitive: "" }, { primitive: 0 });
// ^?
type TupleMapperNew<Tuple extends any[]> = {
[Key in keyof Tuple]: MyMappedType<Tuple[Key]>;
};
// Type should be [MyMappedType<string>, MyMappedType<number>] in TypeScript 4.6 and TypeScript 4.7.
type MyMappedTupleNew = TupleMapperNew<[string, number]>;
function extractPrimitivesNew<Tuple extends any[]>(...mappedTypes: TupleMapperNew<Tuple>): Tuple {
return mappedTypes.map((mappedType) => mappedType.primitive) as Tuple;
}
// Inferred type should be [string, number] in TypeScript 4.6 and TypeScript 4.7.
const myPrimitiveTupleNew = extractPrimitivesNew({ primitive: "" }, { primitive: 0 });
🙁 Actual behavior
The type tuple is inferred as [unknown, unknown] in TypeScript 4.7.
🙂 Expected behavior
The type tuple is inferred as [string, number] in TypeScript 4.7.
Issue Analytics
- State:
- Created a year ago
- Reactions:2
- Comments:8 (5 by maintainers)
Top GitHub Comments
The change between v4.6.4 and v4.7.4 occurred at 787bb9ddb60368f84b1ed1b0971aa2bf79fdec77.
Helpers like the conditionally mapped type
TupleMapperOld
is what you find when you search the web for type tuple mapping in TypeScript even though it no longer seems to be needed.I started using the pattern a few years ago due to having issues with unwanted mapping over non-numeric keys and I believe that the pattern is somewhat well-established in code bases. The fix is simple, just remove the conditional, but the weird behavior of lost inference in certain circumstances might leave people puzzled and could, as you say, indicate a bug.