Typescript: Unable to infer types from callback in bindCallback function for RxJS

Created on 28 Mar 2018  路  5Comments  路  Source: microsoft/TypeScript

Currently type inference is unable to provide useful type inference for functions such as RxJS's bindCallback.

I have a PR here (https://github.com/ReactiveX/rxjs/pull/3480) that tries to add overloads to support the desired behavior, but when I test it, it does not infer types properly.


TypeScript Version: @latest as of this moment


Search Terms: callback type inference, wrapping node callbacks, variadic arguments

Code

export function bindCallback(callbackFunc: (callback: () => any) => any, scheduler?: SchedulerLike): () => Observable<void>;
export function bindCallback<R1>(callbackFunc: (callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): () => Observable<R1>;
export function bindCallback<R1, R2>(callbackFunc: (callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): () => Observable<[R1, R2]>;
export function bindCallback<R1, R2, R3>(callbackFunc: (callback: (res1: R1, res2: R2, res3: R3) => any) => any, scheduler?: SchedulerLike): () => Observable<[R1, R2, R3]>;
export function bindCallback<A1>(callbackFunc: (arg1: A1, callback: () => any) => any, scheduler?: SchedulerLike): (arg1: A1) => Observable<void>;
export function bindCallback<A1, R1>(callbackFunc: (arg1: A1, callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): (arg1: A1) => Observable<R1>;
export function bindCallback<A1, R1, R2>(callbackFunc: (arg1: A1, callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): (arg1: A1) => Observable<[R1, R2]>;
export function bindCallback<A1, R1, R2, R3>(callbackFunc: (arg1: A1, callback: (res1: R1, res2: R2, res3: R3) => any) => any, scheduler?: SchedulerLike): (arg1: A1) => Observable<[R1, R2, R3]>;
export function bindCallback<A1, A2>(callbackFunc: (arg1: A1, arg2: A2, callback: () => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2) => Observable<void>;
export function bindCallback<A1, A2, R1>(callbackFunc: (arg1: A1, arg2: A2, callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2) => Observable<R1>;
export function bindCallback<A1, A2, R1, R2>(callbackFunc: (arg1: A1, arg2: A2, callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2) => Observable<[R1, R2]>;

function foo(a: number, b: number, callback: (c: string, d: string) => any): any {
   setTimeout(() => callback(a + '!', b + '!');
}

const source = bindCallback(foo)(1, 3);

Expected behavior:

source should be Observable<[string, string]>

Actual behavior:

source is Observable<void>

Playground Link:
https://www.typescriptlang.org/play/#src=interface%20SchedulerLike%20%7B%20%7D%3B%0D%0A%0D%0Ainterface%20Observable%3CT%3E%20%7B%20%7D%3B%0D%0A%0D%0Aexport%20function%20bindCallback(callbackFunc%3A%20(callback%3A%20()%20%3D%3E%20any)%20%3D%3E%20any%2C%20scheduler%3F%3A%20SchedulerLike)%3A%20()%20%3D%3E%20Observable%3Cvoid%3E%3B%0D%0Aexport%20function%20bindCallback%3CR1%3E(callbackFunc%3A%20(callback%3A%20(res1%3A%20R1)%20%3D%3E%20any)%20%3D%3E%20any%2C%20scheduler%3F%3A%20SchedulerLike)%3A%20()%20%3D%3E%20Observable%3CR1%3E%3B%0D%0Aexport%20function%20bindCallback%3CR1%2C%20R2%3E(callbackFunc%3A%20(callback%3A%20(res1%3A%20R1%2C%20res2%3A%20R2)%20%3D%3E%20any)%20%3D%3E%20any%2C%20scheduler%3F%3A%20SchedulerLike)%3A%20()%20%3D%3E%20Observable%3C%5BR1%2C%20R2%5D%3E%3B%0D%0Aexport%20function%20bindCallback%3CR1%2C%20R2%2C%20R3%3E(callbackFunc%3A%20(callback%3A%20(res1%3A%20R1%2C%20res2%3A%20R2%2C%20res3%3A%20R3)%20%3D%3E%20any)%20%3D%3E%20any%2C%20scheduler%3F%3A%20SchedulerLike)%3A%20()%20%3D%3E%20Observable%3C%5BR1%2C%20R2%2C%20R3%5D%3E%3B%0D%0Aexport%20function%20bindCallback%3CA1%3E(callbackFunc%3A%20(arg1%3A%20A1%2C%20callback%3A%20()%20%3D%3E%20any)%20%3D%3E%20any%2C%20scheduler%3F%3A%20SchedulerLike)%3A%20(arg1%3A%20A1)%20%3D%3E%20Observable%3Cvoid%3E%3B%0D%0Aexport%20function%20bindCallback%3CA1%2C%20R1%3E(callbackFunc%3A%20(arg1%3A%20A1%2C%20callback%3A%20(res1%3A%20R1)%20%3D%3E%20any)%20%3D%3E%20any%2C%20scheduler%3F%3A%20SchedulerLike)%3A%20(arg1%3A%20A1)%20%3D%3E%20Observable%3CR1%3E%3B%0D%0Aexport%20function%20bindCallback%3CA1%2C%20R1%2C%20R2%3E(callbackFunc%3A%20(arg1%3A%20A1%2C%20callback%3A%20(res1%3A%20R1%2C%20res2%3A%20R2)%20%3D%3E%20any)%20%3D%3E%20any%2C%20scheduler%3F%3A%20SchedulerLike)%3A%20(arg1%3A%20A1)%20%3D%3E%20Observable%3C%5BR1%2C%20R2%5D%3E%3B%0D%0Aexport%20function%20bindCallback%3CA1%2C%20R1%2C%20R2%2C%20R3%3E(callbackFunc%3A%20(arg1%3A%20A1%2C%20callback%3A%20(res1%3A%20R1%2C%20res2%3A%20R2%2C%20res3%3A%20R3)%20%3D%3E%20any)%20%3D%3E%20any%2C%20scheduler%3F%3A%20SchedulerLike)%3A%20(arg1%3A%20A1)%20%3D%3E%20Observable%3C%5BR1%2C%20R2%2C%20R3%5D%3E%3B%0D%0Aexport%20function%20bindCallback%3CA1%2C%20A2%3E(callbackFunc%3A%20(arg1%3A%20A1%2C%20arg2%3A%20A2%2C%20callback%3A%20()%20%3D%3E%20any)%20%3D%3E%20any%2C%20scheduler%3F%3A%20SchedulerLike)%3A%20(arg1%3A%20A1%2C%20arg2%3A%20A2)%20%3D%3E%20Observable%3Cvoid%3E%3B%0D%0Aexport%20function%20bindCallback%3CA1%2C%20A2%2C%20R1%3E(callbackFunc%3A%20(arg1%3A%20A1%2C%20arg2%3A%20A2%2C%20callback%3A%20(res1%3A%20R1)%20%3D%3E%20any)%20%3D%3E%20any%2C%20scheduler%3F%3A%20SchedulerLike)%3A%20(arg1%3A%20A1%2C%20arg2%3A%20A2)%20%3D%3E%20Observable%3CR1%3E%3B%0D%0Aexport%20function%20bindCallback%3CA1%2C%20A2%2C%20R1%2C%20R2%3E(callbackFunc%3A%20(arg1%3A%20A1%2C%20arg2%3A%20A2%2C%20callback%3A%20(res1%3A%20R1%2C%20res2%3A%20R2)%20%3D%3E%20any)%20%3D%3E%20any%2C%20scheduler%3F%3A%20SchedulerLike)%3A%20(arg1%3A%20A1%2C%20arg2%3A%20A2)%20%3D%3E%20Observable%3C%5BR1%2C%20R2%5D%3E%3B%0D%0Aexport%20function%20bindCallback%3CT%3E(callbackFunc%3A%20Function%2C%0D%0A%20%20scheduler%3F%3A%20SchedulerLike)%3A%20(...args%3A%20any%5B%5D)%20%3D%3E%20Observable%3CT%3E%20%7B%0D%0A%20%20return%20(...args%3A%20any%5B%5D)%20%3D%3E%20%7B%0D%0A%20%20%20%20return%20null%20as%20Observable%3Cany%3E%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0A%0D%0Afunction%20foo(a%3A%20number%2C%20b%3A%20number%2C%20callback%3A%20(c%3A%20string%2C%20d%3A%20string)%20%3D%3E%20any)%3A%20any%20%7B%0D%0A%20%20%20setTimeout(()%20%3D%3E%20callback(a%20%2B%20'!'%2C%20b%20%2B%20'!')%3B%0D%0A%7D%0D%0A%0D%0Aconst%20source%20%3D%20bindCallback(foo)(1%2C%203)%3B

Related Issues:

Needs More Info

Most helpful comment

Yeah, you need variadic types. We have been talking about it a lot lately, and i think we will start tackling it soon.

All 5 comments

From a cursory look, seems like you just ordered your overloads in the opposite order. i am guessing you want export function bindCallback<A1, A2, R1, R2> ... to be at the top, since bindCallback<A1, A2>(callbackFunc: ... that you have more generic and overload resolution proceeds in order of declarations.

Thank you, @mhegazy, that worked, however it's _REALLY_ ugly. Haha. Ideally, there would be some way to have "spread generics" or the like.

Here's what the type overloads for RxJS's bindCallback looks like now:

export function bindCallback<R1, R2, R3, R4>(callbackFunc: (callback: (res1: R1, res2: R2, res3: R3, res4: R4, ...args: any[]) => any) => any, scheduler?: SchedulerLike): () => Observable<any[]>;
export function bindCallback<R1, R2, R3>(callbackFunc: (callback: (res1: R1, res2: R2, res3: R3) => any) => any, scheduler?: SchedulerLike): () => Observable<[R1, R2, R3]>;
export function bindCallback<R1, R2>(callbackFunc: (callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): () => Observable<[R1, R2]>;
export function bindCallback<R1>(callbackFunc: (callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): () => Observable<R1>;
export function bindCallback(callbackFunc: (callback: () => any) => any, scheduler?: SchedulerLike): () => Observable<void>;

export function bindCallback<A1, R1, R2, R3, R4>(callbackFunc: (arg1: A1, callback: (res1: R1, res2: R2, res3: R3, res4: R4, ...args: any[]) => any) => any, scheduler?: SchedulerLike): (arg1: A1) => Observable<any[]>;
export function bindCallback<A1, R1, R2, R3>(callbackFunc: (arg1: A1, callback: (res1: R1, res2: R2, res3: R3) => any) => any, scheduler?: SchedulerLike): (arg1: A1) => Observable<[R1, R2, R3]>;
export function bindCallback<A1, R1, R2>(callbackFunc: (arg1: A1, callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): (arg1: A1) => Observable<[R1, R2]>;
export function bindCallback<A1, R1>(callbackFunc: (arg1: A1, callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): (arg1: A1) => Observable<R1>;
export function bindCallback<A1>(callbackFunc: (arg1: A1, callback: () => any) => any, scheduler?: SchedulerLike): (arg1: A1) => Observable<void>;

export function bindCallback<A1, A2, R1, R2, R3, R4>(callbackFunc: (arg1: A1, arg2: A2, callback: (res1: R1, res2: R2, res3: R3, res4: R4, ...args: any[]) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2) => Observable<any[]>;
export function bindCallback<A1, A2, R1, R2, R3>(callbackFunc: (arg1: A1, arg2: A2, callback: (res1: R1, res2: R2, res3: R3) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2) => Observable<[R1, R2, R3]>;
export function bindCallback<A1, A2, R1, R2>(callbackFunc: (arg1: A1, arg2: A2, callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2) => Observable<[R1, R2]>;
export function bindCallback<A1, A2, R1>(callbackFunc: (arg1: A1, arg2: A2, callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2) => Observable<R1>;
export function bindCallback<A1, A2>(callbackFunc: (arg1: A1, arg2: A2, callback: () => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2) => Observable<void>;

export function bindCallback<A1, A2, A3, R1, R2, R3, R4>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, callback: (res1: R1, res2: R2, res3: R3, res4: R4, ...args: any[]) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3) => Observable<any[]>;
export function bindCallback<A1, A2, A3, R1, R2, R3>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, callback: (res1: R1, res2: R2, res3: R3) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3) => Observable<[R1, R2, R3]>;
export function bindCallback<A1, A2, A3, R1, R2>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3) => Observable<[R1, R2]>;
export function bindCallback<A1, A2, A3, R1>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3) => Observable<R1>;
export function bindCallback<A1, A2, A3>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, callback: () => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3) => Observable<void>;

export function bindCallback<A1, A2, A3, A4, R1, R2, R3, R4>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, callback: (res1: R1, res2: R2, res3: R3, res4: R4, ...args: any[]) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => Observable<any[]>;
export function bindCallback<A1, A2, A3, A4, R1, R2, R3>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, callback: (res1: R1, res2: R2, res3: R3) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => Observable<[R1, R2, R3]>;
export function bindCallback<A1, A2, A3, A4, R1, R2>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => Observable<[R1, R2]>;
export function bindCallback<A1, A2, A3, A4, R1>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => Observable<R1>;
export function bindCallback<A1, A2, A3, A4>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, callback: () => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => Observable<void>;

export function bindCallback<A1, A2, A3, A4, A5, R1, R2, R3, R4>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, callback: (res1: R1, res2: R2, res3: R3, res4: R4, ...args: any[]) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Observable<any[]>;
export function bindCallback<A1, A2, A3, A4, A5, R1, R2, R3>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, callback: (res1: R1, res2: R2, res3: R3) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Observable<[R1, R2, R3]>;
export function bindCallback<A1, A2, A3, A4, A5, R1, R2>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, callback: (res1: R1, res2: R2) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Observable<[R1, R2]>;
export function bindCallback<A1, A2, A3, A4, A5, R1>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, callback: (res1: R1) => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Observable<R1>;
export function bindCallback<A1, A2, A3, A4, A5>(callbackFunc: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, callback: () => any) => any, scheduler?: SchedulerLike): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Observable<void>;

export function bindCallback<A, R>(callbackFunc: (...args: Array<A | ((result: R) => any)>) => any, scheduler?: SchedulerLike): (...args: A[]) => Observable<R>;
export function bindCallback<A, R>(callbackFunc: (...args: Array<A | ((...results: R[]) => any)>) => any, scheduler?: SchedulerLike): (...args: A[]) => Observable<R[]>;

export function bindCallback(callbackFunc: Function, scheduler?: SchedulerLike): (...args: any[]) => Observable<any>;

It doesn't support every use case, it just seemed silly to go beyond a certain point.

In particular, notice above the lines that have an R4, just so it can ignore that type and return Observable<any[]>

Yeah, you need variadic types. We have been talking about it a lot lately, and i think we will start tackling it soon.

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

Was this page helpful?
0 / 5 - 0 ratings