Reputation: 22125
I have the following flow
code (available here):
type SquareState = {
value: number,
};
type InternalSquare = {
value: number | null,
};
export function getMax(squares: Array<InternalSquare> | Array<SquareState>): number {
return squares.reduce(function(a : number, b : InternalSquare | SquareState): number {
if (b.value === null) {
return a;
} else {
return Math.max(a, b.value);
}
}, 0);
}
Which is generating this error:
9: export function getMax(squares: Array<InternalSquare> | Array<SquareState>): number {
^ number [1] is incompatible with null [2] in property `value`.
References:
2: value: number,
^ [1]
6: value: number | null,
^ [2]
As far as I understand, there should be no error, because there's a guard against null
. The code works fine with either:
export function getMax(squares: Array<SquareState>): number
or
export function getMax(squares: Array<InternalSquare>): number
But not with Array<SquareState> | Array<InternalSquare>
Also, if I replace the reduce with a for loop, it works:
export function getMax(squares: Array<InternalSquare> | Array<SquareState>): number {
var max = 0;
for(var i = 0 ; i < squares.length ; i ++) {
if (squares[i].value === null) {
continue;
} else {
max = Math.max(max, squares[i].value);
}
}
return max;
}
What am I missing?
Upvotes: 3
Views: 69
Reputation: 3478
I think you might want to accept an array of either InternalSquare
or SquareState
:
(Try)
type SquareState = {
value: number,
};
type InternalSquare = {
value: number | null,
};
export function getMax(squares: Array<InternalSquare | SquareState>): number {
return squares.reduce(function(a : number, b : InternalSquare | SquareState): number {
if (b.value === null) {
return a;
} else {
return Math.max(a, b.value);
}
}, 0);
}
It doesn't seem like flow realizes that reducing the union of two arrays (Array<A> | Array<B>
) is equivalent to reducing an array of the union of both types (Array<A | B>
). This might even be PR-worthy.
Interestingly, spreading the array allows flow to understand the type as Array<InternalSquare | SquareState>
:
(Try)
type SquareState = {
value: number,
};
type InternalSquare = {
value: number | null,
};
export function getMax(squares: Array<InternalSquare> | Array<SquareState>): number {
const afterSpread = [...squares]
return afterSpread.reduce(function(a : number, b : InternalSquare | SquareState): number {
if (b.value === null) {
return a;
} else {
return Math.max(a, b.value);
}
}, 0);
}
Even more interesting, if you change one of the arrays (but not both!) to a $ReadOnlyArray, it also understands what's going on here:
type SquareState = {
value: number,
};
type InternalSquare = {
value: number | null,
};
export function getMax(squares: $ReadOnlyArray<InternalSquare> | Array<SquareState>): number {
return squares.reduce(function(a : number, b : InternalSquare | SquareState): number {
if (b.value === null) {
return a;
} else {
return Math.max(a, b.value);
}
}, 0);
}
I'd hazard a guess that it actually evaluates each of the possibilities in that case and finds both possibilities work. Total speculation though.
Upvotes: 2