Skip to content

Commit 27bcff9

Browse files
fix(javascript): keep await using and throwing bigint operations
The one pass folded an `await using` declaration's value into its read, so the resource was never disposed: only `using` was excluded. A bigint division by `0n` or negative exponent throws, so `/`, `%` and `**` on bigints now count as effects unless the right side is a literal that cannot throw. Claude-Session: https://claude.ai/code/session_01Tgxgyb4N35ndP6cC4KcrXC
1 parent 3bc0878 commit 27bcff9

3 files changed

Lines changed: 31 additions & 8 deletions

File tree

‎lib/javascript/side-effects.js‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,13 @@ const binaryConverts = (tree, operator, left, right, context) => {
384384
return false;
385385
}
386386
if (leftKind === "bigint" || rightKind === "bigint") {
387-
// A bigint mixes with nothing else, and `>>>` throws on one.
388-
return leftKind !== rightKind || operator === ">>>";
387+
// A bigint mixes with nothing else, `>>>` throws on one, and so do a
388+
// division by `0n` and a negative exponent, which no literal is.
389+
if (leftKind !== rightKind || operator === ">>>") return true;
390+
if (operator !== "/" && operator !== "%" && operator !== "**") return false;
391+
const divisor = unwrap(tree, right);
392+
if (tree.type(divisor) !== NodeType.Literal) return true;
393+
return operator !== "**" && !tree.value(divisor);
389394
}
390395
return !(becomesNumber(leftKind) && becomesNumber(rightKind));
391396
};

‎lib/javascript/syntax-printer.js‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63242,7 +63242,7 @@ const createNodePrinter = (modules) => {
6324263242
if (scopesVars) setFirst[declarator] = quiet ? 1 : 2;
6324363243
if (!hasEdgeComments(statement) && !hasEdgeComments(declarator)) {
6324463244
candidates.push(declarator);
63245-
if (tree.textOf(Part.kind, statement) !== "using") {
63245+
if (!disposes(statement)) {
6324663246
keepsEffect[declarator] = 2;
6324763247
}
6324863248
}
@@ -63351,6 +63351,15 @@ const createNodePrinter = (modules) => {
6335163351
);
6335263352
};
6335363353

63354+
/**
63355+
* @param {number} statement a variable declaration
63356+
* @returns {boolean} whether it is `using` or `await using`, which disposes of its values
63357+
*/
63358+
const disposes = (statement) => {
63359+
const kind = tree.textOf(Part.kind, statement);
63360+
return kind === "using" || kind === "await using";
63361+
};
63362+
6335463363
/**
6335563364
* Whether the function the walk is in may run before what comes before it
6335663365
* in the given one: it is in a function the given one declares, which is
@@ -63399,10 +63408,7 @@ const createNodePrinter = (modules) => {
6339963408
holdsCollapsed(next);
6340063409
return;
6340163410
}
63402-
if (
63403-
type !== NodeType.VariableDeclaration ||
63404-
tree.textOf(Part.kind, statement) === "using"
63405-
) {
63411+
if (type !== NodeType.VariableDeclaration || disposes(statement)) {
6340663412
return;
6340763413
}
6340863414
// Each declarator folds into the next value after it, of a declarator
@@ -63610,7 +63616,7 @@ const createNodePrinter = (modules) => {
6361063616
case NodeType.SwitchStatement:
6361163617
return childOf(Field.discriminant, statement);
6361263618
case NodeType.VariableDeclaration:
63613-
if (tree.textOf(Part.kind, statement) === "using") return 0;
63619+
if (disposes(statement)) return 0;
6361463620
return childOf(
6361563621
Field.init,
6361663622
tree.field(0, Field.declarations, statement)

‎test/unitCases/javascript/printer.unittest.js‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15212,6 +15212,18 @@ describe("JavaScript minifier printing a SyntaxTree", () => {
1521215212
{},
1521315213
"function f(e){if(e)return 1;else{class f{}g(new f)}if(e)return 2;else{function n(){}n()}if(e)return 3;else/*! c */{h()}}"
1521415214
],
15215+
// A value `using` or `await using` disposes of folds nowhere.
15216+
[
15217+
"async function f(){ await using x = g(); h(x) } async function k(){ await using y = g(), z = q(y); h(z) } function m(){ using w = g(); h(w) }",
15218+
{},
15219+
"async function f(){await using n=g();h(n)}async function k(){await using n=g(),i=q(n);h(i)}function m(){using n=g();h(n)}"
15220+
],
15221+
// Dividing a bigint by zero or raising it to a negative power throws, so it stays unless the right side is a literal that cannot.
15222+
[
15223+
"function b(a){ 1n / 0n; 2n ** -1n; 1n % 0n; 1n / 2n; 1n % 3n; 2n ** 3n; 1n / (2n); 1n / a; 1n + 2n }",
15224+
{},
15225+
"function b(n){1n/0n;2n**-1n;1n%0n;1n/n}"
15226+
],
1521515227
// A `switch` drops a `break` ending it and a `default` doing nothing last, and a value folds into `import()`.
1521615228
[
1521715229
"function f(c){ switch (c) { case -1: c = 1; break; case 0: c = 2; break; default: break } switch (c) { case 1: x(); break } switch (c) { default: break } switch (c) { case 1: y(); break; default: } a: switch (c) { case 1: z(); break a } switch (c) { case 1: y(); /*! c */ break } var i = r(c); return import(i) }",

0 commit comments

Comments
 (0)