Repository: gcanti/fp-ts
Pull request #1584 (2021-09-15 18:08:22)

Ensuring that bind and bindW pass the correct params through rather t…
…han get rewritten by the function passed in

Fixes https://github.com/gcanti/fp-ts/issues/1583

The following code improperly raises a type error:
```Typescript
  const func1 = (i: {arg1: string}) => TE.right('result1')
  const func2 = (i: {arg2: string, result1: string}) => TE.right('result2')

  pipe(
    TE.right({arg1: 'arg1', arg2: 'arg2'}),
    TE.bindW('result1', func1),
    TE.bindW('result2', func2),
  )
```
This is because this line improperly rewrites the parameter list that gets passed into the next function in the pipe `TE.bindW('result1', func1),`

This MR changes the passed in function to have its own generic that extends the param list.  This allows the params to flow through as expected.

edit: formatting

feydan: Unfortunately, this seems to cause another issue:
```typescript
// proper param passthrough
    const c = (p: { a: number }) => _.right<string, number>(p.a)
    const d = (p: { b: string }) => _.right<string, string>(p.b)
    U.deepStrictEqual(
      await pipe(
        _.right<string, number>(1),
        _.bindTo('a'),
        _.bind('b', () => _.right('b')),
        _.bind('c', c),
        _.bind('d', d),
        _.bind('e', _.fromIOK(i => () => 'io')) // Type 'A2' is not assignable to type 'A2'. Two different types with this name exist, but they are unrelated.
      )(),
      E.right({ a: 1, b: 'b', c: 1, d: 'b', e: 'io' })
    )
```
Looking into alternatives.
feydan: > Unfortunately, this seems to cause another issue:
> 
> ```ts
> // proper param passthrough
>     const c = (p: { a: number }) => _.right<string, number>(p.a)
>     const d = (p: { b: string }) => _.right<string, string>(p.b)
>     U.deepStrictEqual(
>       await pipe(
>         _.right<string, number>(1),
>         _.bindTo('a'),
>         _.bind('b', () => _.right('b')),
>         _.bind('c', c),
>         _.bind('d', d),
>         _.bind('e', _.fromIOK(i => () => 'io')) // Type 'A2' is not assignable to type 'A2'. Two different types with this name exist, but they are unrelated.
>       )(),
>       E.right({ a: 1, b: 'b', c: 1, d: 'b', e: 'io' })
>     )
> ```
> 
> Looking into alternatives.

I think I found a solution -- the change is committed.
gcanti: `npm run fix-prettier` should fix the CI failure

Patch:
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..7c2e87df
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,1 @@
+github: [gcanti]
diff --git a/src/Chain.ts b/src/Chain.ts
index 5d29e4c4..a61a7b83 100644
--- a/src/Chain.ts
+++ b/src/Chain.ts
@@ -115,49 +115,49 @@ export function bind<M extends URIS4>(
   M: Chain4<M>
 ): <N extends string, A, S, R, E, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => Kind4<M, S, R, E, B>
+  f: <A2 extends A>(a: A | A2) => Kind4<M, S, R, E, B>
 ) => (ma: Kind4<M, S, R, E, A>) => Kind4<M, S, R, E, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }>
 export function bind<M extends URIS3>(
   M: Chain3<M>
 ): <N extends string, A, R, E, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => Kind3<M, R, E, B>
+  f: <A2 extends A>(a: A | A2) => Kind3<M, R, E, B>
 ) => (ma: Kind3<M, R, E, A>) => Kind3<M, R, E, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }>
 export function bind<M extends URIS3, E>(
   M: Chain3C<M, E>
 ): <N extends string, A, R, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => Kind3<M, R, E, B>
+  f: <A2 extends A>(a: A | A2) => Kind3<M, R, E, B>
 ) => (ma: Kind3<M, R, E, A>) => Kind3<M, R, E, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }>
 export function bind<M extends URIS2>(
   M: Chain2<M>
 ): <N extends string, A, E, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => Kind2<M, E, B>
+  f: <A2 extends A>(a: A | A2) => Kind2<M, E, B>
 ) => (ma: Kind2<M, E, A>) => Kind2<M, E, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }>
 export function bind<M extends URIS2, E>(
   M: Chain2C<M, E>
 ): <N extends string, A, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => Kind2<M, E, B>
+  f: <A2 extends A>(a: A | A2) => Kind2<M, E, B>
 ) => (ma: Kind2<M, E, A>) => Kind2<M, E, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }>
 export function bind<M extends URIS>(
   M: Chain1<M>
 ): <N extends string, A, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => Kind<M, B>
+  f: <A2 extends A>(a: A | A2) => Kind<M, B>
 ) => (ma: Kind<M, A>) => Kind<M, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }>
 export function bind<M>(
   M: Chain<M>
 ): <N extends string, A, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => HKT<M, B>
+  f: <A2 extends A>(a: A | A2) => HKT<M, B>
 ) => (ma: HKT<M, A>) => HKT<M, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }>
 export function bind<M>(
   M: Chain<M>
 ): <N extends string, A, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => HKT<M, B>
+  f: <A2 extends A>(a: A | A2) => HKT<M, B>
 ) => (ma: HKT<M, A>) => HKT<M, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }> {
   return (name, f) => (ma) => M.chain(ma, (a) => M.map(f(a), (b) => Object.assign({}, a, { [name]: b }) as any))
 }
diff --git a/src/Either.ts b/src/Either.ts
index 4eb76a30..2d70832f 100644
--- a/src/Either.ts
+++ b/src/Either.ts
@@ -1403,7 +1403,7 @@ export const bind = /*#__PURE__*/ bind_(Chain)
  */
 export const bindW: <N extends string, A, E2, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => Either<E2, B>
+  f: <A2 extends A>(a: A | A2) => Either<E2, B>
 ) => <E1>(
   fa: Either<E1, A>
 ) => Either<E1 | E2, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }> = bind as any
diff --git a/src/IOEither.ts b/src/IOEither.ts
index 07e1b994..8705c5c8 100644
--- a/src/IOEither.ts
+++ b/src/IOEither.ts
@@ -948,7 +948,7 @@ export const bind = /*#__PURE__*/ bind_(Chain)
  */
 export const bindW: <N extends string, A, E2, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => IOEither<E2, B>
+  f: <A2 extends A>(a: A | A2) => IOEither<E2, B>
 ) => <E1>(
   fa: IOEither<E1, A>
 ) => IOEither<E1 | E2, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }> = bind as any
diff --git a/src/Reader.ts b/src/Reader.ts
index a2dcc7e0..82e80c4c 100644
--- a/src/Reader.ts
+++ b/src/Reader.ts
@@ -506,7 +506,7 @@ export const bind = /*#__PURE__*/ bind_(Chain)
  */
 export const bindW: <N extends string, A, R2, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => Reader<R2, B>
+  f: <A2 extends A>(a: A | A2) => Reader<R2, B>
 ) => <R1>(
   fa: Reader<R1, A>
 ) => Reader<R1 & R2, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }> = bind as any
diff --git a/src/ReaderEither.ts b/src/ReaderEither.ts
index f26e4381..91d6c75b 100644
--- a/src/ReaderEither.ts
+++ b/src/ReaderEither.ts
@@ -938,7 +938,7 @@ export const bind = /*#__PURE__*/ bind_(Chain)
  */
 export const bindW: <N extends string, A, R2, E2, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => ReaderEither<R2, E2, B>
+  f: <A2 extends A>(a: A | A2) => ReaderEither<R2, E2, B>
 ) => <R1, E1>(
   fa: ReaderEither<R1, E1, A>
 ) => ReaderEither<R1 & R2, E1 | E2, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }> = bind as any
diff --git a/src/ReaderIO.ts b/src/ReaderIO.ts
index 44d2dedd..79a6252d 100644
--- a/src/ReaderIO.ts
+++ b/src/ReaderIO.ts
@@ -437,7 +437,7 @@ export const bind = /*#__PURE__*/ bind_(Chain)
  */
 export const bindW: <N extends string, A, R2, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => ReaderIO<R2, B>
+  f: <A2 extends A>(a: A | A2) => ReaderIO<R2, B>
 ) => <R1>(
   fa: ReaderIO<R1, A>
 ) => ReaderIO<R1 & R2, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }> = bind as any
diff --git a/src/ReaderTask.ts b/src/ReaderTask.ts
index 8d9e6b84..26a853b9 100644
--- a/src/ReaderTask.ts
+++ b/src/ReaderTask.ts
@@ -541,7 +541,7 @@ export const bind = /*#__PURE__*/ bind_(Chain)
  */
 export const bindW: <N extends string, A, R2, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => ReaderTask<R2, B>
+  f: <A2 extends A>(a: A | A2) => ReaderTask<R2, B>
 ) => <R1>(
   fa: ReaderTask<R1, A>
 ) => ReaderTask<R1 & R2, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }> = bind as any
