Generics in Typescript serve to limit the type of parameters and deduce the type of arguments provided.
Imagine a scenario where a user inputs the generic value manually:
getMinimum<number[]>([1, 2, 3], str)
. It becomes evident that
number[]
aligns with
[1, 2, 3]
but not with
"Hello world"
.
It is essential to have a distinct generic for each parameter when their types differ, even if they meet the same criteria:
type Length = { length : number };
function getMinimum<T extends Length, U extends Length>(arg1 : T, arg2 : U) : T | U {
if (arg1.length >= arg2.length) {
return arg2;
}
else {
return arg1;
}
}
The reason why
getMinimum([1, 2, 3], { length: 12 })
succeeds with your implementation while
getMinimum([1, 2, 3], str)
does not can be explained as follows:
Please note: This explanation is based on personal understanding.
When associating two arguments with a single generic, TS likely undergoes the following process:
- Deduce the types of each argument individually;
- Verify if these types satisfy the constraint;
- If they do not, reject the arguments;
- If they do, intersect them to identify a common type;
- If the intersection results in
never
, discard the first argument and highlight the second in the error message.
In the case of
getMinimum([1, 2, 3], { length: 12 })
: TS infers
number[]
for
arg1
and
{length: number}
for
arg2
, checks their compatibility with
{ length: number }
, then combines them, resulting in
number[]
and accepts the type.
With getMinimum([1, 2, 3], str)
: TS deduces number[]
for arg1
and string
for arg2
, verifies their fit with { length: number }
, intersects them, leading to never
and declining the first argument.
A unified type that satisfies both { length: number }
for number[]
and string
would be
{ length: number } & (string | number[])
, yet TS does not attempt to infer this type. This could be due to the intention of determining the most specific type possible rather than broadening it, as narrower types are more beneficial.
It is crucial to differentiate between type inference and type validation: TS can accurately infer the return type even if the type checker disapproves of the arguments, as these are separate processes. In this context, it is evident that T
should be typeof arg1 | typeof arg2
in the return type position.