I have a value of type Array<A>
(an array of subtypes). Flow doesn’t let me pass it to a place that expects Array<A | B>
(an array of supertypes), even though it obviously works.
For example, I can’t assign a value with type Array<'left' | 'right'>
to a variable whose type is Array<string>
:
const directions: Array<'left' | 'right'> = ['right', 'left'];
const messages: Array<string> = directions; // error
This error is raised:
2: const messages: Array<string> = directions; // error
^ Cannot assign `directions` to `messages` because in array element: Either string [1] is incompatible with string literal `left` [2]. Or string [1] is incompatible with string literal `right` [3].
References:
2: const messages: Array<string> = directions; // error
^ [1]
1: const directions: Array<'left' | 'right'> = ['right', 'left'];
^ [2]
1: const directions: Array<'left' | 'right'> = ['right', 'left'];
^ [3]
Try Flow demo
Similarly, I can’t pass an Array<ANode>
to a function that takes Array<Node>
, even though Node
is ANode | BNode
:
type ANode = {type: 'a', value: string};
type BNode = {type: 'b', count: number};
type Node = ANode | BNode;
function getFirstNodeType(nodes: Array<Node>): string {
return nodes[0].type;
}
// works
const nodesSupertype: Array<Node> = [{type: 'a', value: 'foo'}];
getFirstNodeType(nodesSupertype);
// error
const nodesSubtype: Array<ANode> = [{type: 'a', value: 'foo'}];
getFirstNodeType(nodesSubtype); // error
16: getFirstNodeType(nodesSubtype); // error
^ Cannot call `getFirstNodeType` with `nodesSubtype` bound to `nodes` because property `value` is missing in `BNode` [1] but exists in `ANode` [2] in array element.
References:
6: function getFirstNodeType(nodes: Array<Node>): string {
^ [1]
15: const nodesSubtype: Array<ANode> = [{type: 'a', value: 'foo'}];
^ [2]
16: getFirstNodeType(nodesSubtype); // error
^ Cannot call `getFirstNodeType` with `nodesSubtype` bound to `nodes` because string literal `a` [1] is incompatible with string literal `b` [2] in property `type` of array element.
References:
1: type ANode = {type: 'a', value: string};
^ [1]
2: type BNode = {type: 'b', count: number};
^ [2]
Try Flow demo
Flow raises an error because it thinks you might mutate the array:
// given the definitions of `Node`, `ANode`, and `BNode` from the question’s second example
function getFirstNodeType(nodes: Array<Node>): string {
const bNode: BNode = {type: 'b', count: 0}
// Mutate the parameter `nodes`. Adding a `BNode` is valid according its type.
nodes.push(bNode);
return nodes[0].type;
}
const nodesSubtype: Array<ANode> = [{type: 'a', value: 'foo'}];
getFirstNodeType(nodesSubtype); // error
Try Flow demo
After running the above code, nodesSubtype
would contain a BNode
even though it is declared as an array of ANode
, violating its type.
There are two solutions to convince Flow that you won’t mutate the array. The clearest one is to replace Array
with the Flow utility type $ReadOnlyArray
.
function getFirstNodeType(nodes: $ReadOnlyArray<Node>): string { // use $ReadOnlyArray
return nodes[0].type;
}
const nodesSubtype: Array<ANode> = [{type: 'a', value: 'foo'}];
getFirstNodeType(nodesSubtype); // no error
Try Flow demo
You only have to make that replacement in the function parameter’s type (e.g. nodes
), but if you liked you could use $ReadOnlyArray
everywhere it applies (e.g. for nodesSubtype
).
$ReadOnlyArray
is the most type-safe solution, but you may not want to have to change all your existing functions to use it. In that case, your alternative is to cast the array type through any
instead. Yes, you could have done that from the beginning, but at least now you know why it’s safe to do this cast. You can even leave a comment explaining why this cast exists, which might help you catch assumptions no longer being valid.
function getFirstNodeType(nodes: Array<Node>): string {
return nodes[0].type;
}
const nodesSubtype: Array<ANode> = [{type: 'a', value: 'foo'}];
// casting `Array<ANode>` to `Array<Node>` is okay because we know that `getFirstNodeType` won’t actually modify the array
getFirstNodeType(((nodesSubtype: any): Array<Node>));
Try Flow demo
If you don’t care about documenting the problem, you can omit the comment and only cast to any
:
getFirstNodeType((nodesSubtype: any));
Try Flow demo
$ReadOnlyArray
became documented in August 2018, thanks to this pull request.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With