Skip to content

Commit

Permalink
Fix TypeScript type inference for functions passed to map
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
Avaq and tjjfvi committed Jan 7, 2021
1 parent 76c48d0 commit 8eef14a
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 11 deletions.
27 changes: 16 additions & 11 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,27 @@ export interface Nodeback<E, R> {
(err: E | null, value?: R): void
}

export interface ConcurrentFutureInstance<L, R> {
export interface Functor<A> {
input: unknown
'fantasy-land/map'<B extends this['input']>(mapper: (value: A) => B): Functor<B>
}

type Unfunctor<F extends Functor<unknown>, B> = ReturnType<(F & { input: B })['fantasy-land/map']>

export interface ConcurrentFutureInstance<L, R> extends Functor<R> {
sequential: FutureInstance<L, R>
'fantasy-land/ap'<A, B>(this: ConcurrentFutureInstance<L, (value: A) => B>, right: ConcurrentFutureInstance<L, A>): ConcurrentFutureInstance<L, B>
'fantasy-land/map'<RB>(mapper: (value: R) => RB): ConcurrentFutureInstance<L, RB>
'fantasy-land/map'<RB extends this['input']>(mapper: (value: R) => RB): ConcurrentFutureInstance<L, RB>
'fantasy-land/alt'(right: ConcurrentFutureInstance<L, R>): ConcurrentFutureInstance<L, R>
}

export interface FutureInstance<L, R> {
export interface FutureInstance<L, R> extends Functor<R> {

/** The Future constructor */
constructor: FutureTypeRep

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

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

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

/** Map over the resolution value of the given Future or ConcurrentFuture. See https://github.com/fluture-js/Fluture#map */
export function map<RA, RB>(mapper: (value: RA) => RB): <T extends FutureInstance<any, RA> | ConcurrentFutureInstance<any, RA>>(source: T) =>
T extends FutureInstance<infer L, RA> ?
FutureInstance<L, RB> :
T extends ConcurrentFutureInstance<infer L, RA> ?
ConcurrentFutureInstance<L, RB> :
never;
export const map: {
<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>
<A, B>(f: (a: A) => B): <F extends Functor<A>>(f: F) => Unfunctor<F, B>
}

/** Map over the rejection reason of the given Future. See https://github.com/fluture-js/Fluture#maprej */
export function mapRej<LA, LB>(mapper: (reason: LA) => LB): <R>(source: FutureInstance<LA, R>) => FutureInstance<LB, R>
Expand Down
4 changes: 4 additions & 0 deletions test/types/map.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ expectType<fl.ConcurrentFutureInstance<string, string>> (fl.map (String) (reject
// Usage with pipe on Future instances (https://git.io/JLx3F).
expectType<fl.FutureInstance<never, string>> (resolved .pipe (fl.map (String)));
expectType<fl.FutureInstance<string, string>> (rejected .pipe (fl.map (String)));

// Function parameter inference from the second argument in a pipe (https://git.io/JLxsX).
expectType<fl.FutureInstance<never, number>> (resolved .pipe (fl.map (x => x)));
expectType<fl.FutureInstance<string, never>> (rejected .pipe (fl.map (x => x)));

0 comments on commit 8eef14a

Please sign in to comment.