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

CatchIf - predicate that is always true does not Exclude<...> error types correctly #4148

Open
akomm opened this issue Dec 16, 2024 · 3 comments
Labels
4.0 Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@akomm
Copy link

akomm commented Dec 16, 2024

What version of Effect is running?

3.11.6

What steps can reproduce the bug?

Code is based on Effect-TS Example for catchif

Open the code below in TS-Playground

import { Effect, Random } from "effect"

class HttpError {
  readonly _tag = "HttpError"
}

class ValidationError {
  readonly _tag = "ValidationError"
}

//      ┌─── Effect<string, HttpError | ValidationError, never>
//      ▼
const program = Effect.gen(function* () {
  const n1 = yield* Random.next
  const n2 = yield* Random.next
  if (n1 < 0.5) {
    yield* Effect.fail(new HttpError())
  }
  if (n2 < 0.5) {
    yield* Effect.fail(new ValidationError())
  }
  return "some result"
})

//      ┌─── should be Effect<string, never, never> but is Effect<string, HttpError | ValidationError, never>
//      ▼
const recovered = program.pipe(
//    ^?
  Effect.catchIf(
    (error) => error._tag === "HttpError" || error._tag === "ValidationError",
    () => Effect.succeed("Recovering from HttpError")
  )
)

What is the expected behavior?

The inferred type of const recovered should be Effect<string, never, never>

What do you see instead?

Effect<string, HttpError | ValidationError, never>

Additional information

When a function does not narrow down the input, its not inferred as a type predicate: microsoft/TypeScript#60741 (comment)

This cause non-linear behavior in catchIf.

I know that if statically known at implementation time, I could use catchTags or catchAll.

But when the predicate changes based on external input and the implementation is independent of it, the input change affecting the predicate will require a change in the implementation. It is unexpected behavior and need to be tracked down.

Here is a suggestion adding an overload for this case: microsoft/TypeScript#60741 (comment)

@akomm akomm added the bug Something isn't working label Dec 16, 2024
@akomm akomm changed the title CatchIf - predicate that is always true or false does not Exclude<...> error types correctly CatchIf - predicate that is always true does not Exclude<...> error types correctly Dec 16, 2024
@akomm
Copy link
Author

akomm commented Dec 16, 2024

Based on some new finding, it might actually be impossible to reliable solve this on Effect-TS side: microsoft/TypeScript#60741 (comment)

@mikearnaldi
Copy link
Member

Unfortunately in cases where the check doesn't reduce the input TS doesn't infer a type predicate, unsure about the choice on the TS side but we have no control over it

@mikearnaldi mikearnaldi added Design Limitation Constraints of the existing architecture prevent this from being fixed and removed bug Something isn't working labels Dec 16, 2024
@mikearnaldi
Copy link
Member

The only thing we could do is to not use predicates in the first place:

export const skip: unique symbol = Symbol.for("....")
export type skip = typeof skip

export declare const catchIf2: {
  <E, X, A2, E2, R2>(
    refinement: (e: E) => X | skip,
    f: (e: Exclude<NoInfer<X>, skip>) => Effect<A2, E2, R2>
  ): <A, R>(self: Effect<A, E, R>) => Effect<A2 | A, E2 | Exclude<X, skip>, R2 | R>
}

used like:

const recovered = program.pipe(
  Effect.catchIf2(
    (error) => error._tag === "HttpError" || error._tag === "ValidationError" ? Effect.skip : error,
    () => Effect.succeed("Recovering from HttpError")
  )
)

would do the job.

This is somehow related to #3318

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4.0 Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

2 participants