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