Skip to content

transpiler: stop minifying new Error() (and other known-global constructs) into calls that JSC tail-calls - #37388

Open
robobun wants to merge 7 commits into
mainfrom
farm/57c5d184/keep-new-error-constructors
Open

transpiler: stop minifying new Error() (and other known-global constructs) into calls that JSC tail-calls#37388
robobun wants to merge 7 commits into
mainfrom
farm/57c5d184/keep-new-error-constructors

Conversation

@robobun

@robobun robobun commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

An error created with return new Error(...) has no stack frame for the function that created it. Error factory helpers are common (const e = new Error(msg); return e; has the same problem, since the single-use binding gets inlined into the return), so a lot of user stack traces are missing their most useful frame.

function a() { return new Error("a"); }
function b() { const e = new TypeError("b"); return e; }
const c = () => new RangeError("c");
for (const fn of [a, b, c]) console.log(fn().stack.split("\n")[1]);
# bun 1.3.14, 1.4.0, main      # bun with this PR            # node
    at /tmp/x.js:4:41              at a (/tmp/x.js:1:27)        at a (/tmp/x.js:1:23)
    at /tmp/x.js:4:41              at b (/tmp/x.js:2:30)        at b (/tmp/x.js:2:26)
    at /tmp/x.js:4:41              at c (/tmp/x.js:3:21)        at c (/tmp/x.js:3:17)

(The remaining four column difference in the .stack string is a separate, pre-existing thing: that formatter reports the constructor name's column for any new X() frame, user classes included, while Bun's code frames and V8 report the new keyword.)

Cause

KnownGlobal::minify_global_constructor (src/ast/known_global.rs) rewrites new Error(...), and the other seven native error constructors, into plain calls Error(...) to save four bytes. It runs whenever minify_syntax is on, which is always the case for the runtime transpiler (bun run, bun test, ...) and for bun build --minify.

Modules are strict mode code, and JSC implements proper tail calls in strict mode. return Error("a") is a call in tail position, so JSC emits op_tail_call and pops a()'s frame before the Error constructor runs; the stack captured in ErrorInstance::finishCreation never contains it (BUN_JSC_dumpGeneratedBytecodes=1 shows tail_call for a versus call when another statement follows). Constructs are never tail calls, which is why throw new Error(), class constructors, and any function with a statement between the new and the return were unaffected.

Fix

