This PR implementsvariadic tuple types, i.e. the ability for tuple types to have spreads of generic types that can be replaced with actual elements through type instantiation. The PR effectively implements the features discussed in#5453.
Some examples of new capabilities provided in this PR:
// Variadic tuple elementstypeFoo<Textendsunknown[]>=[string, ...T,number];typeT1=Foo<[boolean]>;// [string, boolean, number]typeT2=Foo<[number,number]>;// [string, number, number, number]typeT3=Foo<[]>;// [string, number]// Strongly typed tuple concatenationfunctionconcat<Textendsunknown[],Uextendsunknown[]>(t:[...T],u:[...U]):[...T, ...U]{return[...t, ...u];}constns=[0,1,2,3];// number[]constt1=concat([1,2],['hello']);// [number, number, string]constt2=concat([true],t1);// [boolean, number, number, string]constt3=concat([true],ns);// [boolean, ...number[]]// Inferring parts of tuple typesdeclarefunctionfoo<Textendsstring[],U>(...args:[...T,()=>void]):T;foo(()=>{});// []foo('hello','world',()=>{});// ["hello", "world"]foo('hello',42,()=>{});// Error, number not assignable to string// Inferring to a composite tuple typefunctioncurry<Textendsunknown[],Uextendsunknown[],R>(f:(...args:[...T, ...U])=>R, ...a:T){return(...b:U)=>f(...a, ...b);}constfn1=(a:number,b:string,c:boolean,d:string[])=>0;constc0=curry(fn1);// (a: number, b: string, c: boolean, d: string[]) => numberconstc1=curry(fn1,1);// (b: string, c: boolean, d: string[]) => numberconstc2=curry(fn1,1,'abc');// (c: boolean, d: string[]) => numberconstc3=curry(fn1,1,'abc',true);// (d: string[]) => numberconstc4=curry(fn1,1,'abc',true,['x','y']);// () => number
Structure and instantiation
The basic structure of a non-generic tuple type remains unchanged with this PR: Zero or more required elements, followed by zero or more optional elements, optionally followed by a rest element (for example[A, B?, ...C[]]
). However, it is now possible to havevariadic elements anywhere in a tuple type, except following the optional rest element (for example[A, ...T, B?, ...U, ...C[]]
).
A variadic elemement is a spread element of the form...T
, whereT
is a generic type constrained to any array or tuple type (specifically, any type that is assignable toreadonly any[]
). Intuitively, a variadic element...T
is a placeholder that is replaced with one or more elements through generic type instantiation. Instantiation of a tuple type with a variadic element...T
depends on the type argument provided forT
as follows:
- When the type argument for
T
is a union type, the union is spread over the tuple type. For example,[A, ...T, B]
instantiated withX | Y | Z
as the type argument forT
yields a union of instantiations of[A, ...T, B]
withX
,Y
andZ
as the type argument forT
respectively. - When the type argument is a tuple type,
T
is replaced with the elements of that tuple type. For example,[A, ...T, B]
instantiated with[X, Y]
as the type argument forT
yields[A, X, Y, B]
. - When the type argument is an array type,
T
is replaced with a rest element. For example,[A, ...T]
instantiated withX[]
as the type argument forT
yields[A, ...X[]]
. - When the type argument is another generic type,
T
is simply replaced with that type. - When the type argument is
any
,T
is replaced with...any[]
. - When the type argument is
never
, the entire result isnever
.
Instantiation of a generic tuple type includesnormalization to ensure the resulting tuple type follows the basic structure described above. Specifically:
- Optionality is removed from any optional elements that precede a required element. For example,
[A?, ...T, B?]
instantiated with[X, Y?]
as the type argument forT
yields[A, X, Y?, B?]
. - Rest elements absorb any following elements. For example,
[A, ...T, B]
instantiated withX[]
as the type argument forT
yields[A, ...(X | B)[]]
. - Tuples with only a single rest element are reduced to array types. For example,
[...X[]]
is reduced to simplyX[]
.
NOTE: With#41544 we now support starting and middle rest elements in tuple types.
Type relationships
Generally, a tuple typeS
is related to a tuple typeT
by pairwise relating elements ofS
to the elements ofT
. Variadic elements are processed as follows:
- A variadic element
...U
inS
is related to a variadic element...V
inT
ifU
is related toV
. - A variadic element
...U
inS
is related to a rest element...X[]
inT
ifU
is related toX[]
.
Some examples:
functionfoo1<Textendsunknown[],UextendsT>(x:[string, ...unknown[]],y:[string, ...T],z:[string, ...U]){x=y;// Okx=z;// Oky=x;// Errory=z;// Okz=x;// Errorz=y;// Error}
Tuple types with single variadic elements have the following relations:
[...T]
is related toT
.T
is related toreadonly [...T]
.T
is related to[...T]
whenT
is constrained to a mutable array or tuple type.
Some examples:
functionfoo2<Textendsreadonlyunknown[]>(t:T,m:[...T],r:readonly[...T]){t=m;// Okt=r;// Errorm=t;// Errorm=r;// Errorr=t;// Okr=m;// Ok}
Type inference
Inference between tuple types with the same structure (i.e. same number of elements and fixed, variadic, or rest kind matched to the same kind in each position), simply infers pairwise between the element types. For example, inference from[string, ...Partial<S>, number?]
to[string, ...T, number?]
infersPartial<S>
forT
.
Inference between tuple typesS
andT
with different structure divides each tuple into a starting fixed part, a middle part, and an ending fixed part. Any one of these parts may be empty.
The starting fixed parts ofS
andT
consist of those elements inS
andT
that are fixed (i.e. neither variadic nor rest elements) in both types matching from the start of each type.
IfT
contains at least one variadic element andS
has no ending rest element, the ending fixed parts ofS
andT
consist of those elements inS
andT
that are fixed in both types matching from the end of each type.
IfT
contains at least one variadic element andS
has an ending rest element, the ending fixed part ofT
consists of those elements inT
that are fixed matching from the end of the type, and the ending fixed part ofS
is empty.
IfT
contains no variadic elements, the ending fixed parts ofS
andT
are empty.
The middle parts ofS
andT
are those elements inS
andT
that remain between the starting and ending fixed parts of the types respectively.
Inference then proceeds as follows:
Pairwise infer between the elements in the starting parts.
If the middle part ofS
is a single rest element, infer from that rest element to every element in the middle part ofT
.
If the middle part ofT
is a single variadic or rest element, infer from a tuple consisting of the middle part ofS
to that variadic or rest element.
If the middle part ofT
is exactly two variadic elements...A
and...B
, and animplied arity exists forA
, infer from a tuple consisting of the initial middle part ofS
toA
and from a tuple consisting of the remaining middle part ofS
toB
, where the length of the initial middle part corresponds to the implied arity forA
.
Pairwise infer between the elements in the ending parts, or infer from the rest element inS
to the elements of the ending part ofT
.
In the context of inference for a call of a generic function with a rest parameterR
, the implied arity forR
is the number of rest arguments supplied forR
. In all other contexts, a type parameter has no implied arity. For an example of inference involving an implied arity, see thecurry
function in the introduction.
Some examples:
typeFirst<Textendsreadonlyunknown[]>=T[0];typeDropFirst<Textendsreadonlyunknown[]>=Textendsreadonly[any?, ...inferU] ?U :[...T];typeLast<Textendsreadonlyunknown[]>=Textendsreadonly[...infer_, inferU] ?U :Textendsreadonly[...infer_,(inferU)?] ?U|undefined :undefined;typeDropLast<Textendsreadonlyunknown[]>=Textendsreadonly[...inferU,any?] ?U :[...T];typeT1=First<[number,boolean,string]>;// [number]typeT2=DropFirst<[number,boolean,string]>;// [boolean, string]typeT3=Last<[number,boolean,string]>;// [string]typeT4=DropLast<[number,boolean,string]>;// [number, boolean]
Spreads in array literals
When an array literal has a tuple type, a spread of a value of a generic array-like type produces a variadic element. For example:
functionfoo3<Textendsunknown[],Uextendsunknown[]>(t:[...T],u:[...U]){return[1, ...t,2, ...u,3]asconst;// readonly [1, ...T, 2, ...U, 3]}constt=foo3(['hello'],[10,true]);// readonly [1, string, 2, number, boolean, 3]
When the contextual type of an array literal is a tuple type, a tuple type is inferred for the array literal. The type[...T]
, whereT
is an array-like type parameter, can conveniently be used to indicate a preference for inference of tuple types:
declarefunctionft1<Textendsunknown[]>(t:T):T;declarefunctionft2<Textendsunknown[]>(t:T):readonly[...T];declarefunctionft3<Textendsunknown[]>(t:[...T]):T;declarefunctionft4<Textendsunknown[]>(t:[...T]):readonly[...T];ft1(['hello',42]);// (string | number)[]ft2(['hello',42]);// readonly (string | number)[]ft3(['hello',42]);// [string, number]ft4(['hello',42]);// readonly [string, number]
Indexing and destructuring
Indexing and destructuring of generic tuple types appropriately recognizes fixed elements at the start of the tuple type. Beyond the fixed elements, the type is simply a union of the remaining element types.
functionf1<Textendsunknown[]>(t:[string, ...T],n:number){consta=t[0];// stringconstb=t[1];// [string, ...T][1]constc=t[2];// [string, ...T][2]constd=t[n];// [string, ...T][number]}functionf2<Textendsunknown[]>(t:[string, ...T,number],n:number){consta=t[0];// stringconstb=t[1];// [string, ...T, number][1]constc=t[2];// [string, ...T, number][2]constd=t[n];// [string, ...T, number][number]}functionf3<Textendsunknown[]>(t:[string, ...T]){let[...ax]=t;// [string, ...T]let[b1, ...bx]=t;// string, [...T]let[c1,c2, ...cx]=t;// string, [string, ...T][1], T[number][]}functionf4<Textendsunknown[]>(t:[string, ...T,number]){let[...ax]=t;// [string, ...T, number]let[b1, ...bx]=t;// string, [...T, number]let[c1,c2, ...cx]=t;// string, [string, ...T, number][1], (number | T[number])[]}
Rest parameters and spread arguments
Spread expressions with fixed length tuples are now appropriately flattened in argument lists. For example:
declarefunctionfs1(a:number,b:string,c:boolean, ...d:number[]):void;functionfs2(t1:[number,string],t2:[boolean],a1:number[]){fs1(1,'abc',true,42,43,44);fs1(...t1,true,42,43,44);fs1(...t1, ...t2,42,43,44);fs1(...t1, ...t2, ...a1);fs1(...t1);// Error: Expected at least 3 arguments, but got 2fs1(...t1,45);// Error: Type '45' is not assignable to type 'boolean'}
A rest parameter of a generic tuple type can be used to infer types from the middle part of argument lists. For example:
declarefunctionfr1<Textendsunknown[]>(x:number, ...args:[...T,number]):T;functionfr2<Uextendsunknown[]>(u:U){fr1(1,2);// []fr1(1,'hello',true,2);// [string, boolean]fr1(1, ...u,'hi',2);// [...U, string]fr1(1);// Error: Expected 2 arguments, but got 1}
Application of mapped types
When a mapped type is applied to a generic tuple type, non-variadic elements are eagerly mapped but variadic elements continue to be generic. Effectively,M<[A, B?, ...T, ...C[]]
is resolved as[...M<[A]>, ...M<[B?]>, ...M<T>, ...M<C[]>]
. For example:
typeTP1<Textendsunknown[]>=Partial<[string, ...T,number]>;// [string?, ...Partial<T>, number?]
Fixes#5453.
Fixes#26113.
Uh oh!
There was an error while loading.Please reload this page.
This PR implementsvariadic tuple types, i.e. the ability for tuple types to have spreads of generic types that can be replaced with actual elements through type instantiation. The PR effectively implements the features discussed in#5453.
Some examples of new capabilities provided in this PR:
Structure and instantiation
The basic structure of a non-generic tuple type remains unchanged with this PR: Zero or more required elements, followed by zero or more optional elements, optionally followed by a rest element (for example
[A, B?, ...C[]]
). However, it is now possible to havevariadic elements anywhere in a tuple type, except following the optional rest element (for example[A, ...T, B?, ...U, ...C[]]
).A variadic elemement is a spread element of the form
...T
, whereT
is a generic type constrained to any array or tuple type (specifically, any type that is assignable toreadonly any[]
). Intuitively, a variadic element...T
is a placeholder that is replaced with one or more elements through generic type instantiation. Instantiation of a tuple type with a variadic element...T
depends on the type argument provided forT
as follows:T
is a union type, the union is spread over the tuple type. For example,[A, ...T, B]
instantiated withX | Y | Z
as the type argument forT
yields a union of instantiations of[A, ...T, B]
withX
,Y
andZ
as the type argument forT
respectively.T
is replaced with the elements of that tuple type. For example,[A, ...T, B]
instantiated with[X, Y]
as the type argument forT
yields[A, X, Y, B]
.T
is replaced with a rest element. For example,[A, ...T]
instantiated withX[]
as the type argument forT
yields[A, ...X[]]
.T
is simply replaced with that type.any
,T
is replaced with...any[]
.never
, the entire result isnever
.Instantiation of a generic tuple type includesnormalization to ensure the resulting tuple type follows the basic structure described above. Specifically:
[A?, ...T, B?]
instantiated with[X, Y?]
as the type argument forT
yields[A, X, Y?, B?]
.[A, ...T, B]
instantiated withX[]
as the type argument forT
yields[A, ...(X | B)[]]
.[...X[]]
is reduced to simplyX[]
.NOTE: With#41544 we now support starting and middle rest elements in tuple types.
Type relationships
Generally, a tuple type
S
is related to a tuple typeT
by pairwise relating elements ofS
to the elements ofT
. Variadic elements are processed as follows:...U
inS
is related to a variadic element...V
inT
ifU
is related toV
....U
inS
is related to a rest element...X[]
inT
ifU
is related toX[]
.Some examples:
Tuple types with single variadic elements have the following relations:
[...T]
is related toT
.T
is related toreadonly [...T]
.T
is related to[...T]
whenT
is constrained to a mutable array or tuple type.Some examples:
Type inference
Inference between tuple types with the same structure (i.e. same number of elements and fixed, variadic, or rest kind matched to the same kind in each position), simply infers pairwise between the element types. For example, inference from
[string, ...Partial<S>, number?]
to[string, ...T, number?]
infersPartial<S>
forT
.Inference between tuple types
S
andT
with different structure divides each tuple into a starting fixed part, a middle part, and an ending fixed part. Any one of these parts may be empty.The starting fixed parts of
S
andT
consist of those elements inS
andT
that are fixed (i.e. neither variadic nor rest elements) in both types matching from the start of each type.If
T
contains at least one variadic element andS
has no ending rest element, the ending fixed parts ofS
andT
consist of those elements inS
andT
that are fixed in both types matching from the end of each type.If
T
contains at least one variadic element andS
has an ending rest element, the ending fixed part ofT
consists of those elements inT
that are fixed matching from the end of the type, and the ending fixed part ofS
is empty.If
T
contains no variadic elements, the ending fixed parts ofS
andT
are empty.The middle parts of
S
andT
are those elements inS
andT
that remain between the starting and ending fixed parts of the types respectively.Inference then proceeds as follows:
Pairwise infer between the elements in the starting parts.
If the middle part of
S
is a single rest element, infer from that rest element to every element in the middle part ofT
.If the middle part of
T
is a single variadic or rest element, infer from a tuple consisting of the middle part ofS
to that variadic or rest element.If the middle part of
T
is exactly two variadic elements...A
and...B
, and animplied arity exists forA
, infer from a tuple consisting of the initial middle part ofS
toA
and from a tuple consisting of the remaining middle part ofS
toB
, where the length of the initial middle part corresponds to the implied arity forA
.Pairwise infer between the elements in the ending parts, or infer from the rest element in
S
to the elements of the ending part ofT
.In the context of inference for a call of a generic function with a rest parameter
R
, the implied arity forR
is the number of rest arguments supplied forR
. In all other contexts, a type parameter has no implied arity. For an example of inference involving an implied arity, see thecurry
function in the introduction.Some examples:
Spreads in array literals
When an array literal has a tuple type, a spread of a value of a generic array-like type produces a variadic element. For example:
When the contextual type of an array literal is a tuple type, a tuple type is inferred for the array literal. The type
[...T]
, whereT
is an array-like type parameter, can conveniently be used to indicate a preference for inference of tuple types:Indexing and destructuring
Indexing and destructuring of generic tuple types appropriately recognizes fixed elements at the start of the tuple type. Beyond the fixed elements, the type is simply a union of the remaining element types.
Rest parameters and spread arguments
Spread expressions with fixed length tuples are now appropriately flattened in argument lists. For example:
A rest parameter of a generic tuple type can be used to infer types from the middle part of argument lists. For example:
Application of mapped types
When a mapped type is applied to a generic tuple type, non-variadic elements are eagerly mapped but variadic elements continue to be generic. Effectively,
M<[A, B?, ...T, ...C[]]
is resolved as[...M<[A]>, ...M<[B?]>, ...M<T>, ...M<C[]>]
. For example:Fixes#5453.
Fixes#26113.