Photo by icon0.com from Pexels

Making Flow error suppressions more specific

Daniel Sainati
Mar 16, 2020 · 6 min read

We’re improving Flow error suppressions so that they don’t accidentally hide errors.

Over the coming year, Flow is planning to make many changes and improvements to its type system. One of the consequences of this, however, is that you may need to add more suppressions to your code as Flow gets better at finding existing issues.

This can be a problem because Flow’s suppressions affect more than just the original error you intended to suppress. This reduces Flow’s ability to catch errors. This is a result of two properties: First, that Flow suppressions can be located at any part of a type error, and second, that Flow suppressions can suppress any kind of type error. To fix these shortcomings, we are going to require that suppressions be placed at the location in your code where the error actually occurs, and that they include an error code specifying the kind of error you want to suppress.

To understand what all this means, let’s dig in to the nature of Flow’s type errors.

What is a type error?

When Flow encounters an issue while checking your code, like an incompatibility between two types or an invalid property access, it generates an error that reports to the user precisely what is wrong with their code. These errors can contain a wide variety of information depending on the particular circumstances that caused them, but the two that we care most about are the kind of the error and the locations of the error.

The kind of the error encodes the specific nature of the bad behavior that Flow detected in your code, whether it be something simple like passing too many arguments to a function, or something complex like spreading a union type. This information tells Flow exactly what kind of information to display to the user to best explain exactly where you went wrong.

The locations of the error contain information about where the error is located in your code. There are two kinds of locations an error can contain, a primary location (of which there can be only one), and secondary locations, of which there can be many or none at all. The primary location roughly corresponds to the location in your code where the error actually occurred, while the secondary locations carry information about the definition sites of the types and values involved in the error. For example:

function foo(x : number) : void {}
foo("hello");
/* error */
foo("hello");
^ Cannot call `foo` with `"hello"` bound to `x` because string [1] is incompatible with number [2].
References:
2: foo("hello");
^ [1]
1: function foo(x : number) : void {}
^ [2]

In this example, the error occurs when is called with , since is not a . This means that the primary location of the error, where it occurs, is on the second line. The secondary location of this error is on the first line, because is a part of this error, and the first line is where it is defined. If we were to call with a variable, however, like this:

function foo(x : number) : void {}
let y : string = "hello";
foo(y);
/* error */
foo(y);
^ Cannot call `foo` with `y` bound to `x` because string [1] is incompatible with number [2].
References:
2: let y : string = "hello";
^ [1]
1: function foo(x : number) : void {}
^ [2]

The error here has its primary location on the third line, but has two secondary locations at the definition sites of and .

So what’s so wrong with suppressions?

Well, because suppressions can be applied on both the primary and secondary locations, putting a comment above the definition of would suppress the errors in both of the above examples. Since appears as a secondary location in every error that uses it, that would mean that any use of that results in an error would be suppressed by this one suppression, anywhere that it might occur. You would be able to call with any number of arguments of any type and Flow would not surface any errors or warnings to you about any of this. This degrades the confidence of users in Flow’s type checking and allows bugs it would otherwise have caught to make it into production.

In addition to this, Flow’s suppressions affect all kinds of errors, as long as they contain a location that the suppression covers. This means that multiple errors of different kinds can be suppressed by a single suppression. To see the danger in this, consider the following example:

// library file lib.jsfunction foo(x : number) : void {}// impl file
const {foo} = require("lib.js");
let y : string = "hello";
// $FlowFixMe
foo(y);

This suppression comment prevents Flow from raising the type incompatibility issue here, but let’s imagine you’re okay with this because it works at runtime. Now imagine that an update to the library file added a second argument to , and uses it in such a way that not providing this argument would cause a runtime error. Now there is a second error on the call to : the call does not supply the right number of arguments, but Flow will not report this to you, because there is still a suppression on the previous line. Our change has caused Flow to raise a second type of error, but the suppression captures both of them, and the bug is able to escape Flow’s notice.

What are we going to do about this?

To address this we are going to make the following changes:

  • Enforce primary locations. We will be changing the suppression behavior such that suppressions only apply to errors when placed at their primary location. For example, instead of allowing you to suppress an invalid call at the definition of the function, we will now require that the suppression be located at the call-site itself. This way, Flow will still be able to alert you about other possibly invalid calls to the function.
  • Add error codes. We will also be adding error codes to our suppressions so they only suppress errors of the specified kind. This will prevent suppressions from unexpectedly suppressing a kind of error you were not expecting to encounter.
  • Standardize suppression syntax. Previously the acceptable syntax for an error suppression could be configured manually in the for a given project, allowing inconsistent suppression syntax across projects. As part of the above changes we will also be standardizing the suppression syntax; only supporting the or formats.

Note that the enforcement of primary locations will be released in version 0.121.0, while error codes and standardized suppressions will be added in a later release.

Rollout

We recognize that this change may cause a significant number of errors in your codebases to become invalid, especially if you were in the habit of placing suppressions in library code. To ease this burden, we have a couple of suggestions:

  • To relocate your newly invalid suppressions to their primary locations, we recommend a combination of the and utilities provided in the Flow tool. Running will remove any comments that no longer suppress an error because they are not on a primary location, and will place new suppressions on unsuppressed locations. The script can be accessed by cloning the Flow repository on GitHub.
  • Suppressions without error codes will continue to suppress any and all errors on their location, but once we roll out the error codes feature, you can add the proper codes to your codebase via a similar process as above. Removing all your old comments and re-adding them with will include the error code in the newly added comment.

We look forward to bringing you a safer codebase!

Flow

The official publication for the Flow static type checker…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store