Skip to content

Commit

Permalink
Class private properties (babel#7842)
Browse files Browse the repository at this point in the history
* Update member-expression-to-functions

1. Babel using British spellings, so `memoise`
2. Provide a helper `AssignmentMemoiser` class, which will assign the memo'd value with the `n`th access.

* Private properties!

* Fixes

* Tests

* Update helper name

* Fix privates that reference other privates

* Don't extend a builtin

* Rebase
  • Loading branch information
jridgewell authored May 18, 2018
1 parent 70eb206 commit 27c39c5
Show file tree
Hide file tree
Showing 177 changed files with 3,053 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const visitor = {

// The helper requires three special methods on state: `get`, `set`, and
// `call`.
// Optionally, a special `memoize` method may be defined, which gets
// Optionally, a special `memoise` method may be defined, which gets
// called if the member is in a self-referential update expression.
// Everything else will be passed through as normal.
const state = {
Expand Down Expand Up @@ -55,10 +55,10 @@ const state = {
);
},

memoize(memberPath) {
memoise(memberPath) {
const { node } = memberPath;
if (node.computed) {
MEMOIZED.set(node, ...);
MEMOISED.set(node, ...);
}
},

Expand Down
56 changes: 44 additions & 12 deletions packages/babel-helper-member-expression-to-functions/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,39 @@
import * as t from "@babel/types";

class AssignmentMemoiser {
constructor() {
this._map = new WeakMap();
}

has(key) {
return this._map.has(key);
}

get(key) {
if (!this.has(key)) return;

const record = this._map.get(key);
const { value } = record;

record.count--;
if (record.count === 0) {
// The `count` access is the outermost function call (hopefully), so it
// does the assignment.
return t.assignmentExpression("=", value, key);
}
return value;
}

set(key, value, count) {
return this._map.set(key, { count, value });
}
}

const handle = {
memoise() {
// noop.
},

handle(member) {
const { node, parent, parentPath } = member;

Expand All @@ -9,11 +42,10 @@ const handle = {
if (parentPath.isUpdateExpression({ argument: node })) {
const { operator, prefix } = parent;

// Give the state handler a chance to memoize the member,
// since we'll reference it twice.
if (this.memoize) {
this.memoize(member);
}
// Give the state handler a chance to memoise the member, since we'll
// reference it twice. The second access (the set) should do the memo
// assignment.
this.memoise(member, 2);

const value = t.binaryExpression(
operator[0],
Expand Down Expand Up @@ -44,11 +76,10 @@ const handle = {
let value = right;

if (operator !== "=") {
// Give the state handler a chance to memoize the member,
// since we'll reference it twice.
if (this.memoize) {
this.memoize(member);
}
// Give the state handler a chance to memoise the member, since we'll
// reference it twice. The second access (the set) should do the memo
// assignment.
this.memoise(member, 2);

value = t.binaryExpression(
operator.slice(0, -1),
Expand Down Expand Up @@ -79,11 +110,12 @@ const handle = {
// it wishes to be transformed.
// Additionally, the caller must pass in a state object with at least
// get, set, and call methods.
// Optionally, a memoize method may be defined on the state, which will be
// Optionally, a memoise method may be defined on the state, which will be
// called when the member is a self-referential update.
export default function memberExpressionToFunctions(path, visitor, state) {
path.traverse(visitor, {
...state,
...handle,
...state,
memoiser: new AssignmentMemoiser(),
});
}
77 changes: 29 additions & 48 deletions packages/babel-helper-replace-supers/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,8 @@ const visitor = traverse.visitors.merge([
},
]);

const memoized = new WeakMap();
const specHandlers = {
memoize(superMember) {
memoise(superMember, count) {
const { scope, node } = superMember;
const { computed, property } = node;
if (!computed) {
Expand All @@ -84,44 +83,34 @@ const specHandlers = {
return;
}

memoized.set(property, memo);
this.memoiser.set(property, memo, count);
},

get(superMember) {
prop(superMember) {
const { computed, property } = superMember.node;
const thisExpr = t.thisExpression();
if (this.memoiser.has(property)) {
return t.cloneNode(this.memoiser.get(property));
}

let prop;
if (computed && memoized.has(property)) {
prop = t.cloneNode(memoized.get(property));
} else {
prop = computed ? property : t.stringLiteral(property.name);
if (computed) {
return t.cloneNode(property);
}

return t.stringLiteral(property.name);
},

get(superMember) {
return t.callExpression(this.file.addHelper("get"), [
getPrototypeOfExpression(this.getObjectRef(), this.isStatic, this.file),
prop,
thisExpr,
this.prop(superMember),
t.thisExpression(),
]);
},

set(superMember, value) {
const { computed, property } = superMember.node;

let prop;
if (computed && memoized.has(property)) {
prop = t.assignmentExpression(
"=",
t.cloneNode(memoized.get(property)),
property,
);
} else {
prop = computed ? property : t.stringLiteral(property.name);
}

return t.callExpression(this.file.addHelper("set"), [
getPrototypeOfExpression(this.getObjectRef(), this.isStatic, this.file),
prop,
this.prop(superMember),
value,
t.thisExpression(),
t.booleanLiteral(superMember.isInStrictMode()),
Expand All @@ -134,12 +123,21 @@ const specHandlers = {
};

const looseHandlers = {
memoize: specHandlers.memoize,
call: specHandlers.call,
...specHandlers,

prop(superMember) {
const { property } = superMember.node;
if (this.memoiser.has(property)) {
return t.cloneNode(this.memoiser.get(property));
}

return t.cloneNode(property);
},

get(superMember) {
const { isStatic, superRef } = this;
const { property, computed } = superMember.node;
const { computed } = superMember.node;
const prop = this.prop(superMember);

let object;
if (isStatic) {
Expand All @@ -155,29 +153,12 @@ const looseHandlers = {
: t.memberExpression(t.identifier("Object"), t.identifier("prototype"));
}

let prop;
if (computed && memoized.has(property)) {
prop = t.cloneNode(memoized.get(property));
} else {
prop = property;
}

return t.memberExpression(object, prop, computed);
},

set(superMember, value) {
const { property, computed } = superMember.node;

let prop;
if (computed && memoized.has(property)) {
prop = t.assignmentExpression(
"=",
t.cloneNode(memoized.get(property)),
property,
);
} else {
prop = property;
}
const { computed } = superMember.node;
const prop = this.prop(superMember);

return t.assignmentExpression(
"=",
Expand Down
35 changes: 35 additions & 0 deletions packages/babel-helpers/src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -967,3 +967,38 @@ helpers.applyDecoratedDescriptor = () => template.program.ast`
return desc;
}
`;

helpers.classPrivateFieldLooseKey = () => template.program.ast`
var id = 0;
export default function _classPrivateFieldKey(name) {
return "__private_" + (id++) + "_" + name;
}
`;

helpers.classPrivateFieldLooseBase = () => template.program.ast`
export default function _classPrivateFieldBase(receiver, privateKey) {
if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) {
throw new TypeError("attempted to use private field on non-instance");
}
return receiver;
}
`;

helpers.classPrivateFieldGet = () => template.program.ast`
export default function _classPrivateFieldGet(receiver, privateMap) {
if (!privateMap.has(receiver)) {
throw new TypeError("attempted to get private field on non-instance");
}
return privateMap.get(receiver);
}
`;

helpers.classPrivateFieldSet = () => template.program.ast`
export default function _classPrivateFieldSet(receiver, privateMap, value) {
if (!privateMap.has(receiver)) {
throw new TypeError("attempted to set private field on non-instance");
}
privateMap.set(receiver, value);
return value;
}
`;
2 changes: 2 additions & 0 deletions packages/babel-plugin-proposal-class-properties/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
],
"dependencies": {
"@babel/helper-function-name": "7.0.0-beta.47",
"@babel/helper-member-expression-to-functions": "7.0.0-beta.47",
"@babel/helper-optimise-call-expression": "7.0.0-beta.47",
"@babel/helper-plugin-utils": "7.0.0-beta.47",
"@babel/helper-replace-supers": "7.0.0-beta.47",
"@babel/plugin-syntax-class-properties": "7.0.0-beta.47"
Expand Down
Loading

0 comments on commit 27c39c5

Please sign in to comment.