typescript infer type from constructor argument called by sub class

Viewed 366

I have a class with some property/methods that return value which was orignially passed via the constructor:

class Base {
  constructor(arg) {
     this.options = arg.options;
  }
}

People suggest this class with generics as the typescript equivalent:

class Base<T extends {options: any}> {
    options: T["options"];

    constructor(arg: T) {
        this.options = arg.options;
    }    
}

// works fine with instantiation
const inst = new Base({options: {foo: 123}});
inst.options.foo++; // ok
inst.options.bar; // TS Error

Now i have a sub class which calls the super class's constructor and passes these options:

class Sub extends Base { // TS Error
    constructor() {
        super({options: {foo: 123}});
        this.options.foo++; // TS Error
    }
}

Typescript compiler isn't happy with this and wants the Base class being extended passing the type. But then i have the information twice, in the super call and the extends notation. What is the correct ts syntax to solve my issue?

(code in typescript playground)

1 Answers

Edit:

The problem doesn't lie with super() but with extends Base. One way or another, typescript has to infer the generic type for Base and you can't expect typescript to infer that from the call to super().


I would have at first suggested doing this

    class Sub<T extends {options: any}> extends Base<T> {
    constructor() {
        super({options: {foo: 123}})
    }}

but this won't work since a concrete instance is not allowed to be assigned to a type parameter. It would have worked if we would instead have done this

    class Sub<T extends {options: any}> extends Base<T> {
    constructor(arg: T) {
        super(arg)
    }}

const inst2 = new Sub({options: {foo: 123}})
inst2.options.foo++; // ok   

I think the correct solution would be to simply do this

    class Sub extends Base<{options: any}> {
    constructor() {
        super({options: {foo: 123}})
    }}

However, there's still a problem.

const inst2 = new Sub();
inst2.options.foo++; //options is any which makes sense, we said that options is any above

Do you know why we use generics? When we don't know in advance what the type is. Since in this instance we know exactly what the type is, then we can safely do this:

class Sub extends Base<{ options: {foo: number} }> {
    // or even class Sub extends Base<{ options: {foo: 123} }> {
    constructor() {
        super({options: {foo: 123}});
        this.options.foo++;
    }
}

const inst2 = new Sub();
inst2.options.foo++;

Is it okay to do this? Of course, if we passed something other than options to Base, it would have triggered a warning. So we meet the requirements of Base and at the same time make sure that what we pass to super() are in accordance with what we said the generic type is.

Related