Vaune_
Vaune_

Reputation: 51

Return type hinting from function parameter

How can I provide return type hinting from a function parameter?

class BaseClass {
   constructor(private logger:Logger){}
}

class ChildClass extends BaseClass {
   public helloWorld() {
      console.log('Hello World')
   }
}

function buildInstance<T extends BaseClass>(target: typeof BaseClass, logger: Logger): T {
  logger.defaults.meta = { path: __dirname }
  return new target(logger) as T
}

const instance = buildInstance<ChildClass>(ChildClass)
instance.helloWorld() // instance is understood to be ChildClass instance

The above works, in so far as, instance is type hinted for ChildClass correctly, but I feel like that is messy and I might be missing something. Is there someway to simply my function / call signatures to infer return type from the argument. Or conversely, is there a way to infer new target from the type argument?

Upvotes: 0

Views: 709

Answers (1)

chvolkmann
chvolkmann

Reputation: 524

If you declare your function as buildInstance<T>(type: T): T, you're telling TypeScript that type is an instance of T (the same type that is returned, the instance type). But you want to pass a class into it, hence you need to express that. buildInstance<T>(type: Class<T>): T satisfies this.

Demo: https://repl.it/@chvolkmann/DimgrayGiddyVertex

// something that can be constructed with "new", returning T
type Class<T> = new (...args: any[]) => T

class Vehicle {}
class Car extends Vehicle {}
class Truck extends Vehicle {}


function buildVehicle<T extends Vehicle>(Type: Class<T>) : T {
  return new Type()
}

const car = buildVehicle(Car) // type: Car
const truck = buildVehicle(Truck) // type: Truck

Here is the same example, but slightly more involved. Here, also the arguments you can pass to the respective class are type checked. Might require TypeScript 4+ for all that generic jazz.

// something that can be constructed with "new", accepting argument of type A, returning T
type Class<T, A extends any[] = any[]> = new (...args: A) => T

class Vehicle {
  constructor(x: number) {}
}
class Car extends Vehicle {}
class Truck extends Vehicle {}


function buildVehicle<T extends Vehicle, A extends any[]>(Type: Class<T, A>, ...args: A) : T {
  // args is type checked!
  // A is a tuple of whatever Vehicle expects as constructor argument types
  return new Type(...args)
}

// compiles fine
let car = buildVehicle(Truck, 42) 

// TS2554: Expected 2 arguments, but got 1.
car = buildVehicle(Car) 

//  TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
car = buildVehicle(Truck, 'foobar')

Upvotes: 2

Related Questions