Loi Nguyen Huynh
Loi Nguyen Huynh

Reputation: 9948

What is the difference between type [...T] and T when T extends any[] in typescript?

Look at the example below, why:

enter image description here

declare function PromiseAll1<T extends any[]>(
  values: [...T]
): Promise<{
  [K in keyof T]: Awaited<T[K]>;
}>;
const promiseAllTesting1 = PromiseAll1([1, 2, Promise.resolve(3)]);

declare function PromiseAll2<T extends any[]>(
  values: T
): Promise<{
  [K in keyof T]: Awaited<T[K]>;
}>;
const promiseAllTesting2 = PromiseAll2([1, 2, Promise.resolve(3)]);

It's from this type-challenge btw

Upvotes: 1

Views: 108

Answers (2)

jcalz
jcalz

Reputation: 328758

If T is an array or tuple type, then the syntax ...T inside a tuple type is called a variadic tuple type, as implemented in microsoft/TypeScript#39094.

Variadic tuple types allow you to concatenate tuples at the type level. So, for example, if T is [string, number], then [...T, boolean] is [string, number, boolean]. And [...T, ...T] is [string, number, string, number]. And [...T] is [string, number].

That means the type [...T] is essentially the same thing as T (although if T is a readonly array type or readonly tuple type, then [...T] will be a mutable version of it; so [...readonly [string, number]] is [string, number].)


But if they're basically the same thing, what's the difference between a generic function like

const f1 = <T extends any[]>(t: T) => t;

and one like

const f2 = <T extends any[]>(t: [...T]) => t;

? The answer is that they affect generic type argument inference. The f1 version with just T uses normal type inference in the face of arrays. Normally if you write

const a1 = [1, "two", true];

then the compiler will infer that a1 is an unordered array type, not a tuple type:

// const a1: (string | number | boolean)[]

The same thing happens when you call f1():

const x1 = f1([1, "two", true]);
// const x1: (string | number | boolean)[]

But for f2 with [...T] gives the compiler a hint that you would like T to be a tuple type, so it changes the inference rules a bit to accomplish that:

const x2 = f2([1, "two", true]);
// const x2: [number, string, boolean]

This behavior is documented in the implementing pull request linked above:

When the contextual type of an array literal is a tuple type, a tuple type is inferred for the array literal. The type [...T], where T is an array-like type parameter, can conveniently be used to indicate a preference for inference of tuple types (emphasis added).

Therefore, if you want array literals to be inferred as unordered arrays, use just T, but if you want them to be inferred as tuples, use [...T].

Playground link to code

Upvotes: 2

Tachibana Shin
Tachibana Shin

Reputation: 3908

declare function PromiseAll1<T extends any[]>(
  values: [...T] // (1)
): Promise<{
  [K in keyof T]: Awaited<T[K]>;
}>;

(1): T represents the elements in that array. so if values has length=3 then T is a type with array also length is 3 you can learn more about three dot in javascript

declare function PromiseAll2<T extends any[]>(
  values: T // (2)
): Promise<{
  [K in keyof T]: Awaited<T[K]>;
}>;

(2): T represents the whole array and by default an array has length which is dynamic

Upvotes: 0

Related Questions