github gvergnaud/ts-pattern v2.1.3

latest releases: v5.1.1, v5.1.0, v5.0.8...
3 years ago

This release features a major refactoring of the way exhaustive pattern matching is enforced, that should drastically improve its compile time performances on medium to large input types.

Motivation

.exhaustive() used to transform the input type in a flat union type, containing all the possible combination of all unions contained in the input type. For instance a type like:

type Input = {type: "a", mode: "b" | "c" | "d"} |  {type: "b", mode: "f" | "g"}

Would be turned into a type like this:

type DistributedInput =
  | {type: "a", mode: "b"} 
  | {type: "a", mode: "c"} 
  | {type: "a", mode: "d"} 
  | {type: "b", mode: "f"} 
  | {type: "b", mode: "g"}   

This solution was working fine, but the downside is that sometimes your Input type contains some huge unions that you never (or rarely) want to match against. Here is an example:

type CSSColor = "grey" | "red" | "yellow" | "blue" | "green" | ...; // hundreds of color names

type Input  =
  | { type: "text"; color: CSSColor }
  | { type: "button"; color: CSSColor; backgroundColor: CSSColor };

match(input)
    .exhaustive() // "union type that is too complex to represent"
    .with({ type: "button" }, () => ...)
    .with({ type: "text" }, () => ...)
    .run();

We hit the union limit of 500 items, even though we actually didn't need to transform the input type at all since we were only matching against its type property.

Changes

Now exhaustive matching is a lot smarter because it only computes the combination of unions against which you are really matching, which means that in the case described above the input type is unchanged:

match(input)
    .exhaustive() // nothing happens at this point, `Input` stay the same.
    .with({ type: "button" }, () => ...) // we match on the `type` property, we don't need to change `Input`.
    .with({ type: "text" }, () => ...) // same as above.
    .run(); // it works

If we were matching on a specific color, though, we would have to distribute the CSSColor union over the Input type:

match(input)
    .exhaustive() // input: Input
    .with({ type: "button" }, () => ...) // input: Input
    // below we are matching on both `type` and `color`, we need to 
    // distribute matched unions over the Input type:
    .with({ type: "text", color: "blue" }, () => ...) 
    /* input: | { type: "button"; color: CSSColor; backgroundColor: CSSColor }
     *        | { type: "text", color: "grey" }
     *        | { type: "text", color: "red" }
     *        | { type: "text", color: "yellow" }
     *        | ... all possible colors except "blue". We need to distribute at this point,
     *              but note that the `type: "button"` case hasn't been distributed, 
     *              so we don't reach the `union too complex to represent` limit.
     */     
    .run();

You can find more details in this issue #16 from @m-rutter

Don't miss a new ts-pattern release

NewReleases is sending notifications on new releases.