Skip to main content

Flow all-in

tip

Flow infers the widest type that makes your code work. If you don't want inference to widen your type, the solution is always to annotate.

so the way it works is that the Flow team has a rotating oncall. it's relatively calm as oncalls go (we aren't getting woken up in the middle of the night), but whoever is oncall is responsible for doing support (we have an internal group where people can ask questions), and also responsible for taking the lead if something goes wrong with Flow or the various related integrations we have. near the beginning of the year we also made it so that the oncall is responsible for addressing libdef and documentation PRs, since there is usually no clear owner for those, and pretty much anyone should be able to review them

(source Discord)

Exit codes

Flow exited with some numeric exit code and you are not sure what does it mean? You can find these exit codes here: https://github.com/facebook/flow/blob/eaffc8cdf09fb268c6f9ab9914685cbbd562e534/src/common/exit_status/flowExitStatus.ml

Sound vs. complete

A sound type system (or analysis) is one that rejects all erroneous programs, and a complete system accepts all correct programs.

A sound type system is one that is not lenient, in that it rejects all invalid programs plus some number of valid programs. A complete type system accepts all valid programs, and some invalid ones to boot. Which to pick?

A sound type system is one that ensures your program does not get into invalid states. For example, if an expressions static type is string, at runtime, you are guaranteed only to get a string when you evaluate it.

Please note: not everything can be expressed/modeled in your type system so you have to take into account also dynamic errors (division by zero or integer overflow) when writing your program.

Programmers dislike having the computer reject a program that would have run fine, simply because the computer couldnt make sure it would run fine without actually running it. In short, restrictive type systems drive programmers to more flexible environments.

Type-safe filter function

declare function filter<K, V, P: $Pred<1>, T: Array<V> | $ReadOnlyArray<V>>(
fn: P,
xs: T,
): Array<$Refine<V, P, 1>>;

const input: $ReadOnlyArray<number | string> = [1, 'a', 2, 3, 'b'];

function isNumber(x: mixed): boolean %checks {
return typeof x === 'number';
}

// const result: $ReadOnlyArray<number> = input.filter(isNumber);
// ^ Cannot assign `input.filter(...)` to `result` because string [1] is incompatible with number [2] in array element.
const result: $ReadOnlyArray<number> = input.filter(isNumber);

const result_fixed: $ReadOnlyArray<number> = filter(isNumber, input);

flow.org/try

Force type casting

You may find yourself in a situation where you want to force cast some type despite it's originally defined differently. Example:

type StringOrNumber = string | number;
const foo: StringOrNumber = 'hello';

const bar1: string = foo; // cannot assign `foo` to `bar` because number is incompatible with string
const bar2: string = (foo: string); // cannot cast `foo` to string because number is incompatible with string

Both errors are correct and valid. The solution is to cast the type via any like this:

const bar: string = (foo: any);

So you cast foo to an any because any accepts any type of value as an input. Then because the any type also allows you to read all possible types from it, you can assign the any value to bar because an any is also a string.

This obviously means that you are going against the type system and you might be doing something wrong. Did you want to use conditional refinement instead?

type StringOrNumber = string | number;
const foo: StringOrNumber = 'hello';

if (typeof foo === 'string') {
const bar: string = foo; // no errors!
}

It's important to note that both suppression comments and cast-through any are unsafe. They override Flow completely, and will happily perform completely nonsensical "casts". To avoid this, use refinements that Flow understands whenever you can, and avoid use of the other, unsafe "casting" techniques.

Source: https://stackoverflow.com/questions/41328728/how-can-flow-be-forced-to-cast-a-value-to-another-type/45068255

$Compose, $ComposeReverse

Compose pattern is very common among JS community. Here are some examples: Redux, Recompose, Lodash and Ramda

You can enable this common compose pattern like so:

declare var compose: $Compose;
declare var composeReverse: $ComposeReverse;

More info: https://github.com/facebook/flow/commit/ab9bf44c725efd2ed6d7e1e957c5566b6eb6f688

$CharSet

Handy utility for things like regexp flags or for any other case where the flags (chars) cannot repeat.

type RegExpFlags = $CharSet<'gimsuy'>;

const a: RegExpFlags = 'miug'; // OK
const b: RegExpFlags = 'iii'; // not OK! ("i" is duplicated)
const c: RegExpFlags = 'abc'; // not OK! ("a", "b", "c" are not valid members)

Contributing to native libdevs

First, you have to build Flow locally (check official README for updated instructions: https://github.com/facebook/flow):

brew install opam                       # http://opam.ocaml.org/
opam init
opam switch create . --deps-only -y # install OCaml and Flow's dependencies
eval $(opam env) # probably not necessary, read `opam init` step
make

Now, you can start making changes to libdevs:

make
bash ./runtests.sh -t node_tests bin/flow
bash ./runtests.sh -t node_tests -r bin/flow # to write snapshots

Please note, building like this should be faster for local development:

make build-flow-debug

Now, you can use this new binary from source in your application to test new features. Just use newly built bin/flow instead of Flow binary from NPM like so:

bin/flow status <ROOT>

What to do when something goes wrong during the build?

make clean
make build-flow-debug

Performance of Large unions (simple enums)

I've been working on this recently so I can give you an overview. Essentially the reasons large unions are slow is that the amount of work Flow needs to do can grow exponentially with the size of the union. To determine if a union is a valid subtype of another type, we need to consider whether every single element of the union is a valid subtype, while to determine if it's a supertype we need to check if at least one of its cases is a supertype. If the union isn't actually a supertype we end up needing to check every case. Where this gets really bad is when we compare two union types, and this can easily result in an exponential case where we need to perform a lot of work for every single combination of union cases.

Luckily we have a lot of optimizations in place for dealing with unions, especially those that can be simplified to enums (unions of strings or number literals). 450 variants is really not that large in the scheme of things; we deal with unions with upwards of 100,000 elements routinely. The only caution I would suggest is to make sure that you don't add non-literal types to your enum unions, because that will cause our optimizations to fail and leave you with the worst case peformance.

Thanks @sainati on Discord.

Enums

You have to explicitly enable support for enums in .flowconfig to be able to use them:

[options]
enums=true

Additionally, there are some requirements for minimal versions of Babel, Eslint, Prettier + related configuration. You can check how were the enums enabled in adeira/univers or consult the official docs: https://flow.org/en/docs/enums/

Now, you can use this new feature:

enum E1 {
A,
B,
}

enum E2 of number {
A = 1,
B = 2,
}

Basic methods support:

enum E {
A,
B,
}

const a: ?E = E.cast('A');
const b: Iterable<E> = E.members();
const c: boolean = E.isValid('A');

Rather negative discussion about enums in Flow: https://github.com/facebook/flow/issues/7837

Declaration with mixins

Mixins (not React mixins) are a little know feature in Flow which is no longer being publicly advertised (even though it used to be in docs).

You can declare a class which mixes in 1 or more other classes with the mixins keyword. Mixing class B into class A copies B's fields and methods into A. Note, however, that any fields or methods that B inherits are not copied over. Mixins are for code reuse, not for multiple inheritance.

Example:

// You can mixin more than one class
declare class MyClass extends Child mixins MixinA, MixinB {}
declare class MixinA {
a: number;
b: number;
}
// Mixing in MixinB will NOT mix in MixinBase
declare class MixinB extends MixinBase {}
declare class MixinBase {
c: number;
}
declare class Child extends Base {
a: string;
c: string;
}
declare class Base {
b: string;
}

var c = new MyClass();
(c.a: number); // Both Child and MixinA provide `a`, so MixinA wins
(c.b: number); // The same principle holds for `b`, which Child inherits
(c.c: string); // mixins does not copy inherited properties, so `c` comes from Child

You can combine mixins with implements as well:

declare interface Test {
test(): void;
}

declare class MyClass extends Child mixins MixinA, MixinB implements Test {
test(): void;
}

Callable objects

This type allows you to use the function as a function and as an object at the same time.

type MemoizedFactorial = {
+cache: {
[number]: number,
},
(number): number,
};

const factorial: MemoizedFactorial = (n) => {
if (!factorial.cache) {
factorial.cache = {};
}
if (factorial.cache[n] !== undefined) {
return factorial.cache[n];
}
factorial.cache[n] = n === 0 ? 1 : n * factorial(n - 1);
return factorial.cache[n];
};

See: https://flow.org/en/docs/types/functions/#toc-callable-objects

Alternatively, you can use so called internal slot property:

type MemoizedFactorial = {
+cache: {
[number]: number,
},
[[call]](number): number,
};

Support of internal slot properties in tools like Babel or Eslint is not great though.

Interesting Flow commands

yarn flow graph cycle src/incubator/graphql/src/public/FAQ/types/outputs/FAQArticle.js

# Outputs dependency graphs of flow repositories. Subcommands:
# cycle: Produces a graph of the dependency cycle containing the input file
# dep-graph: Produces the dependency graph of a repository
yarn flow dump-types src/packages/relay/src/QueryRenderer.js
yarn flow check --debug
yarn flow check --profile
yarn flow autofix

source

Private object properties

class Thing {
prop1: string;
#prop2: string = 'I am private!';
}

new Thing().prop1;
new Thing().prop2; // <- ERROR
7: (new Thing()).prop2;
^ Cannot get `new Thing().prop2` because property `prop2` is missing in `Thing` [1].
References:
7: (new Thing()).prop2;
^ [1]

Predicate functions with %checks

%checks is an experimental predicate type. Check this code (no Flow errors):

function isGreaterThan5(x: string | number): boolean {
if (typeof x === 'string') {
return parseInt(x) > 5;
}
return x > 5;
}

But you can slightly refactor it and you'll get unexpected errors:

function isString(y): boolean {
return typeof y === 'string';
}

function isGreaterThan5(x: string | number): boolean {
if (isString(x)) {
return parseInt(x) > 5;
}
return x > 5;
}
9:   return x > 5;
^ Cannot compare string [1] to number [2].
References:
5: function isGreaterThan5(x: string | number) {
^ [1]
9: return x > 5;
^ [2]

This is because the refinement information of y as string instead of string | number is lost when returning from the isString function. You have to fix it like this:

function isString(y): boolean %checks {
return typeof y === 'string';
}

You can also declare the predicate like this:

declare function isSchema(schema: mixed): boolean %checks(schema instanceof GraphQLSchema);

Object indexer properties

const o: { [string]: number, ... } = {};
o['foo'] = 0;
o['bar'] = 1;
o[42] = 2; // Error!
const foo: number = o['foo'];

When an object type has an indexer property, property accesses are assumed to have the annotated type, even if the object does not have a value in that slot at runtime. It is the programmers responsibility to ensure the access is safe, as with arrays.

This is similar to the issue with possibly undefined array elements:

const obj: { [number]: string, ... } = {};
obj[42].length; // No type error, but will throw at runtime!

Indexer properties can be also mixed with normal properties:

const obj: {
size: number,
[id: number]: string,
...
} = {
size: 0,
};

function add(id: number, name: string) {
obj[id] = name;
obj.size++;
}

This information is not valid from version 0.126.0+ since indexer properties are now implemented even for exact objects, see: https://github.com/facebook/flow/commit/97e3a103227a381de0fd0be197bf25f6d6b6081a.

Please note: indexer property doesn't make any sense on exact objects:

type S = {| [key: string]: string |};
const s: S = { key: 'value' }; // cannot assign object literal to `s` because property `key` is missing in `S`

This is not a bug - it simply doesn't make any sense with exact objects. It could be eventually repurposed though, see: https://github.com/facebook/flow/issues/7862 (related issue: https://github.com/facebook/flow/issues/3162)

However, it still somehow works when you are not trying to re-assign the whole value, see:

type S = {| [key: string]: string |};

const s: S = { key: 'value' }; // Error: Cannot assign object literal to `s` because property `key` is missing in `S` but exists in object literal.

function test(x: S): string {
x.y = 'string'; // OK (would fail with a wrong type assignment)
return x.test; // OK
}

Difference between & and ...

It's easy to misunderstand the difference between intersection types (A & B) and spreading types ({ ...A, b:boolean }) in Flow.

type A = { a: number };
type B = { b: boolean };
type C = { c: string };

// Intersection types are the opposite of union types!
const a: A & B & C = {
a: 1,
b: true,
c: 'ok',
};

const b: $Exact<A> | $Exact<B> | $Exact<C> = {
a: 1,
// b: true,
// c: 'ok'
};

const c: {
...{ a: number, b: string },
a: string,
} = {
a: '1', // only string, no number
b: '2',
};

const d: {|
...{| a: number, b: string |},
a: string,
|} = {
a: '1', // works the same with exact types
b: '2',
};

// Impossible type:
// const e: {| a: number |} & {| a: string |} = {
// a: ???,
// }

No errors!

@flow pragma consequences

It is incorrect to assume that adding @flow pragma to your files just enables typesystem without any side-effects. There can be some unexpected changes in fact:

  1. /*:: ... */ and /*: ... */ comments have special meaning (https://flow.org/en/docs/types/comments/)
  2. a<b>(c) becomes a type argument, rather than ((a < b) > c)

https://github.com/facebook/flow/issues/7928#issuecomment-511428223

Conditions in Flow using $Call

(jbrown215) \$Call is a shitty syntax for conditional types, but it exists. You can use overloading to simulate the cases, so:

type Fun = ((number) => string) & ((string) => number);
type SwapNumberAndString<T: number | string> = $Call<Fun, T>;

Is approximately in TS:

type SwapNumberAndString<T extends number | string> = T extends number ? string : number;