Reputation: 677
I have a function that takes a class as an input, the return type of that function is determined by the class. Here what I got so far, I would expect res
to be of type number
. Is it even possible
abstract class Command<R> {}
abstract class CommandHandler<R, C extends Command<R>> {
abstract execute(cmd: C): R
}
class GetNumberCommand extends Command<number> {}
class GetNumberCommandHandler extends CommandHandler<number, GetNumberCommand> {
execute(cmd: GetNumberCommand) {
return 1
}
}
function mediator<C extends Command<R>, R>(cmd: C): R {
// [ts] Type 'GetNumberCommandHandler' is not assignable to type 'CommandHandler<R, C>'.
const handler: CommandHandler<R, C> = new GetNumberCommandHandler()
return handler.execute(cmd)
}
// res should be of type number, it's `{}`
const res = mediator(new GetNumberCommand())
Upvotes: 1
Views: 115
Reputation: 249516
This line const handler: CommandHandler<R, C> = new GetNumberCommandHandler()
is not really type safe, since the compiler can't guarantee R
and C
will be compatible with number
and GetNumberCommand
of GetNumberCommandHandler
, after all R
and C
could be any types.
The fact that R
is not inferred correctly we could fix in one of two ways
Using a conditional type to extract R
and only have oner type parameter to mediator
abstract class Command<R> {
_type: R // we need to use the type parameter otherwise typescript will just ignore it
}
abstract class CommandHandler<R, C extends Command<R>> {
abstract execute(cmd: C): R
}
class GetNumberCommand extends Command<number> {}
class GetNumberCommandHandler extends CommandHandler<number, GetNumberCommand> {
execute(cmd: GetNumberCommand) {
return 1
}
}
function mediator<C extends Command<any>>(cmd: C): (C extends Command<infer R> ? R : never) {
const handler: CommandHandler<any, C> = new GetNumberCommandHandler() // we use any so no type assertion is needed
return handler.execute(cmd)
}
const res = mediator(new GetNumberCommand()) // number
The second option is to not have the C
parameter, since we don't use the actual type of the command ever again cmd
could be typed as Command<R>
and R
will be inferred correctly :
abstract class Command<R> {
_type: R // we need to use the type parameter otherwise typescript will just ignore it
}
abstract class CommandHandler<R, C extends Command<R>> {
abstract execute(cmd: C): R
}
class GetNumberCommand extends Command<number> {}
class GetNumberCommandHandler extends CommandHandler<number, GetNumberCommand> {
execute(cmd: GetNumberCommand) {
return 1
}
}
function mediator<R>(cmd: Command<R>): R {
const handler: CommandHandler<R, Command<R>> = new GetNumberCommandHandler() as any // we need a type assertion to make this work (and a custom way to ensure this is actually valid)
return handler.execute(cmd)
}
const res = mediator(new GetNumberCommand()) // number
Upvotes: 2