tc39/proposal-decorators

Should decorated private methods be settable?

Closed this issue · 5 comments

Currently, decorated private methods are not settable. This is inline with the general behavior of private methods, which are implemented as private slots which can be read but not updated. However, this limits some of their potential use cases - for instance it is not possible currently to make a @bound decorator which would work with private methods.

One possibility would be to allow decorated methods to have a set value on their access object. This would allow private method initializers added via addInitializer to update the value of the method, allowing them to do pretty much the same things as public method decorators. However, this could also make the implementation of private methods a bit more complex and possibly less performant.

Perhaps I have not fully understood the limitation you describe or I have made a big mistake in the implementation of the private method decorators.

In the experimental transpiler I have implemented the private method assignment in such a way that, when a method decorator returns a function, it replaces the private method:

class C {
  @decorator
  #m() {
    console.log("private method");
  }
}

is transpiled as:

const _C_m_symbol_pp0gp8 = Symbol();

class C {
  _C_m_temp_ug7bto() {
      console.log("private method");
  }
  static [_C_m_symbol_pp0gp8] = decorator(C.prototype._C_m_temp_ug7bto, {
    kind: "method",
    name: "#m",
    isStatic: false,
    isPrivate: true,
    access: {
      get: C.prototype[_C_m_symbol_pp0gp8]
    },
    ...__PrepareMetadata(C.prototype, "private", "#m")
  }) ?? C.prototype._C_m_temp_ug7bto;
  #m = C[_C_m_symbol_pp0gp8];
  [_C_m_symbol_pp0gp8]() {
    return this.#m;
  }
}

delete C.prototype._C_m_temp_ug7bto;

As a result, you can set the private method with another one, but, of course, you cannot bind the private method to the instance object (as an @bound decorator).

i thought private methods can’t be passed as functions, so why would you need to bind them?

@ljharb you can pass it as a function within the class body, e.g.:

class Foo {
  #a = 123;

  #onClick() {
    console.log(this.#a);
  }

  constructor() {
    document.querySelector('body').addEventListener('click', this.#onClick.bind(this));
  }
}

ah ok, thanks

After some discussion it is clear that while beneficial for DX, this would change the performance characteristics of private methods and so would require some sort of syntactic opt-in, which is not ideal for DX. As such, decorated private methods will not be made settable.