Tips for setting up Typescript to deduce discriminated unions when `is` and `never` are included in the code

In the past, I had a library included in my project that I later removed (deleted from package.json). However, this library had a peer dependency on fp-ts, so I had to directly add fp-ts to my project. The fp-ts library includes an Either type which allows for checking left/right values:

export declare const isLeft: <E>(ma: Either<E, unknown>) => ma is Left<E>

When I use this in an if statement like so:

if (E.isLeft(result)) {
    // ...
}

then TypeScript correctly infers the type in the else block to be a right value.

However, after moving the dependency to be directly included in my project instead of just as a peer dependency, I encountered an issue where the following case no longer works, resulting in 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 arises from the fact that if result is a Left, the fail function is called, which returns a type of never (throws an error). Therefore, TypeScript should be able to infer that in the expect statement, result can only have a right value and not a left. This was functioning correctly before. What adjustments do I need to make to resolve this issue?

Answer №1

It turns out that in order for your fail assertion function to work properly, it should be written as a regular function statement rather than a fat-arrow function. (Thanks to the insights shared by david_p in this post)

Simply make the change to function fail(...): never and everything should function correctly:

function fail(msg: string): never {
    throw new GenericProgramError(msg);
}

Below is a fully functional 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>

Explanation of Why This Works

I arrived at this solution through some trial and error.

Initially, I attempted to convert your code into an assertion function.

However, I encountered a peculiar issue: "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)

Researching this error led me to david_p's explanation, which clarified that arrow functions cannot be used as assertion functions (technically, it is possible but involves explicitly defining the type signature for the assigned variable; using a function statement is generally simpler).

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to access dynamic URL parameters in the Next.js 13 app router from a nested server-side component?

This query pertains to the Server-Side components in Next.js 13 app router: I have a good grasp on how the slug parameter is passed to the default function in app/blog/[slug]/page.tsx: export default function Page({ params }: { params: { slug: string } }) ...

Using Cypress and JWT, automate the login process for JHipster

Is there a way to automate the bypassing of the JHipster login screen? This is my goal: let jwt_token before(function fetchUser() { cy.request('POST', '/api/authenticate', { username: 'user', password: &a ...

Determine whether there is only one array in the object that contains values

At the moment, I am attempting to examine an array in order to determine if only one of its elements contains data. Consider this sample array: playersByGender = { mens: [], womens: [], other: [] }; Any combination of these elements may contain dat ...

Learn the method for triggering events with a strongly-typed payload in Vue 3 Composition API and TypeScript

I'm currently exploring Vue 3 Composition API along with TypeScript, particularly focusing on emitting events with a strictly typed payload. There's an example provided below, but I'm unsure if it's the most effective way to achieve t ...

The React state remains stagnant and does not receive any updates

Although there have been numerous questions on this topic before, each one seems to be unique and I haven't found a close match to my specific issue. In my scenario, I have a grid containing draggable ItemComponents. When an item is selected, additio ...

Exploring Angular 5's *ngFor directive with an array of objects

Here is the data I am working with: Initial set of data: var input = [ {ru: "R201", area: "211", unit: "211"}, {ru: "R201", area: "212", unit: "NONE"}, {ru: "R201", area: "HCC", unit: "NONE"}]; Desired result data: var result = [ {area: ...

Issues encountered while establishing a connection to an API in React Native

When attempting to log in a user by connecting to my API, I encountered some issues. It seems that every time my laptop has a different IP address, I need to make changes in the file where the fetch or XMLHttpRequest is located in order for the login proce ...

Prevent the event listener from continuously triggering

I have a situation where every time I create an Angular component, an event listener is added. However, upon leaving the page and returning to it, a new event listener is added because the constructor is called again. The problem arises when this event is ...

The TypeScript compiler is tolerant when a subclass inherits a mixin abstract class without implementing all its getters

Update: In response to the feedback from @artur-grzesiak below, we have made changes to the playground to simplify it. We removed a poorly named interface method and now expect the compiler to throw an error for the unimplemented getInterface. However, the ...

Issue occurred while trying to render a React component with Typescript and WebPack

I am in the process of creating a basic React component that simply displays a page saying Hello. However, I'm encountering an error in my console. My compiler of choice is TypeScript. To set up my project, I am following this guide: https://github.co ...

What is the most effective method for delivering a Promise after an asynchronous request?

Currently, I am working on creating an asynchronous function in TypeScript that utilizes axios to make an HTTP request and then returns a Promise for the requested data. export async function loadSingleArweaveAbstraction(absId : string) : Promise<Abstra ...

What is the best way to utilize the next-env.d.ts file within Next.js?

In my Next.js TypeScript project, I came across a file named next-env.d.ts. This got me thinking about how I can declare enums that would be accessible across all my Next.js files. Can you guide me on how to achieve this and use the enums throughout my p ...

I'm trying to determine in Stencil JS if a button has been clicked in a separate component within a different class. Can anyone assist

I've created a component named button.tsx, which contains a function that performs specific tasks when the button is clicked. The function this.saveSearch triggers the saveSearch() function. button.tsx {((this.test1) || this.selectedExistingId) && ...

Can you clarify the typescript type of the Mutation list within the callback function of the mutation observer?

Can you provide the typescript data type of the variable mutationList in the callback function? const targetNode = document.getElementById("some-id"); const config = { attributes: true, childList: true, subtree: true }; const callback = (mutationList, ...

Having trouble executing my Node.js project written in Typescript due to an error: TypeError [ERR_UNKNOWN_FILE_EXTENSION] - the file extension ".ts" for /app/src/App.ts is unrecognized

After attempting to launch my application on Heroku, I encountered the following stack trace. The app is a basic ts.app using ts-node and nodemon. I am eagerly awaiting the solution to this issue. 2020-05-30T00:03:12.201106+00:00 heroku[web.1]: Starting p ...

What steps can be taken to resolve the error message "Property does not have an initializer and is not definitively assigned in the constructor"?

I'm encountering an issue with these classes. I want to utilize the doSomething() method that is exclusive to class B without having to type cast it each time. However, when I specify property a to be of type B, it gives me an error saying it's n ...

Retrieve the array from the response instead of the object

I need to retrieve specific items from my database and then display them in a table. Below is the SQL query I am using: public async getAliasesListByDomain(req: Request, res: Response): Promise<void> { const { domain } = req.params; const a ...

issue TS2322: The function returns a type of '() => string' which cannot be assigned to type 'string

I have recently started learning Angular 6. Below is the code I am currently working on: export class DateComponent implements OnInit { currentDate: string = new Date().toDateString; constructor() { } ngOnInit() { } } However, I am encounterin ...

Using Rollup for TypeScript imports with absolute paths

Link to Source Code: https://github.com/arvigeus/roll-on-slow Output Bundle Location: dist Build Log: build.log After bundling with Rollup, warnings are thrown for incorrect source maps (Error when using sourcemap for reporting an error: Can't resolv ...

What is the most effective method for integrating templates using AngularJS and Webpack2?

UPDATE: I haven't come across a method to import templates using an import statement rather than require, but I have realized that I can streamline my configuration. In the webpack config, opt for html-loader over ngtemplate-loader for /\.html$/ ...