mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
fix(macros): prevent incorrect const inlining of macro-returned objects/arrays
Objects and arrays returned from macros were being treated as "const values" for constant propagation, causing each reference to be replaced with a fresh copy. This broke mutation semantics where obj.foo = x followed by reading obj.foo would read from a different object. Fixes #26362 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2657,8 +2657,11 @@ pub const Data = union(Tag) {
|
||||
.e_inlined_enum,
|
||||
=> true,
|
||||
.e_string => |str| str.next == null,
|
||||
.e_array => |array| array.was_originally_macro,
|
||||
.e_object => |object| object.was_originally_macro,
|
||||
// Objects and arrays are NOT const values because they are mutable.
|
||||
// Inlining them would create fresh copies at each reference site,
|
||||
// breaking mutation semantics (e.g., obj.foo = 1 would modify a
|
||||
// different object than a subsequent read of obj.foo).
|
||||
// See: https://github.com/oven-sh/bun/issues/26362
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
86
test/regression/issue/26362.test.ts
Normal file
86
test/regression/issue/26362.test.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDir } from "harness";
|
||||
|
||||
test("objects returned from macros should be mutable", async () => {
|
||||
using dir = tempDir("issue-26362", {
|
||||
"macro.ts": `export const getObj = () => ({});`,
|
||||
"index.ts": `
|
||||
import { getObj } from "./macro.ts" with { type: "macro" };
|
||||
|
||||
const obj = getObj();
|
||||
|
||||
// Object should be extensible (not frozen/sealed)
|
||||
if (!Object.isExtensible(obj)) {
|
||||
throw new Error("Object is not extensible");
|
||||
}
|
||||
|
||||
// Should be able to add properties
|
||||
obj.foo = "bar";
|
||||
|
||||
// Should be able to read back the property we just set
|
||||
if (obj.foo !== "bar") {
|
||||
throw new Error("Property was not set correctly, got: " + obj.foo);
|
||||
}
|
||||
|
||||
console.log("success");
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "index.ts"],
|
||||
cwd: String(dir),
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stderr).toBe("");
|
||||
expect(stdout).toInclude("success");
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("arrays returned from macros should be mutable", async () => {
|
||||
using dir = tempDir("issue-26362-array", {
|
||||
"macro.ts": `export const getArr = () => [];`,
|
||||
"index.ts": `
|
||||
import { getArr } from "./macro.ts" with { type: "macro" };
|
||||
|
||||
const arr = getArr();
|
||||
|
||||
// Array should be extensible
|
||||
if (!Object.isExtensible(arr)) {
|
||||
throw new Error("Array is not extensible");
|
||||
}
|
||||
|
||||
// Should be able to push elements
|
||||
arr.push("first");
|
||||
|
||||
// Should be able to read back what we pushed
|
||||
if (arr[0] !== "first") {
|
||||
throw new Error("Element was not pushed correctly, got: " + arr[0]);
|
||||
}
|
||||
|
||||
if (arr.length !== 1) {
|
||||
throw new Error("Array length is wrong, got: " + arr.length);
|
||||
}
|
||||
|
||||
console.log("success");
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "index.ts"],
|
||||
cwd: String(dir),
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stderr).toBe("");
|
||||
expect(stdout).toInclude("success");
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
Reference in New Issue
Block a user