Skip to content

Commit

Permalink
feat(transformer/class-properties): transform private in expression
Browse files Browse the repository at this point in the history
  • Loading branch information
Dunqing committed Dec 31, 2024
1 parent cde89df commit 26dad2f
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 109 deletions.
2 changes: 2 additions & 0 deletions crates/oxc_transformer/src/common/helper_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ pub enum Helper {
SuperPropSet,
ReadOnlyError,
WriteOnlyError,
CheckInRHS,
}

impl Helper {
Expand Down Expand Up @@ -191,6 +192,7 @@ impl Helper {
Self::SuperPropSet => "superPropSet",
Self::ReadOnlyError => "readOnlyError",
Self::WriteOnlyError => "writeOnlyError",
Self::CheckInRHS => "checkInRHS",
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions crates/oxc_transformer/src/es2022/class_properties/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ impl<'a, 'ctx> Traverse<'a> for ClassProperties<'a, 'ctx> {
Expression::TaggedTemplateExpression(_) => {
self.transform_tagged_template_expression(expr, ctx);
}
// "#prop in object"
Expression::PrivateInExpression(_) => {
self.transform_private_in_expression(expr, ctx);
}
_ => {}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::mem;

use oxc_allocator::String as ArenaString;
use oxc_allocator::{Box as ArenaBox, String as ArenaString};
use oxc_ast::{ast::*, NONE};
use oxc_span::SPAN;
use oxc_syntax::{reference::ReferenceId, symbol::SymbolId};
Expand Down Expand Up @@ -1820,6 +1820,63 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
*target = AssignmentTarget::from(replacement.into_member_expression());
}

/// Transform private field in expression.
///
/// * Static
/// `#prop in object` -> `_checkInRHS(object) === Class`
///
/// * Instance prop
/// `#prop in object` -> `_prop.has(_checkInRHS(object))`
///
/// * Instance method
/// `#method in object` -> `_Class_brand.has(_checkInRHS(object))`
///
// `#[inline]` so that compiler sees that `expr` is an `Expression::PrivateFieldExpression`
#[inline]
pub(super) fn transform_private_in_expression(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let Expression::PrivateInExpression(private_in) = ctx.ast.move_expression(expr) else {
unreachable!();
};

*expr = self.transform_private_in_expression_impl(private_in, ctx);
}

fn transform_private_in_expression_impl(
&mut self,
private_field: ArenaBox<'a, PrivateInExpression<'a>>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let PrivateInExpression { left, right, span, .. } = private_field.unbox();

let ResolvedPrivateProp { class_bindings, prop_binding, is_method, is_static, .. } =
self.classes_stack.find_private_prop(&left);

if is_static {
let class_binding = class_bindings.get_or_init_static_binding(ctx);
let class_ident = class_binding.create_read_expression(ctx);
let left = self.create_check_in_rhs(right, SPAN, ctx);
return ctx.ast.expression_binary(
span,
left,
BinaryOperator::StrictEquality,
class_ident,
);
}

let callee = if is_method {
class_bindings.brand().create_read_expression(ctx)
} else {
prop_binding.create_read_expression(ctx)
};
let callee = create_member_callee(callee, "has", ctx);
let argument = self.create_check_in_rhs(right, SPAN, ctx);
ctx.ast.expression_call(span, callee, NONE, ctx.ast.vec1(Argument::from(argument)), false)
}

/// Duplicate object to be used in get/set pair.
///
/// If `object` may have side effects, create a temp var `_object` and assign to it.
Expand Down Expand Up @@ -2151,4 +2208,19 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
let expressions = ctx.ast.vec_from_array([object, error]);
ctx.ast.expression_sequence(span, expressions)
}

