Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can Typescript infer the type of an instance of an extension class instantiated by a method of its base?

Tags:

Consider the following Typescript snippet:

class Animal {
  constructor(name: string) {
    this.name = name;
  }
  name: string;

  haveBaby(name: string): ?? return type ?? {
    return new this.constructor(name); // Error
  }
}

class Cat extends Animal {}
class Dog extends Animal {}
class Gerbil extends Animal {} // etc.

let sorachi = new Cat("Sorachi"); // a: Cat
let sorachiJr = a.haveBaby("Sorachi Jr."); // I want: sorachiJr: Cat

Animals can have babies, and a baby should be the same kind of animal as the parent, i.e., should be an instance of the same class as the parent. How do I assign types in this situation, so that Typescript knows that sorachiJr: Cat?

The code snippet above doesn't work. The line return new this.constructor(name) produces the error [ts] Cannot use 'new' with an expression whose type lacks a call or construct signature. in VS Code. The only solution I was able to find and understand was replacing this.constructor(name) with (<any>this.constructor)(name) or (<any>this).constructor(name), but then the type inferred for sorachiJr is any, too, rather than Cat. I tried casting to typeof this rather than any, but got the error [ts] Cannot find name 'this'.

How can I convince Typescript that having babies is a species-preserving operation?

like image 810
fmg Avatar asked Oct 16 '18 18:10

fmg


People also ask

Which of the following is correct for the infer types in TypeScript?

Types are inferred by TypeScript compiler when: Variables are initialized. Default values are set for parameters. Function return types are determined.

What is type inferring in TS?

The TypeScript compiler infers the type information when there is no explicit information available in the form of type annotations. In TypeScript, TypeScript compiler infers the type information when: Variables and members are initialized. Setting default values for parameters. Determined function return types.

What additional data types exist in TypeScript that are not included in JavaScript?

In addition to hexadecimal and decimal literals, TypeScript also supports binary and octal literals introduced in ECMAScript 2015.

Do you need to define types in TypeScript?

When you don't specify a type, and TypeScript can't infer it from context, the compiler will typically default to any . You usually want to avoid this, though, because any isn't type-checked. Use the compiler flag noImplicitAny to flag any implicit any as an error.


1 Answers

The preserving the type of the class the method was called on is easy, we just use the polymorphic this type. To convince the ts that constructor will be a constructor that takes a string and returns an instance the same type as the current class requires a type assertion

type AnimalConstructor<T extends Animal> = new (name: string) => T
class Animal {
    constructor(name: string) {
        this.name = name;
    }
    name: string;

    haveBaby(name: string): this  {
        return new (this.constructor as AnimalConstructor<this>)(name); 
    }
}

class Cat extends Animal {}
class Dog extends Animal {}
class Gerbil extends Animal {} // etc.

let sorachi = new Cat("Sorachi"); // a: Cat
let sorachiJr = sorachi.haveBaby("Sorachi Jr."); 

Note Typescript can't validate the fact that the derived class constructor only expects a single string parameter, it might require more or less parameters. This makes this constructor not type safe.

like image 119
Titian Cernicova-Dragomir Avatar answered Oct 13 '22 01:10

Titian Cernicova-Dragomir