TypeScript Version: 3.1.0-dev.20180809
Search Terms: tuple spread function call
Code
function foo (a: 1, b: 2, c: 3) { }
const bar: [1, 2] = [1, 2];
foo(...bar, 3);
Expected behavior:
No error.
Actual behavior:
On the third line: Expected 3 arguments, but got 1 or more.
Notes:
If this is intended to work this way, or is simply not implemented yet, this issue can be edited into a feature request.
First use case I can think of is my Vector2/3 classes, which support xy: [number, number] and xyz: [number, number, number]. I have some methods which require x, y, and z values, and do not want a vector (legacy code). It would be nice to be able to spread into them.
Another use pertaining to my vector example is constructing a V3 from a V2, new Vector3(...v2.xy, z)
Also, this already works if the tuple exactly matches the method call, example:
const baz: [1, 2, 3] = [1, 2, 3];
foo(...baz);
This seems to be related to #26058 (at least the merging part of that proposal)
I saw that issue but wasn't sure how related it would be, because you can't spread into tuples at all, but you can spread into function calls when the arguments exactly match the tuple.
const x: [1, 2, 3] = [1, 2, 3];
const y = [...x]; // (1 | 2 | 3)[]
function foo(a: 1, b: 2, c: 3) {}
foo(...x); // works
Guess I should have put it in the related section anyway 馃槢
It is currently working as intended, so I'm changing this issue to a suggestion. The compiler only flattens tuples into parameter lists when they occur in a spread parameter in the last parameter position. When a spread parameter occurs earlier than the last parameter position, the remaining parameters are considered to just be part of the spread, i.e. foo(x, ...y, z) is checked as foo(x, ...[...y, z]).
@ahejlsberg this still seems like a bug to me, it's not that uncommon to have a spread in an earlier position. As well the logic being applied seems to be affecting spread even without using tuples, see #28010
@ahejlsberg
When a spread parameter occurs earlier than the last parameter position, the remaining parameters are considered to just be part of the spread, i.e.
foo(x, ...y, z)is checked asfoo(x, ...[...y, z]).
I don't quite understand the problem or difference here. If y is a tuple of known fixed length 2 with types ty1 and ty2, then [...y, z] should be identifiable as tuple of known fixed length 3 with types ty1, ty2 and tz. In that case the signature is known and can be checked accordingly, no matter where the spread occurs.
What am I missing?
What am I missing?
It certainly is possible to do what you're suggesting, but it adds more complexity to the compiler logic that reasons about the effective argument list (i.e. the "flattened" argument list). Also, it assumes that the fixed length of the tuple can be trusted at run-time. For example, treating a longer array as a fixed length tuple doesn't lead to issues when it is in the last position, but would cause alignment bugs when more arguments follow.
I also have a use case for this in existing code that I am porting to TS: f(...tupleMaker(1), ...tupleMaker(2)). The error is "Expected 4 arguments, but got 1 or more. An argument for 'firstArg' was not provided."
This issue was fixed by #39094 which is included in the 4.0 beta. Playground link:
Most helpful comment
This issue was fixed by #39094 which is included in the 4.0 beta. Playground link:
https://www.typescriptlang.org/play/index.html?ts=4.0.0-beta#code/GYVwdgxgLglg9mABMOdEAoCGAuRBGAGkQCNcAmIiXAZgEpEBvRAXwFgAoDiBAZyhMwAnXAG1CiMgF1EAXkRiiUgNwcOKOOgB024kKJ0V7IA