Home > OS >  Different behaviors between Named function and Anonymous function in type checking by TypeScript
Different behaviors between Named function and Anonymous function in type checking by TypeScript

Time:07-07

I want to understand the different behaviors between Named function and Anonymous function. Both functions have the same type signature, but Anonymous function version occurs type error.

Those functions are used as exhaustiveness check in switch-case-default statement.

Below code and Ts Config are available at TypeScript: TS Playground.

type Codes = 'A' | 'B';
type AMessage = { code: 'A', messageA: string }
type BMessage = { code: 'B', messageB: string }
type AllMessage = AMessage | BMessage;

/**
 * Named function
 */
function assertUnreachableFunction(_x: never): never {
  throw new Error("Didn't expect to get here");
}

/**
 * Anonymous function
 */
const assertUnreachableConst = (_x: never): never => {
  throw new Error("Didn't expect to get here");
}

type testMethodType = (code: Codes) => AllMessage;

/**
 * OK! A variable "code" is 'A' or 'B' or 'C', so never go through default statement.
 */
const test_PASS1: testMethodType = (code) => {
  switch (code) {
    case 'A':
      const messageA: AMessage = { code, messageA: 'I AM MESSAGE A' };
      return messageA;
    case 'B':
      const messageB: BMessage = { code, messageB: 'I AM MESSAGE B' };
      return messageB;
  }
}

/**
 * OK! An assertUnreachableFunction is handled properly.
 */
const test_PASS2: testMethodType = (code) => {
  switch (code) {
    case 'A':
      const messageA: AMessage = { code, messageA: 'I AM MESSAGE A' };
      return messageA;
    case 'B':
      const messageB: BMessage = { code, messageB: 'I AM MESSAGE B' };
      return messageB;
    default:
      // A variable "code" here is never type.
      assertUnreachableFunction(code);
  }
}

/**
 * NG! Strange behavior. Same signature but anonymous function version is not handled properly.
 * 
 * Type '(code: Codes) => AMessage | BMessage | undefined' is not assignable to type 'testMethodType'.
 *   Type 'AMessage | BMessage | undefined' is not assignable to type 'AllMessage'.
 *     Type 'undefined' is not assignable to type 'AllMessage'.(2322)
 */
const test_FAIL1: testMethodType = (code) => {
  switch (code) {
    case 'A':
      const messageA: AMessage = { code, messageA: 'I AM MESSAGE A' };
      return messageA;
    case 'B':
      const messageB: BMessage = { code, messageB: 'I AM MESSAGE B' };
      return messageB;
    default:
      assertUnreachableConst(code);
  }
}

CodePudding user response:

The problem stems from how function statements, function declarations and arrow functions are treated.

Return types are actually assigned differently when not using type annotations (function declarations get void by default and not never). This is not the case here though.

I don't know why they are treated different when a switch is used, most likely is tied to flow control analysis.

There a couple of ways to get over this:

  1. return the function return assertUnreachableConst(code);. At this point TS will work as expected
  2. explicitly set the type for the anonymous function
/**
 * Anonymous function
 */
const assertUnreachableConst: (_x: never): never = (_x: never): never => {
  throw new Error("Didn't expect to get here");
}

CodePudding user response:

I think the main issue is that throw exceptions are not a type in Typescript, but there is a PR to implement one that looks promising -> https://github.com/microsoft/TypeScript/pull/40468

Alternatively keep the throw inside the executing path, one advantage to this approach is less typing.. :)

eg..

TS Playground

  • Related