tc39 / proposal-private-fields

A Private Fields Proposal for ECMAScript
https://tc39.github.io/proposal-private-fields/
317 stars 19 forks source link

Private fields are tied to classes #93

Open futpib opened 7 years ago

futpib commented 7 years ago

Since class is syntactic sugar for function and "special" "prototype" property (at least class constructors are defined in terms of function and "prototype"), I feel like it's better to tie private fields to those constructs. In other words, one should be able to achieve exactly same results with function and prototype as with a class.

Currently this proposal adds PrivateFieldEnvironment to Execution Contexts and the only defined way to create new PrivateFieldEnvironment is ClassDefinitionEvaluation (that is evaluation of a class ... expression).

This will result in that whenever user wants to create on object with truly private fields, they will have to use a dummy class declaration with all it's limitation (like inability to declare arbitrary class members in runtime in reflective manner (without eval-like features)).

I propose that at least

EDIT: The Object.create part has been revised as unnecessary later in this thread.

littledan commented 7 years ago

This proposal deliberately avoids making private fields a kind of property. That approach was attempted at TC39 in the past, but it leads to the question: If private fields are object properties, then how do Proxies view them? It didn't seem like there was any good answer to the question that preserved the properties that Proxies were intended to have. cc @erights

futpib commented 7 years ago

@littledan Thanks, I get that. I guess what I was really trying to say was that making PrivateFieldEnvironment, PrivateFieldIdentifers and [[PrivateFieldValues]] usable by means other than in class declaration would make private fields more useful.

This does not necessarily mean that existing reflection methods like Object.getOwnPropertyNames or Object.getOwnPropertySymbols should expose private properties. Nor even that there should be special methods like Object.getOwnPrivatePropertyNames specifically for [[PrivateFieldValues]].

But I think making a new universal object slot [[PrivateFieldValues]] exclusively for class declaration is very limiting.

This does not seem to contradict the arguments made in this FAQ entry.

littledan commented 7 years ago

Sorry for misunderstanding your message. I've been thinking about what sort of reflection mechanism we should have for private fields; one draft is in this file. However, I've been thinking about this mostly as it relates to decorators--not use outside of a class.

I don't think it'd be great if people had to go around using imperative, semi-reflective machinery to use private fields outside of classes. It would be nicer if some sort of syntax just works.

For example, we could make the #names be available to everything in the script or module, but not outside of it; then we could allow object literals to have private fields, and reference them from outside of that literal. The downside of this particular idea is that people often concatenate scripts when shipping them, and this concatenation would break the encapsulation; generally, it's nice that JavaScript has this property that you can wrap things in separate IIFEs and they'll stay basically separate, and it would be unfortunate to lose this property.

Do you have another particular idea for how we should enable this feature?

futpib commented 7 years ago

Sorry for being unclear, I see how my objections are vague. My prime motivation for creating this issue after reading the proposal was that adding new slots to objects and especially the execution context to be used only in classes (which are a derived concept themselves) left me uncomfortable since desire for hard encapsulation is not necessarily tied exclusively to classes or OOP. Another thing is the inability to define private properties dynamically/reflectively.

PrivateFieldEnvironment looks like just like the usual lexical environment, except it is used for private identifiers. Very similarly to how lexical environment is used for common identifiers. Unfortunately I can't imagine a really-really nice way to extend this proposal in it's current form to allow general use of PrivateFieldEnvironment, but let me propose the best I got:

function Point () {
  private #x;
  private #y;

  const p = {
    #x: 0,
    #y: 0
  };

  p.#x; // 0
  p.#y; // 0

  return p;
}

I think this is pretty self-explanatory (function declarations get a PrivateFieldEnvironment just like class declarations), but If there is any interest in this, I can try to formalize this.

EDIT: The lack of reflection still bugs me, even though if you view private identifiers like another kind of lexical identifiers, the lack of reflection kind of makes sense.

ljharb commented 7 years ago

Interesting idea! Would the following be the desugaring into WeakMaps?

function Point() {
  const x = new WeakMap();
  const y = new WeakMap();

  const p = {};
  x.set(p, 0);
  y.set(p, 0);

  x.get(p); // 0
  y.get(p); // 0

  return p;
}
futpib commented 7 years ago

Yeap, that's right.

futpib commented 7 years ago

Oops, no, actually, that's not quite right.

