2

I would like to dynamically generate some tests, for that I have to call a method with the method name to be called and then all the test setup is done and the method is called.

So basically I call createTest('methodName') instead of it('methodName', () => ...lotsOfBoringStuff...).

For that I would like to type the method appropriately, so I have autocomplete and are sure, that I only call it for the correct methods.

I managed to whip something together that "should work", but TS does complain about incompatible types:

type MethodOf<T> = {
  [P in keyof T]: T[P] extends () => unknown ? P : never;
}[keyof T];

function doStuff<T, N extends MethodOf<T>>(t: T, method: N): unknown {
  const meth: () => unknown = t[method]; // <-- boom: {} cannot be assigned to () => unknown
  return meth();
}

const t = {
  abc: 'str',
  foobar: () => 1,
};

doStuff(t, 'foobar');  // <-- works as expected + autocompletion

type T1 = typeof t;
type N1 = MethodOf<T1>; // 'foobar'
type M1 = T1[N1]; // () => number // <-- works as expected

Why doesn't TS detect that T[MethodOf<T>] is actually a callable method? Is there an alternative to casting it to any before assigning it?

I'm using typescript 4.6.

ST-DDT
  • 2,615
  • 3
  • 30
  • 51

1 Answers1

1

If we define a type OnlyMethods like this:

type OnlyMethods<T> = Pick<T, MethodOf<T>>;

Then change the signature of doStuff to this:

function doStuff<T extends OnlyMethods<T>, N extends MethodOf<T>>(t: T, method: N): unknown {

The body of the function now works:

function doStuff<T extends OnlyMethods<T>, N extends MethodOf<T>>(t: T, method: N): unknown {
  const meth = t[method]; // not even a type annotation is needed!
  return meth();          // it just WORKS
}

I believe this counter-intuitive solution works because T is now only methods, and N is only keys that correspond to methods, which means T[N] is always a method.

Playground

kelsny
  • 23,009
  • 3
  • 19
  • 48