neverthrow: "expression is not callable" with .andThen()

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?

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 2
  • Comments: 18 (8 by maintainers)

Most upvoted comments

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.

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

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 😕

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.

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.