According to various GitHub issues, the existence of an error is intended, but the exact wording of the error isn't always appropriate. See microsoft/TypeScript#25642, microsoft/TypeScript#27910, microsoft/TypeScript#41402.
If you have a value x
of type X
and a value y
of type Y
, then the rule for whether x === y
is allowed is that X
and Y
must be "comparable". This, roughly, means that either X extends Y
or Y extends X
are known to be true. If neither of those are known to be true, then x === y
will be disallowed.
The fact that both X extends Z
and Y extends Z
might be known to be true for some third type Z
doesn't change that. In fact, the unknown
type is the top type in TypeScript, which means that X extends unknown
and Y extends unknown
will be true for every X
and Y
. And so the fact that X
and Y
are constrained to some common supertype really does not imply much about X
and Y
's suitability to be compared to each other.
In your case, L extends AUnion
and R extends AUnion
, but neither L extends R
nor R extends L
are known to be true. So the compiler disallows the comparison.
As for the specific error message wording, it's bad. It is provably incorrect that x === y
"will always return false
" when X
and Y
are not "comparable". And when the compiler says "X
and Y
has no overlap", it certainly sounds like it's saying that "X & Y
is never
", but of course this isn't necessarily true. There are cases like this which will always return false
(e.g., if X
is string
and Y
is number
, or if X
is {z: string}
and Y
is {z: number}
).
But the emptiness of X & Y
really isn't the issue. The compiler will prevent comparison of {x: string}
and {y: number}
even though the intersection, {x: string; y: number}
, is most definitely not empty. So the comparison isn't really prevented because it's impossible for the two values to be the same; it's prevented because comparing two not-directly-related types is often indicative of an error.
If you want to compare two values whose types are considered "incomparable" by the compiler, you can always widen one or both of them to comparable types. In your case you know that both L
and R
are comparable to AUnion
, so you can do something like
const proc: Proc = (l, r) => {
const _l: AUnion = l;
const _r: AUnion = r;
if (_l === _r) { // okay
return 0;
}
return 1;
}
or
const proc: Proc = (l, r) => {
if (l as AUnion === r) { // okay
return 0;
}
return 1;
}
Playground link to code