Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bindNodeCallback): Works on overloaded functions #6072

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 10 additions & 2 deletions api_guard/dist/types/index.d.ts
@@ -1,3 +1,5 @@
export declare type AllButLast<T extends any[]> = T extends T ? (T extends [...(infer H), any] ? H : never) : never;

export declare const animationFrame: AnimationFrameScheduler;

export declare function animationFrames(timestampProvider?: TimestampProvider): Observable<{
Expand Down Expand Up @@ -35,10 +37,10 @@ export declare class BehaviorSubject<T> extends Subject<T> {
}

export declare function bindCallback(callbackFunc: (...args: any[]) => void, resultSelector: (...args: any[]) => any, scheduler?: SchedulerLike): (...args: any[]) => Observable<any>;
export declare function bindCallback<A extends readonly unknown[], R extends readonly unknown[]>(callbackFunc: (...args: [...A, (...res: R) => void]) => void, schedulerLike?: SchedulerLike): (...arg: A) => Observable<R extends [] ? void : R extends [any] ? R[0] : R>;
export declare function bindCallback<Fn extends (...args: any[]) => any>(callbackFunc: Fn, schedulerLike?: SchedulerLike): (...arg: AllButLast<OverloadedParameters<Fn>>) => Observable<Last<Parameters<Fn>> extends () => any ? void : Last<Parameters<Fn>> extends (arg: infer R) => any ? R : Last<Parameters<Fn>> extends (...args: infer R) => any ? R : never>;

export declare function bindNodeCallback(callbackFunc: (...args: any[]) => void, resultSelector: (...args: any[]) => any, scheduler?: SchedulerLike): (...args: any[]) => Observable<any>;
export declare function bindNodeCallback<A extends readonly unknown[], R extends readonly unknown[]>(callbackFunc: (...args: [...A, (err: any, ...res: R) => void]) => void, schedulerLike?: SchedulerLike): (...arg: A) => Observable<R extends [] ? void : R extends [any] ? R[0] : R>;
export declare function bindNodeCallback<Fn extends (...args: any[]) => any>(callbackFunc: Fn, schedulerLike?: SchedulerLike): (...arg: AllButLast<OverloadedParameters<Fn>>) => Observable<Last<OverloadedParameters<Fn>> extends (err: any) => any ? void : Last<Parameters<Fn>> extends (err: any, arg: infer R) => any ? R : Last<Parameters<Fn>> extends (err: any, ...args: infer R) => any ? R : never>;

export declare function combineLatest(sources: []): Observable<never>;
export declare function combineLatest<A extends readonly unknown[]>(sources: readonly [...ObservableInputTuple<A>]): Observable<A>;
Expand Down Expand Up @@ -211,6 +213,8 @@ export declare function interval(period?: number, scheduler?: SchedulerLike): Ob

export declare function isObservable<T>(obj: any): obj is Observable<T>;

export declare type Last<T extends any[]> = [any, ...T][T['length']];

export declare function lastValueFrom<T>(source: Observable<T>): Promise<T>;

export declare function merge<A extends readonly unknown[]>(...args: [...ObservableInputTuple<A>]): Observable<A[number]>;
Expand Down Expand Up @@ -361,6 +365,10 @@ export interface Operator<T, R> {
export interface OperatorFunction<T, R> extends UnaryFunction<Observable<T>, Observable<R>> {
}

export declare type OverloadedParameters<T> = OverloadedArgumentsAndReturnType<T>[0];

export declare type OverloadedReturnType<T> = OverloadedArgumentsAndReturnType<T>[1];

export declare function pairs<T>(arr: readonly T[], scheduler?: SchedulerLike): Observable<[string, T]>;
export declare function pairs<O extends Record<string, unknown>>(obj: O, scheduler?: SchedulerLike): Observable<[keyof O, O[keyof O]]>;
export declare function pairs<T>(iterable: Iterable<T>, scheduler?: SchedulerLike): Observable<[string, T]>;
Expand Down
20 changes: 19 additions & 1 deletion spec-dtslint/observables/bindCallback-spec.ts
@@ -1,6 +1,5 @@
import { bindCallback } from 'rxjs';
import { a, b, c, d, e, f, g, A, B, C, D, E, F, G } from '../helpers';
import { SchedulerLike } from '../../src';

describe('callbackFunc', () => {
const f0 = (cb: () => void) => {
Expand Down Expand Up @@ -269,3 +268,22 @@ describe('callbackFunc overkill' , () => {
const o = bindCallback(fa10cb5) // $ExpectType (_1: 1, _2: 2, _3: 3, _4: 4, _5: 5, _6: 6, _7: 7, _8: 8, _9: 9, _10: 10) => Observable<[_11: 11, _12: 12, _13: 13, _14: 14, _15: 15]>
});
});

describe('works with overloads' , () => {
function foo(num: number, cb: (result: boolean) => void): void;
function foo(num: string, cb: (result: boolean) => void): void;
function foo(num: string, flag: boolean, cb: (result: boolean) => void): void;
function foo(...args: any[]) {}

const o = bindCallback(foo);
o(1).subscribe(v => {
v // $ExpectType boolean
});
o('1').subscribe(v => {
v // $ExpectType boolean
});
o('1', true).subscribe(v => {
v // $ExpectType boolean
});
o(1, true); // $ExpectError
});
19 changes: 19 additions & 0 deletions spec-dtslint/observables/bindNodeCallback-spec.ts
@@ -0,0 +1,19 @@
import { bindNodeCallback } from 'rxjs';
import { a, b, c, d, e, f, g, A, B, C, D, E, F, G } from '../helpers';

import * as fs from 'fs';

describe('works with overloads' , () => {
const o = bindNodeCallback(fs.readFile);
o('foo').subscribe(v => {
v // $ExpectType Buffer
});
o('foo', {encoding: 'ascii', flag: ''}).subscribe(v => {
v // $ExpectType Buffer
});
o('foo', {}).subscribe(v => {
v // $ExpectType Buffer
});
o('foo', {encoding: 'foo'}); // $ExpectError
o(true); // $ExpectError
});
19 changes: 14 additions & 5 deletions src/internal/observable/bindCallback.ts
@@ -1,5 +1,5 @@
/* @prettier */
import { SchedulerLike } from '../types';
import { AllButLast, OverloadedParameters, SchedulerLike, Last } from '../types';
import { Observable } from '../Observable';
import { bindCallbackInternals } from './bindCallbackInternals';

Expand All @@ -11,11 +11,20 @@ export function bindCallback(
scheduler?: SchedulerLike
): (...args: any[]) => Observable<any>;

// args is the arguments array and we push the callback on the rest tuple since the rest parameter must be last (only item) in a parameter list
export function bindCallback<A extends readonly unknown[], R extends readonly unknown[]>(
callbackFunc: (...args: [...A, (...res: R) => void]) => void,
export function bindCallback<Fn extends (...args: any[]) => any>(
callbackFunc: Fn,
schedulerLike?: SchedulerLike
): (...arg: A) => Observable<R extends [] ? void : R extends [any] ? R[0] : R>;
): (
...arg: AllButLast<OverloadedParameters<Fn>>
) => Observable<
Last<Parameters<Fn>> extends () => any
? void
: Last<Parameters<Fn>> extends (arg: infer R) => any
? R
: Last<Parameters<Fn>> extends (...args: infer R) => any
? R
: never
>;

// tslint:enable:max-line-length

Expand Down
19 changes: 14 additions & 5 deletions src/internal/observable/bindNodeCallback.ts
@@ -1,6 +1,6 @@
/* @prettier */
import { Observable } from '../Observable';
import { SchedulerLike } from '../types';
import { SchedulerLike, AllButLast, OverloadedParameters, Last } from '../types';
import { bindCallbackInternals } from './bindCallbackInternals';

/** @deprecated resultSelector is deprecated, pipe to map instead */
Expand All @@ -10,11 +10,20 @@ export function bindNodeCallback(
scheduler?: SchedulerLike
): (...args: any[]) => Observable<any>;

// args is the arguments array and we push the callback on the rest tuple since the rest parameter must be last (only item) in a parameter list
export function bindNodeCallback<A extends readonly unknown[], R extends readonly unknown[]>(
callbackFunc: (...args: [...A, (err: any, ...res: R) => void]) => void,
export function bindNodeCallback<Fn extends (...args: any[]) => any>(
callbackFunc: Fn,
schedulerLike?: SchedulerLike
): (...arg: A) => Observable<R extends [] ? void : R extends [any] ? R[0] : R>;
): (
...arg: AllButLast<OverloadedParameters<Fn>>
) => Observable<
Last<OverloadedParameters<Fn>> extends (err: any) => any
? void
: Last<Parameters<Fn>> extends (err: any, arg: infer R) => any
? R
: Last<Parameters<Fn>> extends (err: any, ...args: infer R) => any
? R
: never
>;

/**
* Converts a Node.js-style callback API to a function that returns an
Expand Down
96 changes: 96 additions & 0 deletions src/internal/types.ts
Expand Up @@ -276,3 +276,99 @@ export type ValueFromNotification<T> = T extends { kind: 'N' | 'E' | 'C' }
export type Falsy = null | undefined | false | 0 | -0 | 0n | '';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benlesh Completely off topic, but shouldn't this include NaN?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, probably.

Copy link
Collaborator

@cartant cartant Mar 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NaN is not a type; it's a value, AFAICT; number is the type.


export type TruthyTypesOf<T> = T extends Falsy ? never : T;

/**
* One of those magic types that can extract types from an
* overloaded function. See https://github.com/ReactiveX/rxjs/issues/5942
* for more details
*/
type OverloadedArgumentsAndReturnType<T> = T extends {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this at all. haha. T extends a function that.. looks the same in 10 cases? But... isn't the same? But it's the same... but different.

same-but-different

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait... this is a type that is a "better Parameters<T>"? I still don't quite understand how it works.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is the magical overloaded function inference technique described at https://stackoverflow.com/questions/52760509/typescript-returntype-of-overloaded-function/52761156#52761156

(...args: infer A1): infer R;
(...args: infer A2): infer R;
(...args: infer A3): infer R;
(...args: infer A4): infer R;
(...args: infer A5): infer R;
(...args: infer A6): infer R;
(...args: infer A7): infer R;
(...args: infer A8): infer R;
(...args: infer A9): infer R;
(...args: infer A10): infer R;
}
? [A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10, R]
: T extends {
(...args: infer A1): infer R;
(...args: infer A2): infer R;
(...args: infer A3): infer R;
(...args: infer A4): infer R;
(...args: infer A5): infer R;
(...args: infer A6): infer R;
(...args: infer A7): infer R;
(...args: infer A8): infer R;
(...args: infer A9): infer R;
}
? [A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9, R]
: T extends {
(...args: infer A1): infer R;
(...args: infer A2): infer R;
(...args: infer A3): infer R;
(...args: infer A4): infer R;
(...args: infer A5): infer R;
(...args: infer A6): infer R;
(...args: infer A7): infer R;
(...args: infer A8): infer R;
}
? [A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8, R]
: T extends {
(...args: infer A1): infer R;
(...args: infer A2): infer R;
(...args: infer A3): infer R;
(...args: infer A4): infer R;
(...args: infer A5): infer R;
(...args: infer A6): infer R;
(...args: infer A7): infer R;
}
? [A1 | A2 | A3 | A4 | A5 | A6 | A7, R]
: T extends {
(...args: infer A1): infer R;
(...args: infer A2): infer R;
(...args: infer A3): infer R;
(...args: infer A4): infer R;
(...args: infer A5): infer R;
(...args: infer A6): infer R;
}
? [A1 | A2 | A3 | A4 | A5 | A6, R]
: T extends {
(...args: infer A1): infer R;
(...args: infer A2): infer R;
(...args: infer A3): infer R;
(...args: infer A4): infer R;
(...args: infer A5): infer R;
}
? [A1 | A2 | A3 | A4 | A5, R]
: T extends {
(...args: infer A1): infer R;
(...args: infer A2): infer R;
(...args: infer A3): infer R;
(...args: infer A4): infer R;
}
? [A1 | A2 | A3 | A4, R]
: T extends {
(...args: infer A1): infer R;
(...args: infer A2): infer R;
(...args: infer A3): infer R;
}
? [A1 | A2 | A3, R]
: T extends {
(...args: infer A1): infer R;
(...args: infer A2): infer R;
}
? [A1 | A2, R]
: T extends (...args: infer A) => infer R
? [A, R]
: never;

export type OverloadedParameters<T> = OverloadedArgumentsAndReturnType<T>[0];
export type OverloadedReturnType<T> = OverloadedArgumentsAndReturnType<T>[1];

export type AllButLast<T extends any[]> = T extends T ? (T extends [...(infer H), any] ? H : never) : never;
export type Last<T extends any[]> = [any, ...T][T['length']];