A Work in Progress

developer's blog by Wei
lots of work in progress notes
TimelineCredits

Notes on Flow 0.98

April 30, 2019

It's a Monday after Flow updated 😱 0.98 seems to contain many fixes. Happy to see Flow making more sense version by version. I'm also glad to be learning bits and bites about Flow on the go so I thought I'd share about that :)

Release notes are here, and following are my notes. Flow please be happy 🤞


Turn the deprecated-utility lint on by default

Possible errors:

  • Deprecated utility. Using $Subtype types is not recommended! (deprecated-utility)
  • Deprecated utility. Using $Supertype types is not recommended! (deprecated-utility)

deprecated-utility is a lint rule that was added in 0.89. So far $Subtype and $Supertype are deprecated. However, Flow only starts to complain from this version because deprecated-utility is now turned on by default.

You can turn it off by adding back the lint option in .flowconfig:

# .flowconfig
[lints]
deprecated-utility=off

But it's actually not that hard to fix.

So what were $Subtype and $Supertype anyway? $Subtype normally means what is expected here is more refined than what's inside $Subtype. Anyway, that's just my understanding. You can still find the docs here.

Perhaps you previously used $Subtype to refine component props, as suggested at the time of their deprecation, you can bring back the refinement using bounded generics.

Some older version of library definitions from Flow-Typed may raise errors. (For example, we were using Redux v3 before Flow 0.89 that caused unhappiness. However, there is already a libdef update in Flow-Typed.)

I felt a bit hairy about them anyway. Now that they're gone, time to feel happy and get rid of them :)


Changes and fixes around read-only object and array

Possible errors:

  • Cannot assign 42 to x[0] because read-only arrays cannot be written to.
  • Cannot assign array to problem because read-only array type [1] is incompatible with array type [2].
  • Cannot assign 3 to x.a because property a is not writable.

It's great to see that Flow is constantly improving and how. (Plus with a slight hint of humor to read about the fixes on the previous unsoundedness).

So I've found three changes and fixes around read-only things.

  • Read-only arrays cannot be written to with any-typed indexes, commit

This now disallows you to mutate read only arrays. And you may ask, why should I be allowed to mutate them anyway? Turns out previously if the index is of type any you would have been able to. 🤷🏻‍♀️

Then there are two more:

  • Mixed refines to read-only object under typeof = object, commit
  • Mixed refined by array produces $ReadOnlyArray, commit

I do not have a better explanation than the commit messages there. So let me quote:

[Bugbash] Mixed refines to read-only object under typeof = object Summary: It is not safe to refine mixed to {[string] : mixed} because this allows writes of any kind into the refined object, and we don't know what type of values the object holds. The only safe operation is to read mixed out of the object, so the properties must be read only.

function bad(x: mixed) {
  if (typeof x === 'object' && x !== null) {
    x.a = 3
  }
}

let obj: { a: string } = { a: 'oops' }
bad(obj) // yikes

And the one with arrays:

[Bugbash] mixed refined by array produces $ReadOnlyArray<mixed>

Summary: It is not safe to refine mixed to Array<mixed> as this allows for writing arbitrary values to the result of the refinement, and since we only know that the value is an array, we cannot know that it is safe to write a given value to the array. We do know, however, that anything we get out of the array will be mixed, so it is safe to type it as a $ReadOnlyArray<mixed>.

function bad(array: mixed) {
  if (Array.isArray(array)) {
    const problem: Array<mixed> = array
    problem[1] = 0
  }
}
bad((['3']: Array<string>))

Infer void before type checking starts for functions without a return statement

Possible errors:

Note: I think the possible errors caused by this fix can be quite variable. Here are two I know of:

  • Cannot cast x.noReturn() to number because undefined [1] is incompatible with number [2].
  • Cannot shadow proto property yourFunction [1] because object type [2] is incompatible with undefined [3] in the return value.

Commit on Flow codebase: Infer void early for functions with no explicit return

If a function does not have a return statement, Flow now infers that to void. Interestingly, the most common errors that arise after this were regarding shadowing proto property. Check out the following Try Flow:

import * as React from 'react'

class MyComponent extends React.Component<*> {
  handleClick: () => {} // <- {} means an object here

  handleClick() {
    console.log('clicked!')
  }

  render() {
    return <div onClick={this.handleClick} />
  }
}

Fixes are simply to annotate them properly () => void


Fix a bug which prevented Flow from asking for required type annotations.

This line sounds scary 😱 But I haven't encountered any unhappiness that seems related yet. Will come back here if I do.

References