In the following block of code, flow errors occur on the OuterY and OuterZ type definitions only when the getInnerValues function is present.
The errors complain that "Y" and "Z" are incompatible with "X". For example: "string literal Y is incompatible with string literal X.".
/* #flow */
type Inner<T> = { value: T };
type OuterX = { inner: Array<Inner<"X">> };
type OuterY = { inner: Array<Inner<"Y">> };
type OuterZ = { inner: Array<Inner<"Z">> };
type Outer = OuterX | OuterY | OuterZ;
// If the next line is present, errors occur on
// lines 6 and 7 complaining that "Y" and "Z" are
// incompatible with "X". When the next line is
// commented out, the errors go away. Why??
const getInnerValues = (outer: Outer) => outer.inner.map(inner => inner.value);
Why is this happening?
Click here to see the issue on flow.org/try
Click here to see the same issue with stricter typing on flow.org/try
Flow doesn't realize that there exists an inner property of type {value: string} for all the possible cases of Outer. One way to fix this is to type the function to accept an object with the expected type:
(Try)
/* #flow */
type Inner<T> = { value: T };
type OuterX = { inner: Array<Inner<"X">> };
type OuterY = { inner: Array<Inner<"Y">> };
type OuterZ = { inner: Array<Inner<"Z">> };
type Outer = OuterX | OuterY | OuterZ;
// no errors
const getInnerValues = (outer: {inner: Array<{value: string}>}) =>
outer.inner.map(inner => inner.value);
Another way to do this (probably the better way) is to redefine Outer as type which accepts a type parameter. Then you can generically type your getInnerValues function to accept generic Outer instances:
(Try)
/* #flow */
type Inner<T> = { value: T };
type OuterX = { inner: Array<Inner<"X">> };
type OuterY = { inner: Array<Inner<"Y">> };
type OuterZ = { inner: Array<Inner<"Z">> };
type Outer<T> = {
inner: Array<Inner<T>>
}
// no errors
const getInnerValues = <T>(outer: Outer<T>) => outer.inner.map(inner => inner.value);
Related
I have this piece of code:
/* #flow */
import { List } from 'immutable';
type NotMapped = {|
first: Array<number>,
second: number,
|};
type Mapped = {|
first: List<number>,
second: number,
|};
const notMapped: NotMapped = {
first: [1, 2, 3],
second: 10,
};
const map = () => {
const first = List(notMapped.first);
return { ...notMapped, first };
}
const result: Mapped = map();
I want result to beMapped type but I get:
{|
first: List<number> | Array<number>,
second: number
|}
How come that flow thinks it might be Array<number> when I explicitly set first as List(notMapped.first)? It only works if I set return { second: notMapped.second, first };, but this isn't a solution because I have large amount of data and I cannot set every item.
I have checked and this is well-known issue, but when spreading types, not object with assigned type. Is there any solution for this?
I'm struggling with flowtype declaration for a generic function with different pairs of parameters.
My goal is to have a function which return an object of certain union type depending on input parameters.
I'm having a big load of messages that i want to type (for this example i'm using only two)
type Message1 = {
event: 'UI',
type: 'receive',
payload: boolean
}
type Message2 ={
event: 'UI',
type: 'send',
payload: {
foo: boolean;
bar: string;
}
}
type MessageFactory<T> = (type: $PropertyType<T, 'type'>, payload: $PropertyType<T, 'payload'>) => T;
export const factory: MessageFactory<Message1> = (type, payload) => {
return {
event: 'UI',
type,
payload
}
}
factory('receive', true);
// factory('send', { foo: true, bar: "bar" });
when i change
MessageFactory<Message1>
to
MessageFactory<Message1 | Message2>
it will throw an error
Could not decide which case to select. Since case 1 [1] may work but if it doesn't case 2 [2] looks promising too. To fix add a type annotation to `payload` [3] or to `type` [4]
You can ty it here
any idea how to declare this function?
or is it stupid idea and i'm going to the wrong direction?
any better solutions?
Create a GenericMessage with type parameters for your desired properties (type and payload), then have your factory return a GenericMessage:
(Try)
type GenericMessage<TYPE: string, PAYLOAD> = {
event: 'UI',
type: TYPE,
payload: PAYLOAD
}
const factory = <T: string, P>(type: T, payload: P): GenericMessage<T, P> => {
return {
event: 'UI',
type,
payload
}
}
const test1 = factory('receive', true);
const test2 = factory('send', { foo: true, bar: "bar" });
// Let's check the new type against Message1 and Message2:
type Message1 = {
event: 'UI',
type: 'receive',
payload: boolean
}
type Message2 ={
event: 'UI',
type: 'send',
payload: {
foo: boolean;
bar: string;
}
}
// Type assertions
(test1: Message1);
(test2: Message2);
(test1: Message2); // Error!
If you want, you can create a MessageFactory type that returns a GenericMessage<T, P>. You can also create an EVENT type parameter if you need to control the event property on the object.
(You don't need to call it GenericMessage, I just called it that to make a distinction between your existing types and this new one)
So in this basic example (tryflow):
// basic identity function example with generic type
type Foo = { prop: number};
type Bar = { prop: string };
const foo: Foo = { prop: 1 };
const bar: Bar = { prop: 'a' };
function identity<T>(same: T): T {
return same;
}
// here identity acts as (Foo) => Foo
const foo2: Foo = identity(foo);
// and here it's (Bar) => Bar
const bar2: Bar = identity(bar);
My identity function, using generics, takes whatever type is given to it. As arguments are bound to it, T becomes first Foo and then Bar.
What I want is a higher-order function which returns a generic function. I can write a higher-order function which uses generics (tryflow):
type IdentityFunction = <T>(self: T) => T;
// error here
const baseId: IdentityFunction = (same) => same;
// ^ Cannot assign function to
// `baseId` because `T` [1] is
// incompatible with `T` [2] in
// the return value.
type Foo = { prop: number};
type Bar = { prop: string };
const foo: Foo = { prop: 1 };
const bar: Bar = { prop: 'a' };
function makeIdentity(func: IdentityFunction): IdentityFunction {
return func;
}
const identity: IdentityFunction = makeIdentity(baseId);
const foo2: Foo = identity(foo);
const bar2: Bar = identity(bar);
For me, this approach makes the most sense. I'm honestly not sure why I get this error. How can T be incompatible with itself? Is it because a type is never explicitly applied to T? It's somehow indeterminate so it just can't be used for anything? But then, isn't that the whole point of generics? Anyway, I'm sure I'm just missing some subtle point of the type system, or maybe I'm going about this the wrong way. Any help appreciated.
You need to generically type your baseID function so Flow knows what you expect as argument and return type. It seems like Flow doesn't use the type of IndentityFunction when trying to figure out what the baseId function is really doing.
(Try)
type IdentityFunction = <T>(self: T) => T;
// no more error
const baseId: IdentityFunction = <S>(same: S): S => same;
type Foo = { prop: number};
type Bar = { prop: string };
const foo: Foo = { prop: 1 };
const bar: Bar = { prop: 'a' };
function makeIdentity(func: IdentityFunction): IdentityFunction {
return func;
}
const identity: IdentityFunction = makeIdentity(baseId);
const foo2: Foo = identity(foo);
const bar2: Bar = identity(bar);
You can simplify the instantiation of baseId to:
const baseId = <S>(same: S): S => same;
And flow still understands what's going on here.
This behavior is a little confusing and I wonder if there is a good reason for it. You would think that it could take what's on the lefthand side and apply it to the function on the right (especially in simple cases like this one). Maybe it has to do with how flow sees the righthand expression? If anyone else has an idea, I'd love to hear it.
Either way, I tend to avoid declaring the type of functions on the lefthand side of declarations. Not as a rule, I just rarely want to declare the type of a function somewhere besides the function itself.
I have to make two keys optional index and remove in an object. However if one is provided the other must be there. So its like this:
type Props = {
isSettings: boolean,
} | {
index: number,
remove: $PropertyType<FieldProps, 'remove'> // (index: number) => void
}
Where the second object is optional. The above is not working, as it is not expecting isSettings in the 3rd object. But it is always required.
Standard object types in Flowtype are objects defined to have at least the properties specified. This means that if you have a type like { isSettings: boolean } you are saying only that that the object has an isSettings property that is a boolean. It is allowed to have other properties, it just has to know the type of isSettings.
This means that if you have a type
type Props = {
isSettings: boolean,
} | {
index: number,
remove: (index: number) => void
};
then doing
var obj: Props = ...
if (obj.remove) {
var n: number = obj.index;
}
will fail, because it doesn't prove anything, because you have not prohibited there being a remove property on both objects.
In order to refine an object type like this one, Flow needs to be told that each type has exactly the given set of properties. This is where Flow's Exact object types come in.
If you change your types to be
type Props = {|
isSettings: boolean,
|} | {|
index: number,
remove: (index: number) => void
|};
then a snippet like
var obj: Props = ...
if (obj.remove) {
var n: number = obj.index;
}
will work as expected, because the presence of remove means there must be a property called index that is a number.
I'm new to flow, any trying to cover some of my functions, however often I have these snippets where I extract fields form an object based on some condition. But I'm struggling to cover them with flow.
const _join = function ( that: Array<Object>, by: string, index: number) {
that.forEach((thatOBJ: {[string]: any}, i: number)=>{
let obj: {[string]: any} = {};
for (let field: string in thatOBJ) {
if (field !== by) {
obj[`${index.toString()}_${field}`] = thatOBJ[field]; // NOT COVERED
} else {
obj[field] = thatOBJ[field]; // NOT COVERED
}
that[i] = obj;
}
});
}
The array that in this code is a data array so can really be in any format of mongodb data.
Any ideas on what to add to make the two lines which are not covered by flow covered?
Thanks.
A few notes...
This function has a "side effect" since you're mutating that rather than using a transformation and returning a new object.
Array<Object> is an Array of any, bounded by {}. There are no other guarantees.
If you care about modeling this functionality and statically typing them, you need to use unions (or |) to enumerate all the value possibilities.
It's not currently possible to model computed map keys in flow.
This is how I'd re-write your join function:
// #flow
function createIndexObject<T>(obj: { [string]: T }, by: string, index: number): { [string]: T } {
return Object.keys(obj).reduce((newObj, key) => {
if (key !== by) {
newObj[`${index}_${key}`] = newObj[key]
} else {
newObj[key] = obj[key]
}
return newObj
}, {})
}
// NO ERROR
const test1: { [string]: string | number } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)
// ERROR
const test2: { [string]: string | boolean } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)