diff --git a/scripts/build/cargo-config.ts b/scripts/build/cargo-config.ts index 2d42608e7697..bc8f109145cb 100644 --- a/scripts/build/cargo-config.ts +++ b/scripts/build/cargo-config.ts @@ -60,9 +60,10 @@ function linkerFor(triple: string, cfg: Config): string { * Write `.cargo/config.toml` next to the workspace `Cargo.toml` (repo root). * Returns the absolute path written. * - * Windows-msvc targets are omitted: the MSVC linker isn't a clang driver and - * doesn't take `-fuse-ld=lld`; that path is handled entirely via env in - * `rust.ts` (`CARGO_TARGET_..._LINKER = cfg.msvcLinker`). + * Windows-msvc targets get a rustflags-only section (no `linker =` line): + * the MSVC linker isn't a clang driver and doesn't take `-fuse-ld=lld`; + * that path is handled entirely via env in `rust.ts` + * (`CARGO_TARGET_..._LINKER = cfg.msvcLinker`). */ export function generateCargoConfig(cfg: Config): string { const outPath = resolve(cfg.cwd, ".cargo", "config.toml"); @@ -79,10 +80,20 @@ export function generateCargoConfig(cfg: Config): string { "# file is correct on whatever machine ran configure.", ]; + // `-Zpolonius=next` everywhere: workspace code relies on the polonius + // borrow checker (see the matching push in rust.ts), so every rustc + // invocation that type-checks workspace crates needs it or borrowck + // fails. Windows-msvc triples get a rustflags-only section (their linker + // is env-only, see the doc comment above) so `cargo check --target + // *-windows-msvc` / `rust:check-all` work. + const polonius = `"-Z", "polonius=next"`; for (const triple of allRustTargets) { - if (tripleOs(triple) === "windows") continue; lines.push(""); lines.push(`[target.${triple}]${triple === host ? " # host" : ""}`); + if (tripleOs(triple) === "windows") { + lines.push(`rustflags = [${polonius}]`); + continue; + } lines.push(`linker = ${JSON.stringify(linkerFor(triple, cfg))}`); // -Qunused-arguments: rustc passes link args that don't apply to every // artifact kind (e.g. `-no-pie` when it links a target cdylib; none @@ -95,7 +106,7 @@ export function generateCargoConfig(cfg: Config): string { // `cargo build`/`cargo check`, rust-analyzer); real linker errors still // fail the link. lines.push( - `rustflags = ["-C", "link-arg=-fuse-ld=lld", "-C", "link-arg=-Qunused-arguments", "-A", "linker_messages"]`, + `rustflags = ["-C", "link-arg=-fuse-ld=lld", "-C", "link-arg=-Qunused-arguments", "-A", "linker_messages", ${polonius}]`, ); } lines.push(""); diff --git a/scripts/build/rust.ts b/scripts/build/rust.ts index 4e3a41ae477f..5535d914580b 100644 --- a/scripts/build/rust.ts +++ b/scripts/build/rust.ts @@ -410,6 +410,15 @@ export function cargoBuildInvocation(cfg: Config): CargoInvocation { if (!cfg.ci) { rustflags.push("-Zthreads=8"); } + // Polonius alpha borrow checker: accepts NLL "problem case 3" (a borrow + // returned/escaping on one path no longer blocks the other paths), which + // workspace code now relies on — e.g. `src/collections/linear_fifo.rs` + // no longer compiles under the stock checker. Nightly-only; the pinned + // toolchain is nightly. Must stay in sync with the generated + // `.cargo/config.toml` (cargo-config.ts) so plain `cargo check`, + // rust-analyzer, and `rust:check-all` accept the same code the ninja + // build does. + rustflags.push("-Zpolonius=next"); // rustc does not emit `.llvm_addrsig` by default on *any* target (verified // empirically — Linux-gnu, musl, darwin, msvc all missing it). lld's // `--icf=safe` (flags.ts:960) and lld-link's `/OPT:SAFEICF` (flags.ts:778) diff --git a/src/bun_core/fmt.rs b/src/bun_core/fmt.rs index 6a0a57b27e95..82cd9cc27da5 100644 --- a/src/bun_core/fmt.rs +++ b/src/bun_core/fmt.rs @@ -2337,29 +2337,24 @@ pub fn format_ip<'a>( write!(cursor, "{}", address).map_err(|_| crate::CrateError::NoSpaceLeft)?; let written = cursor.position() as usize; - // Reshaped for borrowck — compute (start, end) offsets against `into` - // instead of iteratively reborrowing a `result` slice, so the final - // returned `&mut into[start..end]` carries the caller's `'a` lifetime - // cleanly. - let mut start = 0usize; - let mut end = written; + let mut result = &mut into[..written]; // Strip `:` - if let Some(colon) = strings::last_index_of_char(&into[start..end], b':') { - end = start + colon; + if let Some(colon) = strings::last_index_of_char(result, b':') { + result = &mut result[..colon]; } // Strip brackets - if start < end && into[start] == b'[' && into[end - 1] == b']' { - start += 1; - end -= 1; + if result.first() == Some(&b'[') && result.last() == Some(&b']') { + let len = result.len(); + result = &mut result[1..len - 1]; } // Strip `%` — Node formats addresses via uv_inet_ntop on the bare // in6_addr and never includes the zone identifier; the scope is exposed // separately (e.g. `scopeid` in os.networkInterfaces()). - if let Some(percent) = strings::index_of_char_usize(&into[start..end], b'%') { - end = start + percent; + if let Some(percent) = strings::index_of_char_usize(result, b'%') { + result = &mut result[..percent]; } - Ok(&mut into[start..end]) + Ok(result) } // ─────────────────────────────────────────────────────────────────────────── diff --git a/src/bundler/Chunk.rs b/src/bundler/Chunk.rs index 1c029dfe9890..77dd4086f205 100644 --- a/src/bundler/Chunk.rs +++ b/src/bundler/Chunk.rs @@ -308,27 +308,20 @@ impl Chunk { // Look up the CSS chunk via the JS chunk's css_chunks indices. // This correctly handles deduplicated CSS chunks that are shared // across multiple HTML entry points (see issue #23668). - // Note: reshaped for borrowck — we scan immutably for the JS chunk, copy the - // css-chunk index into a local, drop the borrow, then re-borrow mutably. let entry_point_id = self.entry_point.entry_point_id(); - let css_idx: Option = 'find: { - for other in chunks.iter() { - if let Content::Javascript(js) = &other.content { - if other.entry_point.is_entry_point() - && other.entry_point.entry_point_id() == entry_point_id - { - let css_chunk_indices = &js.css_chunks[..]; - if !css_chunk_indices.is_empty() { - break 'find Some(css_chunk_indices[0] as usize); - } - break 'find None; + for other in chunks.iter() { + if let Content::Javascript(js) = &other.content { + if other.entry_point.is_entry_point() + && other.entry_point.entry_point_id() == entry_point_id + { + let css_chunk_indices = &js.css_chunks[..]; + if !css_chunk_indices.is_empty() { + let idx = css_chunk_indices[0] as usize; + return Some(&mut chunks[idx]); } + break; } } - None - }; - if let Some(idx) = css_idx { - return Some(&mut chunks[idx]); } // Fallback: match by entry_point_id for cases without a JS chunk. for other in chunks.iter_mut() { diff --git a/src/bundler/transpiler.rs b/src/bundler/transpiler.rs index 536e3ea26598..3e44e4a6f80c 100644 --- a/src/bundler/transpiler.rs +++ b/src/bundler/transpiler.rs @@ -471,28 +471,18 @@ impl<'a> Transpiler<'a> { let mut cache_bust_buf = bun_paths::PathBuffer::uninit(); // Bust directory cache and try again - // reshaped for borrowck — a single labelled block would - // return a slice that aliases either `entry_point` (via - // `dirname`) or `cache_bust_buf`. Rust can't unify the two - // disjoint mutable borrows of `cache_bust_buf` across `break`, - // so compute `busted` directly instead. - let busted: bool = 'name: { + let buster_name = 'name: { if bun_paths::is_absolute(entry_point) { let dir = bun_paths::resolve_path::dirname::( entry_point, ); if !dir.is_empty() { // Normalized with trailing slash - let buster_name = bun_paths::string_paths::normalize_slashes_only( + break 'name bun_paths::string_paths::normalize_slashes_only( &mut cache_bust_buf[..], dir, bun_paths::SEP, ); - break 'name self.resolver.bust_dir_cache( - bun_paths::string_paths::without_trailing_slash_windows_path( - buster_name, - ), - ); } } @@ -500,17 +490,16 @@ impl<'a> Transpiler<'a> { let parts: [&[u8]; 2] = [entry_point, b".."]; let top_level_dir = self.fs().top_level_dir; - let buster_name = bun_paths::resolve_path::join_abs_string_buf_z::< - bun_paths::platform::Auto, - >( - top_level_dir, &mut cache_bust_buf[..], &parts - ); - self.resolver.bust_dir_cache( - bun_paths::string_paths::without_trailing_slash_windows_path( - buster_name.as_bytes(), - ), + bun_paths::resolve_path::join_abs_string_buf_z::( + top_level_dir, + &mut cache_bust_buf[..], + &parts, ) + .as_bytes() }; + let busted = self.resolver.bust_dir_cache( + bun_paths::string_paths::without_trailing_slash_windows_path(buster_name), + ); // Only re-query if we previously had something cached. if busted { diff --git a/src/collections/array_hash_map.rs b/src/collections/array_hash_map.rs index fde76b1d563b..6f7afe4495f4 100644 --- a/src/collections/array_hash_map.rs +++ b/src/collections/array_hash_map.rs @@ -1199,15 +1199,9 @@ impl, A: MapAllocator> ArrayHashMap Result, AllocError> { let gop = self.get_or_put(key)?; if !gop.found_existing { - // SAFETY: re-borrow at same index — `gop` borrows `self` so go - // through the slot it already points at. *gop.value_ptr = value; } - // Can't return `gop` while it borrows in the branch above without - // NLL gymnastics; recompute via index. - let i = gop.index; - let found = gop.found_existing; - Ok(self.gop_at(i, found)) + Ok(gop) } } diff --git a/src/collections/linear_fifo.rs b/src/collections/linear_fifo.rs index 4e639ca3c74b..1c763d8f5297 100644 --- a/src/collections/linear_fifo.rs +++ b/src/collections/linear_fifo.rs @@ -448,12 +448,11 @@ impl> LinearFifo { self.ensure_unused_capacity(size)?; // try to avoid realigning buffer - // reshaped for borrowck — check len, drop borrow, maybe - // realign, then take the final borrow. - if self.writable_slice(0).len() < size { + let mut slice = self.writable_slice(0); + if slice.len() < size { self.realign(); + slice = self.writable_slice(0); } - let slice = self.writable_slice(0); debug_assert!(slice.len() >= size); Ok(&mut slice[..size]) } diff --git a/src/dotenv/env_loader.rs b/src/dotenv/env_loader.rs index 0ae6c8e6a18d..46b55247eeba 100644 --- a/src/dotenv/env_loader.rs +++ b/src/dotenv/env_loader.rs @@ -195,20 +195,16 @@ impl Loader { buf: &'b mut PathBuffer, ) -> Option<&'b ZStr> { // Check NODE or npm_node_execpath env var, but only use it if the file actually exists. - // NLL workaround: compute the length in an inner scope so the borrow of `buf` for the - // executable check ends before we either return a fresh borrow or fall through to `which`. - let env_len = self + if let Some(node) = self .get(b"NODE") .or_else(|| self.get(b"npm_node_execpath")) .filter(|n| !n.is_empty() && n.len() < MAX_PATH_BYTES) - .map(|node| { - buf[..node.len()].copy_from_slice(node); - buf[node.len()] = 0; - node.len() - }); - if let Some(len) = env_len { - if bun_sys::is_executable_file_path(ZStr::from_buf(&buf[..], len)) { - return Some(ZStr::from_buf(&buf[..], len)); + { + buf[..node.len()].copy_from_slice(node); + buf[node.len()] = 0; + let node_path = ZStr::from_buf(&buf[..], node.len()); + if bun_sys::is_executable_file_path(node_path) { + return Some(node_path); } } @@ -1105,17 +1101,13 @@ impl<'a> Parser<'a> { if end >= self.src.len() { return Ok(&self.src[self.src.len()..]); } - // reshaped for borrowck — `parse_quoted` returns a borrow of - // `self.value_buffer`; capture only its length, then re-borrow the buffer - // after the match so the unquoted fallthrough can re-borrow `self`. - let quoted_len: Option = match self.src[end] { - b'`' => self.parse_quoted::()?.map(|v| v.len()), - b'"' => self.parse_quoted::()?.map(|v| v.len()), - b'\'' => self.parse_quoted::()?.map(|v| v.len()), + let quoted = match self.src[end] { + b'`' => self.parse_quoted::()?, + b'"' => self.parse_quoted::()?, + b'\'' => self.parse_quoted::()?, _ => None, }; - if let Some(len) = quoted_len { - let value = &self.value_buffer[..len]; + if let Some(value) = quoted { return Ok(if IS_PROCESS { value } else { diff --git a/src/exe_format/elf.rs b/src/exe_format/elf.rs index 9d231ec56e0a..6272cb245f1d 100644 --- a/src/exe_format/elf.rs +++ b/src/exe_format/elf.rs @@ -167,17 +167,14 @@ impl ElfFile { if strtab_end > self.data.len() as u64 { return; } - // reshaped for borrowck — copy strtab bounds out so we can - // re-borrow self.data mutably below. - let strtab_off = usize::try_from(strtab_shdr.sh_offset).expect("int cast"); - let strtab_len = usize::try_from(strtab_shdr.sh_size).expect("int cast"); + let strtab = &self.data[usize::try_from(strtab_shdr.sh_offset).expect("int cast")..] + [..usize::try_from(strtab_shdr.sh_size).expect("int cast")]; for i in 0..shnum as usize { let shdr = self.read_shdr(ehdr.e_shoff, u16::try_from(i).expect("int cast")); - if shdr.sh_name as usize >= strtab_len { + if shdr.sh_name as usize >= strtab.len() { continue; } - let strtab = &self.data[strtab_off..][..strtab_len]; let name = slice_to_nul(&strtab[shdr.sh_name as usize..]); if name != b".interp" { continue; diff --git a/src/install/PackageManager/WorkspacePackageJSONCache.rs b/src/install/PackageManager/WorkspacePackageJSONCache.rs index 41287a66c09c..a4ad73d25cac 100644 --- a/src/install/PackageManager/WorkspacePackageJSONCache.rs +++ b/src/install/PackageManager/WorkspacePackageJSONCache.rs @@ -152,12 +152,8 @@ impl WorkspacePackageJSONCache { &buf[..abs_package_json_path.len()] }; - // reshaped for borrowck — we cannot hold an entry borrow across - // `self.map.remove`, so check - // membership up front and only insert into the map after a successful - // read+parse. Net map state is identical on every path. - if self.map.contains_key(path) { - return GetResult::Entry(self.map.get_mut(path).unwrap()); + if let Some(entry) = self.map.get_mut(path) { + return GetResult::Entry(entry); } // Owned NUL-terminated copy reused diff --git a/src/install/PackageManager/patchPackage.rs b/src/install/PackageManager/patchPackage.rs index a416be8d9293..13c8fb2aba16 100644 --- a/src/install/PackageManager/patchPackage.rs +++ b/src/install/PackageManager/patchPackage.rs @@ -1242,16 +1242,10 @@ fn overwrite_package_in_node_modules_folder( type NodeModulesIterator<'a> = tree::Iterator<'a, { tree::IteratorPathStyle::NodeModules }>; -// reshaped for borrowck — `tree::Iterator::next` returns an -// `IteratorNext<'_>` borrowing the iterator's internal `path_buf`, so we -// cannot return it from inside a `while let` (borrowck rejects the next -// iteration's reborrow even though it's unreachable). Callers only need -// `relative_path`, so copy it out into an owned `Vec`. - -fn node_modules_folder_for_dependency_ids( - iterator: &mut NodeModulesIterator<'_>, +fn node_modules_folder_for_dependency_ids<'a>( + iterator: &'a mut NodeModulesIterator<'_>, ids: &[IdPair], -) -> Option> { +) -> Option<&'a ZStr> { loop { let node_modules = iterator.next(None)?; let mut found = false; @@ -1262,21 +1256,21 @@ fn node_modules_folder_for_dependency_ids( } } if found { - return Some(node_modules.relative_path.as_bytes().to_vec()); + return Some(node_modules.relative_path); } } } -fn node_modules_folder_for_dependency_id( - iterator: &mut NodeModulesIterator<'_>, +fn node_modules_folder_for_dependency_id<'a>( + iterator: &'a mut NodeModulesIterator<'_>, dependency_id: DependencyID, -) -> Option> { +) -> Option<&'a ZStr> { loop { let node_modules = iterator.next(None)?; if !node_modules.dependencies.contains(&dependency_id) { continue; } - return Some(node_modules.relative_path.as_bytes().to_vec()); + return Some(node_modules.relative_path); } } @@ -1350,7 +1344,7 @@ fn pkg_info_for_name_and_version( Global::crash(); } }; - return (pkg_id, folder); + return (pkg_id, folder.as_bytes().to_vec()); } // we found multiple dependents of the supplied pkg + version @@ -1368,7 +1362,7 @@ fn pkg_info_for_name_and_version( } }; - return (pkg_id, folder); + return (pkg_id, folder.as_bytes().to_vec()); } // Otherwise the user did not supply a version, just the pkg name @@ -1386,7 +1380,7 @@ fn pkg_info_for_name_and_version( Global::crash(); } }; - return (pkg_id, folder); + return (pkg_id, folder.as_bytes().to_vec()); } // Otherwise we have multiple matches @@ -1419,7 +1413,7 @@ fn pkg_info_for_name_and_version( Global::crash(); } }; - return (pkg_id, folder); + return (pkg_id, folder.as_bytes().to_vec()); } bun_core::pretty_errorln!( diff --git a/src/install/isolated_install.rs b/src/install/isolated_install.rs index 35e5ea78bcfc..de46149aee20 100644 --- a/src/install/isolated_install.rs +++ b/src/install/isolated_install.rs @@ -1478,10 +1478,8 @@ pub(crate) fn install_isolated_packages( } unreachable!(); }; - // Reshaped for borrowck — copy members to - // avoid holding a borrow into scc_stack while mutating. - let members: Vec = scc_stack[start..].to_vec(); - for &m in &members { + let members = &scc_stack[start..]; + for &m in members { on_stack[m as usize] = false; } if members.len() == 1 { @@ -1546,7 +1544,7 @@ pub(crate) fn install_isolated_packages( scc_ext.clear_retaining_capacity(); let mut member_sub: Vec = Vec::new(); let mut any_ineligible = false; - for &m in &members { + for &m in members { if entry_hashes[m as usize] == 0 { any_ineligible = true; } @@ -1608,7 +1606,7 @@ pub(crate) fn install_isolated_packages( h = 1; } let final_h: u64 = if any_ineligible { 0 } else { h }; - for &m in &members { + for &m in members { entry_hashes[m as usize] = final_h; } } diff --git a/src/install/isolated_install/Hardlinker.rs b/src/install/isolated_install/Hardlinker.rs index 4856f2de0c90..6664f40f6ebd 100644 --- a/src/install/isolated_install/Hardlinker.rs +++ b/src/install/isolated_install/Hardlinker.rs @@ -60,32 +60,14 @@ impl Hardlinker { #[cfg(windows)] { let mut cwd_buf = bun_paths::w_path_buffer_pool::get(); - // `get_fd_path_w` writes the raw `\\?\C:\...` result into - // `cwd_buf` and returns a SUB-SLICE (offset 4, or 6 for UNC) after - // stripping the long-path prefix. We can't keep that slice borrowed - // across the loop (borrowck vs `cwd_buf`), so capture both its start - // OFFSET and length, then reslice `cwd_buf[off..off+len]` per-iter. - // Slicing from 0 would yield `\\?\C:\…` with the last 4 chars of the - // real cwd dropped — wrong path for every project-relative hardlink. - let (dest_cwd_off, dest_cwd_len) = { - let dest_cwd: &[u16] = match sys::get_fd_path_w(Fd::cwd(), &mut cwd_buf[..]) { - Ok(s) => &*s, - Err(_) => { - return Ok(sys::Result::Err(sys::Error::from_code( - sys::E::ACCES, - sys::Tag::link, - ))); - } - }; - // SAFETY: `dest_cwd` is a sub-slice of `cwd_buf` by contract of - // `get_fd_path_w` (it returns `&mut out_buffer[off..]`). - // NB: capture `len`/`dest_ptr` first so NLL drops the `&mut cwd_buf` - // loan (held via `dest_cwd`) before `cwd_buf.as_ptr()` takes `&cwd_buf` - // — otherwise E0502 on x86_64-pc-windows-msvc. - let len = dest_cwd.len(); - let dest_ptr = dest_cwd.as_ptr(); - let off = unsafe { dest_ptr.offset_from(cwd_buf.as_ptr()) } as usize; - (off, len) + let dest_cwd: &[u16] = match sys::get_fd_path_w(Fd::cwd(), &mut cwd_buf[..]) { + Ok(s) => &*s, + Err(_) => { + return Ok(sys::Result::Err(sys::Error::from_code( + sys::E::ACCES, + sys::Tag::link, + ))); + } }; loop { @@ -131,10 +113,7 @@ impl Hardlinker { { &[dest_slice] } else { - &[ - &cwd_buf[dest_cwd_off..dest_cwd_off + dest_cwd_len], - dest_slice, - ] + &[dest_cwd, dest_slice] }; let joined = bun_paths::resolve_path::join_string_buf_w_same::< bun_paths::platform::Windows, diff --git a/src/jsc/ConsoleObject.rs b/src/jsc/ConsoleObject.rs index 29152e29c5cf..4804eba53213 100644 --- a/src/jsc/ConsoleObject.rs +++ b/src/jsc/ConsoleObject.rs @@ -776,14 +776,15 @@ impl<'a> TablePrinter<'a> { let value = cols_iter.value; // find or create the column for the property - let col_idx: usize = 'brk: { + let (col_idx, column) = 'brk: { let col_str = BunString::init(col_key); - // reshaped for borrowck — split find/append. - if let Some(idx) = - columns[1..].iter().position(|col| col.name.eql(&col_str)) + if let Some((idx, col)) = columns[1..] + .iter_mut() + .enumerate() + .find(|(_, col)| col.name.eql(&col_str)) { - break 'brk 1 + idx; + break 'brk (1 + idx, col); } // Need to ref this string because JSPropertyIterator @@ -795,11 +796,12 @@ impl<'a> TablePrinter<'a> { name: col_str, width: 1, }); - break 'brk columns.len() - 1; + let idx = columns.len() - 1; + break 'brk (idx, &mut columns[idx]); }; let cell = self.format_cell::(cell_text, value)?; - columns[col_idx].width = columns[col_idx].width.max(cell.width); + column.width = column.width.max(cell.width); let slot = col_idx - 1; if row.cells.len() <= slot { row.cells.resize(slot + 1, None); diff --git a/src/runtime/api/bun/spawn/stdio.rs b/src/runtime/api/bun/spawn/stdio.rs index a6ba3fafb659..68739681f76a 100644 --- a/src/runtime/api/bun/spawn/stdio.rs +++ b/src/runtime/api/bun/spawn/stdio.rs @@ -191,10 +191,6 @@ impl Stdio { } } - // Note: reshaped for borrowck — `remain` borrows `*self`, so we - // must drop it before mutating `self`. Shadowing ends the borrow here. - let _ = remain; - // Assigning to `*self` drops the previous variant via `Drop` // (and closes a prior `.memfd`). *self = Stdio::Memfd(fd); diff --git a/src/runtime/bake/production.rs b/src/runtime/bake/production.rs index 95aa242fdfa6..4e1dd88d5ddd 100644 --- a/src/runtime/bake/production.rs +++ b/src/runtime/bake/production.rs @@ -91,9 +91,6 @@ pub fn build_command(ctx: Context) -> crate::Result<()> { Global::crash(); } }; - // Note: reshaped for borrowck — clone the cwd slice so the PathBuffer - // borrow doesn't span the rest of the function (the buffer is never reused). - let cwd: Box<[u8]> = Box::from(cwd); // Create a VM + global for loading the config file, plugins, and // performing build time prerendering. @@ -211,7 +208,7 @@ pub fn build_command(ctx: Context) -> crate::Result<()> { // LIFO order — under the API lock, before the VM is destroyed. let mut pt = PerThread::placeholder(vm_ptr); - match build_with_vm(ctx, &cwd, &mut pt) { + match build_with_vm(ctx, cwd, &mut pt) { Ok(()) => {} Err(crate::Error::JSError) => { // SAFETY: vm.global is live for VM lifetime. @@ -530,8 +527,6 @@ fn build_with_vm(ctx: Context, cwd: &[u8], pt: &mut PerThread) -> crate::Result< let mut root_dir_buf = PathBuffer::uninit(); let root_dir_path = resolve_path::join_abs_string_buf::(cwd, &mut root_dir_buf.0, &[b"dist"]); - // Note: reshaped for borrowck — copy out so root_dir_buf can drop. - let root_dir_path: Box<[u8]> = Box::from(root_dir_path); // Note: borrowck — `framework` is `&mut options.framework`; reborrow // through it instead of `options.framework` to avoid stacking borrows. @@ -1183,7 +1178,7 @@ fn build_with_vm(ctx: Context, cwd: &[u8], pt: &mut PerThread) -> crate::Result< let render_promise = unsafe { &mut *BakeRenderRoutesForProdStatic( global, - BunString::init(&*root_dir_path), + BunString::init(root_dir_path), pt.all_server_files.as_ref().unwrap().get(), server_render_funcs, server_param_funcs, diff --git a/src/runtime/cli/open.rs b/src/runtime/cli/open.rs index c09dc4a8819d..b8d72fe8c177 100644 --- a/src/runtime/cli/open.rs +++ b/src/runtime/cli/open.rs @@ -85,22 +85,18 @@ impl Editor { editor: Editor, buf: &'a mut PathBuffer, cwd: &[u8], - out: &mut &'a [u8], - ) -> bool { - let Some(path_env) = env.get(b"PATH") else { - return false; - }; + ) -> Option<&'a [u8]> { + let path_env = env.get(b"PATH")?; if let Some(path) = BIN_NAME[editor] { if !path.is_empty() { if let Some(bin) = which(buf, path_env, cwd, path) { - *out = bin.as_bytes(); - return true; + return Some(bin.as_bytes()); } } } - false + None } pub(crate) fn by_fallback_path_for_editor( @@ -129,22 +125,15 @@ impl Editor { env: &mut dot_env::Loader, buf: &'a mut PathBuffer, cwd: &[u8], - out: &mut &'a [u8], - ) -> Option { - // Note: borrowck — see `by_path` above; same Polonius-case reborrow. - let buf_ptr: *mut PathBuffer = buf; + ) -> Option<(Editor, &'a [u8])> { for &editor in &DEFAULT_PREFERENCE_LIST { - // SAFETY: exclusive per-iteration reborrow; we return immediately on hit. - if Self::by_path_for_editor(env, editor, unsafe { &mut *buf_ptr }, cwd, out) { - return Some(editor); + if let Some(bin) = Self::by_path_for_editor(env, editor, &mut *buf, cwd) { + return Some((editor, bin)); } - // Note: reshaped for borrowck — by_fallback_path_for_editor writes a - // 'static slice; we widen `out` to accept it via a temporary. let mut static_out: &'static [u8] = b""; if Self::by_fallback_path_for_editor(editor, Some(&mut static_out)) { - *out = static_out; - return Some(editor); + return Some((editor, static_out)); } } @@ -465,12 +454,6 @@ impl EditorContext { pub(crate) fn detect_editor(&mut self, env: &mut dot_env::Loader) { let mut buf = PathBuffer::uninit(); - // Note: borrowck — `by_path_for_editor`/`by_fallback` tie `out`'s lifetime - // to `&'a mut buf`. On the `false` path NLL conservatively keeps `buf` borrowed - // (Polonius case). Re-borrow through a raw pointer at each call site; on a hit - // we return immediately so only one `&mut` is ever live. - let buf_ptr: *mut PathBuffer = &raw mut buf; - let mut out: &[u8] = b""; // first: choose from user preference if !self.name.is_empty() { @@ -484,18 +467,16 @@ impl EditorContext { // "vscode" if let Some(editor_) = Editor::by_name(bun_paths::basename(self.name)) { - if Editor::by_path_for_editor( + if let Some(bin) = Editor::by_path_for_editor( env, editor_, - // SAFETY: see note above — exclusive per-call reborrow. - unsafe { &mut *buf_ptr }, + &mut buf, Fs::FileSystem::instance().top_level_dir, - &mut out, ) { self.editor = Some(editor_); self.path = Fs::FileSystem::instance() .dirname_store - .append_slice(out) + .append_slice(bin) .expect("unreachable"); return; } @@ -515,18 +496,16 @@ impl EditorContext { // EDITOR=code if let Some(editor_) = Editor::detect(env) { - if Editor::by_path_for_editor( + if let Some(bin) = Editor::by_path_for_editor( env, editor_, - // SAFETY: see note above — exclusive per-call reborrow. - unsafe { &mut *buf_ptr }, + &mut buf, Fs::FileSystem::instance().top_level_dir, - &mut out, ) { self.editor = Some(editor_); self.path = Fs::FileSystem::instance() .dirname_store - .append_slice(out) + .append_slice(bin) .expect("unreachable"); return; } @@ -544,17 +523,13 @@ impl EditorContext { } // Don't know, so we will just guess based on what exists - if let Some(editor_) = Editor::by_fallback( - env, - // SAFETY: see note above — exclusive per-call reborrow. - unsafe { &mut *buf_ptr }, - Fs::FileSystem::instance().top_level_dir, - &mut out, - ) { + if let Some((editor_, bin)) = + Editor::by_fallback(env, &mut buf, Fs::FileSystem::instance().top_level_dir) + { self.editor = Some(editor_); self.path = Fs::FileSystem::instance() .dirname_store - .append_slice(out) + .append_slice(bin) .expect("unreachable"); return; } diff --git a/src/runtime/cli/upgrade_command.rs b/src/runtime/cli/upgrade_command.rs index fcd16bf1b0bf..8325b8db3bda 100644 --- a/src/runtime/cli/upgrade_command.rs +++ b/src/runtime/cli/upgrade_command.rs @@ -900,9 +900,6 @@ impl UpgradeCommand { .expect("oom"); let mut buf = PathBuffer::uninit(); - // Separate fallback buffer — borrowck holds `buf` for the lifetime - // of `which`'s returned `Option<&ZStr>` even across the `None` arm. - let mut buf2 = PathBuffer::uninit(); let powershell_path: &ZStr = match which( &mut buf, bun_core::env_var::PATH.get().unwrap_or(b""), @@ -917,7 +914,7 @@ impl UpgradeCommand { let hardcoded_system_powershell = bun_paths::join_abs_string_buf_z::( system_root, - &mut buf2[..], + &mut buf[..], &[ system_root, b"System32\\WindowsPowerShell\\v1.0\\powershell.exe", diff --git a/src/runtime/dns_jsc/dns.rs b/src/runtime/dns_jsc/dns.rs index 950620131d25..c1a7a5a5d96a 100644 --- a/src/runtime/dns_jsc/dns.rs +++ b/src/runtime/dns_jsc/dns.rs @@ -4831,9 +4831,6 @@ impl Resolver { *poll_entry.value_ptr } else { let new_poll = UvDnsPoll::new(this_ptr, fd); - // Publish into the map first so the `GetOrPutResult` borrow can - // end (NLL) before we may need to `swap_remove` on init failure. - *poll_entry.value_ptr = new_poll; // SAFETY: `Loop::get()` is the live per-thread uws loop; // `new_poll` is a fresh heap allocation with a zeroed `uv_poll_t`. if unsafe { @@ -4844,6 +4841,7 @@ impl Resolver { let _ = polls.swap_remove(&fd); return; } + *poll_entry.value_ptr = new_poll; new_poll }; diff --git a/src/runtime/node/node_fs.rs b/src/runtime/node/node_fs.rs index 75e13e84411a..4733220ec1f2 100644 --- a/src/runtime/node/node_fs.rs +++ b/src/runtime/node/node_fs.rs @@ -10061,12 +10061,7 @@ fn zig_delete_tree_min_stack_size_with_kind_hint( // ever store a single path component that was returned from the // filesystem. let mut dir_name_buf = PathBuffer::uninit(); - let mut dir_name_len = sub_path.len().min(dir_name_buf.len()); - dir_name_buf[..dir_name_len].copy_from_slice(&sub_path[..dir_name_len]); - // `dir_name` conceptually aliases either `sub_path` or `dir_name_buf`; - // the borrow checker won't let that alias survive the copy/reassignment - // below, so track `(is_sub_path, len)` and re-slice on each use. - let mut dir_name_is_sub_path = true; + let mut dir_name: &[u8] = sub_path; // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function. // Go through each entry and if it is not a directory, delete it. If it is a directory, @@ -10089,8 +10084,7 @@ fn zig_delete_tree_min_stack_size_with_kind_hint( dir = new_dir; let n = entry_name.len().min(dir_name_buf.len()); dir_name_buf[..n].copy_from_slice(&entry_name[..n]); - dir_name_len = n; - dir_name_is_sub_path = false; + dir_name = &dir_name_buf[..n]; continue 'scan_dir; } Err(E::ENOTDIR) => { @@ -10127,11 +10121,6 @@ fn zig_delete_tree_min_stack_size_with_kind_hint( // Now to remove the directory itself. dir.close(); - let dir_name: &[u8] = if dir_name_is_sub_path { - sub_path - } else { - &dir_name_buf[..dir_name_len] - }; if let Some(d) = cleanup_dir_parent { match dt_delete_dir(&d, dir_name) { Ok(()) | Err(E::ENOENT) | Err(E::ENOTEMPTY) | Err(E::EEXIST) => { diff --git a/src/runtime/node/path.rs b/src/runtime/node/path.rs index 353690647807..d57ef6208112 100644 --- a/src/runtime/node/path.rs +++ b/src/runtime/node/path.rs @@ -1082,11 +1082,10 @@ fn format_t<'a, T: PathCharCwd>( // const base = pathObject.base || // `${pathObject.name || ''}${formatExt(pathObject.ext)}`; let mut base_len = base.len(); - // Borrowck: track range into buf instead of slice. - let base_or_name_ext_range: (usize, usize) = if base_len > 0 { + let base_or_name_ext = if base_len > 0 { memmove(&mut buf[0..base_len], base); - (0, base_len) + &buf[0..base_len] } else { let formatted_ext_len = { // Borrowck: inline format_ext_t to avoid overlapping &mut. @@ -1115,9 +1114,9 @@ fn format_t<'a, T: PathCharCwd>( memmove(&mut buf[0..name_len], _name); } if buf_size > 0 { - (0, buf_size) + &buf[0..buf_size] } else { - (0, base_len) + &buf[0..base_len] } }; @@ -1126,20 +1125,17 @@ fn format_t<'a, T: PathCharCwd>( // return base; // } if dir_len == 0 { - return &buf[base_or_name_ext_range.0..base_or_name_ext_range.1]; + return base_or_name_ext; } // Translated from the following JS code: // return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`; - base_len = base_or_name_ext_range.1 - base_or_name_ext_range.0; + base_len = base_or_name_ext.len(); if base_len > 0 { buf_offset = if dir_is_root { dir_len } else { dir_len + 1 }; // Move all bytes to the right by dirLen + (maybe 1 for the separator). // Use copy_within because baseOrNameExt and buf overlap. - buf.copy_within( - base_or_name_ext_range.0..base_or_name_ext_range.1, - buf_offset, - ); + buf.copy_within(0..base_len, buf_offset); } memmove(&mut buf[0..dir_len], dir_or_root); buf_size = dir_len + base_len; @@ -1335,8 +1331,7 @@ fn join_posix_t<'a, T: PathCharCwd>( let mut buf_offset: usize; // Back joined by expandable buf2 in case it is long. - // Borrowck: track length instead of slice into buf2. - let mut joined_len: usize = 0; + let mut joined: &[T] = &[]; for path in paths { // validateString of `path is performed in pub fn join. @@ -1358,13 +1353,13 @@ fn join_posix_t<'a, T: PathCharCwd>( buf_size += len; memmove(&mut buf2[buf_offset..buf_size], path); - joined_len = buf_size; + joined = &buf2[0..buf_size]; } } if buf_size == 0 { return l::(CHAR_STR_DOT); } - normalize_posix_t(&buf2[0..joined_len], buf) + normalize_posix_t(joined, buf) } /// # Safety @@ -3475,10 +3470,9 @@ fn to_namespaced_path_windows_t<'a, T: PathCharCwd>( ) -> MaybeSlice<'a, T> { // validateString of `path` is performed in pub fn toNamespacedPath. // Backed by buf. - // Borrowck: capture length, then re-borrow buf. - let resolved_len = resolve_windows_t(&[path], buf, buf2)?.len(); + let resolved = resolve_windows_t(&[path], buf, buf2)?; - let len = resolved_len; + let len = resolved.len(); if len <= 2 { buf[0..path.len()].copy_from_slice(path); buf[path.len()] = T::default(); @@ -3488,11 +3482,11 @@ fn to_namespaced_path_windows_t<'a, T: PathCharCwd>( let buf_offset: usize; let buf_size: usize; - let byte0 = buf[0]; + let byte0 = resolved[0]; if byte0 == T::from_u8(CHAR_BACKWARD_SLASH) { // Possible UNC root - if buf[1] == T::from_u8(CHAR_BACKWARD_SLASH) { - let byte2 = buf[2]; + if resolved[1] == T::from_u8(CHAR_BACKWARD_SLASH) { + let byte2 = resolved[2]; if byte2 != T::from_u8(CHAR_QUESTION_MARK) && byte2 != T::from_u8(CHAR_DOT) { // Matched non-long UNC root, convert the path to a long UNC path @@ -3517,8 +3511,8 @@ fn to_namespaced_path_windows_t<'a, T: PathCharCwd>( } } } else if is_windows_device_root_t(byte0) - && buf[1] == T::from_u8(CHAR_COLON) - && buf[2] == T::from_u8(CHAR_BACKWARD_SLASH) + && resolved[1] == T::from_u8(CHAR_COLON) + && resolved[2] == T::from_u8(CHAR_BACKWARD_SLASH) { // Matched device root, convert the path to a long UNC path @@ -3536,7 +3530,7 @@ fn to_namespaced_path_windows_t<'a, T: PathCharCwd>( buf[buf_size] = T::default(); return Ok(&buf[0..buf_size]); } - Ok(&buf[0..resolved_len]) + Ok(resolved) } fn to_namespaced_path_windows_js_t( diff --git a/src/runtime/node/types.rs b/src/runtime/node/types.rs index 12f682ee117f..9a626a3e10da 100644 --- a/src/runtime/node/types.rs +++ b/src/runtime/node/types.rs @@ -960,20 +960,15 @@ impl PathLikeExt for PathLike { // SAFETY: buf[4+n] == 0 written above. return ZStr::from_buf(&buf[..], 4 + n); } - // reshaped for borrowck — capture the length so - // the `Ok` borrow ends at the match, then re-derive. - let resolved_len = match bun_paths::resolve_path::PosixToWinNormalizer::resolve_cwd_with_external_buf_z(buf, sliced) { - Ok(res) => Some(res.len()), + match bun_paths::resolve_path::PosixToWinNormalizer::resolve_cwd_with_external_buf_z( + buf, sliced, + ) { + Ok(res) => return res, // The cwd root + path don't fit `buf` (UNC cwds can push // a near-MAX_PATH_BYTES path over); fall through to the // plain copy / too-long handling below. - Err(bun_paths::Error::Sys(bun_errno::SystemErrno::ENAMETOOLONG)) => None, + Err(bun_paths::Error::Sys(bun_errno::SystemErrno::ENAMETOOLONG)) => {} Err(e) => panic!("Error while resolving path: {e:?}"), - }; - if let Some(len) = resolved_len { - // SAFETY: `resolve_cwd_with_external_buf_z` wrote the NUL - // at `buf[len]`. - return ZStr::from_buf(&buf[..], len); } } } diff --git a/src/runtime/socket/Listener.rs b/src/runtime/socket/Listener.rs index d1c3a18fe91a..54a104b27b9f 100644 --- a/src/runtime/socket/Listener.rs +++ b/src/runtime/socket/Listener.rs @@ -1158,15 +1158,12 @@ impl Listener { use bun_sys::FdExt as _; let mut buf = PathBuffer::uninit(); - // Note: reshaped for borrowck — `normalize_pipe_name` borrows - // `buf` for the returned slice; store length and re-borrow after the - // `connection` match drops. - let mut pipe_name_len: Option = None; + let mut pipe_name: Option<&[u8]> = None; let is_named_pipe = match &mut connection { // we check if the path is a named pipe otherwise we try to connect using AF_UNIX UnixOrHost::Unix(slice) => match normalize_pipe_name(slice, buf.as_mut_slice()) { Some(name) => { - pipe_name_len = Some(name.len()); + pipe_name = Some(name); true } None => false, @@ -1273,7 +1270,7 @@ impl Listener { let named_pipe_result = match tls_ref.connection.get().as_ref().unwrap() { UnixOrHost::Unix(_) => WindowsNamedPipeContext::connect( global, - &buf[..pipe_name_len.unwrap()], + pipe_name.unwrap(), ssl_taken.take(), ctx_for_pipe, PipeSocketType::Tls(tls_ref), @@ -1346,7 +1343,7 @@ impl Listener { let named_pipe_result = match tcp_ref.connection.get().as_ref().unwrap() { UnixOrHost::Unix(_) => WindowsNamedPipeContext::connect( global, - &buf[..pipe_name_len.unwrap()], + pipe_name.unwrap(), None, None, PipeSocketType::Tcp(tcp_ref), diff --git a/src/runtime/test_runner/snapshot.rs b/src/runtime/test_runner/snapshot.rs index ff586c680b4f..b5b94dddc248 100644 --- a/src/runtime/test_runner/snapshot.rs +++ b/src/runtime/test_runner/snapshot.rs @@ -157,11 +157,8 @@ impl<'a> Snapshots<'a> { name_with_counter.extend_from_slice(counter_string); let name_hash: u64 = hash(&name_with_counter); - // reshaped for borrowck — `get` then early-return borrows `*self.values` - // immutably for the whole fn body (NLL limitation with returned borrows), preventing - // the later `insert`. Probe with `contains_key` first; re-lookup on hit. - if self.values.contains_key(&name_hash) { - return Ok(Some(&**self.values.get(&name_hash).unwrap())); + if let Some(value) = self.values.get(&name_hash) { + return Ok(Some(&**value)); } // doesn't exist. append to file bytes and add to hashmap. diff --git a/src/runtime/webcore/Body.rs b/src/runtime/webcore/Body.rs index 93041722ba08..4c895df5bd52 100644 --- a/src/runtime/webcore/Body.rs +++ b/src/runtime/webcore/Body.rs @@ -1547,11 +1547,6 @@ impl Value { _ => {} } - // reshaped for borrowck — re-borrow locked after the early *self = Null path above. - let Value::Locked(locked) = self else { - unreachable!() - }; - reader.producer.set(locked.producer); let context_ptr: *mut ByteStream = &raw mut reader.context; diff --git a/src/sql_jsc/postgres/DataCell.rs b/src/sql_jsc/postgres/DataCell.rs index 153924396a5b..23b11c25a8ec 100644 --- a/src/sql_jsc/postgres/DataCell.rs +++ b/src/sql_jsc/postgres/DataCell.rs @@ -422,10 +422,6 @@ fn parse_array( let mut has_exponent = false; let mut has_negative_sign = false; let mut has_positive_sign = false; - // reshaped for borrowck — cannot mutate `slice` mid-loop while - // iterating it (the Infinity arm). We capture the advance amount and - // apply after the loop. - let mut advance_after: Option = None; for (index, &byte) in slice.iter().enumerate() { match byte { b'0'..=b'9' => {} @@ -491,7 +487,7 @@ fn parse_array( } else { array.push(SQLDataCell::float8(val)); } - advance_after = Some(8 + (is_negative as usize)); + slice = try_slice(slice, 8 + (is_negative as usize)); break; } @@ -502,9 +498,6 @@ fn parse_array( } } } - if let Some(n) = advance_after { - slice = try_slice(slice, n); - } if is_infinity { continue; } diff --git a/src/which/lib.rs b/src/which/lib.rs index 9613ace0d929..0e12e39e7299 100644 --- a/src/which/lib.rs +++ b/src/which/lib.rs @@ -82,12 +82,7 @@ pub fn which_for_spawn<'a>( let mut rel: Vec = Vec::with_capacity(bin.len() + 2); rel.extend_from_slice(b"./"); rel.extend_from_slice(bin); - // PORT NOTE: NLL Polonius limitation — raw-ptr reborrow so the None - // branch can fall through without `buf` appearing borrowed. - // SAFETY: the borrow does not escape this block on the None path. - let buf_reborrow: &'a mut PathBuffer = - unsafe { &mut *std::ptr::from_mut::(buf) }; - if let Some(found) = which(buf_reborrow, b"", cwd, &rel) { + if let Some(found) = which(&mut *buf, b"", cwd, &rel) { return Some(found); } } @@ -354,13 +349,8 @@ pub(crate) fn which_win<'a>( // check if bin is in cwd if strings::index_of_char(bin, b'/').is_some() || strings::index_of_char(bin, b'\\').is_some() { - // NLL/Polonius limitation — raw-ptr reborrow so the None branch can - // fall through without `buf` appearing borrowed. - // SAFETY: bin_path borrow does not escape this block on the None path. - let buf_reborrow: &'a mut WPathBuffer = - unsafe { &mut *std::ptr::from_mut::(buf) }; if let Some(bin_path) = search_bin_in_path( - buf_reborrow, + &mut *buf, &mut *path_buf, cwd, strings::without_prefix_comptime(bin, b"./"), @@ -375,13 +365,8 @@ pub(crate) fn which_win<'a>( // iterate over system path delimiter for segment_part in strings::tokenize(path, b";") { - // NLL/Polonius limitation — re-borrowing `buf` across loop iterations - // when returning a reference tied to its lifetime. - // SAFETY: on None the borrow ends; on Some we return immediately. - let buf_reborrow: &'a mut WPathBuffer = - unsafe { &mut *std::ptr::from_mut::(buf) }; if let Some(bin_path) = search_bin_in_path( - buf_reborrow, + &mut *buf, &mut *path_buf, segment_part, bin, diff --git a/test/js/bun/console/__snapshots__/bun-inspect-table.test.ts.snap b/test/js/bun/console/__snapshots__/bun-inspect-table.test.ts.snap index f166e92f896f..e5f146bdd788 100644 --- a/test/js/bun/console/__snapshots__/bun-inspect-table.test.ts.snap +++ b/test/js/bun/console/__snapshots__/bun-inspect-table.test.ts.snap @@ -287,3 +287,39 @@ exports[`inspect.table (with colors in 2nd position) { a: 1, b: 2 } 2`] = ` └───┴────────┘ " `; + +exports[`inspect.table [ { d: 1 }, { b: 2 }, { a: 4, d: 3 }, { a: 5, c: 6, e: 7 } ] 1`] = ` +"┌───┬───┬───┬───┬───┬───┐ +│ │ d │ b │ a │ c │ e │ +├───┼───┼───┼───┼───┼───┤ +│ 0 │ 1 │ │ │ │ │ +│ 1 │ │ 2 │ │ │ │ +│ 2 │ 3 │ │ 4 │ │ │ +│ 3 │ │ │ 5 │ 6 │ 7 │ +└───┴───┴───┴───┴───┴───┘ +" +`; + +exports[`inspect.table columns keep first-seen order across rows when not sorted 1`] = ` +"┌───┬───┬───┬───┬───┬───┐ +│ │ d │ b │ a │ c │ e │ +├───┼───┼───┼───┼───┼───┤ +│ 0 │ 1 │ │ │ │ │ +│ 1 │ │ 2 │ │ │ │ +│ 2 │ 3 │ │ 4 │ │ │ +│ 3 │ │ │ 5 │ 6 │ 7 │ +└───┴───┴───┴───┴───┴───┘ +" +`; + +exports[`inspect.table (ansi) [ { d: 1 }, { b: 2 }, { a: 4, d: 3 }, { a: 5, c: 6, e: 7 } ] 1`] = ` +"┌───┬───┬───┬───┬───┬───┐ +│ \x1B[0m\x1B[1m \x1B[0m │ \x1B[0m\x1B[1md\x1B[0m │ \x1B[0m\x1B[1mb\x1B[0m │ \x1B[0m\x1B[1ma\x1B[0m │ \x1B[0m\x1B[1mc\x1B[0m │ \x1B[0m\x1B[1me\x1B[0m │ +├───┼───┼───┼───┼───┼───┤ +│ 0 │ \x1B[0m\x1B[33m1\x1B[0m │ │ │ │ │ +│ 1 │ │ \x1B[0m\x1B[33m2\x1B[0m │ │ │ │ +│ 2 │ \x1B[0m\x1B[33m3\x1B[0m │ │ \x1B[0m\x1B[33m4\x1B[0m │ │ │ +│ 3 │ │ │ \x1B[0m\x1B[33m5\x1B[0m │ \x1B[0m\x1B[33m6\x1B[0m │ \x1B[0m\x1B[33m7\x1B[0m │ +└───┴───┴───┴───┴───┴───┘ +" +`; diff --git a/test/js/bun/console/bun-inspect-table.test.ts b/test/js/bun/console/bun-inspect-table.test.ts index 951c22318da5..2bf36b015237 100644 --- a/test/js/bun/console/bun-inspect-table.test.ts +++ b/test/js/bun/console/bun-inspect-table.test.ts @@ -18,6 +18,9 @@ const inputs = [ [1, 2, 3], ["a", 1, "b", 2, "c", 3], [/a/, 1, /b/, 2, /c/, 3], + // columns discovered across rows: later rows re-find earlier columns and + // append new ones + [{ d: 1 }, { b: 2 }, { d: 3, a: 4 }, { a: 5, c: 6, e: 7 }], ]; describe("inspect.table", () => { @@ -27,6 +30,14 @@ describe("inspect.table", () => { }); }); + test("columns keep first-seen order across rows when not sorted", () => { + // d, b, a, c, e: discovery order is deliberately non-alphabetical so this + // fails if discovered columns were ever sorted instead of appended + expect( + inspect.table([{ d: 1 }, { b: 2 }, { d: 3, a: 4 }, { a: 5, c: 6, e: 7 }], { colors: false }), + ).toMatchSnapshot(); + }); + it.each([ null, undefined, diff --git a/test/js/bun/util/which.test.ts b/test/js/bun/util/which.test.ts index 55762ea6e797..3c2502a979e5 100644 --- a/test/js/bun/util/which.test.ts +++ b/test/js/bun/util/which.test.ts @@ -270,3 +270,22 @@ test("Bun.which can find executables in a non-ascii directory", async () => { process.chdir(cwd); } }); + +test("Bun.which finds a bin in a later PATH segment after earlier misses", async () => { + await using dir = tempDir("which-path-segments", { + "first/.keep": "", + "third/prog_in_third": "#!/usr/bin/env sh\necho posix\nexit 0\n", + "third/prog_in_third.cmd": "@echo win32\n@exit 0\n", + }); + const d = String(dir); + if (!isWindows) { + chmodSync(join(d, "third/prog_in_third"), 0o755); + } + + const delim = isWindows ? ";" : ":"; + // first segment exists but misses, second doesn't exist, third hits + const PATH = [join(d, "first"), join(d, "does-not-exist"), join(d, "third")].join(delim); + const suffix = isWindows ? ".cmd" : ""; + expect(which("prog_in_third", { PATH })).toBe(join(d, "third", "prog_in_third" + suffix)); + expect(which("prog_in_nowhere", { PATH })).toBe(null); +}); diff --git a/test/js/node/path/to-namespaced-path.test.js b/test/js/node/path/to-namespaced-path.test.js index 19893dd6787e..a2ed199ce71b 100644 --- a/test/js/node/path/to-namespaced-path.test.js +++ b/test/js/node/path/to-namespaced-path.test.js @@ -70,6 +70,17 @@ describe("path.toNamespacedPath", () => { assert.strictEqual(path.win32.toNamespacedPath(emptyObj), emptyObj); }); + test("win32 branch coverage", () => { + // already-namespaced input is returned as resolved, unchanged + assert.strictEqual(path.win32.toNamespacedPath("\\\\?\\UNC\\a\\b"), "\\\\?\\UNC\\a\\b"); + // device root, trailing separator preserved + assert.strictEqual(path.win32.toNamespacedPath("C:\\"), "\\\\?\\C:\\"); + // resolve() collapses dot-dot segments and keeps the drive letter's case + assert.strictEqual(path.win32.toNamespacedPath("c:/foo/../bar"), "\\\\?\\c:\\bar"); + // UNC root conversion inserts the UNC prefix and keeps the trailing slash + assert.strictEqual(path.win32.toNamespacedPath("\\\\server\\share"), "\\\\?\\UNC\\server\\share\\"); + }); + test("posix", () => { assert.strictEqual(path.posix.toNamespacedPath("/foo/bar"), "/foo/bar"); assert.strictEqual(path.posix.toNamespacedPath("foo/bar"), "foo/bar");