Skip to content

Commit 8eef14a

Browse files
Avaqtjjfvi
andcommitted
Fix TypeScript type inference for functions passed to map
The approach here was designed by @tjjfvi, who has been a tremendous help on the TypeScript Discord server. Thank you! Co-Authored-By: tjjfvi <[email protected]>
1 parent 76c48d0 commit 8eef14a

File tree

2 files changed

+20
-11
lines changed

2 files changed

+20
-11
lines changed

index.d.ts

+16-11
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,27 @@ export interface Nodeback<E, R> {
2121
(err: E | null, value?: R): void
2222
}
2323

24-
export interface ConcurrentFutureInstance<L, R> {
24+
export interface Functor<A> {
25+
input: unknown
26+
'fantasy-land/map'<B extends this['input']>(mapper: (value: A) => B): Functor<B>
27+
}
28+
29+
type Unfunctor<F extends Functor<unknown>, B> = ReturnType<(F & { input: B })['fantasy-land/map']>
30+
31+
export interface ConcurrentFutureInstance<L, R> extends Functor<R> {
2532
sequential: FutureInstance<L, R>
2633
'fantasy-land/ap'<A, B>(this: ConcurrentFutureInstance<L, (value: A) => B>, right: ConcurrentFutureInstance<L, A>): ConcurrentFutureInstance<L, B>
27-
'fantasy-land/map'<RB>(mapper: (value: R) => RB): ConcurrentFutureInstance<L, RB>
34+
'fantasy-land/map'<RB extends this['input']>(mapper: (value: R) => RB): ConcurrentFutureInstance<L, RB>
2835
'fantasy-land/alt'(right: ConcurrentFutureInstance<L, R>): ConcurrentFutureInstance<L, R>
2936
}
3037

31-
export interface FutureInstance<L, R> {
38+
export interface FutureInstance<L, R> extends Functor<R> {
3239

3340
/** The Future constructor */
3441
constructor: FutureTypeRep
3542

3643
/** Apply a function to this Future. See https://github.com/fluture-js/Fluture#pipe */
37-
pipe<T>(fn: (future: FutureInstance<L, R>) => T): T
44+
pipe<T>(fn: (future: this) => T): T
3845

3946
/** Attempt to extract the rejection reason. See https://github.com/fluture-js/Fluture#extractleft */
4047
extractLeft(): Array<L>
@@ -43,7 +50,7 @@ export interface FutureInstance<L, R> {
4350
extractRight(): Array<R>
4451

4552
'fantasy-land/ap'<A, B>(this: FutureInstance<L, (value: A) => B>, right: FutureInstance<L, A>): FutureInstance<L, B>
46-
'fantasy-land/map'<RB>(mapper: (value: R) => RB): FutureInstance<L, RB>
53+
'fantasy-land/map'<RB extends this['input']>(mapper: (value: R) => RB): FutureInstance<L, RB>
4754
'fantasy-land/alt'(right: FutureInstance<L, R>): FutureInstance<L, R>
4855
'fantasy-land/bimap'<LB, RB>(lmapper: (reason: L) => LB, rmapper: (value: R) => RB): FutureInstance<LB, RB>
4956
'fantasy-land/chain'<LB, RB>(mapper: (value: R) => FutureInstance<LB, RB>): FutureInstance<L | LB, RB>
@@ -135,12 +142,10 @@ export function isNever(value: any): boolean
135142
export function lastly<L>(cleanup: FutureInstance<L, any>): <R>(action: FutureInstance<L, R>) => FutureInstance<L, R>
136143

137144
/** Map over the resolution value of the given Future or ConcurrentFuture. See https://github.com/fluture-js/Fluture#map */
138-
export function map<RA, RB>(mapper: (value: RA) => RB): <T extends FutureInstance<any, RA> | ConcurrentFutureInstance<any, RA>>(source: T) =>
139-
T extends FutureInstance<infer L, RA> ?
140-
FutureInstance<L, RB> :
141-
T extends ConcurrentFutureInstance<infer L, RA> ?
142-
ConcurrentFutureInstance<L, RB> :
143-
never;
145+
export const map: {
146+
<B, F extends Functor<unknown>>(f: Functor<unknown> extends F ? never : (a: F extends Functor<infer A> ? A : never) => B): (source: F) => Unfunctor<F, B>
147+
<A, B>(f: (a: A) => B): <F extends Functor<A>>(f: F) => Unfunctor<F, B>
148+
}
144149

145150
/** Map over the rejection reason of the given Future. See https://github.com/fluture-js/Fluture#maprej */
146151
export function mapRej<LA, LB>(mapper: (reason: LA) => LB): <R>(source: FutureInstance<LA, R>) => FutureInstance<LB, R>

test/types/map.test-d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ expectType<fl.ConcurrentFutureInstance<string, string>> (fl.map (String) (reject
1919
// Usage with pipe on Future instances (https://git.io/JLx3F).
2020
expectType<fl.FutureInstance<never, string>> (resolved .pipe (fl.map (String)));
2121
expectType<fl.FutureInstance<string, string>> (rejected .pipe (fl.map (String)));
22+
23+
// Function parameter inference from the second argument in a pipe (https://git.io/JLxsX).
24+
expectType<fl.FutureInstance<never, number>> (resolved .pipe (fl.map (x => x)));
25+
expectType<fl.FutureInstance<string, never>> (rejected .pipe (fl.map (x => x)));

0 commit comments

Comments
 (0)