Skip to content

v3.0.1-next.6

Pre-release
Pre-release
Compare
Choose a tag to compare
@gvergnaud gvergnaud released this 12 Mar 10:15

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