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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions