From 43da0a26b6fc19277a2cccd9fd355f62234ec725 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Wed, 14 Apr 2021 17:45:46 -0500 Subject: [PATCH 1/4] fix: forkJoin/combineLatest return Observable if passed any Resolves #6226 --- api_guard/dist/types/index.d.ts | 2 ++ spec-dtslint/observables/combineLatest-spec.ts | 5 +++++ spec-dtslint/observables/forkJoin-spec.ts | 5 +++++ src/internal/AnyCatcher.ts | 3 +++ src/internal/observable/combineLatest.ts | 2 ++ src/internal/observable/forkJoin.ts | 2 ++ 6 files changed, 19 insertions(+) create mode 100644 src/internal/AnyCatcher.ts diff --git a/api_guard/dist/types/index.d.ts b/api_guard/dist/types/index.d.ts index 71d8c83c8d..f35ba1690c 100644 --- a/api_guard/dist/types/index.d.ts +++ b/api_guard/dist/types/index.d.ts @@ -52,6 +52,7 @@ export declare function combineLatest(...sourcesAn export declare function combineLatest(sourcesObject: { [K in any]: never; }): Observable; +export declare function combineLatest(arg: T): Observable; export declare function combineLatest>>(sourcesObject: T): Observable<{ [K in keyof T]: ObservedValueOf; }>; @@ -134,6 +135,7 @@ export declare function forkJoin(...sourcesAndR export declare function forkJoin(sourcesObject: { [K in any]: never; }): Observable; +export declare function forkJoin(arg: T): Observable; export declare function forkJoin>>(sourcesObject: T): Observable<{ [K in keyof T]: ObservedValueOf; }>; diff --git a/spec-dtslint/observables/combineLatest-spec.ts b/spec-dtslint/observables/combineLatest-spec.ts index e3462f9c1a..2ebb408c98 100644 --- a/spec-dtslint/observables/combineLatest-spec.ts +++ b/spec-dtslint/observables/combineLatest-spec.ts @@ -147,3 +147,8 @@ describe('combineLatest({})', () => { const res = combineLatest(obj); // $ExpectError }); }); + +it('should take in any and return Observable because we do not know if it is an array or object', () => { + const arg: any = null; + const res = combineLatest(arg); // $ExpectType Observable +}); \ No newline at end of file diff --git a/spec-dtslint/observables/forkJoin-spec.ts b/spec-dtslint/observables/forkJoin-spec.ts index 226a731e8a..6d2f2e0f9d 100644 --- a/spec-dtslint/observables/forkJoin-spec.ts +++ b/spec-dtslint/observables/forkJoin-spec.ts @@ -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 + }) }); diff --git a/src/internal/AnyCatcher.ts b/src/internal/AnyCatcher.ts new file mode 100644 index 0000000000..efc4d98208 --- /dev/null +++ b/src/internal/AnyCatcher.ts @@ -0,0 +1,3 @@ +export interface AnyCatcher { + '@@@@@@CATCH_ANY_WITH_THIS@@@@@@@@': 'wat'; +} diff --git a/src/internal/observable/combineLatest.ts b/src/internal/observable/combineLatest.ts index df116b24a2..6b29188c86 100644 --- a/src/internal/observable/combineLatest.ts +++ b/src/internal/observable/combineLatest.ts @@ -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; @@ -47,6 +48,7 @@ export function combineLatest( // combineLatest({a, b, c}) export function combineLatest(sourcesObject: { [K in any]: never }): Observable; +export function combineLatest(arg: T): Observable; export function combineLatest>>( sourcesObject: T ): Observable<{ [K in keyof T]: ObservedValueOf }>; diff --git a/src/internal/observable/forkJoin.ts b/src/internal/observable/forkJoin.ts index 6b771ce938..c6cad54d51 100644 --- a/src/internal/observable/forkJoin.ts +++ b/src/internal/observable/forkJoin.ts @@ -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; @@ -27,6 +28,7 @@ export function forkJoin( // forkJoin({a, b, c}) export function forkJoin(sourcesObject: { [K in any]: never }): Observable; +export function forkJoin(arg: T): Observable; export function forkJoin>>( sourcesObject: T ): Observable<{ [K in keyof T]: ObservedValueOf }>; From f8d7fc5e805ba81e650b3973e06903836fe9ca84 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Wed, 14 Apr 2021 19:24:38 -0500 Subject: [PATCH 2/4] chore: make shenanigans readonly Co-authored-by: Daniel Rosenwasser --- src/internal/AnyCatcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal/AnyCatcher.ts b/src/internal/AnyCatcher.ts index efc4d98208..abfe5553f6 100644 --- a/src/internal/AnyCatcher.ts +++ b/src/internal/AnyCatcher.ts @@ -1,3 +1,3 @@ export interface AnyCatcher { - '@@@@@@CATCH_ANY_WITH_THIS@@@@@@@@': 'wat'; + readonly ' @@@@@@CATCH_ANY_WITH_THIS@@@@@@@@': 'wat'; } From 951e74a78bdfc823de0ac7324d7649d9859b96f2 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Wed, 14 Apr 2021 19:33:56 -0500 Subject: [PATCH 3/4] chore: Ensure there is no way the arg should work If the user passed that exact shape of object in AnyCatcher, technically it should work. This ensures there's no way it would work properly so `Observable` is accurate. Also adds some documentation to the areas --- src/internal/AnyCatcher.ts | 10 +++++++++- src/internal/observable/combineLatest.ts | 5 +++++ src/internal/observable/forkJoin.ts | 5 +++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/internal/AnyCatcher.ts b/src/internal/AnyCatcher.ts index abfe5553f6..4aa013771a 100644 --- a/src/internal/AnyCatcher.ts +++ b/src/internal/AnyCatcher.ts @@ -1,3 +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` or an `Observable<{ [key: K]: T }>`, + * so `forkJoin(any)` would mean we need to return `Observable`. + * + * @internal + */ export interface AnyCatcher { - readonly ' @@@@@@CATCH_ANY_WITH_THIS@@@@@@@@': 'wat'; + readonly ' @@@@@@CATCH_ANY_WITH_THIS@@@@@@': false; } diff --git a/src/internal/observable/combineLatest.ts b/src/internal/observable/combineLatest.ts index 6b29188c86..8228c7db42 100644 --- a/src/internal/observable/combineLatest.ts +++ b/src/internal/observable/combineLatest.ts @@ -48,6 +48,11 @@ export function combineLatest( // combineLatest({a, b, c}) export function combineLatest(sourcesObject: { [K in any]: never }): Observable; +/** + * 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(arg: T): Observable; export function combineLatest>>( sourcesObject: T diff --git a/src/internal/observable/forkJoin.ts b/src/internal/observable/forkJoin.ts index c6cad54d51..d8c8964a5f 100644 --- a/src/internal/observable/forkJoin.ts +++ b/src/internal/observable/forkJoin.ts @@ -28,6 +28,11 @@ export function forkJoin( // forkJoin({a, b, c}) export function forkJoin(sourcesObject: { [K in any]: never }): Observable; +/** + * 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(arg: T): Observable; export function forkJoin>>( sourcesObject: T From c6ff1c9a52e2f07dc5fca8df81c81d58a52c0b83 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Thu, 15 Apr 2021 08:23:54 -0500 Subject: [PATCH 4/4] refactor: Move any catcher signatures up --- api_guard/dist/types/index.d.ts | 4 ++-- spec-dtslint/observables/of-spec.ts | 6 ++++++ src/internal/AnyCatcher.ts | 5 ++--- src/internal/observable/combineLatest.ts | 19 +++++++++++++------ src/internal/observable/forkJoin.ts | 20 ++++++++++++++------ 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/api_guard/dist/types/index.d.ts b/api_guard/dist/types/index.d.ts index f35ba1690c..9667748ba4 100644 --- a/api_guard/dist/types/index.d.ts +++ b/api_guard/dist/types/index.d.ts @@ -40,6 +40,7 @@ export declare function bindCallback void, resultSelector: (...args: any[]) => any, scheduler?: SchedulerLike): (...args: any[]) => Observable; export declare function bindNodeCallback(callbackFunc: (...args: [...A, (err: any, ...res: R) => void]) => void, schedulerLike?: SchedulerLike): (...arg: A) => Observable; +export declare function combineLatest(arg: T): Observable; export declare function combineLatest(sources: []): Observable; export declare function combineLatest(sources: readonly [...ObservableInputTuple]): Observable; export declare function combineLatest(sources: readonly [...ObservableInputTuple], resultSelector: (...values: A) => R, scheduler: SchedulerLike): Observable; @@ -52,7 +53,6 @@ export declare function combineLatest(...sourcesAn export declare function combineLatest(sourcesObject: { [K in any]: never; }): Observable; -export declare function combineLatest(arg: T): Observable; export declare function combineLatest>>(sourcesObject: T): Observable<{ [K in keyof T]: ObservedValueOf; }>; @@ -126,6 +126,7 @@ export declare type Falsy = null | undefined | false | 0 | -0 | 0n | ''; export declare function firstValueFrom(source: Observable): Promise; +export declare function forkJoin(arg: T): Observable; export declare function forkJoin(scheduler: null | undefined): Observable; export declare function forkJoin(sources: readonly []): Observable; export declare function forkJoin(sources: readonly [...ObservableInputTuple]): Observable; @@ -135,7 +136,6 @@ export declare function forkJoin(...sourcesAndR export declare function forkJoin(sourcesObject: { [K in any]: never; }): Observable; -export declare function forkJoin(arg: T): Observable; export declare function forkJoin>>(sourcesObject: T): Observable<{ [K in keyof T]: ObservedValueOf; }>; diff --git a/spec-dtslint/observables/of-spec.ts b/spec-dtslint/observables/of-spec.ts index c2fad5f300..58a6133586 100644 --- a/spec-dtslint/observables/of-spec.ts +++ b/spec-dtslint/observables/of-spec.ts @@ -136,4 +136,10 @@ it('should deprecate correctly', () => { of(a, b); // $ExpectNoDeprecation of(a, b, c); // $ExpectNoDeprecation of(a, b, c, d); // $ExpectNoDeprecation +}); + +it('should handle null and undefined properly', () => { + const a = of(undefined); // $ExpectType Observable + const b = of(null); // $ExpectType Observable + const c = [of(1), of(2), of(undefined), of(3)] as const; }); \ No newline at end of file diff --git a/src/internal/AnyCatcher.ts b/src/internal/AnyCatcher.ts index 4aa013771a..b42ff7a7b8 100644 --- a/src/internal/AnyCatcher.ts +++ b/src/internal/AnyCatcher.ts @@ -6,6 +6,5 @@ * * @internal */ -export interface AnyCatcher { - readonly ' @@@@@@CATCH_ANY_WITH_THIS@@@@@@': false; -} +declare const anyCatcherSymbol: unique symbol; +export type AnyCatcher = typeof anyCatcherSymbol; diff --git a/src/internal/observable/combineLatest.ts b/src/internal/observable/combineLatest.ts index 8228c7db42..b915b4192f 100644 --- a/src/internal/observable/combineLatest.ts +++ b/src/internal/observable/combineLatest.ts @@ -11,6 +11,19 @@ import { createObject } from '../util/createObject'; import { OperatorSubscriber } from '../operators/OperatorSubscriber'; import { AnyCatcher } from '../AnyCatcher'; +// combineLatest(any) +// We put this first because we need to catch cases where the user has supplied +// _exactly `any`_ as the argument. Since `any` literally matches _anything_, +// we don't want it to randomly hit one of the other type signatures below, +// as we have no idea at build-time what type we should be returning when given an any. + +/** + * 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(arg: T): Observable; + // combineLatest([a, b, c]) export function combineLatest(sources: []): Observable; export function combineLatest(sources: readonly [...ObservableInputTuple]): Observable; @@ -48,12 +61,6 @@ export function combineLatest( // combineLatest({a, b, c}) export function combineLatest(sourcesObject: { [K in any]: never }): Observable; -/** - * 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(arg: T): Observable; export function combineLatest>>( sourcesObject: T ): Observable<{ [K in keyof T]: ObservedValueOf }>; diff --git a/src/internal/observable/forkJoin.ts b/src/internal/observable/forkJoin.ts index d8c8964a5f..d8d0e56242 100644 --- a/src/internal/observable/forkJoin.ts +++ b/src/internal/observable/forkJoin.ts @@ -8,6 +8,20 @@ import { mapOneOrManyArgs } from '../util/mapOneOrManyArgs'; import { createObject } from '../util/createObject'; import { AnyCatcher } from '../AnyCatcher'; +// forkJoin(any) +// We put this first because we need to catch cases where the user has supplied +// _exactly `any`_ as the argument. Since `any` literally matches _anything_, +// we don't want it to randomly hit one of the other type signatures below, +// as we have no idea at build-time what type we should be returning when given an any. + +/** + * 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(arg: T): Observable; + +// forkJoin(null | undefined) export function forkJoin(scheduler: null | undefined): Observable; // forkJoin([a, b, c]) @@ -28,12 +42,6 @@ export function forkJoin( // forkJoin({a, b, c}) export function forkJoin(sourcesObject: { [K in any]: never }): Observable; -/** - * 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(arg: T): Observable; export function forkJoin>>( sourcesObject: T ): Observable<{ [K in keyof T]: ObservedValueOf }>;