Maybe Types
In JavaScript, null
implicitly converts to all the primitive types; it is
also a valid inhabitant of any object type.
In contrast, Flow considers null
to be a distinct value that is not part of
any other type. For example, the following code does not typecheck:
1 2 3 | /* @flow */ var o = null; print(o.x); |
file.js:3:7,9: property x
Property cannot be accessed on possibly null value
file.js:2:9,12: null
Type Annotating Null#
Any type T
can be made to include null
(and the related value undefined
) by writing ?T
: the latter type
is a maybe type that describes null
(or undefined
) or the set of values of T
.
1 2 3 | /* @flow */ var o: ?string = null; print(o.length); |
file.js:3:7,14: property length
Property cannot be accessed on possibly null or undefined value
file.js:2:9,14: ?string
Relaxing a type into a maybe type makes it a valid annotation for any location that may
contain null
, but it still does not allow useful operations to be performed
on it (as shown by the code above). To do so, we must perform a null
check,
as follows:
1 2 3 4 5 6 | /* @flow */ var o: ?string = null; if (o == null) { o = 'hello'; } print(o.length); |
In this code, after the if
-statement Flow infers that o
is not null
(it
either was null
before the if
-statement but is now an object, or was not
null
before the if
-statement). So the code typechecks.
NOTE
It is important to note that we used
==
instead of===
to do anull
check. This ensures that we also check forundefined
, which is considered part of a maybe type. Using=== null
instead would require yet another=== undefined
to cover all cases.
This illustrates an interesting feature of Flow: it understands the effects of some dynamic type tests and can adjust the types of local variables accordingly (in technical terms, Flow’s analysis is path-sensitive).
Read more about dynamic type tests.
Maybe and Objects#
In addition to being able to adjust types of local variables, Flow can sometimes also adjust types of object properties, especially when there are no intermediate operations between a check and a use. In general, though, aliasing of objects limits the scope of this form of reasoning, since a check on an object property may be invalidated by a write to that property through an alias, and it is difficult for a static analysis to track aliases precisely.
In particular, don’t expect a nullable field to be recognized as non-null
in some method because a null
check is
performed in some other method in your code, even when it is clear to you that
the null
check is sufficient for safety at run time (say, because you know
that calls to the former method always follow calls to the latter method). On
the other hand, you can always propagate the result of a null
-check by
explicitly passing around the non-null
value in your code, and if you are
careful enough it should be possible to satisfy Flow without doing additional
null
checks.
Undefined Values and Optional Types#
Undefined values, just like null
, can cause issues too. Unfortunately,
undefined values are ubiquitous in JavaScript and it is hard to avoid them
without severely affecting the usability of the language. For example, arrays
can have holes for elements; object properties can be dynamically added and
removed. Flow ignores the possibility of undefined
resulting from object property and array element
accesses. Being stricter would force the programmer to do undefined
checks (like null
checks) on each dereference of
an array element or object property to get anything useful done.
However, Flow does detect undefined
local variables
and return values, and it considers optional parameters and properties to possibly be undefined
. As such, uses of these types must be guarded by undefined
checks to avoid errors.
You can edit this page on GitHub and send us a pull request!