[microsoft/TypeScript] Leading and middle rest elements in tuple types (#41544)
DRANK

With this PR we support leading and middle rest elements in tuple types. For example, we now support the following: ```ts type T1 = [...string[], number]; // Zero or more strings followed by a number type T2 = [number, ...boolean[], string, string]; // A number followed by zero or more booleans followed by two strings ``` Previously it was not possible to have anything follow a rest element and thus it was not possible to express tuple types that end in a fixed set of elements. Such tuple types are useful for strongly typing functions with variable parameter lists that end in a fixed set of parameters. For example, the following is now permitted: ```ts function f1(...args: [...string[], number]) { const strs = args.slice(0, -1) as string[]; const num = args[args.length - 1] as number; // ... } f1(5); f1('abc', 5); f1('abc', 'def', 5); f1('abc', 'def', 5, 6); // Error ``` Note the use of a rest parameter with a tuple type that _starts_ with a rest element. Also note that indexing a tuple type beyond its starting fixed elements (if any) yields a union type of all possible element types (in this case `string | number`). Thus, type assertions are required in the code above. Tuple type layouts are now governed by these rules: * An optional element cannot precede a required element or follow a rest element. * Multiple rest elements are not permitted. Thus, the supported layouts of tuple types are: * Zero or more required elements, followed by zero or more optional elements, followed by zero or one rest element, or * Zero or more required elements, followed by a rest element, followed by zero or more required elements. In either layout, zero or more generic variadic elements may be present at any position in the tuple type. Errors are reported on tuple types that don't match the layout rules, but through generic type instantiation it is still possible to create invalid layouts. For this reason, tuple types are _normalized_ following generic type instantiation: * Optional elements preceding the last required element are turned into required elements. * Elements between the first rest element and the last rest or optional element are turned into a single rest element with a union of the element types. Some examples of normalization: ```ts type Tup3<T extends unknown[], U extends unknown[], V extends unknown[]> = [...T, ...U, ...V]; type TN1 = Tup3<[number], string[], [number]>; // [number, ...string[], number] type TN2 = Tup3<[number], [string?], [boolean]>; // [number, string | undefined, boolean] type TN3 = Tup3<[number], string[], [boolean?]>; // [number, (string | boolean | undefined)[]] type TN4 = Tup3<[number], string[], boolean[]>; // [number, (string | boolean)[]] type TN5 = Tup3<string[], number[], boolean[]>; // (string | number | boolean)[] ``` Fixes #39595.

github.com
Related Topics: TypeScript
1 comments
  • TypeScript 4.2でタプル型がさらに強化される見込み。これまでタプル型の中の...string[]のような可変長の部分(rest elements)はタプル型の最後にしか現れることができなかったが、その制限が解除される。これにより、[...unknown[], number]のようなタプル型が記述可能となり、関数定義に使えば「最後の引数はnumberで他の引数は何が何個来てもいい」のような定義が書ける。
    もちろんTS4.0で登場したVariadic Tuple Typesとも組み合わせられる。