[static private] Use explicit descriptors instead of an object (#8620)

This is similar to ec69b4bb1256c061ac76f53dfed09c4283ec6a31, which
was about private instance fields.

Private properties can be non-writable (thanks to decorators), or have
get/set accessors. If we stored this information on the `privateClass`
object, we would need to always use `Object.getOwnPropertyDescriptor`
before reading or writing a property because accessors need to be called
with the correct `this` context (it should be the actual class, not the
object hat stores the private properties). This commit simplifies that
operation a bit by removing the container object.

It also have another advantage, which instance fields already have
thanks to the use of separate weakmaps: unused private static fields
can be tree-shaken away or garbage-collected, while properties of an
object can't. Also, they can be easilier minified.
This commit is contained in:
Nicolò Ribaudo 2018-09-05 15:08:40 +02:00 committed by GitHub
parent f6643d1804
commit c5279eeca4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 136 additions and 186 deletions

View File

@ -1055,24 +1055,26 @@ helpers.classPrivateFieldSet = helper("7.0.0-beta.0")`
`; `;
helpers.classStaticPrivateFieldSpecGet = helper("7.0.1")` helpers.classStaticPrivateFieldSpecGet = helper("7.0.1")`
export default function _classStaticPrivateFieldSpecGet( export default function _classStaticPrivateFieldSpecGet(receiver, classConstructor, descriptor) {
receiver, classConstructor, privateClass, privateId
) {
if (receiver !== classConstructor) { if (receiver !== classConstructor) {
throw new TypeError("Private static access of wrong provenance"); throw new TypeError("Private static access of wrong provenance");
} }
return privateClass[privateId]; return descriptor.value;
} }
`; `;
helpers.classStaticPrivateFieldSpecSet = helper("7.0.1")` helpers.classStaticPrivateFieldSpecSet = helper("7.0.1")`
export default function _classStaticPrivateFieldSpecSet( export default function _classStaticPrivateFieldSpecSet(receiver, classConstructor, descriptor, value) {
receiver, classConstructor, privateClass, privateId, value
) {
if (receiver !== classConstructor) { if (receiver !== classConstructor) {
throw new TypeError("Private static access of wrong provenance"); throw new TypeError("Private static access of wrong provenance");
} }
privateClass[privateId] = value; 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;
return value; return value;
} }
`; `;

View File

@ -164,40 +164,27 @@ export default declare((api, options) => {
...privateNameHandlerSpec, ...privateNameHandlerSpec,
get(member) { get(member) {
const { file, name, privateClassId, classRef } = this; const { file, privateId, classRef } = this;
return t.callExpression( return t.callExpression(
file.addHelper("classStaticPrivateFieldSpecGet"), file.addHelper("classStaticPrivateFieldSpecGet"),
[ [this.receiver(member), t.cloneNode(classRef), t.cloneNode(privateId)],
this.receiver(member),
classRef,
privateClassId,
t.stringLiteral(name),
],
); );
}, },
set(member, value) { set(member, value) {
const { file, name, privateClassId, classRef } = this; const { file, privateId, classRef } = this;
return t.callExpression( return t.callExpression(
file.addHelper("classStaticPrivateFieldSpecSet"), file.addHelper("classStaticPrivateFieldSpecSet"),
[ [
this.receiver(member), this.receiver(member),
classRef, t.cloneNode(classRef),
privateClassId, t.cloneNode(privateId),
t.stringLiteral(name),
value, value,
], ],
); );
}, },
call(member, args) {
// The first access (the get) should do the memo assignment.
this.memoise(member, 1);
return optimiseCall(this.get(member), this.receiver(member), args);
},
}; };
function buildClassPropertySpec(ref, path, state) { function buildClassPropertySpec(ref, path, state) {
@ -308,50 +295,32 @@ export default declare((api, options) => {
state, state,
); );
const staticNodesToAdd = [keyDecl, buildInit()]; return [keyDecl, buildInit()];
return [staticNodesToAdd];
} }
function buildClassStaticPrivatePropertySpec( function buildClassStaticPrivatePropertySpec(ref, path, state) {
ref, const { parentPath, scope } = path;
path, const { name } = path.node.key.id;
state,
privateClassId,
) {
const { scope, parentPath } = path;
const { key, value } = path.node;
const { name } = key.id;
const staticNodesToAdd = [];
if (!privateClassId) {
// Create a private static "host" object if it does not exist
privateClassId = path.scope.generateUidIdentifier(ref.name + "Statics");
staticNodesToAdd.push(
template.statement`const PRIVATE_CLASS_ID = Object.create(null);`({
PRIVATE_CLASS_ID: privateClassId,
}),
);
}
const privateId = scope.generateUidIdentifier(name);
memberExpressionToFunctions(parentPath, privateNameVisitor, { memberExpressionToFunctions(parentPath, privateNameVisitor, {
name, name,
privateClassId, privateId,
classRef: ref, classRef: ref,
file: state, file: state,
...staticPrivatePropertyHandlerSpec, ...staticPrivatePropertyHandlerSpec,
}); });
staticNodesToAdd.push( return [
t.expressionStatement( template.statement.ast`
t.callExpression(state.addHelper("defineProperty"), [ var ${privateId} = {
privateClassId, // configurable is always false for private elements
t.stringLiteral(name), // enumerable is always false for private elements
value || scope.buildUndefinedNode(), writable: true,
]), value: ${path.node.value || scope.buildUndefinedNode()}
), }
); `,
];
return [staticNodesToAdd, privateClassId];
} }
const buildClassProperty = loose const buildClassProperty = loose
@ -461,21 +430,16 @@ export default declare((api, options) => {
} }
} }
let p = 0; let p = 0;
let privateClassId;
for (const prop of props) { for (const prop of props) {
if (prop.node.static) { if (prop.node.static) {
if (prop.isPrivate()) { if (prop.isPrivate()) {
let staticNodesToAdd; staticNodes.push(
[ ...buildClassStaticPrivateProperty(
staticNodesToAdd, t.cloneNode(ref),
privateClassId, prop,
] = buildClassStaticPrivateProperty( state,
t.cloneNode(ref), ),
prop,
state,
privateClassId,
); );
staticNodes.push(...staticNodesToAdd);
} else { } else {
staticNodes.push( staticNodes.push(
buildClassProperty(t.cloneNode(ref), prop, state), buildClassProperty(t.cloneNode(ref), prop, state),

View File

@ -7,7 +7,7 @@ class Foo {
} }
static test() { static test() {
return babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _FooStatics, "foo"); return babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _foo);
} }
test() { test() {
@ -16,8 +16,9 @@ class Foo {
} }
var _FooStatics = Object.create(null); var _foo = {
writable: true,
babelHelpers.defineProperty(_FooStatics, "foo", "foo"); value: "foo"
};
var _bar = new WeakMap(); var _bar = new WeakMap();

View File

@ -1,20 +1,16 @@
export default (param => { export default (param => {
var _class, _temp; var _class, _temp, _props;
return function () { return _temp = _class = class App {
_temp = _class = class App { getParam() {
getParam() { return param;
return param; }
}
}; }, _props = {
writable: true,
var _classStatics = Object.create(null); value: {
babelHelpers.defineProperty(_classStatics, "props", {
prop1: 'prop1', prop1: 'prop1',
prop2: 'prop2' prop2: 'prop2'
}); }
return _temp; }, _temp;
}();
}); });

View File

@ -1,38 +1,32 @@
function classFactory() { function classFactory() {
var _class, _temp; var _class, _temp, _foo, _bar;
return function () { return _temp = _class = class Foo {
_temp = _class = class Foo { constructor() {
constructor() { _foo.set(this, {
_foo.set(this, { writable: true,
writable: true, value: "foo"
value: "foo" });
}); }
}
instance() { instance() {
return babelHelpers.classPrivateFieldGet(this, _foo); return babelHelpers.classPrivateFieldGet(this, _foo);
} }
static() { static() {
return babelHelpers.classStaticPrivateFieldSpecGet(Foo, _class, _classStatics, "bar"); return babelHelpers.classStaticPrivateFieldSpecGet(Foo, _class, _bar);
} }
static instance(inst) { static instance(inst) {
return babelHelpers.classPrivateFieldGet(inst, _foo); return babelHelpers.classPrivateFieldGet(inst, _foo);
} }
static static() { static static() {
return babelHelpers.classStaticPrivateFieldSpecGet(Foo, _class, _classStatics, "bar"); return babelHelpers.classStaticPrivateFieldSpecGet(Foo, _class, _bar);
} }
}; }, _foo = new WeakMap(), _bar = {
writable: true,
var _foo = new WeakMap(); value: "bar"
}, _temp;
var _classStatics = Object.create(null);
babelHelpers.defineProperty(_classStatics, "bar", "bar");
return _temp;
}();
} }

View File

@ -1,16 +1,12 @@
var _class, _temp; var _class, _temp, _test;
call(function () { call((_temp = _class = class {}, _test = {
_temp = _class = class {}; writable: true,
value: true
var _classStatics = Object.create(null); }, _temp));
babelHelpers.defineProperty(_classStatics, "test", true);
return _temp;
}());
export default class _class2 {} export default class _class2 {}
var _test2 = {
var _class2Statics = Object.create(null); writable: true,
value: true
babelHelpers.defineProperty(_class2Statics, "test", true); };
; ;

View File

@ -1,18 +1,14 @@
function withContext(ComposedComponent) { function withContext(ComposedComponent) {
var _class, _temp; var _class, _temp, _propTypes;
return function () { return _temp = _class = class WithContext extends Component {}, _propTypes = {
_temp = _class = class WithContext extends Component {}; writable: true,
value: {
var _classStatics = Object.create(null);
babelHelpers.defineProperty(_classStatics, "propTypes", {
context: PropTypes.shape({ context: PropTypes.shape({
addCss: PropTypes.func, addCss: PropTypes.func,
setTitle: PropTypes.func, setTitle: PropTypes.func,
setMeta: PropTypes.func setMeta: PropTypes.func
}) })
}); }
return _temp; }, _temp;
}();
} }

View File

@ -10,14 +10,15 @@ function () {
babelHelpers.createClass(Foo, [{ babelHelpers.createClass(Foo, [{
key: "test", key: "test",
value: function test(x) { value: function test(x) {
return babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _FooStatics, "foo").call(Foo, x); return babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _foo).call(Foo, x);
} }
}]); }]);
return Foo; return Foo;
}(); }();
var _FooStatics = Object.create(null); var _foo = {
writable: true,
babelHelpers.defineProperty(_FooStatics, "foo", function (x) { value: function (x) {
return x; return x;
}); }
};

View File

@ -1,10 +1,10 @@
export class MyClass {} export class MyClass {}
var _property = {
var _MyClassStatics = Object.create(null); writable: true,
value: value
babelHelpers.defineProperty(_MyClassStatics, "property", value); };
export default class MyClass2 {} export default class MyClass2 {}
var _property2 = {
var _MyClass2Statics = Object.create(null); writable: true,
value: value
babelHelpers.defineProperty(_MyClass2Statics, "property", value); };

View File

@ -1,10 +1,6 @@
var _class, _temp; var _class, _temp, _num;
var Foo = function () { var Foo = (_temp = _class = class Foo {}, _num = {
_temp = _class = class Foo {}; writable: true,
value: 0
var _classStatics = Object.create(null); }, _temp);
babelHelpers.defineProperty(_classStatics, "num", 0);
return _temp;
}();

View File

@ -1,35 +1,37 @@
class Base { class Base {
static getThis() { static getThis() {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Base, _BaseStatics, "foo"); return babelHelpers.classStaticPrivateFieldSpecGet(this, Base, _foo);
} }
static updateThis(val) { static updateThis(val) {
return babelHelpers.classStaticPrivateFieldSpecSet(this, Base, _BaseStatics, "foo", val); return babelHelpers.classStaticPrivateFieldSpecSet(this, Base, _foo, val);
} }
static getClass() { static getClass() {
return babelHelpers.classStaticPrivateFieldSpecGet(Base, Base, _BaseStatics, "foo"); return babelHelpers.classStaticPrivateFieldSpecGet(Base, Base, _foo);
} }
static updateClass(val) { static updateClass(val) {
return babelHelpers.classStaticPrivateFieldSpecSet(Base, Base, _BaseStatics, "foo", val); return babelHelpers.classStaticPrivateFieldSpecSet(Base, Base, _foo, val);
} }
} }
var _BaseStatics = Object.create(null); var _foo = {
writable: true,
babelHelpers.defineProperty(_BaseStatics, "foo", 1); value: 1
};
class Sub1 extends Base { class Sub1 extends Base {
static update(val) { static update(val) {
return babelHelpers.classStaticPrivateFieldSpecSet(this, Sub1, _Sub1Statics, "foo", val); return babelHelpers.classStaticPrivateFieldSpecSet(this, Sub1, _foo2, val);
} }
} }
var _Sub1Statics = Object.create(null); var _foo2 = {
writable: true,
babelHelpers.defineProperty(_Sub1Statics, "foo", 2); value: 2
};
class Sub2 extends Base {} class Sub2 extends Base {}

View File

@ -1,14 +1,15 @@
class Foo { class Foo {
static test() { static test() {
return babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _FooStatics, "bar"); return babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _bar);
} }
test() { test() {
return babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _FooStatics, "bar"); return babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _bar);
} }
} }
var _FooStatics = Object.create(null); var _bar = {
writable: true,
babelHelpers.defineProperty(_FooStatics, "bar", void 0); value: void 0
};

View File

@ -1,17 +1,18 @@
class Foo { class Foo {
static test() { static test() {
return babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _FooStatics, "bar"); return babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _bar);
} }
test() { test() {
return babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _FooStatics, "bar"); return babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _bar);
} }
} }
var _FooStatics = Object.create(null); var _bar = {
writable: true,
babelHelpers.defineProperty(_FooStatics, "bar", "foo"); value: "foo"
};
expect("bar" in Foo).toBe(false); expect("bar" in Foo).toBe(false);
expect(Foo.test()).toBe("foo"); expect(Foo.test()).toBe("foo");
expect(Foo.test()).toBe("foo"); expect(Foo.test()).toBe("foo");