TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

A derived class is allowed be created without a parent's getter or setter.

Open dwilsonactual opened this issue 3 years ago β€’ 7 comments

Bug Report

πŸ”Ž Search Terms

class extend override getter setter accessor

πŸ•— Version & Regression Information

  • This seems to occur on all versions on the Playground (3.9+)

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

class Parent {
    public get f() { return () => {}; }
}

class Child extends Parent {
    public set f(func: () => void) { }
}

const c = new Child();
c.f(); // runtime error because Child doesn't implement the getter for f

πŸ™ Actual behavior

Class Child is allowed to extend class Parent even though, by adding a setter for f, it discards the getter for f and therefore doesn't implement Parent's contract.

πŸ™‚ Expected behavior

TypeScript should emit an error because Child is not compatible with Parent.

dwilsonactual avatar Jan 24 '22 20:01 dwilsonactual

Sounds like #30852.

MartinJohns avatar Jan 24 '22 20:01 MartinJohns

@MartinJohns I think you're right, the root of the problem is that when you add a setter, TypeScript assumes you have a getter whether one exists or not, and this is apparently by design.

dwilsonactual avatar Jan 24 '22 21:01 dwilsonactual

It'd be nice to fix this but I'm not even sure how. The problem is that this isn't a soundness violation -- if we write out the getter that is implicitly present, there's no problem there either:

class Parent {
    public get f() { return () => {}; }
}

class Child extends Parent {
    public get(): never {
        throw new Error("readonly");
    }
    public set f(func: () => void) { }
}

An ad-hoc rule that a setter must have a matching getter if there's a property in the base class feels like a hack.

RyanCavanaugh avatar Jan 24 '22 23:01 RyanCavanaugh

I've convinced myself that the fix for now (if not forever) is to add this eslint rule to require getters and setters to always come in pairs "accessor-pairs": [ "error", { "setWithoutGet": true, "getWithoutSet": true }]

dwilsonactual avatar Jan 25 '22 16:01 dwilsonactual

In the following code, typescript expects a boolean, however at runtime it is actually undefined.

Playground Link

class Parent {
    protected _value = false;
    public get value() { return this._value; }
}

class Child extends Parent {
    public set value(value: boolean) { this._value = value; }
}

function f(): boolean {
    const obj = new Child();
    return obj.value;
}

function g(): true {
    const obj = new Child();
    obj.value = true;
    return obj.value
}

console.log(f()) // undefined
console.log(g()) // undefined

I think that most would expect Child to inherit Parent's getter and behave the same as the following code: Playground Link

class Parent {
    protected _value = false;
    public get value() { return this._value; }
}

class Child extends Parent {
    public get value() { return this._value; }
    public set value(value: boolean) { this._value = value; }
}

function f(): boolean {
    const obj = new Child();
    return obj.value;
}

function g(): true {
    const obj = new Child();
    obj.value = true;
    return obj.value
}

console.log(f()) // false
console.log(g()) // true

OwenDelahoy avatar Feb 08 '22 04:02 OwenDelahoy

I think that most would expect Child to inherit Parent's getter

Agree completely that it’s expected to inherit the getter. IMO this makes overriding the setter unnecessarily difficult.

BlakeOne avatar Jun 24 '22 09:06 BlakeOne

just being hit by this issue. The typeScript should error if the parent class has getter/setter but the child only have setter, because in most cases, people's intention is to "inherit" the getter from the parent class.

class Parent {
    get a() {
        return 1
    }
    set a(val: number) {}
}
class Child extends Parent {
    set a(val: number) {
        super.a = val
    }
}
const child = new Child()
child.a = 2
console.log('--- after write --')
console.log(child.a)
//          ts: child.a is number
//          js: child.a is undefined

Jack-Works avatar Nov 29 '22 10:11 Jack-Works