Skip to content

Commit b357e54

Browse files
committed
fix(ts): base type of curried producer
Previously, a curried producer could not be passed a readonly version of its expected base type. For example, a curried producer that expects its draft argument to be "any[]" could not be passed a "ReadonlyArray<any>" value. Related test: f94fcaa
1 parent f94fcaa commit b357e54

File tree

2 files changed

+33
-17
lines changed

2 files changed

+33
-17
lines changed

__tests__/produce.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import produce, {
22
produce as produce2,
33
applyPatches,
44
Patch,
5-
nothing
5+
nothing,
6+
Draft
67
} from "../dist/immer.js"
78

89
// prettier-ignore
@@ -67,27 +68,28 @@ it("can update readonly state via standard api", () => {
6768

6869
// NOTE: only when the function type is inferred
6970
it("can infer state type from default state", () => {
70-
type Producer = (base: number | undefined) => number
71-
72-
let foo: Producer = {} as any
73-
exactType(produce(_ => {}, 1), foo)
74-
exactType(foo(2), {} as number)
71+
type Producer = <T>(
72+
base: (T extends number ? T : number) | undefined
73+
) => number
74+
let foo = produce(_ => {}, 1)
75+
exactType(foo, {} as Producer)
76+
exactType(foo(2), 0 as number)
7577
})
7678

7779
it("can infer state type from recipe function", () => {
78-
type Producer = (
79-
base: string | number | undefined,
80+
type Base = string | number
81+
type Producer = <T>(
82+
base: (T extends Base ? T : Base) | undefined,
8083
_2: number
81-
) => string | number
84+
) => Base
8285

83-
let foo: Producer = {} as any
84-
let recipe = (_: string | number, _2: number) => {}
85-
exactType(produce(recipe, 1), foo)
86+
let foo = produce((_: string | number, _2: number) => {}, 1)
87+
exactType(foo, {} as Producer)
8688
exactType(foo("", 0), {} as string | number)
8789
})
8890

8991
it("cannot infer state type when the function type and default state are missing", () => {
90-
exactType(produce(_ => {}), {} as (base: any) => any)
92+
exactType(produce(_ => {}), {} as <T>(base: T) => any)
9193
})
9294

9395
it("can update readonly state via curried api", () => {
@@ -138,13 +140,21 @@ it("can apply patches", () => {
138140
})
139141

140142
it("can provide rest parameters to a curried producer", () => {
141-
type Foo = (base: object, _2: number, _3: number) => object
143+
type Foo = <T>(
144+
base: Draft<T> extends {} ? T : object,
145+
_2: number,
146+
_3: number
147+
) => object
142148
let foo = produce((_1: object, _2: number, _3: number) => {})
143149
exactType(foo, {} as Foo)
144150
foo({}, 1, 2)
145151

146152
// With initial state:
147-
type Bar = (base: object | undefined, _2: number, _3: number) => object
153+
type Bar = <T>(
154+
base: (Draft<T> extends {} ? T : object) | undefined,
155+
_2: number,
156+
_3: number
157+
) => object
148158
let bar = produce((_1: object, _2: number, _3: number) => {}, {})
149159
exactType(bar, {} as Bar)
150160
bar(undefined, 1, 2)

src/immer.d.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,18 @@ export interface IProduce {
9696
...rest: Rest
9797
) => Return,
9898
defaultBase: Default
99-
): (base: Base | undefined, ...rest: Rest) => Produced<Base, Return>
99+
): <T>(
100+
base: (Draft<T> extends Draft<Base> ? T : Base) | undefined,
101+
...rest: Rest
102+
) => Produced<Base, Return>
100103

101104
/** Curried producer with no default value */
102105
<Base = any, Rest extends any[] = [], Return = void>(
103106
recipe: (this: Draft<Base>, draft: Draft<Base>, ...rest: Rest) => Return
104-
): (base: Base, ...rest: Rest) => Produced<Base, Return>
107+
): <T>(
108+
base: Draft<T> extends Draft<Base> ? T : Base,
109+
...rest: Rest
110+
) => Produced<Base, Return>
105111
}
106112

107113
export const produce: IProduce

0 commit comments

Comments
 (0)