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

Document object type spread #3534

Closed
chanderson0 opened this issue Mar 17, 2017 · 14 comments
Closed

Document object type spread #3534

chanderson0 opened this issue Mar 17, 2017 · 14 comments
Assignees

Comments

@chanderson0
Copy link

Given:

type A = {
  a: number;
}
type B = {
  b: number;
  ...A;
}

I'd expect B to be equivalent to:

type B = {
  b: number;
  a: number;
}

But instead it's:

type B = {
  b: ?number;
  a: ?number;
}

Why? Is that the intended behavior?

Tested in v0.42.0 and master: https://flowtype.org/try/#0C4TwDgpgBAglC8UDeAoKUCGAuKA7ArgLYBGEATgNwoC+VokUAQgsmlMTgSeVegHQCYVamxQBjAPa4AzsCgAPHHERJMOAIxRa4qbKggczFWqj5cAEwgAzAJa4I5gDTscZy7fvmtVIA

@nmn nmn added the bug label Mar 17, 2017
@vkurchatkin
Copy link
Contributor

It seems to be more like { b: mixed } in lower bound position:

({b: 0 }: B); // works
({b: '' }: B); // also works

@vkurchatkin
Copy link
Contributor

vkurchatkin commented Mar 18, 2017

This seems to be the intended behaviour indeed. Basically, spread type is a type of result of object spread operation. Object spread only copies own properties, but object properties can also be inherited. Another thing to remember is that non-exact objects can have any properties other than defined.

So here are the rules:

type A = {
  a: number
}

type B = {
  b: number,
  ...A
}

// The same as

type B = {
  a?: number, // every property from spread becomes optional
  b: mixed // every property before non-exact spread becomes mixed
};
type A = {
  a: number
}
type B = {
  ...A,
  b: number
}

// The same as

type B = {
  a?: number,
  b:  number // properties after spread stay the same
};
type A = {
  a: number
}

type B = {
  b: number,
  ...$Exact<A>
}

// The same as

type B = {
  a: number,
  b:  number
};

$Exact fixes both problems, because exact objects are not allowed to have additional properties and implies that all properties are own.

@guigrpa
Copy link

guigrpa commented Mar 18, 2017

That's brilliant, thanks for this super useful addition to the language. I've been longing for this for quite some time. Shouldn't you write a blog post or something? This is really a killer feature.

@samwgoldman
Copy link
Member

Sorry, last week got away from me and I didn't have docs prepared for the release of 0.42. Working on it, though.

@samwgoldman samwgoldman self-assigned this Mar 22, 2017
@samwgoldman samwgoldman changed the title Object type spread creates optional values Document object type spread Mar 22, 2017
@guigrpa
Copy link

guigrpa commented Apr 14, 2017

@vkurchatkin Let me see if I understood correctly. In your example…

type A = {
  a: number
}
type B = {
  ...A,
  b: number
}

// The same as

type B = {
  a?: number,
  b:  number // properties after spread stay the same
};

…an object called foo may match A with an inherited property a. However, when that same object is spread (e.g. { ...foo, b: 3 }) that inherited property would not be copied over. Flow tries to mimic that behaviour by making a optional, is that right?

@vkurchatkin
Copy link
Contributor

@guigrpa yes, sounds right

@lewisf
Copy link

lewisf commented Apr 22, 2017

I have a follow up question that I think is related. I'm trying to use object type spread to increase the strictness of an object, but there's behavior that I don't understand.

Here are my types:

type Action = {
  type: string,
  payload?: any
}

type MyAction = {
  payload: any,
  ...$Exact<Action>
}

I'm hoping that by doing this, MyAction no longer has an optional payload property.

However, when I do this:

const y: Action = {
  type: 'ACTION'
};

(y: MyAction);

Why doesn't flow throw an error on y: MyAction.

Here's the flow.org/try link: https://flow.org/try/#0C4TwDgpgBAggxsAlgewHZQLxQN4CgpSiQBcUAzsAE6KoDmANPlGAIYgA2yLAJgPyktUIXAF9cuItACyIeEjSYcTVhy7cBQxgQB0ugCQBRAB4sEAHjkpUAPlFNcAegdQA8gGs2hABaIyUAG4s7IjcLMAQfix+AJJQEEaQCLhwaBRQRqQylgpYeASSpADkMADCACrRLgByhVrMbJw8pNgijCIA3OJOsKjcUIjAUHBRwJExcQkQSQAUGbAIVgCUnV3OAEYAroOIAGZQsVQghMhQ3CfAXtCUEP4QlGQQyamDIKTZ6LlMBVDF5ZU1ohW3Vi8USgwuvmO3koyAA7lBBHFKDDKFA1lMWBsHlAjmcIqhCoMvCxbj8VI1uIUEb1+oM8WQCcBcNNXlAsgs0MsgA

@vkurchatkin
Copy link
Contributor

@lewisf I agree, this looks like a bug

@rosskevin
Copy link

rosskevin commented Jun 2, 2017

EDIT: I just noticed the post right above about strictness ;). Heres perhaps another example that ultimately tries to use the lenient type (e.g. relay mutation)

We start with lenient base types, then start restricting them in our components. I could go into more details, but it may be too much context, so I'll let the example speak for itself:

type Lenient = {
  a?: number,
  c: string
}
type Restrictive = {
  ...$Exact<Lenient>,
  a: number,
}
  
function foo(bar: Lenient): string {
  return 'baz'
}

const r: Restrictive = { a: 1, c: 'foo' };

// Cast from Restrictive to Lenient fails
const l = (r: Lenient)

// Restrictive is incompatible with Lenient
foo(r)
17: const l = (r: Lenient)
               ^ object type. This type is incompatible with
17: const l = (r: Lenient)
                  ^ object type
20: foo(r)
        ^ object type. This type is incompatible with the expected param type of
10: function foo(bar: Lenient): string {
                      ^ object type

I'm struggling to see why foo() won't accept the more lenient type, and why I cannot cast from Restrictive to Lenient.

This is a big problem for us, what part of object type spread am I missing?

@rosskevin
Copy link

rosskevin commented Jun 2, 2017

Using $Supertype seems to workaround my issue...but I'd like someone to tell me for sure that it is doing what I expect. $Supertype is undocumented so I can't tell if this is as intended.

@rattrayalex-stripe
Copy link

@rosskevin I'm not sure if this is the Correct answer, but it works if you make the "lenient" field covariant, eg +a?: number instead of a?: number.

Note that const l = (r: Lenient) no longer fails; I'm not sure whether that is desired or not.

@IanLondon
Copy link

It seems to work differently with exact types:

// @flow
type A = {|
  a: number,
  aa: string
|}

type B = {|
  b: boolean,
  bb: Array<string>
|}

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

const ok: AB = {a: 1, aa: 'aaa', b: false, bb: ['ya', 'yaa']} // works


// uncomment for errors

// const extraField: AB = {a: 1, aa: 'aaa', b: false, bb: ['ya', 'yaa'], badField: 321}

// const missingField: AB = {a: 1, b: false, bb: ['ya', 'yaa']}

Try Flow

@villesau
Copy link
Contributor

villesau commented Apr 17, 2018

@vkurchatkin I think your explanation would make sense if we were talking about JS objects. But in this case we are spreading types right? As seen in the original post all the props are well defined so IMO it's clear that the spreading should work like explained in the original comment. There is no risk that there would leak some magical types that overrides some properties. The behaviour where something becomes optional or mixed ruins the idea of spreading and is very confusing. Am I missing something here?

Fixing by using exact does not sound like a solution to me. As far as I know, non-exact means that the object can contain some other values that the developer is not interested in (e.g they just flows trough the system without dev needing to know about them). But the types still can't contain anything else than what's defined so it should be very clear what has been spread and what has not been spread.

@gkz
Copy link
Member

gkz commented Mar 31, 2023

Object type spread is documented as of 4865dee, also this blog post: https://medium.com/flow-type/spreads-common-errors-fixes-9701012e9d58
additionally, you initial example no longer errors

@gkz gkz closed this as completed Mar 31, 2023
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