fix(transformer/explicit-resource-management): lower using in catch and finally (#27142)

Lower `using` and `await using` declarations in `catch` and `finally` blocks.

fixes #27139
This commit is contained in:
camc314 committed 2026-09-28 13:22:39 +00:00
1 parent ad5dd5df4d
commit 24b7015d2c
5 files changed
+162 -35

No files matched your search

@@ -310,7 +310,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for ExplicitResourceManagement<'a> {
}
}
/// Transform try statement.
/// Transform `using` declarations in each of the try, catch, and finally blocks.
///
/// ```js
/// try {
@@ -331,38 +331,25 @@ impl<'a> Traverse<'a, TransformState<'a>> for ExplicitResourceManagement<'a> {
/// } catch (err) { }
/// ```
fn enter_try_statement(&mut self, node: &mut TryStatement<'a>, ctx: &mut TraverseCtx<'a>) {
let scope_id = node.block.scope_id();
if let Some((new_stmts, needs_await, using_ctx)) =
self.transform_statements(&mut node.block.body, scope_id, ctx)
{
let block_stmt_scope_id = ctx.insert_scope_between(
ctx.scoping().scope_parent_id(scope_id).unwrap(),
scope_id,
ScopeFlags::empty(),
);
node.block.body = ArenaVec::from_value_in(
Self::create_try_stmt(
BlockStatement::boxed_with_scope_id(SPAN, new_stmts, scope_id, ctx),
&using_ctx,
block_stmt_scope_id,
needs_await,
SPAN,
ctx,
),
ctx,
);
let current_hoist_scope_id = ctx.current_hoist_scope_id();
node.block.set_scope_id(block_stmt_scope_id);
ctx.scoping_mut().move_binding_by_symbol_id(
scope_id,
current_hoist_scope_id,
using_ctx.symbol_id,
);
ctx.scoping_mut().change_scope_parent_id(scope_id, Some(block_stmt_scope_id));
self.transform_try_block(&mut node.block, ctx);
if let Some(handler) = &mut node.handler {
let original_scope_id = handler.body.scope_id();
self.transform_try_block(&mut handler.body, ctx);
let new_scope_id = handler.body.scope_id();
if new_scope_id != original_scope_id
&& let Some(param) = &handler.param
{
param.pattern.bound_names(&mut |ident| {
ctx.scoping_mut().move_binding_by_symbol_id(
original_scope_id,
new_scope_id,
ident.symbol_id(),
);
});
}
}
if let Some(finalizer) = &mut node.finalizer {
self.transform_try_block(finalizer, ctx);
}
}
@@ -598,6 +585,47 @@ impl<'a> Traverse<'a, TransformState<'a>> for ExplicitResourceManagement<'a> {
}
impl<'a> ExplicitResourceManagement<'a> {
/// Wrap a try, catch, or finally block containing `using` declarations in a disposal try.
fn transform_try_block(
&mut self,
block: &mut ArenaBox<'a, BlockStatement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
let scope_id = block.scope_id();
if let Some((new_stmts, needs_await, using_ctx)) =
self.transform_statements(&mut block.body, scope_id, ctx)
{
let block_stmt_scope_id = ctx.insert_scope_between(
ctx.scoping().scope_parent_id(scope_id).unwrap(),
scope_id,
ScopeFlags::empty(),
);
block.body = ArenaVec::from_value_in(
Self::create_try_stmt(
BlockStatement::boxed_with_scope_id(SPAN, new_stmts, scope_id, ctx),
&using_ctx,
block_stmt_scope_id,
needs_await,
SPAN,
ctx,
),
ctx,
);
let current_hoist_scope_id = ctx.current_hoist_scope_id();
block.set_scope_id(block_stmt_scope_id);
ctx.scoping_mut().move_binding_by_symbol_id(
scope_id,
current_hoist_scope_id,
using_ctx.symbol_id,
);
ctx.scoping_mut().change_scope_parent_id(scope_id, Some(block_stmt_scope_id));
}
}
/// Transform block statement.
///
/// Input:
@@ -1,6 +1,6 @@
commit: fd665901
Passed: 280/408
Passed: 281/409
# All Passed:
* babel-plugin-transform-class-static-block
@@ -21,7 +21,7 @@ Passed: 280/408
* plugin-tagged-template-transform
# babel-plugin-transform-explicit-resource-management (3/4)
# babel-plugin-transform-explicit-resource-management (4/5)
* export-class-name/input.js
Symbol reference IDs mismatch for "C":
after transform: SymbolId(1): [ReferenceId(1), ReferenceId(2), ReferenceId(3), ReferenceId(7)]
@@ -0,0 +1,28 @@
function disposeSync(make) {
try {
using tryResource = make("try");
throw new Error("catch");
} catch (error) {
using catchResource = make(error.message);
} finally {
using finallyResource = make("finally");
}
}
async function disposeAsync(make) {
try {
throw new Error("catch");
} catch {
await using catchResource = await make("catch");
} finally {
await using finallyResource = await make("finally");
}
}
function disposeDestructured(make) {
try {
throw { name: "catch", code: 1 };
} catch ({ name, code }) {
using resource = make(name, code);
}
}
@@ -0,0 +1,3 @@
{
"plugins": ["transform-explicit-resource-management"]
}
@@ -0,0 +1,68 @@
function disposeSync(make) {
try {
try {
var _usingCtx = babelHelpers.usingCtx();
const tryResource = _usingCtx.u(make("try"));
throw new Error("catch");
} catch (_) {
_usingCtx.e = _;
} finally {
_usingCtx.d();
}
} catch (error) {
try {
var _usingCtx2 = babelHelpers.usingCtx();
const catchResource = _usingCtx2.u(make(error.message));
} catch (_) {
_usingCtx2.e = _;
} finally {
_usingCtx2.d();
}
} finally {
try {
var _usingCtx3 = babelHelpers.usingCtx();
const finallyResource = _usingCtx3.u(make("finally"));
} catch (_) {
_usingCtx3.e = _;
} finally {
_usingCtx3.d();
}
}
}
async function disposeAsync(make) {
try {
throw new Error("catch");
} catch {
try {
var _usingCtx4 = babelHelpers.usingCtx();
const catchResource = _usingCtx4.a(await make("catch"));
} catch (_) {
_usingCtx4.e = _;
} finally {
await _usingCtx4.d();
}
} finally {
try {
var _usingCtx5 = babelHelpers.usingCtx();
const finallyResource = _usingCtx5.a(await make("finally"));
} catch (_) {
_usingCtx5.e = _;
} finally {
await _usingCtx5.d();
}
}
}
function disposeDestructured(make) {
try {
throw { name: "catch", code: 1 };
} catch ({ name, code }) {
try {
var _usingCtx6 = babelHelpers.usingCtx();
const resource = _usingCtx6.u(make(name, code));
} catch (_) {
_usingCtx6.e = _;
} finally {
_usingCtx6.d();
}
}
}