I am learning TS and have the following code:
function getOrDefault<T = string>(value: T, defaultValue: T, car: T): T {
return value || defaultValue;
}
const strValue = getOrDefault("Hello", "Default", "o");
const numValue = getOrDefault(undefined, 42, "o");
The TS Playground compiler complains about the numValue variable because Argument of type '"o"' is not assignable to parameter of type '42'.
whereas the strValue doesn’t complain.
When I hover over the first function call, the type is "Hello" | "Default" | "o", in the 2nd call its only showing 42 | undefined. If I replace "o" with a number then the error goes away and it appears in the union list as well.
I’m wondering what does the error mean about "o" and the parameter of type 42, why isn’t it added into the union list as undefined and 42 are? Any simple explanation to understand this would be appreciated.
2
Answers
In your generic, all three arguments are of type T
But for your numValue, you are providing a number for the second parameter and a string for the third.
The type system is telling you that a number and a string are not the same type.
TypeScript uses various heuristic rules to determine when to synthesize union types when faced with multiple inference candidates. In general this is just prohibited; a call signature of type
<T>(x: T, y: T)=>void
will tend to reject calls likef(x, y)
wherex
is of typeX
andy
is of typeY
, instead of synthesizing the unionX | Y
and accepting it. This is described in microsoft/TypeScript#49850 and links from there, and Why isn't the type argument inferred as a union type?.But there are exceptions to this.
One is if
X
orY
arenull
orundefined
. Before the introduction of--strictNullChecks
in TS2.0,null
andundefined
were considered assignable to all types, and thus they were implicitly accepted in calls likef("abc", undefined)
. Since then they are now considered to be distinct types, but they are still allowed in calls like the above. So unions withnull
andundefined
are synthesized more eagerly.Another exception involves literal types like
"a"
or1
ortrue
. Before TypeScript introduced string literal types in TS1.8 as implemented in microsoft/TypeScript#5185 and numeric and boolean literal types as implemented in microsoft/TypeScript#9407, A call likef("a", "b")
orf(1, 2)
orf(true, false)
would be accepted becauseT
was juststring
,number
, orboolean
. But now that literal types exist, a straight reading of "don’t synthesize union types" would mean those might be rejected. But nobody wants that to be rejected. So even if literal types are inferred, literal types of the same widened type will be accepted and a union of them synthesized. So you’ll get"a" | "b"
or1 | 2
ortrue | false
(which is justboolean
).That’s the answer to the question as asked. If you think it should be possible to ask TypeScript to synthesize unions in situations like this then you might want to give an upvote to microsoft/TypeScript#44312. Until then you just need to work around it. The easiest workaround is to just add more generic type parameters and synthesize their union yourself:
Playground link to code