I may have misunderstood how disjointed unions should work with bounded polymorphism but consider this:
/* @flow */
type Action = { type: string };
type MyAction = { type: 'LOL' } | { type: 'OMG' }; // A subset of Action, isn't it?
function dispatch(action: Action) {
console.log(action);
}
var action: MyAction = { type: 'LOL' }; // works if I change to : Action
dispatch(action);
Actual output:
index.js:11:1,16: function call
Error:
index.js:3:23,28: string
Expected string literal LOL
index.js:4:25,29: string literal LOL
index.js:11:1,16: function call
Error:
index.js:3:23,28: string
Expected string literal OMG
index.js:4:43,47: string literal OMG
Found 2 errors
I'd expect it to pass type check because MyAction is a subset of Action (every possible form of MyAction satisfies Action).
I'm actually wrong. It doesn't have anything to do with either disjoint unions, or bounded polymorphism :-P
This is enough to reproduce the issue:
/* @flow */
type Action = { type: string };
type MyAction = { type: 'LOL' };
function dispatch(action: Action) {
console.log(action);
}
var action: MyAction = { type: 'LOL' };
dispatch(action);
I get:
index.js:11:1,16: function call
Error:
index.js:3:23,28: string
Expected string literal LOL
index.js:4:25,29: string literal LOL
Found 1 error
I'd expect it to pass the type check because clearly a string literal is also a string.
Just from the error message, it looks to me like flow is unifying the type Action and MyAction in the function call instead of using subtyping.
And, actually, I think that we need unification here, because of mutability. Consider the following example:
type Action = { type: string };
type MyAction = { type: 'LOL' };
function dispatch(action: Action) {
// action.foo is just string, so assignment is valid locally
// however, the passed-in value is now mistyped
action.foo = "bar";
}
var action: MyAction = { type: 'LOL' };
dispatch(action);
I would really like to see flow accept the kind of code you've written (while rejecting the code in my example), and I can see a couple ways to get there:
However, neither of these is simple to implement...
I'll also note that the issue title is a bit misleading. { prop: "literal" } is a subtype of { prop: string }, as my example below shows. The real issue here is that flow is pessimistic about when this subtyping can apply.
/* @flow */
declare function test(foo: { bar: string }): void;
// Object literals are safe to pass in
test({ bar: "baz" }); // OK
// Even if there is an alias, because the type is unconstrained
var x = { bar: "baz" };
test(x); // OK
// But if we specify a more specific type, we need to ensure consistency
var y: { bar: "baz" } = { bar: "baz" };
test(y); // Error
Closing, this can be fixed using covariant property:
type Action = { +type: string }; // <-- here it is
type MyAction = { type: 'LOL' } | { type: 'OMG' }; // A subset of Action, isn't it?
function dispatch(action: Action) {
console.log(action);
}
var action: MyAction = { type: 'LOL' };
dispatch(action);
Most helpful comment
Closing, this can be fixed using covariant property: