Flow: Flow claims type incompatibility for async function when returned value is instance of subclass with a constructor

Created on 9 Dec 2015  路  8Comments  路  Source: facebook/flow

Consider the following minimal example class that extends node's EventEmitter:

import { EventEmitter } from 'events';

class Example extends EventEmitter {
  constructor() {
    super();
  }
}

Now consider the following helper:

function makeExample(): Example {
  return new Example();
}

As expected, Flow 0.19.1 has no issues with this and reports 0 errors.

Now consider an async version of this helper:

async function makeExampleAsync(): Promise<Example> {
  return new Example();
}

This makes Flow complain:

index.js:16
 16:   return new Example();
              ^^^^^^^^^^^^^ Example. This type is incompatible with
430:     static resolve<T>(object?: Promise<T> | T): Promise<T>;
                                    ^^^^^^^^^^ Promise. See: /private/tmp/flow/flowlib_38091f69/core.js:430

What's weird:

  • Using no base class or custom base class (rather than some node-provided one) makes the error go away
  • Getting rid of the custom constructor in the subclass makes the error go away.

Given that the sync version of the helper is cool with Flow, I can only assume that this a bug in Flow? I don't really see how the async version changes any of the contracts and assumptions that Flow can make.

Most helpful comment

The following code works:

/* @flow */

import { EventEmitter } from 'events';

class Example extends EventEmitter {
  constructor() {
    super();
  }
}

async function makeExampleAsync(): Promise<Example> {
  return new Example();
}

function makeExample(): Promise<Example> {
  let example = new Example();
  return Promise.resolve(example);
}

Likely fixed in 0.28 as part of the big union/intersection re-implementation.

All 8 comments

What if you do

async function makeExampleAsync(): Promise<Example> {
    return await new Promise(function(resolve, reject) {
       resolve(new Example());
    }).then(function(example) {
        return example;
    });
}

?

Sadly that doesn't change anything. Even the following causes the same error:

function makeExampleAsync(): Promise<Example> {
  let example = new Example();
  return Promise.resolve(example);
}

If this is related to #834 (which I don't quite understand, because I'm lacking context -- the repo that's referenced has been deleted), I'd be happy to sprinkle some extra type checks into my code. But where?

So this is pretty strange, but we've seen errors with Promise and EventEmitter interacting poorly before. See #545 and #683. I'm honestly not sure what is going on there...

@samwgoldman any workarounds you can think of? I tried typing everything out with Promise<any> rather than Promise<Example> but that doesn't help either...

Never mind, this worked for me as a (sad) workaround:

async function makeExampleAsync(): Promise<any> {
  let example: any = new Example();
  return example;
}

This is a problem I saw a ton of times while trying to declare Bluebird properly for flow:

The issue seems to be Flow bailing out early on a union type when it really should be trying the other side.

For example, the definition of Promise.resolve() is:

declare class Promise<R> {
  // ... 
  static resolve<T>(object?: Promise<T> | T): Promise<T>;
}

Which fails the above example with the error:

 12:   return new Example();
              ^^^^^^^^^^^^^ Example. This type is incompatible with
 17:     static resolve<T>(object?: Promise<T> | T): Promise<T>;
                                    ^^^^^^^^^^ Promise. See: interfaces/promise.js:17

But simply inverting the union fixes the issue. This is really odd, because unions shouldn't have ordering:

  static resolve<T>(object?: T | Promise<T>): Promise<T>;
  No errors!

I verified that it was, indeed, typechecking:

class Example {}
// No errors!
async function makeExampleAsync(): Promise<Example> {
  return new Example();
}

//  16:   return new Foo();
//             ^^^^^^^^^ Foo. This type is incompatible with
//  17:     static resolve<T>(object?: T | Promise<T>): Promise<T>;
//                                  ^^^^^^^^^^^^^^ union type. See: interfaces/promise.js:17
class Foo {}
async function makeExampleAsync(): Promise<Foo> {
  return new Example();
}

I just ran into this today...has there been any progress on this issue?

The following code works:

/* @flow */

import { EventEmitter } from 'events';

class Example extends EventEmitter {
  constructor() {
    super();
  }
}

async function makeExampleAsync(): Promise<Example> {
  return new Example();
}

function makeExample(): Promise<Example> {
  let example = new Example();
  return Promise.resolve(example);
}

Likely fixed in 0.28 as part of the big union/intersection re-implementation.

Was this page helpful?
0 / 5 - 0 ratings