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

.orElse() does not work properly #197

Open
xkatianx opened this issue Feb 15, 2025 · 5 comments
Open

.orElse() does not work properly #197

xkatianx opened this issue Feb 15, 2025 · 5 comments

Comments

@xkatianx
Copy link

"dependencies": {
  "typescript": "5.7.3",
  "ts-results-es": "5.0.1"
}
import { Ok, Err, type Result } from "ts-results-es";

function r() {
  return [Ok(0), Err(""), Err(!0)][0];
}

r().orElse(r); // error

This expression is not callable.
Each member of the union type '{ (other: (error: boolean) => Ok): Result<T, never>; <R extends Result<any, any>>(other: (error: boolean) => R): R; } | { (other: (error: string) => Ok): Result<...>; <R extends Result<...>>(other: (error: string) => R): R; } | ((_other: (error: never) => Result<...>) => Ok<...>)' has signatures, but none of those signatures are compatible with each other.ts(2349)

Workaround:

type OkContent<T> = T extends Ok<infer U> ? U : never;
type ErrContent<T> = T extends Err<infer U> ? U : never;

declare module "ts-results-es" {
  interface ErrImpl<E> {
    andThen2(fn: unknown): Err<E>;
    orElse2<R extends Result<OkContent<R>, ErrContent<R>>>(
      fn: (err: E) => R
    ): R;
  }
  interface OkImpl<T> {
    andThen2<R extends Result<OkContent<R>, ErrContent<R>>>(
      fn: (val: T) => R
    ): R;
    orElse2(fn: unknown): Ok<T>;
  }
}

r().orElse2(r); // no error

.andThen() probably has the same issue.

@jstasiak
Copy link
Collaborator

Hey @xkatianx, thank you for the report.

May I ask what's the situation where you see types like this (Ok<...> | Err<...> | Err<...>)?

@xkatianx
Copy link
Author

Not really Ok<...> | Err<...> | Err<...> but here is one of my situation simplified:

class T1 { name = "T1" as const }
class T2 { name = "T2" as const }
class T3 { name = "T3" as const }
class T4 { name = "T4" as const }
class T5 { name = "T5" as const }
class T6 { name = "T6" as const }

function foo1(): Result<T1, T2> {
  return 0 as any
}
function foo2(): Result<T3, T4> {
  return 0 as any
}
function foo3(): Result<T5, T6> {
  return 0 as any
}

const a = foo1().andThen(foo2).orElse(foo3) // error

@xkatianx
Copy link
Author

Some things just won't work when the type is Ok<...> | Err<...> | Err<...>, so I always wrap them with this

export function asResult<T extends Result<OkContent<T>, ErrContent<T>>>(
  res: T,
): Result<OkContent<T>, ErrContent<T>> {
  return res;
}

It'd be nice to have this built in.

@jstasiak
Copy link
Collaborator

I'm on the fence if this is an orElse problem (on the type consumption side so to speak) or andThen problem (because it produces a type that orElse cannot be called on) but either way it's a legitimate issue.

@xkatianx
Copy link
Author

Hey @xkatianx, thank you for the report.

May I ask what's the situation where you see types like this (Ok<...> | Err<...> | Err<...>)?

Now I have a situation.

function foo(arr?: number[]): Result<number[], null> {
  return arr == null ? Err(null) : Ok(arr);
}
function unique(arr?: number[]) {
  return foo(arr).andThen((a) => { // error
    if (a.length === 0) return Err(new NoElementError());
    if (a.length === 1) return Ok(a.pop()!);
    return Err(new ManyElementsError());
  });
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants