Skip to content

Releases: gvergnaud/ts-pattern

v3.1.1

25 Mar 09:08
e42438f
Compare
Choose a tag to compare

Better inference of the output type

when not provided, ts-pattern tries to infer the Output type in Match<Input, Output> (the type of a match expression). Until now, the Output type was inferred from the return type of the first branch:

declare let n: number;
let res = match(n)
  .with(2, () => "two") // we return a string, the type of our match expression becomes `Match<number, string>`
  .with(__, () => null) // Type error! `null` isn't of type `string`
  .exhaustive();

This behavior was forcing us to specify the output type to match with match<number, string>(n).

With this release, ts-pattern is a bit smarter about it and automatically infers the Output as the union of the return types of all branches:

declare let n: number;
let res = match(n)
  .with(2, () => "two")
  .with(__, () => null)
  .exhaustive();

// res is inferred as `string | null`

v3.0.6

17 Mar 10:18
Compare
Choose a tag to compare

a few small improvements:

  • for readonly tuples, forbid passing tuple patterns with a different size than the input to .with()
  • Some improvements on doc string comments for match(), .with(), .when() and .exhaustive.

v3.0.4

16 Mar 18:17
Compare
Choose a tag to compare

Bug fix on exhaustive checking for readonly tuple patterns containing a wildcard pattern. These patterns were never considered exhaustive, unless using __ to match the whole input. Examples of impacted patterns: readonly [__, "hello", 2] or readonly [{ x: 0 }, __].

v3.0.3

16 Mar 08:30
Compare
Choose a tag to compare

Fixes a small type inference bug that could occur with input types with optional properties. See this issue for more detail #20

v3.0.2

14 Mar 21:17
Compare
Choose a tag to compare

ts-pattern v3

This versions introduces a few breaking change for the sake of a better general developer experience.

.exhaustive() now ends the pattern matching expression

with v2

const f = (x: 1 | 2 | 3) => 
  match(x)
    .exhaustive()
    .with(1, () => 'one')
    .with(2, () => 'two')
    .with(3, () => 'three')
    .run();

with v3

const f = (x: 1 | 2 | 3) => 
  match(x)
    .with(1, () => 'one')
    .with(2, () => 'two')
    .with(3, () => 'three')
    .exhaustive();

selections are now passed as first argument to the handler

with v2

type Value = { type: 'vec2', x: number, y: number } | { type: 'number', value: number }
const f = (x: [Value, Value]) => 
  match(x)
    .with([{ type: 'vec2', x: select('x'), y: select('y') }, __], (_, { x, y }) => ...)
     ...
    .run();

with v3

type Value = { type: 'vec2', x: number, y: number } | { type: 'number', value: number }
const f = (x: [Value, Value]) => 
  match(x)
    .with([{ type: 'vec2', x: select('x'), y: select('y') }, __], ({ x, y }) => ...)
     ...
    .exhaustive();

Anonymous selection support

ts-pattern now supports anonymous selection for when you want to extract a single value from your pattern:

with v2

// Not possible

with v3

type Value = { type: 'vec2', x: number, y: number } | { type: 'number', value: number }
const f = (x: Value) => 
  match(x)
    .with({ type: 'number', value: select() }, (value) => /* value: number */)
    // you can't have several anonymous `select()` in the same pattern. This is a type error:
    .with({ type: 'vec2', x: select(), y: select() }, (value) => /* value: SeleveralAnonymousSelectError */)
    .exhaustive();

Support for when clauses within .exhaustive() match expressions

when(predicate) patterns and match(...).when(predicate) are now permitted within .exhaustive() match expressions.

with v2

// Not possible

with v3

If your predicate is a type guard function, the case will be considered handled:

type Input = 'a' | 'b'

match<Input>('a')
  .when((x): x is 'a' => x === 'a' , () => {...})
  .when((x): x is 'b' => x === 'b' , () => {...})
  .exhaustive(); // This compiles


match<Input>('a')
  .when((x): x is 'a' => x === 'a' , () => {...})
  // This doesn't compiles
  .exhaustive();

But if your predicate isn't a type guard, exhaustive checking will consider that this clause never matches anything:

match<Input>('a')
  .when((x): x is 'a' => x === 'a' , () => {...})
  .when(x => x === 'b' , () => {...})
  // This doesn't compiles, because ts-pattern has no way to know that the 'b' case is handled
  .exhaustive();

It works similarily with the when() helper function:

type input = { type: 'success', data: string[] } | { type: 'error' }

match(input)
  .with({ type: 'success', data: when(xs => xs.length > 0) }, () => {...})
  .with({ type: 'error' }, () => {...})
  // this doesn't compile, { type: 'success' } with an empty data array is not handled
  .exhaustive();


match(input)
  .with({ type: 'success', data: when(xs => xs.length > 0) }, () => {...})
  .with({ type: 'success' }, () => {...})
  .with({ type: 'error' }, () => {...})
  .exhaustive(); // this compiles

v3.0.1-next.6

12 Mar 10:15
Compare
Choose a tag to compare
v3.0.1-next.6 Pre-release
Pre-release

ts-pattern v3

This versions introduces a few breaking change for the sake of a better general developer experience.

.exhaustive() now ends the pattern matching expression

with v2

const f = (x: 1 | 2 | 3) => 
  match(x)
    .exhaustive()
    .with(1, () => 'one')
    .with(2, () => 'two')
    .with(3, () => 'three')
    .run();

with v3

const f = (x: 1 | 2 | 3) => 
  match(x)
    .with(1, () => 'one')
    .with(2, () => 'two')
    .with(3, () => 'three')
    .exhaustive();

