Solal Solal
Solal Solal

Reputation: 93

Typescript: Calling a static function that has `this` parameter

I'm currently hitting a wall in typescript.

Basically, I want to call a static function from a class extending a specific abstract class.

I get the following error

The 'this' context of type 'typeof A' is not assignable to method's 'this' of type 'AStatic<AStatic<unknown>>'. Cannot assign an abstract constructor type to a non-abstract constructor type.

Here is a link to a Typescript playground

Here is the code:

type AStatic<S extends A> = { new(): S };

abstract class A {
  static callStatic<S extends AStatic<S>>(this: AStatic<S>) {
    console.log('hey')
  }
}

class B extends A {
}


class D extends A {
}

class C {
  aType: typeof A;
  constructor(type: typeof A) {
    this.aType = type;
    this.aType.callStatic(); // error is here
  }
}

const c = new C(B);
const c_2 = new C(D);

The only way I managed to make it build in typescript is by passing any like the instead of typeof A. It's just a shame because I get no support from my IDE for the functions of A.

Note that I don't have control over class A and type AStatic, since those are from an external libray.

Upvotes: 2

Views: 1527

Answers (2)

Connor Low
Connor Low

Reputation: 7186

You are close! Take a look your pseudo-definition for A:

abstract class A {
  static callStatic<S extends AStatic<S>>(this: AStatic<S>) {
  //                                      ^^^^^^^^^^^^^^^^
  //           the `this` parameter must be type AStatic<A>

And looking at AStatic:

type AStatic<S extends A> = { new(): A };
//                            ^^^^^ - type must have a constructor, 
//                                      which rules out abstract classes.

This rules out typeof A as the this parameter, since it is abstract. We could try using AStatic directly:

class C {
  aType: AStatic<A>;
  constructor(type: AStatic<A>) {
    this.aType = type;
    this.aType.callStatic();
    //         ^^^^^^^^^^ - Property 'callStatic' does not exist on type 'AStatic<A>'
  }
}

But callStatic is not defined on AStatic. The solution is an intersection type:

class C {
  aType: AStatic<A> & typeof A
  constructor(type: AStatic<A> & typeof A) {
    this.aType = type;
    this.aType.callStatic() // works!
  }
}

As MunsMan pointed out though, unless you override callStatic on your derived types you don't need to pass typeof A at all:

const c = new C(B);
c.callStatic(); // hey
B.callStatic(); // hey
D.callStatic(); // hey

In other words, as long as you have a non-abstract version of A, you can call callStatic (or any static method/property) on that type and it will work the same way every time!

Upvotes: 2

MunsMan
MunsMan

Reputation: 178

You are trying to call an explicit instance of A, which is not necessary.

type AStatic<S extends A> = { new(): A };

abstract class A {
    static callStatic<S extends AStatic<S>>(this: AStatic<S>) {
        console.log('hey')
    }
}

class B extends A {

}

class C {
    aType: typeof A;
    constructor(type: typeof A) {
        this.aType = type;
        B.callStatic()
    }
}

const c = new C(B);

Because the method callStatic is static, you can just call the implementation of this method in B.

Upvotes: 2

Related Questions