Search code examples
typescriptdiscriminated-unionunion-types

Calling a function from a map of union types


I have a mapping for a discriminated union from a type of the union to a function using a member of the union as a parameter:

export interface Truncate {
  type: 'truncate'
  maxLength: number
}

export interface Append {
  type: 'append'
  suffix: string
}

export type Applicable = Truncate | Append

const applicableMap: { [K in Applicable['type']]: (base: string, applicable: Extract<Applicable, { type: K }>) => string } = {
  'truncate': (s, applicable) => {
    return s.slice(0, applicable.maxLength)
  },
  'append': (s, applicable) => {
    return s + applicable.suffix
  }
}

So far so good; TypeScript correctly infers the types of the applicable argument in each function and performs an exhaustive checking on the keys, so I don't accidentally forget to add an entry to the map if I add a new member.

Now, I have an apply function that picks a function from the map and tries to call it:

function apply(s: string, applicable: Applicable): string {
  return applicableMap[applicable.type](s, applicable)
}

However, the fails to compile due to the folllowing:

Argument of type 'Applicable' is not assignable to parameter of type 'never'.
The intersection 'Add & Append' was reduced to 'never' because property 'type' has conflicting types in some constituents.
Type 'Add' is not assignable to type 'never'.ts(2345)

This is quite surprising to me, as I would expect TypeScript to correctly infer that this will always be a valid call.

The only fix I found so far is to call it as applicableMap[applicable.type](s, applicable as any), however I find using any to be always a hack.


Now the question is:

Is this just a current TypeScript limitation, or is there a way to adjust the types here whilst preserving the type inference and exhaustive checking?


Solution

  • Check on https://github.com/microsoft/TypeScript/issues/30581 which seems like an acknowledged limitation in the Typescript compiler, and has roughly the same shape as your problem I think.