Smarter union type checking

In TypeScript 3.4 and prior, the following example would fail:

  1. type S = { done: boolean, value: number }
  2. type T =
  3. | { done: false, value: number }
  4. | { done: true, value: number };
  5. declare let source: S;
  6. declare let target: T;
  7. target = source;

That’s because S isn’t assignable to { done: false, value: number } nor { done: true, value: number }.Why?Because the done property in S isn’t specific enough - it’s boolean whereas each constituent of T has a done property that’s specifically true or false.That’s what we meant by each constituent type being checked in isolation: TypeScript doesn’t just union each property together and see if S is assignable to that.If it did, some bad code could get through like the following:

  1. interface Foo {
  2. kind: "foo";
  3. value: string;
  4. }
  5. interface Bar {
  6. kind: "bar";
  7. value: number;
  8. }
  9. function doSomething(x: Foo | Bar) {
  10. if (x.kind === "foo") {
  11. x.value.toLowerCase();
  12. }
  13. }
  14. // uh-oh - luckily TypeScript errors here!
  15. doSomething({
  16. kind: "foo",
  17. value: 123,
  18. });

However, this was a bit overly strict for the original example.If you figure out the precise type of any possible value of S, you can actually see that it matches the types in T exactly.

In TypeScript 3.5, when assigning to types with discriminant properties like in T, the language actually will go further and decompose types like S into a union of every possible inhabitant type.In this case, since boolean is a union of true and false, S will be viewed as a union of { done: false, value: number } and { done: true, value: number }.

For more details, you can see the original pull request on GitHub.