I had a library in my project that I stopped using (deleted from package.json
), and one of its peer dependencies was fp-ts
, so I had to add fp-ts
to my project. fp-ts
has an Either
type which can be checked for left/right values:
export declare const isLeft: <E>(ma: Either<E, unknown>) => ma is Left<E>
If I put this in an if
if (E.isLeft(result)) {
// ...
}
then in the else
Typescript will correctly infer that my Either
has a right
value.
My problem is that ever since I moved the dependency to my project (instead of indirectly using it as a peer dependency) the following case doesn't work anymore, I get a compiler error:
const fail = (msg: string): never => {
throw new GenericProgramError(msg);
};
if (E.isLeft(result)) {
fail("Expected a successful result");
}
expect(result.right).toEqual(
// ^^^--- Property 'right' does not exist on type 'Left'
// ...
);
The problem here is that if result
is a Left
then I fail
which returns never
(throws), so Typescript should be able to infer that in expect
result
can only have a right
and not a left
. And this was working before. What do I need to change to fix this?
The answer is that your fail
assertion function should be a function statement and not a fat-arrow function.
(Thanks to david_p's answer here: https://stackoverflow.com/a/72689922/81723)
So just change to function fail(...): never
and it should work:
function fail(msg: string): never {
throw new GenericProgramError(msg);
}
Here's the full working example:
import * as E from 'fp-ts/Either';
declare function expect(value: any);
const result = E.right<string, boolean>(true);
function fail(msg: string): never {
throw new Error(msg);
}
if (E.isLeft(result)) {
fail("Expected a successful result");
}
expect(result.right).toEqual();
// ^^^ ✅ result: E.Right<boolean>
Why this works
I stumbled onto the answer in a roundabout way.
I was trying to work this out by changing your code to an assertion function.
But I got the strange error: "Assertions require every name in the call target to be declared with an explicit type annotation.(2775)"
const fail2 = (result: E.Either<any, any>): asserts result is E.Right<any> => {
if (E.isLeft(result)) {
throw new Error('Expected a successful result');
}
};
fail2(result);
// Error: Assertions require every name in the call target to be declared with an explicit type annotation.(2775)
Searching for this error gave me david_p's answer which explains that you can't use an arrow function as an assertion function (well, technically you can, but you need to explicitly define the type signature for the variable being assigned to. In practice it's easier to just use a function statement instead).