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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: forkJoin/combineLatest return Observable<unknown> if passed any #6227

Merged
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions api_guard/dist/types/index.d.ts
Expand Up @@ -52,6 +52,7 @@ export declare function combineLatest<A extends readonly unknown[]>(...sourcesAn
export declare function combineLatest(sourcesObject: {
[K in any]: never;
}): Observable<never>;
export declare function combineLatest<T extends AnyCatcher>(arg: T): Observable<unknown>;
export declare function combineLatest<T extends Record<string, ObservableInput<any>>>(sourcesObject: T): Observable<{
[K in keyof T]: ObservedValueOf<T[K]>;
}>;
Expand Down Expand Up @@ -134,6 +135,7 @@ export declare function forkJoin<A extends readonly unknown[], R>(...sourcesAndR
export declare function forkJoin(sourcesObject: {
[K in any]: never;
}): Observable<never>;
export declare function forkJoin<T extends AnyCatcher>(arg: T): Observable<unknown>;
export declare function forkJoin<T extends Record<string, ObservableInput<any>>>(sourcesObject: T): Observable<{
[K in keyof T]: ObservedValueOf<T[K]>;
}>;
Expand Down
5 changes: 5 additions & 0 deletions spec-dtslint/observables/combineLatest-spec.ts
Expand Up @@ -147,3 +147,8 @@ describe('combineLatest({})', () => {
const res = combineLatest(obj); // $ExpectError
});
});

it('should take in any and return Observable<unknown> because we do not know if it is an array or object', () => {
const arg: any = null;
const res = combineLatest(arg); // $ExpectType Observable<unknown>
});
5 changes: 5 additions & 0 deletions spec-dtslint/observables/forkJoin-spec.ts
Expand Up @@ -117,4 +117,9 @@ describe('forkJoin([])', () => {
it('should force user cast for array of 6+ observables', () => {
const res = forkJoin([of(1, 2, 3), of('a', 'b', 'c'), of(1, 2, 3), of(1, 2, 3), of(1, 2, 3), of(1, 2, 3), of(1, 2, 3)]); // $ExpectType Observable<[number, string, number, number, number, number, number]>
});

it('should return unknown for argument of any', () => {
const arg: any = null;
const res = forkJoin(arg); // $ExpectType Observable<unknown>
})
});
11 changes: 11 additions & 0 deletions src/internal/AnyCatcher.ts
@@ -0,0 +1,11 @@
/**
* This is just a type that we're using to identify `any` being passed to
* function overloads. This is used because of situations like {@link forkJoin},
* where it could return an `Observable<T[]>` or an `Observable<{ [key: K]: T }>`,
* so `forkJoin(any)` would mean we need to return `Observable<unknown>`.
*
* @internal
*/
export interface AnyCatcher {
benlesh marked this conversation as resolved.
Show resolved Hide resolved
readonly ' @@@@@@CATCH_ANY_WITH_THIS@@@@@@': false;
}
7 changes: 7 additions & 0 deletions src/internal/observable/combineLatest.ts
Expand Up @@ -9,6 +9,7 @@ import { mapOneOrManyArgs } from '../util/mapOneOrManyArgs';
import { popResultSelector, popScheduler } from '../util/args';
import { createObject } from '../util/createObject';
import { OperatorSubscriber } from '../operators/OperatorSubscriber';
import { AnyCatcher } from '../AnyCatcher';

// combineLatest([a, b, c])
export function combineLatest(sources: []): Observable<never>;
Expand Down Expand Up @@ -47,6 +48,12 @@ export function combineLatest<A extends readonly unknown[]>(

// combineLatest({a, b, c})
export function combineLatest(sourcesObject: { [K in any]: never }): Observable<never>;
/**
* You have passed `any` here, we can't figure out if it is
* an array or an object, so you're getting `unknown`. Use better types.
* @param arg Something typed as `any`
*/
export function combineLatest<T extends AnyCatcher>(arg: T): Observable<unknown>;
Copy link
Collaborator

@cartant cartant Apr 15, 2021

Choose a reason for hiding this comment

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

suggestion: I would make this the first signature. IDK why any skips all of the signatures above this one and I would not like to rely upon that behaviour never changing. There should be no reason this signature cannot be the first.

Actually, I do know why, but I still think the weirdo, any-catcher signatures should be first - just in case someone adds an additional signature before it and effects different behaviour. The tests would break of, course, but it might not be immediately obvious why. I think the any-catcher sigs should go first and should be accompanied by a comment that explains why. Or at least says they should remain as the first-placed overload signatures.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a really good idea!

export function combineLatest<T extends Record<string, ObservableInput<any>>>(
sourcesObject: T
): Observable<{ [K in keyof T]: ObservedValueOf<T[K]> }>;
Expand Down
7 changes: 7 additions & 0 deletions src/internal/observable/forkJoin.ts
Expand Up @@ -6,6 +6,7 @@ import { popResultSelector } from '../util/args';
import { OperatorSubscriber } from '../operators/OperatorSubscriber';
import { mapOneOrManyArgs } from '../util/mapOneOrManyArgs';
import { createObject } from '../util/createObject';
import { AnyCatcher } from '../AnyCatcher';

export function forkJoin(scheduler: null | undefined): Observable<never>;

Expand All @@ -27,6 +28,12 @@ export function forkJoin<A extends readonly unknown[], R>(

// forkJoin({a, b, c})
export function forkJoin(sourcesObject: { [K in any]: never }): Observable<never>;
/**
* You have passed `any` here, we can't figure out if it is
* an array or an object, so you're getting `unknown`. Use better types.
* @param arg Something typed as `any`
*/
export function forkJoin<T extends AnyCatcher>(arg: T): Observable<unknown>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

suggestion: Make this the first overload signature.

export function forkJoin<T extends Record<string, ObservableInput<any>>>(
sourcesObject: T
): Observable<{ [K in keyof T]: ObservedValueOf<T[K]> }>;
Expand Down