Skip to content

Proposal: Support spread operator for arrays and tuples in function calls #5296

Closed

Description

Currently, Typescript does not support all uses of Ecmascript's spread operator. Specifically, in function calls, Typescript only supports spreading array arguments that match rest parameters. #4130 is a recent mention of this limitation. I propose two ways of addressing this limitation in function calls:

  1. Support array arguments with the spread operator that do not match rest parameters.
  2. Support tuple arguments with the spread operator.

Although (2) is cleaner from a type checker's point of view, I believe that (1) is more useful because it can type more of the uses that Javascript programmers are accustomed to writing. Of course, both proposals could be implemented.

Proposal 1: Spread operator supports array arguments

The spread operator may be used with the last array argument of a function call, even when that argument doesn't match a rest parameter in the function definition. However, the parameters must all be optional and of the same type. The type must match the element type of the array.
For example,

function f(s: string, a?: number, b?: number,  ...rest: number[]) {
    // ...
}
function g(s: string, a?: number, b?: number) {
    // ...
}
// legal
let long: number[] = [1,2,3];
let medium: number[] = [1,2];
let short: number[] = [1];
let empty: number[] = [];
f("foo", ...long); // and medium, short and empty
f("foo", ...long, 1, ...long); // and medium, short and empty
g("foo", ...long); // and medium, short and empty
// illegal
f(...["foo", 1, 2]); // array of type (number | string)[] doesn't match any parameter type
g("foo", ...long, 1); // 1 doesn't match any parameter
g("foo", ...empty, 1); // 1 would match, but the compiler can't tell

The checker consumes all parameters that match the array type after the spread operator is used, until

  1. A non-matching type is encountered, causing an error.
  2. The end of the parameter list is encountered, succeeding.
  3. A rest parameter is encountered, succeeding if the rest parameter's array type matches the spread argument's array type.
  4. TODO: These rules don't account for trailing arguments that also match a rest parameter.

At runtime the spread operator has EcmaScript semantics. Basically, undefined if the array is too short, and extra arguments are ignored.

Proposal 2: Spread operator supports tuple arguments

The spread operator may be used with a tuple argument at any position in a function call. Extra members of the tuple will even match rest parameters if the tuple is long enough.

function f(s: string, a: number, b: number, c: number,  ...rest: number[]) {
    // ...
}
function g(s: string, a: number, b: number) {
}
// legal
let quad: [string, number, number, number, number] = ["foo", 1,2,3,4];
let triple: [string, number, number, number] = ["foo", 1,2,3];
let double: [string, number, number] = ["foo", 1,2];
f(...quad); // and triple
f(...double, 3);
f("foo", ...[1,2,3]);
f(...quad, 1, ...[1,2,3]); // and triple
g(...double);
// illegal
f(...double); // too short -- double doesn't match parameter 'c'
g(...triple); // too many parameters
g(...quad); // too many parameters

The checker matches each member of the spread tuple argument with one parameter until

  1. A member's type does not match a parameter type, causing an error.
  2. The end of the parameter list is encountered, succeeding if there are no more tuple members.
  3. A rest parameter is encountered, succeeding if the remaining tuple members are typed as an array and that type matches the rest parameter's type.
  4. TODO: These rules don't account for trailing arguments that also match a rest parameter.

Notes

The tuple proposal has the problem that it types tuples as tuples in function applications only -- as arrays elsewhere. This is inconsistent, even if function application is arguably the most tuple-like of locations. And it's not clear whether either proposal will allow real uses of the spread operator that Javascript programmers are expecting.

If both proposals are implemented, function calls with spread array literals work in more cases if the literal is always typed as tuple, and provide more and better errors in the cases that do not type check:

f(...["bar",1,2,3,4,5]) // tuple gives no error; array gives error: bad type (number | string)[]
g(...["bar",1,2,3,4,5]) // tuple gives error: too long; array gives error bad type (number | string)[]
function rest(a?: number, b?: number, ...rest:number[]);
function fixed(a: number, b: number);
rest(...[1,2,3,4,5]); // tuple gives no error; array gives no error
fixed(...[1,2,3,4,5]); // tuple gives error: too long; array gives no error

So spread array literals should always be interpreted as tuples.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

FixedA PR has been merged for this issueSuggestionAn idea for TypeScript

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions