Assertion Functions
Assertion functions narrow a value's type after a call returns normally. Use them to check a value and throw an error when the check fails.
Unlike type guards, which return a boolean for use in a conditional, assertion functions return void. The asserts annotation tells Flow how to narrow values after a successful call.
Basic Usage ≥0.336
Annotate the return type with asserts parameter is Type:
1function assertString(value: unknown): asserts value is string {2 if (typeof value !== 'string') {3 throw new Error('Expected a string');4 }5}6
7function uppercase(value: unknown): string {8 assertString(value);9 value as number; // ERROR: value is now a stringincompatible-typeCannot cast value to number because string [1] is incompatible with number [2].10 return value.toUpperCase();11}After assertString(value) returns, Flow narrows value to string. You do not need an if at the call site.
Asserting a Condition
Use asserts parameter without an is Type clause to assert that the argument is truthy. You can pass a value directly or an expression that Flow understands as a refinement:
1function assert(condition: unknown): asserts condition {2 if (!condition) {3 throw new Error('Assertion failed');4 }5}6
7function check(name: ?string, count: unknown) {8 assert(name);9 name as string; // OK10 name as null; // ERRORincompatible-typeCannot cast name to null because string [1] is incompatible with null [2].11
12 assert(typeof count === 'number');13 count as number; // OK14}A truthiness assertion rejects falsy values such as 0, false, and '', as well as null and undefined.
Flow treats assert(false) as an abnormal exit, so code after the call is unreachable.
Validating the Function Body
Flow checks that paths where the assertion does not hold exit abnormally, for example by throwing an error. An explicit return; or reaching the end of the body is only allowed once the assertion holds. Calling another assertion function can establish the assertion or throw when it fails.
1function missingCheck(value: unknown): asserts value is string {} // ERRORincompatible-type-guardCannot return from assertion function at the end of its body because unknown [1] is incompatible with string [2].2
3function incompleteCheck(value: unknown, skip: boolean): asserts value is string {4 if (skip) {5 return; // ERROR: value has not been narrowedincompatible-type-guardCannot return from assertion function because unknown [1] is incompatible with string [2].6 }7 if (typeof value !== 'string') {8 throw new Error('Expected a string');9 }10}11
12function missingTruthyCheck(value: ?string): asserts value {} // ERRORincompatible-type-guardCannot return from assertion function at the end of its body because value [1] may still be falsy here: it may be ?"" [2]. Throw an error or call another assertion function on every path where it is falsy.When asserting a parameter, the name must refer to a regular parameter of the function, rather than a rest parameter or a name bound by destructuring. For asserts parameter is Type, Type must be a subtype of the parameter's type. You cannot reassign the asserted parameter to satisfy the assertion: that would change the local parameter, not the caller's value.
Unlike a two-sided type guard, an assertion function does not need to prove that values rejected by the check are outside the asserted type. For example, a function that asserts a number may also reject negative numbers.
Function Types
You can use assertion annotations in function types, including callback parameters, object properties, and library definitions:
1type StringAssertion = (value: unknown) => asserts value is string;2
3declare function assertString(value: unknown): asserts value is string;4
5function check(value: unknown, callbackValue: unknown, callback: StringAssertion) {6 const localAssert: StringAssertion = assertString;7 localAssert(value);8 value as string; // OK9
10 callback(callbackValue);11 callbackValue as string; // OK12}Call Restrictions
Assertion functions return void, so a standalone call is the usual way to use them. Flow applies assertion narrowing to standalone call statements; assigning the result to a variable does not narrow the argument:
1declare function assertString(value: unknown): asserts value is string;2
3function check(value: unknown) {4 const result = assertString(value);5 result as void; // OK6 value as string; // ERROR: this call did not narrow valueincompatible-typeCannot cast value to string because unknown [1] is incompatible with string [2].7
8 assertString(value);9 value as string; // OK10}Current Limitations
Flow recognizes function declarations, imported assertion functions, and aliases defined at module scope. Aliases inside a function body need an explicit type annotation for assertion narrowing. The same requirement applies to local objects holding assertion methods:
1declare function assertString(value: unknown): asserts value is string;2type StringAssertion = (value: unknown) => asserts value is string;3
4function check(value: unknown) {5 const inferred = assertString;6 inferred(value); // ERROR: the local alias needs an annotationunrecognized-assertion-callCannot narrow with this call to an assertion function because inferred [1] is not declared with an explicit type annotation. Annotate inferred or call the assertion function directly.7
8 const annotated: StringAssertion = assertString;9 annotated(value);10 value as string; // OK11}Flow currently requires the callee to be an identifier or a chain of named properties, such as assertString(value) or checks.assertString(value). Computed property access and calling a function returned by another call are not supported for assertion narrowing:
1declare const checks: {2 assertString(value: unknown): asserts value is string,3};4declare function getAssertion(): (value: unknown) => asserts value is string;5
6function check(value: unknown) {7 checks['assertString'](value); // ERRORunrecognized-assertion-callCannot narrow with this call to an assertion function because the call target is not an identifier or a dotted name such as a.b.c.8 getAssertion()(value); // ERRORunrecognized-assertion-callCannot narrow with this call to an assertion function because the call target is not an identifier or a dotted name such as a.b.c.9
10 checks.assertString(value);11 value as string; // OK12}Flow currently does not narrow values for optional calls or calls rooted in this or super.
Adoption
Assertion functions are available starting in Flow 0.336. In Flow 0.336, enable them in your .flowconfig:
[options]
experimental.assertion_functions=true
Starting in Flow 0.337, assertion functions are enabled by default and you do not need this flag.
invariantStarting in Flow 0.337, Flow no longer gives functions named invariant special narrowing or control-flow behavior. If you relied on that behavior, annotate the function or its library definition with asserts condition:
1declare function invariant(condition?: unknown, message?: string): asserts condition;2
3function check(value: ?string): string {4 invariant(value != null);5 return value; // OK: value is a string6}See Also
- Type Guards — narrow values in conditional branches
- Refinements — built-in type narrowing
- Functions — function return type annotations