Home > Software engineering >  Identifying the second parameter type when it depends on the first parameter type in the function�
Identifying the second parameter type when it depends on the first parameter type in the function�

Time:09-27

I have 2 types of messages and different context for each.

I've created a function which takes type and context and is able to tell the caller of the function which context to use based on the type

const MessageOptions = {
  textMessage: "textMessage",
  interactiveFormMessage: "interactiveFormMessage"
} as const;

type MessageOptions = typeof MessageOptions[keyof typeof MessageOptions];

interface SimpleTextMessageContext { ... }

interface InteractiveFormMessageContext { ... }


interface MessageTypes {
  textMessage: SimpleTextMessageContext;
  interactiveFormMessage: InteractiveFormMessageContext;
};

function sendInteractiveMessage<T extends MessageOptions>(
  type: T,
  details: MessageTypes[T]
) { ... }

sendInteractiveMessage(MessageOptions.textMessage, /* expects text message context */{});
sendInteractiveMessage(MessageOptions.interactiveFormMessage, /* expects form context */{});

The problem I'm having is that inside the implementation of sendInteractiveMessage I cannot refer to details as the right context w/o details as SimpleTextMessageContext. see example:

function sendInteractiveMessage<T extends MessageOptions>(
  type: T,
  details: MessageTypes[T]
) {
  if (type === MessageOptions.textMessage) {
    details // type here is MessageOptions[T] and NOT SimpleTextMessageContext
    // ^?
  }
}

Is there a way to effect details's type in the implementation based on type's type?

Thank you!

Playground Link

CodePudding user response:

You can achieve the same behavior as before while having the types being narrowed by creating a union of the possible arguments to your function:

type Args = {
  [K in MessageOptions]: [type: K, details: MessageTypes[K]];
}[MessageOptions]

function sendInteractiveMessage(...[type, details]: Args) {
  if (type === MessageOptions.textMessage) {
    details
  }
}

You'll notice that autocomplete now gives you fields of both types for the details parameter, which is understandably annoying. You can opt-in to use an external signature (which is just your original functions') so userland is happy and you're happy with the function body as well:

type Args = {
  [K in MessageOptions]: [type: K, details: MessageTypes[K]];
}[MessageOptions]

// extra signature so userland doesn't get excess invalid properties
function sendInteractiveMessage<K extends MessageOptions>(type: K, details: MessageTypes[K]): void;
function sendInteractiveMessage(...[type, details]: Args) {
  if (type === MessageOptions.textMessage) {
    details
  }
}

Playground

  • Related