Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type lost when using interfaces with filter, find, first, last #2163

Closed
timblakely opened this issue Nov 30, 2016 · 8 comments
Closed

Type lost when using interfaces with filter, find, first, last #2163

timblakely opened this issue Nov 30, 2016 · 8 comments

Comments

@timblakely
Copy link

RxJS version: RC4

Code to reproduce:

interface Bar {
  bar?: string;
}
class Foo implements Bar {
  constructor(public bar: string = 'name') {}
}

let foo: Foo = new Foo();
Observable.of(foo)
    .filter(foo => foo.bar === 'name')
    .subscribe(foo => console.log(foo.bar));  <--- works fine

let foo2: Bar = new Foo(); <--- type is interface, not the class
Observable.of(foo2)
    .filter(foo => foo.bar === 'name')
    .subscribe(foo => console.log(foo.bar)); <-- "Property 'bar' does not exist on type '{}'"

Expected behavior:

Typing to be maintained through the filter() function.

Actual behavior:

Type is lost downstream from .filter,() instead being typed as '{}'

Additional information:

Worked in RC2. Occurs for all functions changed in RC3

@tetsuharuohzeki
Copy link
Contributor

Relate #2119

@tetsuharuohzeki
Copy link
Contributor

tetsuharuohzeki commented Nov 30, 2016

By vscode's intellisense, Observable.of(foo2).filter(foo => foo.bar === 'name') is:

(property) Observable<Bar>.filter: <Bar, {}>(this: Observable<Bar>, predicate: ((value: Bar, index: number) => boolean) | ((value: Bar, index: number) => value is {}), thisArg?: any) => Observable<{}>

edited:

I confirm this by

  • vscode: v1.7.2
  • tsserver which is included in typescript@v2.0.10

@tetsuharuohzeki
Copy link
Contributor

By this result, I seem tsc cannot conclude whether predicate is type guard or simple functions returning boolean without specifying type parameters explicitly.

@tetsuharuohzeki
Copy link
Contributor

@timblakely

The simple workaround is here:

let foo2: Bar = new Foo();
Observable.of(foo2)
-   .filter(foo => foo.bar === 'name')
+   .filter((foo): foo is Bar => foo.bar === 'name')
    .subscribe(foo => console.log(foo.bar));

tetsuharuohzeki added a commit to tetsuharuohzeki/rxjs that referenced this issue Nov 30, 2016
…r, find, first, last.

Abstract
------------
- This fixes ReactiveX#2163
  by reverts commit 922d04e.
  - So this reverts ReactiveX#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).
@rob3c
Copy link
Contributor

rob3c commented Dec 3, 2016

I think I found a way to rewrite the typings to save type guards without breaking anything (this time lol - sorry!) Please take a peek at #2170 (especially the new test cases), and let me know if anything else breaks.

Here's an excerpt relevant to the OP's example in this issue. It shows the new cast-less type guard support working alongside the previous boolean predicate behavior with classes and interfaces.

interface Bar { bar?: string; }
interface Baz { baz?: number; }
class Foo implements Bar, Baz { constructor(public bar: string = 'name', public baz: number = 42) {} }

const isBar = (x: any): x is Bar => x && (<Bar>x).bar !== undefined;

const foo: Foo = new Foo();
Observable.of(foo).filter(foo => foo.baz === 42)
  .subscribe(x => x.baz); // x is still Foo
Observable.of(foo).filter(isBar)
  .subscribe(x => x.bar); // x is Bar!

const foobar: Bar = new Foo(); // type is interface, not the class
Observable.of(foobar).filter(foobar => foobar.bar === 'name')
  .subscribe(x => x.bar); // <-- x is still Bar
Observable.of(foobar).filter(isBar)
  .subscribe(x => x.bar); // <--- x is Bar!

const barish = { bar: 'quack', baz: 42 } // type can quack like a Bar
Observable.of(barish).filter(x => x.bar === 'quack')
  .subscribe(x => x.bar); // x is still { bar: string; baz: number; }
Observable.of(barish).filter(isBar)
  .subscribe(bar => bar.bar); // x is Bar!

tetsuharuohzeki added a commit to tetsuharuohzeki/rxjs that referenced this issue Dec 6, 2016
…r, find, first, last.

Abstract
------------
- This fixes ReactiveX#2163
  by reverts commit 922d04e.
  - So this reverts ReactiveX#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).
@kwonoj
Copy link
Member

kwonoj commented Dec 13, 2016

I think #2170 fixed this issue, closing.

@QuentinFchx
Copy link

I still have the issue when using obs.filter(Boolean). Is is expected?
Should I use obs.filter(x => !!x) instead? I find the latter less elegant 😕

@lock
Copy link

lock bot commented Jun 6, 2018

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Jun 6, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
5 participants