Issue
I have a followupquestion to Create type for dependent function parameters
The function should take up to 3 parameters which are typed in an object (see TMessageTypeMap of the example below)
I want both
- typesafety and autocomplete when calling the function (works with
IMessageFunction
) - typesafety and autocomplete inside the function (works with (
(...args:TMessageTupleType) => void
)
I made two more comprehensive variations of the example in the question above. Do you see a way to combine the advantages of both approaches?
export type TMessageTypeMap = {
event: TEventTypeMap;
config: TConfigTypeMap;
};
export type TConfigTypeMap = {
login: {
value1: string;
};
logout: {
value2: number;
};
};
export type TEventTypeMap = {
eventNo1: {
e1Param1: number;
e1Param2: string;
};
eventNo2: {
e2Param1: string;
e1Param2: number;
};
};
export interface IMessageFunction {
<K extends keyof TMessageTypeMap, T extends keyof TMessageTypeMap[K]>(
messageCategory: K,
eventType: T,
parameters: TMessageTypeMap[K][T],
callback?: (evt?: any) => void
): void;
}
export type KeyKeyValue<O> = {
[K in keyof O]: {
[T in keyof O[K]]: [K, T, O[K][T], (evt?: any)=>void];
}[keyof O[K]];
}[keyof O];
export type TMessageTupleType = KeyKeyValue<TMessageTypeMap>;
// Method 1: FunctionType Interface
// calling the function only showing possible combinations
// in the function types cant be derived correctly
const functionMessageFunction : IMessageFunction = (category, type, params, callback) => {
if(category == "event") {
// cannot inherit type
if(type == "pi") {
}
}
}
// Method 2: Tuple Type
// strictly typed in the function but
// calling it does not provide code completion
const functionMessageTupleType : (...args:TMessageTupleType) => void = (category, type, params, callback) => {
if(category === "event") {
if(type ==="eventNo1") {
// works - type is derived correctly
params.e1Param1;
}
}
}
// only correct parameters can be provided
const result = functionMessageFunction("event", "eventNo1" , {
e1Param1: 1, e1Param2: "jkasdf"
})
// all "type"-combinations and params-combinations are suggested by typescript (eventNo1, eventNo2, login, logout)
const result2 = functionMessageTupleType('event', "login", {
value2: 1
})
// even correct variations arent accepted by the compiler
const result2Variant = functionMessageTupleType('event', "eventNo1", {
e1Param1: 123
})
Solution
It is not currently possible to satisfy both of those points simultaneously. To get type safety inside the function implementation you will need to use arguments of a discriminated union type. But that makes your function look like multiple overloads, and autocomplete does not work well there, as described in microsoft/TypeScript#26892. To get autocomplete you need the call signature to be generic, but generic function implementations cannot do the sort of discriminated union narrowing you are trying to achiever.
So you're stuck. The closest you can get to having both is to use the type-safe implementation and then just assert that it behaves like the appropriate generic function. Assertions are not safe, so you need to be careful that it is accurate. Like this:
const functionMessage = ((...[category, type, params, callback]: TMessageTupleType) => {
if(category === "event") {
if(type ==="eventNo1") {
params.e1Param1;
}
}
}) as IMessageFunction // <-- assert
The implementation of functionMessage()
understands that category
and type
can be used to narrow params
. And callers only see relevant suggestions:
const result = functionMessage("event", "")
// ^
// eventNo1
// eventNo2
And yet you could completely lie to the compiler this way, say by implementing the function incorrectly:
type TWrongMessageTupleType =
["event", "eventNo2", { e1Param1: number; e1Param2: string; }] |
["event", "eventNo1", { e2Param1: string; e1Param2: number; }];
const whoops = ((...[category, type, params]: TWrongMessageTupleType) => {
if (category === "event") {
if (type === "eventNo1") {
params.e2Param1;
}
}
}) as IMessageFunction // <-- still works
The assertion is accepted; the compiler has no idea whether or not functionMessage
's or whoops
's implementation is a correct IMessageFunction
. It believes you when you say that it is. So again, care is needed. Because there's no way to get complete compiler-verified type safety here.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.