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 inference problem with first and last #3946

Closed
cartant opened this issue Jul 24, 2018 · 4 comments
Closed

Type inference problem with first and last #3946

cartant opened this issue Jul 24, 2018 · 4 comments

Comments

@cartant
Copy link
Collaborator

cartant commented Jul 24, 2018

Bug Report

Current Behavior

The changes in PR #3945 exposed a bug with typings for the first and last operators.

With those operators, it's possible to specify a default. And if one is specified, its type is used to infer both the operator's input and output types. And, in doing so, the inferred MonoTypeOperatorFunction type can be incompatible with the source observable.

Reproduction

See this test for first:

expectObservable(e1.pipe(first(x => x === 's', 'd'))).toBe(expected);

Expected behavior

Because the default value is specified as using a string literal, the inferred type is a literal type: MonoTypeOperatorFunction<"d"> and that's not compatible with the source observable's type of Observable<string>.

Possible Solution

The current workaround used for the test is to explicitly specify the type parameter:

expectObservable(e1.pipe(first<string>(x => x === 's', 'd'))).toBe(expected);

Instead of using T as the type for the default parameter, it ought to be possible to use another type parameter that extends T. Something like this, maybe:

export function first<T, D extends T = T>(
  predicate?: null,
  defaultValue?: D
): MonoTypeOperatorFunction<T>;
/* ... etc. ... */
@cartant
Copy link
Collaborator Author

cartant commented Jul 24, 2018

Actually, I think this is a TypeScript bug.

If this signature is removed:

export function first<T, S extends T>(
  predicate: (value: T, index: number, source: Observable<T>) => value is S,
  defaultValue?: T
): OperatorFunction<T, S>;

The problem goes away, as the return type from first is inferred to be MonoTypeOperatorFunction<string>.

However, with the above signature in place, the return type from first is inferred to be MonoTypeOperatorFunction<"d"> - which prevents the operator being applied to an Observable<string> source.

That doesn't make much sense, as the signature's return type is OperatorFunction, not MonoTypeOperatorFunction and that makes me think this is a TypeScript bug.

@cartant
Copy link
Collaborator Author

cartant commented Jul 25, 2018

FWIW, here's a minimal repro of the problem:

interface Foo<T> {}
interface Bar<T, S> {}

function foo<T, S extends T>(predicate: (value: T) => value is S, defaultValue?: S): Bar<T, S>;
function foo<T>(predicate: (value: T) => boolean, defaultValue?: T): Foo<T>;
function foo<T>(predicate: (value: T) => boolean, defaultValue?: any): Bar<T, any> | Foo<T> {
  throw new Error('Unimplemented');
}
const result = foo(x => x === 's', 'd'); // Foo<"d">

If inserted into the RxJS project - say, after the implementation of first - result is inferred as Foo<"d">.

However, I've been unable to reproduce this in another project, so it's possible that it's a bug that's only effected with a particular combination of TypeScript version and compilerOptions. Or something.

@cartant
Copy link
Collaborator Author

cartant commented Jul 25, 2018

Opened a TypeScript issue for the problem: microsoft/TypeScript#25917

@cartant cartant closed this as completed Jul 25, 2018
@cartant cartant reopened this Jul 25, 2018
@cartant
Copy link
Collaborator Author

cartant commented Jul 26, 2018

The weird, compiler-options-dependent behaviour appears to have been fixed in TypeScript 3.0.

@cartant cartant closed this as completed Jul 26, 2018
@lock lock bot locked as resolved and limited conversation to collaborators Aug 25, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant