Flow: Using spread operator to create a new type with possibly undefined property creates an object where either of the property values are possible

Created on 26 Jun 2018  路  7Comments  路  Source: facebook/flow

type UnnormalizedEventType = {|
  name?: 'foo'
|};

type EventType = {|
  ...UnnormalizedEventType,
  name?: 'bar'
|};

const event: EventType = ({}: any);

const test = (name?: 'bar') => {};

test(event.name);

https://flow.org/try/#0C4TwDgpgBAqgdnA9gJwLYEMA2BLAXhAEwFEA3COYAFXGgF4oBvAHwCgoo51UIB+ALigByAGaJEglkwC+AbhYtQkKKXJUaUeszZQAdHvhI0WPIRUVqkADTbO3fkIBG6ZBOlyWAY0RwAzsCgQZBQCZmpK9AAUDFIC6HAgAJTuXr7+wBB+GlARtrwCgk4uCRoAfIyy8ul+EYGqOrlJQA

Expectation: No error.

Actual:

14: test(event.name);
         ^ Cannot call `test` with `event.name` bound to `name` because string literal `foo` [1] is incompatible with string literal `bar` [2].
References:
2:   name?: 'foo'            ^ [1]
12: const test = (name?: 'bar') => {};
                         ^ [2]

My understanding is that EventType should become:

type EventType = {|
  name?: 'bar'
|};

Though instead it appears to be treated as:

type EventType = {|
  name?: 'foo' | 'bar'
|};

Is this a bug/ feature?

If this is a feature, then how do I spread-copy an object without creating union of both object property types and just keeping the latest.

destructors spread bug

Most helpful comment

Yep! That's exactly what's happening...to make the implementation simpler we were turning the inline properties into another spread. But we totally could make the inline properties always override. I'll mark this as a bug!

All 7 comments

It does work as expected when the properties are not optional, e.g.

https://flow.org/try/#0C4TwDgpgBAqgdnA9gJwLYEMA2BLAXhAEwFEA3COYAFXGgF4oBvAHwCgoo51UIAuKAcgBmiRPxZMAvgG4WLUJCilyVGlHrM2UAHQ74SNFjyElFapAA0mztz78ARumRjJMlgGNEcAM7AoEMhR8JioK9AAUDBJ86HAgAJSuHt6+wBA+alBh1rwCDk5xagB8jNKyqT5h-spa2QlAA

type UnnormalizedEventType = {|
  name: 'foo'
|};

type EventType = {|
  ...UnnormalizedEventType,
  name: 'bar'
|};

const event: EventType = ({}: any);

const test = (name: 'bar') => {};

test(event.name);

How to make it work with optional properties?

You can workaround this with type WorkingSpread<A,B> = $Rest<A, B> & B; but I would guess that {...A, ...B} should resolve to the same object because they would with the ES spread operator, but they don't. Which seems like a bug.

ping @samwgoldman @panagosg7 @nmote

ping @gabelevi can this be marked as a bug?

@gajus - I think the idea here is for object type spread to mimic object spread. So you in theory should be able to type something like

function foo(objA: typeA, objB: typeB): { ...typeA, ...typeB} {
  return { ...objA, ...objB }
}

My guess is that the object spread in your example kind of works like

type UnnormalizedEventType = {|
  name?: 'foo'
|};

type EventType = {|
  ...UnnormalizedEventType,
  ...{|name?: 'bar'|},
|};

And since name is optional, Flow doesn't know whether it is present.

But yeah, as you wrote it we probably could just override the previous value. Let me ask @samwgoldman if I'm missing some edge case.

Yep! That's exactly what's happening...to make the implementation simpler we were turning the inline properties into another spread. But we totally could make the inline properties always override. I'll mark this as a bug!

@gabelevi Keep hitting this issue over and over. :-(

https://flow.org/try/#0KYDwDg9gTgLgBDAnmYcDKwCGMCWA7AcwEEosAVZVAXjgG8AfAKDjgGoAzAVxwBMAuOAGcYUfAQA0zNnkwBbYAOGjCklq0FZchAJKCiAG30QAxtmD84AIwgR9WPI3oBfANyNGoSLASU4AVTw8aFlMfRwAL3MMbDEScl8aBikObgslMTh6ODxOWUtgKFVpOQUhETEi9U0xXQMjUxhzAWtbe0dXd2MIPGFs4NCI4GitYlJMOBoACk5A-rDInmHYsYEAoKgQ+ajqwjjMChQASgEl3bGD6gA+OilSGE4oPBuWFgA6d5n1zcHFndGsIosLi8AQAcnYNlBUg6riAA

@samwgoldman any news on this?

The only quasi-useful solution I have found is to create a custom utility as such:

https://flow.org/try/#0KYDwDg9gTgLgBDAnmYcDKwCGMCWA7AcwEEosAVZVAXjgG8AfAKDjgGoAzAVxwBMAuOAGcYUfAQA0zNnkwBbYAOGjCklq0FZchAJKCiAG30QAxtmD84AIwgR9WPI3oBfANyNGoSLASU4AVTw8aFlMfRwAL3MMbDEScl8aBikObgslMTh6ODxOWUtgKFVpOQUhETEi9U0xXQMjUxhzAWtbe0dXd2MIPGE4CFkcGAAxVLgaAApBTksAK2BjGABKMYA+OikunvhaKRYAej24YEEwvBgAWh4cQUxLO3O8UAvT1CDzzhyNHnOAN0woQS7OBcXgCRqyMBFFgAOlhpCm+hgUicYyE0zmCzcUlIME4UDwcHhnERbg6jE2vSCUBCYUi0S0xFImFR4w+VJpESi1UIcUwAgC7NCnJ49NiTIoKEWAlFPPFCTWOxYOLxBMVLDgsOh-UGI14rMCwSFkRF3MZWEWUOBqQEAHJ2DYbcjSS4gA

Was this page helpful?
0 / 5 - 0 ratings