Skip to content

Commit 26e4f0d

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 67f054c commit 26e4f0d

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
@@ -281,13 +281,27 @@ describe('Observable.prototype.filter', () => {
281281
const isString = (x: string | number): x is string => typeof x === 'string';
282282

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

288288
// To avoid the lint error about unused variables
289289
expect(guardedFilter).to.not.equal(true);
290290
expect(boolFilter).to.not.equal(true);
291291
}
292+
293+
{
294+
interface Bar {
295+
bar?: string;
296+
}
297+
class Foo implements Bar {
298+
constructor(public bar: string = 'name') {}
299+
}
300+
301+
let foo: Bar = new Foo(); // <--- type is interface, not the class
302+
Observable.of(foo)
303+
.filter(foo => foo.bar === 'name')
304+
.subscribe(foo => console.log(foo.bar)); // <-- "Property 'bar' does not exist on type '{}'"
305+
}
292306
});
293307
});

spec/operators/find-spec.ts

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

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

spec/operators/first-spec.ts

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

225225
// After the type guard `first` predicates, the type is narrowed to string
226-
const guardedFirst1 = x.first(isString).filter(s => s.length > 1).map(s => s.substr(1)); // Observable<string>
227-
const guardedFirst2 = x.first(isString, s => s.substr(0)).filter(s => s.length > 1); // Observable<string>
226+
const guardedFirst1 = x.first<string | number, string>(isString).filter(s => s.length > 1).map(s => s.substr(1)); // Observable<string>
227+
const guardedFirst2 = x.first<string | number, string>(isString, s => s.substr(0)).filter(s => s.length > 1); // Observable<string>
228228
// Without a resultSelector, `first` maintains the original type (TS can't do this yet)
229229
const boolFirst1 = x.first(x => typeof x === 'string', null, ''); // Observable<string | number>
230230
// `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
@@ -151,8 +151,8 @@ describe('Observable.prototype.last', () => {
151151
const isString = (x: string | number): x is string => typeof x === 'string';
152152

153153
// After the type guard `last` predicates, the type is narrowed to string
154-
const guardedLast1 = x.last(isString).filter(s => s.length > 1).map(s => s.substr(1)); // Observable<string>
155-
const guardedLast2 = x.last(isString, s => s.substr(0)).filter(s => s.length > 1); // Observable<string>
154+
const guardedLast1 = x.last<string | number, string>(isString).filter(s => s.length > 1).map(s => s.substr(1)); // Observable<string>
155+
const guardedLast2 = x.last<string | number, string>(isString, s => s.substr(0)).filter(s => s.length > 1); // Observable<string>
156156
// Without a resultSelector, `last` maintains the original type (TS can't do this yet)
157157
const boolLast1 = x.last(x => typeof x === 'string', null, ''); // Observable<string | number>
158158
// `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)