Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return subtype from superclass method instead of superclass type

I think this is an issue of implementing generics correctly, but I am not sure.

I created a Github gist representing the problem here: https://gist.github.com/ORESoftware/66b72b4b85262d957cb03ad097e4743e

Say I have this superclass:

  class A {

    foo(): A {
      return this;
    }

  }

and several subclasses, one for example looks like so:

   class B extends A {

     bar(): B {
      return this;
     }

   }

so if I do

new B().foo().bar()

this will work at runtime, but it doesn't compile with TypeScript. That's because foo() declares the return type to be A, not type B.

How can I return the type that this is, instead of declaring foo() to always return type A?

I tried this:

enter image description here

but I get this error:

enter image description here

like image 280
Alexander Mills Avatar asked Oct 31 '25 05:10

Alexander Mills


2 Answers

You have to return the type of this using a polymorphic this type.

abstract class A {
    foo(): this {
        return this;
    }
}

class B extends A {
    bar(): this {
        return this;
    }
}

which will allow for

const b = new B();

b.foo().bar();
like image 111
Shane Avatar answered Nov 03 '25 03:11

Shane


I have two examples for you, one with overloads and one with a generic interface.

Overloads

If you meant for the new C().foo().zoom() version to work, you can achieve that, while still getting a warning about the bar() mistake with the following code, which creates a compatible overload that returns a subtype of the type in the parent class:

class A {
  foo(): A {
    return this;
  }
}

class B extends A {
  foo(): B {
    return this;
  }

  bar(): B {
    return this;
  }
}

class C extends A {
  foo(): C {
    return this;
  }

  zoom(): C {
    return this;
  }
}

const result = new C().foo().zoom();

If the real method in your code actually does something you want to re-use, you can call super.foo()... but in the example code that isn't needed.

  foo(): C {
    const a = super.foo();
    // You still need to return this, as it is a C, not an A.
    return this;
  }

Generics

You can't make the base class generic, in order to return a type T. You can't use a class as a type constraint on its own type argument. You also have a problem with A not being guaranteed compatible with a T that extends A.

What you could do is introduce an interface, and use it on each class:

interface Fooable<T> {
  foo(): T;
}

class A {
  foo(): any {
    return this;
  }
}

class B extends A implements Fooable<C> {
  bar(): B {
    return this;
  }
}

class C extends A implements Fooable<C> {
  zoom(): C {
    return this;
  }
}

const result = new C().foo().zoom();
like image 28
Fenton Avatar answered Nov 03 '25 01:11

Fenton