Skip to content
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

Allow compiling #foo in obj without compiling private fields #13172

Conversation

nicolo-ribaudo
Copy link
Member

Q                       A
Fixed Issues?
Patch: Bug Fix?
Major: Breaking Change?
Minor: New Feature? Yes
Tests Added + Pass? Yes
Documentation PR Link
Any Dependency Changes?
License MIT

Currently we throw an error when compiling private brand checks without compiling private fields. This makes it impossible to add this plugin to preset-env, since it will throw an error in browsers with private fields support but without brand checks support.

This PR adds a new option to compile private brand checks without relying on the private fields transform: preset-env can then enable this option when necessary.

@nicolo-ribaudo nicolo-ribaudo added PR: New Feature 🚀 A type of pull request used for our changelog categories Spec: Brand Check for Private Fields labels Apr 19, 2021
@nicolo-ribaudo nicolo-ribaudo changed the title Add class static blocks to preset-env's shippedProposals (#13114) Allow compiling #foo in obj without compiling private fields Apr 19, 2021
@codesandbox-ci
Copy link

codesandbox-ci bot commented Apr 19, 2021

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 7fcf7c4:

Sandbox Source
babel-repl-custom-plugin Configuration
babel-plugin-multi-config Configuration

@babel-bot
Copy link
Collaborator

babel-bot commented Apr 19, 2021

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/45465/

@@ -0,0 +1,14 @@
var _FooBrandCheck = new WeakSet();
Copy link
Contributor

@JLHwung JLHwung Apr 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also place FooBrandCheck to a private static field, e.g.

class Foo {
  private static #_FooBrandCheck = new WeakSet();
  constructor() {
    Foo.#_FooBrandCheck.add(this);
  }

  get #foo() {}

  test(other) {
    return Foo.#_FooBrandCheck.has(other);
  }
}

so we avoid the edge cases when FooBrandCheck might be injected to an incorrect scope, e.g. when a class is in the param initializer.

// <-- FooBrandCheck will be injected to here
(x = class { #foo; test (other) { return #foo in other } }) => {}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh I love this idea

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish class access were not stage-2. The class binding can be overwritten. Guess we may have to stick to temporary variable.

get #foo() {}

test(other) {
return _FooBrandCheck.has(other);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Is _FooBrandCheck.has(other) equivalent to other instanceof Foo?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's different when other is a subclass of Foo

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Bar, a subclass of Foo, has super() in its constructor, does it imply #foo in bar is true where bar is an instance of Bar?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I was confusing this test with the private fields one. instanceof is different because it checks the prototype:

class Base {
  constructor() {
    return {};
  }
}

class Foo extends Base {
  #foo;

  static check(obj) {
    return #foo in obj;
  }
}

new Foo() instanceof Foo; // false
Foo.check(new Foo); // true
class Foo {
  #foo;

  static check(obj) {
    return #foo in obj;
  }
}

let foo = new Foo();
foo.__proto__ = {};
foo instanceof Foo; // false
Foo.check(foo); // true
class Foo {
  #foo;

  static check(obj) {
    return #foo in obj;
  }
}

let foo = Object.create(Foo.prototype);
foo instanceof Foo; // true
Foo.check(foo); // false

If Bar, a subclass of Foo, has super() in its constructor, does it imply #foo in bar is true where bar is an instance of Bar?

If by "bar is an instance of Bar" you mean bar instanceof Bar then no, there isn't an implication in any direction.

Copy link
Member

@jridgewell jridgewell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're going to have ordering issues:

class Foo {
  #x = 1;
  y = #x in this;
}

assert.equal(new Foo().y, true);

@nicolo-ribaudo
Copy link
Member Author

Fixed both the bugs!

export default declare((api, options) => {
api.assertVersion(7);

const { loose, nativePrivateFields } = options;

if (nativePrivateFields) return pluginPrivateIn(api);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we default nativePrivateFields to !isRequire("proposal-private-methods", api.targets())?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't necessarily intended to be a public api right since we can just check targets? Or did we want to be able to specify that

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally every plugin should automatically compile as few as possible based on the targets, but users should still be able to override it via options.


function unshadow(name, targetScope, scope) {
while (scope !== targetScope) {
if (scope.hasOwnBinding(name)) scope.rename(name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be easier to rename the class, instead of all conflicting bindings?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'll leave this as-is: renaming a class is always observable (because it changes the .name), while renaming the other conflicts is often not observable (if we rename a variable). It can still be observable if the conflict is with a class/function, but at least it's not always.

);
} else {
const id = getWeakSetId(
classWeakSets,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
classWeakSets,
staticWeakSets,

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used for instance methods, but it's a single weakset for all the instance methods in the class.

@nicolo-ribaudo
Copy link
Member Author

@JLHwung Rather than using targets, I realized that we can always use the new implementation (bf01056). All the tests in https://github.com/nicolo-ribaudo/babel/tree/private-in-to-native-private/packages/babel-plugin-proposal-private-property-in-object/test/fixtures/private ensure that we keep the old behavior when compiling private fields.

@JLHwung
Copy link
Contributor

JLHwung commented Apr 21, 2021

@nicolo-ribaudo Does the new implementation increase the output size when all class features are compiled? If so I would prefer to use it only when private elements are natively supported.

@nicolo-ribaudo
Copy link
Member Author

nicolo-ribaudo commented Apr 21, 2021

When all the class features are compiled we match the old output, because it's handled by the class features helper enabled by one of the other plugins. You can see that bf01056 didn't change any test, except for removing an error.

@nicolo-ribaudo nicolo-ribaudo merged commit 6e9c515 into babel:feat-7.14.0/preset-env Apr 21, 2021
@nicolo-ribaudo nicolo-ribaudo deleted the private-in-to-native-private branch April 21, 2021 13:46
@github-actions github-actions bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Jul 22, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 22, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
outdated A closed issue/PR that is archived due to age. Recommended to make a new issue PR: New Feature 🚀 A type of pull request used for our changelog categories Spec: Brand Check for Private Fields
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants