I was trying to make the types dependent on the type of the passed argument. A contrived example is:
type NS = "num" | "str"
type Data<T extends NS> = T extends "num" ? number : string
type Func<T extends NS> = (x: Data<T>) => Data<T>
type Funcs = {[T in NS]: Func<T>}
type Obj = {[T in NS]: Data<T>}
const funcs: Funcs = {
num: (x) => x * 2,
str: (x) => x + x
}
function useFunc<T extends NS>(ns: T, obj: Obj): Data<T> {
const f = funcs[ns]
const x = obj[ns]
return f(x)
}
The problem is that f(x) has type number | string rather than Data<T>, that seems caused by f having type Funcs[T] rather than Func<T>. x also has type Obj[T] rather than Data<T>.
So typescript cannot infer that Funcs[T] and Func<T> are the same, and Obj[T] and Data<T> are the same, even though that is how Funcs and Obj are defined...
I can make it work by typecasting everything, but it defeats the purpose of using types:
function useFunc<T extends NS>(ns: T, obj: Obj): Data<T> {
const f = funcs[ns] as Func<T>
const x = obj[ns] as unknown as Data<T>
// note the double-cast as Obj[T] and Data<T> "do not sufficiently overlap"
return f(x)
}
Is there a way to make it work in typescript?
EDIT:
I know that it can be made work in this way, that works for the call sites, but pointless from code re-use point of view - combining the same logic on different types was the actual motivation to do it, and in this way it is simply repeated.
function useFunc<T extends NS>(ns: T, obj: Obj): Data<T>
function useFunc(ns: NS, obj: Obj): Data<NS> {
if (ns === "num") {
const f = funcs[ns]
const x = obj[ns]
return f(x)
}
const f = funcs[ns]
const x = obj[ns]
return f(x)
}
Is there a way to avoid this repetition without losing type safety?