Flow, a new static type checker for JavaScript

Avik ChaudhuriBasil HosmerGabriel Levi

Today we’re excited to release an early version of Flow, a new open-source static type checker for JavaScript. Flow adds static typing to JavaScript to improve developer productivity and code quality. In particular, static typing offers benefits like early error checking, which helps you avoid certain kinds of runtime failures, and code intelligence, which aids code maintenance, navigation, transformation, and optimization.

We have designed Flow so developers can reap its benefits without losing the “feel” of coding in JavaScript. Flow adds minimal compile-time overhead, as it does all its work proactively in the background. And Flow does not force you to change how you code — it performs sophisticated program analysis to work with the idioms you already know and love.

Flow is still in its early days, but we’re already experimenting with it at Facebook. We hope you will enjoy using it on your projects, and look forward to your feedback. You can visit flowtype.org to get started.

Overview

Facebook loves JavaScript; it’s fast, it’s expressive, and it runs everywhere, which makes it a great language for building products. At the same time, the lack of static typing often slows developers down. Bugs are hard to find (e.g., crashes are often far away from the root cause), and code maintenance is a nightmare (e.g., refactoring is risky without complete knowledge of dependencies). Flow improves speed and efficiency so developers can be more productive while using JavaScript.

But layering a static type system onto JavaScript is not trivial. JavaScript’s building blocks are extremely expressive, and simple type systems do not suffice to precisely model their semantics. To seamlessly work with several common JavaScript idioms, Flow employs the kind of data-flow and control-flow analysis that compilers typically perform to extract semantic information from code. It then uses this information for type inference, building on advanced techniques in type theory. Of course, designing a powerful static analysis is not sufficient — JavaScript codebases can be huge, so type checking has to be blazing fast as not to disrupt the developer’s edit-run cycle. Flow performs its analysis modularly, guided by types at module boundaries. This enables an aggressively parallel and incremental type checking architecture, similar to Hack. This allows type checking to appear instantaneous, even on millions of lines of code.

Flow’s type checking is opt-in — you do not need to type check all your code at once. However, underlying the design of Flow is the assumption that most JavaScript code is implicitly statically typed; even though types may not appear anywhere in the code, they are in the developer’s mind as a way to reason about the correctness of the code. Flow infers those types automatically wherever possible, which means that it can find type errors without needing any changes to the code at all. On the other hand, some JavaScript code, especially frameworks, make heavy use of reflection that is often hard to reason about statically. For such inherently dynamic code, type checking would be too imprecise, so Flow provides a simple way to explicitly trust such code and move on. This design is validated by our huge JavaScript codebase at Facebook: Most of our code falls in the implicitly statically typed category, where developers can check their code for type errors without having to explicitly annotate that code with types.

This makes Flow fundamentally different than existing JavaScript type systems (such as TypeScript), which make the weaker assumption that most JavaScript code is dynamically typed, and that it is up to the developer to express which code may be amenable to static typing. In general, such a design leads to reduced coverage: Fewer type errors are caught, and tools are less effective. While this is a reasonable choice for some code, in general such a design does not provide as many benefits as it could without significant additional effort. Still, Flow provides a simple way to switch to this weak mode of type checking where desired, which is typically useful when checking existing code.

To illustrate the difference, check out this small example:

function onlyWorksOnNumbers(x) {
  return x * 10;
}
onlyWorksOnNumbers(‘Hello, world!’);

Flow will catch the error (we’re trying to multiply a number and a string), whereas a more conservative analysis would require explicitly annotating x to give its type. In this toy example that wouldn’t matter, but the amount of annotation required can be prohibitive for large codebases. Flow finds the error in this program without the need for an annotation — though of course the programmer is free to add a type annotation if they choose.

Type system

Many features of Flow’s type system are as expected. The standard primitive types (number, string, boolean) are supported, and implicit conversions between them are banned, barring a few legitimate cases. Structured types, such as those for functions, objects, and arrays, are also supported. Types can be polymorphic.

