Skip to content

Commit

Permalink
feat(transformer/class-properties): transform super assignment expres…
Browse files Browse the repository at this point in the history
…sions within static prop initializer (#7991)

Alternative of #7956 and #7959. Unlike the previous method, adapting duplicating the same logic rather than making the same logic transform function to be generic
  • Loading branch information
Dunqing committed Dec 18, 2024
1 parent e3d0889 commit cc57db3
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 4 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 @@ -159,6 +159,7 @@ pub enum Helper {
ClassPrivateFieldLooseKey,
ClassPrivateFieldLooseBase,
SuperPropGet,
SuperPropSet,
}

impl Helper {
Expand All @@ -183,6 +184,7 @@ impl Helper {
Self::ClassPrivateFieldLooseKey => "classPrivateFieldLooseKey",
Self::ClassPrivateFieldLooseBase => "classPrivateFieldLooseBase",
Self::SuperPropGet => "superPropGet",
Self::SuperPropSet => "superPropSet",
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1608,7 +1608,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
/// * Anything else `foo()` -> `_foo = foo()`, `_foo`
///
/// Returns 2 `Expression`s. The first must be inserted into output first.
fn duplicate_object(
pub(super) fn duplicate_object(
&self,
object: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,15 @@ impl<'a, 'ctx, 'v> VisitMut<'a> for StaticInitializerVisitor<'a, 'ctx, 'v> {
self.transform_call_expression_if_super_member_expression(call_expr);
self.class_properties.transform_call_expression(expr, self.ctx);
}
// `super.prop = value`, `super.prop += value`, `super.prop ??= value` or
// `object.#prop = value`, `object.#prop += value`, `object.#prop ??= value` etc
Expression::AssignmentExpression(_) => {
self.class_properties.transform_assignment_expression(expr, self.ctx);
self.transform_assignment_expression_if_super_member_assignment_target(expr);
// Check again if it's an assignment expression, because it could have been transformed
// to other expression.
if matches!(expr, Expression::AssignmentExpression(_)) {
self.class_properties.transform_assignment_expression(expr, self.ctx);
}
}
// `object.#prop++`, `--object.#prop`
Expression::UpdateExpression(_) => {
Expand Down Expand Up @@ -521,4 +527,14 @@ impl<'a, 'ctx, 'v> StaticInitializerVisitor<'a, 'ctx, 'v> {
.transform_call_expression_for_super_member_expr(call_expr, self.ctx);
}
}

fn transform_assignment_expression_if_super_member_assignment_target(
&mut self,
expr: &mut Expression<'a>,
) {
if self.this_depth == 0 {
self.class_properties
.transform_assignment_expression_for_super_assignment_target(expr, self.ctx);
}
}
}
170 changes: 170 additions & 0 deletions crates/oxc_transformer/src/es2022/class_properties/supers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,152 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
arguments.push(Argument::from(array));
}

/// Transform assignment expression where the left-hand side is a member expression with `super`.
///
/// * `super.prop = value`
/// -> `_superPropSet(_Class, "prop", value, _Class, 1)`
/// * `super.prop += value`
/// -> `_superPropSet(_Class, "prop", _superPropGet(_Class, "prop", _Class) + value, _Class, 1)`
/// * `super.prop &&= value`
/// -> `_superPropGet(_Class, "prop", _Class) && _superPropSet(_Class, "prop", value, _Class, 1)`
///
/// * `super[prop] = value`
/// -> `_superPropSet(_Class, prop, value, _Class, 1)`
/// * `super[prop] += value`
/// -> `_superPropSet(_Class, prop, _superPropGet(_Class, prop, _Class) + value, _Class, 1)`
/// * `super[prop] &&= value`
/// -> `_superPropGet(_Class, prop, _Class) && _superPropSet(_Class, prop, value, _Class, 1)`
//
// `#[inline]` so can bail out fast without a function call if `left` is not a member expression
// with `super` as member expression object (fairly rare).
// Actual transform is broken out into separate functions.
pub(super) fn transform_assignment_expression_for_super_assignment_target(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let Expression::AssignmentExpression(assign_expr) = expr else { unreachable!() };
match &assign_expr.left {
AssignmentTarget::StaticMemberExpression(member) if member.object.is_super() => {
self.transform_assignment_expression_for_super_static_member_expr(expr, ctx);
}
AssignmentTarget::ComputedMemberExpression(member) if member.object.is_super() => {
self.transform_assignment_expression_for_super_computed_member_expr(expr, ctx);
}
_ => {}
};
}

/// Transform assignment expression where the left-hand side is a static member expression
/// with `super`.
///
/// * `super.prop = value`
/// -> `_superPropSet(_Class, "prop", value, _Class, 1)`
/// * `super.prop += value`
/// -> `_superPropSet(_Class, "prop", _superPropGet(_Class, "prop", _Class) + value, _Class, 1)`
/// * `super.prop &&= value`
/// -> `_superPropGet(_Class, "prop", _Class) && _superPropSet(_Class, "prop", value, _Class, 1)`
//
// `#[inline]` so that compiler sees that `expr` is an `Expression::AssignmentExpression`.
#[inline]
fn transform_assignment_expression_for_super_static_member_expr(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let Expression::AssignmentExpression(assign_expr) = ctx.ast.move_expression(expr) else {
unreachable!()
};
let AssignmentExpression { span, operator, right: value, left } = assign_expr.unbox();
let AssignmentTarget::StaticMemberExpression(member) = left else { unreachable!() };
let property = ctx.ast.expression_string_literal(
member.property.span,
member.property.name.clone(),
None,
);
*expr =
self.transform_super_assignment_expression_impl(span, operator, property, value, ctx);
}

/// Transform assignment expression where the left-hand side is a computed member expression
/// with `super` as member expr object.
///
/// * `super[prop] = value`
/// -> `_superPropSet(_Class, prop, value, _Class, 1)`
/// * `super[prop] += value`
/// -> `_superPropSet(_Class, prop, _superPropGet(_Class, prop, _Class) + value, _Class, 1)`
/// * `super[prop] &&= value`
/// -> `_superPropGet(_Class, prop, _Class) && _superPropSet(_Class, prop, value, _Class, 1)`
///
// `#[inline]` so that compiler sees that `expr` is an `Expression::AssignmentExpression`.
#[inline]
fn transform_assignment_expression_for_super_computed_member_expr(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let Expression::AssignmentExpression(assign_expr) = ctx.ast.move_expression(expr) else {
unreachable!()
};
let AssignmentExpression { span, operator, right: value, left } = assign_expr.unbox();
let AssignmentTarget::ComputedMemberExpression(member) = left else { unreachable!() };
let property = member.unbox().expression;
*expr =
self.transform_super_assignment_expression_impl(span, operator, property, value, ctx);
}

/// Transform assignment expression where the left-hand side is a member expression with `super`
/// as member expr object.
///
/// * `=` -> `_superPropSet(_Class, <prop>, <value>, _Class, 1)`
/// * `+=` -> `_superPropSet(_Class, <prop>, _superPropGet(_Class, <prop>, _Class) + <value>, 1)`
/// * `&&=` -> `_superPropGet(_Class, <prop>, _Class) && _superPropSet(_Class, <prop>, <value>, _Class, 1)`
fn transform_super_assignment_expression_impl(
&mut self,
span: Span,
operator: AssignmentOperator,
property: Expression<'a>,
value: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
if operator == AssignmentOperator::Assign {
// `super[prop] = value` -> `_superPropSet(_Class, prop, value, _Class, 1)`
self.create_super_prop_set(span, property, value, ctx)
} else {
// Make 2 copies of `object`
let (property1, property2) = self.duplicate_object(property, ctx);

if let Some(operator) = operator.to_binary_operator() {
// `super[prop] += value`
// -> `_superPropSet(_Class, prop, _superPropGet(_Class, prop, _Class) + value, _Class, 1)`

// `_superPropGet(_Class, prop, _Class)`
let get_call = self.create_super_prop_get(SPAN, property2, false, ctx);

// `_superPropGet(_Class, prop, _Class) + value`
let value = ctx.ast.expression_binary(SPAN, get_call, operator, value);

// `_superPropSet(_Class, prop, _superPropGet(_Class, prop, _Class) + value, 1)`
self.create_super_prop_set(span, property1, value, ctx)
} else if let Some(operator) = operator.to_logical_operator() {
// `super[prop] &&= value`
// -> `_superPropGet(_Class, prop, _Class) && _superPropSet(_Class, prop, value, _Class, 1)`

// `_superPropGet(_Class, prop, _Class)`
let get_call = self.create_super_prop_get(SPAN, property1, false, ctx);

// `_superPropSet(_Class, prop, value, _Class, 1)`
let set_call = self.create_super_prop_set(span, property2, value, ctx);

// `_superPropGet(_Class, prop, _Class) && _superPropSet(_Class, prop, value, _Class, 1)`
ctx.ast.expression_logical(span, get_call, operator, set_call)
} else {
// The above covers all types of `AssignmentOperator`
unreachable!();
}
}
}

/// Member:
/// `_superPropGet(_Class, prop, _Class)`
///
Expand Down Expand Up @@ -155,4 +301,28 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
// `_superPropGet(_Class, prop, _Class)` or `_superPropGet(_Class, prop, _Class, 2)`
self.ctx.helper_call_expr(Helper::SuperPropGet, span, arguments, ctx)
}

/// `_superPropSet(_Class, prop, value, _Class, 1)`
fn create_super_prop_set(
&mut self,
span: Span,
property: Expression<'a>,
value: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let temp_binding = self.current_class_mut().bindings.get_or_init_static_binding(ctx);
let arguments = ctx.ast.vec_from_array([
Argument::from(temp_binding.create_read_expression(ctx)),
Argument::from(property),
Argument::from(value),
Argument::from(temp_binding.create_read_expression(ctx)),
Argument::from(ctx.ast.expression_numeric_literal(
SPAN,
1.0,
None,
NumberBase::Decimal,
)),
]);
self.ctx.helper_call_expr(Helper::SuperPropSet, span, arguments, ctx)
}
}
4 changes: 2 additions & 2 deletions tasks/transform_conformance/snapshots/oxc.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
commit: 54a8389f

