1

Typescript playground

Given this union of events:

type Events = 
  | { type: 'CREATE'; data: { foo: string } }
  | { type: 'READ'; uuid: string } 
  | { type: 'UPDATE'; data: { foo: string; uuid: string } };

I've made functions that processes the events, and also I collected a map:

const processors: Processors<Events> = {
  CREATE: (event) => {
    console.log(event.data.foo);
  },
  READ: (event) => {
    console.log(event.uuid);
  },
  UPDATE: (event) => {
    console.log(event.data.uuid);
  },
}

The Processors<Event> type isn't complex and works predictably:

type Processors<E extends EventObject> =
  { [T in E['type']]: (event: Extract<E, { type: T}>) => void };

I've got a problem when trying to use these functions to handle events:

function(event: Events) {
  const receiver = processors[event.type]; //  this produces (event: never) => void 
  receiver(event);
           ^^^^^ error
}

The argument type of the receiver is intersection because it comes from contra-variant position in Processors<Event> type:

receiver(event: 
  { type: 'CREATE'; data: { foo: string } } &
  { type: 'READ'; uuid: string } &
  { type: 'UPDATE'; data: { foo: string; uuid: string } }
);

I assume the Events union is not assignable to the Events intersection.

If I discriminate the union, the problem goes away:

if (event.type === 'CREATE') {
  const receiver = processors[event.type] 
  receiver(event);
}

But this is getting unreasonable as we know that event always has one and only one type at any given time.

Is it possible to solve the problem without type casting? It is as if you can convert the intersection into the union, discriminate intersection.

function(event: Events) {
  const receiver = processors[event.type]; //  this produces (event: never) => void 
  receiver(event);
           ^^^^^ How to solve the problem without useless and redundant code?
}
teux
  • 2,510
  • 2
  • 9
  • 5
  • 1
    You unfortunately do need type assertions (what you're calling "casting") to get this to compile without redundant code. This is a pain point I've seen people run into enough that I filed [microsoft/TypeScript#30581](https://github.com/microsoft/TypeScript/issues/30581) about it; I'll add this question to the growing list there. See [this question](https://stackoverflow.com/questions/56781010/typescript-how-to-map-objects-in-a-discriminated-union-to-functions-they-can-be) for more info – jcalz May 14 '21 at 18:37

0 Answers0