mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(transformer/class-properties): transform super assignment expressions 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
This commit is contained in:
parent
e3d0889009
commit
cc57db38e1
7 changed files with 295 additions and 4 deletions
|
|
@ -159,6 +159,7 @@ pub enum Helper {
|
|||
ClassPrivateFieldLooseKey,
|
||||
ClassPrivateFieldLooseBase,
|
||||
SuperPropGet,
|
||||
SuperPropSet,
|
||||
}
|
||||
|
||||
impl Helper {
|
||||
|
|
@ -183,6 +184,7 @@ impl Helper {
|
|||
Self::ClassPrivateFieldLooseKey => "classPrivateFieldLooseKey",
|
||||
Self::ClassPrivateFieldLooseBase => "classPrivateFieldLooseBase",
|
||||
Self::SuperPropGet => "superPropGet",
|
||||
Self::SuperPropSet => "superPropSet",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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(_) => {
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)`
|
||||
///
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
commit: 54a8389f
|
||||
|
||||
Passed: 112/127
|
||||
Passed: 113/128
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
Loading…
Reference in a new issue