Flow: Intersection of exact types

Created on 22 Sep 2017  路  13Comments  路  Source: facebook/flow

Is it a bug or "by design", that I can't intersect two exact types as follow:

type A = {| foo: string |};
type B = {| bar: number |};
type AB = A & B;

const ab: AB = { foo: 'test', bar: 123 };
5: const ab: AB = { foo: 'test', bar: 123 };
                  ^ property `bar`. Property not found in
3: type AB = A & B;
             ^ object type
5: const ab: AB = { foo: 'test', bar: 123 };
                  ^ property `foo`. Property not found in
3: type AB = A & B;
                 ^ object type

https://flow.org/try/#0PTAEAEDMBsHsHcBQiAuBPADgU1AQVALygDeAPqJLLAFygDOKATgJYB2A5qKQL4DcqmHACFCJcgCMAho1qsArgFtxWRlz4DseEUXwAyUEP6IAxrFYNQk8bVzaSFKrQDkKLAycAaUFJmgAjABMAMygfEA

The Documentation shows similar (in my mind) example (string & number), but my case is quite different - it makes exact types almost useless...

Most helpful comment

You can spread object types (undocumented as far as I can tell), but it makes your example pass type checking:

type AB = {| ...A, ...B |};

All 13 comments

You can spread object types (undocumented as far as I can tell), but it makes your example pass type checking:

type AB = {| ...A, ...B |};

indeed, but then I'm experiencing #2405... but thanks anyway for help!

Not quite an exact type, but you could potentially use:

type A = {| foo: string |}
type B = {| bar: number |}
type AB = { ...A, ...B, [any]: empty }

const ab: AB = { foo: 'test', bar: 123 } // passes
const ba: AB = { ...ab } // passes

const ea: AB = { foo: 'test', bar: 123, baz: 1 } // fails
const eb: AB = { ...ab, qux: 1 } // fails

looks nice, but seems to be hack again :( Wondering if this is a bug, or have I to write such way.

Weird thing is:

import React from 'react';

type A = {| foo: string |};
type B = {| bar: number |};
type AB = A & B;

class Foo extends React.Component<AB> {

    render() {
        const { foo, bar } = this.props;
        return (
            <div {...{ foo, bar }} />
        );
    }
}

No errors!

https://flow.org/try/#0PTAEAEDMBsHsHcBQiCWBbADrATgF1AEoCmAhgMb6TaxqgDk2pFdA3MrgJ4ZGgCCoAXlABvAD6hIsWAC5QAZ1zYUAOwDmoUQF82nbqABCgkeIBGJbLOUBXNCaLYN2xLp69DQ-gDIDbRGWgkcnKgAGJSoEQAHrhEygAmwcTkuAB0AMI0WMqxuAA8bgB8IsigpaCM8fYAFACUxWUNoGSwygoiElIANKBmDppGuAAWKHIpGNQYcmyNZYy4VtjKoFWIMzO5cSgAbiIpe8IdsN29oJr9wAWrazXTZZqImkA

You can't intersect exact types. Semantically, this would yield an empty type.

Sounds reasonable. But then, why props can be intersected even with exact types?

@vkurchatkin and what about exact utility type of intersection? Something like $Exact<OwnProps & StateProps & DispatcProps>.

If what you want is "an object type that has keys from two other object types", you want to use an object spread type rather than intersection. (Intersection has a very specific and non-intuitive meaning about being able to treat an object as either one type or another at the receiver's choice, as opposed to union, which is at the provider's choice. I think that isn't what you mean here.)

To represent an exact object types with the keys/value types of combining several other exact object types, I think what you want is:

{| ...OwnProps, ...StatePrope, ...DispatchProps |}

@asolove-stripe wow! Thanks. May you explain a little bit that part of intersections?

@rkurbatov just found this thread following another issue. To explain the part about intersections:

type A = {| foo: number |};
type B = { bar: string }'
type AB = A & B;

This means that an object of type AB should both be a valid A AND a valid B. But A is an exact type meaning that it cannot have any other properties than foo, meaning that an object satisfying the A type requirement can never satisfy the B type requirement, since it is not allowed to have the bar property.

This is the reason why intersection types doesn't work with exact types (unless the types are identical). The object spread syntax is what you want for creating a new type, with all the properties of both type A and B.

@ahem thank you. Much clearer now.

Explain the following por favor:

type A = {| foo: string |};
type B = { bar: number };
type AB = B & A;

declare var abc:AB;

// Doesn't work, obviously
abc.d = 3;

// Works... okay...
abc.foo = 'test';
abc.bar = 123;


// Doesn't work??
const ab: AB = { foo: 'test', bar: 123 };

ab and abc are basically identical. Why are they behaving differently? Is this a bug?

@knyzorg: the explanation for the difference is that declare var x: Type means "assume there is a variable x that already matches Type". And if there were an abc of type AB, it would be legal to set its foo and bar properties. But that's different from actually _constructing_ a value of that type, which is impossible.

The reason it's impossible is that no object can both have the key "bar" and yet only have the key "foo", which is what that intersection type is requiring.

Was this page helpful?
0 / 5 - 0 ratings