From 43777cffeee8e939dd744967bbeacd1cd46e01dd Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Thu, 19 Jun 2025 11:56:27 -0800 Subject: [PATCH] fix passing nested object to macro (#20467) Co-authored-by: nektro <5464072+nektro@users.noreply.github.com> --- src/bun.js/bindings/JSValue.zig | 6 +++++ src/bun.js/bindings/bindings.cpp | 13 ++++++++++ src/js_ast.zig | 7 ++--- test/bundler/bun-build-api.test.ts | 41 +++++++++++++++++++++++++++++- test/harness.ts | 6 +++++ 5 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/bun.js/bindings/JSValue.zig b/src/bun.js/bindings/JSValue.zig index 3a89ee7770..09a92f0032 100644 --- a/src/bun.js/bindings/JSValue.zig +++ b/src/bun.js/bindings/JSValue.zig @@ -397,6 +397,12 @@ pub const JSValue = enum(i64) { JSC__JSValue__putMayBeIndex(this, globalObject, key, value); } + extern fn JSC__JSValue__putToPropertyKey(target: JSValue, globalObject: *JSGlobalObject, key: JSC.JSValue, value: JSC.JSValue) void; + pub fn putToPropertyKey(target: JSValue, globalObject: *JSGlobalObject, key: JSC.JSValue, value: JSC.JSValue) error{JSError}!void { + JSC__JSValue__putToPropertyKey(target, globalObject, key, value); + if (globalObject.hasException()) return error.JSError; + } + extern fn JSC__JSValue__putIndex(value: JSValue, globalObject: *JSGlobalObject, i: u32, out: JSValue) void; pub fn putIndex(value: JSValue, globalObject: *JSGlobalObject, i: u32, out: JSValue) void { JSC__JSValue__putIndex(value, globalObject, i, out); diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 34db22da41..ea4b74cf94 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -3664,6 +3664,19 @@ void JSC__JSValue__put(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* arg1, object->putDirect(arg1->vm(), Zig::toIdentifier(*arg2, arg1), JSC::JSValue::decode(JSValue3)); } +void JSC__JSValue__putToPropertyKey(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* arg1, JSC::EncodedJSValue arg2, JSC::EncodedJSValue arg3) +{ + auto& vm = JSC::getVM(arg1); + auto scope = DECLARE_THROW_SCOPE(vm); + auto obj = JSValue::decode(JSValue0); + auto key = JSValue::decode(arg2); + auto value = JSValue::decode(arg3); + auto object = obj.asCell()->getObject(); + auto pkey = key.toPropertyKey(arg1); + RETURN_IF_EXCEPTION(scope, ); + object->putDirectMayBeIndex(arg1, pkey, value); +} + extern "C" void JSC__JSValue__putMayBeIndex(JSC::EncodedJSValue target, JSC::JSGlobalObject* globalObject, const BunString* key, JSC::EncodedJSValue value) { auto& vm = JSC::getVM(globalObject); diff --git a/src/js_ast.zig b/src/js_ast.zig index edeb32c2a1..b609627083 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -1967,11 +1967,12 @@ pub const E = struct { defer obj.unprotect(); const props: []const G.Property = this.properties.slice(); for (props) |prop| { - if (prop.kind != .normal or prop.class_static_block != null or prop.key == null or prop.key.?.data != .e_string or prop.value == null) { + if (prop.kind != .normal or prop.class_static_block != null or prop.key == null or prop.value == null) { return error.@"Cannot convert argument type to JS"; } - var key = prop.key.?.data.e_string.toZigString(allocator); - obj.put(globalObject, &key, try prop.value.?.toJS(allocator, globalObject)); + const key = try prop.key.?.data.toJS(allocator, globalObject); + const value = try prop.value.?.toJS(allocator, globalObject); + try obj.putToPropertyKey(globalObject, key, value); } return obj; diff --git a/test/bundler/bun-build-api.test.ts b/test/bundler/bun-build-api.test.ts index 4ad15e2595..fae46580c4 100644 --- a/test/bundler/bun-build-api.test.ts +++ b/test/bundler/bun-build-api.test.ts @@ -1,8 +1,9 @@ import assert from "assert"; import { describe, expect, test } from "bun:test"; import { readFileSync, writeFileSync } from "fs"; -import { bunEnv, bunExe, tempDirWithFiles } from "harness"; +import { bunEnv, bunExe, tempDirWithFiles, tempDirWithFilesAnon } from "harness"; import path, { join } from "path"; +import { cwd } from "process"; import { buildNoThrow } from "./buildNoThrow"; describe("Bun.build", () => { @@ -632,3 +633,41 @@ test("onEnd Plugin does not crash", async () => { })(), ).rejects.toThrow("On-end callbacks is not implemented yet. See https://github.com/oven-sh/bun/issues/2771"); }); + +test("macro with nested object", async () => { + const dir = tempDirWithFilesAnon({ + "index.ts": ` +import { testMacro } from "./macro" assert { type: "macro" }; + +export const testConfig = testMacro({ + borderRadius: { + 1: "4px", + 2: "8px", + }, +}); + `, + "macro.ts": ` +export function testMacro(val: any) { + return val; +} + `, + }); + + const build = await Bun.build({ + entrypoints: [join(dir, "index.ts")], + }); + + expect(build.outputs).toHaveLength(1); + expect(build.outputs[0].kind).toBe("entry-point"); + expect(await build.outputs[0].text()).toEqualIgnoringWhitespace(`// ${path.relative(cwd(), dir)}/index.ts +var testConfig = { + borderRadius: { + "1": "4px", + "2": "8px" + } +}; +export { + testConfig +}; +`); +}); diff --git a/test/harness.ts b/test/harness.ts index ee3cb2c0cb..001d6b39e0 100644 --- a/test/harness.ts +++ b/test/harness.ts @@ -262,6 +262,12 @@ export function tempDirWithFiles( return base; } +export function tempDirWithFilesAnon(filesOrAbsolutePathToCopyFolderFrom: DirectoryTree | string): string { + const base = tmpdirSync(); + makeTreeSync(base, filesOrAbsolutePathToCopyFolderFrom); + return base; +} + export function bunRun(file: string, env?: Record | NodeJS.ProcessEnv) { var path = require("path"); const result = Bun.spawnSync([bunExe(), file], {