Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't assign array of objects to array of union types in Flow

I'm learning Flow and therefore I'm working on a little hobby project with JavaScript and Flow. I have a class Foo and a different class Bar that I want to take in an array of Foo objects as an option in the constructor. However, I also want to be able to send some other data for each such object, and so I want to have an array where each element is either a plain Foo object, or a Foo object wrapped in an array or object.

However, when I tried to write the code for this, I got some weird errors that I don't understand the reason for. As far as I can tell, it thinks that there's a type conflict because Foo isn't compatible with all of the types of the union, but as far as I understand it should only have to be compatible with at least one of them...

Here's the minimal code I needed to reproduce the exact errors I got (link to Try Flow example):

// @flow

class Foo { }

interface BarOptions {
  foos: ( Foo | [ Foo ] | { foo: Foo } )[];           // line 6
}

class Bar {
  constructor(options?: BarOptions) { }
}

const foo: Foo = new Foo();

const bar = new Bar({
  foos: [ foo ],                                    // line 16
});

I get the following errors:

Line 6:
  tuple type: This type is incompatible with Foo
  object type: This type is incompatible with Foo
Line 16:
  tuple type: This type is incompatible with Foo
  object type: This type is incompatible with Foo

Is there a intuitive (or unintuitive) reason for these errors?

like image 304
Frxstrem Avatar asked Dec 17 '16 03:12

Frxstrem


2 Answers

Do you think it might be related to this open Github ticket? If we replace interface with type, it validates:

// @flow

class Foo { }

type BarOptions ={
  foos: 
        Class<Foo> |
        Foo |
        Foo[] |
        { foo: Foo } 
}

class Bar {
  constructor(options?: BarOptions) { }
}

const foo: Foo = new Foo();

const bar = new Bar({
  foos: Foo,
//   foos: foo,
//   foos: [foo],
//   foos: { foo: foo },
});
like image 112
Ivan Chaer Avatar answered Oct 20 '22 01:10

Ivan Chaer


I think BarOptions should actually be a type alias instead of an interface. An interface declares a type that classes can implement. Interfaces are not data types and they should not have fields (containing data).

Here everything works if we just change interface BarOptions to type BarOptions =.

Alternatively you can change foo to become a getter function:

interface BarOptions {
  foos(): ( Foo | [ Foo ] | { foo: Foo } )[];
}
like image 32
homam Avatar answered Oct 20 '22 00:10

homam