When attempting to perform an exhaustive check on a generic Enum, the process fails

One interesting feature of TypeScript is the ability to exhaustively check Enums, such as in switch statements.

Challenge

I am faced with the task of using an Enum as a Generic type to restrict other parameters based on the provided enumVal.

While it may be simpler to overload the function for this basic example, practicality becomes an issue when dealing with numerous possible combinations. For instance, consider the following enum:

Illustration

enum MyEnum {
  FOO = 'foo',
  BAR = 'bar'
}

Utilizing the Enum directly results in successful compilation:

function exhaustiveSwitch(enumVal: MyEnum) {
  switch (enumVal) {
    case MyEnum.FOO:
      break
    case MyEnum.BAR:
      break
    default:
      const _exhaustiveCheck: never = enumVal
  }
}

However, incorporating the Enum as a Generic triggers the error message

Type 'EnumVal' is not assignable to type 'never'
:

function exhaustiveSwitchGeneric<EnumVal extends MyEnum> (enumVal: EnumVal) {
  switch (enumVal) {
    case MyEnum.FOO:
      break
    case MyEnum.BAR:
      break
    default:
      const _exhaustiveCheck: never = enumVal //Type 'EnumVal' is not assignable to type 'never'
  }
}

(view example here)

Inquiry

  1. Why does using the Enum as a Generic fail in this scenario?
  2. Is there an alternative method to utilize the exact value provided (e.g. MyEnum.FOO) for another parameter without employing Generics? (excluding function overloading)

While there might be valid reasons behind this behavior, I believe that employing Generics in this manner should yield similar results to direct usage.

Answer №1

The issue here is quite significant. In TypeScript, it is not possible to extend Enums as there is no syntax or support for it. Therefore, attempting code like EnumVal extends MyEnum will only work for MyEnum. If you wish to extend a variant, you need to create a literal union such as 'foo' | 'bar', where the `extends` statement makes sense.

Below is evidence demonstrating the inability to extend Enum:

enum MyEnum {
  FOO = 'foo',
  BAR = 'bar'
}

enum MySubEnum {
  FOO = 'foo',
}

type MySubEnumExtendsMyEnum = MySubEnum extends MyEnum ? true : false // false
exhaustiveSwitchGeneric(MySubEnum.FOO)
// compile error: 
// Argument of type 'MySubEnum' is not assignable to parameter of type 'MyEnum'.

Furthermore, exclusive switch is not achievable with generics. However, a workaround is possible. The following example utilizes string literals since extending Enums is not an option. See below:

type MyVariant = 'foo' | 'bar';
type SubVariant = 'foo';

type SubVariantExtendsMyVariant = SubVariant extends MyVariant ? true : false; // true

function exhaustiveSwitchGeneric<Val extends MyVariant>(val: Val) {
  const v = val as MyVariant // important line
  switch (v) {
    case 'foo':
      break
    case 'bar':
      break
    default:
      const _a: never = v;
  }
}

We employ a trick by using val as MyVariant, ensuring that the switch is exclusive and covers all variants of MyVariant

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

Organizing Activities in Angular Version 5

Looking for an event calendar solution for Angular 5 After thorough research, I came across FullCalendar. However, I encountered several issues while trying to implement it in my Angular project 5. Is there any alternative to FullCalendar that is compati ...

JavaScript's async function has the capability to halt execution on its own accord

Presented here is a JavaScript async function designed to populate a sudoku board with numbers, essentially solving the puzzle. To enhance the user experience and showcase the recursion and backtracking algorithm in action, a sleeper function is utilized b ...

TypeScript is unable to detect the .sequelizerc configuration file

