Home > Enterprise >  why L extends AUnion, R extends AUnion have no overlap
why L extends AUnion, R extends AUnion have no overlap

Time:01-10

this is not about object {} === {},

found this issues don't know if this is the same

The set of types - AUnion is not an empty set. Two more types (L and R) are extending it. My understanding that these L, R are at least as wide as AUnion, and, therefore I expect that elements in L and R have no empty intersection - namely AUnion.

What part of TypeScript type system I'm missing?

given the TypeScript code

type AUnion = 'a'|'b';
type Proc = <L extends AUnion, R extends AUnion>(l:L, r:R)=>0|1;
declare const proc:Proc;

One can call the proc with two equal arguments

const arg:'a' = 'a';
proc(arg,arg); // this is fine (type-wise) 'a' does extend AUnion

As far as I understand 'a' is equal to 'a'. Their types are the one a and a extend the union type AUnion.

Let's implement the type.


const proc:Proc = (l, r)=>{
  if(l===r){
    return 0;
  }
  return 1;
}

Yet the TypeScript complains

This condition will always return 'false' since the types 'L' and 'R' have no overlap.

Playground Link

What is it I'm missing?

CodePudding user response:

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

  •  Tags:  
  • Related