Skip to content

Commit

Permalink
fix(types): support union type inference for merge operators
Browse files Browse the repository at this point in the history
  • Loading branch information
benlesh committed Jan 9, 2019
1 parent ebfb11a commit c2ac39c
Show file tree
Hide file tree
Showing 12 changed files with 75 additions and 51 deletions.
11 changes: 11 additions & 0 deletions spec-dtslint/observables/concat-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string | number>
});

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<string | number | boolean | number[]>
});
4 changes: 4 additions & 0 deletions spec-dtslint/operators/concatMap-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>
});

it('should support union-type projections', () => {
const o = of(Math.random()).pipe(concatMap(n => n > 0.5 ? of('life') : of(42))); // $ExpectType Observable<string | number>
});

it('should enforce types', () => {
const o = of(1, 2, 3).pipe(concatMap()); // $ExpectError
});
Expand Down
5 changes: 5 additions & 0 deletions spec-dtslint/operators/concatMapTo-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ it('should support an undefined resultSelector', () => {
const o = of(1, 2, 3).pipe(concatMapTo(of('foo'), undefined)); // $ExpectType Observable<string>
});

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<string | number>
});

it('should enforce types', () => {
const o = of(1, 2, 3).pipe(concatMapTo()); // $ExpectError
});
Expand Down
4 changes: 4 additions & 0 deletions spec-dtslint/operators/mergeMap-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>
});

it('should support union-type projections', () => {
const o = of(Math.random()).pipe(mergeMap(n => n > 0.5 ? of('life') : of(42))); // $ExpectType Observable<string | number>
});

it('should enforce types', () => {
const o = of(1, 2, 3).pipe(mergeMap()); // $ExpectError
});
Expand Down
5 changes: 5 additions & 0 deletions spec-dtslint/operators/mergeMapTo-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>
});

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<string | number>
});

it('should enforce types', () => {
const o = of(1, 2, 3).pipe(mergeMapTo()); // $ExpectError
});
Expand Down
23 changes: 10 additions & 13 deletions src/internal/observable/concat.ts
Original file line number Diff line number Diff line change
@@ -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<T>(v1: ObservableInput<T>, scheduler?: SchedulerLike): Observable<T>;
export function concat<T, T2>(v1: ObservableInput<T>, v2: ObservableInput<T2>, scheduler?: SchedulerLike): Observable<T | T2>;
export function concat<T, T2, T3>(v1: ObservableInput<T>, v2: ObservableInput<T2>, v3: ObservableInput<T3>, scheduler?: SchedulerLike): Observable<T | T2 | T3>;
export function concat<T, T2, T3, T4>(v1: ObservableInput<T>, v2: ObservableInput<T2>, v3: ObservableInput<T3>, v4: ObservableInput<T4>, scheduler?: SchedulerLike): Observable<T | T2 | T3 | T4>;
export function concat<T, T2, T3, T4, T5>(v1: ObservableInput<T>, v2: ObservableInput<T2>, v3: ObservableInput<T3>, v4: ObservableInput<T4>, v5: ObservableInput<T5>, scheduler?: SchedulerLike): Observable<T | T2 | T3 | T4 | T5>;
export function concat<T, T2, T3, T4, T5, T6>(v1: ObservableInput<T>, v2: ObservableInput<T2>, v3: ObservableInput<T3>, v4: ObservableInput<T4>, v5: ObservableInput<T5>, v6: ObservableInput<T6>, scheduler?: SchedulerLike): Observable<T | T2 | T3 | T4 | T5 | T6>;
export function concat<T>(...observables: (ObservableInput<T> | SchedulerLike)[]): Observable<T>;
export function concat<T, R>(...observables: (ObservableInput<any> | SchedulerLike)[]): Observable<R>;
export function concat<O1 extends ObservableInput<any>>(v1: O1, scheduler?: SchedulerLike): Observable<ObservedValueOf<O1>>;
export function concat<O1 extends ObservableInput<any>, O2 extends ObservableInput<any>>(v1: O1, v2: O2, scheduler?: SchedulerLike): Observable<ObservedValueOf<O1> | ObservedValueOf<O2>>;
export function concat<O1 extends ObservableInput<any>, O2 extends ObservableInput<any>, O3 extends ObservableInput<any>>(v1: O1, v2: O2, v3: O3, scheduler?: SchedulerLike): Observable<ObservedValueOf<O1> | ObservedValueOf<O2> | ObservedValueOf<O3>>;
export function concat<O1 extends ObservableInput<any>, O2 extends ObservableInput<any>, O3 extends ObservableInput<any>, O4 extends ObservableInput<any>>(v1: O1, v2: O2, v3: O3, v4: O4, scheduler?: SchedulerLike): Observable<ObservedValueOf<O1> | ObservedValueOf<O2> | ObservedValueOf<O3> | ObservedValueOf<O4>>;
export function concat<O1 extends ObservableInput<any>, O2 extends ObservableInput<any>, O3 extends ObservableInput<any>, O4 extends ObservableInput<any>, O5 extends ObservableInput<any>>(v1: O1, v2: O2, v3: O3, v4: O4, v5: O5, scheduler?: SchedulerLike): Observable<ObservedValueOf<O1> | ObservedValueOf<O2> | ObservedValueOf<O3> | ObservedValueOf<O4> | ObservedValueOf<O5>>;
export function concat<O1 extends ObservableInput<any>, O2 extends ObservableInput<any>, O3 extends ObservableInput<any>, O4 extends ObservableInput<any>, O5 extends ObservableInput<any>, O6 extends ObservableInput<any>>(v1: O1, v2: O2, v3: O3, v4: O4, v5: O5, v6: O6, scheduler?: SchedulerLike): Observable<ObservedValueOf<O1> | ObservedValueOf<O2> | ObservedValueOf<O3> | ObservedValueOf<O4> | ObservedValueOf<O5> | ObservedValueOf<O6>>;
export function concat<O extends ObservableInput<any>>(...observables: (O | SchedulerLike)[]): Observable<ObservedValueOf<O>>;
export function concat<O1 extends ObservableInput<any>, R>(...observables: (ObservableInput<any> | SchedulerLike)[]): Observable<R>;
/* tslint:enable:max-line-length */
/**
* Creates an output Observable which sequentially emits all values from given
Expand Down Expand Up @@ -113,9 +113,6 @@ export function concat<T, R>(...observables: (ObservableInput<any> | SchedulerLi
* @name concat
* @owner Observable
*/
export function concat<T, R>(...observables: Array<ObservableInput<any> | SchedulerLike>): Observable<R> {
if (observables.length === 1 || (observables.length === 2 && isScheduler(observables[1]))) {
return from(<any>observables[0]);
}
export function concat<O extends ObservableInput<any>, R>(...observables: Array<O | SchedulerLike>): Observable<ObservedValueOf<O> | R> {
return concatAll<R>()(of(...observables));
}
2 changes: 1 addition & 1 deletion src/internal/operators/concat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ export function concat<T, R>(...observables: Array<ObservableInput<any> | Schedu
* @deprecated Deprecated in favor of static {@link concat}.
*/
export function concat<T, R>(...observables: Array<ObservableInput<any> | SchedulerLike>): OperatorFunction<T, R> {
return (source: Observable<T>) => source.lift.call(concatStatic<T, R>(source, ...observables));
return (source: Observable<T>) => source.lift.call(concatStatic(source, ...observables));
}
16 changes: 8 additions & 8 deletions src/internal/operators/concatMap.ts
Original file line number Diff line number Diff line change
@@ -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<T, R>(project: (value: T, index: number) => ObservableInput<R>): OperatorFunction<T, R>;
export function concatMap<T, O extends ObservableInput<any>>(project: (value: T, index: number) => O): OperatorFunction<T, ObservedValueOf<O>>;
/** @deprecated resultSelector no longer supported, use inner map instead */
export function concatMap<T, R>(project: (value: T, index: number) => ObservableInput<R>, resultSelector: undefined): OperatorFunction<T, R>;
export function concatMap<T, O extends ObservableInput<any>>(project: (value: T, index: number) => O, resultSelector: undefined): OperatorFunction<T, ObservedValueOf<O>>;
/** @deprecated resultSelector no longer supported, use inner map instead */
export function concatMap<T, I, R>(project: (value: T, index: number) => ObservableInput<I>, resultSelector: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R): OperatorFunction<T, R>;
export function concatMap<T, R, O extends ObservableInput<any>>(project: (value: T, index: number) => O, resultSelector: (outerValue: T, innerValue: ObservedValueOf<O>, outerIndex: number, innerIndex: number) => R): OperatorFunction<T, R>;
/* tslint:enable:max-line-length */

/**
Expand Down Expand Up @@ -66,9 +66,9 @@ export function concatMap<T, I, R>(project: (value: T, index: number) => Observ
* @method concatMap
* @owner Observable
*/
export function concatMap<T, I, R>(
project: (value: T, index: number) => ObservableInput<I>,
resultSelector?: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R
): OperatorFunction<T, I|R> {
export function concatMap<T, R, O extends ObservableInput<any>>(
project: (value: T, index: number) => O,
resultSelector?: (outerValue: T, innerValue: ObservedValueOf<O>, outerIndex: number, innerIndex: number) => R
): OperatorFunction<T, ObservedValueOf<O>|R> {
return mergeMap(project, resultSelector, 1);
}
16 changes: 8 additions & 8 deletions src/internal/operators/concatMapTo.ts
Original file line number Diff line number Diff line change
@@ -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<T>(observable: ObservableInput<T>): OperatorFunction<any, T>;
export function concatMapTo<T, O extends ObservableInput<any>>(observable: O): OperatorFunction<T, ObservedValueOf<O>>;
/** @deprecated */
export function concatMapTo<T>(observable: ObservableInput<T>, resultSelector: undefined): OperatorFunction<any, T>;
export function concatMapTo<T, O extends ObservableInput<any>>(observable: O, resultSelector: undefined): OperatorFunction<T, ObservedValueOf<O>>;
/** @deprecated */
export function concatMapTo<T, I, R>(observable: ObservableInput<I>, resultSelector: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R): OperatorFunction<T, R>;
export function concatMapTo<T, R, O extends ObservableInput<any>>(observable: O, resultSelector: (outerValue: T, innerValue: ObservedValueOf<O>, outerIndex: number, innerIndex: number) => R): OperatorFunction<T, R>;
/* tslint:enable:max-line-length */

/**
Expand Down Expand Up @@ -62,9 +62,9 @@ export function concatMapTo<T, I, R>(observable: ObservableInput<I>, resultSelec
* @method concatMapTo
* @owner Observable
*/
export function concatMapTo<T, I, R>(
innerObservable: ObservableInput<I>,
resultSelector?: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R
): OperatorFunction<T, R> {
export function concatMapTo<T, R, O extends ObservableInput<any>>(
innerObservable: O,
resultSelector?: (outerValue: T, innerValue: ObservedValueOf<O>, outerIndex: number, innerIndex: number) => R
): OperatorFunction<T, ObservedValueOf<O>|R> {
return concatMap(() => innerObservable, resultSelector);
}
8 changes: 3 additions & 5 deletions src/internal/operators/mergeAll.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@

import { mergeMap } from './mergeMap';
import { identity } from '../util/identity';
import { MonoTypeOperatorFunction, OperatorFunction, ObservableInput } from '../types';

export function mergeAll<T>(concurrent?: number): OperatorFunction<ObservableInput<T>, T>;
import { OperatorFunction, ObservableInput } from '../types';

/**
* Converts a higher-order Observable into a first-order Observable which
Expand Down Expand Up @@ -57,6 +55,6 @@ export function mergeAll<T>(concurrent?: number): OperatorFunction<ObservableInp
* @method mergeAll
* @owner Observable
*/
export function mergeAll<T>(concurrent: number = Number.POSITIVE_INFINITY): MonoTypeOperatorFunction<T> {
return mergeMap<T, T>(identity as (value: T, index: number) => ObservableInput<T>, concurrent);
export function mergeAll<T>(concurrent: number = Number.POSITIVE_INFINITY): OperatorFunction<ObservableInput<T>, T> {
return mergeMap(identity, concurrent);
}
18 changes: 9 additions & 9 deletions src/internal/operators/mergeMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T, R>(project: (value: T, index: number) => ObservableInput<R>, concurrent?: number): OperatorFunction<T, R>;
export function mergeMap<T, O extends ObservableInput<any>>(project: (value: T, index: number) => O, concurrent?: number): OperatorFunction<T, ObservedValueOf<O>>;

This comment has been minimized.

Copy link
@chumbalum

chumbalum Jan 30, 2019

Hello @benlesh

is this supposed to be correct?

I use mergeMap to chain observables like this:

getBankAccounts(): Observable<BankAccount[]> {
  return this.getActiveAccount().pipe(
    mergeMap<Account, BankAccount[]>(account => {
      return this.getBankAccounts(account)  <- Returns Observable<BankAccount[]>
    })
  )
}

And BankAccount[] won't fit with that type specification. It's working without specifying the types of mergeMap though.

/** @deprecated resultSelector no longer supported, use inner map instead */
export function mergeMap<T, R>(project: (value: T, index: number) => ObservableInput<R>, resultSelector: undefined, concurrent?: number): OperatorFunction<T, R>;
export function mergeMap<T, O extends ObservableInput<any>>(project: (value: T, index: number) => O, resultSelector: undefined, concurrent?: number): OperatorFunction<T, ObservedValueOf<O>>;
/** @deprecated resultSelector no longer supported, use inner map instead */
export function mergeMap<T, I, R>(project: (value: T, index: number) => ObservableInput<I>, resultSelector: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R, concurrent?: number): OperatorFunction<T, R>;
export function mergeMap<T, R, O extends ObservableInput<any>>(project: (value: T, index: number) => O, resultSelector: (outerValue: T, innerValue: ObservedValueOf<O>, outerIndex: number, innerIndex: number) => R, concurrent?: number): OperatorFunction<T, R>;
/* tslint:enable:max-line-length */

/**
Expand Down Expand Up @@ -70,16 +70,16 @@ export function mergeMap<T, I, R>(project: (value: T, index: number) => Observab
* @method mergeMap
* @owner Observable
*/
export function mergeMap<T, I, R>(
project: (value: T, index: number) => ObservableInput<I>,
resultSelector?: ((outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R) | number,
export function mergeMap<T, R, O extends ObservableInput<any>>(
project: (value: T, index: number) => O,
resultSelector?: ((outerValue: T, innerValue: ObservedValueOf<O>, outerIndex: number, innerIndex: number) => R) | number,
concurrent: number = Number.POSITIVE_INFINITY
): OperatorFunction<T, I|R> {
): OperatorFunction<T, ObservedValueOf<O>|R> {
if (typeof resultSelector === 'function') {
// DEPRECATED PATH
return (source: Observable<T>) => 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') {
Expand Down
14 changes: 7 additions & 7 deletions src/internal/operators/mergeMapTo.ts
Original file line number Diff line number Diff line change
@@ -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<T>(innerObservable: ObservableInput<T>, concurrent?: number): OperatorFunction<any, T>;
export function mergeMapTo<T, O extends ObservableInput<any>>(innerObservable: O, concurrent?: number): OperatorFunction<any, ObservedValueOf<O>>;
/** @deprecated */
export function mergeMapTo<T, I, R>(innerObservable: ObservableInput<I>, resultSelector: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R, concurrent?: number): OperatorFunction<T, R>;
export function mergeMapTo<T, R, O extends ObservableInput<any>>(innerObservable: O, resultSelector: (outerValue: T, innerValue: ObservedValueOf<O>, outerIndex: number, innerIndex: number) => R, concurrent?: number): OperatorFunction<T, R>;
/* tslint:enable:max-line-length */

/**
Expand Down Expand Up @@ -46,11 +46,11 @@ export function mergeMapTo<T, I, R>(innerObservable: ObservableInput<I>, resultS
* @method mergeMapTo
* @owner Observable
*/
export function mergeMapTo<T, I, R>(
innerObservable: ObservableInput<I>,
resultSelector?: ((outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R) | number,
export function mergeMapTo<T, R, O extends ObservableInput<any>>(
innerObservable: O,
resultSelector?: ((outerValue: T, innerValue: ObservedValueOf<O>, outerIndex: number, innerIndex: number) => R) | number,
concurrent: number = Number.POSITIVE_INFINITY
): OperatorFunction<T, I|R> {
): OperatorFunction<T, ObservedValueOf<O>|R> {
if (typeof resultSelector === 'function') {
return mergeMap(() => innerObservable, resultSelector, concurrent);
}
Expand Down

0 comments on commit c2ac39c

Please sign in to comment.