Unsafe Typecasts with Flow

John Wineman
Mar 1, 2018 · 2 min read

I was reading over Andrew Clark’s Context API PR for React and I saw the following code:

((frame: any): FrameDev).debugElementStack = [];

The syntax was unfamiliar to me when I first read it, luckily there was a comment explaining how it works but I thought it warranted a longer explanation.

Because Flow sits on top of JavaScript the types you’re able to construct with it are more flexible than the types you construct in languages that have types built into the spec.

Flow’s docs briefly mention unsafe typecasts:

By casting the value to any, you can then cast to whatever you want.

This is unsafe and not recommended. But it’s sometimes useful when you are doing something with a value which is very difficult or impossible to type and want to make sure that the result has the desired type.

The reason this technique isn’t documented in more detail is because this is a hack. However this hack is unique to type systems like Flow and allows you to balance the freedom of loose types with the safety of static types so it’s worth understanding in more detail.

Consider the following code:

// @flowconst toString = (num: number) => {
  return num.toString();
};const add = (a: number, b: number) => {
  return a + b;
};const a = toString(1);
const b = toString(2);
add(a, b);

This will give you the following error in Flow:

string This type is incompatible with the expected param type of number

In a statically typed language we would have to check the variable’s type every time and then convert it. This is an example of where an unsafe typecast allows us to trade some of the safety of a static type for the freedom of a loose one:

const a = (toString(1): any);
const b = (toString(2): any);
add(a, b);

This resets the type of a and b to any which, in this case, is able to flow into number. Unfortunately this reset also causes the following code to error:

const a = (toString(1): any);
const b = ("string": any);
add(a, b);

Because "string" + 1 is valid JavaScript which evaluates to"string1" because of type coercion, which is the main reason Flow exists, which is why this is a hack. In order for Flow to error now you would have to do something type specific to that variable:

const add = (a: number, b: number) => {
  a = a.toFixed(a); // format a to two decimals 
  return a + b;
};const a = (toString(1): any);
const b = ("string": any);
add(a, b);

In this case Flow will error on a = a.toFixed(a) with:

string This type is incompatible with the expected param type of number

You should avoid this technique when possible but by understanding how it works you can apply it selectively and save a lot of time.

John Wineman

Written by

Follow me on Twitter: @johnwineman