tc39 / proposal-private-declarations

A proposal to allow trusted code _outside_ of the class lexical scope to access private state
https://tc39.es/proposal-private-declarations/
MIT License
25 stars 4 forks source link

Add private after creation? #12

Open mhofman opened 2 years ago

mhofman commented 2 years ago

Should it be possible to add a private field to an existing object after it has been created? If yes, should there be a mechanism for the object to "opt-out" of getting a private field added? If no, that would restrict private declarations to be added only syntactically through something like the outer in the current description, and not through "Set" semantics.

bakkot commented 2 years ago

In my view, no. Other than the return override trick - which is a dumb edge case no one should even be aware of, much less use - private fields are installed when an object is created, and that consistency is an important part of reasoning about them.

I have no problem with restricting private declarations to object literals, especially now that we've finally conceded that { __proto__: null } is a part of the language.

bathos commented 2 years ago

which is a dumb edge case no one should even be aware of, much less use

This syntactic feature has singlehandedly prevented new additions from being second class again and again. She's a lone warrior fighting off whole armies and succeeding. There are currently at least six primitive capabilities tied to this, and it's dumb, sure ... but it ain't her fault they ended up that way.

Given they can be allocated at any time already, I don't know what "that consistency is an important part of reasoning about them" could mean.

(I don't think it's essential for this syntax to support it as well, but I don't agree with the reasoning given for it.)

ljharb commented 2 years ago

Since it's possible to install a private field on any object with the return override trick, I think it must be possible to do so with objects, altho it's fine if it's unergonomic to do so. Object literals must not be second-class to class instances.

bakkot commented 2 years ago

Given they can be allocated at any time already, I don't know what "that consistency is an important part of reasoning about them" could mean.

It means if you're looking at a code base, you can reasonably start from the assumption that private fields are added at creation, so you just need to find the place that declares the field and that will tell you what kind of object you're dealing with. This is an entirely reasonable assumption because only extremely unusual code code will violate it, even though it's technically possible to do so; adding an explicit, not-obviously-bad way of adding private fields after creation would make this property not hold.

This is like how you can reasonably assume that, for example, Object.getOwnPropertyDescriptor(obj, "field") isn't going to change obj, even though with a Proxy that might not be the case.

I am very strongly opposed to reasoning that just because something is technically possible it must guide the design of the language as if this is a thing users should be thinking about about.


@ljharb

Since it's possible to install a private field on any object with the return override trick, I think it must be possible to do so with objects

I don't know what this means. As you note, "it's possible to install a private field on any object with the return override trick", and therefore "it [is] possible to do so with objects" already.

jridgewell commented 2 years ago

Should it be possible to add a private field to an existing object after it has been created?

When I argued for Private Symbols instead of the current Private Fields, several of the delegates explained that class field's only-during-creation design is explicitly required to prevent bad usage (I disagree, but oh well).

If I remember right, it's possible that we could add an explicit "initialize" syntax, but it must not be obj.#priv = 1 set access. Since we're only adding syntatic forms, we can't use normal Object.defineProperty(obj, #priv) to define the property.

mhofman commented 2 years ago

FWIW, we would be very interested in finding a mechanism which objects could use to block return-override shenanigans, in particular private field additions. What that mechanism looks like, or if there would be committee interest in specifying it is TBD, but for us it would be moot if a proposal introduces a "blessed" way to add private fields to an object without the object's creator having a say about it.

ljharb commented 2 years ago

@bakkot a class private field, yes - but would that remain true with the private declarations added by this proposal?

bakkot commented 2 years ago

@ljharb Yes, since you can refer to those in classes (if I understand the proposal correctly):

private #x;

function addPrivateXToArbitraryObject(o) {
  class EvilReturnOverride {
    constructor(o) {
      return o;
    }
  }

  class Child extends EvilReturnOverride {
    outer #x; // refers to the `private #x;` declaration
    constructor(o) {
      super(o);
    }
  }

  new Child(o);
}
ljharb commented 2 years ago

Sounds good, then i'm fine with only allowing it on object literal creation (since return override allows it later)

robbiespeed commented 1 year ago

I would appreciate a method of adding after creation. I'm currently using a WeakMap to store private data on functions, but would rather use private fields.

Simplified example:

private #ref

function connect(emitter: (callback: () => void) => void, ref: object) {
  emitter.#ref = ref;
}

There would be no way to initialize the emitter (a function) with the #ref private field, even if all creation of emitters was limited to the same scope of the private declaration.

robbiespeed commented 3 months ago

I no longer think my use case of initializing a private field after creation is necessary or desirable. If I were still in need of such a pattern then using a stamper class to initialize the private field would work (with or without this proposal).