From 170fe394cbb410ef29a719914f411dc10c730d86 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:09:56 +0000 Subject: [PATCH 1/3] minify: do not fold new Array(x, ...spread) into an array literal With more than one argument, minify_syntax folds new Array(a, b) into [a, b]. When an argument is a spread, the argument count is not known until runtime: new Array(5, ...rest) is new Array(5), a length, when rest is empty, but the fold produced [5]. Keep the constructor call for any spread argument, as the single-argument case already does. --- src/ast/known_global.rs | 11 ++++++++++- test/bundler/bundler_minify.test.ts | 14 +++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/ast/known_global.rs b/src/ast/known_global.rs index 47fe7ff9dbaf..04d5945cbb38 100644 --- a/src/ast/known_global.rs +++ b/src/ast/known_global.rs @@ -243,7 +243,16 @@ impl KnownGlobal { // > 1 _ => { // new Array(1, 2, 3) -> [1, 2, 3] - // But NOT new Array(3) which creates an array with 3 empty slots + // But NOT new Array(3) which creates an array with 3 empty slots, + // and `new Array(5, ...rest)` is `new Array(5)` when `rest` is empty. + if e + .args + .slice() + .iter() + .any(|arg| matches!(arg.data, js_ast::ExprData::ESpread(_))) + { + return Some(Self::call_from_new(e, loc)); + } Some(js_ast::Expr::init( E::Array { items: bun_alloc::AstAlloc::take(&mut e.args), diff --git a/test/bundler/bundler_minify.test.ts b/test/bundler/bundler_minify.test.ts index 4c8ea49b3bc0..433d92383de5 100644 --- a/test/bundler/bundler_minify.test.ts +++ b/test/bundler/bundler_minify.test.ts @@ -1197,6 +1197,8 @@ describe("bundler", () => { capture(new Array()); capture(new Array(3)); capture(new Array(1, 2, 3)); + capture(new Array(...unknownValue)); + capture(new Array(5, ...unknownValue)); // Test Array with non-numeric single arguments (should convert to literal) capture(new Array("string")); @@ -1237,6 +1239,8 @@ describe("bundler", () => { 2, 3 ]`, // new Array(1, 2, 3) -> [1, 2, 3] + "Array(...unknownValue)", // a spread may leave a single number behind, which is a length + "Array(5, ...unknownValue)", `[ "string" ]`, // new Array("string") -> ["string"] @@ -1329,6 +1333,12 @@ describe("bundler", () => { const a3 = new Array(n); const a4 = Array(n); capture(a3.length === a4.length && a3.length === 3 && a3[0] === undefined); + + // A spread can leave a single number behind at runtime, and then it is a length + const none = []; + const a5 = new Array(5, ...none); + capture(a5.length === 5); + capture(0 in a5 === false); // Test Object semantics const o1 = new Object(); @@ -1357,6 +1367,8 @@ describe("bundler", () => { "0 in sparse === !1", 'JSON.stringify(sparse) === "[null,null,null,null,null]"', "a3.length === a4.length && a3.length === 3 && a3[0] === void 0", + "a5.length === 5", + "0 in a5 === !1", "typeof o1 === typeof o2", "o1.constructor === o2.constructor", "typeof f1 === typeof f2", @@ -1367,7 +1379,7 @@ describe("bundler", () => { minifySyntax: true, target: "bun", run: { - stdout: "true\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue", + stdout: "true\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue", }, }); From 7c18c2cbb8411d9844dd23e10a7d8d12d202981f Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:11:36 +0000 Subject: [PATCH 2/3] Bump the runtime transpiler cache version --- src/jsc/RuntimeTranspilerCache.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/jsc/RuntimeTranspilerCache.rs b/src/jsc/RuntimeTranspilerCache.rs index 98e9aa0a506b..a37b8714fbba 100644 --- a/src/jsc/RuntimeTranspilerCache.rs +++ b/src/jsc/RuntimeTranspilerCache.rs @@ -56,7 +56,8 @@ bun_core::declare_scope!(cache, visible); /// u8/u16/u32 ids and implied slots dropped, instead of fixed u32 arrays. /// Version 27: ModuleInfo string table holds Latin-1 / UTF-16 bodies, not WTF-8. /// Version 28: the define table and `--drop` entries participate in the features hash. -const EXPECTED_VERSION: u32 = 28; +/// Version 29: `new Array(x, ...spread)` is no longer folded into an array literal. +const EXPECTED_VERSION: u32 = 29; /// Source files smaller than this are not written to / read from the on-disk /// transpiler cache. Originally 50 KiB, which excluded almost every file in a From fb5b797c42b16a381d4c248b59956a7266b94452 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 06:25:58 +0000 Subject: [PATCH 3/3] [autofix.ci] apply automated fixes --- src/ast/known_global.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ast/known_global.rs b/src/ast/known_global.rs index 04d5945cbb38..15c439d79221 100644 --- a/src/ast/known_global.rs +++ b/src/ast/known_global.rs @@ -245,8 +245,7 @@ impl KnownGlobal { // new Array(1, 2, 3) -> [1, 2, 3] // But NOT new Array(3) which creates an array with 3 empty slots, // and `new Array(5, ...rest)` is `new Array(5)` when `rest` is empty. - if e - .args + if e.args .slice() .iter() .any(|arg| matches!(arg.data, js_ast::ExprData::ESpread(_)))