Skip to content

Commit 48fcc8f

Browse files
committed
fix: improve higher order type inference for WritableDraft
1 parent 570c800 commit 48fcc8f

File tree

2 files changed

+51
-40
lines changed

2 files changed

+51
-40
lines changed

__tests__/produce.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,10 @@ describe("curried producer", () => {
200200
// With initial state:
201201
{
202202
type Recipe = (state?: State | undefined, ...rest: number[]) => State
203-
let bar = produce((state: Draft<State>, ...args: number[]) => {},
204-
_ as State)
203+
let bar = produce(
204+
(state: Draft<State>, ...args: number[]) => {},
205+
_ as State
206+
)
205207
assert(bar, _ as Recipe)
206208
bar(_ as State, 1, 2)
207209
bar(_ as State)
@@ -532,7 +534,7 @@ it("infers draft, #720 - 2", () => {
532534
function useState<S>(
533535
initialState: S | (() => S)
534536
): [S, Dispatch<SetStateAction<S>>] {
535-
return [initialState, function() {}] as any
537+
return [initialState, function () {}] as any
536538
}
537539
type Dispatch<A> = (value: A) => void
538540
type SetStateAction<S> = S | ((prevState: S) => S)
@@ -569,7 +571,7 @@ it("infers draft, #720 - 3", () => {
569571
function useState<S>(
570572
initialState: S | (() => S)
571573
): [S, Dispatch<SetStateAction<S>>] {
572-
return [initialState, function() {}] as any
574+
return [initialState, function () {}] as any
573575
}
574576
type Dispatch<A> = (value: A) => void
575577
type SetStateAction<S> = S | ((prevState: S) => S)
@@ -758,3 +760,15 @@ it("allows for mixed property value types", () => {
758760
}
759761
})
760762
})
763+
764+
it("allows higher order type inference", () => {
765+
function _test<A>() {
766+
type S = {prop: A | undefined}
767+
produce(
768+
draft => {
769+
draft.prop = undefined
770+
},
771+
{prop: undefined} as S
772+
)
773+
}
774+
})

src/types/types-external.ts

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,11 @@ type AtomicObject = Function | Promise<any> | Date | RegExp
1616
*/
1717
export type IfAvailable<T, Fallback = void> =
1818
// fallback if any
19-
true | false extends (T extends never
20-
? true
21-
: false)
19+
true | false extends (T extends never ? true : false)
2220
? Fallback // fallback if empty type
2321
: keyof T extends never
24-
? Fallback // original type
25-
: T
22+
? Fallback // original type
23+
: T
2624

2725
/**
2826
* These should also never be mapped but must be tested after regular Map and
@@ -37,7 +35,7 @@ export type WritableDraft<T> = T extends any[]
3735
: WritableNonArrayDraft<T>
3836

3937
type WritableNonArrayDraft<T> = {
40-
-readonly [K in keyof T]: T[K] extends infer V
38+
-readonly [K in keyof T]: {_: T[K]} extends {_: infer V}
4139
? V extends object
4240
? Draft<V>
4341
: V
@@ -48,31 +46,31 @@ type WritableNonArrayDraft<T> = {
4846
export type Draft<T> = T extends PrimitiveType
4947
? T
5048
: T extends AtomicObject
51-
? T
52-
: T extends ReadonlyMap<infer K, infer V> // Map extends ReadonlyMap
53-
? Map<Draft<K>, Draft<V>>
54-
: T extends ReadonlySet<infer V> // Set extends ReadonlySet
55-
? Set<Draft<V>>
56-
: T extends WeakReferences
57-
? T
58-
: T extends object
59-
? WritableDraft<T>
60-
: T
49+
? T
50+
: T extends ReadonlyMap<infer K, infer V> // Map extends ReadonlyMap
51+
? Map<Draft<K>, Draft<V>>
52+
: T extends ReadonlySet<infer V> // Set extends ReadonlySet
53+
? Set<Draft<V>>
54+
: T extends WeakReferences
55+
? T
56+
: T extends object
57+
? WritableDraft<T>
58+
: T
6159

6260
/** Convert a mutable type into a readonly type */
6361
export type Immutable<T> = T extends PrimitiveType
6462
? T
6563
: T extends AtomicObject
66-
? T
67-
: T extends ReadonlyMap<infer K, infer V> // Map extends ReadonlyMap
68-
? ReadonlyMap<Immutable<K>, Immutable<V>>
69-
: T extends ReadonlySet<infer V> // Set extends ReadonlySet
70-
? ReadonlySet<Immutable<V>>
71-
: T extends WeakReferences
72-
? T
73-
: T extends object
74-
? {readonly [K in keyof T]: Immutable<T[K]>}
75-
: T
64+
? T
65+
: T extends ReadonlyMap<infer K, infer V> // Map extends ReadonlyMap
66+
? ReadonlyMap<Immutable<K>, Immutable<V>>
67+
: T extends ReadonlySet<infer V> // Set extends ReadonlySet
68+
? ReadonlySet<Immutable<V>>
69+
: T extends WeakReferences
70+
? T
71+
: T extends object
72+
? {readonly [K in keyof T]: Immutable<T[K]>}
73+
: T
7674

7775
export interface Patch {
7876
op: "replace" | "remove" | "add"
@@ -117,7 +115,7 @@ type InferRecipeFromCurried<Curried> = Curried extends (
117115
? (
118116
draft: Draft<State>,
119117
...rest: Args
120-
) => ValidRecipeReturnType<Draft<State>>
118+
) => ValidRecipeReturnType<Draft<State>>
121119
: never
122120
: never
123121

@@ -136,7 +134,7 @@ type InferCurriedFromRecipe<
136134
? (
137135
base: Immutable<DraftState>,
138136
...args: RestArgs
139-
) => ReturnTypeWithPatchesIfNeeded<DraftState, UsePatches> // N.b. we return mutable draftstate, in case the recipe's first arg isn't read only, and that isn't expected as output either
137+
) => ReturnTypeWithPatchesIfNeeded<DraftState, UsePatches> // N.b. we return mutable draftstate, in case the recipe's first arg isn't read only, and that isn't expected as output either
140138
: never // incorrect return type
141139
: never // not a function
142140

@@ -151,7 +149,7 @@ type InferCurriedFromInitialStateAndRecipe<
151149
? (
152150
base?: State | undefined,
153151
...args: RestArgs
154-
) => ReturnTypeWithPatchesIfNeeded<State, UsePatches>
152+
) => ReturnTypeWithPatchesIfNeeded<State, UsePatches>
155153
: never // recipe doesn't match initial state
156154

157155
/**
@@ -181,10 +179,9 @@ export interface IProduce {
181179
): Curried
182180

183181
/** Curried producer that infers curried from the recipe */
184-
<Recipe extends AnyFunc>(recipe: Recipe): InferCurriedFromRecipe<
185-
Recipe,
186-
false
187-
>
182+
<Recipe extends AnyFunc>(
183+
recipe: Recipe
184+
): InferCurriedFromRecipe<Recipe, false>
188185

189186
/** Curried producer that infers curried from the State generic, which is explicitly passed in. */
190187
<State>(
@@ -200,9 +197,9 @@ export interface IProduce {
200197
) => ValidRecipeReturnType<State>,
201198
initialState: State
202199
): (state?: State, ...args: Args) => State
203-
<State>(recipe: (state: Draft<State>) => ValidRecipeReturnType<State>): (
204-
state: State
205-
) => State
200+
<State>(
201+
recipe: (state: Draft<State>) => ValidRecipeReturnType<State>
202+
): (state: State) => State
206203
<State, Args extends any[]>(
207204
recipe: (state: Draft<State>, ...args: Args) => ValidRecipeReturnType<State>
208205
): (state: State, ...args: Args) => State

0 commit comments

Comments
 (0)