mastilver
mastilver

Reputation: 677

get function return type from generic type

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

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

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

Related Questions