mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(transformer/class-properties): exit faster from super replacement visitor (#8028)
When inserting instance property initializers into class constructor, need to search for and transform `super()`. Exit that visit earlier in some cases, for better performance and smaller output.
This commit is contained in:
parent
de4c7726f9
commit
897a1a8946
6 changed files with 146 additions and 39 deletions
|
|
@ -682,30 +682,43 @@ impl<'a, 'c> ConstructorBodySuperReplacer<'a, 'c> {
|
||||||
mut self,
|
mut self,
|
||||||
constructor: &mut Function<'a>,
|
constructor: &mut Function<'a>,
|
||||||
) -> (ScopeId, InstanceInitsInsertLocation<'a>) {
|
) -> (ScopeId, InstanceInitsInsertLocation<'a>) {
|
||||||
|
// This is not a real loop. It always breaks on 1st iteration.
|
||||||
|
// Only here so that can break out of it from within inner `for` loop.
|
||||||
|
#[expect(clippy::never_loop)]
|
||||||
|
'outer: loop {
|
||||||
let body_stmts = &mut constructor.body.as_mut().unwrap().statements;
|
let body_stmts = &mut constructor.body.as_mut().unwrap().statements;
|
||||||
let mut body_stmts_iter = body_stmts.iter_mut();
|
for (index, stmt) in body_stmts.iter_mut().enumerate() {
|
||||||
|
|
||||||
loop {
|
|
||||||
let mut body_stmts_iter_enumerated = body_stmts_iter.by_ref().enumerate();
|
|
||||||
if let Some((index, stmt)) = body_stmts_iter_enumerated.next() {
|
|
||||||
// If statement is standalone `super()`, insert inits after `super()`.
|
// If statement is standalone `super()`, insert inits after `super()`.
|
||||||
// We can avoid a `_super` function for this common case.
|
// We can avoid a `_super` function for this common case.
|
||||||
if let Statement::ExpressionStatement(expr_stmt) = &*stmt {
|
if let Statement::ExpressionStatement(expr_stmt) = stmt {
|
||||||
if let Expression::CallExpression(call_expr) = &expr_stmt.expression {
|
if let Expression::CallExpression(call_expr) = &mut expr_stmt.expression {
|
||||||
if call_expr.callee.is_super() {
|
if let Expression::Super(super_) = &call_expr.callee {
|
||||||
|
// Found `super()` as top-level statement
|
||||||
|
if self.super_binding.is_none() {
|
||||||
|
// This is the first `super()` found.
|
||||||
|
// So can just insert initializers after it - no need for `_super` function.
|
||||||
let insert_location =
|
let insert_location =
|
||||||
InstanceInitsInsertLocation::ExistingConstructor(index + 1);
|
InstanceInitsInsertLocation::ExistingConstructor(index + 1);
|
||||||
return (self.constructor_scope_id, insert_location);
|
return (self.constructor_scope_id, insert_location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `super()` was previously found in nested position before this.
|
||||||
|
// So we do need a `_super` function.
|
||||||
|
// But we don't need to look any further for any other `super()` calls,
|
||||||
|
// because calling `super()` after this would be an immediate error.
|
||||||
|
let span = super_.span;
|
||||||
|
self.replace_super(call_expr, span);
|
||||||
|
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traverse statement looking for `super()` deeper in the statement
|
// Traverse statement looking for `super()` deeper in the statement
|
||||||
self.visit_statement(stmt);
|
self.visit_statement(stmt);
|
||||||
if self.super_binding.is_some() {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
if self.super_binding.is_none() {
|
||||||
// No `super()` anywhere in constructor.
|
// No `super()` anywhere in constructor.
|
||||||
// This is weird, but legal code. It would be a runtime error if the class is constructed
|
// This is weird, but legal code. It would be a runtime error if the class is constructed
|
||||||
// (unless the constructor returns early).
|
// (unless the constructor returns early).
|
||||||
|
|
@ -716,34 +729,23 @@ impl<'a, 'c> ConstructorBodySuperReplacer<'a, 'c> {
|
||||||
// What we get is completely legal output and correct `Semantic`, just longer than it
|
// What we get is completely legal output and correct `Semantic`, just longer than it
|
||||||
// could be. But this should very rarely happen in practice, and minifier will delete
|
// could be. But this should very rarely happen in practice, and minifier will delete
|
||||||
// the `_super` function as dead code.
|
// the `_super` function as dead code.
|
||||||
|
// So set `super_binding` and exit the loop, so it's treated as if `super()` was found
|
||||||
|
// in a nested position.
|
||||||
// TODO: Delete the initializers instead.
|
// TODO: Delete the initializers instead.
|
||||||
let super_func_scope_id = self.create_super_func_scope();
|
self.super_binding = Some(self.create_super_binding());
|
||||||
let super_binding = self.create_super_binding();
|
|
||||||
let insert_location =
|
|
||||||
InstanceInitsInsertLocation::SuperFnInsideConstructor(super_binding);
|
|
||||||
return (super_func_scope_id, insert_location);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// `super()` found in nested position. There may be more `super()`s in constructor.
|
break;
|
||||||
// Convert them all to `_super()`.
|
|
||||||
for stmt in body_stmts_iter {
|
|
||||||
self.visit_statement(stmt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let super_func_scope_id = self.create_super_func_scope();
|
let super_func_scope_id = self.ctx.scopes_mut().add_scope(
|
||||||
let super_binding = self.super_binding.unwrap();
|
|
||||||
let insert_location = InstanceInitsInsertLocation::SuperFnInsideConstructor(super_binding);
|
|
||||||
(super_func_scope_id, insert_location)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create scope for `_super` function inside constructor body
|
|
||||||
fn create_super_func_scope(&mut self) -> ScopeId {
|
|
||||||
self.ctx.scopes_mut().add_scope(
|
|
||||||
Some(self.constructor_scope_id),
|
Some(self.constructor_scope_id),
|
||||||
NodeId::DUMMY,
|
NodeId::DUMMY,
|
||||||
ScopeFlags::Function | ScopeFlags::Arrow | ScopeFlags::StrictMode,
|
ScopeFlags::Function | ScopeFlags::Arrow | ScopeFlags::StrictMode,
|
||||||
)
|
);
|
||||||
|
let super_binding = self.super_binding.unwrap();
|
||||||
|
let insert_location = InstanceInitsInsertLocation::SuperFnInsideConstructor(super_binding);
|
||||||
|
(super_func_scope_id, insert_location)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
commit: 54a8389f
|
commit: 54a8389f
|
||||||
|
|
||||||
Passed: 114/130
|
Passed: 116/132
|
||||||
|
|
||||||
# All Passed:
|
# All Passed:
|
||||||
* babel-plugin-transform-class-static-block
|
* babel-plugin-transform-class-static-block
|
||||||
|
|
@ -16,7 +16,7 @@ Passed: 114/130
|
||||||
* regexp
|
* regexp
|
||||||
|
|
||||||
|
|
||||||
# babel-plugin-transform-class-properties (15/19)
|
# babel-plugin-transform-class-properties (17/21)
|
||||||
* static-super-assignment-target/input.js
|
* static-super-assignment-target/input.js
|
||||||
x Output mismatch
|
x Output mismatch
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
class C extends S {
|
||||||
|
prop = 1;
|
||||||
|
constructor() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
class C extends S {
|
||||||
|
constructor() {
|
||||||
|
var _super = (..._args) => (super(..._args), babelHelpers.defineProperty(this, "prop", 1), this);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
class C extends S {
|
||||||
|
prop = 1;
|
||||||
|
constructor() {
|
||||||
|
// Insert prop initializer after `super()`
|
||||||
|
super();
|
||||||
|
this.self = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class C2 extends S {
|
||||||
|
prop = 1;
|
||||||
|
constructor() {
|
||||||
|
class Inner extends S {
|
||||||
|
constructor() {
|
||||||
|
// Don't transform - different `super`
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert prop initializer after `super()`
|
||||||
|
super();
|
||||||
|
this.self = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class C3 extends S {
|
||||||
|
prop = 1;
|
||||||
|
constructor() {
|
||||||
|
if (condition) {
|
||||||
|
// Transform to `_super()`
|
||||||
|
super(1, 2);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform to `_super()`
|
||||||
|
super(3, 4);
|
||||||
|
|
||||||
|
if (condition2) {
|
||||||
|
// Don't transform
|
||||||
|
super(5, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't transform
|
||||||
|
super(7, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
class C extends S {
|
||||||
|
constructor() {
|
||||||
|
// Insert prop initializer after `super()`
|
||||||
|
super();
|
||||||
|
babelHelpers.defineProperty(this, "prop", 1);
|
||||||
|
this.self = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class C2 extends S {
|
||||||
|
constructor() {
|
||||||
|
class Inner extends S {
|
||||||
|
constructor() {
|
||||||
|
// Don't transform - different `super`
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert prop initializer after `super()`
|
||||||
|
super();
|
||||||
|
babelHelpers.defineProperty(this, "prop", 1);
|
||||||
|
this.self = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class C3 extends S {
|
||||||
|
constructor() {
|
||||||
|
var _super = (..._args) => (super(..._args), babelHelpers.defineProperty(this, "prop", 1), this);
|
||||||
|
|
||||||
|
if (condition) {
|
||||||
|
// Transform to `_super()`
|
||||||
|
_super(1, 2);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform to `_super()`
|
||||||
|
_super(3, 4);
|
||||||
|
|
||||||
|
if (condition2) {
|
||||||
|
// Don't transform
|
||||||
|
super(5, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't transform
|
||||||
|
super(7, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue