Giter Site home page Giter Site logo

tc39 / proposal-private-fields-in-in Goto Github PK

View Code? Open in Web Editor NEW
34.0 11.0 10.0 241 KB

EcmaScript proposal to provide brand checks without exceptions

Home Page: https://tc39.es/proposal-private-fields-in-in/

License: MIT License

HTML 100.00%
proposal ecmascript javascript private class fields

proposal-private-fields-in-in's Introduction

Ergonomic brand checks for Private Fields

EcmaScript Proposal, specs, and reference implementation to provide brand checks without exceptions.

Spec drafted by @ljharb.

Built on top of tc39/ecma262#1668 pending Class Fields being stage 4 and merged into the larger spec.

This proposal is currently at stage 4 of the process.

Rationale

Private fields have a built-in ”brand check”, in that if you try to access a private field on an object that does not have it installed, it throws an exception.

This is great! However, in order to keep the existence of a private field actually private, I often want to check if an object has a private field, and if not, have some fallback behavior (which might even be throwing a custom exception, with a message that does not reveal that I’m using a private field as my mechanism).

Using try/catch for this does work, but is quite awkward:

class C {
  #brand;

  static isC(obj) {
    try {
      obj.#brand;
      return true;
    } catch {
      return false;
    }
  }
}

This is also an issue for getters (that might throw):

class C {
  #data = null; // populated later

  get #getter() {
    if (!this.#data) {
      throw new Error('no data yet!');
    }
    return this.#data;
  }

  static isC(obj) {
    try {
      obj.#getter;
      return true;
    } catch {
      return false; // oops! might have gotten here because `#getter` threw :-(
    }
  }
}

What is desired is a simple solution to produce a boolean indicating the presence of a private field, that does not require a try/catch or exceptions.

Solution:

in

The most obvious solution is using the in keyword, like so:

class C {
  #brand;

  #method() {}

  get #getter() {}

  static isC(obj) {
    return #brand in obj && #method in obj && #getter in obj;
  }
}

This has no ASI hazards I am aware of; however, it does have one important tradeoff: it permanently kills any possibility for private field shorthand. Specifically, this is because if #brand (in the previous example) is a shorthand for this.#brand, then (#brand) in obj would have to mean the same as this.#brand in obj, which means "is the value of this.#brand in obj?", not "does obj have the private field #brand?".

Rejected solutions:

try statement

Another alternative is:

class C {
  #brand;

  static isC(obj) {
    return try obj.#brand;
  }
}

However, this strongly suggests that try <expression> would be a generic syntax for "if it throws an exception, produce false, otherwise produce true". That would be a much larger proposal, and would perhaps be a bit overkill to solve the specific problem around private fields.

This would require a lookahead restriction for a curly brace (which would mean that the try expression couldn't be an object literal, unless it was wrapped in parens).

Spec

You can view the spec for the in solution rendered as HTML.

Implementations

proposal-private-fields-in-in's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

proposal-private-fields-in-in's Issues

Some thoughts about this proposal

In last meeting, I blocked this proposal forward to stage 3. I try to explain my thoughts about this proposal again.

i think there are basically three issues:

  1. Relationship with refication proposal.

    I hope the syntax should match the concept model, and reification proposal semantic would define the concept model of #x so affect syntax of this proposal. But I found there are many confusion about refication proposal. Different delegates have a very different expectation of that. Some expect private symbol semantic, some expect weakmap semantic, some think "no reification ever". And it's very unclear what's the future of refication proposal. I understand @ljharb feel the confusion of refication proposal wouldn't affect the syntax choice of this proposal, but I really doubt about that. To some degree I believe this proposal depend on refication proposal, especially it would be weird if we had #x in o syntax and we finally reified #x to map-like objects.

  2. Syntax unmatch of KEY in o and o[KEY] when KEY is #priv.

    In last meeting there is a suggestion that we could first figure out what's the invariant we should keep. so if the committee had the meeting of that and had the consensus my concern of KEY in o match o[KEY]should not be a invariant i would not use this reason any more.

  3. May be too fast to advance a proposal from stage 0 to stage 3 in only 4 months.

    This is not a technical issue, but a process question, not specific to this proposal. Of coz currently there is no rule of how long a proposal should stay before stage 3, but it's not very common that a proposal advance fast like that, and most programmers even never know the existence of this feature so we can't expect user feedback at all. I very worry about it could result in premature design. I think the new idea like #11 is the signal that we still have some spaces to explore.

Feedback on alternatives?

Hi everyone! It'd be great to get as much feedback from the community as possible before this proposal advances to stage 2.

  • Do you like the proposed solution, #privateField in obj?
  • Do you have alternative suggestions that would retain the same encapsulation guarantees that private fields already have?
  • anything else?

(If you have thoughts on shorthand syntax, please add those to #2)

Thanks!

in test

Apologies if this is the wrong place to ask this, but I’m attempting to build a test for this feature, and am not sure if the errors and failures I’m encountering in Firefox Nightly (91) are due to a faulty test or a faulty browser. I filled out the readme example a little, like so:

class C {
  #brand = "brand";

  #method() {
  	console_log("#method called");
  	return "method";
  }

  get #getter() {
  	console_log("#getter got");
  	return "getter";
  }

  static isC(obj) {
    return #brand in obj && #method in obj && #getter in obj;
  }
}

Not that I expect any of those console logs to actually do anything, but it can be a quick way to make sure nothing went seriously awry.

In any case, what should I add to the above code to clearly test whether the in feature is working in a given browser? Ideally it would be something that’s easy to adapt to an innerHTML output, because having to open consoles on Browserstack VMs can be annoying and slow.

