I was in need of proper tuple type inference in Typescript and stumbled upon a magic solution for my problem in the official Typescript GitHub.
In the following snippet, tuple3 is obviously the proposed solution. I have added some variations not leading to the expected result.
My question is: Why does tuple3 work like it does? I imagine union types working like an exclusive selection. Check, if the first type in the union fits, if so, use it, otherwise proceed to the next one. Following this rule, I would expect the compiler to find that (for tuple3) [void] doesn't fit the given argument for inference, so it uses {} as type, leading to the same result as tuple4. But apparently the types in the union ([void] | {}) are connected somehow? Is this a feature or a bug? Is it documented somewhere?
function tuple1<TupleT>(tuple: TupleT): TupleT
{ return tuple; }
tuple1([42, 'foo', true]); // type: (string | number | boolean)[]
function tuple2<TupleT extends [void]>(tuple: TupleT): TupleT
{ return tuple; }
tuple2([42, 'foo', true]); // error: [number, string, boolean] not assignable to [void]
function tuple3<TupleT extends [void] | {}>(tuple: TupleT): TupleT
{ return tuple; }
tuple3([42, 'foo', true]); // type: [number, string, boolean]
function tuple4<TupleT extends {}>(tuple: TupleT): TupleT
{ return tuple; }
tuple4([42, 'foo', true]); // type: (string | number | boolean)[]
function tuple5<TupleT extends [any]>(tuple: TupleT): TupleT
{ return tuple; }
tuple5([42, 'foo', true]); // error: [number, string, boolean] not assignable to [any]
[void], but sinceTcan be[void] | {}the tuples will satisfy{}but the[void]part put the compiler in the 'mood' to infer tuples. An interesting note is that this function will allow ANY object as an argument not just a tuple !tuple3({ test : 'ss'});works just fine. I would use a more conventional solution with multiple overloads..