Your function's type is
declare function ololo<T extends object>(
key: NestedKeyOf<T>,
callback: <K extends NestedKeyOf<T>>(
args: { value: GetValueByKey<T, K>; }
) => boolean
): void
but you don't want callback
to be a generic function, since that would mean it would have to accept an argument of type
{value: GetValueByKey<T, K>}
for all possible
K
chosen by
callback
's caller.
Callers choose generic function type arguments, not
implementers. So
K
is in the wrong scope.
The right scope would be to have it on the ololo
function itself, like this:
declare function ololo<T extends object, K extends NestedKeyOf<T>>(
key: K,
callback: (args: { value: GetValueByKey<T, K>; }) => boolean
): void
But unfortunately this won't work directly, as written. You want ololo
's caller to specify T
manually, but have K
inferred by the type of key
. That would require partial type argument inference as requested in microsoft/TypeScript#26242, and so far it's not part of the language. The only options you have when calling ololo()
is to manually specify both T
and K
, or allow TypeScript to infer both T
and K
. The former is redundant (you'd have to write "a.b.c"
twice) and the latter is impossible because there's no value of type T
involved (although this is weird, surely you'd need a value of type T
somewhere, right? More on this later).
The most common workaround here is currying, where you make ololo
just generic in T
directly, and allow callers to manually specify T
. Then it returns a function which is generic in K
, and now TypeScript will infer K
from the value of key
. So ololo
's call signature looks like:
declare function ololo<T extends object>():
<K extends NestedKeyOf<T>>(
key: K,
callback: (args: { value: GetValueByKey<T, K>; }) => boolean
) => void
And we can test it out:
ololo<typeof Myobj>()("a.b.c", ({ value }) => {
// ^? (parameter) value: number
console.log(value.toFixed());
return true;
});
Looks good. You can see the extra function call with ololo<typeof MyObj>()
, which produces a new function where T
is typeof MyObj
and K
is inferred to be "a.b.c"
, which means that value
is now inferred to be of type number
.
That's the answer to the question as asked.
But it really seems that the only way this would be useful though is if the implementation of ololo
actually had a value of type T
somewhere inside it, or something that could produce a value of type T
. There should be some argument to ololo
whose type directly depends on T
. Otherwise what would it actually call callback
on? So I'd expect something like
function ololo<T extends object>(obj: T) {
return <K extends NestedKeyOf<T>>(
key: K,
callback: (args: { value: GetValueByKey<T, K> }) => boolean
) => {
callback({ value: key.split(".").reduce<any>((acc, k) => acc[k], obj) })
}
}
Then you would naturally call ololo
with an actual argument from which TypeScript could infer T
:
const myObjTaker = ololo(Myobj);
And the returned function would be able to infer K
:
myObjTaker("a.b.c", ({ value }) => {
console.log(value.toFixed(2));
return true;
});
// "100.00"
You could also do that without currying, if you really wanted:
function ololo<T extends object, K extends NestedKeyOf<T>>(
obj: T, key: K,
callback: (args: { value: GetValueByKey<T, K> }) => boolean
) {
callback({ value: key.split(".").reduce<any>((acc, k) => acc[k], obj) })
}
ololo(Myobj, "a.b.c", ({ value }) => {
console.log(value.toFixed(2));
return true;
});
// "100.00"
But the curried version allows you to reuse the result for a single object.
Playground link to code