Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(transformer/async-to-generator): move transform methods to AsyncGeneratorExecutor and make it public #6992

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 79 additions & 50 deletions crates/oxc_transformer/src/es2017/async_to_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,12 @@ use crate::{common::helper_loader::Helper, TransformCtx};

pub struct AsyncToGenerator<'a, 'ctx> {
ctx: &'ctx TransformCtx<'a>,
executor: AsyncGeneratorExecutor<'a, 'ctx>,
}

impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self {
Self { ctx }
Self { ctx, executor: AsyncGeneratorExecutor::new(Helper::AsyncToGenerator, ctx) }
}
}

Expand All @@ -77,8 +78,20 @@ impl<'a, 'ctx> Traverse<'a> for AsyncToGenerator<'a, 'ctx> {
Expression::AwaitExpression(await_expr) => {
self.transform_await_expression(await_expr, ctx)
}
Expression::FunctionExpression(func) => self.transform_function_expression(func, ctx),
Expression::ArrowFunctionExpression(arrow) => self.transform_arrow_function(arrow, ctx),
Expression::FunctionExpression(func) => {
if func.r#async && !func.generator && !func.is_typescript_syntax() {
Some(self.executor.transform_function_expression(func, ctx))
} else {
None
}
}
Expression::ArrowFunctionExpression(arrow) => {
if arrow.r#async {
Some(self.executor.transform_arrow_function(arrow, ctx))
} else {
None
}
}
_ => None,
};

Expand All @@ -88,29 +101,32 @@ impl<'a, 'ctx> Traverse<'a> for AsyncToGenerator<'a, 'ctx> {
}

fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
let new_statement = match stmt {
Statement::FunctionDeclaration(func) => self.transform_function_declaration(func, ctx),
let function = match stmt {
Statement::FunctionDeclaration(func) => Some(func),
Statement::ExportDefaultDeclaration(decl) => {
if let ExportDefaultDeclarationKind::FunctionDeclaration(func) =
&mut decl.declaration
{
self.transform_function_declaration(func, ctx)
Some(func)
} else {
None
}
}
Statement::ExportNamedDeclaration(decl) => {
if let Some(Declaration::FunctionDeclaration(func)) = &mut decl.declaration {
self.transform_function_declaration(func, ctx)
Some(func)
} else {
None
}
}
_ => None,
};

if let Some(new_statement) = new_statement {
self.ctx.statement_injector.insert_after(stmt, new_statement);
if let Some(function) = function {
if function.r#async && !function.generator && !function.is_typescript_syntax() {
let new_statement = self.executor.transform_function_declaration(function, ctx);
self.ctx.statement_injector.insert_after(stmt, new_statement);
}
}
}

Expand All @@ -119,7 +135,10 @@ impl<'a, 'ctx> Traverse<'a> for AsyncToGenerator<'a, 'ctx> {
node: &mut MethodDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.transform_function_for_method_definition(&mut node.value, ctx);
let function = &mut node.value;
if function.r#async && !function.generator && !function.is_typescript_syntax() {
self.executor.transform_function_for_method_definition(function, ctx);
}
}
}

Expand All @@ -143,6 +162,17 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
))
}
}
}

pub struct AsyncGeneratorExecutor<'a, 'ctx> {
helper: Helper,
ctx: &'ctx TransformCtx<'a>,
}

impl<'a, 'ctx> AsyncGeneratorExecutor<'a, 'ctx> {
pub fn new(helper: Helper, ctx: &'ctx TransformCtx<'a>) -> Self {
Self { helper, ctx }
}

/// Transforms async method definitions to generator functions wrapped in asyncToGenerator.
///
Expand All @@ -162,15 +192,11 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
/// })();
/// }
/// ```
fn transform_function_for_method_definition(
pub fn transform_function_for_method_definition(
&self,
func: &mut Function<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if !func.r#async {
return;
}

let Some(body) = func.body.take() else {
return;
};
Expand All @@ -183,7 +209,7 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
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.
BindingMover::new(new_scope_id, ctx).visit_formal_parameters(&func.params);
Self::move_formal_parameters_to_target_scope(new_scope_id, &func.params, ctx);

(scope_id, new_scope_id)
};
Expand All @@ -196,36 +222,30 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {

// Modify the wrapper function
func.r#async = false;
func.generator = false;
func.body = Some(ctx.ast.alloc_function_body(SPAN, ctx.ast.vec(), ctx.ast.vec1(statement)));
func.scope_id.set(Some(wrapper_scope_id));
}

/// Transforms [`Function`] whose type is [`FunctionType::FunctionExpression`] to a generator function
/// and wraps it in asyncToGenerator helper function.
fn transform_function_expression(
pub fn transform_function_expression(
&self,
wrapper_function: &mut Function<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
if !wrapper_function.r#async
|| wrapper_function.generator
|| wrapper_function.is_typescript_syntax()
{
return None;
}

) -> Expression<'a> {
let body = wrapper_function.body.take().unwrap();
let params = ctx.alloc(ctx.ast.move_formal_parameters(&mut wrapper_function.params));
let id = wrapper_function.id.take();
let has_function_id = id.is_some();

if !has_function_id && !Self::is_function_length_affected(&params) {
return Some(self.create_async_to_generator_call(
return self.create_async_to_generator_call(
params,
body,
wrapper_function.scope_id.take().unwrap(),
ctx,
));
);
}

let (generator_scope_id, wrapper_scope_id) = {
Expand All @@ -238,7 +258,7 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
// and the caller_function is inside the wrapper function.
// so we need to move the id to the new scope.
if let Some(id) = id.as_ref() {
BindingMover::new(wrapper_scope_id, ctx).visit_binding_identifier(id);
Self::move_binding_identifier_to_target_scope(wrapper_scope_id, id, ctx);
let symbol_id = id.symbol_id.get().unwrap();
*ctx.symbols_mut().get_flags_mut(symbol_id) = SymbolFlags::FunctionScopedVariable;
}
Expand Down Expand Up @@ -294,6 +314,7 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
}
debug_assert!(wrapper_function.body.is_none());
wrapper_function.r#async = false;
wrapper_function.generator = false;
wrapper_function.body.replace(ctx.ast.alloc_function_body(
SPAN,
ctx.ast.vec(),
Expand All @@ -303,22 +324,15 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {

// Construct the IIFE
let callee = ctx.ast.expression_from_function(ctx.ast.move_function(wrapper_function));
Some(ctx.ast.expression_call(SPAN, callee, NONE, ctx.ast.vec(), false))
ctx.ast.expression_call(SPAN, callee, NONE, ctx.ast.vec(), false)
}

/// Transforms async function declarations into generator functions wrapped in the asyncToGenerator helper.
fn transform_function_declaration(
pub fn transform_function_declaration(
&self,
wrapper_function: &mut Function<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Statement<'a>> {
if !wrapper_function.r#async
|| wrapper_function.generator
|| wrapper_function.is_typescript_syntax()
{
return None;
}

) -> Statement<'a> {
let (generator_scope_id, wrapper_scope_id) = {
let wrapper_scope_id =
ctx.create_child_scope(ctx.current_scope_id(), ScopeFlags::Function);
Expand All @@ -340,6 +354,7 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
// Modify the wrapper function
{
wrapper_function.r#async = false;
wrapper_function.generator = false;
let statements = ctx.ast.vec1(Self::create_apply_call_statement(&bound_ident, ctx));
debug_assert!(wrapper_function.body.is_none());
wrapper_function.body.replace(ctx.ast.alloc_function_body(
Expand Down Expand Up @@ -370,20 +385,16 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
let params = Self::create_empty_params(ctx);
let id = Some(bound_ident.create_binding_identifier(ctx));
let caller_function = Self::create_function(id, params, body, scope_id, ctx);
Some(Statement::from(ctx.ast.declaration_from_function(caller_function)))
Statement::from(ctx.ast.declaration_from_function(caller_function))
}
}

/// Transforms async arrow functions into generator functions wrapped in the asyncToGenerator helper.
fn transform_arrow_function(
pub(self) fn transform_arrow_function(
&self,
arrow: &mut ArrowFunctionExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
if !arrow.r#async {
return None;
}

) -> Expression<'a> {
let mut body = ctx.ast.move_function_body(&mut arrow.body);

// If the arrow's expression is true, we need to wrap the only one expression with return statement.
Expand All @@ -401,12 +412,12 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
ctx.scopes_mut().get_flags_mut(generator_function_id).remove(ScopeFlags::Arrow);

if !Self::is_function_length_affected(&params) {
return Some(self.create_async_to_generator_call(
return self.create_async_to_generator_call(
params,
ctx.ast.alloc(body),
generator_function_id,
ctx,
));
);
}

let wrapper_scope_id = ctx.create_child_scope(ctx.current_scope_id(), ScopeFlags::Function);
Expand Down Expand Up @@ -444,7 +455,7 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
let wrapper_function = Self::create_function(None, params, body, wrapper_scope_id, ctx);
// Construct the IIFE
let callee = ctx.ast.expression_from_function(wrapper_function);
Some(ctx.ast.expression_call(SPAN, callee, NONE, ctx.ast.vec(), false))
ctx.ast.expression_call(SPAN, callee, NONE, ctx.ast.vec(), false)
}
}

Expand Down Expand Up @@ -523,7 +534,7 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
ctx.ast.statement_return(SPAN, Some(argument))
}

/// Creates an [`Expression`] that calls the [`Helper::AsyncToGenerator`] helper function.
/// Creates an [`Expression`] that calls the [`AsyncGeneratorExecutor::helper`] helper function.
///
/// This function constructs the helper call with arguments derived from the provided
/// parameters, body, and scope_id.
Expand All @@ -546,7 +557,7 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
let function_expression = ctx.ast.expression_from_function(function);
let argument = ctx.ast.argument_expression(function_expression);
let arguments = ctx.ast.vec1(argument);
self.ctx.helper_call_expr(Helper::AsyncToGenerator, arguments, ctx)
self.ctx.helper_call_expr(self.helper, arguments, ctx)
}

/// Creates a helper declaration statement for async-to-generator transformation.
Expand Down Expand Up @@ -667,6 +678,24 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
fn is_function_length_affected(params: &FormalParameters<'_>) -> bool {
params.items.first().is_some_and(|param| !param.pattern.kind.is_assignment_pattern())
}

#[inline]
fn move_formal_parameters_to_target_scope(
target_scope_id: ScopeId,
params: &FormalParameters<'a>,
ctx: &mut TraverseCtx<'a>,
) {
BindingMover::new(target_scope_id, ctx).visit_formal_parameters(params);
}

#[inline]
fn move_binding_identifier_to_target_scope(
target_scope_id: ScopeId,
ident: &BindingIdentifier<'a>,
ctx: &mut TraverseCtx<'a>,
) {
BindingMover::new(target_scope_id, ctx).visit_binding_identifier(ident);
}
}

/// Moves the bindings from original scope to target scope.
Expand Down
14 changes: 7 additions & 7 deletions crates/oxc_transformer/src/es2017/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
mod async_to_generator;
pub mod options;

use options::ES2017Options;
use oxc_ast::ast::{Expression, Statement};
use oxc_traverse::{Traverse, TraverseCtx};

use crate::{
es2017::{async_to_generator::AsyncToGenerator, options::ES2017Options},
TransformCtx,
};

mod async_to_generator;
pub mod options;
use crate::{es2017::async_to_generator::AsyncToGenerator, TransformCtx};
#[expect(unused_imports)]
pub use async_to_generator::AsyncGeneratorExecutor;

#[allow(dead_code)]
pub struct ES2017<'a, 'ctx> {
Expand Down
Loading