Home > Net >  How to setup correct type in typescript abstract class?
How to setup correct type in typescript abstract class?

Time:11-23

I have problem with setting correct type in abstract class. I would like to do something like this:

abstract class AbstractMap<TTile extends AbstractTile> {
  public readonly fields: TTile[];

  constructor() {
    this.fields = [this.createTile(0,0), this.createTile(0,1)];
  }

  protected abstract createTile(x: number, y: number): TTile;
}

abstract class AbstractTile {
  protected abstract readonly map: AbstractMap<this>;

  protected constructor(public readonly x: number, public readonly y: number) {
  }

  public get neighbors(): this[] {
    return this.map.fields.filter(f => f);
  }
}

class ServerMap extends AbstractMap<ServerTile> {
  protected createTile(x: number, y: number): ServerTile {
    return new ServerTile(x, y, this);
  }
}

class ServerTile extends AbstractTile {
  protected readonly map: ServerMap;

  constructor(x: number, y: number, map: ServerMap) {
    super(x, y);
    this.map = map;
  }
}

I'm trying to get ServerMap type on ServerTile.map property, but all the time I have an error:

   Type 'ServerMap' is not assignable to type 'AbstractMap<this>'.
     Types of property 'fields' are incompatible.
       Type 'ServerTile[]' is not assignable to type 'this[]'.
         Type 'ServerTile' is not assignable to type 'this'.
           'ServerTile' is assignable to the constraint of type 'this', but 'this' could be instantiated with a different subtype of constraint 'ServerTile'.

I tried to set AbstractMap<AbstractTile> instead of AbstractMap<this> in AbstractTile class but then I need to set AbstractTile[] return type on AbstractTile.neighbors function.

CodePudding user response:

The polymorphic this type is an implicitly F-bounded type. It acts like a generic type parameter which is constrained to the current type. And like generics, it is easier to specify the type argument than it is to write code that treats it as an unresolved parameter. Inside the class bodies of AbstractTile and its descendant classes, we only know that this will be some subtype of the current class. But we don't know which one. And that means a lot of code you write could turn out to be technically unsafe in the presence of unanticipated subclasses. For example, with your code, someone could write:

class Oops extends ServerTile { prop = "oops" }

const oopses = new Oops(1, 2, new ServerMap()).neighbors;
// const oopses: Oops[]

The compiler believes that oopses is of type Oops[] because that's the supposed type of neighbors. But your implementation of ServerMap doesn't work that way, and so you get a runtime error if you continue:

oopses.forEach(o => o.prop.toUpperCase()) // compiles okay, but
//            
  • Related