8000 Fix flowtypes for OrderedSet, OrderedMap, toOrderedSet, and toOrderedMap by howardjing · Pull Request #1027 · immutable-js/immutable-js · GitHub 8000
[go: up one dir, main page]

Skip to content

Conversation

@howardjing
Copy link
Contributor
@howardjing howardjing commented Dec 20, 2016

Unfortunately extending Set and Map caused issues with OrderedSet
and OrderedMap flow annotations. For example,

const orderedSet: OrderedSet<number> = OrderedSet.of(1,2,3);

was not valid, because of was incorrectly assumed to return a
Set<number> rather than an OrderedSet<number>.

The fix was to copy and paste the Set and Map flow annotations,
adjusting for OrderedSet and OrderedMap as appropriate.

See #1015 for more details.


Add negative flow test cases for OrderedSet and OrderedMap

One thing that came up as a result of this is that it seems
like the current flow definitions are laxer than typescript
definitions. For example, typescript's intersect looks like

intersect(...iterables: Array<T>[]): Set<T>

whereas flow's intersect looks like

intersect<U>(...iterables: ESIterable<U>[]): Set<T&U>

Also removed unnecessary OrderedSet overrides such as

  • delete
  • remove
  • clear

since we were already extending Set.

Also removed unused variables in immutable-flow tests.

rvikmanis and others added 2 commits December 20, 2016 10:36
Unfortunately extending Set and Map caused issues with OrderedSet
and OrderedMap flow annotations. For example,

```
const orderedSet: OrderedSet<number> = OrderedSet.of(1,2,3);
```

was not valid, because `of` was incorrectly assumed to return a
`Set<number>` rather than an `OrderedSet<number>`.

The fix was to copy and paste the Set and Map flow annotations,
adjusting for OrderedSet and OrderedMap as appropriate.

See immutable-js#1015 for more details.
@howardjing
Copy link
Contributor Author

If there's a way to fix this without copy pasting flow annotations for Map / Set, I'm happy to update this PR as necessary. I made this PR because with immutable 3.8.1 I couldn't use list.toOrderedSet with flow.

@howardjing howardjing changed the title Fix flowtypes Fix flowtypes for OrderedSet, OrderedMap, toOrderedSet, and toOrderedMap Dec 20, 2016
@howardjing howardjing mentioned this pull request Dec 21, 2016

