How to make a required type parameter Y in a function work together with another type parameter K where the latter is only used as a key in a record R = {a: 'aaa', b: 'bbb'} lookup and is not supposed to be explicitly provided? The type parameter K should be inferred from the value of the first parameter k of the funciton foo. I want then to use K to lookup a type in R so that I can use it for the other params of the function.
To allow for only the first type parameter Y to be required, the K param needs to have a default value. But this default value then breaks the inference of R[K] when instead of inferring based on the value of k i.e. R[typeof k] to "aaa" if k was "a", it uses the default of K i.e. R[keyof R] resolving to a union of all types in the record R i.e. "aaa" | "bbb" disregarding k.
Is it possible to have type inferrence for y: R[keyof K] where K is resolved from the value of k without explicitly passing K, but still allow for the required type param Y?
type R = {
a: 'aaa',
b: 'bbb',
}
type Foo = <
Y,
K extends keyof R = keyof R,
>(k: K, f: (x: R[K], y: Y) => any) => any
const foo: Foo = (x, y) => null
// With no type params Rec[K] is correctly inferred from K: 'a'
foo('a', (x, y) => null) // x: "aaa"; y: unknown
// When passing the Y type param, Rec[K] ignores the K: 'a'
foo<number>('a', (x, y) => null) // x: "aaa" | "bbb"; y: number
// When passing both Y and K it works
foo<number, 'a'>('a', (x, y) => null) // x: "aaa"; y: number
I could only make it work by currying the function so each type parameter belongs to a separate function:
type FooCurried = <K extends keyof R = keyof R>(k: K) =>
<Y>(f: (x: R[K], y: Y) => any) =>
any
const fooCurried: FooCurried = x => y => null
fooCurried('a')<number>((x, y) => null) // x: "aaa"; y: number
Found a fairly similar question where the accepted answer says that this can only be done by currying the function, as I did in the example.