Repository navigation
Class property readonly? for external consumers #53707
Description
Suggestion
Add ability to specify that some class property is read-only for users outside the class, but it should still be writable by the code within the class.
This behaviour can be achieved with getters and setter, but: (1) it is very verbose; (2) and there is performance penalty.
class A {
#__foo: string;
public get foo(): string {
return this.#__foo;
}
}Instead, would be nice if this behaviour could be achieved with a single line of code:
class A {
public readonly? foo: string;
}Note ? in readonly?. The ? could indicate a new flavor of readonly keyword, which makes the property read-only for external users, but still writable for code inside the class.
✅ Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
⭐ Suggestion
I see there could be two implementation approaches.
Option 1: No runtime changes, only type checking
Here the compiler would emit the same JavaScript code as if the readonly? keyword was not present. The only difference would be errors in TypeScript, if somebody tries to modify the property from outside the class.
Option 2: Enforcing it
TypeScript could rewrite the property to enforce it being read-only from outside the class, but still writable from within the class.
This TypeScript code:
class A {
public readonly? foo: string = '';
}Would result in JavaScript like this:
class A {
constructor() {
this.#$$foo = '';
}
get foo() {
return this.#$$foo;
}
}💻 Use Cases
Consider this controllable Promise class—Defer, to avoid getter/setter performance penalty and still have TypeScript's readonly annotation, one needs to cast this to any.
export class Defer<T> {
public readonly resolve!: (data: T) => void;
public readonly reject!: (error: any) => void;
public readonly promise: Promise<T> = new Promise<T>((resolve, reject) => {
(this as any).resolve = resolve;
(this as any).reject = reject;
});
}With the proposed change, the code would change to
export class Defer<T> {
public readonly? resolve!: (data: T) => void;
public readonly? reject!: (error: any) => void;
public readonly promise: Promise<T> = new Promise<T>((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
Activity
fatcerberus commentedon Apr 9, 2023
I'm like 99% sure this is a dupe but I can't seem to find the other issue now.
For the record though, option 2 (runtime enforcement) is untenable:
Emitting different code based on the presence of
readonly?would be type-directed emit which is something TS actively avoids doing, by design.MartinJohns commentedon Apr 9, 2023
Duplicate of #37487.
fatcerberus commentedon Apr 9, 2023
Martin Johns (@MartinJohns) How did you find that? I was certain that issue existed but I tried searching for various permutations of
public private protected readonlyand it eluded me every time. 😄MartinJohns commentedon Apr 9, 2023
Bruce Pascoe (@fatcerberus) I searched for
writable in:title. Wasn't totally sure about what term to search for, but I knew the issue existed (I saw it before). Especially thein:titleis super helpful when searching, it weeds out so many issues where the keyword is just coincidentally mentioned.streamich commentedon Apr 10, 2023
Closing it as it is a duplicate of #37487