facebook / flow

Adds static typing to JavaScript to improve developer productivity and code quality.
https://flow.org/
MIT License
22.09k stars 1.86k forks source link

Flow does not refines type of class union as it does with types #4353

Open Gozala opened 7 years ago

Gozala commented 7 years ago

Here is an example of failure that I'd expect to work:

/* @flow */

class Ok <a> {
  isOk: true
  value:a
}

class Error <x> {
  isOk: false
  error:x
}

type Result <x, a> = Ok<a> | Error<x>

export const chain = <x, a, b>(
  f: (value: a) => Result<x, b>,
  result: Result<x, a>
): Result<x, b> => {
  if (result.isOk) {
    return f(result.value)
  } else {
    return result
  }
}

It fails to refine Result to Ok and Error by isOk sentinel field as following erros seem to indicate:

    20:     return f(result.value)
                            ^ property `value`. Property not found in
    20:     return f(result.value)
                     ^ Error
    22:     return result
                   ^ Ok. This type is incompatible with
    18: ): Result<x, b> => {
           ^ union: type application of polymorphic type: class type: Ok | type application of polymorphic type: class type: Error
    22:     return result
                   ^ Ok. This type is incompatible with
    18: ): Result<x, b> => {
           ^ union: type application of polymorphic type: class type: Ok | type application of polymorphic type: class type: Error

25:1

What's interesting that if I change classes to type definitions everything works as expected, but same error is present if interfaces are used

For whatever reason type refinements via switch instead of if seems to work in all cases:

/* @flow */

interface Ok <a> {
  isOk: true,
  value:a
}

interface Error <x> {
  isOk: false,
  error:x
}

type Result <x, a> = Ok<a> | Error<x>

export const chain = <x, a, b>(
  f: (value: a) => Result<x, b>,
  result: Result<x, a>
): Result<x, b> => {
  switch(result.isOk) {
    case true:
        return f(result.value)
    default:
        return result
  }
}
0rvar commented 6 years ago

It works if you rewrite your if statement like this:

if (result.isOk === true) {

It only works with ===, not ==. I am guessing this has to do with flow not wanting to pick a branch unless you make an exact match. In this case, I think Flow could allow matching loosely without losing soundness.