I have a file called .sequelizerc which contains the following configuration: const path = require('path'); module.exports = { config: path.resolve('.', 'src/config/sequelizeCLIConfig.json'), 'migrations-path': ...

Are fp-ts and Jest the perfect pairing for testing Option and Either types with ease?

When working with fp-ts, and conducting unit tests using Jest, I often come across scenarios where I need to test nullable results, typically represented by Option or Either (usually in array find operations). What is the most efficient way to ensure that ...

Preselecting items in PrimeNG Checkbox: A step-by-step guide

How can I ensure that the already selected item is displayed upon loading this div? The code in `rp` consists of an array of type Permission with one element, which should be automatically selected. What could be causing the issue? Here is the HTML snippe ...

Angular HttpClient mapping causes the removal of getters from the target object

Utilizing the HttpClient to fetch Json data from an API, I am utilizing the autoMapping feature of the HttpClient to map the json response to a specified object in this manner: this.httpClient.post<Person>(url, body, { headers: headers, params: http ...

I am encountering an issue with importing modules from the public folder in Next.js when using TypeScript, as I am

I've been running into an issue with importing files in Next.js using TypeScript. I'm trying to use regular imports with custom absolute paths, but I keep getting a module not found error. Oddly enough, my IDE is able to locate the file when I cl ...

How can Java enums be utilized in various scenarios?

Suppose I have the following enum class in Java, how can I access it in various ways? public enum DayOfWeek { SUNDAY(0), MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6); Integer code; DayOfWe ...

Exploring how to traverse a <router-outlet> within its container

I am attempting to switch the active component within a from its parent. After observing how Ionic achieves this, I believe it should resemble the following (simplified): @Component({ template: '<router-outlet></router-outlet>' } ...

Transfer all specified resources from one stack to another in AWS CDK

In the process of creating two stacks, I aim to reference the resources from the first stack, such as Lambda, API Gateway, and DynamoDB, in the second stack without hard coding all the resources using Stack Props. Please note: I do not want to use Stack Pr ...

Using FIND to search an array object results in an error: Type 'undefined' is not compatible with type ''

I'm currently attempting to search for an element within one array and then assign it to another array object. However, I keep receiving the following error message: Type 'ClosureSummary | undefined' is not assignable to type 'Closure ...

"Utilizing Firebase Functions to update information in the Firebase Realtime Database on a daily basis

Currently, I am in the process of working on a project where I aim to provide users with a daily percentage of points based on their current available points and update this data in my Firebase database. My goal is to add points for users on a day-to-day b ...

Explicit final argument in TypeScript

Is it feasible to define a function in TypeScript 2.7.2 and above with variable parameters, but ensuring that the final parameter has a specific type? I am attempting to craft an ambient TypeScript declaration for a JavaScript library that utilizes functi ...

Angular2, multi-functional overlay element that can be integrated with all components throughout the application

These are the two components I have: overlay @Component({ selector: 'overlay', template: '<div class="check"><ng-content></ng-content></div>' }) export class Overlay { save(params) { //bunch ...

Passing the state variable from a hook function to a separate component

I have a hook function or file where I need to export a state named 'isAuthenticated'. However, when I try to import this state using import {isAuthenticated} from '../AuthService/AuthRoute', I encounter an import error. My goal is to m ...

What is the process by which Angular 2 handles imports?

Currently diving into the world of Angular2 with TypeScript. I understand that SystemJS is crucial for enabling the import feature, like this example: import { bootstrap } from "angular2/platform/browser"; While this all makes sense, I find myself questi ...

Is there a way to convert TSX source code into regular TS code?

I am in need of the functionality offered by @babel/plugin-transform-react-jsx, while keeping all types, TypeScript language features, ESNext syntax, comments, etc. I aim to convert TypeScript XML-formatted code into plain TypeScript using a specific JSX ...

Angular button for opening or closing the menu that redirects the page to a localhost URL

I have implemented a template from the link below into my project. So far, everything has been working fine, but recently I noticed that the menu open/close button is malfunctioning. Whenever I click on the close button while on any page (for example, http ...

Troubleshooting overload errors within ReactJS: tips and tricks

import React from 'react' import { Link } from 'react-scroll' import "./Protocol.css" import { ANALYTICS, TRADE, USERS, TRADERS, VOTES, ZEROES } from "../../Constants" const Protocol = () => { return ( ...

What strategies can I use to address the problem of 'unable to resolve import in '@/...'" in vitest?

I encountered an error related to a file path defined in the "vite.config.ts" file. Can you assist me with this issue? Error Log Error Message: FAIL tests/utils/ConvertFromDomainToCountryCode.test.ts [ tests/utils/ConvertFromDomainToCountryCode.test.ts ...