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

Fix for empty models / models with all properties set to optional being able to take any value in TypeScript #1269

Merged
merged 11 commits into from
Oct 28, 2019

Conversation

xaviergonz
Copy link
Contributor

@xaviergonz xaviergonz commented Apr 29, 2019

Apparently, in TS this is valid

const x: {} = 5
const x: {} = [5]

which means that for empty objects / objects where all properties were optional any value was valid

This PR fixes this by turning empty objects into an object like

{
  [some unique symbol]?: undefined
}

which doesn't have any properties in common with array, classes, etc.

Also updates some dev deps and fixes some typos

This was referenced Apr 29, 2019
Closed
@coveralls
Copy link

coveralls commented Apr 29, 2019

Coverage Status

Coverage remained the same at 92.465% when pulling 6b966be on fix-1268 into 8ef9c35 on master.

/** @hidden */
export type ExtractCFromProps<P extends ModelProperties> = { [k in keyof P]: P[k]["CreationType"] }

/** @hidden */
export type ModelCreationType<PC> = { [P in DefinablePropsNames<PC>]: PC[P] } & Partial<PC>
export type KeylessToEmptyObject<O> = keyof O extends never ? EmptyObject : O
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keyof O extends never 💯

@xaviergonz
Copy link
Contributor Author

While it works I'm going to mark it as WIP since merging this would make the 50 iterations error from TS3.4 appear again :/

Hopefully this new feature from TS (microsoft/TypeScript#30637) means what I think it means and then it might even resolve by itself.

Anyway it only exhibits in two cases

  1. when the model has no props the creation/snapshot/instance types accept anything
  2. when all model props are optional then creation accepts arrays and numbers, but not wrong objects, while the other types seem ok

@xaviergonz xaviergonz changed the title Fix for empty models / models with all properties set to optional being able to take any value in TypeScript Fix for empty models / models with all properties set to optional being able to take any value in TypeScript [WIP] Apr 30, 2019
@xaviergonz
Copy link
Contributor Author

Ok, i just checked with the next TSC and it doesn't appear to mean what I think :-/ (unless it wasn't still there in the nightly build)

@mweststrate
Copy link
Member

mweststrate commented Apr 30, 2019 via email

@xaviergonz
Copy link
Contributor Author

xaviergonz commented May 1, 2019

managed to reduce the number of iterations to 46, but still too high I think.

Maybe it would be better to change types.model typings so it cannot take an empty object as props?
The problem would still be there when all props are optional though

@mweststrate
Copy link
Member

Maybe it would be better to change types.model typings so it cannot take an empty object as props?

Sounds as a good idea anyway

The problem would still be there when all props are optional though

That is actually quite a common case I presume 😢

@mweststrate
Copy link
Member

I'm not sure if the issue is big enough to warrant the additional complexity in the typings. Ok to close this one?

@xaviergonz
Copy link
Contributor Author

I'd like to try some ideas I had to reduce the number of types nesting before giving up on this one.

@mweststrate
Copy link
Member

mweststrate commented Jul 19, 2019 via email

@danielkcz
Copy link
Contributor

I still haven't got to proper upgrading to 3.5 due to #1341, but I am expecting this is gonna bite me too. I do have a couple of models with all props optional. I wonder what's wrong with the PR? Does it work with that? Doesn't seem to be overly complicated, so I am not sure why to abandon it.

@xaviergonz
Copy link
Contributor Author

xaviergonz commented Jul 19, 2019

The problem has to do with a limit (added in TS 3.4 I think?) on how deep conditional types can go, which is 50 levels deep. Some medium-big projects where reaching that limit and that got fixed, but this PR adds some more levels and makes it go over 50 again, thus making these medium-big projects un-compilable.

I was just thinking of trying to leverage interfaces as "cached" types to reduce nesting, something like

interface A<X> {
  $whatever: X extends Foo ? Bar : Whatever // imagine some super complex conditional type here
}

types Ble<X> = X extends A<X> ? X["$whatever"] : never

rather than directly

types Ble<X> = X extends Foo ? Bar : Whatever

