New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static Class Features: Stage 3 #8052

Open
rricard opened this Issue May 25, 2018 · 8 comments

Comments

Projects
None yet
6 participants
@rricard
Contributor

rricard commented May 25, 2018

Static Class Features: Stage 3

Advance Static Class Features for Stage 3 (Shu-yu Guo @syg) (Daniel Ehrenberg @littledan)

Proposal co-written with @tim-mc and @robpalme. We also intend to follow up and implement it with @rpamely, @tim-mc, @mkubilayk .

Info

Proposed at TC39 Meeting: May 2018

Slides at the meeting: https://docs.google.com/presentation/d/1YzFr7EIGiX2YagfFMjkI-lVR6ouoRfPbTNLY--NGbC4/edit?usp=sharing

Proposal Repo: https://github.com/tc39/proposal-static-class-features/

Complete article on the subject by Shu-yu Guo: https://rfrn.org/%7Eshu/2018/05/02/the-semantics-of-all-js-class-elements.html

Babel contribution recommended by @jridgewell to @robpalme

The work to be done towards Stage 3 will be added in plugin-proposal-class-properties.

Examples

Static Public Fields

class Account {
  static routingNumber = 1017;
  get transactionIdPrefix() {
    return `${Account.routingNumber}-${this.uuid}`;
  }
}

Static Public Methods

class Account {
  static convertToEuro(dollars) {
     return util.USDToEuro(dollars);
  }

  get euroConversionDisplayAmt () {
    return `Current Balance (EUR): ${Account.convertToEuro(this.accountBalance)}`
  }

Static private fields

class Account {
  static #transactions = [];
  static get nbTransactions() {
    return Account.#transactions.length;
  }
  getDollars() {
    return Account.#transactions.reduce(/* ... */);
  }
}

Static private methods

class Account {
   // ...
  static #makeTransaction(dollars, from, to) {
    Account.#transactions = this.#transactions.concat(/* ... */);
  }
  
  transfer(dollars, targetAccount) {
    return Account.#makeTransaction(dollars, this, targetAccount);
  }
}

Basic Rules

  • Check that the <Class Name>.#<static private field/method> notation is within the lexical scope of <Class Name> - Checked during transform
  • Check that the this.#<static private field/method> has a provenance of <Class Name> - See Subclassing Hazard in the slides (around slide 13) - Checked at runtime

ESTree/Parsing

  • For the static keyword, we use the existing parser
  • For the # character, we use (or enhance) the existing parser (thanks to the work done in #7842 - Class private properties)

Transform

Public

  • Public static fields/methods will be added to the <Class Name> object
  • Accessors will be added using Object.assign

Private

  • Transform to use helper functions similar to private instance fields: classStaticPrivateFieldGet and classStaticPrivateFieldSet
  • Add an in-closure _<static private fields/method> variable outside of the class that will be manipulated by the helper functions
  • Here is a potential output of the two last examples put together, in order to explain how to achieve privacy:
var Account =
/*#__PURE__*/
function () {
  "use strict";

  function Account() {}

  babelHelpers.createClass(Account, [{
    key: "transfer",
    value: function transfer(dollars, targetAccount) {
      babelHelpers.classStaticPrivateFieldGet(Account, _makeTransaction)(dollars, targetAccount);
    }
  }, {
    key: "getDollars",
    value: function getDollars() {
      babelHelpers.classStaticPrivateFieldGet(Account, _transactions).reduce(/* ... */);
    }
  }]);
  
  Object.assign(Account, {
    get nbTransactions() {
      return babelHelpers.classStaticPrivateFieldGet(Account, _transactions).length;
    }
  });

  var _makeTransaction = function(dollars, from, to) {
    babelHelpers.classStaticPrivateFieldSet(Account, _transactions, babelHelpers.classStaticPrivateFieldGet(this, _transactions).concat(/* ... */));
  };

  var _transactions = [];

  return Account;
}();

Note that those implementation details are not final but a way to start the conversation and the actual implementation.

cc @jridgewell, @robpalme, @rpamely, @tim-mc, @mkubilayk

@babel-bot

This comment has been minimized.

Show comment
Hide comment
@babel-bot

babel-bot May 25, 2018

Collaborator

Hey @rricard! We really appreciate you taking the time to report an issue. The collaborators
on this project attempt to help as many people as possible, but we're a limited number of volunteers,
so it's possible this won't be addressed swiftly.

If you need any help, or just have general Babel or JavaScript questions, we have a vibrant Slack
community that typically always has someone willing to help. You can sign-up here
for an invite.

Collaborator

babel-bot commented May 25, 2018

Hey @rricard! We really appreciate you taking the time to report an issue. The collaborators
on this project attempt to help as many people as possible, but we're a limited number of volunteers,
so it's possible this won't be addressed swiftly.

If you need any help, or just have general Babel or JavaScript questions, we have a vibrant Slack
community that typically always has someone willing to help. You can sign-up here
for an invite.

@jridgewell

This comment has been minimized.

Show comment
Hide comment
@jridgewell

jridgewell May 25, 2018

Member

Thanks for the write up!

A few notes:

For the # character, we use (or enhance) the existing parser

This we should already parse everything, including private methods/accessors. We just don't yet have a transformer for them.

Accessors will be added using Object.assign

Babel already has the helpers to assign public static accessors using createClass's third param. In fact, all transforms necessary for public fields/methods/accessors should already be done.

If you can find any non-spec behavior, though, please correct it!

Here is a potential output of the two last examples put together, in order to explain how to achieve privacy:

