I'm going to assume you want this sort of behavior:
type SomeCtor = new () => { a(x: string, y: number): boolean, b(z: number): string }
declare const q: Queue<SomeCtor>;
const a = q.addTaskToQueue(["a", ["", 1]]); // Promise<boolean>;
const b = q.addTaskToQueue(["b", [1]]); // Promise<string>;
I'm also only going to worry about the type signature of addTaskToQueue()
and not about your implementation and how the implementation interacts with the signature. From the type system's point of view, with your types as you've defined, I'd do something like this:
declare class Queue<T extends AnyClass> {
addTaskToQueue<S extends SingleTask<T>>(task: S): Promise<ReturnType<InstanceType<T>[S[0]]>>;
}
The addTaskToQueue()
method must be generic, or the best you will do is get another union as the return type, corresponding to the entire SingleTask<T>
union. You want the return type to depend on which member of the SingleTask<T>
union was passed in. Once we have a type parameter S
corresponding to some member of SingleTask<T>
, we can express the return type as
Promise<ReturnType<InstanceType<T>[S[0]]>>
Note that S[0]
will be the key of InstanceType<T>
passed in, so we are looking up that property. You can verify that gives you the behavior I think you want.
So that's the answer to the question as asked.
As an aside, it's not clear why your generic T
is a constructor type that you invariably process with InstanceType<T>
. The only thing it looks like you're doing with objects of type T
is calling new this.object()
, which specifically assumes that the constructor takes zero arguments. If so, I'd suggest changing T
to be the instance type itself, and refer to the constructor as type new()=>T
:
type SingleTask<T> = {
[P in keyof T]-?: T[P] extends (...a: any) => any
? [P, Parameters<T[P]>]
: never
}[keyof T]
type Task<T> = {
task: SingleTask<T>
resolve: (value?: unknown) => void
reject: (reason: any) => void
}
declare class Queue<T> {
instances: T[];
queue: Task<T>[];
object: new () => T;
addTaskToQueue<S extends SingleTask<T>>(task: S): Promise<ReturnType<T[S[0]]>>;
}
type SomeType = { a(x: string, y: number): boolean, b(z: number): string }
declare const q: Queue<SomeType>;
const a = q.addTaskToQueue(["a", ["", 1]]); // Promise<boolean>;
const b = q.addTaskToQueue(["b", [1]]); // Promise<string>;
That's significantly simpler.
Playground link to code