bun_core: make bun_core::strings a direct alias of string::immutable - #33035
Conversation
…ing::immutable bun_core::strings was a pub mod that glob-merged the crate root (which carried scalar fallbacks from strings_impl) with string::immutable (the highway/simdutf namespace), then hand-disambiguated ~30 colliding names. string::immutable in turn re-exported pieces of crate::strings back, forming a mutual re-export fixpoint, and the same module was reachable as bun_core::strings, bun_core::immutable, bun_core::string_immutable, and bun_core::string::strings. bun_core::strings is now literally 'pub use crate::string::immutable as strings', the other three aliases are gone, immutable's back-edges go through crate::strings_impl (the tier-0 module that actually defines the transcoding primitives), and every scalar twin in strings_impl that shadowed a highway/simdutf implementation is deleted: contains, includes, index_of_char, starts_with, ends_with, eql, eql_long, eql_comptime_ignore_len, index_of_any, index_of_any_t, last_index_of_char, last_index_of_char_t, first_non_ascii16, a duplicate ares_inet_pton extern + is_ipv6_address, a duplicate Encoding enum, the CodepointIterator stub fmt.rs used, and bun_alloc::copy_lowercase_if_needed (shadowed by immutable's). index_of_any / index_of_any_t / index_of_any16 are unified to one signature: non-'static chars and Option<usize> (the alias had been serving a scalar Option<usize> form to half the callers and a highway Option<u32> form to the other half). strings_impl::first_non_ascii is renamed first_non_ascii_usize so strings::first_non_ascii (Option<u32>) is the only spelling of that name. Callers of the deleted root-glob names move to bun_core::strings::*.
…wrapper strings::index_of_any now returns Option<usize>, so the 'as usize' casts on its result and the resolver's local 1-line forwarding wrapper are redundant.
|
Updated 5:13 AM PT - Jun 29th, 2026
❌ @robobun, your commit dae7fce has 1 failures in
🧪 To try this PR locally: bunx bun-pr 33035That installs a local version of the PR into your bun-33035 --bun |
WalkthroughConsolidates Changesstrings namespace consolidation
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@src/jsc/ConsoleObject.rs`:
- Around line 3168-3171: The UTF-16 key-path branch in
ConsoleObject::write_object_property formatting is writing embedded quote
characters back out unescaped, unlike the Latin-1 path. Update the loop that
uses strings::index_of_any16 and writes through writer.write_16_bit /
writer.write_all so that any embedded QUOTE_U16 is emitted with the same
JSON-style escaping as the Latin-1 branch, keeping quoted property names
consistent for non-Latin1 keys.
In `@src/jsc/webcore_types.rs`:
- Around line 831-838: The trailing slash handling in S3 path normalization is
incorrect in the path trimming logic inside the S3 path routine in
webcore_types.rs. Update the branch that checks
bun_core::strings::ends_with(path_name, b"/") so it removes the final byte like
the backslash branch already does, and keep the normalization behavior
consistent in S3::path() for both "/" and "\\" suffixes.
In `@src/runtime/cli/create_command.rs`:
- Around line 1764-1770: Restore the second-slash lookup in
create_command::parse_template_positional so the GitHub-repo detection only
triggers when there is exactly one slash. The current repeated call to
bun_core::strings::index_of_char_usize on positional makes first_slash_index ==
last_slash_index for any slash-containing input, so update the logic to use the
last-slash check as intended and keep foo/bar/baz resolving as a template path
instead of a repo shorthand.
🪄 Autofix (Beta)
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: b1ae73e2-34cc-4c51-a35f-707c555f122f
📒 Files selected for processing (54)
src/ast/expr.rssrc/ast/lib.rssrc/bun_alloc/lib.rssrc/bun_core/Global.rssrc/bun_core/fmt.rssrc/bun_core/lib.rssrc/bun_core/string/MutableString.rssrc/bun_core/string/StringBuilder.rssrc/bun_core/string/StringJoiner.rssrc/bun_core/string/escapeRegExp.rssrc/bun_core/string/identifier.rssrc/bun_core/string/immutable.rssrc/bun_core/string/immutable/unicode.rssrc/bun_core/string/mod.rssrc/bun_core/string/wtf.rssrc/bun_core/util.rssrc/bundler/Chunk.rssrc/bundler/defines.rssrc/bundler/linker_context/postProcessJSChunk.rssrc/bundler/linker_context/writeOutputFilesToDisk.rssrc/bundler/transpiler.rssrc/crash_handler/lib.rssrc/css/generics.rssrc/css/properties/animation.rssrc/css/rules/keyframes.rssrc/http/lib.rssrc/ini/lib.rssrc/install/dependency.rssrc/install/lib.rssrc/js_printer/lib.rssrc/js_printer/renamer.rssrc/jsc/ConsoleObject.rssrc/jsc/VirtualMachine.rssrc/jsc/ipc.rssrc/jsc/webcore_types.rssrc/md/links.rssrc/paths/lib.rssrc/paths/string_paths.rssrc/resolver/fs.rssrc/resolver/resolver.rssrc/runtime/api/cron.rssrc/runtime/api/filesystem_router.rssrc/runtime/bake/FrameworkRouter.rssrc/runtime/bake/dev_server/mod.rssrc/runtime/cli/create_command.rssrc/runtime/cli/mod.rssrc/runtime/jsc_hooks.rssrc/runtime/node/fs_events.rssrc/runtime/server/ServerConfig.rssrc/runtime/socket/udp_socket.rssrc/runtime/webcore/s3/client.rssrc/shell_parser/braces.rssrc/shell_parser/parse.rssrc/url/lib.rs
💤 Files with no reviewable changes (2)
- src/runtime/bake/FrameworkRouter.rs
- src/resolver/resolver.rs
|
All three CodeRabbit findings land on lines this PR only renames; none is a regression from this change. Each one is also byte-for-byte identical in the original Zig source before the Rust rewrite (commit c8b4c36):
They look like real latent bugs worth fixing, but each is a user-visible behavior change (S3 key normalization, |
The comment justifying the resolver-local copy claimed
`bun_core::strings::unicode_draft` has no `pub use` of `BOM`, but
`string/immutable.rs` re-exports it (and this branch already calls
`bun_core::strings::BOM::detect` from `ast/lib.rs`). The canonical impl
is a strict superset of the copy: identical `detect` / `tag_name` /
`remove_and_convert_to_utf8_{and_free,without_dealloc}`, plus
`detect_and_split` and `length`. The one name divergence (`header` vs
`get_header`) is internal to the deleted impl and nothing outside it
calls either. `bun_resolver::fs_full::BOM` keeps re-exporting the name,
so no caller changes.
Also reword the `index_of_any` doc comment: not every caller indexes
with the result (many only test `is_some()`).
There was a problem hiding this comment.
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 `@src/resolver/fs.rs`:
- Line 27: The resolver’s BOM handling now depends on bun_core::strings::BOM,
but that implementation misses UTF-16LE BOM-only inputs because it returns None
for 2-byte buffers. Update the BOM logic used by src/resolver/fs.rs so BOM-only
UTF-16LE data (FF FE) is still detected and stripped/converted correctly before
relying on the shared alias. Keep the fix in the BOM parsing path itself so
callers of BOM continue to handle this edge case.
🪄 Autofix (Beta)
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: ffb32bb8-0662-4b8e-a892-feb9a7135d25
📒 Files selected for processing (2)
src/bun_core/string/immutable.rssrc/resolver/fs.rs
There was a problem hiding this comment.
I didn't find any issues beyond the pre-existing ones already discussed and resolved, but this is a 54-file refactor of the core bun.strings namespace (return-type unification, scalar→highway swaps in fmt.rs, dedup of BOM/is_ipv6_address/etc.), so it's worth a human pass.
Extended reasoning...
Overview
This PR collapses bun_core::strings from a back-compat pub mod that glob-merged the crate root's scalar fallbacks with string::immutable (and hand-disambiguated ~30 colliding names) into a direct pub use crate::string::immutable as strings; alias. It deletes the bun_core::immutable / string_immutable / string::strings alternate spellings, removes every scalar twin in strings_impl that shadowed a highway/simdutf implementation (contains, includes, index_of_char, eql_long, first_non_ascii16, is_ipv6_address, the duplicate Encoding enum, the CodepointIterator stub, etc.), unifies index_of_any{,_t,16} to return Option<usize> with non-'static needles, replaces bun_resolver's near-verbatim BOM duplicate with a re-export, and migrates ~50 call sites across ast/bundler/css/http/install/jsc/md/paths/resolver/runtime/shell/url. Net: +310/−642.
Security risks
None introduced. The change is a namespace consolidation; the only non-rename code paths affected are string-search/transcode primitives. No auth, crypto, permissions, deserialization, or network input-handling logic changes. The deleted duplicate is_ipv6_address / ares_inet_pton extern resolves to the identical canonical impl already in immutable.rs.
Level of scrutiny
Moderate-to-high. Most of the diff is mechanical path renames (bun_core::X → bun_core::strings::X, bun_core::immutable → bun_core::strings) and dropped now-identity as usize casts. But several call sites in src/bun_core/fmt.rs (JS syntax-highlighter redaction arms, PowerShell escaping, path-separator normalization, format_latin1) genuinely switch implementation from a scalar strings_impl loop to the highway-backed immutable one, and index_of_any* changed both signature and edge-case behavior (empty needle now returns None instead of unreachable!(); no longer panics on >4 GB input via the removed u32::try_from(..).unwrap()). The fmt.rs CodepointIterator swap (scalar stub → canonical immutable one) changes cursor.i from usize to a type requiring as usize, which the diff handles. These are exactly the spots a human reviewer should eyeball — the author already ran the targeted regression tests (highlighter.test.ts, powershell-escape.test.ts, redacted-config-logs.test.ts) covering them.
Other factors
- The bug-hunting system found nothing on this revision.
- All four prior inline threads (three CodeRabbit, one mine) were correctly identified as pre-existing behavior carried from the Zig source and resolved as out-of-scope; the author's reasoning for not taking drive-by fixes in a behavior-preserving refactor is sound.
- CI shows 2 failures (
terminal.test.tson macOS x64,test-net-connect-memleak.json Linux x64) that appear unrelated to string-search code paths and look like known-flaky tests, but worth confirming. cargo check --workspacepasses on all 10 target triples per the description; the PR is well-verified.
The refactor is clean and the description is unusually thorough, but 54 files touching the string layer that the resolver, bundler, parser, shell, and JSC formatters all sit on top of is outside the bar for bot approval.
|
The red This diff touches no GC, refcount, event-loop, or |
There was a problem hiding this comment.
No bugs found, but this is a 54-file refactor of the core bun_core::strings namespace that swaps scalar scanners for highway-backed ones and changes the index_of_any return type — worth a human sign-off given how widely these primitives are used.
Extended reasoning...
Overview
This PR collapses bun_core::strings from a hand-disambiguated glob-merge of crate-root scalar fallbacks + string::immutable into a direct pub use crate::string::immutable as strings; alias. It deletes ~15 scalar twin functions from strings_impl (the ones shadowed by highway/simdutf implementations in immutable), unifies the index_of_any{,_t,16} return type from Option<u32> to Option<usize> (dropping the 'static bound and the unreachable!() on empty needles), renames strings_impl::first_non_ascii → first_non_ascii_usize, replaces the resolver's duplicate BOM enum with a re-export of the canonical one, and migrates ~40 downstream call sites to the new paths. Net +310/-642 across 54 files; bun_core/lib.rs alone loses 175 lines.
Security risks
None identified. The change is a namespace consolidation with no auth, crypto, permission, or input-validation surface. The only behavioral deltas are panic removals (index_of_any(slice, b"") now returns None instead of unreachable!(); the u32::try_from(..).unwrap() on >4 GB inputs is gone), neither of which is reachable from JS per the description.
Level of scrutiny
Moderate-to-high. While the vast majority of the diff is mechanical path renames the compiler verifies, the substantive parts are not:
- Several
fmt.rscall sites silently switch from a scalarstrings_implloop to the highway-backedimmutableone (syntax highlighter redaction, PowerShell escaping, path-sep normalization). The author ran the regression tests covering exactly those (highlighter.test.ts,powershell-escape.test.ts,redacted-config-logs.test.ts). fmt.rs'sFormatValidIdentifierswaps the localCodepointIteratorstub for the canonicalstrings::CodepointIterator(whosecursor.iisu32, hence the newas usizecasts).- The
index_of_anysignature change ripples throughMutableString,escapeRegExp,ConsoleObject,FrameworkRouter,md/links, etc., dropping now-identityas usizecasts. - The resolver
BOMdedup was verified line-by-line in the thread to be a strict superset with identicaldetect()semantics.
These are core string-scanning primitives reached from the bundler, resolver, JS printer, shell, CSS, S3, and console — broad enough blast radius that a maintainer should confirm the scalar→highway equivalences are sound.
Other factors
- All four CodeRabbit findings and my own prior inline note were established as pre-existing (verified against
mainand the original Zig) and resolved; the author deliberately kept this PR behavior-neutral and deferred those to follow-ups. - Verification is unusually thorough:
cargo checkon all 10 CI triples, clippy clean,bun bdbuilds, and a targeted test slice over every implementation-switching call site passes. The red musl-x64 lane is a documented repo-wide flake (#33044) unrelated to this diff. - No new tests are added; the description argues (reasonably) that a regression test would pass identically on
main, and the compile checks across 10 triples are the proof for the rename portion. - CodeRabbit suggested
aliianddylan-conwayas reviewers.
What
bun_core::stringswas a back-compatpub mod strings { ... }inbun_core/lib.rsthat glob-merged the crate root (which carried scalar fallbacks from an internalstrings_implmodule) withcrate::string::immutable(the highway/simdutf-backed canonicalbun.stringsnamespace), then hand-disambiguated ~30 colliding names.string::immutablein turn re-exported pieces ofcrate::stringsback into itself, forming a mutual re-export fixpoint, and the same module was reachable asbun_core::strings,bun_core::immutable,bun_core::string_immutable, andbun_core::string::strings.The practical cost was two implementations per name:
bun_core::containsresolved to a bstr scalar whilebun_core::strings::containsresolved to the highway one,bun_core::index_of_charreturnedOption<usize>whilebun_core::strings::index_of_charreturnedOption<u32>, and so on.Change
bun_core::stringsis now literallypub use crate::string::immutable as strings;. Thebun_core::immutable,bun_core::string_immutable, andbun_core::string::stringsaliases are deleted and their callers migrated.string::immutable's back-references tocrate::stringsnow go throughcrate::strings_impl(the tier-0 module that actually defines the transcoding primitives), so the re-export cycle is gone.strings_implthat shadowed a highway/simdutf implementation inimmutableis deleted:contains,includes,index_of_char,starts_with,ends_with,eql,eql_long,eql_comptime_ignore_len,index_of_any,index_of_any_t,last_index_of_char,last_index_of_char_t,first_non_ascii16, a duplicateares_inet_ptonextern plusis_ipv6_address, a duplicateEncodingenum, theCodepointIteratorstubfmt.rsused, andbun_alloc::copy_lowercase_if_needed(shadowed byimmutable's). Their callers move tobun_core::strings::*.bun_resolver's local copy ofBOM(a near-verbatim duplicate ofbun_core::strings::BOM, justified by a comment claimingBOMwas not exported, which was false) becomespub use bun_core::strings::BOM;. The canonical impl is a strict superset and thebun_resolver::fs_full::BOMre-export is unchanged.index_of_any/index_of_any_t/index_of_any16had two live signatures (the alias served a scalarOption<usize>form to half the callers and a highwayOption<u32>form to the other half). They are unified to one: non-'staticcharsandOption<usize>, backed byhighway::index_of_any_char. TheOptionalUsizetype alias this leaves unused is removed, along with theas usizecasts that became identity.strings_impl::first_non_asciiis renamedfirst_non_ascii_usizesostrings::first_non_ascii(theOption<u32>form) is the only spelling of that name, matching the existingindex_of_char/index_of_char_usizepair.After this, every public name in
strings_implis either re-exported fromimmutable(same item) or has a distinct name, sobun_core::Xandbun_core::strings::Xcan never refer to two different functions.src/bun_core/lib.rsalone loses 175 lines; the whole diff is +310/-642 across 54 files (most of the caller churn is mechanical path renames).Verification
cargo check --workspacepasses on all 10 CI target triples (bun run rust:check-all: 10 ok, 0 failed).cargo clippy --workspace --no-depsreports nothing.cargo check --workspace --all-targetshas exactly the same pre-existingcfg(test)errors asmain(verified by stashing the diff and re-running); this change adds none.bun bdbuilds.strings_implloop to the highway one) are concentrated insrc/bun_core/fmt.rs: the JS syntax highlighter's secret-redaction arms, PowerShell argument escaping, and path-separator normalization. The tests that drive those directly pass against the debug build:test/js/bun/util/highlighter.test.ts(which carries the regression tests for three prior out-of-bounds bugs in exactly that boundary arithmetic),test/internal/highlighter.test.ts,test/internal/powershell-escape.test.ts, andtest/cli/install/redacted-config-logs.test.ts.bundler_naming,bundler_string, globmatch/scan,css, shellbrace/lex/parse,md-spec,gfm-compat,inspect,escapeRegExp,escapeHTML, and thetest/regression/issue/23275.test.tsBOM test. (Note: theescapeRegExp/escapeHTML/MutableStringcallers did not change implementation; they were already on the highwayimmutable::index_of_anypath onmain, and the only diff there is droppingas usizecasts the return-type unification made identity.)The only intentional behavioral deltas are removals of panics that no caller depends on:
index_of_any(slice, b"")now returnsNoneinstead of hittingunreachable!(), andindex_of_any/index_of_any16no longer panic on inputs larger than 4 GB (they returnedu32indices viatry_from(..).unwrap()).Why there is no new test file
This change is behavior-preserving by construction, and I could not find a JavaScript-observable input on which
mainand this branch differ:index_of_anycaller passes a compile-time, non-empty character set, and a JSC string tops out at 2^31 code units, below the 4 GBu32overflow the old code asserted on.A regression test for this PR would therefore pass identically with and without the
src/changes, which makes it worthless as a guard. The compile checks and the existing suites listed under Verification are the proof this change admits.