diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 814282a533cc1..7d0b92036d492 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -122,7 +122,10 @@ impl<'a> Transformer<'a> { let mut transformer = TransformerImpl { common: Common::new(&self.env, &self.ctx), decorator: Decorator::new(self.decorator, &self.ctx), - explicit_resource_management: ExplicitResourceManagement::new(&self.ctx), + explicit_resource_management: ExplicitResourceManagement::new( + &self.ctx, + self.env.es2017.async_to_generator, + ), x0_typescript: program .source_type .is_typescript() @@ -202,6 +205,7 @@ impl<'a> Traverse<'a> for TransformerImpl<'a, '_> { ctx: &mut TraverseCtx<'a>, ) { self.x2_es2018.enter_variable_declaration(decl, ctx); + self.explicit_resource_management.enter_variable_declaration(decl, ctx); } fn enter_variable_declarator( @@ -273,12 +277,13 @@ impl<'a> Traverse<'a> for TransformerImpl<'a, '_> { fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { self.common.enter_static_block(block, ctx); - self.x2_es2022.enter_static_block(block, ctx); self.explicit_resource_management.enter_static_block(block, ctx); + self.x2_es2022.enter_static_block(block, ctx); } fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { self.common.exit_static_block(block, ctx); + self.explicit_resource_management.exit_static_block(block, ctx); self.x2_es2022.exit_static_block(block, ctx); } @@ -542,6 +547,7 @@ impl<'a> Traverse<'a> for TransformerImpl<'a, '_> { if let Some(typescript) = self.x0_typescript.as_mut() { typescript.exit_statement(stmt, ctx); } + self.explicit_resource_management.exit_statement(stmt, ctx); self.decorator.enter_statement(stmt, ctx); self.x2_es2018.exit_statement(stmt, ctx); self.x2_es2017.exit_statement(stmt, ctx); diff --git a/crates/oxc_transformer/src/proposals/explicit_resource_management.rs b/crates/oxc_transformer/src/proposals/explicit_resource_management.rs index dba202bbf8caf..0a9e0ddeaf1a1 100644 --- a/crates/oxc_transformer/src/proposals/explicit_resource_management.rs +++ b/crates/oxc_transformer/src/proposals/explicit_resource_management.rs @@ -39,22 +39,44 @@ use rustc_hash::FxHashMap; use oxc_allocator::{Address, Box as ArenaBox, GetAddress, Vec as ArenaVec}; use oxc_ast::{NONE, ast::*}; +use oxc_data_structures::stack::NonEmptyStack; use oxc_ecmascript::BoundNames; use oxc_semantic::{ScopeFlags, ScopeId, SymbolFlags}; use oxc_span::{Atom, SPAN}; -use oxc_traverse::{BoundIdentifier, Traverse, TraverseCtx}; +use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx}; use crate::{Helper, TransformCtx}; pub struct ExplicitResourceManagement<'a, 'ctx> { ctx: &'ctx TransformCtx<'a>, + async_to_generator: bool, + top_level_using: FxHashMap, + + /// keeps track of whether the current static block contains a `using` declaration + /// so that we can transform it in `exit_static_block` + static_blocks_stack: NonEmptyStack, + + /// keeps track of whether the current switch statement contains a `using` declaration + /// so that we can transform it in `exit_statement` + switch_stmt_stack: NonEmptyStack, + + /// keeps track of whether the current block statement contains a `using` declaration + /// so that we can transform it in `exit_statement` + block_stmt_stack: NonEmptyStack, } impl<'a, 'ctx> ExplicitResourceManagement<'a, 'ctx> { - pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self { - Self { ctx, top_level_using: FxHashMap::default() } + pub fn new(ctx: &'ctx TransformCtx<'a>, async_to_generator: bool) -> Self { + Self { + ctx, + async_to_generator, + top_level_using: FxHashMap::default(), + static_blocks_stack: NonEmptyStack::new(false), + switch_stmt_stack: NonEmptyStack::new(false), + block_stmt_stack: NonEmptyStack::new(false), + } } } @@ -123,6 +145,10 @@ impl<'a> Traverse<'a> for ExplicitResourceManagement<'a, '_> { }; } + fn enter_static_block(&mut self, _node: &mut StaticBlock<'a>, _ctx: &mut TraverseCtx<'a>) { + self.static_blocks_stack.push(false); + } + /// Transform class static block. /// /// ```js @@ -143,12 +169,37 @@ impl<'a> Traverse<'a> for ExplicitResourceManagement<'a, '_> { /// } /// } /// ``` - fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { - let scope_id = block.scope_id(); - if let Some(replacement) = - self.transform_statements(&mut block.body, None, scope_id, scope_id, ctx) + fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { + if self.static_blocks_stack.pop() { + let scope_id = block.scope_id(); + if let Some(replacement) = + self.transform_statements(&mut block.body, None, scope_id, scope_id, ctx) + { + block.body = ctx.ast.vec1(replacement); + } + } + } + + fn enter_variable_declaration( + &mut self, + node: &mut VariableDeclaration<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + if matches!(node.kind, VariableDeclarationKind::Using | VariableDeclarationKind::AwaitUsing) + || self.top_level_using.contains_key(&Address::from_ptr(node)) { - block.body = ctx.ast.vec1(replacement); + match ctx.parent() { + Ancestor::StaticBlockBody(_) => { + *self.static_blocks_stack.last_mut() = true; + } + Ancestor::SwitchCaseConsequent(_) => { + *self.switch_stmt_stack.last_mut() = true; + } + Ancestor::BlockStatementBody(_) => { + *self.block_stmt_stack.last_mut() = true; + } + _ => {} + } } } @@ -193,10 +244,31 @@ impl<'a> Traverse<'a> for ExplicitResourceManagement<'a, '_> { // or `SwitchStatement`s. We want the common path for "nothing to do here" not to incur the cost of // a function call. #[inline] - fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + fn enter_statement(&mut self, stmt: &mut Statement<'a>, _ctx: &mut TraverseCtx<'a>) { match stmt { - Statement::BlockStatement(_) => self.transform_block_statement(stmt, ctx), - Statement::SwitchStatement(_) => self.transform_switch_statement(stmt, ctx), + Statement::BlockStatement(_) => { + self.block_stmt_stack.push(false); + } + Statement::SwitchStatement(_) => { + self.switch_stmt_stack.push(false); + } + _ => {} + } + } + + #[inline] + fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + match stmt { + Statement::BlockStatement(_) => { + if self.block_stmt_stack.pop() { + self.transform_block_statement(stmt, ctx); + } + } + Statement::SwitchStatement(_) => { + if self.switch_stmt_stack.pop() { + self.transform_switch_statement(stmt, ctx); + } + } _ => {} } } @@ -603,7 +675,7 @@ impl<'a> ExplicitResourceManagement<'a, '_> { }; let catch = Self::create_catch_clause(&using_ctx, ctx.current_scope_id(), ctx); - let finally = Self::create_finally_block(&using_ctx, current_scope_id, needs_await, ctx); + let finally = self.create_finally_block(&using_ctx, current_scope_id, needs_await, ctx); *stmt = ctx.ast.statement_try(SPAN, block, Some(catch), Some(finally)); } @@ -725,7 +797,7 @@ impl<'a> ExplicitResourceManagement<'a, '_> { } let catch = Self::create_catch_clause(&using_ctx, parent_scope_id, ctx); - let finally = Self::create_finally_block(&using_ctx, parent_scope_id, needs_await, ctx); + let finally = self.create_finally_block(&using_ctx, parent_scope_id, needs_await, ctx); Some(ctx.ast.statement_try(SPAN, block, Some(catch), Some(finally))) } @@ -779,6 +851,7 @@ impl<'a> ExplicitResourceManagement<'a, '_> { /// `{ _usingCtx.d(); }` fn create_finally_block( + &self, using_ctx: &BoundIdentifier<'a>, parent_scope_id: ScopeId, needs_await: bool, @@ -800,7 +873,15 @@ impl<'a> ExplicitResourceManagement<'a, '_> { false, ); - let stmt = if needs_await { ctx.ast.expression_await(SPAN, expr) } else { expr }; + let stmt = if needs_await { + if self.async_to_generator { + ctx.ast.expression_yield(SPAN, false, Some(expr)) + } else { + ctx.ast.expression_await(SPAN, expr) + } + } else { + expr + }; ctx.ast.alloc_block_statement_with_scope_id( SPAN, diff --git a/tasks/coverage/snapshots/semantic_typescript.snap b/tasks/coverage/snapshots/semantic_typescript.snap index eb9a460149df9..f5b1eaf6f6d89 100644 --- a/tasks/coverage/snapshots/semantic_typescript.snap +++ b/tasks/coverage/snapshots/semantic_typescript.snap @@ -41524,24 +41524,24 @@ rebuilt : ScopeId(9): [] tasks/coverage/typescript/tests/cases/conformance/statements/VariableStatements/usingDeclarations/usingDeclarations.1.ts semantic error: Bindings mismatch: -after transform: ScopeId(0): ["C1", "C2", "C3", "N", "_af", "_ag", "_asyncToGenerator", "_awaitAsyncGenerator", "_defineProperty", "_usingCtx2", "_usingCtx20", "_usingCtx23", "_usingCtx24", "_usingCtx25", "_usingCtx26", "_usingCtx27", "_usingCtx28", "_usingCtx29", "_usingCtx6", "_wrapAsyncGenerator", "a", "af", "ag", "d1", "d19", "d20", "d21", "d22", "d23", "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31", "d32", "f", "g"] -rebuilt : ScopeId(0): ["C1", "C2", "C3", "N", "_af", "_ag", "_asyncToGenerator", "_awaitAsyncGenerator", "_defineProperty", "_usingCtx2", "_usingCtx20", "_usingCtx21", "_usingCtx22", "_usingCtx23", "_usingCtx24", "_usingCtx25", "_usingCtx26", "_usingCtx27", "_usingCtx28", "_usingCtx29", "_usingCtx6", "_wrapAsyncGenerator", "a", "af", "ag", "d1", "d19", "d20", "d21", "d22", "d23", "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31", "d32", "f", "g"] +after transform: ScopeId(0): ["C1", "C2", "C3", "N", "_af", "_ag", "_asyncToGenerator", "_awaitAsyncGenerator", "_defineProperty", "_usingCtx19", "_usingCtx2", "_usingCtx22", "_usingCtx23", "_usingCtx24", "_usingCtx25", "_usingCtx26", "_usingCtx27", "_usingCtx28", "_usingCtx29", "_wrapAsyncGenerator", "a", "af", "ag", "d1", "d19", "d20", "d21", "d22", "d23", "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31", "d32", "f", "g"] +rebuilt : ScopeId(0): ["C1", "C2", "C3", "N", "_af", "_ag", "_asyncToGenerator", "_awaitAsyncGenerator", "_defineProperty", "_usingCtx19", "_usingCtx2", "_usingCtx20", "_usingCtx21", "_usingCtx22", "_usingCtx23", "_usingCtx24", "_usingCtx25", "_usingCtx26", "_usingCtx27", "_usingCtx28", "_usingCtx29", "_wrapAsyncGenerator", "a", "af", "ag", "d1", "d19", "d20", "d21", "d22", "d23", "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31", "d32", "f", "g"] Bindings mismatch: -after transform: ScopeId(70): ["_usingCtx21", "_usingCtx22"] +after transform: ScopeId(70): ["_usingCtx20", "_usingCtx21"] rebuilt : ScopeId(29): [] Symbol span mismatch for "N": after transform: SymbolId(26): Span { start: 1385, end: 1386 } rebuilt : SymbolId(66): Span { start: 0, end: 0 } +Symbol scope ID mismatch for "_usingCtx20": +after transform: SymbolId(88): ScopeId(70) +rebuilt : SymbolId(74): ScopeId(0) Symbol scope ID mismatch for "_usingCtx21": after transform: SymbolId(90): ScopeId(70) -rebuilt : SymbolId(74): ScopeId(0) -Symbol scope ID mismatch for "_usingCtx22": -after transform: SymbolId(92): ScopeId(70) rebuilt : SymbolId(78): ScopeId(0) tasks/coverage/typescript/tests/cases/conformance/statements/VariableStatements/usingDeclarations/usingDeclarationsDeclarationEmit.2.ts semantic error: Scope children mismatch: -after transform: ScopeId(0): [ScopeId(2), ScopeId(4), ScopeId(5), ScopeId(6), ScopeId(8)] +after transform: ScopeId(0): [ScopeId(2), ScopeId(4), ScopeId(5), ScopeId(7), ScopeId(9)] rebuilt : ScopeId(0): [ScopeId(1), ScopeId(5), ScopeId(7)] Symbol reference IDs mismatch for "r1": after transform: SymbolId(0): [ReferenceId(1)] diff --git a/tasks/transform_conformance/overrides/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-sync/multiple-nested/output.js b/tasks/transform_conformance/overrides/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-sync/multiple-nested/output.js new file mode 100644 index 0000000000000..b64e874664dc4 --- /dev/null +++ b/tasks/transform_conformance/overrides/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-sync/multiple-nested/output.js @@ -0,0 +1,30 @@ +try { + var _usingCtx3 = babelHelpers.usingCtx(); + const x = _usingCtx3.u(obj); + try { + var _usingCtx2 = babelHelpers.usingCtx(); + const y = _usingCtx2.u( + call(() => { + try { + var _usingCtx = babelHelpers.usingCtx(); + const z = _usingCtx.u(obj); + return z; + } catch (_) { + _usingCtx.e = _; + } finally { + _usingCtx.d(); + } + }) + ); + stmt; + } catch (_) { + _usingCtx2.e = _; + } finally { + _usingCtx2.d(); + } + stmt; +} catch (_) { + _usingCtx3.e = _; +} finally { + _usingCtx3.d(); +}