facebook / flow

Adds static typing to JavaScript to improve developer productivity and code quality.
https://flow.org/
MIT License
22.1k stars 1.86k forks source link

non-associative intersections ( (A&B)&C is different from A&(B&C) ) #6641

Open aij opened 6 years ago

aij commented 6 years ago

I would expect type intersections to be associative, like set intersections, but sometimes they are not. This came up in real code, using type aliases rather than parentheses.

The gist of it is is that

type Props = A & B & C;  // or (A & B) & C;

typechecks, while

type BC = B & C;
type Props = A & BC;  // or A & (B & C);

does not.

I was able to isolate the problematic code enough to reproduce in the sandbox, with v0.77.

I expect the example can be minimized a little further (particularly the part extracted from flow-typed), but I wanted to double check that this really is a bug before spending more time on it. Here is the same example from the sandbox for your convenience:

/* @flow */
import React, { Component } from 'react';
import type { ComponentType, ElementConfig } from 'react';

type Dispatch<A> = any;
type Store = Object;
// START CUT From flow-typed react-redux-v5.x.x.js

  declare type MapStateToProps<S: Object, SP: Object, RSP: Object> = (state: S, props: SP) => RSP;

  declare type MapDispatchToProps<A, OP: Object, RDP: Object> = (dispatch: Dispatch<A>, ownProps: OP) => RDP;
  declare type OmitDispatch<Component> = $Diff<Component, {dispatch: Dispatch<*>}>;

declare export function connect<
    Com: ComponentType<*>,
    S: Object,
    DP: Object,
    RSP: Object,
    CP: $Diff<OmitDispatch<ElementConfig<Com>>, RSP>
    >(
    mapStateToProps: MapStateToProps<S, DP, RSP>,
    mapDispatchToProps?: null
  ): (component: Com) => ComponentType<CP & DP>;

// END   CUT From flow-typed react-redux-v5.x.x.js

type Foo = { boo: boolean };

type A = { a: Foo };
type B = { b: string };
type C = { c: string };

// Both of these typecheck:
//type Props = A & B & C;
//type Props = (A & B) & C;

// But these do not:
type Props = A & (B & C);
//type BC = B & C; type Props = A & BC;

export const Container = connect(
  (a: A): A => a
)(
  (props: Props) => null
)
vicapow commented 5 years ago

I believe this is a more concise example of the issue:

// @flow

type A = {| foo: number | string |};
type B = {| foo: number | string |};
type C = {| foo: number | string |};

const d: $Diff<A, B> = {...null};
const e: $Diff<A & B, B> = {...null};
const f: $Diff<A & B, A & B> = {...null};
const g: $Diff<A & B, A & C> = {...null}; // should not error.

try flow