Skip to content

Commit 739593f

Browse files
fix(typings): Use overloading instead of union type for typings filter, find, first, last.
Abstract ------------ - This fixes #2163 by reverts commit 922d04e. - So this reverts #2119 sadly. Drawback --------- - We would need specify type parameters to their method explicitly if we'd like to narrow the type via `predicate` as type guarde function. - However, I don't think this is a serious problem because we can avoid this drawback with specifying actual types only. - And this changes makes our typings more similar to [TypeScript's Array methods](https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1086-L1097).
1 parent 4df531f commit 739593f

File tree

8 files changed

+74
-37
lines changed

8 files changed

+74
-37
lines changed

spec/operators/filter-spec.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,11 +283,25 @@ describe('Observable.prototype.filter', () => {
283283
const isString = (x: string | number): x is string => typeof x === 'string';
284284

285285
// Here, `s` is a string in the second filter predicate after the type guard (yay - intellisense!)
286-
const guardedFilter = x.filter(isString).filter(s => s.length === 2); // Observable<string>
286+
const guardedFilter = x.filter<string | number, string>(isString).filter(s => s.length === 2); // Observable<string>
287287
// In contrast, this type of regular boolean predicate still maintains the original type
288288
const boolFilter = x.filter(s => typeof s === 'number'); // Observable<string | number>
289289
}
290290

291+
{
292+
interface Bar {
293+
bar?: string;
294+
}
295+
class Foo implements Bar {
296+
constructor(public bar: string = 'name') {}
297+
}
298+
299+
let foo: Bar = new Foo(); // <--- type is interface, not the class
300+
Observable.of(foo)
301+
.filter(foo => foo.bar === 'name')
302+
.subscribe(foo => console.log(foo.bar)); // <-- "Property 'bar' does not exist on type '{}'"
303+
}
304+
291305
// tslint:disable enable
292306
});
293307
});

