Typescript: allow access to abstract getters in the constructor

Created on 15 Feb 2018  路  2Comments  路  Source: microsoft/TypeScript

TypeScript Version:
2.7.1

Actual behavior:
After upgrading from [email protected], the following code does not compile anymore:

abstract class MyClass {
    abstract get b(): number; 

    constructor() {
        console.log(this.b);
    }
}

class MyDerivedClass extends MyClass {
    get b() {
        return 42;
    }
    constructor() {
        super();
    }
}

TS2715: Abstract property 'b' in class 'MyClass' cannot be accessed in the constructor.

This behaviour is related to #9230 and is totally legit as long as we are talking about 'normal' properties like

abstract class MyClass {
    abstract i: number; 

    constructor() {
        console.log(this.i);
    }
}

I understand that my i-property would not have been initialized when the constructor executes, as @RyanCavanaugh pointed out here.

Expected behaviour:
When it comes to getters, things behave a little different. Consider the following piece of code:

abstract class MyClass {
    a: number = -1;

    get b(): number {
        return -2;
    }
    abstract c(): number; 

    constructor() {
        console.log('abstract class constructor: a: ' + this.a);
        console.log('abstract class constructor: b: ' + this.b);
        console.log('abstract class constructor: c: ' + this.c());
    }
}

class MyDerivedClass extends MyClass {
    a: number = 41;
    get b() {
        return 42;
    }
    c() {
        return 43;
    }

    constructor() {
        super();
        console.log('derived class constructor: a: ' + this.a);
        console.log('derived class constructor: b: ' + this.b);
        console.log('derived class constructor: c: ' + this.c());
    }
}

const myClass = new MyDerivedClass();
console.log('anywhere else: a: ' + myClass.a);
console.log('anywhere else: b: ' + myClass.b);
console.log('anywhere else: c: ' + myClass.c());

it produces the following output:

abstract class constructor: a: -1
abstract class constructor: b: 42

You might expect b: -2 here, but we already receive the value provided in the derived class.

abstract class constructor: c: 43

derived class constructor: a: 41
derived class constructor: b: 42
derived class constructor: c: 43

anywhere else: a: 41
anywhere else: b: 42
anywhere else: c: 43

As you can see, accessing b in the constructor results in a different behavior than accessing a. While accessing a returns the value specified in MyClass, accessing b already executes the getter specified in MyDerivedClass. In other words, having abstract get b(): number; declared in MyClass would not result in an uninitialized property inside the MyClass-constructor.

Transpiling my example and then removing the b-property from MyClass in the transpiled JS-source still produces the expected output.

Also, accessing the abstract getter while using [email protected] resulted in the correct behavior (= executing the getter in my derived class).

Long Story short:
When accessing an abstract property inside a constructor, I would expect TS2715 to be thrown. But when the property is actually encapsulated by a getter, I would expect TS2715 not to be thrown as such code would execute correctly.

Background:
In my case, the value returned by the getter can be understood as a child-class-specific configuration that is required to initialize the super-class correctly. When writing my original piece of code, my intention was to force some future developer to implement this getter in his derived class, so the abstract class can be completly initialized inside its own constructor. Of course this dependency could be passed from the derived-class to the super-class by calling an init-function after constructing the object, as @RyanCavanaugh suggested in his explanation, but I think using the abstract getter is easier as you need to know less about the behavior and usage-instructions of the super-class

Playground Link:
TypeScript Playground with my example: http://bit.ly/2F3C2xR

Related Issues:

9230 #19005

Working as Intended

Most helpful comment

Okay, I see. I did not take your example into consideration, but obviously, type assertion is a very good thing here :-)
I ended up changing my abstract property getter into an abstract function which is the proper way to go in such scenarios, I guess.
Thanks for pointing that out to me!

All 2 comments

This code is legal:

class MyDerivedClass extends MyClass {
    b = 42;
    constructor() {
        super();
    }
}

so the fact that the base class declared the property as a getter tells us nothing about whether the concrete class also did so.

It's really just appropriate to use a type assertion here if you have out-of-band knowledge that every single concrete class has correctly implemented a prototype getter whose body doesn't refer to any derived-specialized class properties.

Okay, I see. I did not take your example into consideration, but obviously, type assertion is a very good thing here :-)
I ended up changing my abstract property getter into an abstract function which is the proper way to go in such scenarios, I guess.
Thanks for pointing that out to me!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Antony-Jones picture Antony-Jones  路  3Comments

DanielRosenwasser picture DanielRosenwasser  路  3Comments

manekinekko picture manekinekko  路  3Comments

weswigham picture weswigham  路  3Comments

kyasbal-1994 picture kyasbal-1994  路  3Comments