Object.hasBrand(obj, cls) alternative design

The purpose of this proposal is to support brand checks better. A possible alternative design is a standard library method, Object.hasBrand(obj, cls), which returns true if obj has the private fields defined in cls. It can throw if cls doesn’t have any private fields, since in that case the spec says there is no brand to check.

The problem this solves is that the #fieldname in obj syntax is not quite in agreement with the rest of the language (see discussion in #2), so I vaguely expect we will regret it. It seems better if we can get the feature without new syntax.

But the private fields proposal deliberately doesn't offer any reification of private field names as values. To get around that, we use the constructor itself as a value representing a set of private names.

(A possible objection is that this allows code outside a class to brand-check objects. Is it a violation of privacy? I don’t think so; most object-oriented languages permit brand checks as long as the class itself is accessible.)

`in` does not preclude shorthand?

I'm a bit confused by:

Specifically, this is because if #brand (in the previous example) is a shorthand for this.#brand, then (#brand) in obj would have to mean the same as this.#brand in obj, which means "is the value of this.#brand in obj?", not "does obj have the private field #brand?".

Why is #brand in obj problematic? this.#brand is the reference to the property value of #brand on this so it doesn't collide with the environment reference #brand to my knowledge.

Explicit first class private name alternative

In the July 2020 TC39 meeting, @hax mentioned a goal of reification for private names. Previously, @erights and @waldemarhorwat discussed the option of these first class values for private name brand checks.

I wrote a gist to explore some concrete details about how object-based explicit references to private names (as @erights was encouraging) could work to permit exception-free brand checks to the presence of private names in classes.

With this idea, the syntax #x in obj would be replaced by (private.name #x).has(obj)--a little bit longer, yes, but it accomplishes the same function. private.name creates a fresh object with methods to access the private name.

This approach would be compatible with the membrane-transparency goals that @erights has advocated for. In particular, you could pass this private name as well as the object over a membrane, and the membrane system can unwrap them both to do the check appropriately. This would be impossible if the private name were passed around as a primitive directly--if ergonomic brand checks are reliable and do not use method calls in its semantics, then the membrane system has no chance to unwrap the object.

Overall, I don't know how to weigh the ergonomics goal that @ljharb has articulated with the reification goal that @hax has articulated. To me, neither one seems to be an absolute requirement, but rather tradeoffs about value judgements that we as a committee can make. I hope this gist helps us explore more of the design space.

(I wonder if, for the ergonomics goal, we could be looking more to @erights and @waldemarhorwat 's guards proposal, which used the :: syntax to do a check about whether a JS value matches a guard. This could be a longer-term solution compatible with a near-term capability of first-class private names for "has" checks. Guards are also based on first-class values.)

Class brand

If private fields are specific of classes (are they not?), there is no case where you'd want to check for the existance of a particular private field which could not be satisfied by testing for a given value being an instance of the current class.

While instanceof can be spoofed, and might not be a 100% safe option because of that, a new, unspoofable class brand check could be devised, whose uselfuness would go beyond testing the existance of a private field.

Example:

if (o is of class) {

}

Or, can the class of an object which is an instance of an "actual" JS class be spoofed as well (via constructor)?

Update readme to clearly mark chosen solution and its behaviour

Hi, would it please be possible to update the readme to clearly show the chosen solution and its expected behaviour at a high level? At the moment there are still multiple listed potential solutions but my understanding the in proposal is what was agreed on for stage 3. I think at the moment it is unclear to those unfamiliar with the proposal what change is being introduced into the language from an end-user's standpoint. Thanks!

No need to support strong class inheritance checks here

JS class inheritance is based on prototype inheritance. There is no way to guarantee that an object-o created from class-C has the features of the parent class. and vice versa, there is no guarantee that any object-o2 does not have the featuresof class-C. So why add a strong and official type checking method to class declaration?

Try next case:

class C {
  #x
  isC(o) {
    return true; // or other way: `#x in o`
  }
  aMethod() {
    // do somethig
  }
}

x = new C;
Object.setPrototypeOf(x, null)
x.isC = C.prototype.isC;

// I try a hard class brand check, say `yes`
x.isC(x); // true

// crash!
x.aMethod(); //  x is duck-type instance.

So, Does strong/hard class brank check have any meaning?

Forbidding references to undeclared privates

Continuing discussion from b798be4#r38277028:

I think what's actually necessary is to integrate with AllPrivateIdentifiersValid. That will prevent this case, and raise any unknown private name references during module evaluation (which currently only raises when the in expression evaluates).

This particular case mirrors the existing ones in the class fields proposal (one of which I included for context on line 41)

My point is that banning #constructor in {} seems unnecessary, due to the AllPrivateIdentifiersValid check already provided by Class Fields. But we haven't properly integrated with it. Eg, this is currently a valid program:

function foo() {
  if (false) {
    #undeclared in {};
  }
}

This program will parse just fine, and I can run foo without an error since we'll never evaluate the in expression. Once we add a AllPrivateIdentifiersValid static semantics, this program will fail to parse. And doing this will in turn forbid #constructor in {}, since #constructor can never be declared. It's then always invalid.

Path to Stage 4!

Stage 4

  • committee approval
  • class fields is stage 4
  • two implementations
  • significant in-the-field experience
  • ecma262 PR approved
  • prepare ecma262 PR (based on top of tc39/ecma262#1668)
  • merge test262 tests
  • write test262 tests (PR)

Stage 3

  • committee approval
  • spec editor signoff (@michaelficarra, since the other editors are a champion or stage 2 reviewers)
  • spec reviewer signoff
  • receive implementor feedback

Stage 2

Stage 1

  • committee approval

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.