Open mike-marcacci opened 7 years ago
Or with if ('foo' in u)
. Or with if (u['foo'])
(because some keys require that notation).
You need to use disjoint unions, that basically ask you to add a common property between all the types that compose the union with an unique value on each of them, and then use this property to refine.
@mgtitimoli: Yes, that's how it currently needs to be done. But this is a feature request to overcome that limitation.
First of all, Foo | Bar
is already a disjoint union, in the sense that there can exist no value that is a member of both types. Therefore, typeof u.foo !== 'undefined'
should be enough here to conclude that u typeof Foo
, by a simple deduction. It would be cool if Flow could make that deduction.
I ran into a similar situation today so I wrote up a flow-try: https://flow.org/try/#0KYDwDg9gTgLgBDAnmYcCCcC8cDeBDALjgGcYoBLAOwHMBfAbgChRJYFlUAhLXAIyNIUaDRs3DR4SFOgA2MrIzhwAPnAAkAURB4AxjAA8agErA8AEwDylGYn1oAfPcUr1W3QeOnL1250dNGADMAV0o9cghKOGpgGABJMwAKPGCYAAtoIjQ5AEoBMipqXGcoWOCoKJT06AA6PBVVKoyoGt4mWiA
export type A = {a: string};
export type B = {b: string};
export type All =
| $Exact<$ReadOnly<A>>
| $Exact<$ReadOnly<B>>;
function getId(author: All): string {
return author.a || author.b;
}
Basically the union type only has two options, so if the first is not satisfied then the second is the only option left and will return a string.
Disjoint unions with exact types will work only if you provide literals. If you provide some more generic types like string
, number
, boolean
— it will fail
If this is not a bug, than the docs here are not enough accurate. There should be info, that it works only with literals
Here is an example from docs, where true
was replaced with boolean
and everything breaks:
Disjoint unions with exact types will work only if you provide literals. If you provide some more generic types like string, number, boolean — it will fail
It's not about literals, it's about types that could be "falsy" (as string could be ""
, boolean false
and number 0
).
As type refinement works on values and not keys it make sense that the refinement can't be done with string
, boolean
and number
.
Look again this example and think what's going on with message === ""
type Success = {| message: string |};
type Failed = {| error: string |};
type Response = Success | Failed;
function handleResponse(response: Response) {
if (response.message) {
return response.message;
} else {
// I think Flow is right, refinement can't be done here, message could exists but empty!
return response.error; // Cannot get `response.error` because property `error` is missing in `Success`
}
}
I guess what we need is a way to do type refinement that works by testing the keys of an object as @mhelvens suggested.
Refining exact object union types by shape is currently VERY limited. We should be able to refine such types by their shape using
typeof
at the very least, and ideally any other common refinement techniques (Array.isArray
, etc).From my perspective this should work:
but fails with:
This limitation is also described in this comment, and is related to #4328.