From b357e540c73674dfa26eb586a47cb206bd374ea9 Mon Sep 17 00:00:00 2001 From: aleclarson Date: Fri, 11 Jan 2019 19:17:49 -0500 Subject: [PATCH] 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" value. Related test: f94fcaaee9bb39c52c32c64cf59fbadf352fae05 --- __tests__/produce.ts | 40 +++++++++++++++++++++++++--------------- src/immer.d.ts | 10 ++++++++-- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/__tests__/produce.ts b/__tests__/produce.ts index a6371a56..5f818c5c 100644 --- a/__tests__/produce.ts +++ b/__tests__/produce.ts @@ -2,7 +2,8 @@ import produce, { produce as produce2, applyPatches, Patch, - nothing + nothing, + Draft } from "../dist/immer.js" // prettier-ignore @@ -67,27 +68,28 @@ it("can update readonly state via standard api", () => { // NOTE: only when the function type is inferred it("can infer state type from default state", () => { - type Producer = (base: number | undefined) => number - - let foo: Producer = {} as any - exactType(produce(_ => {}, 1), foo) - exactType(foo(2), {} as number) + type Producer = ( + base: (T extends number ? T : number) | undefined + ) => number + let foo = produce(_ => {}, 1) + exactType(foo, {} as Producer) + exactType(foo(2), 0 as number) }) it("can infer state type from recipe function", () => { - type Producer = ( - base: string | number | undefined, + type Base = string | number + type Producer = ( + base: (T extends Base ? T : Base) | undefined, _2: number - ) => string | number + ) => Base - let foo: Producer = {} as any - let recipe = (_: string | number, _2: number) => {} - exactType(produce(recipe, 1), foo) + let foo = produce((_: string | number, _2: number) => {}, 1) + exactType(foo, {} as Producer) exactType(foo("", 0), {} as string | number) }) it("cannot infer state type when the function type and default state are missing", () => { - exactType(produce(_ => {}), {} as (base: any) => any) + exactType(produce(_ => {}), {} as (base: T) => any) }) it("can update readonly state via curried api", () => { @@ -138,13 +140,21 @@ it("can apply patches", () => { }) it("can provide rest parameters to a curried producer", () => { - type Foo = (base: object, _2: number, _3: number) => object + type Foo = ( + base: Draft extends {} ? T : object, + _2: number, + _3: number + ) => object let foo = produce((_1: object, _2: number, _3: number) => {}) exactType(foo, {} as Foo) foo({}, 1, 2) // With initial state: - type Bar = (base: object | undefined, _2: number, _3: number) => object + type Bar = ( + base: (Draft extends {} ? T : object) | undefined, + _2: number, + _3: number + ) => object let bar = produce((_1: object, _2: number, _3: number) => {}, {}) exactType(bar, {} as Bar) bar(undefined, 1, 2) diff --git a/src/immer.d.ts b/src/immer.d.ts index dda3a748..cd80d4a8 100644 --- a/src/immer.d.ts +++ b/src/immer.d.ts @@ -96,12 +96,18 @@ export interface IProduce { ...rest: Rest ) => Return, defaultBase: Default - ): (base: Base | undefined, ...rest: Rest) => Produced + ): ( + base: (Draft extends Draft ? T : Base) | undefined, + ...rest: Rest + ) => Produced /** Curried producer with no default value */ ( recipe: (this: Draft, draft: Draft, ...rest: Rest) => Return - ): (base: Base, ...rest: Rest) => Produced + ): ( + base: Draft extends Draft ? T : Base, + ...rest: Rest + ) => Produced } export const produce: IProduce