2

For this function:

function foo(a: number, b: number) {/* ... */}

Snippet 1 causes an error:

foo(1, ...[]);

Expected 2 arguments, but got 1 or more.

Snippet 2 causes an error:

foo(1, 2, ...[]);

Expected 2 arguments, but got 3 or more.

The thing that looks strange is that it's not "got 2 or more" in second case. The code doesn't try to address any specific problem, just interested to know what exactly happens.

Why are these snippets 1 and 2 treated differently by TypeScript?


For users who found this question because of the same error, a way to fix it is to type spreaded arguments more strictly:

foo(1, ...[2] as const);
foo(1, ...[2] as [number]);
foo(1, 2, ...[] as const);
foo(1, 2, ...[] as []);
2
  • To be clear, your question is "why is it saying 3 instead of 2?" and not "why are spread array literals widened to arrays instead of being maintained as tuple types?" Right? Commented Jun 15, 2020 at 15:06
  • Thanks a lot for the detailed answer. I'll keep watching the issues you referred to. Yes, tuples indeed wasn't the concern. Commented Jun 15, 2020 at 17:59

1 Answer 1

1

Here's a minimal example that doesn't have any "shouldn't-this-be-a-tuple" aspect to it:

declare const arr: number[];
function foo(a: number, b: number) {/* ... */ }

foo(...arr); // expected 2, got 0 or more
foo(1, ...arr); // expected 2, got 1 or more
foo(1, 2, ...arr) // expected 2, got *3* or more ?!
foo(1, 2, 3, ...arr) // expected 2, got *4* or more ?!

Those are all errors; the in first two cases the compiler must error because the function may not be called with enough arguments.

In the last two cases one could argue that there should be no error because it's usually safe to call a function with more parameters than it needs (see the TypeScript FAQ for this principle being applied to assignability of function types), but the compiler produces an error here anyway.

You're not asking "why is there an error", though. You're asking "why is there that error"? After all, by rights the issue is that foo(1, 2, ...arr) is passing 2 or more arguments and the compiler is enforcing exactly 2. So why does the compiler say you're passing "3 or more" and not "2 or more"?

This seems to be an intentional decision, according to microsoft/TypeScript#20071, the pull request that introduced errors for excess spread arguments:

This PR also changes the error for excess spread arguments to be more understandable:

"Expected 3 arguments, but got least 4".

Previously the error would have been "Expected 3 arguments, but got at least 3", which is, again, technically correct, but not understandable.

I suppose that users seeing "expected 2, but got at least 2" would be confused, since "at least 2" certainly seems compatible with "2". Instead they decided to write out a message that would definitely be an error condition, if not an accurate one. That's the official answer.

You can see in these lines that, given an argument list like (a1, a2, a3, ...spreadArg) the spread argument is counted if you have enough arguments without it; otherwise it's not. So if it expects 2 arguments, then (1, ...arr) is counted as 1 argument, but (1, 2, ...arr) is counted as 3.


Frankly after researching this I'm not too happy about it, since I can imagine error messages that are correct while not being too unclear (in my opinion):

foo(...arr); // expected exactly 2 arguments, but maybe got too few or too many
foo(1, ...arr); // expected exactly 2 arguments, but maybe got too few or too many 
foo(1, 2, ...arr); // expected exactly 2 arguments, but maybe got too many
foo(1, 2, 3, ...arr); // expected exactly 2 arguments, but got too many

This has been reported before, in microsoft/TypeScript#20097 (closed as "working as intended") and in microsoft/TypeScript#20372 (open as "help wanted"), so if you care a lot about it, you (or anyone reading this) could give one of those a 👍 or even submit a pull request fixing it.


Okay, hope that helps; good luck!

Playground link fwiw

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.