tc39/proposal-decorators

Clarify `access` property of auto-accessors and relationship to grouped/auto-accessors proposal

Closed this issue · 2 comments

In https://github.com/tc39/proposal-decorators#class-auto-accessors it is not clear what context.access does. Is this to access the underlying unnamed private field that the accessor wraps, or is it a way to invoke the outermost decorated getter/setter for something like @dec accessor #x;?

This becomes even more complicated when combined with the Grouped and Auto-accessors proposal, as its possible for an auto-accessor to have multiple private elements as well: the unnamed private backing field and a private-named setter:

class C {
  @dec2
  @dec1
  accessor x { get; #set; }

  @dec2
  @dec1
  accessor #y { get; set; }
}

If context.access gives you access to the unnamed backing field, then there is a disparity between what you can do with the getter and setter:

  • If you want to invoke the underlying getter, you can use get.call(this).
  • If you want to get the value from the instance by name, you can pipe through all decorator replacements using this[context.name].
  • If you want to invoke the underlying setter, you can use set.call(this, value).
  • However, there is no way to set the value on the instance by its private name to pipe through all decorator replacements.

Giving access to the unnamed backing field seems to align with context.access in Class Field Decorators.

However, If context.access gives you the ability to evaluate the outermost decorated getter/setter, then there is no way for a decorator to read the unnamed backing field (which would be useful for serializers/deserializers).

Giving access to the outermost decorated getter/setter seems to align with context.access in Class Accessor Decorator.

Hence my confusion. It might be better to distinguish between direct access (as with private fields) and indirect access (for interacting with the outermost decorated declaration):

type Decorator = (value: Input, context: {
  kind: string;
  name: string | symbol;
  directAccess?: { // access private field directly
    get(target: object, receiver: object): unknown; // uncurry `this` and pass in `receiver` to match Reflect.get
    set(target: object, value: unknown, receiver: object): void; // uncurry `this` and pass in `receiver` to match Reflect.set
  };
  indirectAccess?: { // access member via outermost result
    get?(target: object, receiver: object): unknown; // uncurry `this` and pass in `receiver` to match Reflect.get
    set?(target: object, value: unknown, receiver: object): void; // uncurry `this` and pass in `receiver` to match Reflect.set
  };
  isPrivate?: boolean;
  isStatic?: boolean;
  addInitializer?(initializer: () => void): void;
  getMetadata(key: symbol);
  setMetadata(key: symbol, value: unknown);
}) => Output | void;

NOTE: I chose the names directAccess and indirectAccess as a starting point. I'm more than happy discussing alternatives or an alternative API design that serves the same needs.

Finally, is there a reason to distinguish between auto-accesssors and possible future grouped-accessors using the kind of "auto-accessor"? For grouped-accessors will you just use "accessor", or could you use "accessor" for both and distinguish between each based on the presence of context.access (nee. context.directAccess, etc).

The current behavior is that access gives you access to the outer getter and setter that are being decorated, but not the private backing field. The reasoning behind this is that they align directly with the named value that is being decorated. The private backing field is, ultimately, a different named value that is not being decorated.

I think that it would be possible to add access to this backing field in the future by extending the decorator API in an additive way. I think the main thing it's missing at the moment are clear use cases. Serialization is a reasonable example, but serialization could also happen via the values accessed through the getter and setter, and most systems would likely expect values to be serialized this way today since there is no way to access the backing value(s) behind getters and setters in general. The main advantage would be performance, but it's not clear how much of a performance boost this would give and how common this use case is to justify adding the feature and making the decorator interface more complex.

Finally, is there a reason to distinguish between auto-accesssors and possible future grouped-accessors using the kind of "auto-accessor"?

This is not actually necessary, and the spec itself is does reflect this, the kind that is added there is just accessor. Will update the README shortly to reflect that.

I believe this has been answered, so going to close.