Flow: interface vs type subtle differences (different variance rules?)

Created on 4 May 2017  路  5Comments  路  Source: facebook/flow

If you do

interface Foo {
    bar: number | string
}

const buz: Foo = {
    bar: 1,
};

you get

2:  bar: number | string
                      ^ string. This type is incompatible with
6:  bar: 1,
             ^ number

But if you use type instead of interface you get no errors.

type Foo = {
    bar: number | string
}

Is this expected behavior? If it is, why does this make sense? It seems reasonable to expect that interface behaves like type in this case.

Tested on https://flow.org/try/ against version 0.45.0 and master.

Needs docs property variance

Most helpful comment

Would be nice to get a statement from someone on the core team... Especially what @futpib has described is really weird.

All 5 comments

i think it's a bug, basically the problem is that

(({ bar: 1 }: { bar: number }): Foo); // not ok
(({ bar: 1 }: { bar: number | string }): Foo); // ok

afaik { bar: number } is a subtype of { bar: number | string }, so the first example should also work.

(as a workaround you can explicitly annotate the bar property in your object literal)

@madbence I'm always a bit confused on subtypes, but I think that { bar: number } is not a subtype of { bar: number | string }, but is a subtype of { +bar: number | string }. See this example

@futpib You'll also see that it will work with interface. I'm not sure that it means that the difference you spotted is not a bug (I don't know how interfaces work), but covariance might have something to do with that.

@AugustinLF Thanks a lot for your examples, they also made me look up docs more carefully. This is clearly intended and well-documented.

But still this bugs me a bit, at least in this example:

interface Invariant {  property: number | string; }

var value1: Invariant = { property: 42 }; // Error!

function method1(value: Invariant) {
  value.property = 3.14; // Works!
}

Why is writing a string | number property with number considered ok, but initializing it with number is not?

Now that it's clear that it's intended, might as well close the issue, but I guess it's also reasonable to leave it to a project member to decide.

You're getting the error because of variance problems. interface can be implemented by classes, so I think type vs interface makes a difference.

FWIW, this works:

interface Foo {
  +bar: number | string
}

const buz: Foo = {
    bar: 1,
};

Would be nice to get a statement from someone on the core team... Especially what @futpib has described is really weird.

Was this page helpful?
0 / 5 - 0 ratings