Let's assume we have the following object:
interface X {
a: () => number;
b: number;
}
And the following type:
type MethodsOf<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
}[keyof T];
The MethodsOf
type will narrow only the keys of an object whose values are functions. For example:
type A = MethodsOf<X>; // "a"
const a: A = 'a';
const b: A = 'b'; // Error: Type '"b"' is not assignable to type '"a"'.
Now I am trying to create a generic type that will receive an X
interface but will return a type whose keys are narrowed to MethodsOf<X>
and its values are the types of the return values of the MethodsOf<X>
. Example:
type ValuesMapping<T> = ???
// No error:
const mapping: ValuesMapping<X> = {
a: 3
}
// Error:
const mapping: ValuesMapping<X> = {
a: 'blarg'
}
I was trying to implement type ValuesMapping<T>
as follows:
type ValuesMapping<T> = {
[K in MethodsOf<T>]: ReturnType<T[K}>;
};
But I am getting the following error:
TS2344: Type 'T[K]' does not satisfy the constraint '(...args: any) => any'. Type 'T[MethodsOf<T>]' is not assignable to type '(...args: any) => any'. Type 'T[T[keyof T] extends (...args: any[]) => any ? keyof T : never]' is not assignable to type '(...args: any) => any'. Type 'T[keyof T]' is not assignable to type '(...args: any) => any'. Type 'T[string] | T[number] | T[symbol]' is not assignable to type '(...args: any) => any'. Type 'T[string]' is not assignable to type '(...args: any) => any'.
For some context of the issue, I am trying to create a generic mocking type:
type MethodsOf<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
}[keyof T];
type MockMapping<T> = {
[K in MethodsOf<T>]: Mock<ReturnType<T[K]>>;
};
// usage
class X {
a() {
return 1;
}
b = 2;
}
const x: MockMapping<X> = {
a: jest.fn(() => 42),
};
Any help would be appreciated!
CodePudding user response:
TypeScript is unable to understand that the keys K
of T
calculated by MethodsOf<T>
only point to valid function properties. The only thing TypeScript is sure is that K
is actually a key of T
. That's why indexing is still possible.
So we need to help TypeScript understand what the T[K]
should be by using another conditional.
type ValuesMapping<T> = {
[K in MethodsOf<T>]:
ReturnType<T[K] extends (...args: any[]) => any ? T[K] : never>;
};
This compiles without an error.