facebook / flow

Adds static typing to JavaScript to improve developer productivity and code quality.
https://flow.org/
MIT License
22.07k stars 1.86k forks source link

Disjoint unions types with Maps #3466

Open jhnns opened 7 years ago

jhnns commented 7 years ago

I'm trying to use Map as a collection of message handlers. My setup code looks like this:

type OkMessage = {
  type: "ok",
  result: string
}
type ErrorMessage = {
  type: "error",
  error: string
}
type Messages = OkMessage | ErrorMessage
type MessageHandler = (message: Messages) => void
type MessageHandlers = Map<string, MessageHandler>

const messageHandlers: MessageHandlers = new Map()

messageHandlers.set(
  "ok",
  (message: OkMessage): void => console.log(message.result)
)
messageHandlers.set(
  "error",
  (message: ErrorMessage): void => console.error(message.error)
)

function handleMessage(message: Messages): void {
  const handler = messageHandlers.get(message.type)

  handler && handler(message)
}

Messages is a disjoint union type if I understood it correctly. A code branch will be invoked depending on the message.type attribute.

This code snippet, however, gives me a bunch of errors:

2:   type: "ok",
           ^ string literal `ok`. Expected string literal `error`, got `ok` instead
6:   type: "error",
           ^ string literal `error`
6:   type: "error",
           ^ string literal `error`. Expected string literal `ok`, got `error` instead
2:   type: "ok",
           ^ string literal `ok`
17:   (message: OkMessage): void => console.log(message.result)
                ^ property `result`. Property not found in
10: type MessageHandler = (message: Messages) => void
                                    ^ object type
2:   type: "ok",
           ^ string literal `ok`. Expected string literal `error`, got `ok` instead
6:   type: "error",
           ^ string literal `error`
6:   type: "error",
           ^ string literal `error`. Expected string literal `ok`, got `error` instead
2:   type: "ok",
           ^ string literal `ok`
21:   (message: ErrorMessage): void => console.error(message.error)
                ^ property `error`. Property not found in
10: type MessageHandler = (message: Messages) => void
                                    ^ object type

Try it for yourself.

Am I missing something or is this a bug in Flow?

vkurchatkin commented 7 years ago

(message: OkMessage): void => console.log(message.result) doesn't have type MessageHandler

jhnns commented 7 years ago

Yes, you're right. But I have no clue how to model this correctly. I also tried this:

type OkMessage = {
  type: "ok",
  result: string
}
type ErrorMessage = {
  type: "error",
  error: string
}
type MessageHandler<M> = (message: M) => void
type MessageHandlers<H> = Map<string, H>

const messageHandlers: MessageHandlers<MessageHandler<OkMessage> | MessageHandler<ErrorMessage>> = new Map()

messageHandlers.set(
  "ok",
  (message: OkMessage): void => console.log(message.result)
)
messageHandlers.set(
  "error",
  (message: ErrorMessage): void => console.error(message.error)
)

function handleMessage(message: OkMessage | ErrorMessage): void {
  const handler = messageHandlers.get(message.type)

  handler && handler(message)
}

and it reports a similar error.