Skip to content

Allow TypeScript to infer the type of a Future created by node() from its callback #375

@Avaq

Description

@Avaq

node(done => open('somefile', 'r', done)) doesn't infer the callback value type since open returns nothing we can use as a reference. typeof open however can give some information.

so given this signature:

open(a: string, b: string, cb: (er: Error | null, fd: number) => void): void

we can find out its return type using the new infer keyword:

type TypeOf<T extends (a: any, b: any, cb: (er: Error | null, value?: any) => void) => void> = T extends (
  a: any,
  b: any,
  cb: (er: Error | null, value?: infer R) => void
) => void
  ? R
  : any

example:

declare function open(path: string, flags: string | number, callback: (err: Error | null, fd: number) => void): void
type T1 = TypeOf<typeof open>
// number (fd)

with that we can infer the callback type:

import { FutureInstance } from 'fluture'

declare function node<F extends (cb: (er: Error | null, value?: any) => void) => void>(
  f: F
): F extends (cb: (er: infer L, value?: infer R) => void) => void ? () => FutureInstance<NonNullable<L>, R> : () => any
declare function node<F extends (a: any, cb: (er: Error | null, value?: any) => void) => void>(
  f: F
): F extends (a: infer A, cb: (er: infer L, value?: infer R) => void) => void
  ? (a: A) => FutureInstance<NonNullable<L>, R>
  : (a: any) => any
declare function node<F extends (a: any, b: any, cb: (er: Error | null, value?: any) => void) => void>(
  f: F
): F extends (a: infer A, b: infer B, cb: (er: infer L, value?: infer R) => void) => void
  ? (a: A, b: B) => FutureInstance<NonNullable<L>, R>
  : (a: any, b: any) => any
declare function node<F extends (a: any, b: any, c: any, cb: (er: Error | null, value?: any) => void) => void>(
  f: F
): F extends (a: infer A, b: infer B, c: infer C, cb: (er: infer L, value?: infer R) => void) => void
  ? (a: A, b: B, c: C) => FutureInstance<NonNullable<L>, R>
  : (a: any, b: any, c: any) => any

import { open, close } from 'fs'

const t1 = node(open)
// (a: PathLike, b: string | number) => FutureInstance<NodeJS.ErrnoException, number>
const t2 = node(open)(__dirname + '/bla', 'r')
// FutureInstance<NodeJS.ErrnoException, number>
const t3 = node(close)
// (a: number) => FutureInstance<NodeJS.ErrnoException, unknown>

rest of the family

declare function map<A, B>(f: (a: A) => B): <L>(fa: FutureInstance<L, A>) => FutureInstance<L, B>

declare function double(n: number): number

const t4 = map(double)
// <L>(fa: FutureInstance<L, number>) => FutureInstance<L, number>

const t5 = t4(node(open)(__dirname + '/../../package.json', 'r'))
// FutureInstance<NodeJS.ErrnoException, number>

declare function of<A>(a: A): FutureInstance<never, A>

declare function ap<L, A>(fa: FutureInstance<L, A>): <B>(fab: FutureInstance<L, (a: A) => B>) => FutureInstance<L, B>

const t6 = ap(of(42))
// <B>(fab: FutureInstance<unknown, (a: number) => B>) => FutureInstance<unknown, B>

const t7 = t6(of(a => a + 'world'))
// FutureInstance<unknown, string>

const t8 = ap(map(double)(node(open)(__dirname + '/../../package.json', 'r')))(of(a => a + 'world'))
// FutureInstance<NodeJS.ErrnoException, string>

Originally posted by @tetsuo in #374 (comment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions