Skip to content

Generic rest parameters can't be combined with additional arguments #38134

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

Closed
matthewvalentine opened this issue Apr 23, 2020 · 1 comment
Closed
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@matthewvalentine
Copy link

TypeScript Version: 3.9.0-dev.20200422

Search Terms:
Rest, variadic, tuple, function, generic

Code

// Let's say I want to make a decorator. It takes in a function fn and returns one that
//  1. Takes an array of fn's first argument, rather than just one
//  2. Then takes an extra argument before the rest of fn's parameters
//  3. Then takes the rest of fn's parameters as-is.
// And let's say we already have a decorator that does 1 & 3, so we just want to insert
// that extra argument. Coding it is easy! Typing it is not.
//
// Though it presents slightly differently, the error seems to stem from tuples getting 'blurred'.
// They turn from R into R[number][] or from Parameters<F> into Parameters<F>[number][],
// which is similar but makes every parameter type into a union of all of them. 

// ATTEMPTS THAT DO NOT WORK
// Using generic rest argument
function change1R<T, R extends any[]>(
    fn: (t: T, ...rest: R) => void
) {
    return (ts: T[], ...rest: R) => fn(ts[0], ...rest);
}

function change1Add2R<T, R extends any[]>(
    fn: (t: T, ...rest: R) => void
) {
    // ERROR: R[number][] is not assignable to type R
    return change1R((t: T, x: number, ...rest: R) => {
        console.log(x);
        fn(t, ...rest);
    });
}

// Using generic function type for Parameters
function change1F<T, F extends (...args: any[]) => any>(
    fn: (t: T, ...rest: Parameters<F>) => void
) {
    return (ts: T[], ...rest: Parameters<F>) => fn(ts[0], ...rest);
}

function change1Add2F<T, F extends (...args: any[]) => any>(
    fn: (t: T, ...rest: Parameters<F>) => void
) {
    // ERROR: Property 0 is missing in type any[] but required in type
    // [number, ...Parameters<F>[number][]]
    return change1F((t: T, x: number, ...rest: Parameters<F>) => {
        console.log(x);
        fn(t, ...rest);
    });
}

// Using both together
function change1B<T, R extends any[], F extends (t: T, ...rest: R) => any>(
    fn: F,
) {
    return (ts: T[], ...rest: R) => fn(ts[0], ...rest);
}

function change1Add2B<T, R extends any[], F extends (t: T, ...rest: R) => any>(
    fn: F,
) {
    // ERROR: Property 0 is missing in type any[] but required in type [number, ...R[number][]]
    return change1F((t: T, x: number, ...rest: R) => {
        console.log(x);
        fn(t, ...rest);
    });
}

// ONE THAT WORKS
// Using complicated type inspection
type Head<F> = F extends (head: infer Head, ...tail: any) => any ? Head : never;
type Tail<F> = F extends (head: any, ...args: infer Tail) => any ? Tail : never;

function change1C<F extends (...args: any) => any>(
    fn: F,
) {
    // If you don't explicitly specify the return type here, then it will be inferred as 'any'
    return (ts: Head<F>[], ...rest: Tail<F>): ReturnType<F> => fn(ts[0], ...rest);
}

function change1Add2C<F extends (...args: any) => any>(
    fn: F,
) {
    // If you don't explicitly specify the return type here, then it will be inferred as 'any'
    return change1C((t: Head<F>, x: number, ...rest: Tail<F>): ReturnType<F> => {
        console.log(x);
        return fn(t, ...rest);
    })
}

Expected behavior:

The first attempt above (or others) should work. At least, even if there are different errors, the type R[number][] should not appear in any circumstance.

Actual behavior:

The errors encountered are written above. In general, the error seems to stem from tuples getting 'blurred'. They turn from R into R[number][] or from Parameters<F> into Parameters<F>[number][], which are similar but make every parameter type into a union of all of them. Then this type mismatches with the actual parameters type, producing the error.

Playground Link:

Link to Playground

Related Issues:

Hard to say - possibly #35154 ?

@ahejlsberg
Copy link
Member

This is a known design limitation. We'd need the ability represent and reason about tuple types such as [number, ...R] where R is a type parameter R extends any[]. We currently require the rest part of a tuple to be an array type and that's why you're seeing the "blurring" to [number, ...R[number][]]. It would be nice to lift this restriction, but it's not trivial.

@ahejlsberg ahejlsberg added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Apr 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

3 participants