'Typescript is not properly inferring types from intermediately defined types

I have the following type definitions.

type Triple<A, B, C> = A & B & C;
type First<T> = T extends Triple<infer A, infer _B, infer _C> ? A : never;
type Second<T> = T extends Triple<infer _A, infer B, infer _C> ? B : never;
type Third<T> = T extends Triple<infer _A, infer _B, infer C> ? C : never;

// Scene1: Correct behavior

type F1 = First<Triple<{ x: number }, { y: string }, { z: boolean }>>; // { x: number }
type S1 = Second<Triple<{ x: number }, { y: string }, { z: boolean }>>; // { y: string }
type T1 = Third<Triple<{ x: number }, { y: string }, { z: boolean }>>; // { z: boolean }


// Scene2: Seems to be a wrong behavior

type XYZ = Triple<{ x: number }, { y: string }, { z: boolean }>;

type F2 = First<XYZ>; // unknown
type S2 = Second<XYZ>; // unknown
type T2 = Third<XYZ>; // unknown

Can someone explain why in Scene2, the resulting types are unknown? See it in action in typescript playground.



Solution 1:[1]

Types in typescript are based on set theory, hence the properties that apply there are also applicable to them.

Hence operators like & and | are Commutative in nature. Which basically means their orders can be changed.

A & B == B & A

A | B == B | A

This basically means the typescript compiler has more flexibility and can change the order and position of the operands(types) positions.

And infer works when we have a fixed structure and positions defined over which we can ask the compiler to give us the value at the position.

type Triple<A, B, C> = A & B & C;
type First<T> = T extends Triple<infer A, infer _B, infer _C> ? A : never;
type Second<T> = T extends Triple<infer _A, infer B, infer _C> ? B : never;
type Third<T> = T extends Triple<infer _A, infer _B, infer C> ? C : never;

In case 1 :

We are inferring the types from the type parameter of Triple while instantiating itself and hence we can get the types from those positions as typescript knows their actual place from where it should infer.

type F1 = First<Triple<{ x: number }, { y: string }, { z: boolean }>>; // { x: number }
type S1 = Second<Triple<{ x: number }, { y: string }, { z: boolean }>>; // { y: string }
type T1 = Third<Triple<{ x: number }, { y: string }, { z: boolean }>>; // { z: boolean }

In case 2 :

We instantiated the type value first and hence know there is no certainty that the type will still be A & B & C.

Typescript can even store it and use it as B & A & C or C & A & B and other combinations.

So, the best guess it can come up with is the type of it is unknown Hence the desired behavior

type XYZ = Triple<{ x: number }, { y: string }, { z: boolean }>;

type F2 = First<XYZ>; // unknown
type S2 = Second<XYZ>; // unknown
type T2 = Third<XYZ>; // unknown

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Bishwajit jha