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

View File

@ -164,40 +164,27 @@ export default declare((api, options) => {
...privateNameHandlerSpec,
get(member) {
const { file, name, privateClassId, classRef } = this;
const { file, privateId, classRef } = this;
return t.callExpression(
file.addHelper("classStaticPrivateFieldSpecGet"),
[
this.receiver(member),
classRef,
privateClassId,
t.stringLiteral(name),
],
[this.receiver(member), t.cloneNode(classRef), t.cloneNode(privateId)],
);
},
set(member, value) {
const { file, name, privateClassId, classRef } = this;
const { file, privateId, classRef } = this;
return t.callExpression(
file.addHelper("classStaticPrivateFieldSpecSet"),
[
this.receiver(member),
classRef,
privateClassId,
t.stringLiteral(name),
t.cloneNode(classRef),
t.cloneNode(privateId),
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) {
@ -308,50 +295,32 @@ export default declare((api, options) => {
state,
);
const staticNodesToAdd = [keyDecl, buildInit()];
return [staticNodesToAdd];
return [keyDecl, buildInit()];
}
function buildClassStaticPrivatePropertySpec(
ref,
path,
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,
}),
);
}
function buildClassStaticPrivatePropertySpec(ref, path, state) {
const { parentPath, scope } = path;
const { name } = path.node.key.id;
const privateId = scope.generateUidIdentifier(name);
memberExpressionToFunctions(parentPath, privateNameVisitor, {
name,
privateClassId,
privateId,
classRef: ref,
file: state,
...staticPrivatePropertyHandlerSpec,
});
staticNodesToAdd.push(
t.expressionStatement(
t.callExpression(state.addHelper("defineProperty"), [
privateClassId,
t.stringLiteral(name),
value || scope.buildUndefinedNode(),
]),
),
);
return [staticNodesToAdd, privateClassId];
return [
template.statement.ast`
var ${privateId} = {
// configurable is always false for private elements
// enumerable is always false for private elements
writable: true,
value: ${path.node.value || scope.buildUndefinedNode()}
}
`,
];
}
const buildClassProperty = loose
@ -461,21 +430,16 @@ export default declare((api, options) => {
}
}
let p = 0;
let privateClassId;
for (const prop of props) {
if (prop.node.static) {
if (prop.isPrivate()) {
let staticNodesToAdd;
[
staticNodesToAdd,
privateClassId,
] = buildClassStaticPrivateProperty(
t.cloneNode(ref),
prop,
state,
privateClassId,
staticNodes.push(
...buildClassStaticPrivateProperty(
t.cloneNode(ref),
prop,
state,
),
);
staticNodes.push(...staticNodesToAdd);
} else {
staticNodes.push(
buildClassProperty(t.cloneNode(ref), prop, state),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,14 +10,15 @@ function () {
babelHelpers.createClass(Foo, [{
key: "test",
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;
}();
var _FooStatics = Object.create(null);
babelHelpers.defineProperty(_FooStatics, "foo", function (x) {
return x;
});
var _foo = {
writable: true,
value: function (x) {
return x;
}
};

View File

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

View File

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

View File

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

View File

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

View File

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