Skip to content

Commit 479a96d

Browse files
committed
fix(transformer/arrow-functions): transform this and super incorrectly in async arrow function
1 parent 953d1b5 commit 479a96d

File tree

8 files changed

+211
-54
lines changed

8 files changed

+211
-54
lines changed

crates/oxc_transformer/src/common/arrow_function_converter.rs

+140-46
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ pub enum ArrowFunctionConverterMode {
119119
AsyncOnly,
120120
}
121121

122-
#[derive(PartialEq, Eq, Hash)]
122+
#[derive(Debug, PartialEq, Eq, Hash)]
123123
struct SuperMethodKey<'a> {
124124
/// If it is true, the method should accept a value parameter.
125125
is_assignment: bool,
@@ -144,7 +144,8 @@ pub struct ArrowFunctionConverter<'a> {
144144
renamed_arguments_symbol_ids: FxHashSet<SymbolId>,
145145
// TODO(improve-on-babel): `FxHashMap` would suffice here. Iteration order is not important.
146146
// Only using `FxIndexMap` for predictable iteration order to match Babel's output.
147-
super_methods_stack: SparseStack<FxIndexMap<SuperMethodKey<'a>, SuperMethodInfo<'a>>>,
147+
super_methods_stack: NonEmptyStack<FxIndexMap<SuperMethodKey<'a>, SuperMethodInfo<'a>>>,
148+
super_needs_transform_stack: NonEmptyStack<bool>,
148149
}
149150

150151
impl ArrowFunctionConverter<'_> {
@@ -164,7 +165,8 @@ impl ArrowFunctionConverter<'_> {
164165
constructor_super_stack: NonEmptyStack::new(false),
165166
arguments_needs_transform_stack: NonEmptyStack::new(false),
166167
renamed_arguments_symbol_ids: FxHashSet::default(),
167-
super_methods_stack: SparseStack::new(),
168+
super_methods_stack: NonEmptyStack::new(FxIndexMap::default()),
169+
super_needs_transform_stack: NonEmptyStack::new(false),
168170
}
169171
}
170172
}
@@ -190,18 +192,19 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
190192
None,
191193
ctx,
192194
);
193-
194195
debug_assert!(self.this_var_stack.len() == 1);
195196
debug_assert!(self.this_var_stack.last().is_none());
196197
debug_assert!(self.arguments_var_stack.len() == 1);
197198
debug_assert!(self.arguments_var_stack.last().is_none());
198199
debug_assert!(self.constructor_super_stack.len() == 1);
199200
// TODO: This assertion currently failing because we don't handle `super` in arrow functions
200201
// in class static properties correctly.
201-
// e.g. `class C { static f = async () => super.prop; }`
202+
// e.g. `class C { static f = () => super.prop; }`
202203
// debug_assert!(self.constructor_super_stack.last() == &false);
203204
debug_assert!(self.super_methods_stack.len() == 1);
204-
debug_assert!(self.super_methods_stack.last().is_none());
205+
debug_assert!(self.super_methods_stack.last().is_empty());
206+
debug_assert!(self.super_needs_transform_stack.len() == 1);
207+
debug_assert!(self.super_needs_transform_stack.last() == &false);
205208
}
206209

207210
fn enter_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
@@ -213,32 +216,9 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
213216
self.arguments_var_stack.push(None);
214217
self.constructor_super_stack.push(false);
215218

