Skip to content

Commit

Permalink
Implement noUninitializedPrivateFieldAccess assumption (#16267)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Feb 26, 2024
1 parent cb4d920 commit 3c0abc7
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 19 deletions.
1 change: 1 addition & 0 deletions packages/babel-core/src/config/validation/options.ts
Expand Up @@ -281,6 +281,7 @@ const knownAssumptions = [
"noDocumentAll",
"noIncompleteNsImportDetection",
"noNewArrows",
"noUninitializedPrivateFieldAccess",
"objectRestNoSymbols",
"privateFieldsAsSymbols",
"privateFieldsAsProperties",
Expand Down
89 changes: 72 additions & 17 deletions packages/babel-helper-create-class-features-plugin/src/fields.ts
Expand Up @@ -167,9 +167,7 @@ export function privateNameVisitorFactory<S, V>(
// Traverses the outer portion of a class, without touching the class's inner
// scope, for private names.
const nestedVisitor = traverse.visitors.merge([
{
...visitor,
},
{ ...visitor },
environmentVisitor,
]);

Expand Down Expand Up @@ -224,6 +222,7 @@ interface PrivateNameState {
classRef: t.Identifier;
file: File;
noDocumentAll: boolean;
noUninitializedPrivateFieldAccess: boolean;
innerBinding?: t.Identifier;
}

Expand Down Expand Up @@ -361,6 +360,14 @@ function writeOnlyError(file: File, name: string) {
]);
}

function buildStaticPrivateFieldAccess<N extends t.Expression>(
expr: N,
noUninitializedPrivateFieldAccess: boolean,
) {
if (noUninitializedPrivateFieldAccess) return expr;
return t.memberExpression(expr, t.identifier("_"));
}

const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
{
memoise(member, count) {
Expand All @@ -386,7 +393,13 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
},

get(member) {
const { classRef, privateNamesMap, file, innerBinding } = this;
const {
classRef,
privateNamesMap,
file,
innerBinding,
noUninitializedPrivateFieldAccess,
} = this;
const { name } = (member.node.property as t.PrivateName).id;
const {
id,
Expand Down Expand Up @@ -424,16 +437,19 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =

if (!isMethod) {
if (skipCheck) {
return t.memberExpression(t.cloneNode(id), t.identifier("_"));
return buildStaticPrivateFieldAccess(
t.cloneNode(id),
noUninitializedPrivateFieldAccess,
);
}

return t.memberExpression(
return buildStaticPrivateFieldAccess(
t.callExpression(file.addHelper("assertClassBrand"), [
receiver,
t.cloneNode(classRef),
t.cloneNode(id),
]),
t.identifier("_"),
noUninitializedPrivateFieldAccess,
);
}

Expand Down Expand Up @@ -515,7 +531,12 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
},

set(member, value) {
const { classRef, privateNamesMap, file } = this;
const {
classRef,
privateNamesMap,
file,
noUninitializedPrivateFieldAccess,
} = this;
const { name } = (member.node.property as t.PrivateName).id;
const {
id,
Expand Down Expand Up @@ -574,7 +595,10 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
}
return t.assignmentExpression(
"=",
t.memberExpression(t.cloneNode(id), t.identifier("_")),
buildStaticPrivateFieldAccess(
t.cloneNode(id),
noUninitializedPrivateFieldAccess,
),
skipCheck
? value
: t.callExpression(file.addHelper("assertClassBrand"), [
Expand Down Expand Up @@ -615,7 +639,12 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
},

destructureSet(member) {
const { classRef, privateNamesMap, file } = this;
const {
classRef,
privateNamesMap,
file,
noUninitializedPrivateFieldAccess,
} = this;
const { name } = (member.node.property as t.PrivateName).id;
const {
id,
Expand Down Expand Up @@ -669,7 +698,19 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
}

if (isStatic && !isMethod) {
return this.get(member);
const getCall = this.get(member);
if (
!noUninitializedPrivateFieldAccess ||
!t.isCallExpression(getCall)
) {
return getCall;
}
const ref = getCall.arguments.pop();
getCall.arguments.push(template.expression.ast`(_) => ${ref} = _`);
return t.memberExpression(
t.callExpression(file.addHelper("toSetter"), [getCall]),
t.identifier("_"),
);
}

const setCall = this.set(member, t.identifier("_"));
Expand Down Expand Up @@ -793,10 +834,12 @@ export function transformPrivateNamesUsage(
privateNamesMap: PrivateNamesMap,
{
privateFieldsAsProperties,
noUninitializedPrivateFieldAccess,
noDocumentAll,
innerBinding,
}: {
privateFieldsAsProperties: boolean;
noUninitializedPrivateFieldAccess: boolean;
noDocumentAll: boolean;
innerBinding: t.Identifier;
},
Expand All @@ -815,6 +858,7 @@ export function transformPrivateNamesUsage(
file: state,
...handler,
noDocumentAll,
noUninitializedPrivateFieldAccess,
innerBinding,
});
body.traverse(privateInVisitor, {
Expand Down Expand Up @@ -888,14 +932,20 @@ function buildPrivateInstanceFieldInitSpec(
function buildPrivateStaticFieldInitSpec(
prop: NodePath<t.ClassPrivateProperty>,
privateNamesMap: PrivateNamesMap,
noUninitializedPrivateFieldAccess: boolean,
) {
const privateName = privateNamesMap.get(prop.node.key.id.name);
return inheritPropComments(
template.statement.ast`
var ${t.cloneNode(privateName.id)} = {

const value = noUninitializedPrivateFieldAccess
? prop.node.value
: template.expression.ast`{
_: ${prop.node.value || t.buildUndefinedNode()}
};
`,
}`;

return inheritPropComments(
t.variableDeclaration("var", [
t.variableDeclarator(t.cloneNode(privateName.id), value),
]),
prop,
);
}
Expand Down Expand Up @@ -1368,6 +1418,7 @@ export function buildFieldsInitNodes(
file: File,
setPublicClassFields: boolean,
privateFieldsAsProperties: boolean,
noUninitializedPrivateFieldAccess: boolean,
constantSuper: boolean,
innerBindingRef: t.Identifier | null,
) {
Expand Down Expand Up @@ -1473,7 +1524,11 @@ export function buildFieldsInitNodes(
);
} else {
staticNodes.push(
buildPrivateStaticFieldInitSpec(prop, privateNamesMap),
buildPrivateStaticFieldInitSpec(
prop,
privateNamesMap,
noUninitializedPrivateFieldAccess,
),
);
}
break;
Expand Down
Expand Up @@ -75,6 +75,8 @@ export function createClassFeaturePlugin({
const setPublicClassFields = api.assumption("setPublicClassFields");
const privateFieldsAsSymbols = api.assumption("privateFieldsAsSymbols");
const privateFieldsAsProperties = api.assumption("privateFieldsAsProperties");
const noUninitializedPrivateFieldAccess =
api.assumption("noUninitializedPrivateFieldAccess") ?? false;
const constantSuper = api.assumption("constantSuper");
const noDocumentAll = api.assumption("noDocumentAll");

Expand Down Expand Up @@ -256,6 +258,7 @@ export function createClassFeaturePlugin({
{
privateFieldsAsProperties:
privateFieldsAsSymbolsOrProperties ?? loose,
noUninitializedPrivateFieldAccess,
noDocumentAll,
innerBinding,
},
Expand Down Expand Up @@ -296,6 +299,7 @@ export function createClassFeaturePlugin({
file,
setPublicClassFields ?? loose,
privateFieldsAsSymbolsOrProperties ?? loose,
noUninitializedPrivateFieldAccess,
constantSuper ?? loose,
innerBinding,
));
Expand All @@ -317,6 +321,7 @@ export function createClassFeaturePlugin({
file,
setPublicClassFields ?? loose,
privateFieldsAsSymbolsOrProperties ?? loose,
noUninitializedPrivateFieldAccess,
constantSuper ?? loose,
innerBinding,
));
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-helpers/src/helpers-generated.ts
Expand Up @@ -173,10 +173,10 @@ export default Object.freeze({
"7.1.5",
'import toPrimitive from"toPrimitive";export default function toPropertyKey(t){var i=toPrimitive(t,"string");return"symbol"==typeof i?i:String(i)}',
),
// size: 134, gzip size: 134
// size: 144, gzip size: 141
toSetter: helper(
"7.22.0",
'export default function _toSetter(t,e,n){var r=e.length++;return Object.defineProperty({},"_",{set:function(o){e[r]=o,t.apply(n,e)}})}',
'export default function _toSetter(t,e,n){e||(e=[]);var r=e.length++;return Object.defineProperty({},"_",{set:function(o){e[r]=o,t.apply(n,e)}})}',
),
// size: 289, gzip size: 165
typeof: helper(
Expand Down
1 change: 1 addition & 0 deletions packages/babel-helpers/src/helpers/toSetter.ts
@@ -1,6 +1,7 @@
/* @minVersion 7.22.0 */

export default function _toSetter(fn: Function, args: any[], thisArg: any) {
if (!args) args = [];
var l = args.length++;
return Object.defineProperty({}, "_", {
set: function (v) {
Expand Down
@@ -0,0 +1,6 @@
{
"plugins": ["transform-class-properties"],
"assumptions": {
"noUninitializedPrivateFieldAccess": true
}
}
@@ -0,0 +1,23 @@
class A {
static #x = 0;

static dynamicCheck() {
expect(this.#x).toBe(0);
expect(this.#x = 1).toBe(1);
expect(this.#x).toBe(1);
[this.#x] = [2];
expect(this.#x).toBe(2);
}

static noCheck() {
expect(A.#x).toBe(2);
expect(A.#x = 3).toBe(3);
[A.#x] = [4];
expect(A.#x).toBe(4);
}
}

A.dynamicCheck();
A.noCheck();


@@ -0,0 +1,15 @@
class A {
static #x = 2;

static dynamicCheck() {
this.#x;
this.#x = 2;
[this.#x] = [];
}

static noCheck() {
A.#x;
A.#x = 2;
[A.#x] = [];
}
}
@@ -0,0 +1,13 @@
class A {
static dynamicCheck() {
babelHelpers.assertClassBrand(this, A, _x);
_x = babelHelpers.assertClassBrand(this, A, 2);
[babelHelpers.toSetter(babelHelpers.assertClassBrand(this, A, _ => _x = _))._] = [];
}
static noCheck() {
_x;
_x = 2;
[_x] = [];
}
}
var _x = 2;

0 comments on commit 3c0abc7

Please sign in to comment.