Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(transformer/class-properties): transform super update expressions within static prop initializer #7997

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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(_) => {
Expand Down Expand Up @@ -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);
}
}
}
244 changes: 241 additions & 3 deletions crates/oxc_transformer/src/es2022/class_properties/supers.rs
Original file line number Diff line number Diff line change
@@ -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`.
Expand Down Expand Up @@ -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)`
///
Expand Down
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: 113/128
Passed: 114/129

# All Passed:
* babel-plugin-transform-class-static-block
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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];
}
}
};
}
Loading
Loading