ding

create an account to reply

already have one? log in

# [norswap · How TypeScript distributes unions](https://norswap.com/typescript-distribute/)

In the past few weeks I've been writing a little library in TypeScript, and I've learned a lot about the type system in the process. This is the first article, which focuses on how TypeScript deals with unions.

Does this typecheck? And if it does, what is the type of x?

function foo(it: string): string function foo(it: number): number function foo(it: number | string): number | string { return it } declare const it: number | string const x = foo(it)

What about this?

class Foo { foo(): string { return "1" } } class Bar { foo(): number { return 1 } } declare const r: Foo | Bar const x = r.foo()

Final question, in the following snippet does x come out as Foo<number | string> or Foo<number> | Foo<string>?

class Foo<A> { constructor (readonly value: A) {} copy(): Foo<A> { return new Foo(this.value) } } declare const r: Foo<string> | Foo<number> const x = r.copy()

The first example doesn't compile, the second does. This will explain why, and how we can create a functional signature equivalent to that of the first example.

The third example comes out as Foo<number> | Foo<string> (which is assignable to Foo<number | string>). This is what we want! However, we will see how to can force the function to output Foo<number | string>, because that is sometimes convenient.

This is mostly a reference piece for the language nerds amongst you. But if you're curious, let's dive in.

  • - Overload Distributivity
  • - Receiver Distributivity
  • - Conditional Distributivity
  • - "Overloads" via Generic Signatures
  • - Squashing Homogeneous Unions with Extractor Types
  • - Summary

## Overload Distributivity

Let's take our motivating example:

function foo(it: string): string function foo(it: number): number function foo(it: number | string): number | string { return it } declare const it: number | string const x = foo(it) // TS2769: No overload matches this call.

foo is an overloaded function. These work differently in TypeScript than in traditional statically-typed languages like Java. In Java, each overload would be its own distinct function, with its own implementation, that just happen to share a name. In TypeScript, there is a single implementation with multiple signatures.

TypeScript could do what Java does. It could compile each overload to a JavaScript function with a different name and statically assign each call site to the proper overload based on type information.

The reason is doesn't is probably twofold: first, this system allows writing multiple type signatures for a single existing JavaScript function. Second, TypeScript generally shies away from complex code generation: the TypeScript compiler mostly just checks types, then strips them. (There are exceptions, for instance TypeScript enum do generate code, albeit the generation is local and doesn't affect the call site.)

In any case, our argument has type number | string and quite clearly, allowing the call and typing x as number | string is correct.

When matching function arguments, TypeScript simply doesn't distribute signatures over unions.

However, that doesn't hold for method receivers (x in x.foo()). The next section will cover that, the section after that covers some necessary background, and then we'll go on to show how we can actually achieve this overloaded signature!

## Receiver Distributivity