Skip to content

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

@rbuckton

Description

@rbuckton

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).

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