refactor(transformer/async-generator-functions): simplify identifying whether within an async generator function (#7170)

Follow-on after stack up to #7148.

Split `is_inside_async_generator_function` into 2 functions `yield_is_inside_async_generator_function` and `async_is_inside_async_generator_function`.

There are a couple of differences between `yield` and `await`, which we can leverage to make both these functions simpler:

* `yield` cannot appear at top level (there's no such thing as "top level yield").
* `yield` cannot appear in an arrow function (there's no such thing as a "generator arrow function").

In addition:

* Because each function now handles a specific case, no need to check if function containing `async` is an async function. It must be. Ditto for `yield`.
* Remove the `if ctx.current_scope_flags().is_top()` check. Probably this check costs more than it saves because top level `await` is far less common than `await` within async functions. So this check was optimizing for an uncommon case, at a cost to the common case.
* Add more comments to explain the logic.

This is all quite small optimizations, but I was having difficulty understanding the logic, and found that splitting it up made it clearer (at least for me).
This commit is contained in:
overlookmotel 2024-11-06 16:06:51 +00:00
parent 64b7e3a603
commit 84ee581980

View file

@ -157,32 +157,13 @@ impl<'a, 'ctx> Traverse<'a> for AsyncGeneratorFunctions<'a, 'ctx> {
}
impl<'a, 'ctx> AsyncGeneratorFunctions<'a, 'ctx> {
/// Check whether the current node is inside an async generator function.
fn is_inside_async_generator_function(ctx: &mut TraverseCtx<'a>) -> bool {
// Early return if current scope is top because we don't need to transform top-level await expression.
if ctx.current_scope_flags().is_top() {
return false;
}
for ancestor in ctx.ancestors() {
match ancestor {
Ancestor::FunctionBody(func) => return *func.r#async() && *func.generator(),
Ancestor::ArrowFunctionExpressionBody(_) => {
return false;
}
_ => {}
}
}
false
}
/// Transform `yield * argument` expression to `yield asyncGeneratorDelegate(asyncIterator(argument))`.
fn transform_yield_expression(
&self,
expr: &mut YieldExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
if !expr.delegate || !Self::is_inside_async_generator_function(ctx) {
if !expr.delegate || !Self::yield_is_inside_async_generator_function(ctx) {
return None;
}
@ -196,6 +177,19 @@ impl<'a, 'ctx> AsyncGeneratorFunctions<'a, 'ctx> {
})
}
/// Check whether `yield` is inside an async generator function.
fn yield_is_inside_async_generator_function(ctx: &TraverseCtx<'a>) -> bool {
for ancestor in ctx.ancestors() {
// Note: `yield` cannot appear within function params.
// Also cannot appear in arrow functions because no such thing as a generator arrow function.
if let Ancestor::FunctionBody(func) = ancestor {
return *func.r#async();
}
}
// `yield` can only appear in a function
unreachable!();
}
/// Transforms `await expr` expression to `yield awaitAsyncGenerator(expr)`.
/// Ignores top-level await expression.
fn transform_await_expression(
@ -203,7 +197,7 @@ impl<'a, 'ctx> AsyncGeneratorFunctions<'a, 'ctx> {
expr: &mut AwaitExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
if !Self::is_inside_async_generator_function(ctx) {
if !Self::async_is_inside_async_generator_function(ctx) {
return None;
}
@ -213,4 +207,23 @@ impl<'a, 'ctx> AsyncGeneratorFunctions<'a, 'ctx> {
Some(ctx.ast.expression_yield(SPAN, false, Some(argument)))
}
/// Check whether `await` is inside an async generator function.
fn async_is_inside_async_generator_function(ctx: &TraverseCtx<'a>) -> bool {
for ancestor in ctx.ancestors() {
match ancestor {
// Note: `await` cannot appear within function params
Ancestor::FunctionBody(func) => {
return *func.generator();
}
Ancestor::ArrowFunctionExpressionBody(_) => {
// Arrow functions can't be generator functions
return false;
}
_ => {}
}
}
// Top level await
false
}
}