Jemima P.D.
Jemima P.D.

Reputation: 25

Typescript passing arguments with spread syntax throws 'Expected x arguments, but got x or more.' error (TS2556)

I wrote a short example to explain the situation occurred to my code.

I have two functions resembles to following:

function sum3(a: number, b: number, c: number): number {
  return a + b + c
}

function plus1(...args: number[]): number[] {
  return args.map(x => x + 1)
}

function sum3, the first one, needs exact 3 number arguments. e.g. sum3(1, 2, 3) returns 6.

function plus, the second one, has no limit on the number of arguments. e.g. plus1(100, 200) returns Array of number [101, 201], plus1(300, 400, 500) returns [301, 401, 501].

The error happened when I tried to call the function sum3 with spread syntax.

// javascript returns 303
// typescript throws an error: Expected 3 arguments, but got 1 or more. (TS2556)
sum3(101, ...plus1(100, 100))

My typescript version is the newest, 4.2.4. Is this a bug?

Upvotes: 0

Views: 1289

Answers (2)

Roberto Zvjerković
Roberto Zvjerković

Reputation: 10127

Well I actually solved your problem, but I got another error which I cannot solve right now.

This implementation returns a type with the correct number of elements in the tuple, without a need for overloads:

function sum3(a: number, b: number, c: number): number {
    return a + b + c;
}

function plus1<T extends Array<number>>(...args: T): [...T] {
    return args.map(x => x + 1); // Target requires 1 element(s) but source may have fewer.
    //  return args.map(x => x + 1) as [...T]; // No error with casting
}

sum3(101, ...plus1(100, 100)); // [100, 100]
sum3(101, ...plus1(100, 100, 100)); // [100, 100, 100], Expected 3 arguments, but got 4.

I believe the reason for the error is that Array.map typings don't use variadic tuples, they return arrays instead.

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1074148

No, it's not a bug. Think about the general case: plus1 just says it returns number[]. That could have zero, one, two, three, four, five, or any other number of elements in it. But sum3 expects exactly three (one from the other argument, and two from plus1).

One solution is a bunch of explicit overloads (playground link):

function plus1(a: number, b: number): [number, number];
function plus1(a: number, b: number, c: number): [number, number, number];
function plus1(a: number, b: number, c: number, d: number): [number, number, number, number];
function plus1(a: number, b: number, c: number, d: number, e: number): [number, number, number, number, number];
function plus1(...args: number[]): number[] {
    return args.map(x => x + 1)
}

Then your call works, because it matches the function plus1(a: number, b: number): [number, number] signature. That would work for up to five arguments, after which the overload returning number[] would kick in.

Another solution is to use a type assertion on your plus1 call saying that it returns [number, number], not number[] (playground link):

sum3(101, ...(plus1(100, 100) as [number, number]));

...but type assertions are best avoided. For example, if you added a third argument to your plus1 call, the assertion would be incorrect, but TypeScript would have no way of knowing that.

Upvotes: 0

Related Questions