Conditional types that rely on a generic type parameter T
can exhibit intricate behaviors. Even seemingly simple expressions like T extends AAA ? BBB : CCC
can produce fascinating outcomes due to the concept of distribution over unions in T
. Currently, the compiler postpones evaluation of such types until generic type arguments are specified, at which point the type ceases to be generic. The compiler treats deferred generic conditional types as essentially "opaque," making it challenging for the compiler to determine what values can or cannot be assigned to them.
It would be convenient if the compiler could promptly resolve generic conditional types when there is sufficient information about the generic type parameter to do so, but this is not the case. When asked why, the answer lies in the complexity of achieving a resolution method that is both effective and does not significantly impact compiler performance.
This issue has been raised numerous times on GitHub, and within these discussions resides the closest thing to an official explanation for the current behavior:
microsoft/TypeScript#52144 "Resolve deferred conditional types to their true branch when instantiated with a type parameter constrained to the tested type". This feature request remains open. According to this comment:
The reason for deferral is that while resolving to the true branch may be safe, doing so for a generic type might lead to issues if the type parameter is instantiated with a more specific type that should go to the true branch.
We don't yet have a concept of a "partial deferral"; experimenting with this idea would be quite challenging.
microsoft/TypeScript#48243 "Conditional type doesn't go to true or false branch." Although marked as 'Working as Intended', the issue has been closed. As per this comment:
Conditional types do not always follow a linear path (meaning they exhibit predictable behavior between T and a subtype of T), and determining this requires complex reasoning involving scenarios where types behave differently than expected.
There may be additional discussions on this topic, but the explanations above provide insight into the rationale behind the existing behavior.
Regarding the aspect of indexed accesses in generics, the reasoning for deferring stems from the fact that almost any type can have subtypes, including number
(which includes numeric literal types like 0.5
and 42
). Therefore, if T extends {foo: number}
, its foo
property may be more specific than just a generic number type, and prematurely resolving T["foo"]
to number
could result in loss of essential information.