Keep the new on the error constructors. The rewrite is not semantics preserving on JSC: Error() and new Error() build the same object, but in tail position they do not produce the same .stack. Tail calls themselves stay enabled (disabling them was rejected in #26001 for performance); this only stops the transpiler from turning constructs the user wrote into tail calls they did not write. esbuild's --minify keeps new Error() as well. The cost is 4 bytes per error construction in --minify output (2 for the zero-argument form, which prints as new Error); the React SSR development bundle in bundler_npm.test.ts grows by 360 bytes from 222000.

The same function stripped new from new Function(...), and that constructor reads the calling frame too: it takes the source origin of the new body from it, which is what a dynamic import() inside the body resolves against. With return new Function("return import('./x.mjs')") in lib/make.mjs, Bun resolved ./x.mjs relative to the module that called make() instead of lib/ (node: lib/). Same fix, so it is included here.

That left new Object(x) and new Array(x) (non-literal argument, or a possible length) as the only constructs still rewritten into calls, and Array has the same problem once more: return Array(...lengths) is a varargs tail call, so the RangeError thrown for an invalid length has no frame for the function that asked for the array (a direct Array(n) only keeps its frame because JSC's bytecode generator special-cases that callee; Object(x) cannot throw). Rather than document which calls happen to be safe, minify_global_constructor now only ever folds a construct into a literal (new Object() to {}, new Array(1, 2) to [1, 2], new Array(3) to [,,,] under whitespace minification, and so on) and never into a call; call_from_new is gone. That costs another 4 bytes per new Array(n) / new Object(x) site in --minify output, 28 bytes on the React SSR bundle (222388 total). The constructors that are deliberately not folded stay in the table, as RegExp did, so the rule is recorded next to the code that would do it.

While here, the one literal folding that was not equivalent is fixed too: new Array(5, ...rest) was folded into [5, ...rest], but with an empty rest the original is new Array(5), a length. Any spread argument now leaves the construct alone; GlobalConstructorSemanticsPreserved runs that case.

Since the runtime transpiler cache stores transpiled output and is keyed only on cache version, source hash and option flags, entries written by earlier builds would keep serving the call form; EXPECTED_VERSION in RuntimeTranspilerCache.rs moves to 26, as the note at the top of parser.rs asks for.

As a consequence, the position Bun reports for an error created with new XError(...) in code frames and call sites moves back from the constructor name to the new keyword, which is where V8 reports it and where Bun reported it before #22493 introduced the rewrite. The snapshots and column assertions that #22493 updated are updated back (inspect-error, inspect, reportError, console-log, test-test, test-error-code-done-callback, hot, stack, bundler_npm), as is the dev error page frame position in serve.test.ts. inspect-error.test.js's helper that strips debug-build-only internal frames also had to learn the current at require (51:24) format, otherwise the file cannot pass under a debug build.

How did you verify your code works?

New tests, all failing on the unfixed build and passing with the fix:

  • test/js/bun/test/stack.test.ts: a function returning new Error() is in the error's stack. Covers all eight constructors, the inlined const shape, and an expression-bodied arrow; on the unfixed build all ten factories are missing from their error's stack. A second test checks the RangeError from return new Array(...lengths) still names the function (unfixed build: at Array (unknown) followed directly by the caller).
  • test/bundler/bundler_minify.test.ts: minify/ErrorConstructorKeepsNew (bundler output keeps new for every error constructor with minifySyntax; previously ErrorConstructorOptimization asserted the opposite), minify/ReturnedConstructorKeepsItsFrame (runs the minified bundle and checks the Error, inlined TypeError and throwing Array frames), and the new Function(...), new Object(x), new Array(n) / new Array(...xs) captures in minify/AdditionalGlobalConstructorOptimization and minify/ArrayConstructorWithNumberAndMinifyWhitespace, which previously asserted the call form.
  • test/bundler/transpiler/runtime-transpiler.test.ts: a body built with return new Function() resolves import() relative to the returning module (prints which.mjs instead of lib/which.mjs on the unfixed build).

Every other test file touched here fails on the unfixed build, because of the column change or because it pinned the call form (bundler_npm, minify-new-array-with-if), and passes with the fix. I also ran test/js/bun/test, test/js/bun/util, test/js/node/util, test/js/node/v8, test/js/web/console, test/regression/issue, test/bundler/transpiler, test/cli/run and test/cli/hot against the debug build; the remaining failures there are debug-build timeouts, tests needing services or network, or cross-file pollution when many files run in one process, and the stack related ones among them pass when run on their own.


no test proof · iteration 1 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/bundler/bundler_npm.test.ts test/cli/hot/hot.test.ts test/js/bun/http/serve.test.ts test/js/bun/test/test-test.test.ts test/js/bun/util/inspect.test.js

…ays in the stack

With minify_syntax on (always the case for the runtime transpiler, and for
bun build --minify) `new Error(...)` and the other seven native error
constructors were rewritten to plain calls. Modules are strict mode code
and JSC implements proper tail calls there, so `return Error(...)` pops
the calling function's frame before the error captures its stack. Any
`return new Error(...)` helper, including `const e = new Error(); return e`
after the single-use binding is inlined, produced a stack without the
function that created the error. Constructs are never tail calls, so keep
the `new`.

Error positions in code frames and call sites move back from the
constructor name to the `new` keyword, which is where V8 reports them;
the affected snapshots are updated accordingly.
@robobun

robobun commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 3:30 AM PT - Aug 11th, 2026

@robobun, your commit 5d87dd89675932e3312e1b67d8161a0332ee36eb passed in Build #92149! 🎉


🧪   To try this PR locally:

bunx bun-pr 37388

That installs a local version of the PR into your bun-37388 executable, so you can run:

bun-37388 --bun

@robobun

robobun commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author

Status: reproduced on bun 1.3.14, 1.4.0 and a debug build of main with the snippet in the description (return new Error() helpers have no frame of their own; node has it). Fix is in src/ast/known_global.rs: known-global constructs are now only folded into literals, never rewritten into calls (the Error constructors, Function, and the Object/Array fallbacks were), the new Array(x, ...rest) fold is gone, plus the transpiler cache version bump in src/jsc/RuntimeTranspilerCache.rs. New coverage in test/js/bun/test/stack.test.ts, test/bundler/bundler_minify.test.ts and test/bundler/transpiler/runtime-transpiler.test.ts; the column snapshots from #22493, the dev error page position in serve.test.ts and the minify-new-array-with-if snapshot are updated. Waiting on CI for 5d87dd8.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The minifier preserves new for Error-family and Function constructors. Tests update stack frames, source-map positions, snapshots, bundler output, and dynamic import origin behavior.

Changes

Error stack preservation

Layer / File(s) Summary
Preserve constructors and stack frames
src/ast/known_global.rs, test/bundler/bundler_minify.test.ts, test/js/bun/test/stack.test.ts
Error-family and Function constructors retain new. Tests verify emitted syntax and factory-function stack frames.
Validate generated Function origin
test/bundler/transpiler/runtime-transpiler.test.ts
The transpiler test verifies that dynamic imports from new Function() resolve relative to the returning module.
Update generated position expectations
test/bundler/bundler_npm.test.ts, test/cli/hot/hot.test.ts
React SSR mappings, bundle size, and hot-reload source-map offsets reflect the generated output.
Update runtime stack diagnostics
test/js/bun/test/test-error-code-done-callback.test.ts, test/js/bun/test/test-test.test.ts, test/js/bun/util/inspect-error.test.js, test/js/bun/util/inspect.test.js, test/js/bun/util/reportError.test.ts, test/js/web/console/console-log.test.ts, test/js/bun/http/serve.test.ts
Stack columns, caret positions, line numbers, snapshots, and internal-frame filtering reflect revised error locations.

Possibly related PRs

  • oven-sh/bun#35978: Both PRs modify bundler or transpiler generation and runtime import handling.
  • oven-sh/bun#36437: Both PRs modify error stack-trace preservation and constructor-position expectations.

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: preserving new expressions for Error and other known-global constructors during minification.
Description check ✅ Passed The description includes both required sections and provides detailed cause, fix, verification steps, test coverage, and known test limitations.

Comment @coderabbitai help to get the list of available commands.

…s from the calling frame

`return Function(...)` is the same tail call as `return Error(...)`: the
Function constructor then takes the source origin for the new body from the
caller's caller, so an import() inside the body resolves relative to the
wrong file.
@robobun robobun changed the title transpiler: keep new on Error constructors so the creating function stays in the stack trace transpiler: keep new on Error and Function constructors instead of minifying them into tail calls Aug 11, 2026
Comment thread src/ast/known_global.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/bundler/transpiler/runtime-transpiler.test.ts`:
- Line 276: Resolve main.mjs against the existing dir path before constructing
the Bun.spawn command, and use that absolute path in cmd instead of the relative
"main.mjs" entry. Preserve the current working-directory behavior and the rest
of the spawn configuration.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 885a989c-bf00-4f97-a02b-2f0ccfa0aadb

📥 Commits

Reviewing files that changed from the base of the PR and between 5faaf4b and 48434e6.

📒 Files selected for processing (3)
  • src/ast/known_global.rs
  • test/bundler/bundler_minify.test.ts
  • test/bundler/transpiler/runtime-transpiler.test.ts

Comment thread test/bundler/transpiler/runtime-transpiler.test.ts
Comment thread test/cli/hot/hot.test.ts
@robobun

robobun commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author

I was working on the same bug from a separate report and ended up with the same change to src/ast/known_global.rs (branch farm/1213c93c/keep-new-on-error-constructors, not opening a PR for it since this one covers it). One thing from that branch that this PR is missing:

The runtime transpiler cache version needs a bump. src/jsc/RuntimeTranspilerCache.rs validates an entry only by cache_version == EXPECTED_VERSION, the input hash/length, and the features hash (Metadata::decode, from_file_with_cache_file_path); nothing in the key changes between Bun versions. So for any file of 4 KiB or more that a previous Bun already transpiled (in practice node_modules, which is where the return new Error(...) factory helpers live), the cached Error(...) output keeps being served after upgrading and the frame stays missing until the source file itself changes. Bumping EXPECTED_VERSION to 26 makes the fix apply to cached files too (the header comment asks for a bump on parser output changes; version 16 was the bump for adding a minification). The RuntimeTranspilerCache.rs hunk in the branch above is the three lines.

Small extra data point in case it is useful for the tests: the same fixture run as "use strict" CommonJS loses the frames exactly like ESM, and sloppy CommonJS keeps them both before and after, which pins down that the tail call is the mechanism. Also, a user constructed SyntaxError currently gets an extra at <parse> line as its first frame (separate bug), so a first-frame assertion that includes SyntaxError may be affected when that fix lands.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The serve.test.ts column assertion I flagged earlier is fixed in fdfae0b; this run found no further issues. The source change is small and the reasoning (JSC proper tail calls erasing the creating frame; new Function source origin) checks out, but since it reverses #22493's optimization and shifts reported error columns for every bun run/bun build user, a maintainer sign-off on the tradeoff would be worthwhile.

What was reviewed:

  • known_global.rs: Error-family + Function now return None; call_from_new still live via Object/Array arms.
  • Column/snapshot updates: spot-checked the −4 delta in inspect-error, hot.test.ts, serve.test.ts, test-test; node:util.inspect snapshot correctly keeps col 19 (.stack formatter reports constructor name, not new).
  • normalizeError regex rewrite: verified it matches the new at require (51:24) debug-frame shape without stripping real [dir]/...:N:N frames.
Extended reasoning...

Overview

One source file changed (src/ast/known_global.rs): the 8 native Error constructors and Function are moved from the "strip new" arm to a None-returning arm, matching the existing RegExp treatment. The other 12 files are test updates: three add new coverage (stack.test.ts, bundler_minify.test.ts, runtime-transpiler.test.ts), the rest are mechanical column/caret snapshot shifts (−4) that follow from new no longer being stripped, plus the serve.test.ts fix from my earlier comment and a rewrite of the debug-frame filter in inspect-error.test.js.

Security risks

None. This is a minifier correctness fix; no untrusted input handling, no auth/crypto/permissions.

Level of scrutiny

Medium-high. The Rust change is trivially safe (returns None = keep the AST node as-is), but it runs on every module the runtime transpiler touches and changes bun build --minify output size. It also reverses an optimization introduced in #22493 — the PR makes a strong case (semantics-preserving is non-negotiable; esbuild agrees; ~360 bytes on a 222KB bundle), but a maintainer should confirm the tradeoff rather than have it auto-approved.

Other factors

  • All prior review feedback resolved: comment-cop's long-comment complaint (trimmed in 48434e6), CodeRabbit's path suggestion (declined with justification, withdrawn), and my serve.test.ts CI-failure finding (fixed in fdfae0b).
  • call_from_new is not dead code — still used by the Object and Array arms.
  • New tests cover all 8 error constructors, the inlined-const shape, arrow bodies, bundler output captures, runtime stack frames, and the new Function import() origin. Coverage looks complete for the bug class.
  • I did not independently re-derive the bundler_npm.test.ts sourcemap coordinate/filesize updates or the err-stack-fixture.js originalColumn 18→22 shift; those are exact-match assertions CI will validate.

@robobun

robobun commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author

Heads up on an interaction with #37396, which makes the .stack string report new X(...) frames at the new keyword (the four-column difference this description mentions). The two changes are independent, but once both are in, frames at new Error(...) in transpiled code move to the new column in the .stack string as well, so whichever PR lands second needs two of this PR's expectations refreshed: err.line and err.column are set in test/js/bun/test/stack.test.ts becomes column: 13 / originalColumn: 13, and the node:util.inspect case in test/js/bun/util/inspect-error.test.js moves from :19 to :15. The code-frame / Bun.inspect expectations updated here are unaffected, since that path already reported the new keyword. Both PRs also append a test at the end of stack.test.ts, so expect a small textual conflict there.

…the transpiler cache version

The remaining call rewrites (`new Object(x)` and `new Array(x)` with a
non-literal or length argument) have the same problem as the error
constructors: `return Array(...lengths)` is a varargs tail call, so the
RangeError thrown for an invalid length has no frame for the function that
asked for the array. Only the literal foldings remain, which makes the rule
a single sentence.

Cached transpiler output written by earlier versions still holds the call
form, so the runtime transpiler cache version moves to 26.
Comment thread src/ast/known_global.rs
Comment thread src/jsc/RuntimeTranspilerCache.rs
@robobun robobun changed the title transpiler: keep new on Error and Function constructors instead of minifying them into tail calls transpiler: stop minifying new Error() (and other known-global constructs) into calls that JSC tail-calls Aug 11, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this PR after the serve.test.ts fix (fdfae0b) and cache-version bump (967a6e8) landed and didn't find further bugs. Because it changes runtime-transpiler output for every module and shifts the reported column of every new Error(...) code frame, a human look would still be worthwhile before merge.

What was reviewed:

  • minify_global_constructor: every former call_from_new site now returns None; the literal-folding arms for Object/Array are unchanged, and the deleted helper has no remaining callers.
  • Column/snapshot updates: spot-checked the −4 shift in hot.test.ts, serve.test.ts, test-test.test.ts, test-error-code-done-callback.test.ts and inspect-error.test.js against their fixtures.
  • New tests in stack.test.ts / bundler_minify.test.ts / runtime-transpiler.test.ts cover the Error family, the new Array(n) throw path, and new Function source-origin resolution respectively.
  • The two open comment-cop flags (known_global.rs:95, RuntimeTranspilerCache.rs:56) look like false positives — both 3-line comments match the established style of their neighbors (the RegExp note and the Version 22–25 log entries).
Extended reasoning...

Overview

The PR stops KnownGlobal::minify_global_constructor from rewriting new X(...) into a plain call X(...) for the eight native error constructors, Function, and the non-foldable Object/Array cases. The call_from_new helper is deleted and every site that used it now returns None, leaving the new expression as written. The literal-folding paths (new Object(){}, new Array(1,2,3)[1,2,3], small new Array(n)[,,,]) are untouched. EXPECTED_VERSION in RuntimeTranspilerCache.rs is bumped 25→26 so cached .pile entries holding the old call form are invalidated. Twelve test files carry the resulting −4 column shift in code-frame snapshots plus new coverage for the tail-call mechanism.

Security risks

None identified. The change removes a byte-saving rewrite; output becomes closer to the input source. No parsing of untrusted data, no new syscalls, no auth/permission surface.

Level of scrutiny

High. minify_global_constructor runs on every new Ident(...) expression in every file the runtime transpiler and bun build --minify process, and the reported error column is user-visible in stack traces, code frames, the dev error page, and sourcemap-consuming tooling. The Rust diff itself is small and strictly de-optimizing (fewer transforms applied), which limits the blast radius, but the breadth of affected snapshot assertions across the suite and the noted interaction with #37396 make this worth a maintainer's eyes.

Other factors

  • My earlier inline finding (serve.test.ts:1954 column) was addressed in fdfae0b and the author swept the rest of the suite for similar assertions.
  • The farm-branch suggestion to bump the transpiler cache version was applied in 967a6e8; the new Version 26 comment follows the same multi-line format as Versions 22–25, so the reopened comment-cop flag on it appears to be a heuristic false positive. Same for the 3-line rationale in known_global.rs, which mirrors the existing RegExp note. These are still open on the thread, so a human should confirm and resolve.
  • robobun flagged a textual conflict and two expectation refreshes needed against #37396 depending on merge order — informational, not a defect in this PR.
  • Test coverage is thorough: the new stack.test.ts case exercises all eight error constructors plus the inlined-const and arrow shapes; bundler_minify.test.ts asserts both the printed output and the runtime stack of the minified bundle; runtime-transpiler.test.ts pins the new Function source-origin behavior. The bundler_npm.test.ts filesize/mapping updates quantify the byte cost (+388 on a 222 KB React SSR bundle).

@robobun

robobun commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, agreed on the interaction with #37396: the two are independent, and whichever lands second refreshes those two .stack-string expectations (stack.test.ts err.line and err.column are set and the node:util.inspect case in inspect-error.test.js) plus the trivial append conflict at the end of stack.test.ts. If #37396 merges first I will rebase this one and update them here.

Comment thread src/ast/known_global.rs
With a spread in the argument list the runtime argument count is unknown;
when the spread is empty, new Array(5, ...rest) is new Array(5), a length,
while [5, ...rest] is a one element array.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this PR and didn't find any bugs — both earlier findings (the serve.test.ts column and the new Array(x, ...rest) fold) are addressed. Because this changes what the runtime transpiler emits for every module (error-frame columns move, minified output grows ~4 bytes per construct, cache version bumps), a maintainer sign-off on the size/behavior trade-off and the coordination with #37396 would be worthwhile.

What was reviewed:

  • minify_global_constructor: verified every former call_from_new site now returns None or a literal; the new spread guard on the Array arm covers both n==1 and n>1.
  • EXPECTED_VERSION bump follows the pattern for versions 22-25; the changelog line matches the parser-output change.
  • Column deltas: spot-checked the −4 shifts in inspect-error/hot/serve/test-test against the fixture indent, and the +4 originalColumn in stack.test.ts (transpiled-output position, now 4 bytes longer with new ).
  • normalizeError rewrite in inspect-error.test.js: the new regex still matches the old (:1:21) shape as well as the current (51:24) form.
Extended reasoning...

Overview

The PR stops KnownGlobal::minify_global_constructor (src/ast/known_global.rs) from ever rewriting new X(...) into a plain call X(...). The call_from_new helper is deleted; the eight error constructors, Function, RegExp, and the non-literal-foldable Object/Array cases now return None (keep the construct as written). A spread-argument guard is added to the Array arm so new Array(5, ...rest) is never folded into [5, ...rest]. RuntimeTranspilerCache::EXPECTED_VERSION bumps 25→26 so cached call-form output is invalidated. Thirteen test files are updated: three gain new tests (stack.test.ts, bundler_minify.test.ts, runtime-transpiler.test.ts), the rest are mechanical column-snapshot shifts (−4) plus the React SSR bundle size (222000→222388) and sourcemap column offsets in bundler_npm.test.ts.

Security risks

None. The change is purely subtractive on the minifier side (it stops performing a rewrite) and a version-constant bump on the cache side. No new parsing of untrusted input, no new I/O, no allocation-size arithmetic.

Level of scrutiny

High. minify_global_constructor runs during the visit pass of every module the runtime transpiler processes (bun run, bun test) and every --minify build. The change alters user-visible output: error-frame columns move by −4 across the board (Bun.inspect, code frames, dev error page), and minified bundle sizes grow slightly. While the Rust diff itself is small and easy to verify as correct-by-subtraction, the behavioral surface area and the explicit trade-off (4 bytes per construct vs. correct stack frames) is the kind of decision a maintainer should ratify.

Other factors

  • Both prior automated findings on this PR were fixed in fdfae0b (serve.test.ts column 19→15) and 5d87dd8 (spread guard before the match n dispatch).
  • Test coverage is thorough: every rewritten constructor has a capture assertion pinning the new form, plus runtime tests that execute the bundled output and check the stack frames. The author verified USE_SYSTEM_BUN=1 fails and the debug build passes across the touched directories.
  • There is a documented interaction with open PR #37396 (which moves the .stack string column to new as well); whichever lands second refreshes two expectations in stack.test.ts and inspect-error.test.js. This is coordination a human should be aware of, not a defect in this PR.
  • The normalizeError helper rewrite in inspect-error.test.js is a functional change (regex instead of substring match) needed for the test to pass under debug builds; I checked the new pattern still matches the old (:1:21) internal-frame format via the :? before \d+.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant