diff --git a/crates/oxc_transformer/src/es2022/class_properties/static_prop_init.rs b/crates/oxc_transformer/src/es2022/class_properties/static_prop_init.rs index 184d3fb123322..e49c8b1256b2c 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/static_prop_init.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/static_prop_init.rs @@ -207,7 +207,12 @@ impl<'a, 'ctx, 'v> VisitMut<'a> for StaticInitializerVisitor<'a, 'ctx, 'v> { } // `object.#prop++`, `--object.#prop` Expression::UpdateExpression(_) => { - self.class_properties.transform_update_expression(expr, self.ctx); + self.transform_update_expression_if_super_member_assignment_target(expr); + // Check again if it's an update expression, because it could have been transformed + // to other expression. + if matches!(expr, Expression::UpdateExpression(_)) { + self.class_properties.transform_update_expression(expr, self.ctx); + } } // `object?.#prop` Expression::ChainExpression(_) => { @@ -540,4 +545,17 @@ impl<'a, 'ctx, 'v> StaticInitializerVisitor<'a, 'ctx, 'v> { .transform_assignment_expression_for_super_assignment_target(expr, self.ctx); } } + + // `#[inline]` into visitor to keep common path where assignment expression isn't + // `super.prop++` fast + #[inline] + fn transform_update_expression_if_super_member_assignment_target( + &mut self, + expr: &mut Expression<'a>, + ) { + if self.this_depth == 0 { + self.class_properties + .transform_update_expression_for_super_assignment_target(expr, self.ctx); + } + } } diff --git a/crates/oxc_transformer/src/es2022/class_properties/supers.rs b/crates/oxc_transformer/src/es2022/class_properties/supers.rs index 014bbde06ebd7..b1243f496766a 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/supers.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/supers.rs @@ -1,14 +1,14 @@ //! ES2022: Class Properties //! Transform of `super` expressions. -use oxc_allocator::Vec as ArenaVec; +use oxc_allocator::{Box as ArenaBox, Vec as ArenaVec}; use oxc_ast::ast::*; use oxc_span::SPAN; -use oxc_traverse::TraverseCtx; +use oxc_traverse::{BoundIdentifier, TraverseCtx}; use crate::Helper; -use super::ClassProperties; +use super::{utils::create_assignment, ClassProperties}; impl<'a, 'ctx> ClassProperties<'a, 'ctx> { /// Transform static member expression where object is `super`. @@ -271,6 +271,244 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { } } + /// Transform update expression where the argument is a member expression with `super`. + /// + /// * `++super.prop` or `super.prop--` + /// See [`Self::transform_update_expression_for_super_static_member_expr`] + /// + /// * `++super[prop]` or `super[prop]--` + /// See [`Self::transform_update_expression_for_super_computed_member_expr`] + // + // `#[inline]` so can bail out fast without a function call if `argument` 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_update_expression_for_super_assignment_target( + &mut self, + expr: &mut Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + let Expression::UpdateExpression(update_expr) = expr else { unreachable!() }; + + match &update_expr.argument { + SimpleAssignmentTarget::StaticMemberExpression(member) if member.object.is_super() => { + self.transform_update_expression_for_super_static_member_expr(expr, ctx); + } + SimpleAssignmentTarget::ComputedMemberExpression(member) + if member.object.is_super() => + { + self.transform_update_expression_for_super_computed_member_expr(expr, ctx); + } + _ => {} + }; + } + + /// Transform update expression (`++` or `--`) where argument is a static member expression + /// with `super`. + /// + /// * `++super.prop` -> + /// ```js + /// _superPropSet( + /// _Outer, + /// "prop", + /// ( + /// _super$prop = _superPropGet(_Outer, "prop", _Outer), + /// ++_super$prop + /// ), + /// _Outer, + /// 1 + /// ) + /// ``` + /// + /// * `super.prop--` -> + /// ```js + /// ( + /// _superPropSet( + /// _Outer, + /// "prop", + /// ( + /// _super$prop = _superPropGet(_Outer, "prop", _Outer), + /// _super$prop2 = _super$prop--, + /// _super$prop + /// ), + /// _Outer, + /// 1 + /// ), + /// _super$prop2 + /// ) + /// ``` + /// + // `#[inline]` so that compiler sees that `expr` is an `Expression::UpdateExpression`. + #[inline] + fn transform_update_expression_for_super_static_member_expr( + &mut self, + expr: &mut Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + let Expression::UpdateExpression(mut update_expr) = ctx.ast.move_expression(expr) else { + unreachable!() + }; + let SimpleAssignmentTarget::StaticMemberExpression(member) = &mut update_expr.argument + else { + unreachable!() + }; + + let temp_binding = + self.ctx.var_declarations.create_uid_var_based_on_node(member.as_ref(), ctx); + let property = ctx.ast.expression_string_literal( + member.property.span, + member.property.name.clone(), + None, + ); + *expr = + self.transform_super_update_expression_impl(&temp_binding, update_expr, property, ctx); + } + + /// Transform update expression (`++` or `--`) where argument is a computed member expression + /// with `super`. + /// + /// * `++super[prop]` -> + /// ```js + /// _superPropSet( + /// _Outer, + /// prop, + /// ( + /// _super$prop = _superPropGet(_Outer, prop, _Outer), + /// ++_super$prop + /// ), + /// _Outer, + /// 1 + /// ) + /// ``` + /// + /// * `super[prop]--` -> + /// ```js + /// ( + /// _superPropSet( + /// _Outer, + /// prop, + /// ( + /// _super$prop = _superPropGet(_Outer, prop, _Outer), + /// _super$prop2 = _super$prop--, + /// _super$prop + /// ), + /// _Outer, + /// 1 + /// ), + /// _super$prop2 + /// ) + /// ``` + // + // `#[inline]` so that compiler sees that `expr` is an `Expression::UpdateExpression`. + #[inline] + fn transform_update_expression_for_super_computed_member_expr( + &mut self, + expr: &mut Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + let Expression::UpdateExpression(mut update_expr) = ctx.ast.move_expression(expr) else { + unreachable!() + }; + let SimpleAssignmentTarget::ComputedMemberExpression(member) = &mut update_expr.argument + else { + unreachable!() + }; + let temp_binding = + self.ctx.var_declarations.create_uid_var_based_on_node(member.as_ref(), ctx); + let property = ctx.ast.move_expression(&mut member.expression); + *expr = + self.transform_super_update_expression_impl(&temp_binding, update_expr, property, ctx); + } + + /// Transform update expression (`++` or `--`) where argument is a member expression with `super`. + /// + /// * `++super[prop]` -> + /// ```js + /// _superPropSet( + /// _Outer, + /// prop, + /// ( + /// _super$prop = _superPropGet(_Outer, prop, _Outer), + /// ++_super$prop + /// ), + /// _Outer, + /// 1 + /// ) + /// ``` + /// + /// * `super[prop]--` -> + /// ```js + /// ( + /// _superPropSet( + /// _Outer, + /// prop, + /// ( + /// _super$prop = _superPropGet(_Outer, prop, _Outer), + /// _super$prop2 = _super$prop--, + /// _super$prop + /// ), + /// _Outer, + /// 1 + /// ), + /// _super$prop2 + /// ) + /// ``` + fn transform_super_update_expression_impl( + &mut self, + temp_binding: &BoundIdentifier<'a>, + mut update_expr: ArenaBox<'a, UpdateExpression<'a>>, + property: Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Expression<'a> { + // Make 2 copies of `property` + let (property1, property2) = self.duplicate_object(property, ctx); + + // `_superPropGet(_Class, prop, _Class)` + let get_call = self.create_super_prop_get(SPAN, property2, false, ctx); + + // `_super$prop = _superPropGet(_Class, prop, _Class)` + let assignment = create_assignment(temp_binding, get_call, ctx); + + // `++_super$prop` / `_super$prop++` (reusing existing `UpdateExpression`) + let span = update_expr.span; + let prefix = update_expr.prefix; + update_expr.span = SPAN; + update_expr.argument = temp_binding.create_read_write_simple_target(ctx); + let update_expr = Expression::UpdateExpression(update_expr); + + if prefix { + // Source = `++super$prop` (prefix `++`) + // `(_super$prop = _superPropGet(_Class, prop, _Class), ++_super$prop)` + let value = ctx + .ast + .expression_sequence(SPAN, ctx.ast.vec_from_array([assignment, update_expr])); + // `_superPropSet(_Class, prop, value, _Class, 1)` + self.create_super_prop_set(span, property1, value, ctx) + } else { + // Source = `super.prop++` (postfix `++`) + // `_super$prop2 = _super$prop++` + let temp_binding2 = self.ctx.var_declarations.create_uid_var(&temp_binding.name, ctx); + let assignment2 = create_assignment(&temp_binding2, update_expr, ctx); + + // `(_super$prop = _superPropGet(_Class, prop, _Class), _super$prop2 = _super$prop++, _super$prop)` + let value = ctx.ast.expression_sequence( + SPAN, + ctx.ast.vec_from_array([ + assignment, + assignment2, + temp_binding.create_read_expression(ctx), + ]), + ); + + // `_superPropSet(_Class, prop, value, _Class, 1)` + let set_call = self.create_super_prop_set(span, property1, value, ctx); + // `(_superPropSet(_Class, prop, value, _Class, 1), _super$prop2)` + ctx.ast.expression_sequence( + span, + ctx.ast.vec_from_array([set_call, temp_binding2.create_read_expression(ctx)]), + ) + } + } + /// Member: /// `_superPropGet(_Class, prop, _Class)` /// diff --git a/tasks/transform_conformance/snapshots/oxc.snap.md b/tasks/transform_conformance/snapshots/oxc.snap.md index 1a266bed558b9..3dd1e4728187a 100644 --- a/tasks/transform_conformance/snapshots/oxc.snap.md +++ b/tasks/transform_conformance/snapshots/oxc.snap.md @@ -1,6 +1,6 @@ commit: 54a8389f -Passed: 113/128 +Passed: 114/129 # All Passed: * babel-plugin-transform-class-static-block @@ -16,7 +16,7 @@ Passed: 113/128 * regexp -# babel-plugin-transform-class-properties (14/17) +# babel-plugin-transform-class-properties (15/18) * static-super-tagged-template/input.js x Output mismatch diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-class-properties/test/fixtures/static-super-update-expression/input.js b/tasks/transform_conformance/tests/babel-plugin-transform-class-properties/test/fixtures/static-super-update-expression/input.js new file mode 100644 index 0000000000000..8f06a383907c6 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-class-properties/test/fixtures/static-super-update-expression/input.js @@ -0,0 +1,59 @@ +let bound = "A"; + +class Outer { + static B = () => { + // Transform + super.A++; + super.A--; + ++super.A; + --super.A; + + super[bound]++; + super[bound]--; + ++super[bound]; + --super[bound]; + + super[unbound]++; + super[unbound]--; + ++super[unbound]; + --super[unbound]; + + class Inner { + method() { + // Don't transform + super.A++; + super.A--; + ++super.A; + --super.A; + + super[bound]++; + super[bound]--; + ++super[bound]; + --super[bound]; + + super[unbound]++; + super[unbound]--; + ++super[unbound]; + --super[unbound]; + } + + static staticMethod() { + // Don't transform + super.A++; + super.A--; + ++super.A; + --super.A; + + super[bound]++; + super[bound]--; + ++super[bound]; + --super[bound]; + + super[unbound]++; + super[unbound]--; + ++super[unbound]; + --super[unbound]; + } + } + }; +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-class-properties/test/fixtures/static-super-update-expression/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-class-properties/test/fixtures/static-super-update-expression/output.js new file mode 100644 index 0000000000000..7bebd5629e7eb --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-class-properties/test/fixtures/static-super-update-expression/output.js @@ -0,0 +1,62 @@ +var _super$A, _super$A2, _super$A3, _super$A4, _super$A5, _super$A6, _super$bound, _super$bound2, _super$bound3, _super$bound4, _super$bound5, _super$bound6, _super$unbound, _unbound, _super$unbound2, _super$unbound3, _unbound2, _super$unbound4, _super$unbound5, _unbound3, _super$unbound6, _unbound4, _Outer; + +let bound = "A"; + +class Outer {} + +_Outer = Outer; +babelHelpers.defineProperty(Outer, "B", () => { + // Transform + babelHelpers.superPropSet(_Outer, "A", (_super$A = babelHelpers.superPropGet(_Outer, "A", _Outer), _super$A2 = _super$A++, _super$A), _Outer, 1), _super$A2; + babelHelpers.superPropSet(_Outer, "A", (_super$A3 = babelHelpers.superPropGet(_Outer, "A", _Outer), _super$A4 = _super$A3--, _super$A3), _Outer, 1), _super$A4; + babelHelpers.superPropSet(_Outer, "A", (_super$A5 = babelHelpers.superPropGet(_Outer, "A", _Outer), ++_super$A5), _Outer, 1); + babelHelpers.superPropSet(_Outer, "A", (_super$A6 = babelHelpers.superPropGet(_Outer, "A", _Outer), --_super$A6), _Outer, 1); + + babelHelpers.superPropSet(_Outer, bound, (_super$bound = babelHelpers.superPropGet(_Outer, bound, _Outer), _super$bound2 = _super$bound++, _super$bound), _Outer, 1), _super$bound2; + babelHelpers.superPropSet(_Outer, bound, (_super$bound3 = babelHelpers.superPropGet(_Outer, bound, _Outer), _super$bound4 = _super$bound3--, _super$bound3), _Outer, 1), _super$bound4; + babelHelpers.superPropSet(_Outer, bound, (_super$bound5 = babelHelpers.superPropGet(_Outer, bound, _Outer), ++_super$bound5), _Outer, 1); + babelHelpers.superPropSet(_Outer, bound, (_super$bound6 = babelHelpers.superPropGet(_Outer, bound, _Outer), --_super$bound6), _Outer, 1); + + babelHelpers.superPropSet(_Outer, _unbound = unbound, (_super$unbound = babelHelpers.superPropGet(_Outer, _unbound, _Outer), _super$unbound2 = _super$unbound++, _super$unbound), _Outer, 1), _super$unbound2; + babelHelpers.superPropSet(_Outer, _unbound2 = unbound, (_super$unbound3 = babelHelpers.superPropGet(_Outer, _unbound2, _Outer), _super$unbound4 = _super$unbound3--, _super$unbound3), _Outer, 1), _super$unbound4; + babelHelpers.superPropSet(_Outer, _unbound3 = unbound, (_super$unbound5 = babelHelpers.superPropGet(_Outer, _unbound3, _Outer), ++_super$unbound5), _Outer, 1); + babelHelpers.superPropSet(_Outer, _unbound4 = unbound, (_super$unbound6 = babelHelpers.superPropGet(_Outer, _unbound4, _Outer), --_super$unbound6), _Outer, 1); + + class Inner { + method() { + // Don't transform + super.A++; + super.A--; + ++super.A; + --super.A; + + super[bound]++; + super[bound]--; + ++super[bound]; + --super[bound]; + + super[unbound]++; + super[unbound]--; + ++super[unbound]; + --super[unbound]; + } + + static staticMethod() { + // Don't transform + super.A++; + super.A--; + ++super.A; + --super.A; + + super[bound]++; + super[bound]--; + ++super[bound]; + --super[bound]; + + super[unbound]++; + super[unbound]--; + ++super[unbound]; + --super[unbound]; + } + } +});