Skip to content

Commit

Permalink
feat(transformer): exponentiation transform: support private fields (#…
Browse files Browse the repository at this point in the history
…6345)

Babel doesn't support private fields in this transform, but there's no reason not to. So we can.
  • Loading branch information
overlookmotel committed Oct 8, 2024
1 parent bd5fb5a commit cf20f3a
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 23 deletions.
87 changes: 82 additions & 5 deletions crates/oxc_transformer/src/es2016/exponentiation_operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,18 @@ impl<'a, 'ctx> Traverse<'a> for ExponentiationOperator<'a, 'ctx> {
AssignmentTarget::AssignmentTargetIdentifier(_) => {
self.convert_identifier_assignment(expr, ctx);
}
// Note: We do not match `AssignmentTarget::PrivateFieldExpression` here.
// From Babel: "We can't generate property ref for private name, please install
// `@babel/plugin-transform-class-properties`".
// TODO: Ensure this plugin interacts correctly with class private properties
// transform, so the property is transformed before this transform.
AssignmentTarget::StaticMemberExpression(_) => {
self.convert_static_member_expression_assignment(expr, ctx);
}
AssignmentTarget::ComputedMemberExpression(_) => {
self.convert_computed_member_expression_assignment(expr, ctx);
}
// Babel refuses to transform this: "We can't generate property ref for private name,
// please install `@babel/plugin-transform-class-properties`".
// But there's no reason not to.
AssignmentTarget::PrivateFieldExpression(_) => {
self.convert_private_field_assignment(expr, ctx);
}
_ => {}
}
}
Expand Down Expand Up @@ -364,6 +365,82 @@ impl<'a, 'ctx> ExponentiationOperator<'a, 'ctx> {
(pow_left, temp_var_inits)
}

/// Convert `AssignmentExpression` where assignee is a private field member expression.
///
/// `obj.#prop **= right` transformed to:
/// * If `obj` is a bound symbol:
/// -> `obj.#prop = Math.pow(obj.#prop, right)`
/// * If `obj` is unbound:
/// -> `var _obj; _obj = obj, _obj.#prop = Math.pow(_obj.#prop, right)`
///
/// `obj.foo.bar.#qux **= right` transformed to:
/// ```js
/// var _obj$foo$bar;
/// _obj$foo$bar = obj.foo.bar, _obj$foo$bar.#qux = Math.pow(_obj$foo$bar.#qux, right)
/// ```
///
/// Temporary variable is to avoid side-effects of getting `obj` / `obj.foo.bar` being run twice.
//
// `#[inline]` so compiler knows `expr` is an `AssignmentExpression` with `PrivateFieldExpression` on left
#[inline]
fn convert_private_field_assignment(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let Expression::AssignmentExpression(assign_expr) = expr else { unreachable!() };
let AssignmentTarget::PrivateFieldExpression(member_expr) = &mut assign_expr.left else {
unreachable!()
};

let (pow_left, temp_var_inits) = self.get_pow_left_private_field(member_expr, ctx);
Self::convert_assignment(assign_expr, pow_left, ctx);
Self::revise_expression(expr, temp_var_inits, ctx);
}

/// Get left side of `Math.pow(pow_left, ...)` for static member expression
/// and replacement for left side of assignment.
fn get_pow_left_private_field(
&mut self,
field_expr: &mut PrivateFieldExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (
// Left side of `Math.pow(pow_left, ...)`
Expression<'a>,
// Temporary var initializations
Vec<'a, Expression<'a>>,
) {
// Object part of 2nd member expression
// ```
// obj.#prop = Math.pow(obj.#prop, right)
// ^^^
// ```
let mut temp_var_inits = ctx.ast.vec();
let obj = self.get_second_member_expression_object(
&mut field_expr.object,
&mut temp_var_inits,
ctx,
);

// Property part of 2nd member expression
// ```
// obj.#prop = Math.pow(obj.#prop, right)
// ^^^^^
// ```
let field = field_expr.field.clone_in(ctx.ast.allocator);

// Complete 2nd member expression
// ```
// obj.#prop = Math.pow(obj.#prop, right)
// ^^^^^^^^^
// ```
let pow_left = Expression::from(
ctx.ast.member_expression_private_field_expression(SPAN, obj, field, false),
);

(pow_left, temp_var_inits)
}

/// Get object part of 2nd member expression to be used as `left` in `Math.pow(left, right)`.
///
/// Also update the original `obj` passed in to function, and add a temp var initializer, if necessary.
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class C {
#p = 1;

method(obj) {
this.#p **= 1;
obj.#p **= 2;
this.x.y.z.#p **= 3;
obj.x.y.z.#p **= 4;
fn().x.y.z.#p **= 5;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class C {
#p = 1;

method(obj) {
var _this, _this$x$y$z, _obj$x$y$z, _fn$x$y$z;
_this = this, _this.#p = Math.pow(_this.#p, 1);
obj.#p = Math.pow(obj.#p, 2);
_this$x$y$z = this.x.y.z, _this$x$y$z.#p = Math.pow(_this$x$y$z.#p, 3);
_obj$x$y$z = obj.x.y.z, _obj$x$y$z.#p = Math.pow(_obj$x$y$z.#p, 4);
_fn$x$y$z = fn().x.y.z, _fn$x$y$z.#p = Math.pow(_fn$x$y$z.#p, 5);
}
}

0 comments on commit cf20f3a

Please sign in to comment.