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

Improve output of using #15959

Merged
merged 10 commits into from Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect users to use the core-js-pure conversion helper? 🤔
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily, but maybe users are importing "things that can be disposed" from libraries that have been compiled with core-js, and that thus use Symbol.for("Symbol.dispose") as their protocol.

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