I'm looking for a way to make my code more type-safe and would like to define a close relationship between some of my types when passing them to generic functions.
For example, for the given set of types:
interface FooParam {}
interface FooReturn {}
interface BarParam {}
interface BarReturn {}
and the following function:
function action<T, R>(foo: T): R
I'd like to closely bind FooParam with FooReturn, and BarParam with BarReturn, so the compiler allows the call only if a pair of associated types is passed as T and R and returns an error otherwise.
action<FooParam, FooReturn>(...) // bound types, OK
action<BarParam, BarReturn>(...) // bound types, OK
action<FooParam, BarReturn>(...) // types are not bound, ERROR
action<FooParam, string>(...) // types are not bound, ERROR
I've actually managed to achieve the above by defining two base interfaces, which will be later used as constraints on the generic types:
interface Param {}
interface Return<T extends Param>{
_typeGuard?: keyof T
}
interface FooParam extends Param {}
interface FooReturn extends Return<FooParam> {}
interface BarParam extends Param {}
interface BarReturn extends Return<BarParam> {}
function action<T extends Param, R extends Return<T>>(foo: T): R
However this seems more like a workaround than a clean solution, especially given the _typeGuard?: keyof T field, which exists only so T actually matters and is checked, not ignored. Another downside is that every type must extend one of my custom interfaces, which sometimes is just not possible.
Is there a better and more universal way to get the described functionality?