Flow disjoint union based on if property exists - flowtype

I wonder if there is any way that I can refine a disjoint union based on if a property exists, something like this:
type Success = {| success: string |};
type Failed = {| error: string |};
type Response = Success | Failed;
function handleResponse(response: Response) {
if (response.hasOwnProperty('success')) {
// It is a Success
var value: string = response.success;
} else {
// It is an Error
var message: string = response.error;
}
}
This code gives me the following errors:
11: var value: string = response.success;
^ Cannot get `response.success` because property `success` is missing in `Failed` [1].
References:
8: function handleResponse(response: Response) {
^ [1]
14: var message: string = response.error;
^ Cannot get `response.error` because property `error` is missing in `Success` [1].
References:
8: function handleResponse(response: Response) {
^ [1]
I want to be sure that if the first if statement is true, the response is a Sucess, otherwise it is an Error without changing the Success or Failed types.
Try Link

It's not possible in the way you would like. Here's an example of a discussion about what you want on GitHub.
What you provided above is not actually a disjoint union, it's simply a union. Using a shared prop that differentiates the two would be a disjoint union, and there it is possible to do type refinement. Here's a Try Link.

Related

Flow. How to specify the union type depending on the function argument

Let's say we have two types and union of them:
type A = {tag: 'a'};
type B = {tag: 'b'};
type U = A | B;
And function, which returns A or B, depending on provided tag:
const getSpecificObj = (tag: string) => U;
How can I specify that returning type? So then I will be able to use it like:
const a: A = getSpecificObj('a');
P.S. Yes, I know that I can write the same function for every type or override. But maybe there are another ways. For example in TS I can write:
type Extract<A, T> = A extends { tag: T } ? A : never;
<T>(tag: T): Extract<U, T>
Rather than returning a union of the types A and B, you need to parameterize the function getSpecificObj by the type of its argument, and then return an object which references that type parameter. Here's how I would do it:
type A = {tag: 'a'};
type B = {tag: 'b'};
function getSpecificObj<T : string>(tag: T) : { tag: T } {
return { tag };
}
const a : A = getSpecificObj('a');
const b : B = getSpecificObj('b');
The annotation <T : string> on getSpecificObj makes sure the function isn't more general than you need, and will only accept strings as tags. You can think of this as moving the string annotation off of the tag parameter and into the generic type of the tag parameter.
You can view the example on the try-flow page here.

Why does this fail?

Live example here.
type Activity = {
verb: string
}
type CommentActivity = {
verb: 'comment'
}
function doStuff (activity: Activity) {}
const commentActivity: CommentActivity = { verb: 'comment' }
const likeActivity: Activity = { verb: 'like' }
doStuff(likeActivity)
doStuff(commentActivity)
Fails with:
15: doStuff(commentActivity)
^ Cannot call `doStuff` with `commentActivity` bound to `activity` because string literal `comment` [1] is incompatible with string [2] in property `verb`.
References:
6: verb: 'comment' ^ [1]
2: verb: string ^ [2]
The error message is clear and I know how to work around this, but I don't understand why a string literal isn't considered a valid string?
This is an issue of property variance. If you have a function like doStuff with the type
(activity: Activity): void => {}
then it is perfectly valid for the function do to
function doStuff (activity: Activity) {
activity.verb = "some new verb";
}
and that 100% typechecks. What that means is that if
doStuff(commentActivity)
were allowed, doStuff would actually change the type of commentActivity, which is why this is throwing an error for you.
What you need to do is tell Flow that you will not be changing the value of .verb, essentially making it read-only inside of doStuff. To do this, you put a + before the name of the property.
type Activity = {
+verb: string
};
(On Flow/try)

class instance returned in a static method doesn't match type

I'm returning an instance of class in a static method but flow is complaining about incompatible generic type. Is this a problem with flow or am I doing something wrong?
Code:
class Foo<T> {
var1: T
constructor(var1: T) {
this.var1 = var1
}
static staticMethod(var1: T) {
return new Foo(var1)
}
}
let x:Foo<number> = new Foo(1) // works
let y:Foo<number> = Foo.staticMethod(1) // doesn't work
Error:
let y:Foo<number> = Foo.staticMethod(1)
^ Cannot assign `Foo.staticMethod(...)` to `y` because `T` [1] is incompatible with number [2] in type argument `T` [3].
It seems like flow can't infer the type here, but it works fine if you annotate the return type.
Change
static staticMethod(var1: T) {
to
static staticMethod(var1: T): Foo<T> {

Meaning of interrogation

I'm a little bit confuse about the meaning difference of using "?"
I offen saw this:
var foo?: number = "bar"
But also saw this:
function foo(bar: {baz: ?string}) { ... }
And also saw both together.
I've read about invariants and maybe types, but if I understood it right, both signals have the same meaning, which is: "this type is of kind 'X', but it maybe is null or undefined".
Is it right or am I getting it wrong?
Here are answers to most of your questions:
// Don't know what this is, or why you would use it
// Error: undefined is incompatible with string
var foo1?: string = undefined;
// ?string means string, null, or undefined
var foo2: ?string = undefined;
type FooOptional = { foo?: string };
type FooMaybe = { foo: ?string };
// If it's optional it can be completely omitted
var foo3: FooOptional = {};
// It can also be explicitly set to undefined
var foo4: FooOptional = { foo: undefined };
// But not null!
var foo5: FooOptional = { foo: null };
// If it's a maybe type, it must be specified
// Error: property `foo` not found
var foo6: FooMaybe = {};
// But you can set it explicitly to null or undefined
var foo7: FooMaybe = { foo: null };
var foo8: FooMaybe = { foo: undefined };
(tryflow link)
Using both together (e.g. {foo?: ?string} as a type) usually (but not in all cases) indicates that the author doesn't quite know what type they want to use and have just added question marks until it typechecks. Typically I have found that if I think it through, it makes sense to use either an optional property or a maybe type, but not both.

Typescript strict arrays and dictionaries

I'm trying to define a dictionary-like type. I can't figure out how to get the Typescript compiler to strictly check the key type.
var map: {[hello:number]: string} = {}
// I get the same results if I declare: var map: string[] = []
map[1.1] = "hello";
map[1.1] = 1.1; // error (as expected)
map["hello"] = "hello"; // missing error on key
map["hello"] = 1.1; // missing error on key
var s2: string = map[1.1];
var i2: number = map[1.1]; // error (as expected)
var s1: string = map["hello"]; // missing error on key
var i1: number = map["hello"]; // missing error on key
I get the same results with Typescript 1.5.3 and 1.6.0-beta.
I can't figure out how to get the Typescript compiler to strictly check the key type.
string indexing is always allowed in TypeScript. This is to mimic the fact that even though you say that you are indexing by a number you are actually indexing by a string (foo[1] is same as foo['1'])
However you can specify a restriction on string as well as number. But note that it must be consistent with number because after all number is going to get converted to a string at runtime anyways. This removes two of the mentioned error cases:
var map: {
[key: number]: string;
[key: string]: string;
} = {};
// I get the same results if I declare: var map: string[] = []
map[1.1] = "hello";
map[1.1] = 1.1; // error (as expected)
map["hello"] = "hello"; // missing error on key
map["hello"] = 1.1; // error
var s2: string = map[1.1];
var i2: number = map[1.1]; // error (as expected)
var s1: string = map["hello"]; // missing error on key
var i1: number = map["hello"]; // error
I made my own Dictionary type, good for dropdowns (I am using kendo dropdowns from kendo UI):
type Dictionary = Array< { key: string, value: string } >;

Resources