mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(transformer/async-to-generator): reparent parameter initializer scopes (#22507)
Fixes a semantic mismatch in `async_to_generator` for async methods with parameter default initializers.
Before this change, the transform reused the original method scope as the inner generator scope and moved parameter bindings to the wrapper scope. Scopes created inside parameter defaults were left parented under the generator, even though the transformed AST places those defaults with the wrapper parameters.
For example:
```js
class C {
async method(value = function fallback() {}) {}
}
```
Conceptually this became:
```js
class C {
method(value = function fallback() {}) {
// `value` belongs to this wrapper scope.
return asyncToGenerator(function* () {
// Before: the scope for `fallback` was still parented here,
// because this generator reused the original async method scope.
})();
}
}
```
A fresh semantic rebuild from the transformed AST sees it differently:
```js
class C {
method(value = function fallback() {
// After rebuild: `fallback` is created from the parameter default,
// so its direct parent is the wrapper method scope.
}) {
return asyncToGenerator(function* () {
// The generator should only own body scopes.
})();
}
}
```
This updates `BindingMover` so that when parameter bindings move to the wrapper, direct scopes created inside parameter-side expressions are reparented there too. It still stops at function/class scope boundaries, so nested body scopes are not traversed or moved.
This commit is contained in:
1 parent
4c9ca72b5f
commit
c73c159e16
3 files changed
+39
-791
No files matched your search
@@ -915,13 +915,21 @@ impl<'a, 'ctx> BindingMover<'a, 'ctx> {
|
||||
fn new(target_scope_id: ScopeId, ctx: &'ctx mut TraverseCtx<'a>) -> Self {
|
||||
Self { ctx, target_scope_id }
|
||||
}
|
||||
|
||||
fn move_scope_to_target(&mut self, scope_id: ScopeId) {
|
||||
self.ctx.scoping_mut().change_scope_parent_id(scope_id, Some(self.target_scope_id));
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visit<'a> for BindingMover<'a, '_> {
|
||||
fn visit_formal_parameter(&mut self, param: &FormalParameter<'a>) {
|
||||
// Only move the parameter binding itself; initializer expressions can contain their own
|
||||
// function/class scopes whose bindings must stay where semantic analysis placed them.
|
||||
// Move the parameter binding itself, then only reparent direct scopes from the initializer.
|
||||
// Initializer expressions can contain function/class bindings that must stay in their own
|
||||
// scopes, so they are not binding-moved.
|
||||
self.visit_binding_pattern(¶m.pattern);
|
||||
if let Some(initializer) = ¶m.initializer {
|
||||
self.visit_expression(initializer);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_formal_parameter_rest(&mut self, param: &FormalParameterRest<'a>) {
|
||||
@@ -931,17 +939,39 @@ impl<'a> Visit<'a> for BindingMover<'a, '_> {
|
||||
}
|
||||
|
||||
fn visit_assignment_pattern(&mut self, pattern: &AssignmentPattern<'a>) {
|
||||
// Default values are expressions, not parameter bindings. Visiting only the left-hand side
|
||||
// avoids moving bindings declared inside the initializer expression.
|
||||
// Move only the left-hand binding. The right-hand default is an expression, so only direct
|
||||
// scopes inside it are reparented; bindings declared inside those scopes stay there.
|
||||
self.visit_binding_pattern(&pattern.left);
|
||||
self.visit_expression(&pattern.right);
|
||||
}
|
||||
|
||||
fn visit_binding_property(&mut self, property: &BindingProperty<'a>) {
|
||||
// Computed keys are expressions and can contain their own scopes. Only the property value
|
||||
// is a binding position.
|
||||
// Computed keys are expressions, not binding positions. They can still contain direct
|
||||
// scopes that moved with the parameter list.
|
||||
if property.computed {
|
||||
self.visit_property_key(&property.key);
|
||||
}
|
||||
self.visit_binding_pattern(&property.value);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_function(&mut self, func: &Function<'a>, _flags: ScopeFlags) {
|
||||
self.move_scope_to_target(func.scope_id());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_arrow_function_expression(&mut self, func: &ArrowFunctionExpression<'a>) {
|
||||
self.move_scope_to_target(func.scope_id());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_class(&mut self, class: &Class<'a>) {
|
||||
// Decorators are evaluated outside the class scope and may contain direct scopes of their
|
||||
// own, so visit them before stopping at the class scope boundary.
|
||||
self.visit_decorators(&class.decorators);
|
||||
self.move_scope_to_target(class.scope_id());
|
||||
}
|
||||
|
||||
/// Visits a binding identifier and moves it to the target scope.
|
||||
fn visit_binding_identifier(&mut self, ident: &BindingIdentifier<'a>) {
|
||||
let symbol_id = ident.symbol_id();
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
||||
commit: 4079bcda
|
||||
|
||||
Passed: 234/382
|
||||
Passed: 235/382
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
@@ -11,6 +11,7 @@ Passed: 234/382
|
||||
* babel-plugin-transform-optional-catch-binding
|
||||
* babel-plugin-transform-async-generator-functions
|
||||
* babel-plugin-transform-object-rest-spread
|
||||
* babel-plugin-transform-async-to-generator
|
||||
* babel-plugin-transform-exponentiation-operator
|
||||
* babel-plugin-transform-arrow-functions
|
||||
* babel-preset-typescript
|
||||
@@ -62,13 +63,6 @@ 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 (27/28)
|
||||
* class/computed-key-binding/input.js
|
||||
Scope parent mismatch:
|
||||
after transform: ScopeId(3): Some(ScopeId(2))
|
||||
rebuilt : ScopeId(3): Some(ScopeId(2))
|
||||
|
||||
|
||||
# babel-plugin-transform-typescript (22/56)
|
||||
* allow-declare-fields-false/input.ts
|
||||
Unresolved references mismatch:
|
||||
|
||||
Reference in new issue
Block a user