From c2ac39cd4636f493bb147b6ba5eb50fece8fc24d Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Tue, 8 Jan 2019 23:49:54 -0800 Subject: [PATCH] fix(types): support union type inference for merge operators --- spec-dtslint/observables/concat-spec.ts | 11 +++++++++++ spec-dtslint/operators/concatMap-spec.ts | 4 ++++ spec-dtslint/operators/concatMapTo-spec.ts | 5 +++++ spec-dtslint/operators/mergeMap-spec.ts | 4 ++++ spec-dtslint/operators/mergeMapTo-spec.ts | 5 +++++ src/internal/observable/concat.ts | 23 ++++++++++------------ src/internal/operators/concat.ts | 2 +- src/internal/operators/concatMap.ts | 16 +++++++-------- src/internal/operators/concatMapTo.ts | 16 +++++++-------- src/internal/operators/mergeAll.ts | 8 +++----- src/internal/operators/mergeMap.ts | 18 ++++++++--------- src/internal/operators/mergeMapTo.ts | 14 ++++++------- 12 files changed, 75 insertions(+), 51 deletions(-) diff --git a/spec-dtslint/observables/concat-spec.ts b/spec-dtslint/observables/concat-spec.ts index c3397910eb..3cfdf8af28 100644 --- a/spec-dtslint/observables/concat-spec.ts +++ b/spec-dtslint/observables/concat-spec.ts @@ -52,3 +52,14 @@ it('should enforce types', () => { const o = concat(5); // $ExpectError const p = concat(of(5), 6); // $ExpectError }); + +it('should support union types', () => { + const u = Math.random() > 0.5 ? of(123) : of('abc'); + const o = concat(u, u, u); // $ExpectType Observable +}); + +it('should support different union types', () => { + const u1 = Math.random() > 0.5 ? of(123) : of('abc'); + const u2 = Math.random() > 0.5 ? of(true) : of([1, 2, 3]); + const o = concat(u1, u2); // $ExpectType Observable +}); diff --git a/spec-dtslint/operators/concatMap-spec.ts b/spec-dtslint/operators/concatMap-spec.ts index 852ed5ecce..1724c484ca 100644 --- a/spec-dtslint/operators/concatMap-spec.ts +++ b/spec-dtslint/operators/concatMap-spec.ts @@ -29,6 +29,10 @@ it('should support an undefined resultSelector', () => { const o = of(1, 2, 3).pipe(concatMap(p => of(Boolean(p)), undefined)); // $ExpectType Observable }); +it('should support union-type projections', () => { + const o = of(Math.random()).pipe(concatMap(n => n > 0.5 ? of('life') : of(42))); // $ExpectType Observable +}); + it('should enforce types', () => { const o = of(1, 2, 3).pipe(concatMap()); // $ExpectError }); diff --git a/spec-dtslint/operators/concatMapTo-spec.ts b/spec-dtslint/operators/concatMapTo-spec.ts index 517f863e80..29b5b39613 100644 --- a/spec-dtslint/operators/concatMapTo-spec.ts +++ b/spec-dtslint/operators/concatMapTo-spec.ts @@ -37,6 +37,11 @@ it('should support an undefined resultSelector', () => { const o = of(1, 2, 3).pipe(concatMapTo(of('foo'), undefined)); // $ExpectType Observable }); +it('should support union types', () => { + const s = Math.random() > 0.5 ? of(123) : of('abc'); + const r = of(1, 2, 3).pipe(concatMapTo(s)); // $ExpectType +}); + it('should enforce types', () => { const o = of(1, 2, 3).pipe(concatMapTo()); // $ExpectError }); diff --git a/spec-dtslint/operators/mergeMap-spec.ts b/spec-dtslint/operators/mergeMap-spec.ts index 056a81f7d7..5eeb83199d 100644 --- a/spec-dtslint/operators/mergeMap-spec.ts +++ b/spec-dtslint/operators/mergeMap-spec.ts @@ -41,6 +41,10 @@ it('should support a undefined resultSelector and concurrent parameter', () => { const o = of(1, 2, 3).pipe(mergeMap(p => of(Boolean(p)), undefined, 4)); // $ExpectType Observable }); +it('should support union-type projections', () => { + const o = of(Math.random()).pipe(mergeMap(n => n > 0.5 ? of('life') : of(42))); // $ExpectType Observable +}); + it('should enforce types', () => { const o = of(1, 2, 3).pipe(mergeMap()); // $ExpectError }); diff --git a/spec-dtslint/operators/mergeMapTo-spec.ts b/spec-dtslint/operators/mergeMapTo-spec.ts index eaf1f18804..b3a4863d7a 100644 --- a/spec-dtslint/operators/mergeMapTo-spec.ts +++ b/spec-dtslint/operators/mergeMapTo-spec.ts @@ -41,6 +41,11 @@ it('should support a resultSelector and concurrent parameter', () => { const o = of(1, 2, 3).pipe(mergeMapTo(of('foo'), (a, b) => b, 4)); // $ExpectType Observable }); +it('should support union types', () => { + const s = Math.random() > 0.5 ? of(123) : of('abc'); + const r = of(1, 2, 3).pipe(mergeMapTo(s)); // $ExpectType +}); + it('should enforce types', () => { const o = of(1, 2, 3).pipe(mergeMapTo()); // $ExpectError }); diff --git a/src/internal/observable/concat.ts b/src/internal/observable/concat.ts index d98725dfa8..3fd86f9377 100644 --- a/src/internal/observable/concat.ts +++ b/src/internal/observable/concat.ts @@ -1,19 +1,19 @@ import { Observable } from '../Observable'; -import { ObservableInput, SchedulerLike } from '../types'; +import { ObservableInput, SchedulerLike, ObservedValueOf } from '../types'; import { isScheduler } from '../util/isScheduler'; import { of } from './of'; import { from } from './from'; import { concatAll } from '../operators/concatAll'; /* tslint:disable:max-line-length */ -export function concat(v1: ObservableInput, scheduler?: SchedulerLike): Observable; -export function concat(v1: ObservableInput, v2: ObservableInput, scheduler?: SchedulerLike): Observable; -export function concat(v1: ObservableInput, v2: ObservableInput, v3: ObservableInput, scheduler?: SchedulerLike): Observable; -export function concat(v1: ObservableInput, v2: ObservableInput, v3: ObservableInput, v4: ObservableInput, scheduler?: SchedulerLike): Observable; -export function concat(v1: ObservableInput, v2: ObservableInput, v3: ObservableInput, v4: ObservableInput, v5: ObservableInput, scheduler?: SchedulerLike): Observable; -export function concat(v1: ObservableInput, v2: ObservableInput, v3: ObservableInput, v4: ObservableInput, v5: ObservableInput, v6: ObservableInput, scheduler?: SchedulerLike): Observable; -export function concat(...observables: (ObservableInput | SchedulerLike)[]): Observable; -export function concat(...observables: (ObservableInput | SchedulerLike)[]): Observable; +export function concat>(v1: O1, scheduler?: SchedulerLike): Observable>; +export function concat, O2 extends ObservableInput>(v1: O1, v2: O2, scheduler?: SchedulerLike): Observable | ObservedValueOf>; +export function concat, O2 extends ObservableInput, O3 extends ObservableInput>(v1: O1, v2: O2, v3: O3, scheduler?: SchedulerLike): Observable | ObservedValueOf | ObservedValueOf>; +export function concat, O2 extends ObservableInput, O3 extends ObservableInput, O4 extends ObservableInput>(v1: O1, v2: O2, v3: O3, v4: O4, scheduler?: SchedulerLike): Observable | ObservedValueOf | ObservedValueOf | ObservedValueOf>; +export function concat, O2 extends ObservableInput, O3 extends ObservableInput, O4 extends ObservableInput, O5 extends ObservableInput>(v1: O1, v2: O2, v3: O3, v4: O4, v5: O5, scheduler?: SchedulerLike): Observable | ObservedValueOf | ObservedValueOf | ObservedValueOf | ObservedValueOf>; +export function concat, O2 extends ObservableInput, O3 extends ObservableInput, O4 extends ObservableInput, O5 extends ObservableInput, O6 extends ObservableInput>(v1: O1, v2: O2, v3: O3, v4: O4, v5: O5, v6: O6, scheduler?: SchedulerLike): Observable | ObservedValueOf | ObservedValueOf | ObservedValueOf | ObservedValueOf | ObservedValueOf>; +export function concat>(...observables: (O | SchedulerLike)[]): Observable>; +export function concat, R>(...observables: (ObservableInput | SchedulerLike)[]): Observable; /* tslint:enable:max-line-length */ /** * Creates an output Observable which sequentially emits all values from given @@ -113,9 +113,6 @@ export function concat(...observables: (ObservableInput | SchedulerLi * @name concat * @owner Observable */ -export function concat(...observables: Array | SchedulerLike>): Observable { - if (observables.length === 1 || (observables.length === 2 && isScheduler(observables[1]))) { - return from(observables[0]); - } +export function concat, R>(...observables: Array): Observable | R> { return concatAll()(of(...observables)); } diff --git a/src/internal/operators/concat.ts b/src/internal/operators/concat.ts index 0c1179c69c..aee64666f9 100644 --- a/src/internal/operators/concat.ts +++ b/src/internal/operators/concat.ts @@ -25,5 +25,5 @@ export function concat(...observables: Array | Schedu * @deprecated Deprecated in favor of static {@link concat}. */ export function concat(...observables: Array | SchedulerLike>): OperatorFunction { - return (source: Observable) => source.lift.call(concatStatic(source, ...observables)); + return (source: Observable) => source.lift.call(concatStatic(source, ...observables)); } diff --git a/src/internal/operators/concatMap.ts b/src/internal/operators/concatMap.ts index cc397052aa..c40afb3889 100644 --- a/src/internal/operators/concatMap.ts +++ b/src/internal/operators/concatMap.ts @@ -1,12 +1,12 @@ import { mergeMap } from './mergeMap'; -import { ObservableInput, OperatorFunction } from '../types'; +import { ObservableInput, OperatorFunction, ObservedValueOf } from '../types'; /* tslint:disable:max-line-length */ -export function concatMap(project: (value: T, index: number) => ObservableInput): OperatorFunction; +export function concatMap>(project: (value: T, index: number) => O): OperatorFunction>; /** @deprecated resultSelector no longer supported, use inner map instead */ -export function concatMap(project: (value: T, index: number) => ObservableInput, resultSelector: undefined): OperatorFunction; +export function concatMap>(project: (value: T, index: number) => O, resultSelector: undefined): OperatorFunction>; /** @deprecated resultSelector no longer supported, use inner map instead */ -export function concatMap(project: (value: T, index: number) => ObservableInput, resultSelector: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R): OperatorFunction; +export function concatMap>(project: (value: T, index: number) => O, resultSelector: (outerValue: T, innerValue: ObservedValueOf, outerIndex: number, innerIndex: number) => R): OperatorFunction; /* tslint:enable:max-line-length */ /** @@ -66,9 +66,9 @@ export function concatMap(project: (value: T, index: number) => Observ * @method concatMap * @owner Observable */ -export function concatMap( - project: (value: T, index: number) => ObservableInput, - resultSelector?: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R -): OperatorFunction { +export function concatMap>( + project: (value: T, index: number) => O, + resultSelector?: (outerValue: T, innerValue: ObservedValueOf, outerIndex: number, innerIndex: number) => R +): OperatorFunction|R> { return mergeMap(project, resultSelector, 1); } diff --git a/src/internal/operators/concatMapTo.ts b/src/internal/operators/concatMapTo.ts index 487f91b22b..627d4ec1cc 100644 --- a/src/internal/operators/concatMapTo.ts +++ b/src/internal/operators/concatMapTo.ts @@ -1,12 +1,12 @@ import { concatMap } from './concatMap'; -import { ObservableInput, OperatorFunction } from '../types'; +import { ObservableInput, OperatorFunction, ObservedValueOf } from '../types'; /* tslint:disable:max-line-length */ -export function concatMapTo(observable: ObservableInput): OperatorFunction; +export function concatMapTo>(observable: O): OperatorFunction>; /** @deprecated */ -export function concatMapTo(observable: ObservableInput, resultSelector: undefined): OperatorFunction; +export function concatMapTo>(observable: O, resultSelector: undefined): OperatorFunction>; /** @deprecated */ -export function concatMapTo(observable: ObservableInput, resultSelector: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R): OperatorFunction; +export function concatMapTo>(observable: O, resultSelector: (outerValue: T, innerValue: ObservedValueOf, outerIndex: number, innerIndex: number) => R): OperatorFunction; /* tslint:enable:max-line-length */ /** @@ -62,9 +62,9 @@ export function concatMapTo(observable: ObservableInput, resultSelec * @method concatMapTo * @owner Observable */ -export function concatMapTo( - innerObservable: ObservableInput, - resultSelector?: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R -): OperatorFunction { +export function concatMapTo>( + innerObservable: O, + resultSelector?: (outerValue: T, innerValue: ObservedValueOf, outerIndex: number, innerIndex: number) => R +): OperatorFunction|R> { return concatMap(() => innerObservable, resultSelector); } diff --git a/src/internal/operators/mergeAll.ts b/src/internal/operators/mergeAll.ts index bf8ea5f7bd..4aab502652 100644 --- a/src/internal/operators/mergeAll.ts +++ b/src/internal/operators/mergeAll.ts @@ -1,9 +1,7 @@ import { mergeMap } from './mergeMap'; import { identity } from '../util/identity'; -import { MonoTypeOperatorFunction, OperatorFunction, ObservableInput } from '../types'; - -export function mergeAll(concurrent?: number): OperatorFunction, T>; +import { OperatorFunction, ObservableInput } from '../types'; /** * Converts a higher-order Observable into a first-order Observable which @@ -57,6 +55,6 @@ export function mergeAll(concurrent?: number): OperatorFunction(concurrent: number = Number.POSITIVE_INFINITY): MonoTypeOperatorFunction { - return mergeMap(identity as (value: T, index: number) => ObservableInput, concurrent); +export function mergeAll(concurrent: number = Number.POSITIVE_INFINITY): OperatorFunction, T> { + return mergeMap(identity, concurrent); } diff --git a/src/internal/operators/mergeMap.ts b/src/internal/operators/mergeMap.ts index d843dcdc97..a5b963bcc2 100644 --- a/src/internal/operators/mergeMap.ts +++ b/src/internal/operators/mergeMap.ts @@ -5,16 +5,16 @@ import { Subscription } from '../Subscription'; import { subscribeToResult } from '../util/subscribeToResult'; import { OuterSubscriber } from '../OuterSubscriber'; import { InnerSubscriber } from '../InnerSubscriber'; -import { ObservableInput, OperatorFunction } from '../types'; +import { ObservableInput, OperatorFunction, ObservedValueOf } from '../types'; import { map } from './map'; import { from } from '../observable/from'; /* tslint:disable:max-line-length */ -export function mergeMap(project: (value: T, index: number) => ObservableInput, concurrent?: number): OperatorFunction; +export function mergeMap>(project: (value: T, index: number) => O, concurrent?: number): OperatorFunction>; /** @deprecated resultSelector no longer supported, use inner map instead */ -export function mergeMap(project: (value: T, index: number) => ObservableInput, resultSelector: undefined, concurrent?: number): OperatorFunction; +export function mergeMap>(project: (value: T, index: number) => O, resultSelector: undefined, concurrent?: number): OperatorFunction>; /** @deprecated resultSelector no longer supported, use inner map instead */ -export function mergeMap(project: (value: T, index: number) => ObservableInput, resultSelector: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R, concurrent?: number): OperatorFunction; +export function mergeMap>(project: (value: T, index: number) => O, resultSelector: (outerValue: T, innerValue: ObservedValueOf, outerIndex: number, innerIndex: number) => R, concurrent?: number): OperatorFunction; /* tslint:enable:max-line-length */ /** @@ -70,16 +70,16 @@ export function mergeMap(project: (value: T, index: number) => Observab * @method mergeMap * @owner Observable */ -export function mergeMap( - project: (value: T, index: number) => ObservableInput, - resultSelector?: ((outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R) | number, +export function mergeMap>( + project: (value: T, index: number) => O, + resultSelector?: ((outerValue: T, innerValue: ObservedValueOf, outerIndex: number, innerIndex: number) => R) | number, concurrent: number = Number.POSITIVE_INFINITY -): OperatorFunction { +): OperatorFunction|R> { if (typeof resultSelector === 'function') { // DEPRECATED PATH return (source: Observable) => source.pipe( mergeMap((a, i) => from(project(a, i)).pipe( - map((b, ii) => resultSelector(a, b, i, ii)), + map((b: any, ii: number) => resultSelector(a, b, i, ii)), ), concurrent) ); } else if (typeof resultSelector === 'number') { diff --git a/src/internal/operators/mergeMapTo.ts b/src/internal/operators/mergeMapTo.ts index 57186e253a..65e34b0ef4 100644 --- a/src/internal/operators/mergeMapTo.ts +++ b/src/internal/operators/mergeMapTo.ts @@ -1,12 +1,12 @@ import { Observable } from '../Observable'; -import { OperatorFunction } from '../../internal/types'; +import { OperatorFunction, ObservedValueOf } from '../../internal/types'; import { mergeMap } from './mergeMap'; import { ObservableInput } from '../types'; /* tslint:disable:max-line-length */ -export function mergeMapTo(innerObservable: ObservableInput, concurrent?: number): OperatorFunction; +export function mergeMapTo>(innerObservable: O, concurrent?: number): OperatorFunction>; /** @deprecated */ -export function mergeMapTo(innerObservable: ObservableInput, resultSelector: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R, concurrent?: number): OperatorFunction; +export function mergeMapTo>(innerObservable: O, resultSelector: (outerValue: T, innerValue: ObservedValueOf, outerIndex: number, innerIndex: number) => R, concurrent?: number): OperatorFunction; /* tslint:enable:max-line-length */ /** @@ -46,11 +46,11 @@ export function mergeMapTo(innerObservable: ObservableInput, resultS * @method mergeMapTo * @owner Observable */ -export function mergeMapTo( - innerObservable: ObservableInput, - resultSelector?: ((outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R) | number, +export function mergeMapTo>( + innerObservable: O, + resultSelector?: ((outerValue: T, innerValue: ObservedValueOf, outerIndex: number, innerIndex: number) => R) | number, concurrent: number = Number.POSITIVE_INFINITY -): OperatorFunction { +): OperatorFunction|R> { if (typeof resultSelector === 'function') { return mergeMap(() => innerObservable, resultSelector, concurrent); }