Skip to main content

Type Guards

Flow lets you define functions whose return expression encodes some type predicate over a parameter param. This predicate is annotated in place of a return type annotation as param is PredicateType. It declares that if the function returns true then param is of type PredicateType.

The syntax for a function like this is:

function predicate(param: InputType): param is PredicateType {
return <some_expression>;
}

The type of this function can also be written in terms of a type guard annotation:

type PredicateFunc = (param: InputType) => param is PredicateType;

Basic Usage

Let's see a simple example where we define a type guard function and then use it to refine some values.

Defining a type guard function

1type A = { type: "A"; data: string };2type B = { type: "B"; data: number };3type AorB = A | B;4
5function isA(value: AorB): value is A {6  return value.type === "A";7}

We have defined a data type AorB that is a disjoint union of two types A and B that each have a property type used as tag.

We have also written a user defined type guard function isA defined over objects of type AorB. This function returns true when the value of of the type property of its input is "A". Using the definitions of A and B, Flow can prove that when the value of type is "A" then the type of value will be A.

Using a type guard function to refine values

Functions that have a declared type guard can be used to refine values in conditionals. In the example above, we can use isA to refine a variable of type AorB to just A:

1type A = { type: "A"; data: string };2type B = { type: "B"; data: number };3type AorB = A | B;4
5function isA(value: AorB): value is A {6  return value.type === "A";7}8
9function test(x: AorB) {10  if (isA(x)) {11    // `x` has now been refined to type A.12    // We can assign it variables of type A ...13    const y: A = x;14    // ...and access A's properties through `x`15    const stringData: string = x.data;16
17    // As a sanity check, the following assignment to B will error18    const error: B = x;
19 }20}
18:22-18:22: Cannot assign `x` to `error` because string [1] is incompatible with number [2] in property `data`. [incompatible-type]
18:22-18:22: Cannot assign `x` to `error` because string literal `A` [1] is incompatible with string literal `B` [2] in property `type`. [incompatible-type]

In the then-branch of the conditional if (isA(x)), x will have the type A.

Refine with Array.filter

Flow recognizes when you call filter on an array of type Array<T> with a callback function that holds a type guard with type (value: T) => value is S. It will use this to produce an output array of type Array<S>. Note that S needs to be a subtype of the type of the array element T.

For example

1type Success = $ReadOnly<{type: 'success', value: 23}>;2type Error = $ReadOnly<{type: 'error', error: string}>;3
4type Response =5  | Success6  | Error7
8function filterSuccess(response: Array<Response>): Array<Success> {9  return response.filter(10    (response): response is Success => response.type === 'success'11  );12}13
14function filterError1(response: Array<Response>): Array<Error> {15  const result = response.filter(16    (response): response is Success => response.type === 'success'17  );18  // The following is expected to error19  return result;
20}21 22function filterError2(response: Array<Response>): Array<Error> {23 const result = response.filter(24 // The following is expected to error25 (response): response is Error => response.type === 'success'
26 );27 return result;28}
19:10-19:15: Cannot return `result` because property `error` is missing in object type [1] but exists in object type [2] in array element. [prop-missing]
19:10-19:15: Cannot return `result` because property `value` is missing in object type [1] but exists in object type [2] in array element. [prop-missing]
19:10-19:15: Cannot return `result` because string literal `success` [1] is incompatible with string literal `error` [2] in property `type` of array element. [incompatible-return]
25:38-25:64: Cannot return `response.type === 'success'` because property `error` is missing in object type [1] but exists in object type [2] in the type inferred for type guard parameter `response` [3]. [prop-missing]
25:38-25:64: Cannot return `response.type === 'success'` because property `value` is missing in object type [1] but exists in object type [2] in the type inferred for type guard parameter `response` [3]. [prop-missing]
25:38-25:64: Cannot return `response.type === 'success'` because string literal `success` [1] is incompatible with string literal `error` [2] in property `type` of the type inferred for type guard parameter `response` [3]. [incompatible-return]

In filterError1, filtering produces Array<Success> that is not compatible with the expected return type Array<Error>.

In filterError2, the predicate response.type === 'success' is used to refine Responses to Successs, not Errors.

Defining Type Guard Functions

To ensure that refinement with type guard functions is sound, Flow runs a number of checks associated with these functions.

