How can I create a compiler error if a variable is NOT of type n

ghz 8months ago ⋅ 134 views

How can I create a compiler error if a variable is NOT of type never?

Let's say I have a list of conditionals and I want to handle every possible value of type. If I added a new type in the future and I forget to handle it, I want an error - at least a run-time error, but ideally a compiler error so I catch my mistake before I deploy.

How can I assert that a variable is of type never?

type Job = { type: 'add'; payload: any } | { type: 'send'; payload: any }

const handleJob = (job: Job) => {
  const add = (job: Job) => {
    console.log(job)
  }
  const send = (job: Job) => {
    console.log(job)
  }

  if (job.type === 'add') {
    add(job)
  } else if (job.type === 'send') {
    send(job)
  } else {
    // `job` is actually type `never` here,
    // this error won't ever be thrown unless additional strings are added to the `Job.type` schema.
    throw new Error(`Unhandled job.type "${(job as Job).type}".`)
  }
}

Answers

To ensure that you handle every possible value of the type property in your Job type and receive a compiler error if you forget to handle a new type in the future, you can use exhaustiveness checking in TypeScript.

One way to achieve this is by using a switch statement with an exhaustive check at the end using a TypeScript feature called exhaustiveness checking. Here's how you can modify your handleJob function to achieve this:

type Job = { type: 'add'; payload: any } | { type: 'send'; payload: any }

const handleJob = (job: Job) => {
  const add = (job: Job) => {
    console.log(job)
  }
  const send = (job: Job) => {
    console.log(job)
  }

  switch (job.type) {
    case 'add':
      add(job);
      break;
    case 'send':
      send(job);
      break;
    default:
      // This line will produce a compiler error if there's a new type added to Job
      // that is not handled in the switch statement
      const _exhaustiveCheck: never = job;
      throw new Error(`Unhandled job.type "${job.type}".`);
  }
}

In this code:

  • We use a switch statement to handle each possible value of the type property.
  • The default case contains a TypeScript assertion that job is of type never. This ensures that TypeScript will produce a compiler error if there's a new type added to Job that is not handled in the switch statement.
  • If the default case is reached, it means that job has a type that is not explicitly handled, and an error is thrown.

This approach provides safety against forgetting to handle new types in the future, as TypeScript will give you an error during compilation if you miss handling any case.