Skip to content

Commit

Permalink
Make explicit use of TypeScript's ReaonlySet and ReadonlyMap types. F…
Browse files Browse the repository at this point in the history
…ixes immerjs#494.

TypeScript provides the ReadonlySet and ReadonlyMap types which match
their normal counterparts but without any methods that mutate their data.
This change makes Immutable<Map> and Immutable<Set> map to those types
directly.

Worth noting that Map extends ReadonlyMap so we only need to check for
one to know what the resulting type is, the same goes for Set and
ReadonlySet.

This also removes the intermediate Set/Map types since TypeScript no
longer seems to need those (likely since TypeScript 3.7, see immerjs#448).
  • Loading branch information
Mossop committed Jan 3, 2020
1 parent b55ef46 commit 5e4a0e6
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 20 deletions.
14 changes: 14 additions & 0 deletions __tests__/draft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ test("draft.ts", () => {
assert(fromDraft(toDraft(weak)), weak)
}

// ReadonlyMap instance
{
let val: ReadonlyMap<any, any> = _
let draft: Map<any, any> = _
assert(toDraft(val), draft)
}

// Set instance
{
let val: Set<any> = _
Expand All @@ -199,6 +206,13 @@ test("draft.ts", () => {
assert(fromDraft(toDraft(weak)), weak)
}

// ReadonlySet instance
{
let val: ReadonlySet<any> = _
let draft: Set<any> = _
assert(toDraft(val), draft)
}

// Promise object
{
let val: Promise<any> = _
Expand Down
36 changes: 36 additions & 0 deletions __tests__/immutable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,41 @@ test("types are ok", () => {
assert(val, _ as {readonly a: {readonly b: string}})
}

// Map
{
let val = _ as Immutable<Map<string, string>>
assert(val, _ as ReadonlyMap<string, string>)
}

// Already immutable Map
{
let val = _ as Immutable<ReadonlyMap<string, string>>
assert(val, _ as ReadonlyMap<string, string>)
}

// object in Map
{
let val = _ as Immutable<Map<{a: string}, {b: string}>>
assert(val, _ as ReadonlyMap<{readonly a: string}, {readonly b: string}>)
}

// Set
{
let val = _ as Immutable<Set<string>>
assert(val, _ as ReadonlySet<string>)
}

// Already immutable Set
{
let val = _ as Immutable<ReadonlySet<string>>
assert(val, _ as ReadonlySet<string>)
}

// object in Set
{
let val = _ as Immutable<Set<{a: string}>>
assert(val, _ as ReadonlySet<{readonly a: string}>)
}

expect(true).toBe(true)
})
38 changes: 18 additions & 20 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,46 +21,44 @@ type Tail<T extends any[]> = ((...t: T) => any) extends ((
/** Object types that should never be mapped */
type AtomicObject =
| Function
| WeakMap<any, any>
| WeakSet<any>
| Promise<any>
| Date
| RegExp
| Boolean
| Number
| String

/**
* These should also never be mapped but must be tested after regular Map and
* Set
*/
type WeakReferences = WeakMap<any, any> | WeakSet<any>

export type Draft<T> = T extends AtomicObject
? T
: T extends Map<infer K, infer V>
? DraftMap<K, V>
: T extends Set<infer V>
? DraftSet<V>
: T extends ReadonlyMap<infer K, infer V> // Map extends ReadonlyMap
? Map<Draft<K>, Draft<V>>
: T extends ReadonlySet<infer V> // Set extends ReadonlySet
? Set<Draft<V>>
: T extends WeakReferences
? T
: T extends object
? {-readonly [K in keyof T]: Draft<T[K]>}
: T

// Inline these in ts 3.7
interface DraftMap<K, V> extends Map<Draft<K>, Draft<V>> {}

// Inline these in ts 3.7
interface DraftSet<V> extends Set<Draft<V>> {}

/** Convert a mutable type into a readonly type */
export type Immutable<T> = T extends AtomicObject
? T
: T extends Map<infer K, infer V> // Ideally, but wait for TS 3.7: ? Omit<ImmutableMap<K, V>, "set" | "delete" | "clear">
? ImmutableMap<K, V>
: T extends Set<infer V> // Ideally, but wait for TS 3.7: ? Omit<ImmutableSet<V>, "add" | "delete" | "clear">
? ImmutableSet<V>
: T extends ReadonlyMap<infer K, infer V> // Map extends ReadonlyMap
? ReadonlyMap<Immutable<K>, Immutable<V>>
: T extends ReadonlySet<infer V> // Set extends ReadonlySet
? ReadonlySet<Immutable<V>>
: T extends WeakReferences
? T
: T extends object
? {readonly [K in keyof T]: Immutable<T[K]>}
: T

interface ImmutableMap<K, V> extends Map<Immutable<K>, Immutable<V>> {}

interface ImmutableSet<V> extends Set<Immutable<V>> {}

export interface Patch {
op: "replace" | "remove" | "add"
path: (string | number)[]
Expand Down

0 comments on commit 5e4a0e6

Please sign in to comment.