Flow 0.30.0
/* @flow */
import React, {Component} from 'react'
import * as Immutable from 'immutable'
export type Props = {
calibration: Immutable.Map<any, any>,
}
type PointProps = Props & {
pointIndex: number,
}
export class Point extends Component<void, PointProps, void> {
static validate(props: PointProps): Object {
const {pointIndex, calibration} = props
const outputValue = calibration.getIn(['points', pointIndex, 'y'])
return {outputValue: {error: `'${outputValue}' is not a valid number`}, valid: false}
};
render() {
return null
}
}
Errors:
react-sandbox/scratch/flowissues.js:16
16: const {pointIndex, calibration} = props
^^^^^^^^^^^ property `calibration`. Property cannot be accessed on any member of intersection type
16: const {pointIndex, calibration} = props
^^^^^ intersection
Member 1:
10: type PointProps = Props & {
^^^^^ Props
Error:
18: return {outputValue: {error: `'${outputValue}' is not a valid number`}, valid: false}
^^^^^^^^^^^ undefined (too few arguments, expected default/rest parameters). This type cannot be added to
18: return {outputValue: {error: `'${outputValue}' is not a valid number`}, valid: false}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ string
Member 2:
10: type PointProps = Props & {
^ object type
Error:
16: const {pointIndex, calibration} = props
^^^^^^^^^^^ property `calibration`. Property not found in
10: type PointProps = Props & {
^ object type
This only seems to hapeen if PointProps is an intersection type. The errors about calibration not being part of the shape are wrong and go away if the string template error is fixed.
I can also repro this way:
/* @flow */
import React, {Component} from 'react'
import * as Immutable from 'immutable'
export type Props = {
calibration: Immutable.Map<any, any>,
}
type PointProps = Props & {
pointIndex: number,
}
export class Point extends Component<void, PointProps, void> {
static validate(props: PointProps): Object {
const {pointIndex, calibration} = props
const outputValue = calibration.getIn(['points', pointIndex, 'y'])
return outputValue.toFixed(1)
};
render() {
return null
}
}
Errors:
react-sandbox/scratch/flowissues.js:16
16: const {pointIndex, calibration} = props
^^^^^^^^^^^ property `calibration`. Property cannot be accessed on any member of intersection type
16: const {pointIndex, calibration} = props
^^^^^ intersection
Member 1:
10: type PointProps = Props & {
^^^^^ Props
Error:
18: return outputValue.toFixed(1)
^^^^^^^^^^^^^^^^^^^^^^ call of method `toFixed`. Method cannot be called on possibly undefined value
18: return outputValue.toFixed(1)
^^^^^^^^^^^ undefined (too few arguments, expected default/rest parameters)
Member 2:
10: type PointProps = Props & {
^ object type
Error:
16: const {pointIndex, calibration} = props
^^^^^^^^^^^ property `calibration`. Property not found in
10: type PointProps = Props & {
^ object type
So this is a bug in the immutable.js.flow file you're probably using. See if changing these two lines:
https://github.com/facebook/immutable-js/blob/master/type-definitions/immutable.js.flow#L55-L56
to this single line:
getIn<T>(searchKeyPath: ESIterable<any>, notSetValue?: T): T;
helps.
Detailed explanation: when you don't provide a notSetValue arg in your call, you're implicitly passing undefined, so Flow selects the first case with T = void.
@avikchaudhuri I think you're missing the point. The question is why the incorrect errors like this were getting triggered:
16: const {pointIndex, calibration} = props
^^^^^^^^^^^ property `calibration`. Property cannot be accessed on any member of intersection type
16: const {pointIndex, calibration} = props
^^^^^ intersection
On the surface these seem to have nothing to do with immutable.js, yet when I try to repro with calibration: {outputValue?: number} I only get the string error.
OK, reopening to see how we can do this better. In general, when one branch of an intersection fails for whatever reason, we have to try the other branch. In this case the second branch is doomed to fail, and the first fails because of a declaration bug. I agree it doesn't match expectations, since it's clear in our heads that the second branch should never succeed. But to Flow the two branches are equally to blame.
What you're saying made sense at first but on more thought it doesn't. I can see how for a _union_ type, A | B, if the A branch fails, then it should retry the code assuming the value is of type B. But with an intersection type A & B, if something fails according to the conditions of A, it's not right to retry assuming the value is just of type B, because the value must also be of type A, right?
The more I think about this the hazier it seems to me. Obviously for union types flow just needs to check if one branch of the union passes. So at first I would think that with intersection types, all branches have to pass -- but of course the branch lacking calibration would never pass -- so I'm guessing flow does something more complicated for shape intersections, like combining the props present?
In any case no value of type Props & { calibration: ... } can lack a calibration field, so isn't it a bug if a branch of flow checking believes it doesn't have a calibration?
Most helpful comment
What you're saying made sense at first but on more thought it doesn't. I can see how for a _union_ type,
A | B, if theAbranch fails, then it should retry the code assuming the value is of typeB. But with an intersection typeA & B, if something fails according to the conditions ofA, it's not right to retry assuming the value is just of typeB, because the value must also be of typeA, right?The more I think about this the hazier it seems to me. Obviously for union types flow just needs to check if one branch of the union passes. So at first I would think that with intersection types, all branches have to pass -- but of course the branch lacking
calibrationwould never pass -- so I'm guessing flow does something more complicated for shape intersections, like combining the props present?In any case no value of type
Props & { calibration: ... }can lack acalibrationfield, so isn't it a bug if a branch of flow checking believes it doesn't have acalibration?