mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-15 06:39:17 +00:00
fix(transformer): gate async-only rewrites by owning transform (#26326)
For targets that support async functions but not async generators, async-only converter mode still treated every async function as if the enabled transform would move its body. Plain async arrows and methods gained generated `this` and `super` bindings, and class-field arrows were wrapped unnecessarily.
Use the shared body-movement predicate for every async-only rewrite. Functions left native by the selected target now remain untouched, while async generator functions that are lowered keep their required lexical captures.
## Example
A plain async method using `super` previously gained a generated `_superprop_getValue` binding even though the method remained native. This made `eval("typeof _superprop_getValue")` return `"function"` instead of `"undefined"`. The method now remains unchanged.
Verified with focused output/runtime conformance, full transformer output conformance, and `cargo test -p oxc_transformer`.
Stacked on #26317.
AI assistance was used to diagnose the remaining transform boundaries, implement the predicate checks, and add tests. I remain responsible for reviewing and understanding the changes before merge.
This commit is contained in:
1 parent
f45a9ee58b
commit
3d95477214
7 files changed
+123
-8
No files matched your search
@@ -233,7 +233,8 @@ impl<'a> Traverse<'a, TransformState<'a>> for ArrowFunctionConverter<'a> {
|
||||
|
||||
if Self::is_class_method_like_ancestor(ctx.parent()) {
|
||||
self.super_methods_stack.push(FxIndexMap::default());
|
||||
self.super_needs_transform_stack.push(func.r#async);
|
||||
self.super_needs_transform_stack
|
||||
.push(self.will_transform_async_function(func.r#async, func.generator));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,8 +290,10 @@ impl<'a> Traverse<'a, TransformState<'a>> for ArrowFunctionConverter<'a> {
|
||||
self.this_var_stack.push(None);
|
||||
self.super_methods_stack.push(FxIndexMap::default());
|
||||
}
|
||||
self.super_needs_transform_stack
|
||||
.push(arrow.r#async || *self.super_needs_transform_stack.last());
|
||||
self.super_needs_transform_stack.push(
|
||||
self.will_transform_async_function(arrow.r#async, false)
|
||||
|| *self.super_needs_transform_stack.last(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,7 +432,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for ArrowFunctionConverter<'a> {
|
||||
Expression::ArrowFunctionExpression(arrow)
|
||||
// TODO: If the async arrow function without `this` or `super` usage, we can skip this step.
|
||||
if self.is_async_only()
|
||||
&& arrow.r#async
|
||||
&& self.will_transform_async_function(arrow.r#async, false)
|
||||
&& Self::in_class_property_definition_value(ctx)
|
||||
=> {
|
||||
// Inside class property definition value, since async arrow function will be
|
||||
@@ -631,7 +634,9 @@ impl<'a> ArrowFunctionConverter<'a> {
|
||||
| Ancestor::StaticBlockBody(_) => return None,
|
||||
// Arrow function
|
||||
Ancestor::ArrowFunctionExpressionParams(func) => {
|
||||
return if self.is_async_only() && !*func.r#async() {
|
||||
return if self.is_async_only()
|
||||
&& !self.will_transform_async_function(*func.r#async(), false)
|
||||
{
|
||||
// Continue checking the parent to see if it's inside an async function.
|
||||
continue;
|
||||
} else {
|
||||
@@ -639,7 +644,9 @@ impl<'a> ArrowFunctionConverter<'a> {
|
||||
};
|
||||
}
|
||||
Ancestor::ArrowFunctionExpressionBody(func) => {
|
||||
return if self.is_async_only() && !*func.r#async() {
|
||||
return if self.is_async_only()
|
||||
&& !self.will_transform_async_function(*func.r#async(), false)
|
||||
{
|
||||
// Continue checking the parent to see if it's inside an async function.
|
||||
continue;
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
commit: 1eac4481
|
||||
|
||||
Passed: 273/401
|
||||
Passed: 274/402
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
|
||||
@@ -2,7 +2,7 @@ commit: 1eac4481
|
||||
|
||||
node: v26.5.0
|
||||
|
||||
Passed: 17 of 19 (89.47%)
|
||||
Passed: 18 of 20 (90.00%)
|
||||
|
||||
Failures:
|
||||
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
function outer() {
|
||||
return async () => {
|
||||
void this;
|
||||
return eval("typeof _this");
|
||||
};
|
||||
}
|
||||
|
||||
class Base {
|
||||
get value() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
class Derived extends Base {
|
||||
field = async () => {};
|
||||
|
||||
async direct() {
|
||||
super.value;
|
||||
return eval("typeof _superprop_getValue");
|
||||
}
|
||||
|
||||
arrow() {
|
||||
return async () => {
|
||||
super.value;
|
||||
return eval("typeof _superprop_getValue2");
|
||||
};
|
||||
}
|
||||
|
||||
async *generator() {
|
||||
yield super.value;
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new Derived();
|
||||
return Promise.all([outer()(), instance.direct(), instance.arrow()(), instance.generator().next()]).then(function (results) {
|
||||
expect(results[0]).toBe("undefined");
|
||||
expect(results[1]).toBe("undefined");
|
||||
expect(results[2]).toBe("undefined");
|
||||
expect(results[3].value).toBe(1);
|
||||
expect(instance.field.name).toBe("field");
|
||||
});
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
function outer() {
|
||||
return async () => {
|
||||
void this;
|
||||
return eval("typeof _this");
|
||||
};
|
||||
}
|
||||
|
||||
class Base {
|
||||
get value() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
class Derived extends Base {
|
||||
field = async () => {};
|
||||
|
||||
async direct() {
|
||||
super.value;
|
||||
return eval("typeof _superprop_getValue");
|
||||
}
|
||||
|
||||
arrow() {
|
||||
return async () => {
|
||||
super.value;
|
||||
return eval("typeof _superprop_getValue");
|
||||
};
|
||||
}
|
||||
|
||||
async *generator() {
|
||||
yield super.value;
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"plugins": [
|
||||
"transform-async-generator-functions"
|
||||
]
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
function outer() {
|
||||
return async () => {
|
||||
void this;
|
||||
return eval("typeof _this");
|
||||
};
|
||||
}
|
||||
class Base {
|
||||
get value() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
class Derived extends Base {
|
||||
field = async () => {};
|
||||
async direct() {
|
||||
super.value;
|
||||
return eval("typeof _superprop_getValue");
|
||||
}
|
||||
arrow() {
|
||||
return async () => {
|
||||
super.value;
|
||||
return eval("typeof _superprop_getValue");
|
||||
};
|
||||
}
|
||||
generator() {
|
||||
var _superprop_getValue = () => super.value;
|
||||
return babelHelpers.wrapAsyncGenerator(function* () {
|
||||
yield _superprop_getValue();
|
||||
})();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user