I want to create some abstract relation between Message
and Response
sub-types, such that I can have a generic function that takes a Message
as an argument and returns a Response
. However whenever the function is called with type MessageTypeA
I want the response type to be of ResponseTypeA
and I want TypeScript to assure this relation in some way.
Consider the following TypeScript Code to illustrate the problem:
type MessageTypeA = { a: string };
type MessageTypeB = { b: number };
type MessageTypeC = [number, number];
type Message = MessageTypeA | MessageTypeB | MessageTypeC;
type ResponseTypeA = { a: boolean; aa: number };
type ResponseTypeB = "hello" | "bye";
type ResponseTypeC = number;
type Reponse = ResponseTypeA | ResponseTypeB | ResponseTypeC;
type Pair<M extends Message, R extends Reponse> = {
message: M;
response: R;
};
// I know this should be some type of Map/Record instead
type ValidPairsMap =
| Pair<MessageTypeA, ResponseTypeA>
| Pair<MessageTypeB, ResponseTypeB>
| Pair<MessageTypeC, ResponseTypeC>;
// Something like this, I know syntax is wrong here:
function sendMessageReturnResponse<T extends ValidPair>(
message: typeof T.message
): typeof T.response {
throw "idk how to do this";
}
function main() {
let mesA: MessageTypeA = { a: "msg" };
let resA: ResponseTypeA = sendMessageReturnResponse(mesA);
// error: return value of sendMessageReturnResponse cannot be inferred to be ResponseTypeA
}
What can I do to achieve the objective described above?
CodePudding user response:
I believe you are looking for something like this:
type MessageTypeA = { a: string };
type MessageTypeB = { b: number };
type MessageTypeC = [number, number];
type Message = MessageTypeA | MessageTypeB | MessageTypeC;
type ResponseTypeA = { a: boolean; aa: number };
type ResponseTypeB = "hello" | "bye";
type ResponseTypeC = number;
type Reponse = ResponseTypeA | ResponseTypeB | ResponseTypeC;
type Pair<M extends Message, R extends Reponse> = {
message: M;
response: R;
};
type ValidPairsMap =
| Pair<MessageTypeA, ResponseTypeA>
| Pair<MessageTypeB, ResponseTypeB>
| Pair<MessageTypeC, ResponseTypeC>;
// Something like this, I know syntax is wrong here:
function sendMessageReturnResponse<Msg extends Message>(
message: Msg
): Extract<ValidPairsMap, Pair<Msg, any>>['response'] {
return null as any
}
function main() {
let mesA: MessageTypeA = { a: "msg" };
let resA: ResponseTypeA = sendMessageReturnResponse(mesA); // ok
}
In sendMessageReturnResponse
it is enough to infer only Message
, then you are allowed to filter union type with help of Extract
This line Extract<ValidPairsMap, Pair<Msg, any>>
means: get Pair where message is Msg
from union of ValidPairsMap