Repository: TypeScript
Issue: Suggestion: Improved union type narrowing for comparison situations
Labels: Suggestion,Awaiting More Feedback

# Suggestion

## 🔍 Search Terms

<!--
  💡 Did you know? TypeScript has over 2,000 open suggestions!
  🔎 Please search thoroughly before logging new feature requests as most common ideas already have a proposal in progress.
  The "Common Feature Requests" section of the FAQ lists many popular requests: https://github.com/Microsoft/TypeScript/wiki/FAQ#common-feature-requests

  Replace the text below:
-->

equality, narrow, union

## ✅ Viability Checklist

<!--
   Suggestions that don't meet all these criteria are very, very unlikely to be accepted.
   We always recommend reviewing the TypeScript design goals before investing time writing
   a proposal for ideas outside the scope of the project.
-->
My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).


## ⭐ Suggestion

When comparing union types the TypeScript type checker doesn't narrow types as far as it could, which results in developers needing to be more verbose to fix the errors.

For example, take this code: ([StackBliz version](https://stackblitz.com/edit/typescript-c2gfdz?file=index.ts))

```ts
interface TypeOne {
  type: 'type-one';
  t1Val: number;
}

interface TypeTwo {
  type: 'type-two';
  t2Val: number;
}

type SomeType = TypeOne | TypeTwo;

function compareItems(a: SomeType, b: SomeType): number {
  if (a.type !== b.type) {
    return a.type.localeCompare(b.type);
  }
  if (a.type === 'type-one') {
    return a.t1Val - b.t1Val;
  }
  return a.t2Val - b.t2Val;
}
```

The lines `return a.t1Val - b.t1Val;` and `return a.t2Val - b.t2Val;` fail to compile with the error that `Property 't{1,2}Val' does not exist on type 'SomeType'.`

In isolation, this is perfectly fine, since we have only asserted the value of `a.type`, but we know from the first if statement that `a.type === b.type`, so we should be able to make the same assumptions about the type of `b` at those points.

Specifically, I would like the compiler to be able to infer that if `a.type === 'type-one'` and `a.type === b.type`, then `b.type === 'type-one'` and thus narrow `b` to type `TypeOne`.

## 📃 Motivating Example

```ts
interface TypeOne {
  type: 'type-one';
  t1Val: number;
}

interface TypeTwo {
  type: 'type-two';
  t2Val: number;
}

type SomeType = TypeOne | TypeTwo;

// Before
function compareItems(a: SomeType, b: SomeType): number {
  if (a.type !== b.type) {
    return a.type.localeCompare(b.type);
  }
  if (a.type === 'type-one') {
    return a.t1Val - (b as TypeOne).t1Val;
  }
  return a.t2Val - (b as TypeTwo).t2Val;
}

// After
function compareItems(a: SomeType, b: SomeType): number {
  if (a.type !== b.type) {
    return a.type.localeCompare(b.type);
  }
  if (a.type === 'type-one') {
    return a.t1Val - b.t1Val;
  }
  return a.t2Val - b.t2Val;
}
```

## 💻 Use Cases

This would make certain code patterns (see above) less verbose, more intuitive, and easier to maintain type correctness on. The current solution/workaround requires the explicit use of `as`, which could easily lead to a bug if the definitions of any of the union types change, and makes the code more verbose and harder to read.