selections are now passed as first argument to the handler

with v2

type Value = { type: 'vec2', x: number, y: number } | { type: 'number', value: number }
const f = (x: [Value, Value]) => 
  match(x)
    .with([{ type: 'vec2', x: select('x'), y: select('y') }, __], (_, { x, y }) => ...)
     ...
    .run();

with v3

type Value = { type: 'vec2', x: number, y: number } | { type: 'number', value: number }
const f = (x: [Value, Value]) => 
  match(x)
    .with([{ type: 'vec2', x: select('x'), y: select('y') }, __], ({ x, y }) => ...)
     ...
    .exhaustive();

Anonymous selection support

ts-pattern now supports anonymous selection for when you want to extract a single value from your pattern:

with v2

// Not possible

with v3

type Value = { type: 'vec2', x: number, y: number } | { type: 'number', value: number }
const f = (x: Value) => 
  match(x)
    .with({ type: 'number', value: select() }, (value) => /* value: number */)
    // you can't have several anonymous `select()` in the same pattern. This is a type error:
    .with({ type: 'vec2', x: select(), y: select() }, (value) => /* value: SeleveralAnonymousSelectError */)
    .exhaustive();

Support for when clauses within .exhaustive() match expressions

when(predicate) patterns and match(...).when(predicate) are now permitted within .exhaustive() match expressions.

with v2

// Not possible

with v3

If your predicate is a type guard function, the case will be considered handled:

type Input = 'a' | 'b'

match<Input>('a')
  .when((x): x is 'a' => x === 'a' , () => {...})
  .when((x): x is 'b' => x === 'b' , () => {...})
  .exhaustive(); // This compiles


match<Input>('a')
  .when((x): x is 'a' => x === 'a' , () => {...})
  // This doesn't compiles
  .exhaustive();

But if your predicate isn't a type guard, exhaustive checking will consider that this clause never matches anything:

match<Input>('a')
  .when((x): x is 'a' => x === 'a' , () => {...})
  .when(x => x === 'b' , () => {...})
  // This doesn't compiles, because ts-pattern has no way to know that the 'b' case is handled
  .exhaustive();

It works similarily with the when() helper function:

type input = { type: 'success', data: string[] } | { type: 'error' }

match(input)
  .with({ type: 'success', data: when(xs => xs.length > 0) }, () => {...})
  .with({ type: 'error' }, () => {...})
  // this doesn't compile, { type: 'success' } with an empty data array is not handled
  .exhaustive();


match(input)
  .with({ type: 'success', data: when(xs => xs.length > 0) }, () => {...})
  .with({ type: 'success' }, () => {...})
  .with({ type: 'error' }, () => {...})
  .exhaustive(); // this compiles

v2.4.0

09 Mar 21:10
Compare
Choose a tag to compare

Support for when predicate with .exhaustive()

when(predicate) patterns and match(...).when(predicate) are now permitted within .exhaustive() match expressions.

If your predicate is a type guard function, the case will be considered handled:

type Input = 'a' | 'b'

match<Input>('a')
  .exhaustive()
  .when((x): x is 'a' => x === 'a' , () => {...})
  .when((x): x is 'b' => x === 'b' , () => {...})
  // This compiles
  .run();


match<Input>('a')
  .exhaustive()
  .when((x): x is 'a' => x === 'a' , () => {...})
  // This doesn't compiles
  .run();

But if your predicate isn't a type guard, exhaustive checking will consider that this clause never matches anything:

match<Input>('a')
  .exhaustive()
  .when((x): x is 'a' => x === 'a' , () => {...})
  .when(x => x === 'b' , () => {...})
  // This doesn't compiles, because ts-pattern has no way to know that the 'b' case is handled
  .run();

It works similarily with the when() helper function:

type input = { type: 'success', data: string[] } | { type: 'error' }

match(input)
  .exhaustive()
  .with({ type: 'success', data: when(xs => xs.length > 0) }, () => {...})
  .with({ type: 'error' }, () => {...})
  // this doesn't compile, { type: 'success' } with an empty data array is not handled
  .run();


match(input)
  .exhaustive()
  .with({ type: 'success', data: when(xs => xs.length > 0) }, () => {...})
  .with({ type: 'success' }, () => {...})
  .with({ type: 'error' }, () => {...})
  // this compiles
  .run();

v2.2.3

08 Mar 18:44
Compare
Choose a tag to compare

A few bug fixes regarding type inference:

  • Improved support for readonly inputs
  • Fix a bug with exhaustive matching: string and number literals (like "hello" or 2) where inferred as string and number. When patterns were containing literals, the pattern matching expression was considered exhaustive, even though it wasn't.
  • Fix an inference issue with any: If an any type was contained in your input data structure, ts-pattern was sometimes unable to infer which branch of the input type was matching your pattern.

v2.2.2

03 Mar 08:47
Compare
Choose a tag to compare

Support for multiple patterns

You can now provide up to 5 patterns to .with(), and the code branch will be chosen if your input matches one of these patterns.

Example:

match<Country>('France')
      .exhaustive()
      // if it matches one of these patterns
      .with('France', 'Germany', 'Spain', () => 'Europe')
      .with('USA', () => 'America')
      .run();

    match<Country>('France')
      .exhaustive()
      .with('Germany', 'Spain', () => 'Europe')
      .with('USA', () => 'America')
      // doesn't compile! 'France' is missing
      .run();

v2.1.4

24 Feb 18:43
Compare
Choose a tag to compare

Bug Fixes:

  • The selection object you get in second parameter has a better type inference

General improvements:

  • Tests are now using Expect and Equal type utility functions