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

"expression is not callable" with .andThen() #417

Open
Gregoor opened this issue Sep 2, 2022 · 18 comments
Open

"expression is not callable" with .andThen() #417

Gregoor opened this issue Sep 2, 2022 · 18 comments
Assignees
Labels
hacktoberfest help wanted Extra attention is needed

Comments

@Gregoor
Copy link

Gregoor commented Sep 2, 2022

Hi there,

thanks for the amazing lib, love using every chance to make JS more Rusty and safe.

I did not manage to get a minimal repro yet, but I thought I'd post already in case someone has an idea.

This is the type signature of my Result-returning-function:
image

Unfortunately I get This expression is not callable. when calling .andThen() on it. Curiously enough .map() works.

Any ideas?

@Gregoor
Copy link
Author

Gregoor commented Sep 5, 2022

Repro was actually super simple, that stumbs me a lil:

import { ok, err } from "neverthrow";

const result = Math.random() ? err("wat") : ok(42);

result.andThen(() => ok({}))

https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzhA1gGjgUylOBfOAMyghDgCIA7TAN2xgAsSB3cgbgCgOBjCSgZ3hRM-AK4AbeAF44AWQCGjAHRR5lACakAFAEo4Afiw4t5ZovJ6AXMhRaALACYdnDsLGSla9QBUGmSlq6cFIAfDZaCHg6OkA

@Gregoor
Copy link
Author

Gregoor commented Sep 6, 2022

My current workaround is to explicitly type the result type, i.e.

import { Result, ok, err } from "neverthrow";

const result: Result<number, string> = Math.random() ? err("wat") : ok(42);

result.andThen(() => ok({}))

I suppose something in the err/ok union or its type inference is failing, but I'm not enough of a TS geek to figure out what it is. But I'm pretty sure it's a bug, and not user error, at this point.

@supermacro
Copy link
Owner

Yeah I'm a bit confused as to what's going on here. I would assume it's a type inference issue having to do with never's behaviour in TypeScript and the fact that neverthrow is not taking that into consideration.

Going to see if any of my colleagues know what's going on.

@supermacro supermacro added the help wanted Extra attention is needed label Sep 22, 2022
@boredcity
Copy link

that looks interesting 🕵️ , would you mind assigning me to it? 🙏

@supermacro
Copy link
Owner

that looks interesting detective , would you mind assigning me to it? pray

Done 🙂

@ccjmne
Copy link
Contributor

ccjmne commented Oct 3, 2022

I'm not sure it can actually be directly handled by clever typings from neverthrow, but you could get away with specifically returning a Result<T, E> instead of Ok<T, never> | Err<never, E>:

See your updated typescriptlang example.

import { ok, err, type Result } from "neverthrow";
const result: Result<number, string> = Math.random() ? err("wat") : ok(42);
result.andThen(() => ok({}))

Your actual function's signature could then be:

function getNodeFromPath(ast: t.File, path: Path): Result<t.Node | t.Node[], string>

@boredcity
Copy link

I'm not sure it can actually be directly handled by clever typings from neverthrow, but you could get away with specifically returning a Result<T, E> instead of Ok<T, never> | Err<never, E>:

See your updated typescriptlang example.

import { ok, err, type Result } from "neverthrow";
const result: Result<number, string> = Math.random() ? err("wat") : ok(42);
result.andThen(() => ok({}))

Your actual function's signature could then be:

function getNodeFromPath(ast: t.File, path: Path): Result<t.Node | t.Node[], string>

I believe it is the same solution as this? It's definitely an option, though 👍

I spent a few hours on Sunday tinkering with the types, but no luck as of yet.
Will try my best to figure out a better solution during the weekend though.

@ccjmne
Copy link
Contributor

ccjmne commented Oct 4, 2022

@boredcity Oh... yeah, this is exactly the workaround already figured out and shared. Oops!
This is embarrassing, I completely missed that 🤦‍♀️

I actually ran into a similar problem recently and did spend the few customary weekend hours of tinkering, to no avail.
I'd be very interested in a better solution as well!

@supermacro
Copy link
Owner

Going back to the repro - I'm still confused as to why shared methods / types would just vanish. Could it be that this happens because calling ok returns Result<T, never> while err returns Result<never, E>?

@boredcity
Copy link

boredcity commented Oct 9, 2022

Going back to the repro - I'm still confused as to why shared methods / types would just vanish. Could it be that this happens because calling ok returns Result<T, never> while err returns Result<never, E>?

actually, the complete error string is

Each member of the union type
'{
  <R extends Result<unknown, unknown>>(f: (t: number) => R): Result<InferOkTypes<R>, InferErrTypes<R>>;
  <U, F>(f: (t: number) => Result<U, F>): Result<U, F>;
} | {
  <R extends Result<unknown, unknown>>(_f: (t: never) => R): Result<InferOkTypes<R>, string | InferErrTypes<R>>;
  <U, F>(_f: (t: never) => Result<U, F>): Result<U, string | F>;
}'
has signatures, but none of those signatures are compatible with each other.

so for me, the more surprising part is string | F...

and it goes away if you comment out

  andThen<R extends Result<unknown, unknown>>(
    f: (t: T) => R,
  ): Result<InferOkTypes<R>, InferErrTypes<R> | E>

in Ok and

  andThen<R extends Result<unknown, unknown>>(
    _f: (t: T) => R,
  ): Result<InferOkTypes<R>, InferErrTypes<R> | E>

in Err (other errors appear though 😄).

Still trying to fix the problem, but to no avail for now :/

@boredcity
Copy link

Honestly, I think I'm ready to throw in the towel at this point 😖
If I find a solution for it at some point in the future and the problem still persists I'd be happy to create a PR, but for now, it's probably better to let someone with a better handle on Typescript have a go at this 😕

@supermacro
Copy link
Owner

@tam-carre @incetarik any ideas on what might be happening here?

@supermacro supermacro pinned this issue Oct 16, 2022
@incetarik
Copy link
Contributor

@tam-carre @incetarik any ideas on what might be happening here?

I'll check this out when I complete the other one 👍🏻

@ghost
Copy link

ghost commented Oct 17, 2022

Not sure what the fix it, but I've had the need to annotate away the nevers multiple times in other contexts. I'm sure it is possible for the Ok<OkType> to not have to contain an Err type and vice versa. If we could find the way to achieve this this would likely fix this issue and several others.

@incetarik
Copy link
Contributor

I see, in short this error occurs when the overloads are not compatible with each other.
I may try to fix this but that may cause changing the types and interfaces accordingly, yet I would try to minimize the visible changes to outside. I'll do several things when I'm free but these days I'm busy, I'll notify you about this when I start. @supermacro

@pyrho
Copy link

pyrho commented Feb 7, 2023

diff --git a/src/result.ts b/src/result.ts
index 59edf44..0291597 100644
--- a/src/result.ts
+++ b/src/result.ts
@@ -213,7 +213,6 @@ export class Ok<T, E> implements IResult<T, E> {
   andThen<R extends Result<unknown, unknown>>(
     f: (t: T) => R,
   ): Result<InferOkTypes<R>, InferErrTypes<R> | E>
-  andThen<U, F>(f: (t: T) => Result<U, F>): Result<U, E | F>
   // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
   andThen(f: any): any {
     return f(this.value)
@@ -276,7 +275,6 @@ export class Err<T, E> implements IResult<T, E> {
   andThen<R extends Result<unknown, unknown>>(
     _f: (t: T) => R,
   ): Result<InferOkTypes<R>, InferErrTypes<R> | E>
-  andThen<U, F>(_f: (t: T) => Result<U, F>): Result<U, E | F>
   // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
   andThen(_f: any): any {
     return err(this.error)
diff --git a/tests/typecheck-tests.ts b/tests/typecheck-tests.ts
index 8fd7ce7..178db2e 100644
--- a/tests/typecheck-tests.ts
+++ b/tests/typecheck-tests.ts
@@ -136,7 +136,7 @@ import { Transpose } from '../src/result'
     (function it(_ = 'allows specifying the E and T types explicitly') {
       type Expectation = Result<'yo', number>
 
-      const result: Expectation = ok(123).andThen<'yo', number>(val => {
+      const result: Expectation = ok(123).andThen<Result<'yo', number>>(val => {
         return ok('yo')
       })
     });

This patch fixes the original issue but introduces a new one: the need to explicitely set the template parameters for andThen to Result<A, B> instead of just <A, B>.

In my personal use of neverthrow I never explicitely type instance methods like this, but I do run into the issue OP mentioned quite often.

The patch will also cause breaking changes if people relied on explicitely typing .andThen so it's far from ideal.

Just thought I'd share this finding.

@m-shaka
Copy link
Collaborator

m-shaka commented Aug 10, 2023

Hi.
I read the announcement of TypeScript 5.2 RC and thought this improvement could be helpful for this problem.
I found it still doesn't work but let me share FYI (See the playground)

@Lp-Francois
Copy link

Running into the same issue 😅

I'll add that it doesn't cause the same behaviour with .asyncAndThen()

And the error is the same with ResultAsync.andThen()

Screenshot 2023-08-16 at 22 13 16

➡️ TS Playground link

@supermacro supermacro unpinned this issue Oct 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hacktoberfest help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

8 participants