The TypeScript type checker utilizes control flow type analysis, which means it tries to determine how values inside variables change at runtime and adjusts the types of these variables accordingly. When dealing with union types, if a more specifically-typed value is assigned to a variable or property with that union type, the compiler will narrow the variable's type to be that specific type until another value is assigned.
This feature is often advantageous in scenarios like this:
let x: number | string = Math.random() < 0.5 ? "hello" : "goodbye"; // x is now a string
if (x.length < 6) { // no error here
x = 0; // since x can only be a string or a number, this assignment is valid
}
Even though x
is annotated as a number | string
, the compiler understands that after the initial assignment it will definitively be a
string</code. Therefore, it permits operations like <code>x.length
without complaints, as it knows
x
cannot potentially be a
number</code. This behavior is so helpful that disabling it would cause issues for real-world TypeScript code.</p>
<p>However, this feature is also responsible for the issue you are encountering. After assigning <code>5
to
value
, the compiler perceives
value
as holding a value of narrowed type
5
instead of
Prime
. The compiler alerts you about calling
compare(5 as 5, 3)</code, thinking it's incorrect. To work around this, you must manually widen <code>value
to
Prime</code through type assertion.</p>
<p>To address this, you have different options available such as using type assertion during the initial assignment or within the call to <code>compare()
. You can also specify the generic type
T
in your
compare()
call to resolve the issue.
The most comprehensive source of information on this topic can be found in Microsoft/TypeScript#8513, particularly in this comment.
Hopefully, this explanation helps you navigate through the situation successfully. Good luck!
Link to code