diff --git a/src/ReaderTaskEither.ts b/src/ReaderTaskEither.ts
index d0c30d66..9f58c432 100644
--- a/src/ReaderTaskEither.ts
+++ b/src/ReaderTaskEither.ts
@@ -1408,7 +1408,7 @@ export const bind = /*#__PURE__*/ bind_(Chain)
  */
 export const bindW: <N extends string, A, R2, E2, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => ReaderTaskEither<R2, E2, B>
+  f: <A2 extends A>(a: A | A2) => ReaderTaskEither<R2, E2, B>
 ) => <R1, E1>(
   fa: ReaderTaskEither<R1, E1, A>
 ) => ReaderTaskEither<R1 & R2, E1 | E2, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }> = bind as any
diff --git a/src/StateReaderTaskEither.ts b/src/StateReaderTaskEither.ts
index 94c55b65..c769c808 100644
--- a/src/StateReaderTaskEither.ts
+++ b/src/StateReaderTaskEither.ts
@@ -1125,7 +1125,7 @@ export const bind = /*#__PURE__*/ bind_(Chain)
  */
 export const bindW: <N extends string, A, S, R2, E2, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => StateReaderTaskEither<S, R2, E2, B>
+  f: <A2 extends A>(a: A | A2) => StateReaderTaskEither<S, R2, E2, B>
 ) => <R1, E1>(
   fa: StateReaderTaskEither<S, R1, E1, A>
 ) => StateReaderTaskEither<
diff --git a/src/TaskEither.ts b/src/TaskEither.ts
index 9806fc26..6a2f6885 100644
--- a/src/TaskEither.ts
+++ b/src/TaskEither.ts
@@ -1269,7 +1269,7 @@ export const bind = /*#__PURE__*/ bind_(Chain)
  */
 export const bindW: <N extends string, A, E2, B>(
   name: Exclude<N, keyof A>,
-  f: (a: A) => TaskEither<E2, B>
+  f: <A2 extends A>(a: A | A2) => TaskEither<E2, B>
 ) => <E1>(
   fa: TaskEither<E1, A>
 ) => TaskEither<E1 | E2, { readonly [K in keyof A | N]: K extends keyof A ? A[K] : B }> = bind as any


Test patch:
diff --git a/test/TaskEither.ts b/test/TaskEither.ts
index 8637ab13..3cef2865 100644
--- a/test/TaskEither.ts
+++ b/test/TaskEither.ts
@@ -622,6 +622,52 @@ describe('TaskEither', () => {
     )
   })
 
+  it('do notation ensuring proper param passthrough', async () => {
+    const c = (p: { readonly a: number }) => _.right<string, number>(p.a)
+    const d = (p: { readonly b: string }) => _.right<string, string>(p.b)
+    U.deepStrictEqual(
+      await pipe(
+        _.right<string, number>(1),
+        _.bindTo('a'),
+        _.bind('b', () => _.right('b')),
+        _.bind('c', c),
+        _.bind('d', d),
+        _.bind(
+          'e',
+          _.fromOptionK(() => 'err')((p: { readonly c: number }) => some(p.c))
+        ),
+        _.bind(
+          'f',
+          _.fromOptionK(() => 'err')((p) => some(p.b))
+        )
+      )(),
+      E.right({ a: 1, b: 'b', c: 1, d: 'b', e: 1, f: 'b' })
+    )
+  })
+
+  it('do notation bindW ensuring proper param passthrough', async () => {
+    const c = (p: { readonly a: number }) => _.right<string, number>(p.a)
+    const d = (p: { readonly b: string }) => _.right<string, string>(p.b)
+    U.deepStrictEqual(
+      await pipe(
+        _.right<string, number>(1),
+        _.bindTo('a'),
+        _.bindW('b', () => _.right('b')),
+        _.bindW('c', c),
+        _.bindW('d', d),
+        _.bindW(
+          'e',
+          _.fromOptionK(() => 1)((p: { readonly c: number }) => some(p.c))
+        ),
+        _.bindW(
+          'f',
+          _.fromOptionK(() => ({ err: 'err' }))((p) => some(p.b))
+        )
+      )(),
+      E.right({ a: 1, b: 'b', c: 1, d: 'b', e: 1, f: 'b' })
+    )
+  })
+
   it('apS', async () => {
     U.deepStrictEqual(
       await pipe(_.right<string, number>(1), _.bindTo('a'), _.apS('b', _.right('b')))(),