The trick is that PrivateFieldEnvironment for classes is created when class definition is evaluated. It's better be the same for functions.

The following desugaring gets this right.

const Point = (function () {
  const x = new WeakMap();
  const y = new WeakMap();

  return function Point() {
    const p = {};
    x.set(p, 0);
    y.set(p, 0);

    x.get(p); // 0
    y.get(p); // 0

    return p;
  };
})();

But if we want to be really thorough we would replace WeakMap with something like PrivateFieldIdentifier.

littledan commented 7 years ago

@futpib Interesting idea. Do you think this proposal should have any changes to "future proof" for that as a follow-on?

futpib commented 7 years ago

@littledan Is this too major of a change for the proposal at it's current stage? I think something like this should be included with the first landing of private fields (and should have been included from the get-go). If that's not an option, I guess it's future-proof enough for this to be included later.

This fits the JavaScript's class-function duality so nicely >_<

Click to expand large desugaring examples ```js class Point { #x; #y; constructor(x = 0, y = 0) { #x = +x; #y = +y; } get x() { return #x } set x(value) { #x = +value } get y() { return #y } set y(value) { #y = +value } equals(p) { return #x === p.#x && #y === p.#y } toString() { return `Point<${ #x },${ #y }>` } } ``` After desurating class: ```js const Point = function () { private #x; private #y; function Point(x, y) { this.#x = +x; this.#y = +y; } Point.prototype.equals = function equals(p) { return this.#x === p.#x && this.#y === p.#y; }; Point.prototype.toString = function toString() { return "Point<" + this.#x + "," + this.#y + ">"; }; Object.defineProperty(Point.prototype, "x", { enumerable: true, configurable: true, get: function get() { return this.#x; }, set: function set(value) { this.#x = +value; } }); Object.defineProperty(Point.prototype, "y", { enumerable: true, configurable: true, get: function get() { return this.#y; }, set: function set(value) { this.#y = +value; } }); return Point; }(); ``` After desugaring private identifiers: ```js const Point = function () { const x = new WeakMap(); const y = new WeakMap(); function Point(x, y) { y.set(this, +y); x.set(this, +x); } Point.prototype.equals = function equals(p) { return x.get(this) === x.get(p) && y.get(this) === y.get(p); }; Point.prototype.toString = function toString() { return "Point<" + x.get(this) + "," + y.get(this) + ">"; }; Object.defineProperty(Point.prototype, "x", { enumerable: true, configurable: true, get: function get() { return x.get(this); }, set: function set(value) { x.set(this, +value); } }); Object.defineProperty(Point.prototype, "y", { enumerable: true, configurable: true, get: function get() { return y.get(this); }, set: function set(value) { y.set(this, +value); } }); return Point; }(); ```
bakkot commented 7 years ago

@futpib

Is this too major of a change for the proposal at it's current stage?

Almost certainly. This would be a huge addition, and we've been trying to get class features in for a long time. I would strongly prefer not to add more new things to the proposal at this point.


I want to point out a potential conflict with the current proposal:

function Point () {
  private #x;

  const p = {
    #x: 0
  };

  class Other {
    #x = 1; // Is this the same '#x' as above?
  }
}

We previously considered (and rejected) declaring private fields in classes as private #x, which would avoid the issue nicely. On the other hand, it would be kind of surprising if private #x were the only declaration legal in both function bodies and class bodies.

futpib commented 7 years ago

@bakkot I thinks the "conflict" you pointed out is not an issue when private identifiers are lexically scoped.

class A {
  #x = 0;

  constructor() {
    class B {
      #x = 0;

      constructor() {
        #x = 1; // lexically closest #x
      }
    }
  }
}
bakkot commented 7 years ago

@futpib It's a conflict in the sense that it's not obvious that {#x:0} references a previously declared name whereas class {#x=0} declares a new one.

littledan commented 7 years ago

Almost certainly.

Hang on, @bakkot; it's not at Stage 3 yet. TC39 has been working on this problem for several years; I really want to see it shipped now too, but let's make sure to keep taking any useful feedback we get.

Anyway, @futpib:

I think the way you're resolving the conflict there makes sense--classes introduce a new scope for these identifiers, as does any block that contains "private #foo", and shadowing works lexically. It all makes sense to me, and seems like a consistent extension of the current proposal. This is actually pretty similar to some ideas that I remember seeing on the ECMAScript wiki, but I don't know where to find that; @allenwb do you remember this? It's really nice to have a story for object literals here, and it would actually be consistent with the destructuring proposed in a different issue. The only concerns I have would be:

A good repository to follow up on this further would be the unified class features proposal, which currently assumes that objects' curly brackets will be what sets off the new private name scope.

bterlson commented 7 years ago

Doesn't future proofing for this extension require using private for class declarations now? To illustrate further the hazard @bakkot raises:

private #id;
let instances = 0;
export class A {
  // declare a new private name with same name
  #id = instances++;
}

export function getId(a) {
  // refers to a different name than A's, so will never return an id.
  return a.#id;
}

I'd expect the above to work, though you might expect it to work even with a private keyword before the class declaration. Errors on private name shadowing could mitigate this somewhat.

littledan commented 7 years ago

@bterlson, Would you expect the following example to work (apologies for the horrible names)? Maybe if you come with a Java-like intuition, and you expect such a reference to be disambiguated, you would. The current proposal, though, expects that users can understand what it means for a private name to be shadowed.

Do you think if we introduced the keyword private, it would create the false intuition that that's the only place where a new name binding is created, but without ever creating that syntax, it's more clear that shadowing works the way it's currently proposed?

class Button {
  #id = MakeGUID();
  labelFactory() {
    let button = this;
    return class Label {
      #id = MakeGUID();
      getButtonID() {
        return button.#id;
      }
    }
  }
}
futpib commented 7 years ago

@littledan I know this has been discussed already, but nevertheless, if private keyword was the only thing that can introduce new private identifiers to the scope, there will be much less confusion. I think, if this addition is to be accepted, then both in class and function declarations (or maybe in any block) the only valid form of private identifier declaration should be private #x.

littledan commented 7 years ago

@futpib We already have some other differences with classes compared to outside of classes. Outside of a class, you define a function by function f() {}, but inside of a class, you define a method by f() {}. Doesn't leaving out private inside the class seem analogous?

zenparsing commented 7 years ago

This is similar in some ways to an old proposal by @allenwb for name declaration (back from the private name days), with the difference that the names themselves are not reified. This deserves some thought.

allenwb commented 7 years ago

A precursor proposal to the current private fields proposal used the private #foo; syntax and contemplated supporting private fields in object literals.

The "private name" proposal that @zenparsing mentioned is Syntactic Support for Private Names from 2012. Note that in this context, a Name is more or less what was eventually called a Symbol in ES 2015. It built upon an earlier Instance Variable proposal that dates to early 2011. So, none of these ideas are particularly new, the challenge all along has been building sufficient consensus around any particular private state scheme.

Perhaps not surprisingly, I like some of the ideas in this thread as they address some defficienceis with both the current private fields proposal and plausible extensions to it.

One issue is that strictly linking the lexical declaration of private field identifiers with the definition of private object field limits the utility of inner class definitions. For example while this is legal:

class Outer {
   #foo=42;
   getFooAccessWrapper() {
      const anOuter = this;
      return new class {
            get value() {return anOuter.#foo} //inner class can access outer class private
       }
}
console.log(new Outer().getFooAccessWrapper().value); // 42

There is currently no way to allow an outer class to access an private field of an inner class:

class Outer {
      #helper = new class Helper {#foo};
      exec(service) {
            service(this.#helper); //services needs Helper instances
      }
      set foo(v) {this.#helper.#foo = v}  //Syntax Error: #foo not visibly declared
}

and trying to declare #foo in the outer class doesn't help:

class Outer {
      #foo;  //create a #foo field to eliminate syntax error
      #helper = new class Helper {#foo};  //note introduces a inner #foo
      exec(service) {
            service(this.#helper); //services needs Helper instances
      }
      set foo(v) {this.#helper.#foo = v}  //Reference Error: helper instances don't have this #foo
}

Adding private fields to object literals would have similar issues because, applying the principles of the current proposal, such private fields would be instance private:

function fooFactory(value) {
     return {
           #value: value,
           sameAs(aFoo) {return this.#value === aFoo.#value}
     }
}
console.log(fooFactory(42).sameAs(fooFactory(42)));
    //Reference Error: Each evaluation of obj lit creates a new distinct field idetity

Adding a private #foo; declaration that introduces a new lexically visible private field identifier without actually defining a private field would provide a solution to both of these issues:

class Outer {
      private #foo, #helper;  //create new private field ids #foo and helper
      #helper = new class Helper {#foo}; 
            //Outer instances have a #helper field, Helper instances have a #foo field 
      exec(service) {
            service(this.#helper); //services needs Helper instances
      }
      set foo(v) {this.#helper.#foo = v}  //works! #outer can reference Helper's #foo
}

private #value;
function fooFactory(value) {
     return {
           #value: value,  //defines a private field using the lexically visible #value binding
           sameAs(aFoo) {return this.#value === aFoo.#value}
     }
}
console.log(fooFactory(42).sameAs(fooFactory(42)));  // true!

Note that in the Outer class I used a private declaration for #help even though it probably isn't really necessary. I did this to emphasize the distinction between private declarations that introduce a lexically scoped private field identifier and class instance/object literal field definitions that actually create an object level data slot that is keyed with the currently scoped private field identifier.

In practice, I'm sure that for ergonomic reasons, for the most common cases people would still want to be able to make declarations like:

class P {
     //don't need to also say: private #x,#y;
     #x; 
     #y;
     constructor(x,y)
         this.#x=x;
         this.#y=y;
     }
}

So, I would define the semantics of class private field definition for #foo as using the private #foo declaration that is currently in scope and if there is no visible declaration of private #foo one is automatically introduced with class scope. The same could be done for object literal fields but it feels like a foot-gun to me as I don't think instance private is necessarily what will be wanted for most such obj lit use cases.

A few final thoughts

futpib commented 7 years ago

@allenwb, @littledan I'd still argue that we are better off with a mandatory private keyword for private identifier declarations. This is not similar to omitting function when defining methods in classes because method declaration do not introduce anything to the lexical(-ish) scope.

The confusion outlined in this comment is a valid concern. Without mandatory private, when reading a class declaration, in order to understand weather a #x = 1 line is a declaration (of a new private identifier) or a reference (to an already declared private identifier) one has to skim through all the parent scopes looking for #x. This is trivial in a small example, but in a large file this would be a bummer:

class Outer {
  #x = 0;

  // imagine a lot of code here

  labelFactory() {
    return class Inner {
      #x = 1; // is this a reference or a declaration?
    }
  }
}

Similarly, without mandatory private, it is easy to accidentally break code in inner scope by introducing a new identifier to the outer scope (turning an intended declaration to a reference):

class Outer {
  #x = 0; // imagine this line wasn't here before

  // imagine a lot of code here

  labelFactory() {
    return class Inner {
      // this was a declaration, but it became a reference
      // when one introduced #x to the outer scope
      #x = 1;
    }
  }
}

You can see that with mandatory private this is not an issue, private #x = 1 is always a declaration, #x = 1 is always a reference.

Another point is keeping syntax consistent. Currently one can introduce lexical identifiers only with keywords (const, let, function, class, import ...). (Except in non-strict mode you can do x = 1, but it's considered an anti-pattern universally)

It seems that these arguments are valid even against proposal in it's current form (with private identifiers being exclusive to classes).

littledan commented 7 years ago

@allenwb Thanks a lot for the historical links, Allen. It seems like we may be on a good path, if we're thinking along the lines of those proposals, but then avoiding the Proxy issue by not making private fields be properties. Your analysis seems to make sense to me; all together, sounds like you're suggesting this proposal first, and explicit private lexical declarations could fit in well as a follow-on proposal, and that it's reasonable to use different syntax for these (since they are doing very different things); am I understanding correctly?

One small thing about private in top-level scopes: It seems like it should be restricted to the script/module, rather than joining part of the global lexical contour, right?

@futpib What you're saying sounds like a downside of not having a token before the declaration in the class (or possibly of having the private access shorthand), rather than something that's specific to private fields in particular. For understanding whether a declaration is a declaration or assignment, the issue appears for public fields as well, such as class Outer { x = 0; }. And, in the constructor, for understanding whether a line is an assignment or a declaration, the analogous problem also occurs, where class Outer { constructor() { x = 0; } } will be an assignment to whatever the outer scoped x is. However, after years of debate, TC39 settled on the token-less form for such field declarations at the May 2017 TC39 meeting.

zenparsing commented 7 years ago

@littledan It seems to me that if you wanted to expand the syntax with private name declarations in the future, then the private keyword for fields needs to be mandatory for now.

Presumably, one of the primary use cases for this feature would be "friend" classes.

private #x;

class A {
  #x; // A has an "#x" field
}

class B {
  readX(a) {
    console.log(a.#x); // B has access to "#x";
  }
}

Note that in A a new lexical name is not introduced. The field uses the name defined in the outer scope.

The semantics required for such a pattern would be:

zenparsing commented 7 years ago

Crazy idea (feel free to trounce it):

So this:

class A {
  private _x;
  constructor() {
    this._x = 1;
  }
}

is rewritten at compile-time to have the following semantics:

class A {
  private #x;
  constructor() {
    this.#x = 1;
  }
}

The idea is that with private declarations, the user opts-in to private lookup semantics for that identifier name only.

Advantages:

Disadvantages:

erights commented 7 years ago

This has the (IMO terrible) consequence that if this same code operates on an external object that just happens to use the same name as a public property name, then the obvious obj.name syntax for operating on that external object stops working.

When people express the preference you're reacting to, their preference does not take into account this cost.

allenwb commented 7 years ago

@erights Yes, that problem was what eventually cause all previous non-sigil based approached to be abandoned.

Statically typed languages use type declarations to distinguish the external-public/internal-private cases with out the need of a sigil. But a dynamically typed language doesn't have enough static information to differentiate those cases.

bakkot commented 7 years ago

@zenparsing that one is in the FAQ, even!

littledan commented 7 years ago

@bakkot It might be worth pulling in Allen's comment about how statically typed languages disambiguate here; I think this is pretty critical to the point.

littledan commented 7 years ago

BTW this comment makes me especially convinced about the hazard. Not sure what exactly we should do right now.

bakkot commented 7 years ago

@littledan Do you think changing the private field declaration in classes between #x and private #x could reasonably be done at stage 3? If so, I think we should go ahead with the class fields proposal as it stands, and maybe bring this up as an extension, possibly as its own proposal.

I think this idea actually works well with the current proposal except for the above-noted conflict, and that conflict (it seems to me) is resolved very nicely by requiring private #x as the declaration in classes. The committee has rejected the private requirement there previously, but that was when there was no language-level need for it. I think it would reconsider if there were one.

zenparsing commented 7 years ago

@erights

I added a refinement my (half-serious) proposal above, requiring that names start with an underscore. This reduces the hazard you point out (though it certainly doesn't eliminate it).

erights commented 7 years ago

@zenparsing which raises the question: why is (an unreliable) "_" better than (a reliable) "#" ?

zenparsing commented 7 years ago

@erights Consider it a Plan B for the possibility that there are no available characters to choose that are both reliable and palatable.

erights commented 7 years ago

"#" is only significantly less palatable than "_" until one gets used to it. (Though I admit I'll probably never like it.)

Unreliable is hugely less palatable than reliable.

The generalized private discussed in this thread sure seems to harken back to the old "relationship" ideas and your ( @zenparsing 's) old generalized access notation. Rather than introduce a new kind of lexical namespace, we could instead introduce a the following new bits of syntax:

base::name expands to name.geti(base) base::name = expr expands to name.seti(base, expr) base::name(...args) expands to name.geti(base).call(base, ...args)

For suitable choice of keyword that means hoist out one level:

class Foo ... {
  keyword decl2 name = initExpr;
  ...
}

expands to

const Foo = (function(){"use strict";
  decl2 name = initExpr;
  return class Foo ... {
    ...
  };
})();

or, more generally

decl1 Foo ... {
  keyword decl2 name = initExpr;
  ...
}

expands to

const Foo = (function(){"use strict";
  decl2 name = initExpr;
  return decl1 Foo ... {
    ...
  };
})();

where decl1 may be at least "class" or "function" and decl2 may be at least "let", "const", (or with obvious local changes) "function", or "class".

I am not saying that we should necessarily go there. But if we are considering the generalizations in this thread, then why not go all the way back to this?

littledan commented 7 years ago

@bakkot We need to work the #x vs private #x issue before Stage 3. I'd hope that Stage 3 would mean the language design is basically done, and that implementers, tool authors and users can proceed with the confidence that changes from there would be minor and only based on unforseen things that came up in implementation, etc.

I'm not convinced that # is a character that's significantly uglier than _ or @. For new programmers who have not used another language, or for people who have spent more than 5 minutes looking at JS code using the feature, what's the difference? I could buy that sigils are bad, but it doesn't seem like a particular sigil choice will be fatal. It's hard for me to see why we'd arrive at the need for this Plan B aside from the general sigils-are-bad argument.

If we're talking about reliability: You could see # as still unreliable, e.g., if you have nested classes and want to refer to the private field of the outer one. However, it seems less likely to come up in practice; I think some of these design issues are more on a gradient than absolute.

futpib commented 7 years ago

@erights One step further and we arrive at "operator overloading with magic methods" 😄.

This proposal is still about private fields, I think that's the reason we don't go that general.

This issue is about making private fields usable outside of classes. That's it, and it's not even that huge of a generalization.

Already in this proposal:

This issue proposes:

The mandatory private #x point stands against this proposal in it's current form, but accepting this issue also makes this problem easier to encounter. (also related: #53)

Jamesernator commented 7 years ago

@allenwb I'm mixed on your point about not allowing export private #x, while it's not great in that it would be trivial to get access to the internals by importing such a file, it might also be useful to share it amongst things that need it e.g. someone might implement something in one file but have operators in many separate files (like Observable) in which case modules may need to share that private field.

Now this would be doable as is without exporting them as you could always have:

private #state;

export default class SomeClass {
    #state = {};
}

export function _readState(someClass) {
    return someClass.#state
}

But this is just as bad as allowing export private #x so why not just allow it?

Ultimately it's always going to be possible to break private state if people want to spread things over multiple files. e.g. I can always do this:

class SomeClass {
    private #state

    __breakState() {
        return this.#state
    }
}

What developers should really do instead is make sure that the exposed entry point to the application doesn't expose those private states e.g.

// privateImplementation.js
private #initializer
class Task {
    constructor(initializer) {
        this.#initializer = initializer
    }

    ...
}

export default Task
export #initializer

// someOperator.js
import Task, { #initializer } from "./privateImplementation.js"
function delay(task, time) {
    return new Task((resolve, reject) => 
        setTimeout(_ => task.#initializer(resolve, reject), time)
    )
}

// publicTask.js
export { default } from ".../privateImplementation.js"

// someConsumer.js
import Task from ".../publicTask.js"
import delay from ".../operators/delay.js"
zenparsing commented 7 years ago

@erights I still like that old proposal! The only issue is that we needed to distinguish between "creating" the field and assigning to the field, and the :: proposal didn't provide that distinction. Any ideas?

erights commented 7 years ago

I still like that old proposal!

Thanks!

The only issue is that we needed to distinguish between "creating" the field and assigning to the field, and the :: proposal didn't provide that distinction. Any ideas?

No positive ideas yet, but a possible direction. Since this proposal would separate the (hoisted) declaration of the WeakMap-ish from the initialization of per-instance state, we could again consider moving the per-instance-state initialization back inside the constructor, where it can see the constructor arguments.

littledan commented 7 years ago

One issue with this is that it conflicts with some requirements @wycats has been advocating for about the syntax of decorated private fields. The hope is that you can use something like this for a field which has a private field and a generated reader:

class C {
  @reader #x;
}

Here, the claim is that it would be awkward to insert a keyword such as own (or private) in between @reader and #x because conceptually, for the user, this is actually a publicly readable field, which you address as instance.x. We were previously thinking, if own is the keyword, then it can be omitted when decorators are used, as long as the decorator would supply the placement (in effect, that you could implement an @own decorator, and combine that with your own decorator logic).

However, such logic would not explain why private is required--for @zenparsing 's "friend class" use case above, you'd expect that omitting private would use the #x from the outer lexical scope, not declare a new one just because it was preceded by a decorator.

zenparsing commented 7 years ago

Decorators are the new Proxy: "But have you thought about how this interacts with decorators?" 😄

Jamesernator commented 7 years ago

I don't think adding private between @reader #x is that confusing:

class C {
    // A reader for the private field x
    @reader private #x
}

The fact that it generates a public field is just a detail of @reader adding an additional field, the same argument would apply to the #x syntax by itself.

I would personally expect this to work though if the private syntax was added:

class C {
    // Declare the field
    private #x

    // use it in a decorator
    @reader #x
}
littledan commented 7 years ago

OK, it'd be good to get more context from @wycats here on why exactly @reader private #x is a problem. Either way, personally, I hope we can stick with the syntax without explicitly saying private.