Skip to content

Commit

Permalink
Class Private Static Accessors (#10217)
Browse files Browse the repository at this point in the history
* Remove error for static private accessors

* Private static accessors strict

* Add loose mode support

* Move `value` decl for early return

* Reuse getter/setter template

* Reuse getter/setter templates

* Use `buildUndefinedNode` in accessor templates

* Extract `isAccessor` variable
  • Loading branch information
tim-mc authored and nicolo-ribaudo committed Sep 6, 2019
1 parent da0af5f commit 3e4889d
Show file tree
Hide file tree
Showing 35 changed files with 749 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,6 @@ export function verifyUsedFeatures(path, file) {
if (!hasFeature(file, FEATURES.privateMethods)) {
throw path.buildCodeFrameError("Class private methods are not enabled.");
}

if (path.node.static && path.node.kind !== "method") {
throw path.buildCodeFrameError(
"@babel/plugin-class-features doesn't support class static private accessors yet.",
);
}
}

if (
Expand Down
154 changes: 87 additions & 67 deletions packages/babel-helper-create-class-features-plugin/src/fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,15 @@ export function buildPrivateNamesNodes(privateNamesMap, loose, state) {
// because static fields are directly assigned to a variable in the
// buildPrivateStaticFieldInitSpec function.
const { id, static: isStatic, method: isMethod, getId, setId } = value;
const isAccessor = getId || setId;
if (loose) {
initNodes.push(
template.statement.ast`
var ${id} = ${state.addHelper("classPrivateFieldLooseKey")}("${name}")
`,
);
} else if (isMethod && !isStatic) {
if (getId || setId) {
if (isAccessor) {
initNodes.push(template.statement.ast`var ${id} = new WeakMap();`);
} else {
initNodes.push(template.statement.ast`var ${id} = new WeakSet();`);
Expand Down Expand Up @@ -140,11 +141,13 @@ const privateNameHandlerSpec = {
getId,
setId,
} = privateNamesMap.get(name);
const isAccessor = getId || setId;

if (isStatic) {
const helperName = isMethod
? "classStaticPrivateMethodGet"
: "classStaticPrivateFieldSpecGet";
const helperName =
isMethod && !isAccessor
? "classStaticPrivateMethodGet"
: "classStaticPrivateFieldSpecGet";

return t.callExpression(file.addHelper(helperName), [
this.receiver(member),
Expand All @@ -154,7 +157,7 @@ const privateNameHandlerSpec = {
}

if (isMethod) {
if (getId || setId) {
if (isAccessor) {
return t.callExpression(file.addHelper("classPrivateFieldGet"), [
this.receiver(member),
t.cloneNode(id),
Expand All @@ -180,12 +183,15 @@ const privateNameHandlerSpec = {
static: isStatic,
method: isMethod,
setId,
getId,
} = privateNamesMap.get(name);
const isAccessor = getId || setId;

if (isStatic) {
const helperName = isMethod
? "classStaticPrivateMethodSet"
: "classStaticPrivateFieldSpecSet";
const helperName =
isMethod && !isAccessor
? "classStaticPrivateMethodSet"
: "classStaticPrivateFieldSpecSet";

return t.callExpression(file.addHelper(helperName), [
this.receiver(member),
Expand Down Expand Up @@ -300,9 +306,30 @@ function buildPrivateInstanceFieldInitSpec(ref, prop, privateNamesMap) {
}

function buildPrivateStaticFieldInitSpec(prop, privateNamesMap) {
const { id } = privateNamesMap.get(prop.node.key.id.name);
const value = prop.node.value || prop.scope.buildUndefinedNode();
const privateName = privateNamesMap.get(prop.node.key.id.name);
const { id, getId, setId, initAdded } = privateName;
const isAccessor = getId || setId;

if (!prop.isProperty() && (initAdded || !isAccessor)) return;

if (isAccessor) {
privateNamesMap.set(prop.node.key.id.name, {
...privateName,
initAdded: true,
});

return template.statement.ast`
var ${id.name} = {
// configurable is false by default
// enumerable is false by default
// writable is false by default
get: ${getId ? getId.name : prop.scope.buildUndefinedNode()},
set: ${setId ? setId.name : prop.scope.buildUndefinedNode()}
}
`;
}

const value = prop.node.value || prop.scope.buildUndefinedNode();
return template.statement.ast`
var ${id} = {
// configurable is false by default
Expand All @@ -328,76 +355,44 @@ function buildPrivateMethodInitLoose(ref, prop, privateNamesMap) {
});
`;
}

if (getId || setId) {
const isAccessor = getId || setId;
if (isAccessor) {
privateNamesMap.set(prop.node.key.id.name, {
...privateName,
initAdded: true,
});

if (getId && setId) {
return template.statement.ast`
Object.defineProperty(${ref}, ${id}, {
// configurable is false by default
// enumerable is false by default
// writable is false by default
get: ${getId.name},
set: ${setId.name}
});
`;
} else if (getId && !setId) {
return template.statement.ast`
Object.defineProperty(${ref}, ${id}, {
// configurable is false by default
// enumerable is false by default
// writable is false by default
get: ${getId.name}
});
`;
} else if (!getId && setId) {
return template.statement.ast`
Object.defineProperty(${ref}, ${id}, {
// configurable is false by default
// enumerable is false by default
// writable is false by default
set: ${setId.name}
});
`;
}
return template.statement.ast`
Object.defineProperty(${ref}, ${id}, {
// configurable is false by default
// enumerable is false by default
// writable is false by default
get: ${getId ? getId.name : prop.scope.buildUndefinedNode()},
set: ${setId ? setId.name : prop.scope.buildUndefinedNode()}
});
`;
}
}

function buildPrivateInstanceMethodInitSpec(ref, prop, privateNamesMap) {
const privateName = privateNamesMap.get(prop.node.key.id.name);
const { id, getId, setId, initAdded } = privateName;

if (initAdded) return;

if (getId || setId) {
const isAccessor = getId || setId;
if (isAccessor) {
privateNamesMap.set(prop.node.key.id.name, {
...privateName,
initAdded: true,
});

if (getId && setId) {
return template.statement.ast`
${id}.set(${ref}, {
get: ${getId.name},
set: ${setId.name}
});
`;
} else if (getId && !setId) {
return template.statement.ast`
${id}.set(${ref}, {
get: ${getId.name}
});
`;
} else if (!getId && setId) {
return template.statement.ast`
${id}.set(${ref}, {
set: ${setId.name}
});
`;
}
return template.statement.ast`
${id}.set(${ref}, {
get: ${getId ? getId.name : prop.scope.buildUndefinedNode()},
set: ${setId ? setId.name : prop.scope.buildUndefinedNode()}
});
`;
}
return template.statement.ast`${id}.add(${ref})`;
}
Expand Down Expand Up @@ -429,7 +424,29 @@ function buildPublicFieldInitSpec(ref, prop, state) {
}

function buildPrivateStaticMethodInitLoose(ref, prop, state, privateNamesMap) {
const { id, methodId } = privateNamesMap.get(prop.node.key.id.name);
const privateName = privateNamesMap.get(prop.node.key.id.name);
const { id, methodId, getId, setId, initAdded } = privateName;

if (initAdded) return;

const isAccessor = getId || setId;
if (isAccessor) {
privateNamesMap.set(prop.node.key.id.name, {
...privateName,
initAdded: true,
});

return template.statement.ast`
Object.defineProperty(${ref}, ${id}, {
// configurable is false by default
// enumerable is false by default
// writable is false by default
get: ${getId ? getId.name : prop.scope.buildUndefinedNode()},
set: ${setId ? setId.name : prop.scope.buildUndefinedNode()}
})
`;
}

return template.statement.ast`
Object.defineProperty(${ref}, ${id}, {
// configurable is false by default
Expand Down Expand Up @@ -615,14 +632,14 @@ export function buildFieldsInitNodes(
case isStatic && isPrivate && isMethod && !loose:
needsClassRef = true;
staticNodes.push(
buildPrivateStaticFieldInitSpec(prop, privateNamesMap),
);
staticNodes.unshift(
buildPrivateMethodDeclaration(prop, privateNamesMap, loose),
);
break;
case isStatic && isPrivate && isMethod && loose:
needsClassRef = true;
staticNodes.push(
buildPrivateMethodDeclaration(prop, privateNamesMap, loose),
);
staticNodes.push(
buildPrivateStaticMethodInitLoose(
t.cloneNode(ref),
Expand All @@ -631,6 +648,9 @@ export function buildFieldsInitNodes(
privateNamesMap,
),
);
staticNodes.unshift(
buildPrivateMethodDeclaration(prop, privateNamesMap, loose),
);
break;
case isInstance && isPublic && isField && loose:
instanceNodes.push(buildPublicFieldInitLoose(t.thisExpression(), prop));
Expand All @@ -646,7 +666,7 @@ export function buildFieldsInitNodes(
}

return {
staticNodes,
staticNodes: staticNodes.filter(Boolean),
instanceNodes: instanceNodes.filter(Boolean),
wrapClass(path) {
for (const prop of props) {
Expand Down
20 changes: 14 additions & 6 deletions packages/babel-helpers/src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,9 @@ helpers.classStaticPrivateFieldSpecGet = helper("7.0.2")`
if (receiver !== classConstructor) {
throw new TypeError("Private static access of wrong provenance");
}
if (descriptor.get) {
return descriptor.get.call(receiver);
}
return descriptor.value;
}
`;
Expand All @@ -1186,13 +1189,18 @@ helpers.classStaticPrivateFieldSpecSet = helper("7.0.2")`
if (receiver !== classConstructor) {
throw new TypeError("Private static access of wrong provenance");
}
if (!descriptor.writable) {
// This should only throw in strict mode, but class bodies are
// always strict and private fields can only be used inside
// class bodies.
throw new TypeError("attempted to set read only private field");
if (descriptor.set) {
descriptor.set.call(receiver, value);
} else {
if (!descriptor.writable) {
// This should only throw in strict mode, but class bodies are
// always strict and private fields can only be used inside
// class bodies.
throw new TypeError("attempted to set read only private field");
}
descriptor.value = value;
}
descriptor.value = value;
return value;
}
`;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class Cl {
constructor() {
Object.defineProperty(this, _privateFieldValue, {
get: void 0,
set: _set_privateFieldValue
});
Object.defineProperty(this, _privateField, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
class Cl {
constructor() {
Object.defineProperty(this, _privateFieldValue, {
get: _get_privateFieldValue
get: _get_privateFieldValue,
set: void 0
});
Object.defineProperty(this, _privateField, {
writable: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class Cl {
constructor() {
_privateFieldValue.set(this, {
get: void 0,
set: _set_privateFieldValue
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
class Cl {
constructor() {
_privateFieldValue.set(this, {
get: _get_privateFieldValue
get: _get_privateFieldValue,
set: void 0
});

_privateField.set(this, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,17 @@ var _getA = babelHelpers.classPrivateFieldLooseKey("getA");

var _getB = babelHelpers.classPrivateFieldLooseKey("getB");

var _getB2 = function _getB2() {
return this.b;
};

var _getA2 = function _getA2() {
return A.a;
};

Object.defineProperty(B, _getA, {
value: _getA2
});

var _getB2 = function _getB2() {
return this.b;
};

Object.defineProperty(B, _getB, {
value: _getB2
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ class B extends A {

}

var _getA = function _getA() {
return babelHelpers.get(babelHelpers.getPrototypeOf(B), "a", this);
};

var _getB = function _getB() {
return this.b;
};

var _getA = function _getA() {
return babelHelpers.get(babelHelpers.getPrototypeOf(B), "a", this);
};

var [getA, getB] = B.extract();
Loading

0 comments on commit 3e4889d

Please sign in to comment.