// OrderedMaps have nothing that Maps do not have. We do not need to override constructor & other statics
declare class OrderedMap<K,V> extends Map<K,V> {
declare class OrderedMap<K,V> extends KeyedCollection<K,V> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did you decide to extend KeyedCollection? OrderedMap extends Map.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, didn't realize that. I extendedKeyedCollection because Immutable.Map.isMap is a function whereas Immutable.OrderedMap.isMap doesn't seem to be a function:

image

I'll make the change for this and OrderedSet.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Turns out I'm getting an issue overriding update by extending Map.

image

I'll leave it as extending KeyedCollection for the purposes of this PR. Having OrderedSet extend Set worked without issues (I didn't realize I was already doing that), and I'll update the PR to remove extraneous OrderedSet definitions.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's because of this in the update definition. Does it happen if you replace this on line 574 with OrderedMap<K,V> and with `Map<K,V> on line 580?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately the updater function is still problematic. I can work around it by changing the update signature from

update<K_,V_>(updater: (value: Map<K,V>) => Map<K_,V_>): Map<K_,V_>;

to

update<K_,V_>(updater: (value: any) => any): Map<K_,V_>;

but I'm not sure how else to fix it. The error being generated is as follows:

inconsistent use of library definitions
571:     update<K_,V_>(updater: (value: OrderedMap<K,V>) => OrderedMap<K_,V_>): OrderedMap<K_,V_>;
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ intersection. This type is incompatible with
500:     update<K_,V_>(updater: (value: Map<K,V>) => Map<K_,V_>): Map<K_,V_>;
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ polymorphic type: function type
  Member 1:
  571:     update<K_,V_>(updater: (value: OrderedMap<K,V>) => OrderedMap<K_,V_>): OrderedMap<K_,V_>;
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ polymorphic type: function type
  Error:
  inconsistent use of library definitions
  500:     update<K_,V_>(updater: (value: Map<K,V>) => Map<K_,V_>): Map<K_,V_>;
                                                       ^^^^^^^^^^ Map. This type is incompatible with
  571:     update<K_,V_>(updater: (value: OrderedMap<K,V>) => OrderedMap<K_,V_>): OrderedMap<K_,V_>;
                                                              ^^^^^^^^^^^^^^^^^ OrderedMap
  Member 2:
  572:     update<V_>(key: K, updater: (value: V) => V_): OrderedMap<K,V|V_>;
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ polymorphic type: function type
  Error:
  inconsistent use of library definitions
  500:     update<K_,V_>(updater: (value: Map<K,V>) => Map<K_,V_>): Map<K_,V_>;
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ undefined (too few arguments, expected default/rest parameters). This type is incompatible with
  572:     update<V_>(key: K, updater: (value: V) => V_): OrderedMap<K,V|V_>;
                                       ^^^^^^^^^^^^^^^^ function type
  Member 3:
  573:     update<V_>(key: K, notSetValue: V_, updater: (value: V) => V_): OrderedMap<K,V|V_>;
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ polymorphic type: function type
  Error:
  inconsistent use of library definitions
  500:     update<K_,V_>(updater: (value: Map<K,V>) => Map<K_,V_>): Map<K_,V_>;
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ undefined (too few arguments, expected default/rest parameters). This type is incompatible with
  573:     update<V_>(key: K, notSetValue: V_, updater: (value: V) => V_): OrderedMap<K,V|V_>;
                                                        ^^^^^^^^^^^^^^^^ function type

Copy link
Contributor

Choose a reason for hiding this comment

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

Extending KeyedCollection is better IMO.

orderedStringToNumber = OrderedMap({'a': 1}).mapKeys(x => x)
orderedStringToNumber = OrderedMap({'a': 1}).flatten()
orderedStringToNumber = OrderedMap({'a': 1}).flatten(1)
orderedStringToNumber = OrderedMap({'a': 1}).flatten(true)
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the tests! It would be great if there were tests for expected errors, too. You can do it with // $ExpectError. See here.

@wokalski
Copy link
Contributor
wokalski commented Dec 21, 2016

Good job. I think it's good to merge when there are tests for false positives ($ExpectError)

@howardjing
Copy link
Contributor Author

I'm currently working on false positives, will update this PR tonight or tomorrow. I've found a few edge cases that I'll add TODOs / FIXMEs with explanations.

One thing that came up as a result of this is that it seems
like the current flow definitions are laxer than typescript
definitions. For example, typescript's intersect looks like

```
intersect(...iterables: Array<T>[]): Set<T>
```

whereas flow's intersect looks like

```
intersect<U>(...iterables: ESIterable<U>[]): Set<T&U>
```

Also removed unnecessary OrderedSet overrides such as

  * delete
  * remove
  * clear

since we were already extending Set.

Also removed unused variables in immutable-flow tests.
@howardjing
Copy link
Contributor Author

Updated PR with negative test cases, and removed some unnecessary variables / overrides.

@howardjing
Copy link
Contributor Author

The flow definitions seems to be a little laxer than the typescript definitions, I'm not sure if this is by design, so I left it alone.

@wokalski
Copy link
Contributor

Out of curiosity - what do you exactly mean by laxer?

They are verbose, it's true. I don't have any experience with TS so I cannot compare. immutable is a bit special case because of generics and inheritance hierarchy.

Copy link
Contributor
@wokalski wokalski left a comment

Choose a reason for hiding this comment

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

Thanks for the tests. They are very detailed. I love that. I think that it would be great if the issues with flow definitions landed in a collective issue. Maybe some of them are more controversial so they probably deserve their own issue.

Asking @lacker for an opinion since I'm just helping here

static isOrderedSet(maybeOrderedSet: any): bool;

add<U>(value: U): OrderedSet<T|U>;
delete(value: T): this;
Copy link
Contributor

Choose a reason for hiding this comment

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

nice!

// Some tests look like they are repeated in order to avoid false positives.
// Flow might not complain about an instance of (what it thinks is) T to be assigned to T<K, V>

import Immutable, {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is it deleted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Immutable wasn't actually being used, so I got rid of it.

Copy link
Contributor

Choose a reason for hiding this comment

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

It tested valid imports. I know it might be confusing though. I should've put a comment there.

Copy link
Contributor Author
@howardjing howardjing Dec 23, 2016

Choose a reason for hiding this comment

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

Interesting -- can you elaborate on that? I'll switch back to

import Immutable, {
  // ...
} from 'immutable';

but I'm not clear as to what importingImmutable is testing. Should I add back

import * as Immutable2 from 'immutable'

const ImmutableList = Immutable.List
const ImmutableMap = Immutable.Map
const ImmutableStack = Immutable.Stack
const ImmutableSet = Immutable.Set
const ImmutableKeyedIterable = Immutable.KeyedIterable
const ImmutableRange = Immutable.Range
const ImmutableRepeat = Immutable.Repeat
const ImmutableIndexedSeq = Immutable.IndexedSeq

const Immutable2List = Immutable2.List
const Immutable2Map = Immutable2.Map
const Immutable2Stack = Immutable2.Stack
const Immutable2Set = Immutable2.Set
const Immutable2KeyedIterable = Immutable2.KeyedIterable
const Immutable2Range = Immutable2.Range
const Immutable2Repeat = Immutable2.Repeat
const Immutable2IndexedSeq = Immutable2.IndexedSeq

as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

I made it - I know it's confusing. It tests if (1) the default import (2) the import * as X from 'immutable' (3) individual imports are supported.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cool, I'll add that back and include a comment as to what it's testing. Thanks for the clarification.

// $ExpectError
stringToNumber = Map([['a', 'b']])
// FIXME: this should trigger an error -- this is actually a Map<string, string>
stringToNumber = Map(List.of(List(['a', 'b'])))
Copy link
Contributor

Choose a reason for hiding this comment

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

Great find! Would you mind adding an issue for it? FIXMEs get lost pretty easily.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I'll create an issue with a list of flowtype wonkiness I've found.

* ...iterables: Iterable<K, V>[]
* ```
*/
stringToNumber = Map({'a': 1}).mergeWith((previous, next, key) => 1, [1])
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why it should throw an error :(.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

iterable should look like a map rather than an array -- the following error is thrown when I run the snippet in the javascript console:

image

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah! Gotcha.

* This seems inconsistent with the typescript signature of
*
* ```
* update(updater: (value: Map<K, V>) => Map<K, V>): Map<K, V>
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks, great find again. Would you mind adding an issue (again)?

): OrderedMap<K|K_,V|V_>;

mergeWith<K_,W,X>(
merger: (previous: V, next: W, key: number) => X,
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't key be of type K here? And K_ in the output?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that makes sense to me -- this was copy pasted from 2363 Map so this issue exists there as well. The other thing to note is that iterables should probably be ESIterable<K_,W> rather than ESIterable<W>[].

@howardjing
Copy link
Contributor Author
howardjing commented Dec 23, 2016

To clarify on

The flow definitions seem to be a little laxer than the typescript definitions

I meant that methods that make persistent changes to the data structure in flow are allowed to introduce new types, whereas they are not allowed to introduce new types in typescript. For example, this is the Set#add method in flow:

class Set<T> {
  add<U>(value: U): Set<T|U>;
}

and this is the Set#add method in typescript:

interface Set<T> {
  add(value: T): Set<T>;
}

So we can do the following in flow

Set([1]).add('a');

but not in typescript. This behavior is also allowed in javascript for what it's worth. This seems like an inconsistency that I thought I'd bring up, and probably belongs in a separate issue.

@howardjing
Copy link
Contributor Author

I made a gigantic issue here: #1029, let me know if you want me to split it up into mini issues.

@wokalski
Copy link
Contributor

Great job. The only remaining issue are the mergeWith methods. Do you want to fix them here or should I or you create an issue for it? Other than that - thanks for helping all of us with this contribution.

@howardjing
Copy link
Contributor Author

The mergeWith methods are included in #1029, but I'm working on that now and will update this PR with those changes.

There were some issues with the definitions of Map and OrderedMap's
flowtype signatures for mergeWith and mergeDeepWith.

1. the merger function's `key` was incorrectly specified as being of type number
2. ...iterables was incorrectly specified to be array like rather than object like
@howardjing
Copy link
Contributor Author

Updated with commits to add back the Import tests, and fix mergeWith and mergeDeepWith type signatures for Maps and OrderedMaps. I'm currently working on fixing updateIn for Maps / OrderedMaps in a separate commit.

The `updater` argument was specified as `any` rather than

```
(value: any) => any
```
@howardjing
Copy link
Contributor Author

Updated with fix to updateIn

@wokalski
Copy link
Contributor

@lacker I think it's good to merge.
@howardjing great job. Thanks for being so precise. Sorry for the nitpicking!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

0