mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
fix(transformer/async-to-generator): move parameters to the inner generator function when they could throw errors (#8500)
The new implementation port from [esbuild](df815ac27b/internal/js_parser/js_parser_lower.go (L355-L467)), before from `Babel`.
Babel's transform implementation for the async method is incorrect because the async method should return a rejecting promise when it throws an error. Everything is good if the errors are thrown in the async method body, but the following case will throw an error in the parameters which causes the whole program crushed not a rejecting promise. So we should move the parameters to the inner generator function when the parameters could throw an error.
Input:
```js
class Cls {
// ReferenceError: Cannot access 'b' before initialization
async method(a = b, b = 0) {}
}
```
Before output
```js
class Cls {
method(a = b, b = 0) {
return babelHelpers.asyncToGenerator(function* () {})();
}
}
```
After output:
```js
class Cls {
method() {
// ReferenceError: Cannot access 'b' before initialization
return babelHelpers.asyncToGenerator(function* (a = b, b = 0) {}).apply(this, arguments);
}
}
```
No override tests because Babel doesn't cover this case.
This commit is contained in:
parent
3789d2faf9
commit
06ccb51fae
6 changed files with 288 additions and 2477 deletions
|
|
@ -220,23 +220,70 @@ impl<'a, 'ctx> AsyncGeneratorExecutor<'a, 'ctx> {
|
|||
return;
|
||||
};
|
||||
|
||||
// If parameters could throw errors, we need to move them to the inner function,
|
||||
// because it is an async function, which should return a rejecting promise if
|
||||
// there is an error.
|
||||
let needs_move_parameters_to_inner_function =
|
||||
Self::could_throw_errors_parameters(&func.params);
|
||||
|
||||
let (generator_scope_id, wrapper_scope_id) = {
|
||||
let new_scope_id = ctx.create_child_scope(ctx.current_scope_id(), ScopeFlags::Function);
|
||||
let scope_id = func.scope_id.replace(Some(new_scope_id)).unwrap();
|
||||
// We need to change the parent id to new scope id because we need to this function's body inside the wrapper function,
|
||||
// and then the new scope id will be wrapper function's scope id.
|
||||
ctx.scopes_mut().change_parent_id(scope_id, Some(new_scope_id));
|
||||
// We need to transform formal parameters change back to the original scope,
|
||||
// because we only move out the function body.
|
||||
Self::move_formal_parameters_to_target_scope(new_scope_id, &func.params, ctx);
|
||||
if !needs_move_parameters_to_inner_function {
|
||||
// We need to change formal parameters's scope back to the original scope,
|
||||
// because we only move out the function body.
|
||||
Self::move_formal_parameters_to_target_scope(new_scope_id, &func.params, ctx);
|
||||
}
|
||||
|
||||
(scope_id, new_scope_id)
|
||||
};
|
||||
|
||||
let params = Self::create_empty_params(ctx);
|
||||
let expression = self.create_async_to_generator_call(params, body, generator_scope_id, ctx);
|
||||
// Construct the IIFE
|
||||
let expression = ctx.ast.expression_call(SPAN, expression, NONE, ctx.ast.vec(), false);
|
||||
let params = if needs_move_parameters_to_inner_function {
|
||||
// Make sure to not change the value of the "length" property. This is
|
||||
// done by generating dummy arguments for the outer function equal to
|
||||
// the expected length of the function:
|
||||
//
|
||||
// async function foo(a, b, c = d, ...e) {
|
||||
// }
|
||||
//
|
||||
// This turns into:
|
||||
//
|
||||
// function foo(_x, _x1) {
|
||||
// return _asyncToGenerator(function* (a, b, c = d, ...e) {
|
||||
// }).call(this, arguments);
|
||||
// }
|
||||
//
|
||||
// The "_x" and "_x1" are dummy variables to ensure "foo.length" is 2.
|
||||
let new_params = Self::create_placeholder_params(&func.params, wrapper_scope_id, ctx);
|
||||
mem::replace(&mut func.params, new_params)
|
||||
} else {
|
||||
Self::create_empty_params(ctx)
|
||||
};
|
||||
|
||||
let callee = self.create_async_to_generator_call(params, body, generator_scope_id, ctx);
|
||||
let (callee, arguments) = if needs_move_parameters_to_inner_function {
|
||||
// callee.apply(this, arguments)
|
||||
let property = ctx.ast.identifier_name(SPAN, "apply");
|
||||
let callee =
|
||||
Expression::from(ctx.ast.member_expression_static(SPAN, callee, property, false));
|
||||
|
||||
// this, arguments
|
||||
let this_argument = Argument::from(ctx.ast.expression_this(SPAN));
|
||||
let arguments_argument = Argument::from(ctx.create_unbound_ident_expr(
|
||||
SPAN,
|
||||
Atom::new_const("arguments"),
|
||||
ReferenceFlags::Read,
|
||||
));
|
||||
(callee, ctx.ast.vec_from_iter([this_argument, arguments_argument]))
|
||||
} else {
|
||||
// callee()
|
||||
(callee, ctx.ast.vec())
|
||||
};
|
||||
|
||||
let expression = ctx.ast.expression_call(SPAN, callee, NONE, arguments, false);
|
||||
let statement = ctx.ast.statement_return(SPAN, Some(expression));
|
||||
|
||||
// Modify the wrapper function
|
||||
|
|
@ -788,6 +835,32 @@ impl<'a, 'ctx> AsyncGeneratorExecutor<'a, 'ctx> {
|
|||
params.items.first().is_some_and(|param| !param.pattern.kind.is_assignment_pattern())
|
||||
}
|
||||
|
||||
/// Check whether the function parameters could throw errors.
|
||||
#[inline]
|
||||
fn could_throw_errors_parameters(params: &FormalParameters<'a>) -> bool {
|
||||
params.items.iter().any(|param|
|
||||
matches!(
|
||||
¶m.pattern.kind,
|
||||
BindingPatternKind::AssignmentPattern(pattern) if Self::could_potentially_throw_error_expression(&pattern.right)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// Check whether the expression could potentially throw an error.
|
||||
#[inline]
|
||||
fn could_potentially_throw_error_expression(expr: &Expression<'a>) -> bool {
|
||||
!(matches!(
|
||||
expr,
|
||||
Expression::NullLiteral(_)
|
||||
| Expression::BooleanLiteral(_)
|
||||
| Expression::NumericLiteral(_)
|
||||
| Expression::StringLiteral(_)
|
||||
| Expression::BigIntLiteral(_)
|
||||
| Expression::ArrowFunctionExpression(_)
|
||||
| Expression::FunctionExpression(_)
|
||||
) || expr.is_undefined())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn move_formal_parameters_to_target_scope(
|
||||
target_scope_id: ScopeId,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ commit: c4317b0c
|
|||
|
||||
runtime Summary:
|
||||
AST Parsed : 18055/18055 (100.00%)
|
||||
Positive Passed: 17718/18055 (98.13%)
|
||||
Positive Passed: 17736/18055 (98.23%)
|
||||
tasks/coverage/test262/test/language/expressions/assignment/fn-name-lhs-cover.js
|
||||
codegen error: Test262Error: descriptor value should be ; object value should be
|
||||
|
||||
|
|
@ -213,24 +213,6 @@ transform error: Test262Error: reject reason Expected SameValue(«TypeError: The
|
|||
tasks/coverage/test262/test/language/expressions/class/async-gen-method-static/yield-star-sync-throw.js
|
||||
transform error: throw-arg-1
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/class/async-method/dflt-params-abrupt.js
|
||||
transform error: Test262Error:
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/class/async-method/dflt-params-ref-later.js
|
||||
transform error: ReferenceError: Cannot access 'y' before initialization
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/class/async-method/dflt-params-ref-self.js
|
||||
transform error: ReferenceError: Cannot access 'x' before initialization
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/class/async-method-static/dflt-params-abrupt.js
|
||||
transform error: Test262Error:
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/class/async-method-static/dflt-params-ref-later.js
|
||||
transform error: ReferenceError: Cannot access 'y' before initialization
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/class/async-method-static/dflt-params-ref-self.js
|
||||
transform error: ReferenceError: Cannot access 'x' before initialization
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/class/elements/arrow-body-derived-cls-direct-eval-err-contains-supercall-1.js
|
||||
transform error: Test262Error: Expected a SyntaxError but got a ReferenceError
|
||||
|
||||
|
|
@ -549,21 +531,6 @@ transform error: Test262Error: reject reason Expected SameValue(«TypeError: The
|
|||
tasks/coverage/test262/test/language/expressions/object/method-definition/async-gen-yield-star-sync-throw.js
|
||||
transform error: throw-arg-1
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/object/method-definition/async-meth-dflt-params-abrupt.js
|
||||
transform error: Test262Error:
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/object/method-definition/async-meth-dflt-params-ref-later.js
|
||||
transform error: ReferenceError: Cannot access 'y' before initialization
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/object/method-definition/async-meth-dflt-params-ref-self.js
|
||||
transform error: ReferenceError: Cannot access 'x' before initialization
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/object/method-definition/async-meth-eval-var-scope-syntax-err.js
|
||||
transform error: SyntaxError: Identifier 'a' has already been declared
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/object/method-definition/async-super-call-param.js
|
||||
transform error: ReferenceError: _superprop_getMethod is not defined
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/object/object-spread-proxy-get-not-called-on-dontenum-keys.js
|
||||
transform error: Test262Error: Actual [dontEnumString, 0, enumerableString, 1, Symbol(dont_enum_symbol), Symbol(enumerable_symbol)] and expected [Symbol(dont_enum_symbol), dontEnumString, 0, Symbol(enumerable_symbol), enumerableString, 1] should have the same contents.
|
||||
|
||||
|
|
@ -702,27 +669,6 @@ transform error: Test262Error: reject reason Expected SameValue(«TypeError: The
|
|||
tasks/coverage/test262/test/language/statements/class/async-gen-method-static/yield-star-sync-throw.js
|
||||
transform error: throw-arg-1
|
||||
|
||||
tasks/coverage/test262/test/language/statements/class/async-method/dflt-params-abrupt.js
|
||||
transform error: Test262Error:
|
||||
|
||||
tasks/coverage/test262/test/language/statements/class/async-method/dflt-params-ref-later.js
|
||||
transform error: ReferenceError: Cannot access 'y' before initialization
|
||||
|
||||
tasks/coverage/test262/test/language/statements/class/async-method/dflt-params-ref-self.js
|
||||
transform error: ReferenceError: Cannot access 'x' before initialization
|
||||
|
||||
tasks/coverage/test262/test/language/statements/class/async-method-static/dflt-params-abrupt.js
|
||||
transform error: Test262Error:
|
||||
|
||||
tasks/coverage/test262/test/language/statements/class/async-method-static/dflt-params-ref-later.js
|
||||
transform error: ReferenceError: Cannot access 'y' before initialization
|
||||
|
||||
tasks/coverage/test262/test/language/statements/class/async-method-static/dflt-params-ref-self.js
|
||||
transform error: ReferenceError: Cannot access 'x' before initialization
|
||||
|
||||
tasks/coverage/test262/test/language/statements/class/definition/methods-async-super-call-param.js
|
||||
transform error: ReferenceError: _superprop_getMethod is not defined
|
||||
|
||||
tasks/coverage/test262/test/language/statements/class/elements/arrow-body-derived-cls-direct-eval-err-contains-supercall-1.js
|
||||
transform error: Test262Error: Expected a SyntaxError but got a ReferenceError
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
commit: acbc09a8
|
||||
|
||||
Passed: 131/153
|
||||
Passed: 132/154
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
class Cls {
|
||||
// ReferenceError: Cannot access 'b' before initialization
|
||||
async method(a = b, b = 0) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
class Cls {
|
||||
method() {
|
||||
return babelHelpers.asyncToGenerator(function* (a = b, b = 0) {}).apply(this, arguments);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue