Photo by John-Mark Smith from Pexels

Introducing: Local Type Inference for Flow

Michael Matson Vitousek
Flow
Published in
4 min readSep 27, 2021

--

tl;dr: Over the next year we’ll be changing the core design of Flow’s type inference to be more reliable and predictable. We’ll be requiring more annotations in some cases, but when annotations aren’t necessary the behavior of inferred types will be more intuitive.

In the coming months the Flow team will be building a new core type inference system. This is the engine that Flow uses to determine types in programs and to detect errors.

Why we’re making a change

We’re making this change to better support the experience of developers writing modern, well-typed JavaScript code. When Flow was first developed, a core goal was to provide type information and code intelligence for untyped JavaScript codebases like that which existed at Facebook at the time. As such, it used a sophisticated type inference system that is effective at finding potential runtime errors with minimal requirements for type annotations.

Since Flow’s initial release, Flow and Facebook’s JavaScript ecosystem have co-evolved, as we discuss here, and the importance of such an extraordinarily powerful inference engine has waned. The types-first architecture required annotations at module boundaries in order to allow type-checking to scale, and widespread internal adoption of Flow Strict has also meant that the codebases that Flow is designed to support have become more strongly and explicitly typed.

At the same time, we’ve also seen the disadvantages of relying on the sophistication of our current system. The expressivity of this approach can cause confusing “action-at-a-distance,” where making a (seemingly innocuous) change in one part of a program can lead to surprising errors elsewhere, and in practice requires type annotations that don’t add value to the developer experience.

Finally, the power and complexity of the current design is a constant source of bugs in Flow and it has impeded the Flow team from making changes that should be straightforward and from developing new features to improve developer experience. For example, the unbounded nature of Flow’s current inference made the work of improving object spreads a major project, and other projects such as new, more powerful utility types are blocked by this as well.

What we’re doing

Our aim in the next year is to replace Flow’s current inference engine with a system that behaves more predictably and can be reasoned about more locally. Our solution is to use local type inference as the core system for checking Flow code, bringing the behavior of Flow’s type inference closer to that of related languages like TypeScript and Hack while maintaining our prioritization of type soundness and scalability to large codebases. Local type inference determines the types of expressions by using information available from adjacent parts of the program, rather than from distant usage. The result will be a system that is more predictable and obeys the principle of least surprise, requiring annotations that serve as useful documentation anyways so we can eliminate annotations that don’t add value. This system will also enable us to fix bugs and develop new features more quickly, without reckoning with the complexity of our current approach. If you’re curious to learn more about the foundations of this new approach, you can read some of the research that we’re building on here: Pierce and Turner, 2000 and Dunfield and Krishnaswami, 2021.

This work will require some new categories of annotations to be added in places where the context of checked code isn’t available — most notably, we’ll require annotations on the parameters of all top-level function definitions (but typically not on callbacks defined in-line). However, in cases where annotations aren’t required (such as most local variables, function return types, and object properties), we’ll be able to provide greater confidence that Flow’s inferred type corresponds to the type that a programmer would naturally expect.

While the core of this project is implementing local type inference, we’ll be making a number of other improvements and changes to Flow along the way, including better treatment of unsealed objects, refinements, and more. We’ll be making dedicated posts about these projects over the coming months; the first such update on this-typing has already been posted.

What to expect

As we begin implementing the new system we’ll also be enabling some new annotation requirements ahead of time, to smooth the transition from the previous system to the new one. Such requirements will be initially configurable by options flags in your .flowconfigs, but eventually as we deprecate the old system they’ll become mandatory. We also expect this project to improve Flow’s soundness by removing the old inference engine, which was a major source of bugs due to its complexity. We’ll be shipping improvements as we go, so you may encounter new categories of errors when you upgrade between versions as this project progresses. We will aim to develop and release codemod tools (using the flow codemod command) to ease this transition when possible.

We’ll be sharing more details about specific aspects of this project over the course of the coming months, and we’ll make sure to announce significant user-visible changes, requirements, and codemods at the time that they roll out, so watch this space!

--

--