Passed: 112/127
Passed: 113/128

# All Passed:
* babel-plugin-transform-class-static-block
Expand All @@ -16,7 +16,7 @@ Passed: 112/127
* regexp


# babel-plugin-transform-class-properties (13/16)
# babel-plugin-transform-class-properties (14/17)
* static-super-tagged-template/input.js
x Output mismatch

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const ident = "A";

class Outer {
static B = () => {
// Transform
super.A = 1;
super.A += 1;
super.A -= 1;
super.A &&= 1;
super.A ||= 1;

super[ident] = 1;
super[ident] += 1;
super[ident] -= 1;
super[ident] &&= 1;
super[ident] ||= 1;

class Inner {
method() {
// Don't transform
super.A = 1;
super.A += 1;
super.A -= 1;
super.A &&= 1;
super.A ||= 1;

super[ident] = 1;
super[ident] += 1;
super[ident] -= 1;
super[ident] &&= 1;
super[ident] ||= 1;
}

static staticMethod() {
// Don't transform
super.A = 1;
super.A += 1;
super.A -= 1;
super.A &&= 1;
super.A ||= 1;

super[ident] = 1;
super[ident] += 1;
super[ident] -= 1;
super[ident] &&= 1;
super[ident] ||= 1;
}
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
var _Outer;

const ident = "A";

class Outer {}

_Outer = Outer;
babelHelpers.defineProperty(Outer, "B", () => {
// Transform
babelHelpers.superPropSet(_Outer, "A", 1, _Outer, 1);
babelHelpers.superPropSet(_Outer, "A", babelHelpers.superPropGet(_Outer, "A", _Outer) + 1, _Outer, 1);
babelHelpers.superPropSet(_Outer, "A", babelHelpers.superPropGet(_Outer, "A", _Outer) - 1, _Outer, 1);
babelHelpers.superPropGet(_Outer, "A", _Outer) && babelHelpers.superPropSet(_Outer, "A", 1, _Outer, 1);
babelHelpers.superPropGet(_Outer, "A", _Outer) || babelHelpers.superPropSet(_Outer, "A", 1, _Outer, 1);

babelHelpers.superPropSet(_Outer, ident, 1, _Outer, 1);
babelHelpers.superPropSet(_Outer, ident, babelHelpers.superPropGet(_Outer, ident, _Outer) + 1, _Outer, 1);
babelHelpers.superPropSet(_Outer, ident, babelHelpers.superPropGet(_Outer, ident, _Outer) - 1, _Outer, 1);
babelHelpers.superPropGet(_Outer, ident, _Outer) && babelHelpers.superPropSet(_Outer, ident, 1, _Outer, 1);
babelHelpers.superPropGet(_Outer, ident, _Outer) || babelHelpers.superPropSet(_Outer, ident, 1, _Outer, 1);

class Inner {
method() {
// Don't transform
super.A = 1;
super.A += 1;
super.A -= 1;
super.A &&= 1;
super.A ||= 1;

super[ident] = 1;
super[ident] += 1;
super[ident] -= 1;
super[ident] &&= 1;
super[ident] ||= 1;
}

static staticMethod() {
// Don't transform
super.A = 1;
super.A += 1;
super.A -= 1;
super.A &&= 1;
super.A ||= 1;

super[ident] = 1;
super[ident] += 1;
super[ident] -= 1;
super[ident] &&= 1;
super[ident] ||= 1;
}
}
});

0 comments on commit cc57db3

Please sign in to comment.