mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
fix(transformer): gate this capture by owning async transform (#26227)
For targets that support async functions but not async generators, plain async methods remain native. The shared arrow converter still captured `this` whenever either async transform was active, injecting an observable `_this` binding into methods whose bodies never move.
Track async-to-generator and async-generator lowering separately. Plain async methods are now left untouched when only async generators require lowering, while transformed async generator methods still preserve their receiver.
## Example
With a Chrome 55 target, this method previously gained a generated binding:
```js
async method() {
var _this = this;
void _this;
return eval("typeof _this");
}
```
It now remains unchanged, so the direct evaluation returns `"undefined"` as it does in the source program.
Verified with a Chrome 55 target output/runtime fixture, full transformer output conformance, and `cargo test -p oxc_transformer`.
AI assistance was used to diagnose the transform boundary, implement the fix, and add tests. I remain responsible for reviewing and understanding the changes before merge.
This commit is contained in:
1 parent
affeb14f5a
commit
789969f8a1
7 files changed
+66
-8
No files matched your search
@@ -145,6 +145,8 @@ struct SuperMethodInfo<'a> {
|
||||
|
||||
pub struct ArrowFunctionConverter<'a> {
|
||||
mode: ArrowFunctionConverterMode,
|
||||
async_to_generator_enabled: bool,
|
||||
async_generator_functions_enabled: bool,
|
||||
this_var_stack: SparseStack<BoundIdentifier<'a>>,
|
||||
arguments_var_stack: SparseStack<BoundIdentifier<'a>>,
|
||||
constructor_super_stack: NonEmptyStack<bool>,
|
||||
@@ -158,9 +160,11 @@ pub struct ArrowFunctionConverter<'a> {
|
||||
|
||||
impl ArrowFunctionConverter<'_> {
|
||||
pub fn new(env: &EnvOptions) -> Self {
|
||||
let async_to_generator_enabled = env.es2017.async_to_generator;
|
||||
let async_generator_functions_enabled = env.es2018.async_generator_functions;
|
||||
let mode = if env.es2015.arrow_function.is_some() {
|
||||
ArrowFunctionConverterMode::Enabled
|
||||
} else if env.es2017.async_to_generator || env.es2018.async_generator_functions {
|
||||
} else if async_to_generator_enabled || async_generator_functions_enabled {
|
||||
ArrowFunctionConverterMode::AsyncOnly
|
||||
} else {
|
||||
ArrowFunctionConverterMode::Disabled
|
||||
@@ -168,6 +172,8 @@ impl ArrowFunctionConverter<'_> {
|
||||
// `SparseStack`s are created with 1 empty entry, for `Program`
|
||||
Self {
|
||||
mode,
|
||||
async_to_generator_enabled,
|
||||
async_generator_functions_enabled,
|
||||
this_var_stack: SparseStack::new(),
|
||||
arguments_var_stack: SparseStack::new(),
|
||||
constructor_super_stack: NonEmptyStack::new(false),
|
||||
@@ -630,13 +636,13 @@ impl<'a> ArrowFunctionConverter<'a> {
|
||||
}
|
||||
// Function body (includes class method or object method)
|
||||
Ancestor::FunctionBody(func) => {
|
||||
// If we're inside a class async method or an object async method, and `is_async_only` is true,
|
||||
// the `AsyncToGenerator` or `AsyncGeneratorFunctions` plugin will move the body
|
||||
// of the method into a new generator function. This transformation can cause `this`
|
||||
// to point to the wrong context.
|
||||
// If we're inside a class async method or an object async method, and an
|
||||
// async transform will move the method body into a new generator function,
|
||||
// `this` will point to the wrong context.
|
||||
// To prevent this issue, we replace `this` with `_this`, treating it similarly
|
||||
// to how we handle arrow functions. Therefore, we return the `ScopeId` of the function.
|
||||
return if self.is_async_only()
|
||||
return if (self.async_to_generator_enabled
|
||||
|| (self.async_generator_functions_enabled && *func.generator()))
|
||||
&& *func.r#async()
|
||||
&& Self::is_class_method_like_ancestor(
|
||||
ancestors.next().unwrap()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
commit: 1eac4481
|
||||
|
||||
Passed: 271/399
|
||||
Passed: 272/400
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
|
||||
@@ -2,7 +2,7 @@ commit: 1eac4481
|
||||
|
||||
node: v26.5.0
|
||||
|
||||
Passed: 15 of 17 (88.24%)
|
||||
Passed: 16 of 18 (88.89%)
|
||||
|
||||
Failures:
|
||||
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
const component = {
|
||||
value: 42,
|
||||
async method() {
|
||||
this.value;
|
||||
return eval("typeof _this");
|
||||
},
|
||||
async *generatorMethod() {
|
||||
yield this.value;
|
||||
},
|
||||
};
|
||||
|
||||
return component.method().then(function (value) {
|
||||
expect(value).toBe("undefined");
|
||||
return component.generatorMethod().next();
|
||||
}).then(function (result) {
|
||||
expect(result.value).toBe(42);
|
||||
});
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
const component = {
|
||||
value: 42,
|
||||
async method() {
|
||||
this.value;
|
||||
return eval("typeof _this");
|
||||
},
|
||||
async *generatorMethod() {
|
||||
yield this.value;
|
||||
},
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"chrome": "55"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
const component = {
|
||||
value: 42,
|
||||
async method() {
|
||||
this.value;
|
||||
return eval("typeof _this");
|
||||
},
|
||||
generatorMethod() {
|
||||
var _this = this;
|
||||
return babelHelpers.wrapAsyncGenerator(function* () {
|
||||
yield _this.value;
|
||||
})();
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user