-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Improve Typescript definition for Map #1841
Changes from all commits
47c78b4
0543432
b5d874a
8cf0983
f7c1f33
9ec983f
4d248af
720b847
c762557
3705794
82e957c
43f6590
72f60e9
b58b9d6
b0faf97
42170d6
790773a
3e160f8
a26d4ec
d112b0c
f225394
89b83a0
0d57333
d627ad3
0d5d6c8
ee1eca1
47be7ee
0e9da4d
ad0356b
beeac64
def6f09
c302576
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,8 +60,11 @@ describe('Map', () => { | |
const l = List([List(['a', 'A']), List(['b', 'B']), List(['c', 'C'])]); | ||
const m = Map(l); | ||
expect(m.size).toBe(3); | ||
// @ts-expect-error -- Not supported by typescript since 4.0.0 https://github.com/immutable-js/immutable-js/pull/1626 | ||
expect(m.get('a')).toBe('A'); | ||
// @ts-expect-error -- Not supported by typescript since 4.0.0 https://github.com/immutable-js/immutable-js/pull/1626 | ||
expect(m.get('b')).toBe('B'); | ||
// @ts-expect-error -- Not supported by typescript since 4.0.0 https://github.com/immutable-js/immutable-js/pull/1626 | ||
expect(m.get('c')).toBe('C'); | ||
}); | ||
|
||
|
@@ -97,6 +100,7 @@ describe('Map', () => { | |
it('accepts non-collection array-like objects as keyed collections', () => { | ||
const m = Map({ length: 3, 1: 'one' }); | ||
expect(m.get('length')).toBe(3); | ||
// @ts-expect-error -- type error, but the API is tolerante | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be a type error. Similarly, we should ensure that I filed an issue about this a while back and it was closed as "Working as intended" despite being clearly incorrect. 🤷 - microsoft/TypeScript#45170 (comment) A workaround type to fix this looks like: type ObjectKeys<T> = { [K in keyof T]: K extends number ? `${K}` : K }[keyof T] |
||
expect(m.get('1')).toBe('one'); | ||
expect(m.toJS()).toEqual({ length: 3, 1: 'one' }); | ||
}); | ||
|
@@ -424,7 +428,7 @@ describe('Map', () => { | |
}); | ||
|
||
it('chained mutations does not result in new empty map instance', () => { | ||
const v1 = Map({ x: 1 }); | ||
const v1 = Map<{ x?: number; y?: number }>({ x: 1 }); | ||
const v2 = v1.withMutations(v => v.set('y', 2).delete('x').delete('y')); | ||
expect(v2).toBe(Map()); | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,6 +102,11 @@ declare namespace Immutable { | |
{ | ||
[key in keyof R]: DeepCopy<R[key]>; | ||
} | ||
: T extends MapOf<infer R> | ||
? // convert MapOf to DeepCopy plain JS object | ||
{ | ||
[key in keyof R]: DeepCopy<R[key]>; | ||
} | ||
: T extends Collection.Keyed<infer KeyedKey, infer V> | ||
? // convert KeyedCollection to DeepCopy plain JS object | ||
{ | ||
|
@@ -829,9 +834,96 @@ declare namespace Immutable { | |
* not altered. | ||
*/ | ||
function Map<K, V>(collection?: Iterable<[K, V]>): Map<K, V>; | ||
function Map<R extends { [key in string | number | symbol]: unknown }>( | ||
obj: R | ||
): MapOf<R>; | ||
function Map<V>(obj: { [key: string]: V }): Map<string, V>; | ||
function Map<K extends string | symbol, V>(obj: { [P in K]?: V }): Map<K, V>; | ||
|
||
/** | ||
* Represent a Map constructed by an object | ||
* | ||
* @ignore | ||
*/ | ||
interface MapOf<R extends { [key in string | number | symbol]: unknown }> | ||
extends Map<keyof R, R[keyof R]> { | ||
/** | ||
* Returns the value associated with the provided key, or notSetValue if | ||
* the Collection does not contain this key. | ||
* | ||
* Note: it is possible a key may be associated with an `undefined` value, | ||
* so if `notSetValue` is not provided and this method returns `undefined`, | ||
* that does not guarantee the key was not found. | ||
*/ | ||
get<K extends keyof R>(key: K, notSetValue?: unknown): R[K]; | ||
get<NSV>(key: any, notSetValue: NSV): NSV; | ||
|
||
// https://github.com/microsoft/TypeScript/pull/39094 | ||
getIn<P extends ReadonlyArray<string | number | symbol>>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A nested structure may have keys that are not I think this needs to be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not know why, but if I do that, calling |
||
searchKeyPath: [...P], | ||
notSetValue?: unknown | ||
): RetrievePath<R, P>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Part of what has made typing |
||
|
||
set<K extends keyof R>(key: K, value: R[K]): this; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. similar to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @EduardoAraujoB if key is not in Map({}).set('a', 'a'); // output Map({ a: 'a' }) @leebyron As said on slack I prefered the solution of replicating TypeScript objects: you need to define the interface before: const o = { a: 'a' };
o.b = 'b'; // Property 'b' does not exist on type '{ a: string; }'. |
||
|
||
update(updater: (value: this) => this): this; | ||
update<K extends keyof R>(key: K, updater: (value: R[K]) => R[K]): this; | ||
update<K extends keyof R, NSV extends R[K]>( | ||
key: K, | ||
notSetValue: NSV, | ||
updater: (value: R[K]) => R[K] | ||
): this; | ||
|
||
// Possible best type is MapOf<Omit<R, K>> but Omit seems to broke other function calls | ||
// and generate recursion error with other methods (update, merge, etc.) until those functions are defined in MapOf | ||
delete<K extends keyof R>( | ||
key: K | ||
): Extract<R[K], undefined> extends never ? never : this; | ||
remove<K extends keyof R>( | ||
key: K | ||
): Extract<R[K], undefined> extends never ? never : this; | ||
|
||
toJS(): { [K in keyof R]: DeepCopy<R[K]> }; | ||
|
||
toJSON(): { [K in keyof R]: R[K] }; | ||
} | ||
|
||
// Loosely based off of this work. | ||
// https://github.com/immutable-js/immutable-js/issues/1462#issuecomment-584123268 | ||
|
||
/** @ignore */ | ||
type GetMapType<S> = S extends MapOf<infer T> ? T : S; | ||
|
||
/** @ignore */ | ||
type Head<T extends ReadonlyArray<any>> = T extends [ | ||
infer H, | ||
...Array<unknown> | ||
] | ||
? H | ||
: never; | ||
|
||
/** @ignore */ | ||
type Tail<T extends ReadonlyArray<any>> = T extends [unknown, ...infer I] | ||
? I | ||
: Array<never>; | ||
|
||
/** @ignore */ | ||
type RetrievePathReducer< | ||
T, | ||
C, | ||
L extends ReadonlyArray<any> | ||
> = C extends keyof GetMapType<T> | ||
? L extends [] | ||
? GetMapType<T>[C] | ||
: RetrievePathReducer<GetMapType<T>[C], Head<L>, Tail<L>> | ||
: never; | ||
|
||
/** @ignore */ | ||
type RetrievePath< | ||
R, | ||
P extends ReadonlyArray<string | number | symbol> | ||
> = P extends [] ? P : RetrievePathReducer<R, Head<P>, Tail<P>>; | ||
|
||
interface Map<K, V> extends Collection.Keyed<K, V> { | ||
/** | ||
* The number of entries in this Map. | ||
|
@@ -1049,16 +1141,17 @@ declare namespace Immutable { | |
*/ | ||
merge<KC, VC>( | ||
...collections: Array<Iterable<[KC, VC]>> | ||
): Map<K | KC, V | VC>; | ||
): Map<K | KC, Exclude<V, VC> | VC>; | ||
merge<C>( | ||
...collections: Array<{ [key: string]: C }> | ||
): Map<K | string, V | C>; | ||
): Map<K | string, Exclude<V, C> | C>; | ||
|
||
concat<KC, VC>( | ||
...collections: Array<Iterable<[KC, VC]>> | ||
): Map<K | KC, V | VC>; | ||
): Map<K | KC, Exclude<V, VC> | VC>; | ||
concat<C>( | ||
...collections: Array<{ [key: string]: C }> | ||
): Map<K | string, V | C>; | ||
): Map<K | string, Exclude<V, C> | C>; | ||
|
||
/** | ||
* Like `merge()`, `mergeWith()` returns a new Map resulting from merging | ||
|
@@ -1078,10 +1171,14 @@ declare namespace Immutable { | |
* | ||
* Note: `mergeWith` can be used in `withMutations`. | ||
*/ | ||
mergeWith( | ||
merger: (oldVal: V, newVal: V, key: K) => V, | ||
...collections: Array<Iterable<[K, V]> | { [key: string]: V }> | ||
): this; | ||
mergeWith<KC, VC, VCC>( | ||
merger: (oldVal: V, newVal: VC, key: K) => VCC, | ||
...collections: Array<Iterable<[KC, VC]>> | ||
): Map<K | KC, V | VC | VCC>; | ||
mergeWith<C, CC>( | ||
merger: (oldVal: V, newVal: C, key: string) => CC, | ||
...collections: Array<{ [key: string]: C }> | ||
): Map<K | string, V | C | CC>; | ||
|
||
/** | ||
* Like `merge()`, but when two compatible collections are encountered with | ||
|
@@ -1112,9 +1209,12 @@ declare namespace Immutable { | |
* | ||
* Note: `mergeDeep` can be used in `withMutations`. | ||
*/ | ||
mergeDeep( | ||
...collections: Array<Iterable<[K, V]> | { [key: string]: V }> | ||
): this; | ||
mergeDeep<KC, VC>( | ||
...collections: Array<Iterable<[KC, VC]>> | ||
): Map<K | KC, V | VC>; | ||
mergeDeep<C>( | ||
...collections: Array<{ [key: string]: C }> | ||
): Map<K | string, V | C>; | ||
|
||
/** | ||
* Like `mergeDeep()`, but when two non-collections or incompatible | ||
|
@@ -1582,15 +1682,32 @@ declare namespace Immutable { | |
*/ | ||
merge<KC, VC>( | ||
...collections: Array<Iterable<[KC, VC]>> | ||
): OrderedMap<K | KC, V | VC>; | ||
): OrderedMap<K | KC, Exclude<V, VC> | VC>; | ||
merge<C>( | ||
...collections: Array<{ [key: string]: C }> | ||
): OrderedMap<K | string, V | C>; | ||
): OrderedMap<K | string, Exclude<V, C> | C>; | ||
|
||
concat<KC, VC>( | ||
...collections: Array<Iterable<[KC, VC]>> | ||
): OrderedMap<K | KC, V | VC>; | ||
): OrderedMap<K | KC, Exclude<V, VC> | VC>; | ||
concat<C>( | ||
...collections: Array<{ [key: string]: C }> | ||
): OrderedMap<K | string, Exclude<V, C> | C>; | ||
|
||
mergeWith<KC, VC, VCC>( | ||
merger: (oldVal: V, newVal: VC, key: K) => VCC, | ||
...collections: Array<Iterable<[KC, VC]>> | ||
): OrderedMap<K | KC, V | VC | VCC>; | ||
mergeWith<C, CC>( | ||
merger: (oldVal: V, newVal: C, key: string) => CC, | ||
...collections: Array<{ [key: string]: C }> | ||
): OrderedMap<K | string, V | C | CC>; | ||
|
||
mergeDeep<KC, VC>( | ||
...collections: Array<Iterable<[KC, VC]>> | ||
): OrderedMap<K | KC, V | VC>; | ||
mergeDeep<C>( | ||
...collections: Array<{ [key: string]: C }> | ||
): OrderedMap<K | string, V | C>; | ||
|
||
// Sequence algorithms | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused - is this not currently failing? Why does this PR cause this to start producing errors?