Predicate parameter is a regular parameter to the function

In a type guard annotation of the form parameter is Type, parameter needs to belong to the current function's parameter list.

1function missing(param: mixed): prop is number {
2 return typeof param === "number";3}
1:33-1:36: Cannot find type guard parameter `prop` [1] in the parameters of this function (type). [function-predicate]

It cannot be a parameter bound in a destructuring pattern, or a rest paramter:

1function destructuring({prop}: {prop: mixed}): prop is number {
2 return typeof prop === "number";3}
1:48-1:51: A type guard parameter `prop` [1] cannot reference pattern parameter `prop` [2]. [function-predicate]
1function rest(...value: Array<mixed>): value is Array<mixed> {
2 return Array.isArray(value);3}
1:40-1:44: A type guard parameter `value` [1] cannot reference rest parameter `value` [2]. [function-predicate]

Predicate type is consistent with the parameter type

The type guard Type needs to be compatible with the type of the parameter. In other words, given a definition

function isT(x: ParamType): x is Type {
return ...
}

Flow will check that Type is a subtype of ParamType. So the following will be an error:

1function isNumber(x: string): x is number {
2 return typeof x === "number";3}
1:36-1:41: Cannot use number [1] as type prediate for parameter `x` because number [1] is incompatible with string [2]. A user defined type guard needs to be compatible with its parameter's type. [incompatible-type-guard]

Type guard function returns boolean

A type guard function needs to return a boolean expression. The following are invalid declarations:

1function isNumberNoReturn(x: string): x is string {}
1:39-1:49: Cannot declare a type predicate [1] for function [2] because boolean [1] is incompatible with implicitly-returned undefined. [incompatible-return]
1function nonMaybe<V: {...}>(x: ?V): x is V {2  return x;
3}
2:10-2:10: Cannot return `x` because null or undefined [1] is incompatible with boolean [2]. [incompatible-return]
2:10-2:10: Cannot return `x` because object type [1] is incompatible with boolean [2]. [incompatible-return]

A correct version of nonMaybe would be

1function nonMaybe<V: {...}>(x: ?V): x is V {2  return !!x;3}

Predicate type is consistent with refined type

In addition to the above checks, Flow also ensures that the declared type guard is consistent with the check happening in the body of the function. To establish this it needs to guarantee two things:

  1. The type of the refined parameter at the return location after the predicate of the return expression has been applied is a subtype of the guard type. For example, the following definitions are correct:
1function numOrStr(x: mixed): x is number | string {2  return (typeof x === "number" || typeof x === "string");3}4
5function numOrStrWithException(x: mixed): x is number | string {6  if (typeof x === "number") {7    return true;8  } else {9    if (typeof x === "string") {10        return true;11    } else {12        throw new Error("");13    }14  }15}

But in the following Flow will raise errors:

1function numOrStrError(x: mixed): x is number | string {2  return (typeof x === "number" || typeof x === "boolean");
3}
2:36-2:57: Cannot return `((typeof x) === "number") || ((typeof x) === "boolean")` because in the type inferred for type guard parameter `x` [1]: [incompatible-return] Either boolean [2] is incompatible with number [3]. Or boolean [2] is incompatible with string [4].
  1. The parameter that is refined cannot be reassigned in the body of the type guard function. Therefore the following are errors:
1function isNumberError1(x: mixed): x is number {2  x = 1;3  return typeof x === "number";
4}
3:10-3:30: Cannot use type guard parameter `x` [1] because at this return point it is written to in [2]. [function-predicate]
1function isNumberError2(x: mixed): x is number {
2 function foo() {3 x = 1;4 }5 foo();6 return typeof x === "number";7}
1:36-1:36: Cannot use type guard parameter `x`, because `x` [1] is reassigned in [2]. [function-predicate]

Adoption

To use type guards, you need to upgrade your infrastructure so that it supports the syntax:

  • flow and flow-parser: 0.209.1. Between v0.209.1 to v0.211.1, you need to explicitly enable it in your .flowconfig, under the [options] heading, add type_guards=true.
  • prettier: 3
  • babel with babel-plugin-syntax-hermes-parser. See our Babel guide for setup instructions.
  • eslint with hermes-eslint. See our ESLint guide for setup instructions.