Home > Mobile >  Why does accessing a missing getter in Typescript return 'undefined' rather than cause a c
Why does accessing a missing getter in Typescript return 'undefined' rather than cause a c

Time:10-26

When writing a property setter in Typescript it's not required to include a getter at the same time:

set name(name: string)
{
    ... 
}

However when accessing the corresponding property getter via this.name (which isn't defined) it is not a compile time error and returns the value undefined. I can't understand why and I also can't find any discussion about it.

const theName = this.name;   // undefined at runtime, but typed as 'string'

I'm most likely to end up in this situation accidentally and it can waste a lot of time in the rare case where I don't immediately notice.

The 'traditional' way to define a property is like this, so maybe that explains why the decision was made.

Object.defineProperty(Dog.prototype, "name", {
    set: function (name) {
    },
    enumerable: false,
    configurable: true
});

Wishful thinking of course but I'd much rather the behavior was to return the original 'raw' value passed in - that would be amazing. And avoid this awful mess :-/

set name(name: string)
{
   this._name = name;
   doNameStuff(name);
}
get name() 
{
   return this._name;
}
private _name!: string;

CodePudding user response:

Yes, this is a soundness hole in TypeScript. The compiler is happy to let you write a set property accessor without a corresponding get accessor, but it does not model the resulting behavior of the property very well. Conceptually a property with a setter but no getter should be "writeonly" in the same way that a property with a getter but no setter is readonly. Indeed, if you write a getter but no setter, the compiler infers that the property is readonly:

const foo = {
    get bar() { return 1 }
}
/* const foo: {
    readonly bar: number;
} */

But there is currently no such thing as writeonly in TypeScript, and the compiler instead models such a property as being a normal read-write property:

const baz = {
    set qux(x: number) { }
}
/* const baz: {
    qux: number;
} */

There is a longstanding open issue asking for writeonly properties at microsoft/TypeScript#21759 but it's not clear if or when it will ever be addressed.

TypeScript 4.3 introduced some support for variant accessors which allows the type system to model getter and setter types that are different. But currently there's a requirement that the getter type needs to be assignable to the setter type. You can't express that the setter accepts (say) number but the getter always produces undefined. So there's another open issue at microsoft/TypeScript#43662 asking for unrelated types in the setter and getter. If implemented, that would be another way to model getterless setters; reading the property would be known to always produce undefined. Again, though, it's not clear that this will be implemented.

And there have been some suggestions to just have getterless setters be a compiler error, such as microsoft/TypeScript#30852 but this looks like it would be declined because it would be a breaking change for existing TypeScript code... although in my view it would only break code that already does weird things. But I'm not in charge.

Anyway, as I mentioned in a comment, the only suggestion I could think of for now is for someone to use ESLint's accessor-pairs linter rule. This rule will complain if you forget the getter, so at least you'd be forced to write safer code.

Playground link to code

  • Related