Search code examples
typescripttypeof

How to use typeof in function parameter and force correct variance


Lets have simple class hierarchy and the list of instances. I want to filter items from the list of instances by the class.

There are multiple issues:

  • can not use typeof T syntax. How to write it?
  • can not mark T as out variance - of course I do not want to instantiate anything in the method, but only return instances of T. How to tell it to TS?
class A {
}

class B extends A {
}

class C extends A {
}

function filterByClass<out T extends A>(cls: typeof T, list: A[]): T[]
{
  return list.filter((item) => item instanceof T);
}

Playground Link


Edit:

The answer (from Adrian Kokot) worked until I realized that unfortunately I made the example "too simple". In reality there are constructors with parameters, more like this:

class B extends A {
  constructor(pb: any) {
    super();
  }
}

class C extends A {
  constructor(pc: any) {
    super();
  }
}

Using modified playground from the anwer, the problem with variance emerges:

const a = new A();
const b = new B(1);
const c = new C(1);

const arrC = filterByClass(C, arr); 
Argument of type 'typeof C' is not assignable to parameter of type 'new () => C'.
  Types of construct signatures are incompatible.
    Type 'new (pc: any) => C' is not assignable to type 'new () => C'.

Solution

    1. You can specify the first argument as a constructor, so you will be able to pass the class name.

    2. You can use is operator as a return type in filter method, to tell typescript, that item is instanceof the passed class.

    Possible solution:

    function filterByClass<T extends A>(cls: new () => T, list: A[]): T[]
    {
      return list.filter((item): item is T => item instanceof cls);
    }
    
    filterByClass(C, [new A(), new B(), new C()]); // returns C[];
    

    playground


    Answer to edit:

    To use classes that have different constructors, you can just add ...args: any[] to the first argument:

    function filterByClass<T extends A>(cls: new (...args: any[]) => T, list: A[]): T[]
    {
      return list.filter((item): item is T => item instanceof cls);
    }
    

    playground