TypeScript Version: 3.1.0-dev.20180901
Search Terms: ReturnType, generic, invoke, invoker, property
Code
function invoker <K extends string | number | symbol, A extends any[]> (key: K, ...args: A) {
return <T extends Record<K, (...args: A) => any>> (obj: T): ReturnType<T[K]> => obj[key](...args)
}
const result = invoker('test', true)({ test: (a: boolean) => 123 })
Workaround (duplicate similar ReturnType-like type locally):
export type InvokeResult <T extends (...args: A) => any, A extends any[]> = T extends (...args: A) => infer R ? R : never
export function invoker <K extends string | number | symbol, A extends any[]> (key: K, ...args: A) {
return <T extends Record<K, (...args: A) => any>> (obj: T): InvokeResult<T[K], A> => obj[key](...args)
}
Expected behavior: Valid program and use of ReturnType.
Actual behavior:
Type '(...args: A) => any' does not satisfy the constraint '(...args: any[]) => any'.
Types of parameters 'args' and 'args' are incompatible.
Type 'any[]' is not assignable to type 'A'.
Playground Link: https://www.typescriptlang.org/play/index.html#src=function%20invoker%20%3CK%20extends%20string%20%7C%20number%20%7C%20symbol%2C%20A%20extends%20any%5B%5D%3E%20(key%3A%20K%2C%20...args%3A%20A)%20%7B%0D%0A%20%20return%20%3CT%20extends%20Record%3CK%2C%20(...args%3A%20A)%20%3D%3E%20any%3E%3E%20(obj%3A%20T)%3A%20ReturnType%3CT%5BK%5D%3E%20%3D%3E%20obj%5Bkey%5D(...args)%0D%0A%7D
Related Issues: No, this is out of my skillset to debug tonight. Logging for posterity and help 😄 I did try different workarounds and found I couldn't workaround it with any usage of property access so I added that to the title. For instance, the exact same code inlined results in an empty interface:
export function invoker <K extends string | number | symbol, A extends any[]> (key: K, ...args: A) {
return <T extends Record<K, (...args: A) => any>> (obj: T): T[K] extends (...args: A) => infer R ? R : never => obj[key](...args)
}
const result = invoker('test', true)({ test: (abc: boolean) => 123 })
More minimal repro which makes me realise it may not be an issue but maybe an enhance to ReturnType to specify the generic type parameters:
type Test <T extends (...args: A) => any, A extends any[]> = ReturnType<T>
Yeah, that error is unfortunate. Here's a minimal repro:
type Foo<A extends any[]> = ReturnType<(...args: A) => string>;
In --strictFunctionTypes mode this produces an error similar to the one you're seeing, the issue being that (...args: A) => string does not satisfy the constraint (...args: any[]) => any because any[] is not assignable to A. It seems that we need to be a little more permissive in the way we check assignability of rest parameters with type any[].
Less safety version of ReturnType but working for all inputs:
type UnsafeReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
Update1
But inference not working properly for all cases.
Cases above working correct.
But not working for union type rest parameters case (with enabled strictFunctionTypes):
const foo = (...args: [string, number] | [1]) => ({
type: 'type',
args,
});
type t = UnsafeReturnType <typeof foo>; // any
Most helpful comment
Less safety version of
ReturnTypebut working for all inputs:Update1
But inference not working properly for all cases.
Cases above working correct.
But not working for union type rest parameters case (with enabled
strictFunctionTypes):