fix(transformer/arrow-functions): visit arguments to super() call (#8494)

Follow-on after #8482.

Fix an edge case in arrow functions transform when inserting `_this = this` after `super()`. It is possible (though bizarre) for `super()` to contain another `super()` call. This will throw an error when evaluating the outer `super()`, but it can still be observable in some cases.

```js
let f;
class S {}
class C extends S {
  constructor(x) {
    super(super(), this.x = x, f = async () => this);
  }
}

try { new C(123) } catch {}

const c = await f();
assert(c.x === 123);
```

So, before bailing out from searching for more `super()`s in class constructor, visit the `super()` call's arguments.
This commit is contained in:
overlookmotel 2025-01-15 02:07:05 +00:00
parent ca02c16f8f
commit 99635330c7
6 changed files with 65 additions and 14 deletions

View file

@ -1313,18 +1313,27 @@ impl<'a> VisitMut<'a> for ConstructorBodyThisAfterSuperInserter<'a, '_> {
/// `super();` -> `super(); _this = this;`
fn visit_statements(&mut self, statements: &mut ArenaVec<'a, Statement<'a>>) {
for (index, stmt) in statements.iter_mut().enumerate() {
if matches!(stmt, Statement::ExpressionStatement(expr_stmt) if expr_stmt.expression.is_super_call_expression())
{
let assignment = self.create_assignment_to_this_temp_var();
let assignment = self.ctx.ast.statement_expression(SPAN, assignment);
statements.insert(index + 1, assignment);
// `super();` found as top-level statement in this block of statements.
// No need to continue visiting later statements, because `_this` is definitely assigned
// to at this point - no need to assign to it again.
// This means we don't visit the whole constructor in the common case where `super();`
// appears as a top-level statement early in class constructor
// `constructor() { super(); blah; blah; blah; }`.
break;
if let Statement::ExpressionStatement(expr_stmt) = stmt {
if let Expression::CallExpression(call_expr) = &mut expr_stmt.expression {
if matches!(&call_expr.callee, Expression::Super(_)) {
// Visit arguments in `super(x, y, z)` call.
// Required to handle edge case `super(super(), f = () => this)`.
self.visit_arguments(&mut call_expr.arguments);
// Insert `_this = this;` after `super();`
let assignment = self.create_assignment_to_this_temp_var();
let assignment = self.ctx.ast.statement_expression(SPAN, assignment);
statements.insert(index + 1, assignment);
// `super();` found as top-level statement in this block of statements.
// No need to continue visiting later statements, because `_this` is definitely
// assigned to at this point - no need to assign to it again.
// This means we don't visit the whole constructor in the common case where
// `super();` appears as a top-level statement early in class constructor
// `constructor() { super(); blah; blah; blah; }`.
break;
}
}
}
self.visit_statement(stmt);

View file

@ -1,6 +1,6 @@
commit: 54a8389f
Passed: 129/151
Passed: 130/152
# All Passed:
* babel-plugin-transform-class-static-block

View file

@ -2,7 +2,7 @@ commit: 54a8389f
node: v22.12.0
Passed: 3 of 5 (60.00%)
Passed: 4 of 6 (66.67%)
Failures:

View file

@ -0,0 +1,16 @@
let f;
class S {}
class C extends S {
constructor(x) {
super(super(), this.x = x, f = async () => this);
}
}
try { new C(123) } catch {}
return f().then((c) => {
expect(c).toBeInstanceOf(C);
expect(c.x).toBe(123);
});

View file

@ -0,0 +1,9 @@
let f;
class S {}
class C extends S {
constructor(x) {
super(super(), this.x = x, f = async () => this);
}
}

View file

@ -0,0 +1,17 @@
let f;
class S {}
class C extends S {
constructor(x) {
var _this;
super(
(super(), _this = this),
this.x = x,
f = babelHelpers.asyncToGenerator(function* () {
return _this;
})
);
_this = this;
}
}