Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flow chokes (takes over CPU and never finishes) on the following code example #4370

Closed
Gozala opened this issue Jul 12, 2017 · 7 comments
Closed

Comments

@Gozala
Copy link
Contributor

Gozala commented Jul 12, 2017

Here is somewhat minimal example. I'm sure it could be reduced further, but given that flow takes over my machine when it attempts to type check this it's really difficult and it took me half a day yesterday to find to reduce it this from a much larger code

/* @flow */

export type Dict<a> = {
  [key: string]: ?a
}

export type Accumulator<chunk, result> = (input: chunk, state: result) => result

export type Entry<a> = [string, a]

export const merge = <a, b, result>(
  accumulateLeft: Accumulator<Entry<a>, result>,
  accumulateBoth: Accumulator<Entry<[a, b]>, result>,
  accumulateRight: Accumulator<Entry<b>, result>,
  left: Dict<a>,
  right: Dict<b>,
  init: result
): result => {
  let state = init
  for (let key in left) {
    const leftValue = left[key]
    if (leftValue != null) {
      const rightValue = right[key]
      if (rightValue != null) {
        state = accumulateBoth([key, [leftValue, rightValue]], state)
      } else {
        state = accumulateLeft([key, leftValue], state)
      }
    }
  }

  for (let key in right) {
    const rightValue = right[key]
    const leftValue = left[key]
    if (rightValue != null && leftValue == null) {
      state = accumulateRight([key, rightValue], state)
    }
  }

  return state
}

const log = merge(
  ([key, left], log) => [...log, `- ${key} : ${left}`],
  ([key, [left, right]], log) => [...log, `= ${key} : ${left} -> ${right}`],
  ([key, right], log) => [...log, `+ ${key} : ${right}`],
  { a: 1, b: 2 },
  { b: 18, c: 9 },
  []
)

Here is output I get from flow:

> flow focus-check src/Issue.js
[2017-07-12 10:14:43] executable=/Users/gozala/.config/yarn/global/node_modules/flow-bin/flow-osx-v0.49.1/flow
[2017-07-12 10:14:43] version=0.49.1
[2017-07-12 10:14:43] Parsing
[2017-07-12 10:14:46] Building package heap
[2017-07-12 10:14:46] Loading libraries
[2017-07-12 10:14:48] Resolving dependencies
[2017-07-12 10:14:49] Running local inference
^C
> killall flow
>  date
Wed Jul 12 10:24:41 PDT 2017

Also worth noting that it chokes on usage of a merge if it's not used it seems to do fine.

@Gozala
Copy link
Contributor Author

Gozala commented Jul 12, 2017

I have reduced example little bit further:

/* @flow */

export type Dict<a> = {
  [key: string]: a
}

export const merge = <a, b, result>(
  accumulateLeft: ([string, a], result) => result,
  accumulateBoth: ([string, [a, b]], result) => result,
  accumulateRight: ([string, b], result) => result,
  left: Dict<a>,
  right: Dict<b>,
  init: result
): result => {
  let state = init
  for (let key in left) {
    if (key in right) {
      state = accumulateBoth([key, [left[key], right[key]]], state)
    } else {
      state = accumulateLeft([key, left[key]], state)
    }
  }

  for (let key in right) {
    if (!(key in left)) {
      state = accumulateRight([key, right[key]], state)
    }
  }

  return state
}

const log = merge(
  ([key, left], log) => [...log, `- ${key} : ${left}`],
  ([key, [left, right]], log) => [...log, `= ${key} : ${left} -> ${right}`],
  ([key, right], log) => [...log, `+ ${key} : ${right}`],
  { a: 1, b: 2 },
  { b: 18, c: 9 },
  []
)

I also noticed that merge on line 33 is inferred as follows:

const merge: <number, number, any[]> (
  accumulateLeft: ([string, a]: [any, any], result: any) => any[],
  accumulateBoth: ([string, [a, b]]: [any, [any, any]], result: any) => any[],
  accumulateRight: ([string, b]: [any, any], result: any) => any[],
  left: Dict<number>,
  right: Dict<number>,
  init: any[]
) => any[]

Which seems somewhat odd, but not sure if it indicative of what the issue is.

@Gozala
Copy link
Contributor Author

Gozala commented Jul 12, 2017

