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
.
One-sided Type Guards
Note: This feature is available as of v0.237.0 when option one_sided_type_guards=true
is set in the flowconfig. It is enabled by default as of v0.239.0.
In some cases we may want to declare that a type guard function only refines the then-branch of a conditional. Consider for example the function
1function isPositive(n: ?number): boolean {2 return n != null && n > 0;3}
If we declared n is number
as the type guard of this function then in the following code:
declare n: ?number;
if (isPositive(n)) {
// n is number here
} else {
// n would be null | void here
}
we would be able to establish that n
is null | void
in the else-branch. This is not true, however, since n
could just be a non-negative number.
One-sided type guards, which we annotate as implies param is PredicateType
, let us specify that a predicate narrows the type in only the positive case. For example,
1function isPositive(n: ?number): implies n is number {2 return n != null && n > 0;3}
Now, we'll get the following behavior
declare n: ?number;
if (isPositive(n)) {
// n is number here
} else {
// n is still ?number
}
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:17-25:33: Inconsistent type guard declaration because property `error` is missing in object type [1] but exists in object type [2]. The type of `response` [3] refined with the predicate encoded in return expression `response.type === 'success'` [4] needs to be compatible with the guard type `Error` [2]. See 1. in https://flow.org/en/docs/types/type-guards/#toc-consistency-checks-of-type-guard-functions. [prop-missing]25:17-25:33: Inconsistent type guard declaration because string literal `success` [1] is incompatible with string literal `error` [2] in property `type`. The type of `response` [3] refined with the predicate encoded in return expression `response.type === 'success'` [4] needs to be compatible with the guard type `Error` [5]. See 1. in https://flow.org/en/docs/types/type-guards/#toc-consistency-checks-of-type-guard-functions. [incompatible-type-guard]25:29-25:33: Inconsistent type guard declaration because property `value` is missing in object type [1] but exists in object type [2]. The type of `response` [3] refined with the predicate encoded in return expression `response.type === 'success'` [4] needs to be compatible with the guard type `Error` [1]. See 1. in https://flow.org/en/docs/types/type-guards/#toc-consistency-checks-of-type-guard-functions. [prop-missing]25:17-25:33: Inconsistent type guard declaration. The negation of the predicate encoded in return expression `response.type === 'success'` [1] needs to completely refine away the guard type `Error` [2]. Consider using a one-sided type-guard (`implies x is T`). See 2. in https://flow.org/en/docs/types/type-guards/#toc-consistency-checks-of-type-guard-functions. [incompatible-type-guard]
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 Response
s to Success
s, not Error
s.
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 guard [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:
- 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}
5:43-5:62: Inconsistent type guard declaration. The negation of the predicate encoded in return expression `true` [1] needs to completely refine away the guard type union type [2]. Consider using a one-sided type-guard (`implies x is T`). See 2. in https://flow.org/en/docs/types/type-guards/#toc-consistency-checks-of-type-guard-functions. [incompatible-type-guard]5:43-5:62: Inconsistent type guard declaration. The negation of the predicate encoded in return expression `true` [1] needs to completely refine away the guard type union type [2]. Consider using a one-sided type-guard (`implies x is T`). See 2. in https://flow.org/en/docs/types/type-guards/#toc-consistency-checks-of-type-guard-functions. [incompatible-type-guard]
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}
1:35-1:54: Inconsistent type guard declaration because: [incompatible-type-guard] Either boolean [1] is incompatible with number [2]. Or boolean [1] is incompatible with string [3]. The type of `x` [4] refined with the predicate encoded in return expression `((typeof x) === "number") || ((typeof x) === "boolean")` [5] needs to be compatible with the guard type union type [6]. See 1. in https://flow.org/en/docs/types/type-guards/#toc-consistency-checks-of-type-guard-functions.1:35-1:54: Inconsistent type guard declaration. The negation of the predicate encoded in return expression `((typeof x) === "number") || ((typeof x) === "boolean")` [1] needs to completely refine away the guard type union type [2]. Consider using a one-sided type-guard (`implies x is T`). See 2. in https://flow.org/en/docs/types/type-guards/#toc-consistency-checks-of-type-guard-functions. [incompatible-type-guard]
- Type guard functions can be used to refine the else-branch of conditionals. For example,
1function isNumber(x: mixed): x is number {2 return typeof x === "number";3}4
5declare var value: number | string;6if (isNumber(value)) {7 value as number; // okay8} else {9 value as string; // also okay10}
Therefore, the inverse form of the first requirement also needs to hold. Specifically, if we negate the predicate encoded in the function, and use it to refine the input, then the result must not overlap with the type guard at all. This condition is equivalent to checking that the type guard refined with the negation of the function predicate is a subtype of empty
. For example the following raises an error:
1function isPosNum(x: mixed): x is number { 2 return typeof x === 'number' && x > 0;3}
1:30-1:40: Inconsistent type guard declaration. The negation of the predicate encoded in return expression `((typeof x) === 'number') && (x > 0)` [1] needs to completely refine away the guard type number [2]. Consider using a one-sided type-guard (`implies x is T`). See 2. in https://flow.org/en/docs/types/type-guards/#toc-consistency-checks-of-type-guard-functions. [incompatible-type-guard]
This is because the negation of the predicate of isPosNum
is "x
is not a number or x<=0
". This predicate is equivalent to the empty predicate and does not refine the input type it is applied to.
If you're seeing errors related to this check, consider using a one-sided type guard (write implies x is T
). Ones-sided type guards do not require this check, since they do not refine the else-branch of conditionals.
Note: This check only happens when one_sided_type_guards=true
is set in the flowconfig. It happens by default as of v0.239.0.
- 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
andflow-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, addtype_guards=true
. One-sided type guards are available as of version 0.237.0 with the optionone_sided_type_guards=true
, and are enabled by default as of v0.239.0.prettier
: 3babel
withbabel-plugin-syntax-hermes-parser
. See our Babel guide for setup instructions.eslint
withhermes-eslint
. See our ESLint guide for setup instructions.