Description
TypeScript Version: 3.4.0-dev.20190130
Search Terms:
optional tuple, mapped tuple
Hi, this is a bug report that demonstrates inconsistencies when using mapped tuple types. Different behaviours are demonstrated in this scenario when using mapped tuple types inside and outside a given interface or type. The following code is detailed with comments & questions.
I also left a few other questions in the code about inconsistencies I've encountered. I'd be glad if someone could help me with those (or I'll open issues for those as well)
Code
// Optionalize* is an interface that takes a Function as a generic
// The arguments of the generic are used to compute prototypes
// I wanted use the generic's parameters & make them optional
interface Optionalize01<F extends ((...args: any[]) => any)> {
(...args: Partial<Parameters<F>>): ReturnType<F>
// [ts] A rest parameter must be of an array type. [2370]
}
// Though, this works perfectly, so is it related to the interface only ?
const strange = (...args: Partial<Parameters<(...args: any[]) => any>>) => {}
// But works by doing this tuple trick, can someone explain why?
type Optional<T extends any[]> = // It takes a Tuple type and removes the first type
((...args: T) => any) extends ((head: any, ...tail: infer U) => any) ? Partial<U>
: [] // I usually use this trick to manipulate tuples (that are missing features)
// I'm using this because I can't directly do Parameters<F>[0]
// [ts] Type '0' cannot be used to index type 'Parameters<F>'. [2536]
type Head<T extends any[]> = T[0] // So why should this work?
// So there we are, this is the working example, with tricks
interface Optionalize02<F extends ((...args: any[]) => any)> {
(arg0?: Head<Parameters<F>>, ...args: Optional<Parameters<F>>): ReturnType<F>
}
// Now I can make use of my interface to make any method's arguments optional
const optionalize = <F extends (...args: any) => any>(f: F): Optionalize02<F> => {
// @ts-ignore
return undefined
// We don't care of the implementation of `optionalize` for this example
}
const optionalized = optionalize((a: 1, b: number, c: string) => 'hello')
optionalized() // It Works, but why was it so hard?
export {}
EDIT: Same behaviour with types, it is not limited to interfaces.
type Optionalize01<F extends ((...args: any[]) => any)> =
(...args: Partial<Parameters<F>>) => ReturnType<F>
// [ts] A rest parameter must be of an array type. [2370]
Expected behavior:
A mapped (tuple) type should preserve the tuple, in this example when Partial is applied to Parameters on a rest parameter inside an interface or a type.
Actual behavior:
Works with tricks (see code).
Playground Link:
Related Issues:
#25947