Perhaps surprisingly, Flow does not consider null and undefined to be part of the above types. Types that include them are maybe types, and uses of those types must be guarded with appropriate checks. Other unions of types (for example string | number) are also supported, and their uses need to be similarly guarded. Flow understands the effects of dynamic checks in narrowing down types.

Let’s look at an example to illustrate the handling of null values. The following program always crashes at runtime, but traditional type systems would consider it well typed:

function length(x) {
  return x.length;
}
var total = length('Hello') + length(null);

Flow will catch this error at compile time, and point out that x can be null (so its length property shouldn’t be accessed). In addition, Flow understands the control flow through the program, so a simple variation of this program would be well-typed:

function length(x) {
  if (x !== null) {
    return x.length;
  } else {
    return 0;
  }
}
var total = length('Hello') + length(null);

In addition, Flow understands the intricacies of JavaScript’s object model: constructors, methods, prototypes, and their dynamic extension and binding. There is also experimental support for types that specify complex operations on objects, such as merging objects, extracting their keys, and so on. We hope that in the future these features will enable specifying precise types for framework APIs.

Type errors are commonly reported as incompatibilities between values and their uses: e.g., a function called with too few arguments, a missing property being accessed on an object, or a string used as a number.

Finally, Flow supports the dynamic type (any), which can be used to get around the type system where desired: e.g., any can be used to annotate a location where static analysis is imprecise and causes spurious type errors (typically, when reflection is used). In addition, switching Flow to the weak mode of type checking mentioned above causes all locations that are not annotated with types to be assumed to be annotated with any.

More details on the type system can be found in the documentation.

Scalability

To scale, Flow type checks each module separately, using its knowledge of dependencies of that module with other modules and typed interfaces for those modules. To generate these typed interfaces, Flow may ask for type annotations at module boundaries.

Flow maintains semantic information about an entire codebase in a persistent server running in the background. When the server is started, Flow performs an initial analysis of the entire codebase. Subsequently, whenever a set of files are changed (due to individual files being saved, or a development branch being switched or rebased), the server updates its knowledge by incrementally checking those files and any other files whose correctness may have been affected by these changes. As such, when the developer asks for type errors, they are already available to the server, so it can respond instantaneously. This server architecture is built on the same technology that powers Hack.

Compatibility

Flow is committed to track JavaScript standards as they evolve. It already supports various ES6 features such as destructuring, classes, extended objects, optional function parameters, and core API extensions (e.g., Map, Set, Promise, and new methods on Object, Array, and Math). Other features (notably modules) are on the way. Flow supports modules organized following the CommonJS / Node.js specification.

In addition, Flow supports JSX and can be used to type check React applications. Let’s say you define a React class:

var Hello = React.createClass ({
 render: function() {
   return <div>Hello {this.props.name}</div>;
 }
});

If you make a typo when naming the React class in a JSX literal, Flow will catch the error:

React.render(<Hallo name='Mark'/>, ...);

Moreover, you can statically type check the attributes of a JSX literal when you use React.PropTypes specifications in your React class:

var Hello = React.createClass ({
  propTypes: {
    name: React.PropTypes.string.isRequired
  }
  ...
});

Flow will now catch errors like missing attributes, as in <Hello/>, or attributes with the wrong type, as in <Hello name={42}/>.

More details on React support in Flow can be found in the documentation.

Open source

Flow is implemented mostly in OCaml. The codebase is under active development and will continue to rapidly evolve in the next few months. In addition to working on Facebook-scale codebases, we hope that the implementation of Flow’s analysis engine will be useful to those building similar development tools for JavaScript and other languages. Please let us know if you wish to collaborate!

To find out more about Flow, download it, and try it out, check out flowtype.org, or click on the buttons below.

SourceWatchFork

Keep Updated

Stay up-to-date via RSS with the latest open source project releases from Facebook, news from our Engineering teams, and upcoming events.

Subscribe
Facebook © 2017