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

fix(es/proposal): Update explicit resource management to match spec #8860

Merged
merged 16 commits into from
Apr 15, 2024
76 changes: 76 additions & 0 deletions crates/swc_ecma_transforms_base/src/helpers/_using_ctx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
function _using_ctx() {
var _disposeSuppressedError =
typeof SuppressedError === "function"
? // eslint-disable-next-line no-undef
SuppressedError
: (function (error, suppressed) {
var err = new Error();
err.name = "SuppressedError";
err.suppressed = suppressed;
err.error = error;
return err;
}),
empty = {},
stack = [];
function using(isAwait, value) {
if (value != null) {
if (Object(value) !== value) {
throw new TypeError(
"using declarations can only be used with objects, functions, null, or undefined.",
);
}
// core-js-pure uses Symbol.for for polyfilling well-known symbols
if (isAwait) {
var dispose =
value[Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")];
}
if (dispose == null) {
dispose = value[Symbol.dispose || Symbol.for("Symbol.dispose")];
}
if (typeof dispose !== "function") {
throw new TypeError(`Property [Symbol.dispose] is not a function.`);
}
stack.push({ v: value, d: dispose, a: isAwait });
} else if (isAwait) {
// provide the nullish `value` as `d` for minification gain
stack.push({ d: value, a: isAwait });
}
return value;
}
return {
// error
e: empty,
// using
u: using.bind(null, false),
// await using
a: using.bind(null, true),
// dispose
d: function () {
var error = this.e;

function next() {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
while ((resource = stack.pop())) {
try {
var resource,
disposalResult = resource.d && resource.d.call(resource.v);
if (resource.a) {
return Promise.resolve(disposalResult).then(next, err);
}
} catch (e) {
return err(e);
}
}
if (error !== empty) throw error;
}

function err(e) {
error = error !== empty ? new _disposeSuppressedError(error, e) : e;

return next();
}

return next();
},
};
}
1 change: 1 addition & 0 deletions crates/swc_ecma_transforms_base/src/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ define_helpers!(Helpers {
identity: (),
dispose: (),
using: (),
using_ctx: (),
});

pub fn inject_helpers(global_mark: Mark) -> impl Fold + VisitMut {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::iter::once;

use swc_common::{util::take::Take, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::helper;
use swc_ecma_utils::{find_pat_ids, private_ident, ExprFactory, ModuleItemLike, StmtLike};
use swc_ecma_utils::{
find_pat_ids, private_ident, quote_ident, ExprFactory, ModuleItemLike, StmtLike,
};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};

pub fn explicit_resource_management() -> impl Fold + VisitMut {
Expand All @@ -18,9 +18,7 @@ struct ExplicitResourceManagement {
}

struct State {
stack: Ident,
has_error: Ident,
error_var: Ident,
using_ctx: Ident,
catch_var: Ident,

has_await: bool,
Expand All @@ -29,9 +27,7 @@ struct State {
impl Default for State {
fn default() -> Self {
Self {
stack: private_ident!("_stack"),
has_error: private_ident!("_hasError"),
error_var: private_ident!("_error"),
using_ctx: private_ident!("_usingCtx"),
catch_var: private_ident!("_"),
has_await: false,
}
Expand Down Expand Up @@ -63,13 +59,15 @@ impl ExplicitResourceManagement {
let mut extras = vec![];
let mut try_body = vec![];

let stack_var_decl = VarDeclarator {
let using_ctx_var = VarDeclarator {
span: DUMMY_SP,
name: state.stack.clone().into(),
name: state.using_ctx.clone().into(),
init: Some(
ArrayLit {
CallExpr {
callee: helper!(using_ctx),
span: DUMMY_SP,
elems: vec![],
args: Default::default(),
type_args: Default::default(),
}
.into(),
),
Expand All @@ -80,16 +78,16 @@ impl ExplicitResourceManagement {
span: DUMMY_SP,
kind: VarDeclKind::Var,
declare: false,
decls: vec![stack_var_decl],
decls: vec![using_ctx_var],
}))));

for stmt in stmts.take() {
match stmt.try_into_stmt() {
Ok(stmt @ Stmt::Decl(Decl::Fn(..))) => {
new.push(T::from_stmt(stmt));
}
Ok(Stmt::Decl(Decl::Var(mut var))) => {
var.kind = VarDeclKind::Var;
Ok(Stmt::Decl(Decl::Var(var))) => {
// var.kind = VarDeclKind::Var;
try_body.push(Stmt::Decl(Decl::Var(var)));
}
Ok(stmt) => try_body.push(stmt),
Expand Down Expand Up @@ -310,39 +308,28 @@ impl ExplicitResourceManagement {
// Drop `;`
try_body.retain(|stmt| !matches!(stmt, Stmt::Empty(..)));

// var error = $catch_var
let error_catch_var = Stmt::Decl(Decl::Var(Box::new(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Var,
declare: false,
decls: vec![VarDeclarator {
span: DUMMY_SP,
name: state.error_var.clone().into(),
init: Some(state.catch_var.clone().into()),
definite: false,
}],
})));

// var has_error = true
let has_error_true = Stmt::Decl(Decl::Var(Box::new(VarDecl {
// usingCtx.e = $catch_var
let assign_error = AssignExpr {
span: DUMMY_SP,
kind: VarDeclKind::Var,
declare: false,
decls: vec![VarDeclarator {
span: DUMMY_SP,
name: state.has_error.clone().into(),
init: Some(true.into()),
definite: false,
}],
})));
op: op!("="),
left: state
.using_ctx
.clone()
.make_member(quote_ident!("e"))
.into(),
right: state.catch_var.clone().into(),
}
.into_stmt();

// _usingCtx.d()
let dispose_expr = CallExpr {
span: DUMMY_SP,
callee: helper!(dispose),
args: vec![
state.stack.as_arg(),
state.error_var.as_arg(),
state.has_error.as_arg(),
],
callee: state
.using_ctx
.clone()
.make_member(quote_ident!("d"))
.as_callee(),
args: vec![],
type_args: Default::default(),
};
let dispose_stmt = if state.has_await {
Expand All @@ -366,7 +353,7 @@ impl ExplicitResourceManagement {
param: Some(state.catch_var.into()),
body: BlockStmt {
span: DUMMY_SP,
stmts: vec![error_catch_var, has_error_true],
stmts: vec![assign_error],
},
}),
finalizer: Some(BlockStmt {
Expand Down Expand Up @@ -434,15 +421,16 @@ impl VisitMut for ExplicitResourceManagement {
.map(|d| {
let init = CallExpr {
span: decl.span,
callee: helper!(using),
args: once(state.stack.clone().as_arg())
.chain(once(d.init.unwrap().as_arg()))
.chain(if decl.is_await {
Some(true.as_arg())
callee: state
.using_ctx
.clone()
.make_member(if decl.is_await {
quote_ident!("a")
} else {
None
quote_ident!("u")
})
.collect(),
.as_callee(),
args: vec![d.init.unwrap().as_arg()],
type_args: Default::default(),
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const { deepStrictEqual } = require('node:assert')

let i = 0
let err
try {
await using _x1 = {
async [Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")]() {
throw [1, ++i]
}
}

await using _x2 = {
async [Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")]() {
throw [2, ++i]
}
}

await using _x3 = {
async [Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")]() {
throw [3, ++i]
}
}

await using _x4 = {
async [Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")]() {
throw [4, ++i]
}
}

throw [5, ++i]
} catch (e) {
err = e
}

console.log(err)
deepStrictEqual(err.suppressed, [1, 5])
deepStrictEqual(err.error.suppressed, [2, 4])
deepStrictEqual(err.error.error.suppressed, [3, 3])
deepStrictEqual(err.error.error.error.suppressed, [4, 2])
deepStrictEqual(err.error.error.error.error, [5, 1])
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

let i = 0
let err
try {
await using _x1 = {
async [Symbol.asyncDispose]() {
throw [1, ++i]
}
}

await using _x2 = {
async [Symbol.asyncDispose]() {
throw [2, ++i]
}
}

await using _x3 = {
async [Symbol.asyncDispose]() {
throw [3, ++i]
}
}

await using _x4 = {
async [Symbol.asyncDispose]() {
throw [4, ++i]
}
}

throw [5, ++i]
} catch (e) {
err = e
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
let i = 0;
let err;
try {
try {
var _usingCtx = _using_ctx();
const _x1 = _usingCtx.a({
async [Symbol.asyncDispose] () {
throw [
1,
++i
];
}
});
const _x2 = _usingCtx.a({
async [Symbol.asyncDispose] () {
throw [
2,
++i
];
}
});
const _x3 = _usingCtx.a({
async [Symbol.asyncDispose] () {
throw [
3,
++i
];
}
});
const _x4 = _usingCtx.a({
async [Symbol.asyncDispose] () {
throw [
4,
++i
];
}
});
throw [
5,
++i
];
} catch (_) {
_usingCtx.e = _;
} finally{
await _usingCtx.d();
}
} catch (e) {
err = e;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
{
try {
var _stack = [];
var a = _using(_stack, 1);
var b = _using(_stack, 2, true);
var c = _using(_stack, 3);
var _usingCtx = _using_ctx();
const a = _usingCtx.u(1);
const b = _usingCtx.a(2);
const c = _usingCtx.u(3);
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
await _dispose(_stack, _error, _hasError);
await _usingCtx.d();
}
}