Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
gajus opened this issue Jun 26, 2018 · 8 comments

Comments

@gajus
Copy link

gajus commented Jun 26, 2018

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.

@gajus
Copy link
Author

gajus commented Jun 26, 2018

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?

@EdwardDrapkin
Copy link

EdwardDrapkin commented Jun 30, 2018

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.

@gajus
Copy link
Author

gajus commented Jun 30, 2018

ping @samwgoldman @panagosg7 @nmote

@gajus
Copy link
Author

gajus commented Aug 13, 2018

ping @gabelevi can this be marked as a bug?

@gabelevi
Copy link
Contributor

@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.

@gabelevi gabelevi added the bug label Aug 14, 2018
@gabelevi
Copy link
Contributor

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!

@SamChou19815
Copy link
Contributor

This now works

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants