Skip to content

Commit

Permalink
fix(TS): fix type inference for publish variants
Browse files Browse the repository at this point in the history
- We can never get  out of  via TypeScript any how.
- Resolves a larger issue where operators with a single, static argument and a single  generic inferred too strongly from the argument and lost the additional type information from the source.
  • Loading branch information
benlesh committed Jun 6, 2019
1 parent cb52c1b commit 4d58596
Show file tree
Hide file tree
Showing 11 changed files with 43 additions and 80 deletions.
2 changes: 1 addition & 1 deletion compat/operator/publishBehavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ import { publishBehavior as higherOrder } from 'rxjs/operators';
* @owner Observable
*/
export function publishBehavior<T>(this: Observable<T>, value: T): ConnectableObservable<T> {
return higherOrder(value)(this);
return higherOrder(value)(this) as ConnectableObservable<T>;
}
19 changes: 12 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@
"tslint-etc": "1.5.0",
"tslint-no-toplevel-property-access": "0.0.2",
"tslint-no-unused-expression-chai": "0.0.3",
"typescript": "^3.0.1",
"typescript": "^3.4.5",
"validate-commit-msg": "2.14.0",
"webpack": "^4.31.0"
},
Expand Down
6 changes: 1 addition & 5 deletions spec-dtslint/operators/publish-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import { of, Observable } from 'rxjs';
import { publish } from 'rxjs/operators';

it('should support empty parameter', () => {
// Here, TypeScript versions 3.1 and earlier infer Observable<any>. However,
// the next version infers Observable<number>. It's not possible to specify
// an upper bound for the TypeScript version used by dtslint, so an
// expectation cannot be applied.
const a = of(1, 2, 3).pipe(publish()); // $ExpectType Observable<any>
const a = of(1, 2, 3).pipe(publish()); // $ExpectType Observable<number>
});

it('should infer when type is specified', () => {
Expand Down
9 changes: 7 additions & 2 deletions spec-dtslint/operators/publishBehavior-spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { of } from 'rxjs';
import { publishBehavior } from 'rxjs/operators';
import { publishBehavior, map } from 'rxjs/operators';

it('should enforce parameter', () => {
const a = of(1, 2, 3).pipe(publishBehavior()); // $ExpectError
Expand All @@ -10,5 +10,10 @@ it('should infer correctly with parameter', () => {
});

it('should enforce type on parameter', () => {
const a = of(1, 2, 3).pipe(publishBehavior('a'); // $ExpectError
const a = of(1, 2, 3).pipe(publishBehavior('a')); // $ExpectType Observable<string | number>
});

it('should compose properly', () => {
const fn = () => Math.random() > 0.5;
const a = of(true, false).pipe(map(x => x && fn()), publishBehavior(false)); // $ExpectType Observable<boolean>
});
6 changes: 1 addition & 5 deletions spec-dtslint/operators/publishLast-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import { of } from 'rxjs';
import { publishLast } from 'rxjs/operators';

it('should accept empty parameter', () => {
// Here, TypeScript versions 3.1 and earlier infer Observable<any>. However,
// the next version infers Observable<number>. It's not possible to specify
// an upper bound for the TypeScript version used by dtslint, so an
// expectation cannot be applied.
const a = of(1, 2, 3).pipe(publishLast()); // $ExpectType Observable<any>
const a = of(1, 2, 3).pipe(publishLast()); // $ExpectType Observable<number>
});

it('should infer when type is specified', () => {
Expand Down
45 changes: 0 additions & 45 deletions spec/operators/multicast-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -703,49 +703,4 @@ describe('multicast operator', () => {
});
});
});

describe('typings', () => {
type('should infer the type', () => {
/* tslint:disable:no-unused-variable */
const source = of<number>(1, 2, 3);
const result: ConnectableObservable<number> = source.pipe(multicast(() => new Subject<number>())) as ConnectableObservable<number>;
/* tslint:enable:no-unused-variable */
});

type('should infer the type with a selector', () => {
/* tslint:disable:no-unused-variable */
const source = of<number>(1, 2, 3);
const result: Observable<number> = source.pipe(multicast(() => new Subject<number>(), s => s.pipe(map(x => x))));
/* tslint:enable:no-unused-variable */
});

type('should infer the type with a type-changing selector', () => {
/* tslint:disable:no-unused-variable */
const source = of<number>(1, 2, 3);
const result: Observable<string> = source.pipe(multicast(() => new Subject<number>(), s => s.pipe(map(x => x + '!'))));
/* tslint:enable:no-unused-variable */
});

type('should infer the type for the pipeable operator', () => {
/* tslint:disable:no-unused-variable */
const source = of<number>(1, 2, 3);
// TODO: https://github.com/ReactiveX/rxjs/issues/2972
const result: ConnectableObservable<number> = multicast(() => new Subject<number>())(source);
/* tslint:enable:no-unused-variable */
});

type('should infer the type for the pipeable operator with a selector', () => {
/* tslint:disable:no-unused-variable */
const source = of<number>(1, 2, 3);
const result: Observable<number> = source.pipe(multicast(() => new Subject<number>(), s => s.pipe(map(x => x))));
/* tslint:enable:no-unused-variable */
});

type('should infer the type for the pipeable operator with a type-changing selector', () => {
/* tslint:disable:no-unused-variable */
const source = of<number>(1, 2, 3);
const result: Observable<string> = source.pipe(multicast(() => new Subject<number>(), s => s.pipe(map(x => x + '!'))));
/* tslint:enable:no-unused-variable */
});
});
});
8 changes: 4 additions & 4 deletions src/internal/operators/multicast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { Operator } from '../Operator';
import { Subscriber } from '../Subscriber';
import { Observable } from '../Observable';
import { ConnectableObservable, connectableObservableDescriptor } from '../observable/ConnectableObservable';
import { OperatorFunction, UnaryFunction, ObservedValueOf, ObservableInput } from '../types';
import { OperatorFunction, UnaryFunction, ObservedValueOf, ObservableInput, MonoTypeOperatorFunction } from '../types';

/* tslint:disable:max-line-length */
export function multicast<T>(subject: Subject<T>): UnaryFunction<Observable<T>, ConnectableObservable<T>>;
export function multicast<T, O extends ObservableInput<any>>(subject: Subject<T>, selector: (shared: Observable<T>) => O): UnaryFunction<Observable<T>, ConnectableObservable<ObservedValueOf<O>>>;
export function multicast<T>(subjectFactory: (this: Observable<T>) => Subject<T>): UnaryFunction<Observable<T>, ConnectableObservable<T>>;
export function multicast<T>(subject: Subject<T>): MonoTypeOperatorFunction<T>;
export function multicast<T, O extends ObservableInput<any>>(subject: Subject<T>, selector: (shared: Observable<T>) => O): OperatorFunction<T, ObservedValueOf<O>>;
export function multicast<T>(subjectFactory: (this: Observable<T>) => Subject<T>): MonoTypeOperatorFunction<T>;
export function multicast<T, O extends ObservableInput<any>>(SubjectFactory: (this: Observable<T>) => Subject<T>, selector: (shared: Observable<T>) => O): OperatorFunction<T, ObservedValueOf<O>>;
/* tslint:enable:max-line-length */

Expand Down
2 changes: 1 addition & 1 deletion src/internal/operators/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ConnectableObservable } from '../observable/ConnectableObservable';
import { MonoTypeOperatorFunction, OperatorFunction, UnaryFunction, ObservableInput, ObservedValueOf } from '../types';

/* tslint:disable:max-line-length */
export function publish<T>(): UnaryFunction<Observable<T>, ConnectableObservable<T>>;
export function publish<T>(): MonoTypeOperatorFunction<T>;
export function publish<T, O extends ObservableInput<any>>(selector: (shared: Observable<T>) => O): OperatorFunction<T, ObservedValueOf<O>>;
export function publish<T>(selector: MonoTypeOperatorFunction<T>): MonoTypeOperatorFunction<T>;
/* tslint:enable:max-line-length */
Expand Down
20 changes: 13 additions & 7 deletions src/internal/operators/publishBehavior.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { Observable } from '../Observable';
import { BehaviorSubject } from '../BehaviorSubject';
import { multicast } from './multicast';
import { ConnectableObservable } from '../observable/ConnectableObservable';
import { UnaryFunction } from '../types';
import { OperatorFunction } from '../types';

/**
* @param value
* Multicasts the observable source through an underlying {@link BehaviorSubject}. All subscriptions
* to the resulting observable will subscribe to the underlaying `BehaviorSubject`, the
* resulting observable is a {@link ConnectableObservable}. If you call `connect()` on that observable
* (requires a cast to `ConnectableObservable` in TypeScript), it will subscribe to the source
* observable with the underlying subject and connect that source too all consumers subscribed through the
* subject. Because it's using a `BehaviorSubject`, all new subscriptions will get the most recent value
* that has passed through said subject, _or_ they will get the `initialValue` if no values have
* arrived yet, so long as the published `ConnectableObservable` has been connected.
*
* @param initialValue
* @return {ConnectableObservable<T>}
* @method publishBehavior
* @owner Observable
*/
export function publishBehavior<T>(value: T): UnaryFunction<Observable<T>, ConnectableObservable<T>> {
return (source: Observable<T>) => multicast(new BehaviorSubject<T>(value))(source) as ConnectableObservable<T>;
export function publishBehavior<T, D>(initialValue: D): OperatorFunction<T, T | D> {
return (source: Observable<T>) => multicast(new BehaviorSubject<T | D>(initialValue))(source);
}
4 changes: 2 additions & 2 deletions src/internal/operators/publishLast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Observable } from '../Observable';
import { AsyncSubject } from '../AsyncSubject';
import { multicast } from './multicast';
import { ConnectableObservable } from '../observable/ConnectableObservable';
import { UnaryFunction } from '../types';
import { UnaryFunction, MonoTypeOperatorFunction } from '../types';

/**
* Returns a connectable observable sequence that shares a single subscription to the
Expand Down Expand Up @@ -62,6 +62,6 @@ import { UnaryFunction } from '../types';
* @owner Observable
*/

export function publishLast<T>(): UnaryFunction<Observable<T>, ConnectableObservable<T>> {
export function publishLast<T>(): MonoTypeOperatorFunction<T> {
return (source: Observable<T>) => multicast(new AsyncSubject<T>())(source);
}

0 comments on commit 4d58596

Please sign in to comment.