diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 740a45b3e177b..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() diff --git a/crates/oxc_transformer/src/proposals/explicit_resource_management.rs b/crates/oxc_transformer/src/proposals/explicit_resource_management.rs index f6b432b0c3c97..0a9e0ddeaf1a1 100644 --- a/crates/oxc_transformer/src/proposals/explicit_resource_management.rs +++ b/crates/oxc_transformer/src/proposals/explicit_resource_management.rs @@ -50,6 +50,8 @@ 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 @@ -59,15 +61,21 @@ pub struct ExplicitResourceManagement<'a, 'ctx> { /// 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 { + 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), } } } @@ -178,6 +186,7 @@ impl<'a> Traverse<'a> for ExplicitResourceManagement<'a, '_> { ctx: &mut TraverseCtx<'a>, ) { if matches!(node.kind, VariableDeclarationKind::Using | VariableDeclarationKind::AwaitUsing) + || self.top_level_using.contains_key(&Address::from_ptr(node)) { match ctx.parent() { Ancestor::StaticBlockBody(_) => { @@ -186,6 +195,9 @@ impl<'a> Traverse<'a> for ExplicitResourceManagement<'a, '_> { Ancestor::SwitchCaseConsequent(_) => { *self.switch_stmt_stack.last_mut() = true; } + Ancestor::BlockStatementBody(_) => { + *self.block_stmt_stack.last_mut() = true; + } _ => {} } } @@ -232,10 +244,11 @@ 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 { - // TODO: move this to exit_statement - Statement::BlockStatement(_) => self.transform_block_statement(stmt, ctx), + Statement::BlockStatement(_) => { + self.block_stmt_stack.push(false); + } Statement::SwitchStatement(_) => { self.switch_stmt_stack.push(false); } @@ -243,11 +256,20 @@ impl<'a> Traverse<'a> for ExplicitResourceManagement<'a, '_> { } } + #[inline] fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { - if let Statement::SwitchStatement(_) = stmt { - if self.switch_stmt_stack.pop() { - self.transform_switch_statement(stmt, ctx); + 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); + } + } + _ => {} } } @@ -653,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)); } @@ -775,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))) } @@ -829,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, @@ -850,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(); +}