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

pipe expected 0 arguments #7400

Open
KingMario opened this issue Dec 6, 2023 · 5 comments
Open

pipe expected 0 arguments #7400

KingMario opened this issue Dec 6, 2023 · 5 comments
Labels
TS Issues and PRs related purely to TypeScript issues

Comments

@KingMario
Copy link

KingMario commented Dec 6, 2023

Describe the bug

While using ternary operator to get an observable and pipe with one operator, typescript reports Expected 0 arguments, but got 1.

Expected behavior

No error should be reported for pipe method.

Reproduction code

import { rx, of, map, switchMap } from 'rxjs';

const a = true;

(a ? of(undefined) : of(null))
  .pipe(
    switchMap(() =>
      rx(
        of('World'),
        map((name) => `Hello, ${name}!`)
      )
    )
  )
  .subscribe(console.log);

Reproduction URL

https://stackblitz.com/edit/rxjs-ternary-pipe-error

Version

8.0.0-alpha

Environment

No response

Additional context

Casting the obesrvable as Observable<any> can bypass the error.

@jakovljevic-mladen jakovljevic-mladen added the bug Confirmed bug label Dec 6, 2023
@voliva
Copy link
Contributor

voliva commented Dec 8, 2023

I think this is a limitation on Typescript rather than RxJS.

With TS 5.1 or before, you couldn't do the following:

const a = true;

// b is Array<number> | Array<string>
const b = a ? [1] : ['1'];

// TS 5.1
// Error: This expression is not callable.
b.filter(r => {});

Playground: https://www.typescriptlang.org/play?ts=5.1.6#code/FAYw9gdgzgLgBAQzgXjjATgVwKYG5iiSxwBGKicA-HANoCMAunAFy0DkdbD+wJAdADMAlgBsY2dAAp0KAHxwA3gF8AlPiA

This was fixed in TS 5.2 for Arrays in specific: https://devblogs.microsoft.com/typescript/announcing-typescript-5-2/#easier-method-usage-for-unions-of-arrays

The problem is that Typescript can't find compatible signatures when you have SomeType<A> | SomeType<B>. The workaround with arrays before 5.2 was to manually define the array type as an Array of the union:

const a = true;

const b: Array<number | string> = a ? [1] : ['1'];

// TS 5.1
// Now this works
b.filter(r => {});

And so your workaround is to do the same for observable. In your stackblitz you can try:

const tmp: Observable<undefined | null> = (a ? of(undefined) : of(null));
tmp.pipe(...)

And this will work. This is why from(a ? [undefined] : [null]) works: It returns an Observable<undefined | null> directly instead of a Observable<undefined> | Observable<null>

@benlesh
Copy link
Member

benlesh commented Dec 11, 2023

I filed an issue upstream... because I agree it's confusing, but I'm not sure this is on us... microsoft/TypeScript#56741

We might have a pipe() overload with zero arguments that we could get rid of to provide better errors?

@benlesh benlesh added TS Issues and PRs related purely to TypeScript issues and removed bug Confirmed bug labels Dec 11, 2023
@KingMario KingMario changed the title switchMap expected 0 arguments pipe expected 0 arguments Dec 12, 2023
@rraziel
Copy link
Contributor

rraziel commented Jan 11, 2024

I was wondering about the same thing, having a:

matchThingie(xx, {
  caseA: () => someObs,
  caseB: () => someOtherObs
}); // returns Observable<T> | Observable<U> so can't pipe

I figured I'd try to reproduce it (ok for like 5 minutes but still), adding pieces of the class one at a time but not having much luck, seems like TS is fine with it:

export interface OpFn<T, R> { (source: Observable<T>): Observable<R>; }
declare class Observable<T> {
  source: Observable<any> | undefined;
  operator: OpFn<any, T> | undefined;
  constructor(subscribe?: (this: Observable<T>, subscriber: unknown) => () => void);
  pipe(): Observable<T>;
  pipe<A>(op: OpFn<T, A>): Observable<A>;
  pipe<A, B>(op1: OpFn<T, A>, op2: OpFn<A, B>): Observable<B>;
  // all the other ones
}
declare const y: Observable<number> | Observable<string>;
y.pipe();
// sees all overloads, and somehow the one with no args always returns the first member of the union
// so if I swap number and string, pipe returns Observable<string> instead of Observable<number>

I'm not sure which part of the class makes TS think the other overloads can't be there
And as I see Ryan asked for a simpler reproduction, might not be that easy creating one 😅

@rraziel
Copy link
Contributor

rraziel commented Jan 11, 2024

Actually went a bit further, seems to be the observer in the subscribe() that breaks it:

interface Unsubscribable { unsubscribe(): void; }
interface OperatorFunction<T, R> { (source: Observable<T>): Observable<R>; }
interface Observer<T> { next(value: T): void; }
declare class Observable<T> {
  pipe(): Observable<T>;
  pipe<A>(op: OperatorFunction<T, A>): Observable<A>;
  subscribe(observer: Partial<Observer<T>>): Unsubscribable;
}
declare const y: Observable<number> | Observable<string>;
y.pipe(); // only sees the first overload

@gurvirsinghbaraich
Copy link

This could be simplity solved by changing (a ? of(undefined) : of(null)) to of(a ? undefined : null)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
TS Issues and PRs related purely to TypeScript issues
Projects
None yet
Development

No branches or pull requests

6 participants