Reputation: 1274
When using union of string literals as input argument, what would it take to remove the casts and put the type into the function header:
const get = <T extends "barcode" | "mqtt">(s: T) =>
s === "barcode" ?
<T extends "barcode" ? {scan: () => string} : {pan: () => string}>{scan: () => "we are scanning"} :
<T extends "barcode" ? {scan: () => string} : {pan: () => string}>{pan: () => "we are panning"}
get("barcode").scan() // OK
get("mqtt").pan() // OK
get("barcode").pan() // Error
I ran into this trying to answer someone else's question: https://stackoverflow.com/a/55059318/2684980.
Upvotes: 9
Views: 7827
Reputation: 249646
The cleanest solution is such cases (although not any more type safe than the type assertions) is to use overloads instead. You can use conditional types in the public signature, and a simple union in the implementation signature. You will need to switch to a function declaration as function expressions (arrow or regular) don't easily suport overloads:
function get<T extends "barcode" | "mqtt">(s: T): T extends "barcode" ? { scan: () => string } : { pan: () => string }
function get(s: "barcode" | "mqtt"): { scan: () => string } | { pan: () => string } {
return s === "barcode" ?
{ scan: () => "we are scanning" } :
{ pan: () => "we are panning" }
}
get("barcode").scan() // OK
get("mqtt").pan() // OK
get("barcode").pan() // Error
Upvotes: 16