Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problem between Tuple types and Mapped Tuple types #29654

Closed
millsp opened this issue Jan 30, 2019 · 0 comments
Closed

Problem between Tuple types and Mapped Tuple types #29654

millsp opened this issue Jan 30, 2019 · 0 comments

Comments

@millsp
Copy link
Contributor

millsp commented Jan 30, 2019

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

@millsp millsp closed this as completed Jan 31, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant