I have the problem when I use ts-results-es.
It helps to wrap errors in a class so that we can see what errors a function may throw.
Simplified class definition:
interface BaseResult<T, E> {}
class Err<E> implements BaseResult<never, E> {
readonly error!: E
constructor(val: E) {
this.error = val
}
}
class Ok<T> implements BaseResult<T, never> {
readonly value!: T
constructor(val: T) {
this.value = val
}
}
type Result<T, E> = Ok<T> | Err<E>
Later I have a function randomResult: () => Ok<T> | Err<U> | Err<V>
and I also have a function that takes a result-returning function as the input function eatResultFn<T, E>(fn: () => Result<T, E>) {}
.
Sometimes Typescript doesn’t recognize randomResult
to be () => Result<T, U | V>
and throws an error. This is what the real problem I’d like to solve.
My first idea is to use Result.mapErr()
interface BaseResult<T, E> {
mapErr<F>(mapper: (val: E) => F): Result<T, F>
}
class Err<E> implements BaseResult<never, E> {
// ...
mapErr<E2>(mapper: (err: E) => E2): Err<E2> {
return new Err(mapper(this.error))
}
}
class Ok<T> implements BaseResult<T, never> {
// ...
mapErr(_mapper: unknown): Ok<T> {
return this
}
}
which maps the error to another one.
My mapper is simply e => e
or precisely function (e: U | V) : U | V { return e }
,
but unfortunately I still sometimes get an error that says something like: Argument of type '() => Ok<T> | Err<E2> | Err<U | V>' is not assignable to parameter of type '() => Result<T, E2>'.
E2
?!?!?!
I have no idea why E2
shows here and I expect mapErr
will resolve my problem.
Any solution to get rid of the E2
or any workaround (except directly writing the exact returned type) to my original problem is appreciated.
Thanks for any help 🙂
Please note that the code sometimes works (see
Typescript Playground),
which is another thing I have no idea why.
This is a bug in TypeScript. Generic type parameters like E2
should not be able to “escape” their scopes and roam around unbound in the rest of your code. I have filed microsoft/TypeScript#57356, which has been classified as a bug and, at least as of today (2024-02-10) it has been marked as something that should be fixed for TypeScript 5.5. Sometimes these timelines slip, so we’ll have to wait and see. I’ll edit this answer if there are updates. Until then you’ll need to work around it, probably by annotating the actually expected types even though that’s not what you want to do.
This looks like a bug to me in TypeScript, but I’m not sure if it’s a known one. The term is usually “type parameter leak” as in ms/TS#43961. You might want to file an issue about it in the TS GitHub repo (after paring it down to a minimum example)? As for a workaround, that’s going to be tough to do consistently in the face of a compiler bug.
see prev comment, if you want to file an issue you might use this version of the code which seems fairly minimal. I might be able to file it myself and update here when it’s triaged, but only if you think that would fully address the question if so. Let me know
@jcalz Thanks for the comments. I believe my problem will be solved along with a fix of the type parameter leak, as the errors are always something about E2. I’m afraid that I don’t know TypeScript well to file an issue in GitHub. It’d be much appreciated if you can help me doing so.
Okay I opened ms/TS#57356 and will report back here when I know more.
Well, it’s a TS bug and they’ve got it tentatively scheduled to be fixed for TS5.5. Shall I write up an answer saying as much? Or am I missing something else about the question?
Show 2 more comments