something like that (although it is an oversimplification), but I still have to check if that actually reduces nesting

@xaviergonz
Copy link
Contributor Author

@mweststrate Ok I found a better way to fix it and not increase the depth levels at all, basically by always '&' a non empty object like { [someSymbol]?: any }

Also while at it updated the dev deps, which includes a nice update to the typedocs markdown plugin, which should result in a bit better looking autogenerated docs

@xaviergonz xaviergonz changed the title Fix for empty models / models with all properties set to optional being able to take any value in TypeScript [WIP] Fix for empty models / models with all properties set to optional being able to take any value in TypeScript Jul 22, 2019
Copy link
Member

@k-g-a k-g-a left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@danielkcz
Copy link
Contributor

@xaviergonz We got some approvals here. I suppose you can resolve conflicts and merge? :)

@xaviergonz
Copy link
Contributor Author

Should be fixed.
While at it also update deps and fixed flow typings for TS3.6, but this means that now flows require TS3.6.

@xaviergonz
Copy link
Contributor Author

@mweststrate since the fix for flow typings require ts3.6, is it ok to merge?

@mweststrate
Copy link
Member

I think it is OK if it is mentioned in the changelog, and at least a minor release

@xaviergonz xaviergonz merged commit b626061 into master Oct 28, 2019
@danielkcz danielkcz deleted the fix-1268 branch October 28, 2019 21:16
mbest added a commit to mbest/mobx-state-tree that referenced this pull request Apr 14, 2020
Since mobxjs#1269 marked `castFlowReturn` as deprecated, it shouldn't be mentioned as needed in the docs.
thegedge added a commit to thegedge/mobx-state-tree that referenced this pull request Feb 25, 2024
This was introduced in mobxjs#1269 to
prevent passing in any object to a model type that had no props.

Although this works great, some people have found it harder to work with because
now `keyof typeof MyModelType` now has this extra key they have to deal with.
One could intersect that type with `string`, but it's unfortunate that users
have to do this.

Instead, we use `Record<string, never>` which can only be represented by an
empty object literal. Snapshot preprocessors also had to be tweaked because they
can emit properties that aren't known to them in the case of composition with
other types.
thegedge added a commit to thegedge/mobx-state-tree that referenced this pull request Feb 25, 2024
This was introduced in mobxjs#1269 to
prevent passing in any object to a model type that had no props.

Although this works great, some people have found it harder to work with because
now `keyof typeof MyModelType` now has this extra key they have to deal with.
One could intersect that type with `string`, but it's unfortunate that users
have to do this.

Instead, we use `Record<string, never>` which can only be represented by an
empty object literal. Snapshot preprocessors also had to be tweaked because they
can emit properties that aren't known to them in the case of composition with
other types.
coolsoftwaretyler pushed a commit that referenced this pull request Feb 26, 2024
* Eliminate `NonEmptyObject`

This was introduced in #1269 to
prevent passing in any object to a model type that had no props.

Although this works great, some people have found it harder to work with because
now `keyof typeof MyModelType` now has this extra key they have to deal with.
One could intersect that type with `string`, but it's unfortunate that users
have to do this.

Instead, we use `Record<string, never>` which can only be represented by an
empty object literal. Snapshot preprocessors also had to be tweaked because they
can emit properties that aren't known to them in the case of composition with
other types.

* Don't allow symbols in input snapshots for models with no props
coolsoftwaretyler pushed a commit that referenced this pull request Feb 26, 2024
* Eliminate `NonEmptyObject`

This was introduced in #1269 to
prevent passing in any object to a model type that had no props.

Although this works great, some people have found it harder to work with because
now `keyof typeof MyModelType` now has this extra key they have to deal with.
One could intersect that type with `string`, but it's unfortunate that users
have to do this.

Instead, we use `Record<string, never>` which can only be represented by an
empty object literal. Snapshot preprocessors also had to be tweaked because they
can emit properties that aren't known to them in the case of composition with
other types.

* Don't allow symbols in input snapshots for models with no props
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants