mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
fix(transformer/arrow-functions): _this = this should be inserted after super call expression (#8024)
related: #7792 This PR doesn't contain fixing the async arrow function in `super()`, which is difficult for our architecture. I just found that `esbuild`'s implementation is quite simpler! https://esbuild.github.io/try/#dAAwLjI0LjAALS10YXJnZXQ9ZXMyMDE2AGNsYXNzIEMgZXh0ZW5kcyBTIHsKICBjb25zdHJ1Y3RvcigpIHsKICAgIHN1cGVyKGFzeW5jICgpID0+IHRoaXMpOwogICAgYXN5bmMoKSA9PiB7fQogIH0KfQ
This commit is contained in:
parent
051fbb6909
commit
775a289a55
14 changed files with 290 additions and 55 deletions
|
|
@ -92,10 +92,10 @@ use indexmap::IndexMap;
|
|||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
|
||||
use oxc_allocator::{Box as ArenaBox, Vec as ArenaVec};
|
||||
use oxc_ast::{ast::*, NONE};
|
||||
use oxc_data_structures::stack::{NonEmptyStack, SparseStack};
|
||||
use oxc_ast::{ast::*, visit::walk_mut::walk_expression, VisitMut, NONE};
|
||||
use oxc_data_structures::stack::{NonEmptyStack, SparseStack, Stack};
|
||||
use oxc_semantic::{ReferenceFlags, SymbolId};
|
||||
use oxc_span::{CompactStr, SPAN};
|
||||
use oxc_span::{CompactStr, GetSpan, SPAN};
|
||||
use oxc_syntax::{
|
||||
scope::{ScopeFlags, ScopeId},
|
||||
symbol::SymbolFlags,
|
||||
|
|
@ -139,6 +139,7 @@ pub struct ArrowFunctionConverter<'a> {
|
|||
mode: ArrowFunctionConverterMode,
|
||||
this_var_stack: SparseStack<BoundIdentifier<'a>>,
|
||||
arguments_var_stack: SparseStack<BoundIdentifier<'a>>,
|
||||
constructor_super_stack: Stack<bool>,
|
||||
arguments_needs_transform_stack: NonEmptyStack<bool>,
|
||||
renamed_arguments_symbol_ids: FxHashSet<SymbolId>,
|
||||
// TODO(improve-on-babel): `FxHashMap` would suffice here. Iteration order is not important.
|
||||
|
|
@ -160,6 +161,7 @@ impl ArrowFunctionConverter<'_> {
|
|||
mode,
|
||||
this_var_stack: SparseStack::new(),
|
||||
arguments_var_stack: SparseStack::new(),
|
||||
constructor_super_stack: Stack::new(),
|
||||
arguments_needs_transform_stack: NonEmptyStack::new(false),
|
||||
renamed_arguments_symbol_ids: FxHashSet::default(),
|
||||
super_methods: None,
|
||||
|
|
@ -200,6 +202,7 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
|
|||
|
||||
self.this_var_stack.push(None);
|
||||
self.arguments_var_stack.push(None);
|
||||
self.constructor_super_stack.push(false);
|
||||
if self.is_async_only() && func.r#async && Self::is_class_method_like_ancestor(ctx.parent())
|
||||
{
|
||||
self.super_methods = Some(FxIndexMap::default());
|
||||
|
|
@ -235,6 +238,7 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
|
|||
arguments_var,
|
||||
ctx,
|
||||
);
|
||||
self.constructor_super_stack.pop();
|
||||
}
|
||||
|
||||
fn enter_arrow_function_expression(
|
||||
|
|
@ -331,6 +335,12 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
|
|||
Expression::ThisExpression(this) => {
|
||||
self.get_this_identifier(this.span, ctx).map(Expression::Identifier)
|
||||
}
|
||||
Expression::Super(_) => {
|
||||
if let Some(v) = self.constructor_super_stack.last_mut() {
|
||||
*v = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
Expression::CallExpression(call) => self.transform_call_expression_for_super(call, ctx),
|
||||
Expression::AssignmentExpression(assignment) => {
|
||||
self.transform_assignment_expression_for_super(assignment, ctx)
|
||||
|
|
@ -1033,12 +1043,9 @@ impl<'a> ArrowFunctionConverter<'a> {
|
|||
let arguments = self.create_arguments_var_declarator(target_scope_id, arguments_var, ctx);
|
||||
|
||||
let is_class_method_like = Self::is_class_method_like_ancestor(ctx.parent());
|
||||
let super_method_count = self.super_methods.as_ref().map_or(0, FxIndexMap::len);
|
||||
let declarations_count = usize::from(arguments.is_some())
|
||||
+ if is_class_method_like {
|
||||
self.super_methods.as_ref().map_or(0, FxIndexMap::len)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
+ if is_class_method_like { super_method_count } else { 0 }
|
||||
+ usize::from(this_var.is_some());
|
||||
|
||||
// Exit if no declarations to be inserted
|
||||
|
|
@ -1070,12 +1077,25 @@ impl<'a> ArrowFunctionConverter<'a> {
|
|||
|
||||
// `_this = this;`
|
||||
if let Some(this_var) = this_var {
|
||||
let is_constructor = ctx.scopes().get_flags(target_scope_id).is_constructor();
|
||||
let init = if is_constructor
|
||||
&& (super_method_count != 0
|
||||
|| self.constructor_super_stack.last().copied().unwrap_or(false))
|
||||
{
|
||||
// `super()` is called in the constructor body, so we need to insert `_this = this;`
|
||||
// after `super()` call. Because `this` is not available before `super()` call.
|
||||
ConstructorBodyThisAfterSuperInserter::new(&this_var, ctx)
|
||||
.visit_statements(statements);
|
||||
None
|
||||
} else {
|
||||
Some(Expression::ThisExpression(ctx.ast.alloc_this_expression(SPAN)))
|
||||
};
|
||||
Self::adjust_binding_scope(target_scope_id, &this_var, ctx);
|
||||
let variable_declarator = ctx.ast.variable_declarator(
|
||||
SPAN,
|
||||
VariableDeclarationKind::Var,
|
||||
this_var.create_binding_pattern(ctx),
|
||||
Some(ctx.ast.expression_this(SPAN)),
|
||||
init,
|
||||
false,
|
||||
);
|
||||
declarations.push(variable_declarator);
|
||||
|
|
@ -1095,3 +1115,74 @@ impl<'a> ArrowFunctionConverter<'a> {
|
|||
statements.insert(0, stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor for inserting `this` after `super` in constructor body.
|
||||
struct ConstructorBodyThisAfterSuperInserter<'a, 'arrow> {
|
||||
this_var_binding: &'arrow BoundIdentifier<'a>,
|
||||
ctx: &'arrow mut TraverseCtx<'a>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> ConstructorBodyThisAfterSuperInserter<'a, 'b> {
|
||||
fn new(this_var_binding: &'b BoundIdentifier<'a>, ctx: &'b mut TraverseCtx<'a>) -> Self {
|
||||
Self { this_var_binding, ctx }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn transform_super_call_expression(&mut self, expr: &Expression<'a>) -> Option<Expression<'a>> {
|
||||
if expr.is_super_call_expression() {
|
||||
let assignment = self.ctx.ast.expression_assignment(
|
||||
SPAN,
|
||||
AssignmentOperator::Assign,
|
||||
self.this_var_binding.create_write_target(self.ctx),
|
||||
self.ctx.ast.expression_this(SPAN),
|
||||
);
|
||||
Some(assignment)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VisitMut<'a> for ConstructorBodyThisAfterSuperInserter<'a, '_> {
|
||||
#[inline]
|
||||
fn visit_class(&mut self, _class: &mut Class<'a>) {
|
||||
// Do not need to insert in nested classes
|
||||
}
|
||||
|
||||
/// `super()` -> `super(); _this = this;`
|
||||
#[inline]
|
||||
fn visit_statements(&mut self, statements: &mut ArenaVec<'a, Statement<'a>>) {
|
||||
let mut new_stmts = vec![];
|
||||
|
||||
for (index, stmt) in statements.iter_mut().enumerate() {
|
||||
let Statement::ExpressionStatement(expr_stmt) = stmt else {
|
||||
self.visit_statement(stmt);
|
||||
continue;
|
||||
};
|
||||
if let Some(assignment) = self.transform_super_call_expression(&expr_stmt.expression) {
|
||||
let new_stmt = self.ctx.ast.statement_expression(SPAN, assignment);
|
||||
new_stmts.push((index, new_stmt));
|
||||
} else {
|
||||
self.visit_statement(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
for (index, new_stmt) in new_stmts.into_iter().rev() {
|
||||
// insert the new statement after the super call
|
||||
statements.insert(index + 1, new_stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/// `const A = super()` -> `const A = (super(), _this = this);`
|
||||
#[inline]
|
||||
fn visit_expression(&mut self, expr: &mut Expression<'a>) {
|
||||
if let Some(assignment) = self.transform_super_call_expression(expr) {
|
||||
let span = expr.span();
|
||||
let exprs =
|
||||
self.ctx.ast.vec_from_array([self.ctx.ast.move_expression(expr), assignment]);
|
||||
*expr = self.ctx.ast.expression_sequence(span, exprs);
|
||||
} else {
|
||||
walk_expression(self, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"plugins": [
|
||||
[
|
||||
"transform-class-properties",
|
||||
{
|
||||
"loose": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
var _scopedFunctionWithThis = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("scopedFunctionWithThis");
|
||||
class Child extends Parent {
|
||||
constructor() {
|
||||
super();
|
||||
Object.defineProperty(this, _scopedFunctionWithThis, {
|
||||
writable: true,
|
||||
value: () => {
|
||||
this.name = {};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
Disable arrow functions transform in `options.json` because it malfunctions.
|
||||
But these fixtures aren't to test arrow functions transform.
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"plugins": [
|
||||
[
|
||||
"transform-class-properties",
|
||||
{
|
||||
"loose": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
class Child extends Parent {
|
||||
constructor() {
|
||||
super();
|
||||
this.scopedFunctionWithThis = () => {
|
||||
this.name = {};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
Disable arrow functions transform in `options.json` because it malfunctions.
|
||||
But these fixtures aren't to test arrow functions transform.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
commit: 54a8389f
|
||||
|
||||
Passed: 119/138
|
||||
Passed: 122/141
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
|
|
@ -45,7 +45,7 @@ after transform: SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), R
|
|||
rebuilt : SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), ReferenceId(10)]
|
||||
|
||||
|
||||
# babel-plugin-transform-async-to-generator (14/15)
|
||||
# babel-plugin-transform-async-to-generator (17/18)
|
||||
* super/nested/input.js
|
||||
x Output mismatch
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
class Outer {
|
||||
constructor() {
|
||||
async () => { return [this, 1]; };
|
||||
|
||||
class Inner extends Outer {
|
||||
constructor() {
|
||||
if (condition) {
|
||||
const _super = super();
|
||||
this.fn = async () => { return [this, 2]; };
|
||||
}
|
||||
|
||||
super();
|
||||
async () => { return [this, 3]; };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Outer2 {
|
||||
constructor() {
|
||||
async () => [this, 4];
|
||||
|
||||
class Inner extends Outer2 {
|
||||
constructor() {
|
||||
if (condition) {
|
||||
const _super = super();
|
||||
this.fn = async () => [this, 5];
|
||||
}
|
||||
|
||||
super();
|
||||
async () => [this, 6];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
class Outer {
|
||||
constructor() {
|
||||
var _this = this;
|
||||
babelHelpers.asyncToGenerator(function* () {
|
||||
return [_this, 1];
|
||||
});
|
||||
|
||||
class Inner extends Outer {
|
||||
constructor() {
|
||||
var _this2;
|
||||
|
||||
if (condition) {
|
||||
const _super = (super(), _this2 = this);
|
||||
this.fn = babelHelpers.asyncToGenerator(function* () {
|
||||
return [_this2, 2];
|
||||
});
|
||||
}
|
||||
|
||||
super();
|
||||
_this2 = this;
|
||||
babelHelpers.asyncToGenerator(function* () {
|
||||
return [_this2, 3];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Outer2 {
|
||||
constructor() {
|
||||
var _this3 = this;
|
||||
babelHelpers.asyncToGenerator(function* () {
|
||||
return [_this3, 4];
|
||||
});
|
||||
|
||||
class Inner extends Outer2 {
|
||||
constructor() {
|
||||
var _this4;
|
||||
|
||||
if (condition) {
|
||||
const _super = (super(), _this4 = this);
|
||||
this.fn = babelHelpers.asyncToGenerator(function* () {
|
||||
return [_this4, 5];
|
||||
});
|
||||
}
|
||||
|
||||
super();
|
||||
_this4 = this;
|
||||
babelHelpers.asyncToGenerator(function* () {
|
||||
return [_this4, 6];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
class S {}
|
||||
|
||||
class C extends S {
|
||||
constructor() {
|
||||
super(async () => {
|
||||
this;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class C2 extends S {
|
||||
constructor() {
|
||||
super(async () => this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
class S {}
|
||||
|
||||
class C extends S {
|
||||
constructor() {
|
||||
var _this;
|
||||
super(babelHelpers.asyncToGenerator(function* () {
|
||||
_this;
|
||||
}));
|
||||
_this = this;
|
||||
}
|
||||
}
|
||||
|
||||
class C2 extends S {
|
||||
constructor() {
|
||||
var _this2;
|
||||
super(babelHelpers.asyncToGenerator(function* () {
|
||||
return _this2;
|
||||
}));
|
||||
_this2 = this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
class S {}
|
||||
|
||||
class C extends S {
|
||||
constructor() {
|
||||
if (true) {
|
||||
const _super = super();
|
||||
this.fn = async () => { return [this, 1]; };
|
||||
}
|
||||
|
||||
super();
|
||||
async () => { return [this, 2]; };
|
||||
}
|
||||
}
|
||||
|
||||
class C2 extends S {
|
||||
constructor() {
|
||||
if (true) {
|
||||
const _super = super();
|
||||
this.fn = async () => [this, 1];
|
||||
}
|
||||
|
||||
super();
|
||||
async () => [this, 2];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
class S {}
|
||||
|
||||
class C extends S {
|
||||
constructor() {
|
||||
var _this;
|
||||
if (true) {
|
||||
const _super = (super(), _this = this);
|
||||
this.fn = babelHelpers.asyncToGenerator(function* () {
|
||||
return [_this, 1];
|
||||
});
|
||||
}
|
||||
|
||||
super();
|
||||
_this = this;
|
||||
babelHelpers.asyncToGenerator(function* () {
|
||||
return [_this, 2];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class C2 extends S {
|
||||
constructor() {
|
||||
var _this2;
|
||||
if (true) {
|
||||
const _super = (super(), _this2 = this);
|
||||
this.fn = babelHelpers.asyncToGenerator(function* () {
|
||||
return [_this2, 1];
|
||||
});
|
||||
}
|
||||
|
||||
super();
|
||||
_this2 = this;
|
||||
babelHelpers.asyncToGenerator(function* () {
|
||||
return [_this2, 2];
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue