Skip to content

Commit

Permalink
Support for await using
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed May 17, 2023
1 parent 52ce528 commit dad4497
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 31 deletions.
4 changes: 2 additions & 2 deletions packages/babel-helpers/src/helpers-generated.ts
Expand Up @@ -59,7 +59,7 @@ export default Object.freeze({
),
dispose: helper(
"7.0.0-beta.0",
"function dispose_SuppressedError(suppressed,error){return dispose_SuppressedError=function(suppressed,error){this.suppressed=suppressed,this.error=error,this.stack=(new Error).stack},dispose_SuppressedError.prototype=Object.create(Error.prototype,{constructor:{value:dispose_SuppressedError,writable:!0,configurable:!0}}),new dispose_SuppressedError(suppressed,error)}export default function _dispose(stack,error,hasError,SuppressedError){for(;stack.length>0;){const r=stack.pop();try{r.d.call(r.v)}catch(e){error=hasError?new(SuppressedError||dispose_SuppressedError)(e,error):e,hasError=!0}}if(hasError)throw error}",
"function dispose_SuppressedError(suppressed,error){return dispose_SuppressedError=function(suppressed,error){this.suppressed=suppressed,this.error=error,this.stack=(new Error).stack},dispose_SuppressedError.prototype=Object.create(Error.prototype,{constructor:{value:dispose_SuppressedError,writable:!0,configurable:!0}}),new dispose_SuppressedError(suppressed,error)}export default function _dispose(stack,error,hasError,SuppressedError){function next(){if(0!==stack.length){var r=stack.pop();if(r.a)return r.d.call(r.v).then(next,err);try{r.d.call(r.v)}catch(e){return err(e)}return next()}if(hasError)throw error}function err(e){return error=hasError?new(SuppressedError||dispose_SuppressedError)(e,error):e,hasError=!0,next()}return next()}",
),
iterableToArrayLimit: helper(
"7.0.0-beta.0",
Expand Down Expand Up @@ -87,7 +87,7 @@ export default Object.freeze({
),
using: helper(
"7.0.0-beta.0",
'export default function _using(stack,value){if(null!=value){var dispose=value[Symbol.dispose||Symbol.for("Symbol.dispose")];if("function"!=typeof dispose)throw new TypeError("Property [Symbol.dispose] is not a function.");stack.push({v:value,d:dispose})}return value}',
'export default function _using(stack,value,isAwait){if(null!=value){var dispose=value[Symbol.dispose||Symbol.for("Symbol.dispose")];if("function"!=typeof dispose)throw new TypeError("Property [Symbol.dispose] is not a function.");stack.push({v:value,d:dispose,a:isAwait})}return value}',
),
wrapRegExp: helper(
"7.19.0",
Expand Down
37 changes: 27 additions & 10 deletions packages/babel-helpers/src/helpers/dispose.js
Expand Up @@ -16,16 +16,33 @@ function dispose_SuppressedError(suppressed, error) {
}

export default function _dispose(stack, error, hasError, SuppressedError) {
while (stack.length > 0) {
const r = stack.pop();
try {
r.d.call(r.v);
} catch (e) {
error = hasError
? new (SuppressedError || dispose_SuppressedError)(e, error)
: e;
hasError = true;
function next() {
if (stack.length === 0) {
if (hasError) throw error;
return;
}

var r = stack.pop();
if (r.a) {
return r.d.call(r.v).then(next, err);
} else {
try {
r.d.call(r.v);
} catch (e) {
return err(e);
}
return next();
}
}

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

return next();
}
if (hasError) throw error;

return next();
}
4 changes: 2 additions & 2 deletions packages/babel-helpers/src/helpers/using.js
@@ -1,13 +1,13 @@
/* @minVersion 7.0.0-beta.0 */

export default function _using(stack, value) {
export default function _using(stack, value, isAwait) {
if (value !== null && value !== void 0) {
// core-js-pure uses Symbol.for("Symbol.dispose").
var 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 });
stack.push({ v: value, d: dispose, a: isAwait });
}
return value;
}
Expand Up @@ -3,6 +3,11 @@ import syntacExplicitResourceManagement from "@babel/plugin-syntax-explicit-reso
import { types as t, template } from "@babel/core";
import type { NodePath } from "@babel/traverse";

function isUsingDeclaration(node: t.Node): node is t.VariableDeclaration {
if (!t.isVariableDeclaration(node)) return false;
return node.kind === "using" || node.kind === "await using";
}

export default declare(api => {
// TOOD: assert version 7.22.0
api.assertVersion(7);
Expand All @@ -13,15 +18,15 @@ export default declare(api => {

visitor: {
VariableDeclaration(path) {
if (path.node.kind === "using") {
if (path.node.kind === "using" || path.node.kind === "await using") {
throw path.buildCodeFrameError(
`"using" declaration at the top-level of modules is not supported yet.`,
);
}
},
ForOfStatement(path: NodePath<t.ForOfStatement>) {
const { left } = path.node;
if (!t.isVariableDeclaration(left, { kind: "using" })) return;
if (!isUsingDeclaration(left)) return;

const { id } = left.declarations[0];
const tmpId = path.scope.generateUidIdentifierBasedOnNode(id);
Expand All @@ -37,23 +42,42 @@ export default declare(api => {
},
BlockStatement(path, state) {
let stackId: t.Identifier | null = null;
let needsAwait = false;

for (const node of path.node.body) {
if (!t.isVariableDeclaration(node, { kind: "using" })) continue;
node.kind = "const";
if (!isUsingDeclaration(node)) continue;
stackId ??= path.scope.generateUidIdentifier("stack");
const isAwaitUsing = node.kind === "await using";
needsAwait ||= isAwaitUsing;

node.kind = "const";
node.declarations.forEach(decl => {
decl.init = t.callExpression(state.addHelper("using"), [
t.cloneNode(stackId),
decl.init,
]);
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),
// Pass SuppressedError so that it can be used with "pure"
// polyfills that do not compile the contents of runtime
// helpers.
template.expression.ast`
typeof SuppressedError !== undefined && SuppressedError
`,
],
);
if (needsAwait) disposeCall = t.awaitExpression(disposeCall);

const replacement = template.statement.ast`
try {
var ${stackId} = [];
Expand All @@ -62,15 +86,7 @@ export default declare(api => {
var ${errorId} = _;
var ${hasErrorId} = true;
} finally {
${state.addHelper("dispose")}(
${t.cloneNode(stackId)},
${t.cloneNode(errorId)},
${t.cloneNode(hasErrorId)},
// Pass SuppressedError so that it can be used with "pure"
// polyfills that do not compile the contents of runtime
// helpers.
typeof SuppressedError !== undefined && SuppressedError
);
${disposeCall}
}
`;

Expand Down
@@ -0,0 +1,5 @@
{
using a = 1;
await using b = 2;
using c = 3;
}
@@ -0,0 +1,11 @@
try {
var _stack = [];
const a = babelHelpers.using(_stack, 1);
const b = babelHelpers.using(_stack, 2, true);
const c = babelHelpers.using(_stack, 3);
} catch (_) {
var _error = _;
var _hasError = true;
} finally {
await babelHelpers.dispose(_stack, _error, _hasError, typeof SuppressedError !== undefined && SuppressedError);
}
@@ -0,0 +1,6 @@
{
await using x = obj;
stmt;
await using y = obj, z = obj;
doSomethingWith(x, y);
}
@@ -0,0 +1,13 @@
try {
var _stack = [];
const x = babelHelpers.using(_stack, obj, true);
stmt;
const y = babelHelpers.using(_stack, obj, true),
z = babelHelpers.using(_stack, obj, true);
doSomethingWith(x, y);
} catch (_) {
var _error = _;
var _hasError = true;
} finally {
await babelHelpers.dispose(_stack, _error, _hasError, typeof SuppressedError !== undefined && SuppressedError);
}
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["proposal-explicit-resource-management"]
}

0 comments on commit dad4497

Please sign in to comment.