Skip to content

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

Merged
nicolo-ribaudo merged 8 commits intobabel:feat-7.14.0/preset-envfrom
nicolo-ribaudo:private-in-to-native-private
Apr 21, 2021
Merged

Allow compiling #foo in obj without compiling private fields#13172
nicolo-ribaudo merged 8 commits intobabel:feat-7.14.0/preset-envfrom
nicolo-ribaudo:private-in-to-native-private

Conversation

@nicolo-ribaudo
Copy link
Copy Markdown
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
Copy Markdown

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Member Author

@nicolo-ribaudo nicolo-ribaudo Apr 19, 2021

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
Copy Markdown
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
Copy Markdown
Member Author

@nicolo-ribaudo nicolo-ribaudo Apr 19, 2021

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
Copy Markdown
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 nicolo-ribaudo force-pushed the feat-7.14.0/preset-env branch from 0c58c2e to 838211b Compare April 19, 2021 19:15
@nicolo-ribaudo nicolo-ribaudo force-pushed the private-in-to-native-private branch from 3d06565 to 6f7cfa8 Compare April 19, 2021 19:54
@nicolo-ribaudo
Copy link
Copy Markdown
Member Author

Fixed both the bugs!


const { loose, nativePrivateFields } = options;

if (nativePrivateFields) return pluginPrivateIn(api);
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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 nicolo-ribaudo force-pushed the feat-7.14.0/preset-env branch from 838211b to d7d910a Compare April 20, 2021 21:39
@nicolo-ribaudo nicolo-ribaudo force-pushed the private-in-to-native-private branch from c5d9896 to 7fcf7c4 Compare April 20, 2021 22:05
@nicolo-ribaudo
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

5 participants