spec/operators/find-spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ describe('Observable.prototype.find', () => {
166166
const isString = (x: string | number): x is string => typeof x === 'string';
167167

168168
// After the type guard `find` predicate, the type is narrowed to string
169-
const guardedFind = x.find(isString).filter(s => s.length > 1).map(s => s.substr(1)); // Observable<string>
169+
const guardedFind = x.find<string | number, string>(isString).filter(s => s.length > 1).map(s => s.substr(1)); // Observable<string>
170170
// In contrast, a boolean predicate maintains the original type
171171
const boolFind = x.find(x => typeof x === 'string'); // Observable<string | number>
172172
}

spec/operators/first-spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,8 @@ describe('Observable.prototype.first', () => {
225225
const isString = (x: string | number): x is string => typeof x === 'string';
226226

227227
// After the type guard `first` predicates, the type is narrowed to string
228-
const guardedFirst1 = x.first(isString).filter(s => s.length > 1).map(s => s.substr(1)); // Observable<string>
229-
const guardedFirst2 = x.first(isString, s => s.substr(0)).filter(s => s.length > 1); // Observable<string>
228+
const guardedFirst1 = x.first<string | number, string>(isString).filter(s => s.length > 1).map(s => s.substr(1)); // Observable<string>
229+
const guardedFirst2 = x.first<string | number, string>(isString, s => s.substr(0)).filter(s => s.length > 1); // Observable<string>
230230
// Without a resultSelector, `first` maintains the original type (TS can't do this yet)
231231
const boolFirst1 = x.first(x => typeof x === 'string', null, ''); // Observable<string | number>
232232
// `first` still uses the `resultSelector` return type, if it exists.

spec/operators/last-spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ describe('Observable.prototype.last', () => {
153153
const isString = (x: string | number): x is string => typeof x === 'string';
154154

155155
// After the type guard `last` predicates, the type is narrowed to string
156-
const guardedLast1 = x.last(isString).filter(s => s.length > 1).map(s => s.substr(1)); // Observable<string>
157-
const guardedLast2 = x.last(isString, s => s.substr(0)).filter(s => s.length > 1); // Observable<string>
156+
const guardedLast1 = x.last<string | number, string>(isString).filter(s => s.length > 1).map(s => s.substr(1)); // Observable<string>
157+
const guardedLast2 = x.last<string | number, string>(isString, s => s.substr(0)).filter(s => s.length > 1); // Observable<string>
158158
// Without a resultSelector, `last` maintains the original type (TS can't do this yet)
159159
const boolLast1 = x.last(x => typeof x === 'string', null, ''); // Observable<string | number>
160160
// `last` still uses the `resultSelector` return type, if it exists.

src/operator/filter.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ import { Subscriber } from '../Subscriber';
33
import { Observable } from '../Observable';
44
import { TeardownLogic } from '../Subscription';
55

6-
/* tslint:disable:max-line-length */
6+
// XXX: At [email protected], we need prepare the version which takes `predicate`
7+
// returning `boolean` before to define the one which takes the type guard `predicate`
8+
// so that the type inference works correctly for the case of that `predicate` returning `boolean` simply.
9+
export function filter<T>(this: Observable<T>,
10+
predicate: (value: T, index: number) => boolean,
11+
thisArg?: any): Observable<T>;
712
export function filter<T, S extends T>(this: Observable<T>,
8-
predicate: ((value: T, index: number) => boolean) |
9-
((value: T, index: number) => value is S),
13+
predicate: (value: T, index: number) => value is S,
1014
thisArg?: any): Observable<S>;
11-
/* tslint:disable:max-line-length */
1215

1316
/**
1417
* Filter items emitted by the source Observable by only emitting those that

src/operator/find.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ import { Observable } from '../Observable';
22
import { Operator } from '../Operator';
33
import { Subscriber } from '../Subscriber';
44

5-
/* tslint:disable:max-line-length */
5+
// XXX: At [email protected], we need prepare the version which takes `predicate`
6+
// returning `boolean` before to define the one which takes the type guard `predicate`
7+
// so that the type inference works correctly for the case of that `predicate` returning `boolean` simply.
8+
export function find<T>(this: Observable<T>,
9+
predicate: (value: T, index: number, source: Observable<T>) => boolean,
10+
thisArg?: any): Observable<T>;
611
export function find<T, S extends T>(this: Observable<T>,
7-
predicate: ((value: T, index: number, source: Observable<T>) => boolean) |
8-
((value: T, index: number, source: Observable<T>) => value is S),
12+
predicate: (value: T, index: number, source: Observable<T>) => value is S,
913
thisArg?: any): Observable<S>;
10-
/* tslint:disable:max-line-length */
1114

1215
/**
1316
* Emits only the first value emitted by the source Observable that meets some

src/operator/first.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,26 @@ import { Operator } from '../Operator';
33
import { Subscriber } from '../Subscriber';
44
import { EmptyError } from '../util/EmptyError';
55

6-
/* tslint:disable:max-line-length */
6+
// XXX: At [email protected], we need prepare the version which takes `predicate`
7+
// returning `boolean` before to define the one which takes the type guard `predicate`
8+
// so that the type inference works correctly for the case of that `predicate` returning `boolean` simply.
9+
export function first<T>(this: Observable<T>,
10+
predicate?: (value: T, index: number,
11+
source: Observable<T>) => boolean): Observable<T>;
712
export function first<T, S extends T>(this: Observable<T>,
8-
predicate?: ((value: T, index: number, source: Observable<T>) => boolean) |
9-
((value: T, index: number, source: Observable<T>) => value is S)): Observable<S>;
10-
export function first<T>(this: Observable<T>, predicate: (value: T, index: number, source: Observable<T>) => boolean, resultSelector: void, defaultValue?: T): Observable<T>;
11-
export function first<T, S extends T, R>(this: Observable<T>,
12-
predicate: ((value: T, index: number, source: Observable<T>) => boolean) |
13-
((value: T, index: number, source: Observable<T>) => value is S),
14-
resultSelector?: ((value: S, index: number) => R) | void,
15-
defaultValue?: S): Observable<S>;
16-
export function first<T, R>(this: Observable<T>, predicate?: (value: T, index: number, source: Observable<T>) => boolean, resultSelector?: (value: T, index: number) => R, defaultValue?: R): Observable<R>;
17-
/* tslint:disable:max-line-length */
18-
13+
predicate?: (value: T, index: number,
14+
source: Observable<T>) => value is S): Observable<S>;
15+
export function first<T>(this: Observable<T>,
16+
predicate: (value: T, index: number, source: Observable<T>) => boolean,
17+
resultSelector: (value: T, index: number) => T,
18+
defaultValue?: T): Observable<T>;
19+
export function first<T, S extends T>(this: Observable<T>,
20+
predicate: (value: T, index: number, source: Observable<T>) => value is S,
21+
resultSelector: (value: S, index: number) => S,
22+
defaultValue?: S): Observable<S>;
23+
export function first<T, R>(this: Observable<T>, predicate?: (value: T, index: number, source: Observable<T>) => boolean,
24+
resultSelector?: (value: T, index: number) => R,
25+
defaultValue?: R): Observable<R>;
1926
/**
2027
* Emits only the first value (or the first value that meets some condition)
2128
* emitted by the source Observable.

src/operator/last.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,28 @@ import { Operator } from '../Operator';
33
import { Subscriber } from '../Subscriber';
44
import { EmptyError } from '../util/EmptyError';
55

6-
/* tslint:disable:max-line-length */
6+
// XXX: At [email protected], we need prepare the version which takes `predicate`
7+
// returning `boolean` before to define the one which takes the type guard `predicate`
8+
// so that the type inference works correctly for the case of that `predicate` returning `boolean` simply.
9+
export function last<T>(this: Observable<T>,
10+
predicate?: (value: T, index: number,
11+
source: Observable<T>) => boolean): Observable<T>;
712
export function last<T, S extends T>(this: Observable<T>,
8-
predicate?: ((value: T, index: number, source: Observable<T>) => boolean) |
9-
((value: T, index: number, source: Observable<T>) => value is S)): Observable<S>;
10-
export function last<T>(this: Observable<T>, predicate: (value: T, index: number, source: Observable<T>) => boolean, resultSelector: void, defaultValue?: T): Observable<T>;
11-
export function last<T, S extends T, R>(this: Observable<T>,
12-
predicate: ((value: T, index: number, source: Observable<T>) => boolean) |
13-
((value: T, index: number, source: Observable<T>) => value is S),
14-
resultSelector?: ((value: S, index: number) => R) | void,
15-
defaultValue?: S): Observable<S>;
16-
export function last<T, R>(this: Observable<T>, predicate?: (value: T, index: number, source: Observable<T>) => boolean, resultSelector?: (value: T, index: number) => R, defaultValue?: R): Observable<R>;
17-
/* tslint:disable:max-line-length */
13+
predicate?: (value: T, index: number,
14+
source: Observable<T>) => value is S): Observable<S>;
15+
export function last<T>(this: Observable<T>,
16+
predicate: (value: T, index: number,
17+
source: Observable<T>) => boolean,
18+
resultSelector: (value: T, index: number) => T,
19+
defaultValue?: T): Observable<T>;
20+
export function last<T, S extends T>(this: Observable<T>,
21+
predicate: (value: T, index: number, source: Observable<T>) => value is S,
22+
resultSelector: (value: S, index: number) => S,
23+
defaultValue?: S): Observable<S>;
24+
export function last<T, R>(this: Observable<T>,
25+
predicate?: (value: T, index: number, source: Observable<T>) => boolean,
26+
resultSelector?: (value: T, index: number) => R,
27+
defaultValue?: R): Observable<R>;
1828

1929
/**
2030
* Returns an Observable that emits only the last item emitted by the source Observable.

0 commit comments

Comments
 (0)