Releases: gvergnaud/ts-pattern
v3.1.1
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
v3.0.4
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
v3.0.2
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
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
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
A few bug fixes regarding type inference:
- Improved support for readonly inputs
- Fix a bug with exhaustive matching: string and number literals (like
"hello"
or2
) where inferred asstring
andnumber
. When patterns were containing literals, the pattern matching expression was considered exhaustive, even though it wasn't. - Fix an inference issue with
any
: If anany
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
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();