So, if I do annotate inline functions with return type flow seems to get over the hump that it otherwise chokes over:

/* @flow */

export type Dict<a> = {
  [key: string]: a
}

export const merge = <a, b, result>(
  accumulateLeft: ([string, a], result) => result,
  accumulateBoth: ([string, [a, b]], result) => result,
  accumulateRight: ([string, b], result) => result,
  left: Dict<a>,
  right: Dict<b>,
  init: result
): result => {
  let state = init
  for (let key in left) {
    if (key in right) {
      state = accumulateBoth([key, [left[key], right[key]]], state)
    } else {
      state = accumulateLeft([key, left[key]], state)
    }
  }

  for (let key in right) {
    if (!(key in left)) {
      state = accumulateRight([key, right[key]], state)
    }
  }

  return state
}

const log = merge(
  ([key, left], log): string[] => [...log, `- ${key} : ${left}`],
  ([key, [left, right]], log): string[] => [
    ...log,
    `= ${key} : ${left} -> ${right}`
  ],
  ([key, right], log): string[] => [...log, `+ ${key} : ${right}`],
  { a: 1, b: 2 },
  { b: 18, c: 9 },
  []
)
low focus-check src/Issue.js
[2017-07-12 10:51:46] executable=/Users/gozala/.config/yarn/global/node_modules/flow-bin/flow-osx-v0.49.1/flow
[2017-07-12 10:51:46] version=0.49.1
[2017-07-12 10:51:46] Parsing
[2017-07-12 10:51:56] Building package heap
[2017-07-12 10:51:56] Loading libraries
[2017-07-12 10:52:01] Resolving dependencies
[2017-07-12 10:52:02] Running local inference
[2017-07-12 10:52:02] Calculating dependencies
[2017-07-12 10:52:02] Merging
[2017-07-12 10:52:02] Done
Found 0 errors

@Gozala
Copy link
Contributor Author

Gozala commented Jul 12, 2017

I honestly would much rather have flow request explicit annotation rather than waiting forever on for flow to infer types. At least that way it's clear what the source is and is a lot easier to find a workaround and move on.

@vith
Copy link

vith commented Jul 13, 2017

Looks like I have the same problem. My minimal demo repo: https://github.com/vith/flow-implosion-demo

Note that I also reduced flow-typed/npm/lodash_v4.x.x.js to only the relevant part.

If you wait long enough (about 3 minutes on my system) a subprocess will fill up 20GB of memory and die, at which point the cli process simply prints No errors!

But the log file contains:

[2017-07-13 12:31:27] Merging
Fatal error: out of memory.
Worker exited (code: 2)
Subprocess(12583): fail 2Worker.Worker_exited_abnormally(2)
[2017-07-13 12:34:31] Server is READY
[2017-07-13 12:34:31] Took 185.067990 seconds to initialize.
[2017-07-13 12:34:31] Status: OK

@houshuang
Copy link

houshuang commented Jul 27, 2017

This helped me find the error in my own application, after lot's of trial and error. The original code is much more complex, but here is the absolute minimal example that still hangs on 'merging inference':

// @flow
export const checkComponent = (obj: any[]): Object[] =>
  obj.reduce((acc, x) => {
    if (x === undefined) {
      return [...acc, {}];
    }

    if (x === 'hi') {
      return [...acc, {}];
    }

    if (x.err) {
      return [...acc, {}];
    }
    return acc;
  }, []);

Either removing one branch (if-statement), or adding type annotation to the inline function (reduce), makes flow work again.

@JCMais
Copy link

JCMais commented Nov 17, 2017

Having same issue, need to type flow functions for it to work.

fishythefish pushed a commit that referenced this issue Apr 25, 2018
Summary:
When evaluating spreads inside an array literal, we would "finish" the array type with a different element type for each type that was being spread. This effectively created a "union of array types" instead of a single array type, which could be fanned into an exponential blowup, or worse, an infinite loop, by a suitably crafted cycle in the constraint graph.

Now we pin the element type early and thread it through so that we can always "finish" with that element type.

Fixes #4070
Fixes #4370

Reviewed By: samwgoldman

Differential Revision: D7416763

fbshipit-source-id: 7de51c426df341da9916c99a6e1eba0aec35a8fd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants