Flow: Could not decide which case to select

Created on 25 Nov 2016  路  15Comments  路  Source: facebook/flow

This

export type InitState = {
  type: "init",
  lang: string,
}

export type SecondState = {
  type: "second",
  lang: string,
}

export type State = InitState | SecondState

const transform = (input:State, lang:string) : State => {
  return {
    ...input,
    lang
  }
}

let state: State = {
  type: "init",
  lang: "en"
}

let state2 = transform(state, "nl")

Results in the next error.

 47:   return {
              ^ object literal. Could not decide which case to select
 46: const transform = (input:State, lang:string) : State => {
                                                    ^^^^^ union type
  Case 1 may work:
   44: export type State = InitState | SecondState
                           ^^^^^^^^^ InitState
  But if it doesn't, case 2 looks promising too:
   44: export type State = InitState | SecondState
                                       ^^^^^^^^^^^ SecondState
  Please provide additional annotation(s) to determine whether case 1 works (or consider merging it with case 2):
  .type

Al the cases have lang, so I think it should return the same type as it gets, like the next situation, which works fine:

const transform = (input:State, lang:string) : State => {
  return input
}

Casting the output to State or typeof input does not resolve the issue.

spread enhancement

Most helpful comment

I was thinking some more: Why does flow needs to know which one it is? Can't the function just return a union type in the case where it's undecidable? It only needs to check if the newly created object can match InitState or SecondState (and watching the error it looks like it already knows).

All 15 comments

Here is a problem: when you something to have type State it should either have type State already, or it should be one of InitState or SecondState. Result of object is none of those, it has type { type: 'init' | 'second', lang: string } or something like this.

But when SecondState has additional parameters you have to make them optional because of this transform. This situation is just a illustration what happens in redux and transform is a reducer. It would be great to have a reducer with differente states, and be able te change the state without actually knowing which state you are because al cases are compatible.

be able te change the state without actually knowing which state you are because al cases are compatible

Well, you can actually change (mutate) it, but object spread merges branches together

Off course, but redux (and functional programming in general) is build around immutability to knows when changes has been made. I understand it's hard for flow to choose which type it has to output, but it is not impossible, it has the information to decide, right? Unfortunately for me it is now impossible to add flow type to this situation. Thanks for your feedback.

You could refine input

function transform(input: State, lang: string): State {
  if (state.type === 'init')
    return {
      type: 'init',
      ...input,
      lang
    }
  return {
    type: 'second',
    ...input,
    lang
  }
}

Yes, but I have to much types, it would only make the code less easy to manage which is not really beneficial. I think I am going to extract that code into another reducer. Still this enhancement would be very welcome.

I was thinking some more: Why does flow needs to know which one it is? Can't the function just return a union type in the case where it's undecidable? It only needs to check if the newly created object can match InitState or SecondState (and watching the error it looks like it already knows).

Apologies for bumping such an old thread, but I'm curious if anyone has found a workaround for this. Manual refinement works fine if the disjoint union has 2 or 3 cases, but it not reasonable for larger sets. I'm not clear on why creating a new object from an old one via spread and changing one property (shared amongst all cases) would not pass Flow checks.

Example here

Any solution to this yet?

export default connect(
  (state: ReduxState) => ({
    modalPayload: state.ui.modalPayload,   // Error:(15, 16) Flow: function call. Could not decide which case to select intersection type 
  }),
)(InfoContent);

ReduxState is a state declared in flow typed directory as global. I don't know what the error means. After I upgraded "flow-bin": "^0.57.3",

Some built-in "private" type will be very useful.
Something like $PreferFirstCase<T> where T must be a union type.

[JUST PROPOSAL, NOT IMPLEMENTED]

+1 for $PreferFirstCase<T>!

The issue is actually about broken spread. There is a PR that will fix the issue: https://github.com/facebook/flow/pull/7298

Guess managed to rewrite via Object.assign

This code no longer errors on master

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bennoleslie picture bennoleslie  路  3Comments

ctrlplusb picture ctrlplusb  路  3Comments

marcelbeumer picture marcelbeumer  路  3Comments

jamiebuilds picture jamiebuilds  路  3Comments

doberkofler picture doberkofler  路  3Comments