Why does typescript generics infer different types if array member is wrapped?

Stack Overflow Asked on November 17, 2020

This may be a duplicate question; however I can’t seem to find an answer, so please feel free to point me somewhere if it has already been answereded.

When working with generics, I find that typescript infers different types depending on whether I wrap a generic used as an array member. For example,

// Set up
type Wrapped<I> = { _i: I };

// Not working example
function foo1<TInner extends unknown>(bar: Wrapped<TInner>[]) {
  return bar;
const a = foo1([{ _i: 5 }, { _i: "six" }]); // TInner = number, compiler complains about second array member despite the fact that it's inferring it

// Working example
function foo2<TInner extends Wrapped<unknown>>(bar: TInner[]) {
  return bar;
const b = foo2([{ _i: 5 }, { _i: "six" }]); // TInner = Wrapped<number> | Wrapped<string>

Why is this the case? Is there something in the documentation I’m missing? More generically, can anyone point me to a good explanation of how typescript does inference of generics?

One Answer

Look at the way that the code is written very closely.

// Slightly edited given example

type Wrapped<I> = { _i: I };

function foo<T extends unknown>(baz: Wrapped<T>[]) {}

foo1([ { _i: 5 }, { _i: "six" } ]);

function bar<T extends Wrapped<unknown>>(baz: T[]) {}

foo2([ { _i: 5 }, { _i: "six" } ]);

The way foo's generic parameter is defined, T extends unknown, means that T can be anything, denoted by the unknown, but it's 1 thing.

The actual function's parameter is Wrapped<T>[]; breaking it down, foo accepts an array of Wrapped<T>, meaning it accepts a list of Wrapped objects, but every one of them must have the same type, so it could accept [ { _i: 1 }, { _i: 2 }, { _i: 3 } ], or [ { _i: 'a' }, { _i: 'b' }, { _i: 'c' } ], but it cannot create a union/tuple as it's output, so something like [ { _i: 'a' }, { _i: 2 }, { _i: Symbol(NaN) } ] would fail, because this type is actually Wrapped<string | number | symbol>[], whereas the former two were Wrapped<number>[], and Wrapped<string>[], respectively.

bar, on the other hand, is defined as <T extends Wrapped<unknown>>. In this case, T is just an object of the type Wrapped, without care to what it's type parameter is, as long as it has the general shape of a Wrapped object. Here, it can take an array of Wrapped objects, regardless of whether they are all of the same types, or not.

Summing it up, foo is accepting a contiguous array of the same type, whereas bar is accepting an array of any Wrapped objects.

Answered by xxh on November 17, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP