Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to have TS infer a callback in a generic builder function?

Consider the following use case demo (playground):

// A builder that can self-reference its keys using a ref function
declare function makeObj<K extends string>(
    builder: (ref: (k: K) => number) => Record<K, number>
): Record<K, number>;

// Not using `ref` for now. All good, K is inferred as <"x" | "y">.
const obj1 = makeObj(() => ({ x: 1, y: 2 }));

// Oops, now that we try to use `ref`, K is inferred as <string>.
const obj2 = makeObj(ref => ({ x: 1, y: ref("invalid key, only x or y") }));

// This works, but we'd want K to be automatically inferred.
const obj3 = makeObj<"x" | "y">(ref => ({ x: 1, y: ref("x") }));

So, how should I write makeObj so K is automatically inferred?

like image 591
tokland Avatar asked Aug 02 '21 17:08

tokland


Video Answer


1 Answers

Try declare Record<K, number> as second generic type.

playground

declare function makeObj<K extends string, T extends Record<K, number> = Record<K, number>>(
  builder: (ref: (k: K) => number) => T
): T

const obj1 = makeObj(() => ({ x: 1, y: 2 }));
const obj2 = makeObj(ref => ({ x: 1, y: ref("invalid key, only x or y") }));
const obj3 = makeObj(ref => ({ x: 1, y: ref("x") }));

Limitation

Well, as @kaya3 commented below. This resolution can only infer the return type. It still cannot find the invalid key unless explicitly set generic type.

// error will shown when given explicit generic type
const obj2 = makeObj<'x' | 'y'>(ref => ({x: 1, y: ref("invalid key, only x or y")}));
like image 122
Dean Xu Avatar answered Oct 09 '22 08:10

Dean Xu