website/docs/match/patterns.md
Match patterns both define a condition that must be matched, and new variables that are extracted (like destructuring).
Primitive value patterns include string literals (e.g. 'light'), number literals (e.g. 42), BigInt literals (e.g. 10n), boolean literals (e.g. true), null, and undefined.
You can use variables (e.g. name) or property accesses (e.g. Status.Active) which are either typed as a literal type, or a Flow Enum member. Using variables that are a general type like string or number is not allowed for match patterns - they must be a literal type like 'light'. You can add a type annotation or as const to your string value to type it with a literal type. Computed properties in match patterns only allow literals like foo['bar'] or foo[2].
The identifier _ is special cased for “Wildcard patterns”, if you want to match against a value with that name, rename it to something else first. If you want to create a new variable, take a look at “variable declaration patterns” below - it is done by doing const x.
You can use a number literal prefixed with + or -, or BigInt literals prefixed with - (+ on a BigInt is an error in JS). +0 and -0 are not allowed (Flow doesn’t differentiate these type-wise from 0). NaN is special-cased, since NaN === NaN is always false. It is matched using Number.isNaN.
Other types of expressions are not supported. To match against an arbitrary expression (which has a literal type), first assign it to a variable, and then match against that variable.
Example:
const Status = {active: 'active', paused: 'paused'} as const;
declare const ranks: [10, 20];
declare const fallbackLabel: 'unknown';
declare const value: unknown;
match (value) {
0 => {}
'archived' => {}
null => {}
-1 => {}
fallbackLabel => {} // a literal-typed variable
Status.active => {} // a property access
Status['paused'] => {} // a computed string member
ranks[0] => {} // a computed number member
_ => {}
}
Flow will never output an “unused pattern” error for the undefined pattern, so you can always add it even if the input type does not contain void. This is to cover the case where Flow does not compute a type that is 100% accurate to the runtime value (for example, from indexed access of an array past its length).
At runtime, these checks are done using triple equals ===.
Wildcard patterns, which are a single underscore _, match everything. If you want to match against the value of a variable named _, assign it to a different name first.
If part of your input type cannot be matched exhaustively (e.g. string), then the match will require a wildcard.
Example:
declare const username: string;
match (username) {
_ => {}
}
Variable declaration patterns, like const name, take whatever value is at that position and assign it to a new variable. Conditional check wise, they act like a wildcard and match everything.
Example:
declare const input: string;
const greeting = match (input) {
const name => `Hello, ${name}!`,
};
While let variables are also supported by the runtime, these are a type error for now and only const variables are allowed. If you have a use case, please share it with the team. var is not supported.
Object patterns match object values with the same structure. For example, the pattern {type: 'light', num: 42} matches objects which have the property type with value 'light', and a num property with value 42. If the object value has additional unlisted properties, or is inexact, you need to make your pattern inexact using ..., for example {type: 'light', num: 42, ...}.
Like destructuring, a variable declaration pattern nested inside an object pattern creates a new variable with the value of that property. E.g. match (arg) { {prop: const x} => x } will initialize x with the value arg.prop. You can use object rest to gather the rest of the object’s own properties: {foo: 1, ...const rest}.
Doing just {name} is ambiguous - it could mean {name: name} (matching against the value of variable name) or {name: const name} (extracting the property’s value as a new variable called name), so it’s not allowed. If you want a shorthand for creating new variables, you can use {const name} to mean {name: const name}.
If you have a property with either a wildcard or variable declaration pattern, e.g. {foo: _} or {foo: const x}, it is checked that the property is in the object (checks own and non-own properties).
When checking objects with optional properties, in order to make the check exhaustive you must include a pattern that doesn’t include the optional properties. For example, for {name: string, age?: number}, if you first match with the pattern {name: _, age: _}, you still need to match with the pattern {name: _, ...} in order to handle the cases where the age property doesn’t exist.
Property names can be identifiers (e.g. foo: pattern), string literals (e.g. 'foo': <pattern>), or number literals (e.g. 2: <pattern>). Repeated object keys are banned, and BigInts are not supported as object keys (Flow doesn’t yet support them).
Example:
declare const event:
| {kind: 'click', x: number, y: number}
| {message: string}
| {kind: 'scroll', delta: number};
const summary = match (event) {
{kind: 'click', x: const x, y: const y} => `click at ${x},${y}`,
{const message} => message,
{kind: 'scroll', ...const rest} => `scroll ${rest.delta}`,
};
Getters are also not supported - at runtime they will be evaluated multiple times, once for each conditional check done against them.
Instance patterns match instances of a class. The pattern is a class name followed by an object pattern, for example Point {x: 1, ...} matches values that are instances of Point whose x property is 1. At runtime this is like checking p instanceof Point && p.x === 1.
The object pattern part works just like a regular object pattern: you can extract properties with variable declaration patterns, use the {const x} shorthand, and gather the rest with object rest.
Instances are always inexact, since a class can be extended with additional properties, so you must end the pattern with ....
Example:
class Point {
x: number;
y: number;
}
declare const p: Point;
const sum = match (p) {
Point {const x, const y, ...} => x + y,
};
If you leave off the ..., Flow errors, because the instance could have additional properties:
class Point {
x: number;
y: number;
}
declare const p: Point;
match (p) {
Point {const x, const y} => {} // ERROR: instances are inexact, so `...` is required
}
Different classes in a union can each be matched by their own pattern, and the body is refined to that class:
class Shape {
area(): number {
return 0;
}
}
class Circle extends Shape {
radius: number;
}
class Square extends Shape {
side: number;
}
declare const shape: Circle | Square;
const size = match (shape) {
Circle {const radius, ...} => radius, // matches `Circle` instances
Square {const side, ...} => side, // matches `Square` instances
};
Matching follows the prototype chain like instanceof: a pattern naming a superclass matches instances of any subclass.
class Shape {
area(): number {
return 0;
}
}
class Circle extends Shape {
radius: number;
}
class Square extends Shape {
side: number;
}
declare const shape: Circle | Square;
const out = match (shape) {
Shape {...} => shape.area(), // a superclass pattern matches any subclass instance
};
Object patterns are structural and also match instances, so {const x, const y, ...} matches a Point. An instance pattern, on the other hand, only matches instances of the named class, and never a plain object.
class Point {
x: number;
y: number;
}
declare const p: Point;
const sum = match (p) {
{const x, const y, ...} => x + y, // an object pattern matches the instance
};
The constructor must reference a single class. Using a type, interface, or other value is an error.
class Point {
x: number;
y: number;
}
declare const p: Point;
declare const NotAClass: string;
match (p) {
NotAClass {...} => {} // ERROR: constructor must reference a single class
_ => {}
}
Instance patterns are available by default since Flow v0.317.
Array patterns match both tuple and array values. For example, the pattern ['small', true] matches array values whose length is 2 and whose elements match the pattern’s elements. If you want a looser length check, you can make the pattern inexact using ..., for example ['small', true, ...] will match arrays whose length is >= 2 and whose first elements match the pattern’s elements. You can match any array or tuple with [...].
Like destructuring, a variable declaration pattern nested inside an array pattern creates a new variable with the value of that element. E.g. match (arg) { [const x] => x } will initialize x with the value arg[0]. You can use array rest at the end of the pattern to gather the remaining elements, and check that the length is greater or equal to the pattern length: e.g. [1, 2, ...const rest].
Array patterns match values which pass Array.isArray, so they won’t match array-like objects that aren’t actually arrays, and won’t match iterables. Use Array.from on those types of values first if you want to match them with array patterns.
Example:
declare const color: [0, 0, 0] | [255, 0, 0, 1] | [0, 0, 255, 0.5];
const css = match (color) {
[0, 0, 0] => 'black',
[255, 0, 0, ...] => 'opaque red',
[0, 0, 255, ...const rest] => `blue at ${rest[0]} alpha`,
};
Or patterns allow you to combine multiple patterns using |, for example 'active' | 'paused' will match either string literal.
Variable declaration patterns inside of "or" patterns are not yet supported.
Example:
declare const reply: 'yes' | 'yeah' | 'no' | 'nope';
const answer = match (reply) {
'yes' | 'yeah' => true,
'no' | 'nope' => false,
};
As patterns both match a pattern and create a new variable. For example, [_, _] as pair will first match any arrays whose length is 2, and then assign that value to a new variable called pair.
Example:
declare const result: ['ok', number] | ['error', string];
const handled = match (result) {
['ok', _] as success => success,
['error', _] as failure => failure,
};