Skip to content

Commit

Permalink
Improve output of using (#15959)
Browse files Browse the repository at this point in the history
Co-authored-by: Nicolò Ribaudo <hello@nicr.dev>
  • Loading branch information
liuxingbaoyu and nicolo-ribaudo committed Jan 15, 2024
1 parent 1200542 commit 218faee
Show file tree
Hide file tree
Showing 40 changed files with 513 additions and 428 deletions.
4 changes: 2 additions & 2 deletions Makefile.js

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Makefile.source.mjs
Expand Up @@ -450,7 +450,7 @@ target["bootstrap-flow"] = function () {

target["new-version-checklist"] = function () {
// eslint-disable-next-line no-constant-condition
if (0) {
if (1) {
console.log(
`
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Expand All @@ -459,6 +459,8 @@ target["new-version-checklist"] = function () {
!!!!!! Write any important message here, and change the !!!!!!
!!!!!! if (0) above to if (1) !!!!!!
!!!!!! !!!!!!
!!!!!! - Update usingCtx helper version !!!!!!
!!!!!! !!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
`.trim()
Expand Down
9 changes: 7 additions & 2 deletions packages/babel-helpers/src/helpers-generated.ts
Expand Up @@ -83,10 +83,10 @@ export default Object.freeze({
"7.20.7",
"export default function _defineAccessor(e,r,n,t){var c={configurable:!0,enumerable:!0};return c[e]=t,Object.defineProperty(r,n,c)}",
),
// size: 672, gzip size: 332
// size: 672, gzip size: 333
dispose: helper(
"7.22.0",
'function dispose_SuppressedError(r,e){return"undefined"!=typeof SuppressedError?dispose_SuppressedError=SuppressedError:(dispose_SuppressedError=function(r,e){this.suppressed=r,this.error=e,this.stack=(new Error).stack},dispose_SuppressedError.prototype=Object.create(Error.prototype,{constructor:{value:dispose_SuppressedError,writable:!0,configurable:!0}})),new dispose_SuppressedError(r,e)}export default function _dispose(r,e,s){function next(){for(;r.length>0;)try{var o=r.pop(),p=o.d.call(o.v);if(o.a)return Promise.resolve(p).then(next,err)}catch(r){return err(r)}if(s)throw e}function err(r){return e=s?new dispose_SuppressedError(r,e):r,s=!0,next()}return next()}',
'function dispose_SuppressedError(r,e){return"undefined"!=typeof SuppressedError?dispose_SuppressedError=SuppressedError:(dispose_SuppressedError=function(r,e){this.suppressed=e,this.error=r,this.stack=(new Error).stack},dispose_SuppressedError.prototype=Object.create(Error.prototype,{constructor:{value:dispose_SuppressedError,writable:!0,configurable:!0}})),new dispose_SuppressedError(r,e)}export default function _dispose(r,e,s){function next(){for(;r.length>0;)try{var o=r.pop(),p=o.d.call(o.v);if(o.a)return Promise.resolve(p).then(next,err)}catch(r){return err(r)}if(s)throw e}function err(r){return e=s?new dispose_SuppressedError(e,r):r,s=!0,next()}return next()}',
),
// size: 552, gzip size: 267
importDeferProxy: helper(
Expand Down Expand Up @@ -153,6 +153,11 @@ export default Object.freeze({
"7.22.0",
'export default function _using(o,n,e){if(null==n)return n;if(Object(n)!==n)throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");if(e)var r=n[Symbol.asyncDispose||Symbol.for("Symbol.asyncDispose")];if(null==r&&(r=n[Symbol.dispose||Symbol.for("Symbol.dispose")]),"function"!=typeof r)throw new TypeError("Property [Symbol.dispose] is not a function.");return o.push({v:n,d:r,a:e}),n}',
),
// size: 893, gzip size: 467
usingCtx: helper(
"7.23.9",
'export default function _usingCtx(){var r="function"==typeof SuppressedError?SuppressedError:function(r,n){var e=new Error;return e.name="SuppressedError",e.suppressed=n,e.error=r,e},n={},e=[];function using(r,n){if(null!=n){if(Object(n)!==n)throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");if(r)var o=n[Symbol.asyncDispose||Symbol.for("Symbol.asyncDispose")];if(null==o&&(o=n[Symbol.dispose||Symbol.for("Symbol.dispose")]),"function"!=typeof o)throw new TypeError("Property [Symbol.dispose] is not a function.");e.push({v:n,d:o,a:r})}return n}return{e:n,u:using.bind(null,!1),a:using.bind(null,!0),d:function(){var o=this.e;function next(){for(;r=e.pop();)try{var r,t=r.d.call(r.v);if(r.a)return Promise.resolve(t).then(next,err)}catch(r){return err(r)}if(o!==n)throw o}function err(e){return o=o!==n?new r(o,e):e,next()}return next()}}}',
),
// size: 1256, gzip size: 573
wrapRegExp: helper(
"7.19.0",
Expand Down
8 changes: 4 additions & 4 deletions packages/babel-helpers/src/helpers/dispose.js
@@ -1,10 +1,10 @@
/* @minVersion 7.22.0 */
function dispose_SuppressedError(suppressed, error) {
function dispose_SuppressedError(error, suppressed) {
if (typeof SuppressedError !== "undefined") {
// eslint-disable-next-line no-undef
dispose_SuppressedError = SuppressedError;
} else {
dispose_SuppressedError = function SuppressedError(suppressed, error) {
dispose_SuppressedError = function SuppressedError(error, suppressed) {
this.suppressed = suppressed;
this.error = error;
this.stack = new Error().stack;
Expand All @@ -17,7 +17,7 @@ function dispose_SuppressedError(suppressed, error) {
},
});
}
return new dispose_SuppressedError(suppressed, error);
return new dispose_SuppressedError(error, suppressed);
}

export default function _dispose(stack, error, hasError) {
Expand All @@ -35,7 +35,7 @@ export default function _dispose(stack, error, hasError) {
}

function err(e) {
error = hasError ? new dispose_SuppressedError(e, error) : e;
error = hasError ? new dispose_SuppressedError(error, e) : e;
hasError = true;

return next();
Expand Down
81 changes: 81 additions & 0 deletions packages/babel-helpers/src/helpers/usingCtx.ts
@@ -0,0 +1,81 @@
/* @minVersion 7.23.9 */

type Stack = {
v: any;
d: () => any;
a: boolean;
};

export default function _usingCtx() {
var _disposeSuppressedError =
typeof SuppressedError === "function"
? // eslint-disable-next-line no-undef
SuppressedError
: (function (error: Error, suppressed: Error) {
var err = new Error() as SuppressedError;
err.name = "SuppressedError";
err.suppressed = suppressed;
err.error = error;
return err;
} as SuppressedErrorConstructor),
empty = {},
stack: Stack[] = [];
function using(isAwait: boolean, value: any) {
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 });
}
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(): any {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
while ((resource = stack.pop())) {
try {
var resource,
disposalResult = 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 = error !== empty ? new _disposeSuppressedError(error, e) : e;

return next();
}

return next();
},
};
}
141 changes: 100 additions & 41 deletions packages/babel-plugin-proposal-explicit-resource-management/src/index.ts
Expand Up @@ -47,38 +47,96 @@ export default declare(api => {
path: NodePath<t.BlockStatement | t.StaticBlock>,
state,
) {
let stackId: t.Identifier | null = null;
let needsAwait = false;

for (const node of path.node.body) {
if (!isUsingDeclaration(node)) continue;
stackId ??= path.scope.generateUidIdentifier("stack");
const isAwaitUsing =
node.kind === "await using" ||
TOP_LEVEL_USING.get(node) === USING_KIND.AWAIT;
needsAwait ||= isAwaitUsing;

if (!TOP_LEVEL_USING.delete(node)) {
node.kind = "const";
if (state.availableHelper("usingCtx")) {
let ctx: t.Identifier | null = null;
let needsAwait = false;

for (const node of path.node.body) {
if (!isUsingDeclaration(node)) continue;
ctx ??= path.scope.generateUidIdentifier("usingCtx");
const isAwaitUsing =
node.kind === "await using" ||
TOP_LEVEL_USING.get(node) === USING_KIND.AWAIT;
needsAwait ||= isAwaitUsing;

if (!TOP_LEVEL_USING.delete(node)) {
node.kind = "const";
}
for (const decl of node.declarations) {
decl.init = t.callExpression(
t.memberExpression(
t.cloneNode(ctx),
isAwaitUsing ? t.identifier("a") : t.identifier("u"),
),
[decl.init],
);
}
}
node.declarations.forEach(decl => {
const args = [t.cloneNode(stackId), decl.init];
if (isAwaitUsing) args.push(t.booleanLiteral(true));
decl.init = t.callExpression(state.addHelper("using"), args);
});
}
if (!stackId) return;
if (!ctx) return;

const errorId = path.scope.generateUidIdentifier("error");
const hasErrorId = path.scope.generateUidIdentifier("hasError");
const disposeCall = t.callExpression(
t.memberExpression(t.cloneNode(ctx), t.identifier("d")),
[],
);

let disposeCall: t.Expression = t.callExpression(
state.addHelper("dispose"),
[t.cloneNode(stackId), t.cloneNode(errorId), t.cloneNode(hasErrorId)],
);
if (needsAwait) disposeCall = t.awaitExpression(disposeCall);
const replacement = template.statement.ast`
try {
var ${t.cloneNode(ctx)} = ${state.addHelper("usingCtx")}();
${path.node.body}
} catch (_) {
${t.cloneNode(ctx)}.e = _;
} finally {
${needsAwait ? t.awaitExpression(disposeCall) : disposeCall}
}
` as t.TryStatement;

t.inherits(replacement, path.node);

const replacement = template.statement.ast`
const { parentPath } = path;
if (
parentPath.isFunction() ||
parentPath.isTryStatement() ||
parentPath.isCatchClause()
) {
path.replaceWith(t.blockStatement([replacement]));
} else if (path.isStaticBlock()) {
path.node.body = [replacement];
} else {
path.replaceWith(replacement);
}
} else {
let stackId: t.Identifier | null = null;
let needsAwait = false;

for (const node of path.node.body) {
if (!isUsingDeclaration(node)) continue;
stackId ??= path.scope.generateUidIdentifier("stack");
const isAwaitUsing =
node.kind === "await using" ||
TOP_LEVEL_USING.get(node) === USING_KIND.AWAIT;
needsAwait ||= isAwaitUsing;

if (!TOP_LEVEL_USING.delete(node)) {
node.kind = "const";
}
node.declarations.forEach(decl => {
const args = [t.cloneNode(stackId), decl.init];
if (isAwaitUsing) args.push(t.booleanLiteral(true));
decl.init = t.callExpression(state.addHelper("using"), args);
});
}
if (!stackId) return;

const errorId = path.scope.generateUidIdentifier("error");
const hasErrorId = path.scope.generateUidIdentifier("hasError");

let disposeCall: t.Expression = t.callExpression(
state.addHelper("dispose"),
[t.cloneNode(stackId), t.cloneNode(errorId), t.cloneNode(hasErrorId)],
);
if (needsAwait) disposeCall = t.awaitExpression(disposeCall);

const replacement = template.statement.ast`
try {
var ${stackId} = [];
${path.node.body}
Expand All @@ -90,19 +148,20 @@ export default declare(api => {
}
` as t.TryStatement;

t.inherits(replacement.block, path.node);

const { parentPath } = path;
if (
parentPath.isFunction() ||
parentPath.isTryStatement() ||
parentPath.isCatchClause()
) {
path.replaceWith(t.blockStatement([replacement]));
} else if (path.isStaticBlock()) {
path.node.body = [replacement];
} else {
path.replaceWith(replacement);
t.inherits(replacement.block, path.node);

const { parentPath } = path;
if (
parentPath.isFunction() ||
parentPath.isTryStatement() ||
parentPath.isCatchClause()
) {
path.replaceWith(t.blockStatement([replacement]));
} else if (path.isStaticBlock()) {
path.node.body = [replacement];
} else {
path.replaceWith(replacement);
}
}
},
};
Expand Down
@@ -1,4 +1,4 @@
return async function () {
return (async function () {
let disposed = false;
let beforeReturn;
let inCatch;
Expand All @@ -8,15 +8,19 @@ return async function () {
await using x = {
async [Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")]() {
disposed = true;
}
throw 1;
},
};
beforeReturn = disposed;
throw 0;
} catch {
} catch (e) {
inCatch = disposed;
expect(e.name).toBe("SuppressedError");
expect(e.suppressed).toBe(1);
expect(e.error).toBe(0);
}

expect(beforeReturn).toBe(false);
expect(inCatch).toBe(true);
expect(disposed).toBe(true);
}();
})();
Expand Up @@ -7,12 +7,16 @@ try {
using x = {
[Symbol.dispose || Symbol.for("Symbol.dispose")]() {
disposed = true;
}
throw 1;
},
};
beforeReturn = disposed;
throw 0;
} catch {
} catch (e) {
inCatch = disposed;
expect(e.name).toBe("SuppressedError");
expect(e.suppressed).toBe(1);
expect(e.error).toBe(0);
}

expect(beforeReturn).toBe(false);
Expand Down
Expand Up @@ -5,14 +5,13 @@ function _fn() {
_fn = babelHelpers.asyncToGenerator(function* () {
yield 0;
try {
var _stack = [];
const x = babelHelpers.using(_stack, y, true);
var _usingCtx = babelHelpers.usingCtx();
const x = _usingCtx.a(y);
yield 1;
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally {
yield babelHelpers.dispose(_stack, _error, _hasError);
yield _usingCtx.d();
}
});
return _fn.apply(this, arguments);
Expand Down
@@ -1,10 +1,9 @@
try {
var _stack = [];
const x = babelHelpers.using(_stack, fn());
var _usingCtx = babelHelpers.usingCtx();
const x = _usingCtx.u(fn());
doSomethingWith(x);
} catch (_) {
var _error = _;
var _hasError = true;
_usingCtx.e = _;
} finally {
babelHelpers.dispose(_stack, _error, _hasError);
_usingCtx.d();
}

0 comments on commit 218faee

Please sign in to comment.