Repository navigation
Conversation
…bun.ts is not the builtin MacroEntryPoint::generate chose the `globalThis.Bun` entry module when the file name of the macro's path, without directory and extension, was `bun`. A macro file named bun.ts, bun.js or bun was never imported. An export with the name of a Bun function ran that function in its place. The builtin is now chosen only when the entry path is exactly `bun`.
|
Updated 7:22 PM PT - Oct 24th, 2026
✅ @robobun, your commit 21f5dcb27d4eeb3446065270107a8cfcf2cf964e passed in 🧪 To try this PR locally: bunx bun-pr 46827That installs a local version of the PR into your bun-46827 --bun |
|
Status: open, waiting for CI. How it was reproduced. On main (179664e) and on 1.4.3-canary, a macro imported from
With this PR the two commands print PR: #46827 |
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Built-in macro selection and regression coverage src/bundler/entry_points.rs, test/bundler/transpiler/macro-test.test.ts |
The built-in path requires an empty directory and filename bun. Tests check direct imports, an imports-map alias, and similar specifiers under bun run and external-package build output. |
Suggested reviewers: jarred-sumner
Priority:
Merge Risk: 
Minimal · up to 21f5d
-
The change is ready to merge after normal checks; no actionable risk remains identified.
Pre-merge checks: 
4
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands.
There was a problem hiding this comment.
LGTM — a one-line tightening of the builtin check in MacroEntryPoint::generate, with a fixture test covering the variants.
What was reviewed:
- Traced
PathName::initin src/paths/lib.rs:filenameis the whole input whendiris empty, so the new condition matches only the 3-byte specifierbun;bun/,/bun, and WindowsC:\bunall fall through to the module path as intended. - Confirmed the builtin arrives as exactly
bunvia theBUN_EXTRA_ALIAS_KVSalias (src/resolve_builtins/HardcodedModule.rs) throughMacroContext::callandload_macro_entry_point; grepped for siblingbase == b"bun"sites — this is the only one. - Checked the
stderr: ""assertion against debug builds: the[macro] callline is emitted viaprettyln!to stdout, which thelastLineextraction already accounts for.
Extended reasoning...
The change replaces import_path.base == b"bun" with import_path.dir.is_empty() && import_path.filename == b"bun" in src/bundler/entry_points.rs and adds a describe.concurrent block in test/bundler/transpiler/macro-test.test.ts exercising bun run and bun build --packages=external against a tempDir fixture with bun.ts, scripts/bun, an exports target ./bun.js, @ scope/bun, bun/, the real builtin, and an imports-map alias. It touches no security-sensitive surface; the only behavioral shift is that macro specifiers whose basename is bun now load as modules instead of silently aliasing globalThis.Bun, which is the bug being fixed. The fix is a single line whose semantics I verified directly from PathName::init and the hardcoded alias table, no CODEOWNER covers the changed files, and the test follows the harness conventions (tempDir, bunExe, bunEnv, concurrent pipe draining, combined object assertion), which decided approve.
Problem
bun(bun.ts, anexportstarget ./bun.js) is never loaded. An export with the name of aBunfunction runs that function instead. A userwrite(path, text)macro callsBun.writeat build time, exit 0.error: Macro 'who' not found in 'bun', thenerror: "MacroLoadError" error in macro.MacroEntryPoint::generate(src/bundler/entry_points.rs:202) testsimport_path.base == b"bun".basehas no directory or extension, so/abs/dir/bun.tsmatches.Fix
import_path.dir.is_empty() && import_path.filename == b"bun". It is true only for the exact pathbun, which is how the builtin arrives.test/bundler/transpiler/macro-test.test.ts(2 new tests, both fail on main). Also thetest/regression/issue/macro tests.Background
Bun.registerMacro(id, fn). For the builtin it readsglobalThis.Bun[x].PathName::initsplits a path intodir,base,extandfilename. With an emptydir,filenameis the whole path.&[u8]path parameter written one time: it changes howpkg//macroloads and overlaps Real lifetimes for Source, import records and resolver results #44493. Considered no builtin arm: inheritedBunnames stop resolving.Downsides
--external '*', a relative macro import resolves from the working directory (resolver: do not apply user externals to macro imports #40631). Abun-named file failed there and now loads like any other name, possibly the wrong copy.bun/subwith--external bunstill gets the builtin.generategrows 18 bytes. First load of a macro export: 5567 to 5568 instructions. Per macro call: 0.Notes
Repro (no network, any folder). On main the first command prints
<a>and the second prints the error. With this PR they printmine: <a>andfrom ./bun.ts.Origin. Found by a read of the code, in the review of #42706 (#42706 (comment)). No user reported it. The
basetest came with #7113. Until this lands, a rename of the file or a one-lineexport * from "./bun.ts"file avoids the error case. The silent case gives no sign that a change is necessary.What main does, by export name.
Bunhas no such property:Macro '<name>' not found in 'bun'. A property that is not a function (version):error: Macro must be a function. A function (escapeHTML,write): that function runs with the macro's arguments. Withwrite("./notes.txt", "x"),bun buildreplaced an existing 39-byte notes.txt with 1 byte and printedconsole.log(1);.Why the line is exact.
PathName::init(src/paths/lib.rs) setsfilenameto the text afterdir, or to the whole input whendiris empty. So the test is true only when the entry path is the 3 bytesbun. That string comes from the alias lookup inMacroContext::call(import ... from "bun") and from the resolver for animportsentry that maps tobun.Before and after, release builds of 179664e and this PR,
bun runandbun build, 61 specifier and mode pairs.globalThis.Bunmodule to theawait import()module: ./bun.ts, ./lib/bun.js, ./noext/bun (no extension), a package withexportstarget ./bun.js, and with--packages=externalalso@scope/bunandbun/.bun,#bunthrough animportsmap,node:fs, ./other.ts,pkg//macro, adata:URL.The tests pin the exact compare. A build with
dir.is_empty() && ext.is_empty() && base == b"bun"passes thebun runtest and fails the--packages=externaltest, where the pathbun/takes the builtin module.Measurements, release
bun-profile, 179664e against this PR.load_macro_entry_pointto theJSLock::lockcall (gdbstepi, 3 equal runs each): file macro 5567 to 5568 instructions,bunbuiltin 2227 to 2229.MacroEntryPoint::generate1171 to 1189 bytes (nm -S)..text82164617 to 82164873 bytes (size -A). The strippedbunfile has the same size.heapStats(),BUN_JSC_useGC=0):bunbuiltin 385 to 385, file macro 433 to 433. ./bun.ts is 433 with this PR.Order against the open PRs on this function.
src/bundler/entry_points.rsis byte-identical in the two PRs, so they merge in either order with no conflict (git merge-tree). This PR has no dependency on resolver: keep every main field of a package.json in the shared cache, select per resolver #46652.MacroEntryPoint. The tests here startbunon fixtures, so they stay valid there.Not changed.
bun buildaborts a debug build (ASSERTION FAILED: container.vm().currentThreadIsHoldingAPILock()): resolver: do not apply user externals to macro imports #40631.MacroEntryPoint::generate_idhashes the path and the export name with nothing between them.util.jswith exportxValueandutil.jsxwith exportValueare one macro:["util.js:xValue","util.js:xValue"]. Same on main. No open PR.VirtualMachine::_resolve(src/jsc/VirtualMachine.rs:5260) resolves a specifier whose last part isbun:wrapto the runtime module. Same class, different subsystem. No open PR.data:URL macro whose last/comes directly after a/loses one slash, because the entry module joinsdirandfilename.Self-review, the 5 rejected concerns.
globalThis.Bunmodule (2 concerns): it changes the builtin path.import { toString } from "bun"resolves today and a module namespace has no such export.generate_idhere: a different defect, and it changes every macro id.The first design for this fix changed
generateto take the path as&[u8]and write it one time. The self-review cut it to this line: same result for every path above, no change forpkg//macro, no overlap with #44493, and the same bytes as #46802.Suites run with the debug build.
test/bundler/transpiler/macro-test.test.ts(36 pass, on main 34 pass and 2 fail),test/regression/issue/03830.test.ts,22656,26360,39900,test/bundler/transpiler/scope-mismatch-panic.test.ts(44 pass),test/js/node/disallow-code-generation-from-strings.test.ts(34 pass, 4 skip, with--timeout 600000).