Skip to content

Class property readonly? for external consumers #53707

@streamich

Description

@streamich

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;
  });
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions