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

**Related issue:**

 - Closes #8853
This commit is contained in:
Donny/강동윤 2024-04-15 15:28:11 +09:00 committed by GitHub
parent 3de82531b2
commit 6d240768b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 403 additions and 159 deletions

View File

@ -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();
},
};
}

View File

@ -384,6 +384,7 @@ define_helpers!(Helpers {
identity: (),
dispose: (),
using: (),
using_ctx: (),
});
pub fn inject_helpers(global_mark: Mark) -> impl Fold + VisitMut {

View File

@ -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 {
@ -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,
@ -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,
}
@ -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(),
),
@ -80,7 +78,7 @@ 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() {
@ -88,8 +86,8 @@ impl ExplicitResourceManagement {
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),
@ -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 {
// usingCtx.e = $catch_var
let assign_error = AssignExpr {
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,
}],
})));
op: op!("="),
left: state
.using_ctx
.clone()
.make_member(quote_ident!("e"))
.into(),
right: state.catch_var.clone().into(),
}
.into_stmt();
// var has_error = true
let has_error_true = Stmt::Decl(Decl::Var(Box::new(VarDecl {
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,
}],
})));
// _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 {
@ -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 {
@ -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(),
};

View File

@ -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])

View File

@ -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
}

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -1,14 +1,13 @@
{
try {
var _stack = [];
var x = _using(_stack, obj, true);
var _usingCtx = _using_ctx();
const x = _usingCtx.a(obj);
stmt;
var y = _using(_stack, obj, true), z = _using(_stack, obj, true);
const y = _usingCtx.a(obj), z = _usingCtx.a(obj);
doSomethingWith(x, y);
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
await _dispose(_stack, _error, _hasError);
await _usingCtx.d();
}
}

View File

@ -1,12 +1,11 @@
{
try {
var _stack = [];
var x = _using(_stack, obj);
var _usingCtx = _using_ctx();
const x = _usingCtx.u(obj);
doSomethingWith(x);
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}
}

View File

@ -1,13 +1,12 @@
for await (const x of y){
try {
var _stack = [];
var _usingCtx = _using_ctx();
{
doSomethingWith(x);
}
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}
}

View File

@ -1,13 +1,12 @@
for (const x of y){
try {
var _stack = [];
var _usingCtx = _using_ctx();
{
doSomethingWith(x);
}
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}
}

View File

@ -1,12 +1,11 @@
function fn() {
try {
var _stack = [];
var x = _using(_stack, obj);
var _usingCtx = _using_ctx();
const x = _usingCtx.u(obj);
return doSomethingWith(x);
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}
}

View File

@ -1,12 +1,11 @@
if (test) {
try {
var _stack = [];
var x = _using(_stack, obj);
var _usingCtx = _using_ctx();
const x = _usingCtx.u(obj);
doSomethingWith(x);
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}
}

View File

@ -1,19 +1,18 @@
// main.ts
var _Disposable;
try {
var _stack = [];
var _usingCtx = _using_ctx();
class Disposable {
[Symbol.dispose]() {
console.log('dispose');
}
}
_Disposable = Disposable;
var _disposable = _using(_stack, new Disposable());
var _disposable = _usingCtx.u(new Disposable());
console.log('ok');
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}
export { _Disposable as Disposable };

View File

@ -1,35 +1,32 @@
{
try {
var _stack = [];
var x = _using(_stack, obj);
var _usingCtx = _using_ctx();
const x = _usingCtx.u(obj);
{
try {
var _stack1 = [];
var y = _using(_stack1, call(()=>{
var _usingCtx1 = _using_ctx();
const y = _usingCtx1.u(call(()=>{
try {
var _stack = [];
var z = _using(_stack, obj);
var _usingCtx = _using_ctx();
const z = _usingCtx.u(obj);
return z;
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}
}));
stmt;
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx1.e = _;
} finally{
_dispose(_stack1, _error, _hasError);
_usingCtx1.d();
}
}
stmt;
} catch (_) {
var _error1 = _;
var _hasError1 = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error1, _hasError1);
_usingCtx.d();
}
}

View File

@ -1,17 +1,16 @@
{
try {
var _stack = [];
var _usingCtx = _using_ctx();
stmt;
var x = _using(_stack, obj);
const x = _usingCtx.u(obj);
stmt;
var y = _using(_stack, obj), z = _using(_stack, obj);
const y = _usingCtx.u(obj), z = _usingCtx.u(obj);
stmt;
var w = _using(_stack, obj);
const w = _usingCtx.u(obj);
doSomethingWith(x, z);
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}
}

View File

@ -1,14 +1,13 @@
class A {
static{
try {
var _stack = [];
var x = _using(_stack, y);
var _usingCtx = _using_ctx();
const x = _usingCtx.u(y);
doSomethingWith(x);
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}
}
}

View File

@ -1,11 +1,10 @@
export { x, y };
try {
var _stack = [];
var x = _using(_stack, A);
var y = _using(_stack, B, true);
var _usingCtx = _using_ctx();
var x = _usingCtx.u(A);
var y = _usingCtx.a(B);
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
await _dispose(_stack, _error, _hasError);
await _usingCtx.d();
}

View File

@ -1,12 +1,11 @@
export { _default as default };
try {
var _stack = [];
var x = _using(_stack, null);
var _usingCtx = _using_ctx();
var x = _usingCtx.u(null);
var _default = class {
};
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}

View File

@ -1,12 +1,11 @@
export { C as default };
try {
var _stack = [];
var x = _using(_stack, null);
var _usingCtx = _using_ctx();
var x = _usingCtx.u(null);
var C = class C {
};
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}

View File

@ -1,11 +1,10 @@
export { _default as default };
try {
var _stack = [];
var x = _using(_stack, null);
var _usingCtx = _using_ctx();
var x = _usingCtx.u(null);
var _default = doSomething();
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}

View File

@ -1,11 +1,10 @@
export { fn as default };
try {
var _stack = [];
var x = _using(_stack, null);
var _usingCtx = _using_ctx();
var x = _usingCtx.u(null);
var fn = function fn() {};
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}

View File

@ -1,11 +1,10 @@
export { fn as default };
try {
var _stack = [];
var x = _using(_stack, null);
var _usingCtx = _using_ctx();
var x = _usingCtx.u(null);
var fn = function fn() {};
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}

View File

@ -14,7 +14,7 @@ export { f };
var _b;
var _B;
try {
var _stack = [];
var _usingCtx = _using_ctx();
function g() {
c;
}
@ -22,18 +22,17 @@ try {
doSomething();
let { b } = {};
_b = b;
var c = 2;
let c = 2;
class A {
}
class B {
}
_B = B;
var x = _using(_stack, null);
var x = _usingCtx.u(null);
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally{
_dispose(_stack, _error, _hasError);
_usingCtx.d();
}
export { _g as g };
export { _b as b };

View File

@ -0,0 +1,78 @@
export function _usingCtx() {
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();
},
};
}
export { _usingCtx as _ }

View File

@ -1,7 +1,7 @@
{
"name": "@swc/helpers",
"packageManager": "yarn@4.0.2",
"version": "0.5.9",
"version": "0.5.10",
"description": "External helpers for the swc project.",
"module": "esm/index.js",
"main": "cjs/index.cjs",