Flow: Empty object not of exact type with optional properties

Created on 3 Sep 2016  路  17Comments  路  Source: facebook/flow

Minimal example:

type PossiblyEmpty = $Exact<{
  a?: number
}>

const p: PossiblyEmpty = {} // this errors. Should it?

https://flowtype.org/try/#0PQKgBAAgZgNg9gdzCYAoVAXAngBwKZgAKcAziQJYBGMWAogLY7ZgC8YAJLQB4CGAxhgA8Ab1RgwPAPwAuMADsArvUp4ATqgC+APnR84ckhjA5ZxMlRoMmWVmGEbUQA

Is this by design?

EDIT: inline an example

bug

Most helpful comment

@lgraziani2712 does this not provide your expected results? https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAoVAXAngBwKZgCiYAvGAN5iphgCGA-AFxgDOGATgJYB2A5gDTUqNYMDAB1OOwDWtdnACu3ACZgoUsHgAetAMYYw2fADoJU6SzqXt+fXmVDRYBAAs83MMry0YPXs84MFzA4ACMAKzx9SyDaA11aD1CCPABbHGxjIQBtRKwAXWY0jKwqAF90XThuNk1mYjJyMrAnbjhNdnl2Fkrq2qh60gowUMYARjBmpzxOqUtaay1bDHsgA

All 17 comments

Yeah, looks like a bug to me. Thanks for the minimal example!

For what it's worth, I am responsible for making this an error (in this commit). In general, unsealed things can't be exact, because other references to them can widen the type in unexpected ways.

In this case, there are no other references, so it doesn't really make sense to consider the object literal to be unsealed. I think the fix here is to _not_ create an unsealed object in this case. var o = {} should be unsealed, but var o: T = {} should not be.

Still getting the error. Is that something that will be fixed?

I would also like to see this fixed. Anytime you are using an options parameter it is helpful to allow {}

@samwgoldman It's still an issue!

Look at https://github.com/facebook/flow/issues/4582#issue-249772183 for a workaround

@lgraziani2712 Why the thumbs down? Does it not work correctly? Please share your experience.

The OP presents an object type with an optional attribute, while that workaround doesn't take in consideration those props. I tested myself with my object full of optionals attributes and it didn't work.

@lgraziani2712 does this not provide your expected results? https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAoVAXAngBwKZgCiYAvGAN5iphgCGA-AFxgDOGATgJYB2A5gDTUqNYMDAB1OOwDWtdnACu3ACZgoUsHgAetAMYYw2fADoJU6SzqXt+fXmVDRYBAAs83MMry0YPXs84MFzA4ACMAKzx9SyDaA11aD1CCPABbHGxjIQBtRKwAXWY0jKwqAF90XThuNk1mYjJyMrAnbjhNdnl2Fkrq2qh60gowUMYARjBmpzxOqUtaay1bDHsgA

Ok yep, it worked. I did it wrong: I kept the {||} exact pipes (Edit: when I tested before).

Thank you @chrisblossom

Here is another example.

type Breakdown = {| [string]: number |};
const breakdown: Breakdown = {};

=>

    2: const breakdown: Breakdown = {};
                                    ^ Cannot assign object literal to `breakdown` because inexact object literal [1] is incompatible with exact `Breakdown` [2].
        References:
        2: const breakdown: Breakdown = {};
                                        ^ [1]
        2: const breakdown: Breakdown = {};
                            ^ [2]

I think the explanation given in https://github.com/facebook/flow/issues/2386#issuecomment-244563036 is the right one. But it's been a while...

Here is a possible workaround until bug is not fixed:

/* @flow */

declare type Criteria = {| option1?: number, option2?: string |};

declare export function init(criteria: Criteria): void;

const test = init({ ...undefined });

Another workaround is $Shape:

/* @flow */

declare type Criteria = $Shape<{option1: number, option2: string}>;

declare export function init(criteria: Criteria): void;

init({}); // ok
init({option1: 3}) // ok
init({option1: 3, rogueProp: 'secret'}) // errors as expected

Another workaround, which to my mind is the cleanest of any I've seen here, is to just pass an explicit undefined. For many purposes the following is equivalent to an empty object:

({foo: undefined}: {|foo?: number, bar?: number|});

Recommend you add the label 'unsealed objects' to this and probably close #2977 as a duplicate of this so that progress/conversation can be tracked in one place.

7624 is also a duplicate

Another workaround for this issue is to use Object.freeze (playground):

({ x: 3 }: {| x: number |}); // ok

({}: {||}); // error

(Object.freeze({}): {||}); // ok (workaround)

I learned this from the thread at #2977; repeating it here in case it helps others, because this seems to be the canonical thread for this issue.

If you weren't planning to mutate the object, then this workaround is quite clean because it should have no effect at all on the behavior of the code that consumes the object. And in situations where you are planning to mutate the object, I think you typically want the "unsealed object" behavior anyway so this isn't an issue in the first place.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bennoleslie picture bennoleslie  路  3Comments

jamiebuilds picture jamiebuilds  路  3Comments

ghost picture ghost  路  3Comments

mjj2000 picture mjj2000  路  3Comments

doberkofler picture doberkofler  路  3Comments