Your output looks great. I just want to confirm that the static private helpers will be using strict equality on the class (and should be using a const reference to the class). This is a neat optimization we can make for them, avoiding the WeakMaps necessary for instance privates.


For the design of private methods/accessors, I had originally thought we could fall back to the fields behavior without any issues. But this won't account for accessors, which which is difficult to handle correctly. We'd have to store them as some object with get/set keys, and call them appropriately:

const _accessor  = {
  get() {
    // user's getter code
  },
  set(v) {
    // user's setter code
  }
};

_accessor.get.call(ClassName)
Member

jridgewell commented May 25, 2018

Thanks for the write up!

A few notes:

For the # character, we use (or enhance) the existing parser

This we should already parse everything, including private methods/accessors. We just don't yet have a transformer for them.

Accessors will be added using Object.assign

Babel already has the helpers to assign public static accessors using createClass's third param. In fact, all transforms necessary for public fields/methods/accessors should already be done.

If you can find any non-spec behavior, though, please correct it!

Here is a potential output of the two last examples put together, in order to explain how to achieve privacy:

Your output looks great. I just want to confirm that the static private helpers will be using strict equality on the class (and should be using a const reference to the class). This is a neat optimization we can make for them, avoiding the WeakMaps necessary for instance privates.


For the design of private methods/accessors, I had originally thought we could fall back to the fields behavior without any issues. But this won't account for accessors, which which is difficult to handle correctly. We'd have to store them as some object with get/set keys, and call them appropriately:

const _accessor  = {
  get() {
    // user's getter code
  },
  set(v) {
    // user's setter code
  }
};

_accessor.get.call(ClassName)
@rricard

This comment has been minimized.

Show comment
Hide comment
@rricard

rricard May 25, 2018

Contributor

Thank you for the information, that will help us a lot. I'll start fiddling around with an implementation this weekend.

Contributor

rricard commented May 25, 2018

Thank you for the information, that will help us a lot. I'll start fiddling around with an implementation this weekend.

@hzoo

This comment has been minimized.

Show comment
Hide comment
@hzoo

hzoo May 25, 2018

Member

I usually post this in babel/proposals#12 but all good since we are already about implementing at this point!

Member

hzoo commented May 25, 2018

I usually post this in babel/proposals#12 but all good since we are already about implementing at this point!

@rricard

This comment has been minimized.

Show comment
Hide comment
@rricard

rricard May 29, 2018

Contributor

So I have a work in progress here: master...rricard:static-class-feature-stage-3
I know what to work on but I still have a few questions:

  • What would be a loose version of the code I'm supposed to generate here? I'm targeting a spec implementation first but do I even need to do the loose one ?
  • I have to admit I'm a bit confused on what's missing on the public static implementation of this whole thing. If someone catched a significant thing that differs from babel's public static and the spec, please tell me because I think I didn't get it.

Here's what next on my roadmap:

  • Helpers to handle this.#<static field> accesses
  • Store static private in an object alongside the class
  • Private static setters/getters
  • Update tests
  • Account for a spec version
  • Refactor
  • Add it to stage 3 preset (could be an independent PR)

cc @robpalme, @jridgewell

Contributor

rricard commented May 29, 2018

So I have a work in progress here: master...rricard:static-class-feature-stage-3
I know what to work on but I still have a few questions:

  • What would be a loose version of the code I'm supposed to generate here? I'm targeting a spec implementation first but do I even need to do the loose one ?
  • I have to admit I'm a bit confused on what's missing on the public static implementation of this whole thing. If someone catched a significant thing that differs from babel's public static and the spec, please tell me because I think I didn't get it.

Here's what next on my roadmap:

  • Helpers to handle this.#<static field> accesses
  • Store static private in an object alongside the class
  • Private static setters/getters
  • Update tests
  • Account for a spec version
  • Refactor
  • Add it to stage 3 preset (could be an independent PR)

cc @robpalme, @jridgewell

@rricard

This comment has been minimized.

Show comment
Hide comment
@rricard

rricard Jun 20, 2018

Contributor

I opened #8205 for early feedback. Keep in mind that this is far from finished (see prev. comment) but it gives an idea of the architectural choices made and we can discuss them there before going further. Next goal is to be able to run that against test-262 and make sure it passes for that feature (and backport some missing tests in babel from 262)

Contributor

rricard commented Jun 20, 2018

I opened #8205 for early feedback. Keep in mind that this is far from finished (see prev. comment) but it gives an idea of the architectural choices made and we can discuss them there before going further. Next goal is to be able to run that against test-262 and make sure it passes for that feature (and backport some missing tests in babel from 262)

@nikolay

This comment has been minimized.

Show comment
Hide comment
@nikolay

nikolay Aug 28, 2018

The semantics of # are quite far from 'private'. I recommend using another character (for example, ~) or better yet - the private modifier. Is many language the convetion is using leading underscores - I don't get why this has to be a different character!

nikolay commented Aug 28, 2018

The semantics of # are quite far from 'private'. I recommend using another character (for example, ~) or better yet - the private modifier. Is many language the convetion is using leading underscores - I don't get why this has to be a different character!

@nicolo-ribaudo

This comment has been minimized.

Show comment
Hide comment
@nicolo-ribaudo

nicolo-ribaudo Aug 28, 2018

Member

You can find many discussions about it at https://github.com/tc39/proposal-class-fields

Member

nicolo-ribaudo commented Aug 28, 2018

You can find many discussions about it at https://github.com/tc39/proposal-class-fields

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment