Skip to content

allow access to abstract getters in the constructor #21961

@DortmunderJunge

Description

@DortmunderJunge

TypeScript Version:
2.7.1

Actual behavior:
After upgrading from typescript@2.6.2, 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 typescript@2.6.2 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions