After experimenting with this, I've come to realize that the TypeScript compiler is quite ingenious. It's well-versed in the typeof operator and effectively incorporates it into type analysis.
One of the impressive aspects of TypeScript is its approach to modeling JavaScript types without overwhelming you with complex hierarchies or contravariant/covariant types. Rather than imposing a rigid structure, TypeScript embraces the inherent complexity of JavaScript types using simple mechanisms.
The type system in TypeScript can be likened to Goldilocks' experience - not too much, not too little; just right.
With that introduction in mind, let's delve into the explanation for your question "Why is that?".
In a ternary expression like condition ? X : Y
, the resulting type is the union of typeof(X)
and typeof(Y)
. Essentially, when the condition is true, X (with type typeof(X)
) is returned, and when false, Y (with type typeof(Y)
) is returned. This logic stems from how JS developers mentally process types due to the absence of formal syntax for types in vanilla JS. Thankfully, TypeScript, along with other compile-to-JS systems, introduces type syntax, enabling us to recognize the type of the ternary operation as typeof(X) | typeof(Y)
.
In your specific example where X is an array and Y is a string, the compiler strives to determine the type as typeof([a]) | typeof(a). Determining the type on the right-hand side is straightforward since it's explicitly defined as string
based on the function argument. However, the left-hand side must be an array type denoted as X[] because of [a]
. The challenge lies in figuring out what X represents.
Initially, the compiler infers that typeof(a)
must be Function as per the condition. But considering the function's signature, typeof(a)
is actually string
. Consequently, within the context of [a]
, typeof(a)
simultaneously needs to be both Function and string, which translates to (Function & string). Therefore, the resulting type of the ternary expression becomes
(Function & string)[] | string
.
Further analyzing the intersection of all Function values and all string values leads to an empty set, simplifying Function & string
to never
. Hence, the compiler refines the type to never[] | string
.
While an array of nevers is also unattainable, the final deduced type of b boils down to string
, aligning closely with never[] | string
. Although the compiler could potentially streamline the type expression further, emphasizing simplicity may vary based on individual perspectives.
Similarly echoing Nitzan's viewpoint, never[]
and never share identical sets of values, implying that they are essentially interchangeable. Opting for never[]
provides a more detailed understanding of the potential return shape even though both types signify impossibility.
Contrary to Nitzan's assertion, narrowing down the type to never | string
never | string
Exploring such puzzles always intrigues me, prompting thoughts about the equivalence of an empty basket of apples versus an empty basket of oranges.
Cheers!