216-
if self.is_async_only()
217-
&& (func.r#async || self.super_methods_stack.len() > 1)
218-
&& Self::is_class_method_like_ancestor(ctx.parent())
219-
{
220-
// `self.super_methods_stack.len() > 1` means we are in a nested class method
221-
//
222-
// Only `super` that inside async methods need to be transformed, if it is a
223-
// nested class method and it is not async, we still need to push a `None` to
224-
// `self.super_methods_stack`, because if we don't get a `FxIndexMap` from
225-
// `self.super_methods_stack.last_mut()`, that means we don't need to transform.
226-
// See how to transform `super` in `self.transform_member_expression_for_super`
227-
//
228-
// ```js
229-
// class Outer {
230-
// async method() {
231-
// class Inner extends Outer {
232-
// normal() {
233-
// // `super.value` should not be transformed, because it is not in an async method
234-
// super.value
235-
// }
236-
// }
237-
// }
238-
// }
239-
// ```
240-
let super_methods = if func.r#async { Some(FxIndexMap::default()) } else { None };
241-
self.super_methods_stack.push(super_methods);
219+
if Self::is_class_method_like_ancestor(ctx.parent()) {
220+
self.super_methods_stack.push(FxIndexMap::default());
221+
self.super_needs_transform_stack.push(func.r#async);
242222
}
243223
}
244224

@@ -264,14 +244,11 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
264244
};
265245
let this_var = self.this_var_stack.pop();
266246
let arguments_var = self.arguments_var_stack.pop();
267-
let super_methods = if self.is_async_only()
268-
&& (func.r#async || self.super_methods_stack.len() > 1)
269-
&& Self::is_class_method_like_ancestor(ctx.parent())
270-
{
247+
let super_methods = Self::is_class_method_like_ancestor(ctx.parent()).then(|| {
248+
self.super_needs_transform_stack.pop();
271249
self.super_methods_stack.pop()
272-
} else {
273-
None
274-
};
250+
});
251+
275252
self.insert_variable_statement_at_the_top_of_statements(
276253
scope_id,
277254
&mut body.statements,
@@ -286,11 +263,41 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
286263
fn enter_arrow_function_expression(
287264
&mut self,
288265
arrow: &mut ArrowFunctionExpression<'a>,
289-
_ctx: &mut TraverseCtx<'a>,
266+
ctx: &mut TraverseCtx<'a>,
290267
) {
291268
if self.is_async_only() {
292269
let previous = *self.arguments_needs_transform_stack.last();
293270
self.arguments_needs_transform_stack.push(previous || arrow.r#async);
271+
272+
if Self::if_ancestor_of_class_property_definition_value(ctx) {
273+
self.this_var_stack.push(None);
274+
self.super_methods_stack.push(FxIndexMap::default());
275+
}
276+
self.super_needs_transform_stack
277+
.push(arrow.r#async || *self.super_needs_transform_stack.last());
278+
}
279+
}
280+
281+
fn exit_arrow_function_expression(
282+
&mut self,
283+
arrow: &mut ArrowFunctionExpression<'a>,
284+
ctx: &mut TraverseCtx<'a>,
285+
) {
286+
if self.is_async_only() {
287+
if Self::if_ancestor_of_class_property_definition_value(ctx) {
288+
let this_var = self.this_var_stack.pop();
289+
let super_methods = self.super_methods_stack.pop();
290+
self.insert_variable_statement_at_the_top_of_statements(
291+
arrow.scope_id(),
292+
&mut arrow.body.statements,
293+
this_var,
294+
None,
295+
Some(super_methods),
296+
ctx,
297+
);
298+
}
299+
300+
self.super_needs_transform_stack.pop();
294301
}
295302
}
296303

@@ -318,6 +325,7 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
318325
}
319326

320327
self.this_var_stack.push(None);
328+
self.super_methods_stack.push(FxIndexMap::default());
321329
}
322330

323331
fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
@@ -326,14 +334,14 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
326334
}
327335

328336
let this_var = self.this_var_stack.pop();
337+
let super_methods = self.super_methods_stack.pop();
329338
self.insert_variable_statement_at_the_top_of_statements(
330339
block.scope_id(),
331340
&mut block.body,
332341
this_var,
333342
// `arguments` is not allowed to be used in static blocks
334343
None,
335-
// `super()` Only allowed in class constructor
336-
None,
344+
Some(super_methods),
337345
ctx,
338346
);
339347
}
@@ -390,6 +398,36 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
390398
match_member_expression!(Expression) => {
391399
self.transform_member_expression_for_super(expr, None, ctx)
392400
}
401+
Expression::ArrowFunctionExpression(arrow) => {
402+
// TODO: If the async arrow function without `this` or `super` usage, we can skip this step.
403+
if self.is_async_only()
404+
&& arrow.r#async
405+
&& Self::if_ancestor_of_class_property_definition_value(ctx)
406+
{
407+
// Inside class property definition value, since async arrow function will be
408+
// converted to a generator function by `AsyncToGenerator` plugin, ensure
409+
// `_this = this` and `super` methods are inserted correctly. We need to
410+
// wrap the async arrow function with an normal arrow function IIFE.
411+
//
412+
// ```js
413+
// class A {
414+
// prop = async () => {}
415+
// }
416+
// // to
417+
// class A {
418+
// prop = (() => { return async () => {} })();
419+
// }
420+
// ```
421+
Some(Self::wrap_arrow_function_with_iife(
422+
arrow.span,
423+
arrow.scope_id(),
424+
expr,
425+
ctx,
426+
))
427+
} else {
428+
return;
429+
}
430+
}
393431
_ => return,
394432
};
395433

@@ -404,7 +442,9 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
404442
}
405443

406444
if let Expression::ArrowFunctionExpression(arrow_function_expr) = expr {
407-
if self.is_async_only() && !arrow_function_expr.r#async {
445+
// TODO: Here should return early as long as the async-to-generator plugin is enabled,
446+
// but currently we don't know which plugin is enabled.
447+
if self.is_async_only() || arrow_function_expr.r#async {
408448
return;
409449
}
410450

@@ -640,6 +680,19 @@ impl<'a> ArrowFunctionConverter<'a> {
640680
}
641681
}
642682

683+
/// Check whether the ancestor is an [`Ancestor::PropertyDefinitionValue`],
684+
/// return false if it's reached the statement.
685+
fn if_ancestor_of_class_property_definition_value(ctx: &mut TraverseCtx<'a>) -> bool {
686+
for ancestor in ctx.ancestors() {
687+
if ancestor.is_parent_of_statement() {
688+
return false;
689+
} else if matches!(ancestor, Ancestor::PropertyDefinitionValue(_)) {
690+
return true;
691+
}
692+
}
693+
unreachable!()
694+
}
695+
643696
/// Transforms a `MemberExpression` whose object is a `super` expression.
644697
///
645698
/// In the [`AsyncToGenerator`](crate::es2017::AsyncToGenerator) and
@@ -679,7 +732,11 @@ impl<'a> ArrowFunctionConverter<'a> {
679732
assign_value: Option<&mut Expression<'a>>,
680733
ctx: &mut TraverseCtx<'a>,
681734
) -> Option<Expression<'a>> {
682-
let super_methods = self.super_methods_stack.last_mut()?;
735+
if !*self.super_needs_transform_stack.last() {
736+
return None;
737+
}
738+
739+
let super_methods = self.super_methods_stack.last_mut();
683740

684741
let mut argument = None;
685742
let mut property = "";
@@ -757,7 +814,7 @@ impl<'a> ArrowFunctionConverter<'a> {
757814
call: &mut CallExpression<'a>,
758815
ctx: &mut TraverseCtx<'a>,
759816
) -> Option<Expression<'a>> {
760-
if self.super_methods_stack.last().is_none() || !call.callee.is_member_expression() {
817+
if !*self.super_needs_transform_stack.last() || !call.callee.is_member_expression() {
761818
return None;
762819
}
763820

@@ -796,7 +853,7 @@ impl<'a> ArrowFunctionConverter<'a> {
796853
ctx: &mut TraverseCtx<'a>,
797854
) -> Option<Expression<'a>> {
798855
// Check if the left of the assignment is a `super` member expression.
799-
if self.super_methods_stack.last().is_none()
856+
if !*self.super_needs_transform_stack.last()
800857
|| !assignment.left.as_member_expression().is_some_and(|m| m.object().is_super())
801858
{
802859
return None;
@@ -1149,6 +1206,43 @@ impl<'a> ArrowFunctionConverter<'a> {
11491206

11501207
statements.insert(0, stmt);
11511208
}
1209+
1210+
/// Wrap an arrow function with IIFE
1211+
///
1212+
/// `() => {}` -> `(() => { return () => {}; })()`
1213+
fn wrap_arrow_function_with_iife(
1214+
span: Span,
1215+
scope_id: ScopeId,
1216+
expr: &mut Expression<'a>,
1217+
ctx: &mut TraverseCtx<'a>,
1218+
) -> Expression<'a> {
1219+
let kind = FormalParameterKind::ArrowFormalParameters;
1220+
let params = ctx.ast.formal_parameters(SPAN, kind, ctx.ast.vec(), NONE);
1221+
let statement = ctx.ast.statement_return(SPAN, Some(ctx.ast.move_expression(expr)));
1222+
let statements = ctx.ast.vec1(statement);
1223+
let body = ctx.ast.function_body(SPAN, ctx.ast.vec(), statements);
1224+
let parent_scope_id = ctx
1225+
.create_child_scope(ctx.current_scope_id(), ScopeFlags::Arrow | ScopeFlags::Function);
1226+
ctx.scopes_mut().change_parent_id(scope_id, Some(parent_scope_id));
1227+
let arrow = ctx.ast.alloc_arrow_function_expression_with_scope_id(
1228+
SPAN,
1229+
false,
1230+
false,
1231+
NONE,
1232+
params,
1233+
NONE,
1234+
body,
1235+
parent_scope_id,
1236+
);
1237+
// IIFE
1238+
ctx.ast.expression_call(
1239+
span,
1240+
Expression::ArrowFunctionExpression(arrow),
1241+
NONE,
1242+
ctx.ast.vec(),
1243+
false,
1244+
)
1245+
}
11521246
}
11531247

11541248
/// Visitor for inserting `this` after `super` in constructor body.

crates/oxc_transformer/src/common/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ impl<'a> Traverse<'a> for Common<'a, '_> {
8181
self.arrow_function_converter.enter_arrow_function_expression(arrow, ctx);
8282
}
8383

84+
fn exit_arrow_function_expression(
85+
&mut self,
86+
arrow: &mut ArrowFunctionExpression<'a>,
87+
ctx: &mut TraverseCtx<'a>,
88+
) {
89+
self.arrow_function_converter.exit_arrow_function_expression(arrow, ctx);
90+
}
91+
8492
fn enter_function_body(&mut self, body: &mut FunctionBody<'a>, ctx: &mut TraverseCtx<'a>) {
8593
self.arrow_function_converter.enter_function_body(body, ctx);
8694
}

crates/oxc_transformer/src/lib.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ impl<'a> Traverse<'a> for TransformerImpl<'a, '_> {
281281

282282
#[inline]
283283
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
284+
self.common.enter_expression(expr, ctx);
284285
if let Some(typescript) = self.x0_typescript.as_mut() {
285286
typescript.enter_expression(expr, ctx);
286287
}
@@ -290,15 +291,14 @@ impl<'a> Traverse<'a> for TransformerImpl<'a, '_> {
290291
self.x2_es2018.enter_expression(expr, ctx);
291292
self.x2_es2016.enter_expression(expr, ctx);
292293
self.x4_regexp.enter_expression(expr, ctx);
293-
self.common.enter_expression(expr, ctx);
294294
}
295295

296296
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
297+
self.common.exit_expression(expr, ctx);
297298
self.x1_jsx.exit_expression(expr, ctx);
298299
self.x2_es2022.exit_expression(expr, ctx);
299300
self.x2_es2018.exit_expression(expr, ctx);
300301
self.x2_es2017.exit_expression(expr, ctx);
301-
self.common.exit_expression(expr, ctx);
302302
}
303303

304304
fn enter_simple_assignment_target(
@@ -477,6 +477,8 @@ impl<'a> Traverse<'a> for TransformerImpl<'a, '_> {
477477
arrow: &mut ArrowFunctionExpression<'a>,
478478
ctx: &mut TraverseCtx<'a>,
479479
) {
480+
self.common.exit_arrow_function_expression(arrow, ctx);
481+
480482
// Some plugins may add new statements to the ArrowFunctionExpression's body,
481483
// which can cause issues with the `() => x;` case, as it only allows a single statement.
482484
// To address this, we wrap the last statement in a return statement and set the expression to false.

tasks/transform_conformance/snapshots/oxc.snap.md

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
commit: 54a8389f
22

3-
Passed: 126/145
3+
Passed: 129/147
44

55
# All Passed:
66
* babel-plugin-transform-class-static-block
@@ -10,6 +10,7 @@ Passed: 126/145
1010
* babel-plugin-transform-optional-catch-binding
1111
* babel-plugin-transform-async-generator-functions
1212
* babel-plugin-transform-object-rest-spread
13+
* babel-plugin-transform-async-to-generator
1314
* babel-plugin-transform-exponentiation-operator
1415
* babel-plugin-transform-arrow-functions
1516
* babel-preset-typescript
@@ -46,11 +47,6 @@ after transform: SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), R
4647
rebuilt : SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), ReferenceId(10)]
4748

4849

49-
# babel-plugin-transform-async-to-generator (20/21)
50-
* class/static-block/input.js
51-
x Output mismatch
52-
53-
5450
# babel-plugin-transform-typescript (2/10)
5551
* class-property-definition/input.ts
5652
Unresolved references mismatch:

0 commit comments

Comments
 (0)