/// _checkInRHS(object)
fn create_check_in_rhs(
&self,
object: Expression<'a>,
span: Span,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
self.ctx.helper_call_expr(
Helper::CheckInRHS,
span,
ctx.ast.vec1(Argument::from(object)),
ctx,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
var _Foo_brand = new WeakSet();
class Foo {
constructor() {
babelHelpers.classPrivateMethodInitSpec(this, _Foo_brand);
}
test(other) {
return _Foo_brand.has(babelHelpers.checkInRHS(other));
}
}
function _get_foo() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Foo {
test(other) {
return babelHelpers.checkInRHS(other) === Foo;
}
}
function _get_foo() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
var _Foo_brand = new WeakSet();
class Foo {
constructor() {
babelHelpers.classPrivateMethodInitSpec(this, _Foo_brand);
}
test(other) {
return _Foo_brand.has(babelHelpers.checkInRHS(other));
}
}
function _get_foo() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
var _F_brand = new WeakSet();
var _x = new WeakMap();
var _y = new WeakMap();
class F {
constructor() {
babelHelpers.classPrivateMethodInitSpec(this, _F_brand);
babelHelpers.classPrivateFieldInitSpec(this, _x, 0);
babelHelpers.classPrivateFieldInitSpec(this, _y, (() => {
throw "error";
})());
}
m() {
_F_brand.has(babelHelpers.checkInRHS(this));
_x.has(babelHelpers.checkInRHS(this));
_y.has(babelHelpers.checkInRHS(this));
_F_brand.has(babelHelpers.checkInRHS(this));
}
}
function _get_w() {}
function _z() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Foo {
test(other) {
return babelHelpers.checkInRHS(other) === Foo;
}
}
function _get_foo() {}
79 changes: 2 additions & 77 deletions tasks/transform_conformance/snapshots/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
commit: 54a8389f

Passed: 661/1154
Passed: 686/1154

# All Passed:
* babel-plugin-transform-logical-assignment-operators
Expand Down Expand Up @@ -811,7 +811,7 @@ x Output mismatch
x Output mismatch


# babel-plugin-transform-private-property-in-object (0/59)
# babel-plugin-transform-private-property-in-object (25/59)
* assumption-privateFieldsAsProperties/accessor/input.js
x Output mismatch

Expand Down Expand Up @@ -872,36 +872,6 @@ x Output mismatch
* assumption-privateFieldsAsSymbols/static-method/input.js
x Output mismatch

* private/accessor/input.js
x Output mismatch

* private/field/input.js
x Output mismatch

* private/method/input.js
x Output mismatch

* private/native-classes/input.js
x Output mismatch

* private/nested-class/input.js
x Output mismatch

* private/nested-class-other-redeclared/input.js
x Output mismatch

* private/nested-class-redeclared/input.js
x Output mismatch

* private/static-accessor/input.js
x Output mismatch

* private/static-field/input.js
x Output mismatch

* private/static-method/input.js
x Output mismatch

* private/static-shadow/input.js
x Output mismatch

Expand All @@ -914,9 +884,6 @@ x Output mismatch
* private-loose/method/input.js
x Output mismatch

* private-loose/native-classes/input.js
x Output mismatch

* private-loose/nested-class/input.js
x Output mismatch

Expand All @@ -938,51 +905,9 @@ x Output mismatch
* private-loose/static-shadow/input.js
x Output mismatch

* to-native-fields/accessor/input.js
x Output mismatch

* to-native-fields/class-expression-in-default-param/input.js
x Output mismatch

* to-native-fields/class-expression-instance/input.js
x Output mismatch

* to-native-fields/class-expression-static/input.js
x Output mismatch

* to-native-fields/field/input.js
x Output mismatch

* to-native-fields/half-constructed-instance/input.js
x Output mismatch

* to-native-fields/half-constructed-static/input.js
x Output mismatch

* to-native-fields/method/input.js
x Output mismatch

* to-native-fields/multiple-checks/input.js
x Output mismatch

* to-native-fields/nested-class/input.js
x Output mismatch

* to-native-fields/nested-class-other-redeclared/input.js
x Output mismatch

* to-native-fields/nested-class-redeclared/input.js
x Output mismatch

* to-native-fields/static-accessor/input.js
x Output mismatch

* to-native-fields/static-field/input.js
x Output mismatch

* to-native-fields/static-method/input.js
x Output mismatch

* to-native-fields/static-shadow/input.js
x Output mismatch

Expand Down
48 changes: 17 additions & 31 deletions tasks/transform_conformance/snapshots/babel_exec.snap.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ commit: 54a8389f

node: v22.12.0

Passed: 283 of 374 (75.67%)
Passed: 291 of 374 (77.81%)

Failures:

Expand Down Expand Up @@ -78,9 +78,6 @@ AssertionError: expected '_Class' to be 'Foo' // Object.is equality
AssertionError: expected '_Class' to be 'Foo' // Object.is equality
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-public-static-infer-name-exec.test.js:9:19

./fixtures/babel/babel-plugin-transform-class-static-block-test-fixtures-integration-loose-private-in-exec.test.js
Private field '#bar' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-class-static-block-test-fixtures-integration-loose-private-methods-access-exec.test.js
TypeError: attempted to use private field on non-instance
at _classPrivateFieldBase (./node_modules/.pnpm/@babel[email protected]/node_modules/@babel/runtime/helpers/classPrivateFieldLooseBase.js:2:44)
Expand All @@ -91,9 +88,6 @@ TypeError: attempted to use private field on non-instance
AssertionError: expected [Function Base] to be undefined // Object.is equality
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-static-block-test-fixtures-integration-new-target-exec.test.js:10:29

./fixtures/babel/babel-plugin-transform-class-static-block-test-fixtures-integration-private-in-exec.test.js
Private field '#bar' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-optional-chaining-test-fixtures-assumption-noDocumentAll-parenthesized-expression-member-call-exec.test.js
TypeError: Cannot read properties of undefined (reading 'x')
at m (./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-optional-chaining-test-fixtures-assumption-noDocumentAll-parenthesized-expression-member-call-exec.test.js:10:16)
Expand Down Expand Up @@ -415,40 +409,32 @@ TypeError: "#privateStaticFieldValue" is write-only
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-methods-test-fixtures-static-accessors-privateFieldsAsSymbols-get-only-setter-exec.test.js:14:12

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-assumption-privateFieldsAsProperties-method-exec.test.js
Private field '#foo' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-assumption-privateFieldsAsProperties-rhs-not-object-exec.test.js
Private field '#p' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-assumption-privateFieldsAsSymbols-method-exec.test.js
Private field '#foo' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-assumption-privateFieldsAsSymbols-rhs-not-object-exec.test.js
Private field '#p' must be declared in an enclosing class
ReferenceError: _Foo_brand is not defined
at new Foo (./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-assumption-privateFieldsAsProperties-method-exec.test.js:8:38)
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-assumption-privateFieldsAsProperties-method-exec.test.js:19:13

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-loose-rhs-not-object-exec.test.js
Private field '#p' must be declared in an enclosing class
AssertionError: expected [Function] to throw error including 'right-hand side of \'in\' should be a…' but got '_Class_brand is not defined'
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest[email protected]/node_modules/@vitest/expect/dist/index.js:1438:21)
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest[email protected]/node_modules/@vitest/expect/dist/index.js:923:17)
at Proxy.methodWrapper (./node_modules/.pnpm/[email protected]/node_modules/chai/chai.js:1610:25)
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-loose-rhs-not-object-exec.test.js:176:5

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-loose-static-shadow-exec.test.js
Private field '#x' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-rhs-not-object-exec.test.js
Private field '#p' must be declared in an enclosing class
AssertionError: expected 2 to be 5 // Object.is equality
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-loose-static-shadow-exec.test.js:18:25

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-static-shadow-exec.test.js
Private field '#x' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-to-native-fields-half-constructed-instance-exec.test.js
Private field '#w' must be declared in an enclosing class
AssertionError: expected 2 to be 5 // Object.is equality
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-static-shadow-exec.test.js:18:25

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-to-native-fields-half-constructed-static-exec.test.js
Private field '#w' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-to-native-fields-rhs-not-object-exec.test.js
Private field '#p' must be declared in an enclosing class
AssertionError: expected true to be false // Object.is equality
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-to-native-fields-half-constructed-static-exec.test.js:29:15

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-to-native-fields-static-shadow-exec.test.js
Private field '#x' must be declared in an enclosing class
AssertionError: expected 2 to be 5 // Object.is equality
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-to-native-fields-static-shadow-exec.test.js:18:25

./fixtures/babel/babel-preset-env-test-fixtures-plugins-integration-issue-15170-exec.test.js
AssertionError: expected [Function] to not throw an error but 'ReferenceError: x is not defined' was thrown
Expand Down

0 comments on commit 26dad2f

Please sign in to comment.