diff --git a/Cargo.lock b/Cargo.lock index bc3e11e9b2b..340be45cb90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,6 +204,7 @@ dependencies = [ "bun_core", "bun_dispatch", "bun_paths", + "bun_perf", "bun_ptr", "bun_sys", "bun_wyhash", @@ -356,6 +357,7 @@ dependencies = [ "bstr", "bumpalo", "bun_alloc", + "bun_analytics", "bun_ast", "bun_base64", "bun_collections", @@ -1590,6 +1592,7 @@ dependencies = [ "bitflags", "bstr", "bun_ast", + "bun_collections", "bun_core", "bun_options_types", "const_format", @@ -2041,6 +2044,7 @@ version = "0.0.0" dependencies = [ "bitflags", "bstr", + "bun_analytics", "bun_base64", "bun_boringssl", "bun_boringssl_sys", diff --git a/scripts/build/features-json.ts b/scripts/build/features-json.ts index 5ce60a384c9..6e6b1d823e5 100644 --- a/scripts/build/features-json.ts +++ b/scripts/build/features-json.ts @@ -43,17 +43,25 @@ export function parsePackedFeaturesList(cwd: string): string[] { const sourcePath = resolve(cwd, "src", "analytics", "lib.rs"); const source = readFileSync(sourcePath, "utf8"); - const invocation = source.match(/define_features!\s*\{([\s\S]*?)\n\s*\}/); - if (invocation === null) { + // The macro now recurses internally (`define_features! { @storage ... }`), + // so several invocation-shaped blocks exist; the entry list is the one whose + // body contains ` => (...)` arms. Scan every block and keep the + // entries we find — the density check below still rejects partial parses. + const invocations = [...source.matchAll(/define_features!\s*\{([\s\S]*?)\n\s*\}/g)]; + if (invocations.length === 0) { throw new BuildError(`Could not find the define_features! invocation in ${sourcePath}`, { hint: "parsePackedFeaturesList() in scripts/build/features-json.ts needs updating to match the new shape.", }); } const entries: { index: number; name: string }[] = []; - const entryRe = /(\d+)\s*=>\s*\(\s*\w+\s*,\s*"((?:[^"\\]|\\.)*)"\s*\)/g; - for (const m of invocation[1]!.matchAll(entryRe)) { - entries.push({ index: Number(m[1]), name: m[2]! }); + // ` => (, "")` with an optional + // `, core = IDENT` alias of the bun_core feature static. + const entryRe = /(\d+)\s*=>\s*\(\s*\w+\s*,\s*"((?:[^"\\]|\\.)*)"\s*(?:,\s*core\s*=\s*\w+\s*)?\)/g; + for (const invocation of invocations) { + for (const m of invocation[1]!.matchAll(entryRe)) { + entries.push({ index: Number(m[1]), name: m[2]! }); + } } if (entries.length === 0) { throw new BuildError(`Parsed zero entries from define_features! in ${sourcePath}`, { diff --git a/src/analytics/lib.rs b/src/analytics/lib.rs index 4af975b761d..de62f1a76dc 100644 --- a/src/analytics/lib.rs +++ b/src/analytics/lib.rs @@ -36,7 +36,6 @@ impl TriState { } } -// Zig: `pub var enabled: enum { yes, no, unknown } = .unknown;` static ENABLED: AtomicU8 = AtomicU8::new(TriState::Unknown as u8); pub(crate) fn enabled() -> TriState { @@ -52,9 +51,9 @@ pub fn is_enabled() -> bool { TriState::No => false, TriState::Unknown => { let detected = 'detect: { - // PORT NOTE: `env_var::*.get()` returns `Option` in - // the Rust port even when a default exists; `DO_NOT_TRACK` has - // `default: false` so `.unwrap_or(false)` matches Zig semantics. + // `env_var::*.get()` returns `Option` even when a + // default exists; `DO_NOT_TRACK` has `default: false`, hence + // `.unwrap_or(false)`. if env_var::DO_NOT_TRACK.get().unwrap_or(false) { break 'detect TriState::No; } @@ -79,38 +78,51 @@ pub fn is_enabled() -> bool { /// This answers, "What parts of bun are people actually using?" /// -/// PORT NOTE: In Zig this is a `struct` used purely as a namespace of `pub var` -/// decls, iterated via `@typeInfo` reflection. Rust has no decl reflection, so -/// the feature list is declared once via `define_features!` and that macro +/// The feature list is declared once via `define_features!` and that macro /// generates the statics, `PACKED_FEATURES_LIST`, `PackedFeatures`, /// `packed_features()`, and the `Display` body. pub mod features { use super::*; - // PORT NOTE (cyclebreak): the Zig original is - // `EnumSet(bun.jsc.ModuleLoader.HardcodedModule)`. That enum lives in + // Note (cyclebreak): `bun.jsc.ModuleLoader.HardcodedModule` lives in // `bun_resolve_builtins` (T5) and pulling it here would create a forward // dep (analytics is T1). The only operations we need are `insert` and // ordered iteration of the module *names* for the crash-report formatter, // so store the `&'static str` name (= `@tagName(HardcodedModule)`) instead // of the enum value. Writers (`runtime/jsc_hooks.rs`) call // `BUILTIN_MODULES.lock().insert(<&'static str>::from(hardcoded))`. - // PERF(port): Zig used a packed `EnumSet` (bitset); BTreeSet is O(log n) - // insert — fine for ≤~80 entries written once each at module-load time. + // PERF: BTreeSet is O(log n) insert — fine for ≤~80 entries written once + // each at module-load time. pub(crate) static BUILTIN_MODULES: bun_core::Mutex> = bun_core::Mutex::new(std::collections::BTreeSet::new()); - // PORT NOTE: Zig used a plain mutable global; wrapped in a Mutex here - // because the set is not a single atomic word. + + /// Record a builtin-module load. + pub fn insert_builtin_module(name: &'static str) { + BUILTIN_MODULES.lock().insert(name); + } macro_rules! define_features { - ( $( $(#[$doc:meta])* $idx:literal => ($ident:ident, $name:literal) ),* $(,)? ) => { + // Storage for one feature counter. Entries tagged `core = IDENT` alias + // the tier-0 `bun_core::Global::features::IDENT` static (the MOVE_DOWN + // set written by low-tier crates: dotenv/install/css/todo_panic!/...) + // so the crash-report/analytics readers below observe those writes — + // a fresh static here would be a split brain that stays forever 0. + // Entries with an `export_name` attribute must NOT be `core`-tagged: + // the exported symbol has to be the single canonical definition here. + (@storage $(#[$doc:meta])* $ident:ident) => { + $(#[$doc])* + #[allow(non_upper_case_globals)] + pub static $ident: AtomicUsize = AtomicUsize::new(0); + }; + (@storage $(#[$doc:meta])* $ident:ident, $core:ident) => { + $(#[$doc])* + pub use ::bun_core::Global::features::$core as $ident; + }; + ( $( $(#[$doc:meta])* $idx:literal => ($ident:ident, $name:literal $(, core = $core:ident)?) ),* $(,)? ) => { $( - $(#[$doc])* - #[allow(non_upper_case_globals)] - pub static $ident: AtomicUsize = AtomicUsize::new(0); + define_features! { @storage $(#[$doc])* $ident $(, $core)? } )* - // Zig: `validateFeatureName(decl.name)` per entry at comptime. $( const _: () = assert!( super::validate_feature_name($name.as_bytes()), @@ -118,12 +130,10 @@ pub mod features { ); )* - /// Zig: `pub const packed_features_list = brk: { ... }` pub const PACKED_FEATURES_LIST: &[&str] = &[ $( $name ),* ]; - // Zig: `pub const PackedFeatures = @Type(.{ .@"struct" = .{ .layout = .@"packed", .backing_integer = u64, ... } })` // All fields are `bool` → bitflags over u64. - // PORT NOTE: nightly `${index()}` (macro_metavar_expr) is unavailable + // Note: nightly `${index()}` (macro_metavar_expr) is unavailable // on stable, so each feature carries an explicit `$idx` literal at the // call site. The dense-index assertion below catches gaps/duplicates. ::bitflags::bitflags! { @@ -148,7 +158,8 @@ pub mod features { "feature indices must be dense 0..N with no gaps or duplicates" ); - /// Zig: `pub fn packedFeatures() PackedFeatures` + /// Snapshot of all feature counters as a `PackedFeatures` bitset + /// (bit set iff the feature was used at least once). pub fn packed_features() -> PackedFeatures { let mut bits = PackedFeatures::empty(); $( @@ -214,73 +225,71 @@ pub mod features { }; } - // PORT NOTE: Zig identifiers `@"Bun.stderr"` etc. cannot be Rust idents; - // renamed to `bun_stderr` etc. The string literal preserves the original - // name for output / `PACKED_FEATURES_LIST` (matches `@tagName` semantics). + // The string literal is the name used for output / `PACKED_FEATURES_LIST`. // The leading integer is the bit index in `PackedFeatures` (must be dense // 0..N — asserted at compile time inside the macro). define_features! { - 0 => (bun_stderr, "Bun.stderr"), - 1 => (bun_stdin, "Bun.stdin"), - 2 => (bun_stdout, "Bun.stdout"), - 3 => (web_socket, "WebSocket"), - 4 => (abort_signal, "abort_signal"), - 5 => (binlinks, "binlinks"), - 6 => (bunfig, "bunfig"), - 7 => (define, "define"), - 8 => (dotenv, "dotenv"), - 9 => (debugger, "debugger"), - 10 => (external, "external"), - 11 => (extracted_packages, "extracted_packages"), - 12 => (fetch, "fetch"), - 13 => (git_dependencies, "git_dependencies"), - 14 => (html_rewriter, "html_rewriter"), + 0 => (bun_stderr, "Bun.stderr", core = BUN_STDERR), + 1 => (bun_stdin, "Bun.stdin", core = BUN_STDIN), + 2 => (bun_stdout, "Bun.stdout", core = BUN_STDOUT), + 3 => (web_socket, "WebSocket", core = WEBSOCKET), + 4 => (abort_signal, "abort_signal", core = ABORT_SIGNAL), + 5 => (binlinks, "binlinks", core = BINLINKS), + 6 => (bunfig, "bunfig", core = BUNFIG), + 7 => (define, "define", core = DEFINE), + 8 => (dotenv, "dotenv", core = DOTENV), + 9 => (debugger, "debugger", core = DEBUGGER), + 10 => (external, "external", core = EXTERNAL), + 11 => (extracted_packages, "extracted_packages", core = EXTRACTED_PACKAGES), + 12 => (fetch, "fetch", core = FETCH), + 13 => (git_dependencies, "git_dependencies", core = GIT_DEPENDENCIES), + 14 => (html_rewriter, "html_rewriter", core = HTML_REWRITER), /// TCP server from `Bun.listen` - 15 => (tcp_server, "tcp_server"), + 15 => (tcp_server, "tcp_server", core = TCP_SERVER), /// TLS server from `Bun.listen` - 16 => (tls_server, "tls_server"), - 17 => (http_server, "http_server"), - 18 => (https_server, "https_server"), - 19 => (http_client_proxy, "http_client_proxy"), + 16 => (tls_server, "tls_server", core = TLS_SERVER), + 17 => (http_server, "http_server", core = HTTP_SERVER), + 18 => (https_server, "https_server", core = HTTPS_SERVER), + 19 => (http_client_proxy, "http_client_proxy", core = HTTP_CLIENT_PROXY), /// Set right before JSC::initialize is called - 20 => (jsc, "jsc"), + 20 => (jsc, "jsc", core = JSC), /// Set when bake.DevServer is initialized - 21 => (dev_server, "dev_server"), - 22 => (lifecycle_scripts, "lifecycle_scripts"), - 23 => (loaders, "loaders"), - 24 => (lockfile_migration_from_package_lock, "lockfile_migration_from_package_lock"), - 25 => (text_lockfile, "text_lockfile"), - 26 => (isolated_bun_install, "isolated_bun_install"), - 27 => (hoisted_bun_install, "hoisted_bun_install"), - 28 => (macros, "macros"), - 29 => (no_avx2, "no_avx2"), - 30 => (no_avx, "no_avx"), - 31 => (shell, "shell"), - 32 => (spawn, "spawn"), - 33 => (standalone_executable, "standalone_executable"), - 34 => (standalone_shell, "standalone_shell"), + 21 => (dev_server, "dev_server", core = DEV_SERVER), + 22 => (lifecycle_scripts, "lifecycle_scripts", core = LIFECYCLE_SCRIPTS), + 23 => (loaders, "loaders", core = LOADERS), + 24 => (lockfile_migration_from_package_lock, "lockfile_migration_from_package_lock", core = LOCKFILE_MIGRATION_FROM_PACKAGE_LOCK), + 25 => (text_lockfile, "text_lockfile", core = TEXT_LOCKFILE), + 26 => (isolated_bun_install, "isolated_bun_install", core = ISOLATED_BUN_INSTALL), + 27 => (hoisted_bun_install, "hoisted_bun_install", core = HOISTED_BUN_INSTALL), + 28 => (macros, "macros", core = MACROS), + 29 => (no_avx2, "no_avx2", core = NO_AVX2), + 30 => (no_avx, "no_avx", core = NO_AVX), + 31 => (shell, "shell", core = SHELL), + 32 => (spawn, "spawn", core = SPAWN), + 33 => (standalone_executable, "standalone_executable", core = STANDALONE_EXECUTABLE), + 34 => (standalone_shell, "standalone_shell", core = STANDALONE_SHELL), /// Set when invoking a todo panic - 35 => (todo_panic, "todo_panic"), - 36 => (transpiler_cache, "transpiler_cache"), - 37 => (tsconfig, "tsconfig"), - 38 => (tsconfig_paths, "tsconfig_paths"), - 39 => (virtual_modules, "virtual_modules"), - 40 => (workers_spawned, "workers_spawned"), - 41 => (workers_terminated, "workers_terminated"), + 35 => (todo_panic, "todo_panic", core = TODO_PANIC), + 36 => (transpiler_cache, "transpiler_cache", core = TRANSPILER_CACHE), + 37 => (tsconfig, "tsconfig", core = TSCONFIG), + 38 => (tsconfig_paths, "tsconfig_paths", core = TSCONFIG_PATHS), + 39 => (virtual_modules, "virtual_modules", core = VIRTUAL_MODULES), + 40 => (workers_spawned, "workers_spawned", core = WORKERS_SPAWNED), + 41 => (workers_terminated, "workers_terminated", core = WORKERS_TERMINATED), #[unsafe(export_name = "Bun__napi_module_register_count")] 42 => (napi_module_register, "napi_module_register"), #[unsafe(export_name = "Bun__process_dlopen_count")] 43 => (process_dlopen, "process_dlopen"), 44 => (postgres_connections, "postgres_connections"), 45 => (s3, "s3"), - 46 => (valkey, "valkey"), + 46 => (valkey, "valkey", core = VALKEY), 47 => (csrf_verify, "csrf_verify"), 48 => (csrf_generate, "csrf_generate"), 49 => (unsupported_uv_function, "unsupported_uv_function"), - 50 => (exited, "exited"), - 51 => (yarn_migration, "yarn_migration"), - 52 => (pnpm_migration, "pnpm_migration"), - 53 => (yaml_parse, "yaml_parse"), + 50 => (exited, "exited", core = EXITED), + 51 => (yarn_migration, "yarn_migration", core = YARN_MIGRATION), + 52 => (pnpm_migration, "pnpm_migration", core = PNPM_MIGRATION), + 53 => (yaml_parse, "yaml_parse", core = YAML_PARSE), 54 => (cpu_profile, "cpu_profile"), #[unsafe(export_name = "Bun__Feature__heap_snapshot")] 55 => (heap_snapshot, "heap_snapshot"), @@ -290,8 +299,7 @@ pub mod features { 57 => (webview_webkit, "webview_webkit"), } - // Zig: `comptime { @export(&napi_module_register, .{ .name = "Bun__napi_module_register_count" }); ... }` - // PORT NOTE: C++ declares these as `extern "C" size_t Bun__...;` and + // C++ declares these as `extern "C" size_t Bun__...;` and // reads/increments the value directly, so the exported symbol must BE the // `usize` storage (not a pointer to it). `AtomicUsize` is `#[repr(C)] // usize`-layout-compatible. Handled via `#[unsafe(export_name = "...")]` @@ -300,13 +308,11 @@ pub mod features { // attached to the single definition. } -// Re-exports to mirror Zig's `Features.packedFeatures()` etc. at module scope. pub use features::{ Formatter as FeaturesFormatter, PACKED_FEATURES_LIST, PackedFeatures, packed_features, }; -/// Zig: `pub fn validateFeatureName(name: []const u8) void` (comptime-only). -/// In Rust this is enforced at the macro definition site; kept as a `const fn` +/// Enforced at the macro definition site; kept as a `const fn` /// for documentation / debug assertions. pub(crate) const fn validate_feature_name(name: &[u8]) -> bool { if name.len() > 64 { @@ -336,11 +342,6 @@ pub enum EventName { http_build, } -// Zig: `var random: std.rand.DefaultPrng = undefined;` -// PORT NOTE: declared but never read in analytics.zig — dead code. Dropped -// rather than gated; if a future schema-encode path needs a PRNG, seed one -// locally (PORTING.md §Concurrency: OnceLock<...>, no `static mut`). - const PLATFORM_ARCH: analytics::Architecture = { #[cfg(target_arch = "aarch64")] { @@ -409,11 +410,8 @@ pub mod generate_header { // Linux / Android // ────────────────────────────────────────────────────────────────── - // Zig: `pub var linux_os_name: std.c.utsname = undefined;` - // PORT NOTE: Zig's `Environment.isLinux` is true on Android (it checks - // the kernel, not the libc target), so all Linux-gated items below are - // `any(linux, android)` — `for_linux()` itself branches on Android. - // The cached `utsname` itself now lives in T1 at + // All Linux-gated items below are `any(linux, android)` — + // `for_linux()` itself branches on Android. The cached `utsname` itself now lives in T1 at // `bun_core::ffi::cached_uname()` so `bun_sys` feature probes share the // same single `uname(2)` syscall. @@ -512,7 +510,6 @@ pub mod generate_header { } #[cfg(not(any(target_os = "linux", target_os = "android")))] pub fn kernel_version() -> semver::Version { - // Zig: @compileError("This function is only implemented on Linux") unreachable!("kernel_version() is only implemented on Linux"); } @@ -578,9 +575,6 @@ pub mod generate_header { // FreeBSD // ────────────────────────────────────────────────────────────────── - // Zig std's `std.c.utsname` has no FreeBSD branch; use translate-c's. - // PORT NOTE: Rust's `libc` crate does provide `utsname`/`uname` on - // FreeBSD, so the translate-c indirection is unnecessary here. #[cfg(target_os = "freebsd")] fn for_freebsd() -> analytics::Platform { let name = bun_core::ffi::cached_uname(); @@ -597,5 +591,3 @@ pub use generate_header as GenerateHeader; pub mod schema; pub use schema::{BufReader, Reader, SchemaInt}; - -// ported from: src/analytics/analytics.zig diff --git a/src/analytics/schema.rs b/src/analytics/schema.rs index aa521e7e7d3..62d6be21d8d 100644 --- a/src/analytics/schema.rs +++ b/src/analytics/schema.rs @@ -1,6 +1,6 @@ // GENERATED: re-run the analytics schema generator (peechy) with .rs output -// source: src/analytics/schema.zig -// TODO(port): regenerate remaining analytics::* types for Rust +// Hand-ported subset — the remaining analytics::* types are unused at runtime +// and come back with the next peechy regen (see the `analytics` mod below). use bun_core::Error; @@ -8,68 +8,54 @@ use bun_core::Error; // Reader / Writer // ────────────────────────────────────────────────────────────────────────── // -// Zig's peechy codec exposes a concrete `Reader` struct and a comptime-generic -// `Writer(WritableStream)` struct, but every generated `decode`/`encode` takes -// `reader: anytype` / `writer: anytype` — i.e. structural duck typing. Per -// PORTING.md §Comptime reflection, `anytype` → trait bound: the *protocol* is -// the trait, and the Zig `Reader` struct is one concrete impl (`BufReader` -// below). -// -// Only the primitive-int / byte-slice surface is ported. Zig's -// `readValue(comptime T)` / `writeValue(comptime T, ...)` switch on -// `@typeInfo(T)` to dispatch to enum/packed-struct/`.decode` paths; that -// reflection has no Rust equivalent, so per-type `decode`/`encode` impls call -// the primitive methods directly (which is what the generated schema bodies -// already do). - -/// Zig: `Reader.ReadError = error{EOF}`. -// PORT NOTE: peechy's two error cases (`EOF`, `InvalidValue`) are folded into +// The peechy codec protocol is the `Reader` trait below; `BufReader` is one +// concrete impl. Only the primitive-int / byte-slice surface is implemented; +// per-type `decode`/`encode` impls call the primitive methods directly +// (which is what the generated schema bodies already do). + +// peechy's two error cases (`EOF`, `InvalidValue`) are folded into // the crate-wide `bun_core::Error` so downstream `decode` signatures stay // `Result<_, bun_core::Error>` without an extra `From` hop. -pub(crate) const EOF: Error = Error::TODO; // TODO(port): Error::from_name("EOF") once name→code table lands +// (`Error::from_name` interns at runtime, so this is a fn, not a const.) +#[inline] +pub(crate) fn eof() -> Error { + bun_core::err!("EOF") +} /// Primitive integers encodable in the peechy wire format (native-endian raw -/// bytes). Zig handled this via `comptime T` + `std.mem.readIntSliceNative` / -/// `std.mem.asBytes`; Rust needs an explicit trait bound. +/// bytes). pub use bun_core::NativeEndianInt as SchemaInt; /// Duck-typed reader protocol for peechy `decode` impls. -/// -/// Zig: `fn decode(reader: anytype) anyerror!T` — the `anytype` becomes a -/// `R: Reader` bound on the Rust side. pub trait Reader { - /// Zig: `fn read(this, count: usize) ![]u8` — borrow `count` bytes, - /// advancing the cursor. Errors with `EOF` if fewer than `count` remain. + /// Borrow `count` bytes, advancing the cursor. Errors with `EOF` if + /// fewer than `count` remain. fn read(&mut self, count: usize) -> Result<&[u8], Error>; - /// Zig: `readByte` #[inline] fn read_byte(&mut self) -> Result { Ok(self.read(1)?[0]) } - /// Zig: `readBool` #[inline] fn read_bool(&mut self) -> Result { Ok(self.read_byte()? > 0) } - /// Zig: `readInt(comptime T)` — `std.mem.readIntSliceNative`. #[inline] fn read_int(&mut self) -> Result { let b = self.read(T::SIZE)?; Ok(T::from_ne_slice(b)) } - /// Zig: `readValue(comptime T)` for the primitive-int arm. Struct/enum - /// arms are expressed as per-type `decode(reader)` fns instead (no - /// `@typeInfo` in Rust). + /// Primitive-int read; struct/enum cases are expressed as per-type + /// `decode(reader)` fns instead. #[inline] fn read_value(&mut self) -> Result { self.read_int::() } - /// Zig: `readByteArray` — `u32` length prefix + raw bytes. + /// `u32` length prefix + raw bytes. #[inline] fn read_byte_array(&mut self) -> Result<&[u8], Error> { let len = self.read_int::()? as usize; @@ -80,17 +66,12 @@ pub trait Reader { } } -// peechy `Writer` lives in `bun_options_types::schema::Writer` (the canonical -// `Vec`-backed struct port of `schema.zig:169 fn Writer(WritableStream)`). -// This crate keeps only the read side; encode users depend on options_types -// directly. +// peechy `Writer` lives in `bun_options_types::schema::Writer`. This crate +// keeps only the read side; encode users depend on options_types directly. -/// Concrete buffer-backed reader — direct port of Zig's `pub const Reader = struct`. +/// Concrete buffer-backed reader. /// -/// PORT NOTE: the Zig struct also carries `std.mem.Allocator param` for -/// `readArray`'s nested-slice case; per PORTING.md §Allocators (non-AST crate) -/// the allocator param is dropped — callers that need owned sub-arrays -/// allocate at the call site. +/// Callers that need owned sub-arrays allocate at the call site. pub struct BufReader<'a> { pub buf: &'a [u8], pub remain: &'a [u8], @@ -107,7 +88,7 @@ impl<'a> Reader for BufReader<'a> { fn read(&mut self, count: usize) -> Result<&[u8], Error> { let read_count = core::cmp::min(count, self.remain.len()); if read_count < count { - return Err(EOF); + return Err(eof()); } let (slice, rest) = self.remain.split_at(read_count); self.remain = rest; @@ -122,10 +103,8 @@ impl<'a> Reader for BufReader<'a> { // the schema (EventKind, EventListHeader, …) are unused at runtime today and // will be filled in by the peechy regen. pub mod analytics { - /// Zig: `pub const OperatingSystem = enum(u8) { _none, linux, macos, windows, wsl, android, freebsd, _ }` - // PORT NOTE: Zig's open enum (`_`) is dropped — Rust enums are closed; the - // schema decoder is the only producer of unknown discriminants and it is - // not yet ported. + // Closed enum: the schema decoder is the only producer of unknown + // discriminants and it is not yet implemented. #[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum OperatingSystem { @@ -144,7 +123,6 @@ pub mod analytics { Freebsd, } - /// Zig: `pub const Architecture = enum(u8) { _none, x64, arm, _ }` #[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum Architecture { @@ -155,7 +133,6 @@ pub mod analytics { Arm, } - /// Zig: `pub const Platform = struct { os, arch, version: []const u8 }` #[derive(Copy, Clone)] pub struct Platform { /// os @@ -166,5 +143,3 @@ pub mod analytics { pub version: &'static [u8], } } - -// ported from: src/analytics/schema.zig diff --git a/src/api/lib.rs b/src/api/lib.rs index de79a80e96c..b65da42ec2a 100644 --- a/src/api/lib.rs +++ b/src/api/lib.rs @@ -2,14 +2,13 @@ #![warn(unused_must_use)] //! `bun.schema.api` namespace. //! -//! Ground truth: `src/options_types/schema.zig` (the `pub const api = struct {…}` -//! block — generated from `src/api/schema.peechy`). The full peechy → `.rs` -//! emitter is not landed yet; this crate hand-ports the slice of the schema -//! that downstream crates name today (`bun_ini`, `bun_install`, `bun_runtime` +//! Ground truth: `src/api/schema.peechy`. The full peechy → `.rs` emitter is +//! not landed yet; this crate hand-writes the slice of the schema that +//! downstream crates name today (`bun_ini`, `bun_install`, `bun_runtime` //! bunfig parser) so they can un-gate against real field shapes. //! //! LAYERING: the actual data shapes (`NpmRegistry`, `NpmRegistryMap`, `Ca`, -//! `BunInstall`) were originally hand-ported in two places — here *and* in +//! `BunInstall`) were originally hand-written in two places — here *and* in //! `bun_options_types::schema::api`. Downstream crates ended up holding values //! of one and passing them to functions typed against the other (e.g. //! `bun_options_types::context::install` vs. `bun_ini::load_npmrc_config`), @@ -34,20 +33,18 @@ pub use bun_options_types::schema::api::{ // npm_registry — module path for the nested `NpmRegistry::Parser` // ────────────────────────────────────────────────────────────────────────── -/// Zig nests `pub const Parser = struct {…}` inside `NpmRegistry`. Rust can't -/// nest a type inside a struct, so it lives in a sibling module and the -/// canonical path becomes `bun_api::npm_registry::Parser`. +/// `Parser` lives in a sibling module of `NpmRegistry`; the canonical path +/// is `bun_api::npm_registry::Parser`. pub mod npm_registry { use bun_url::URL; pub use super::NpmRegistry; - // PORT NOTE: `Parser` stays generic over `L` (Log) / `S` (Source) so this - // leaf schema crate doesn't need to name `bun_logger`. The lone live body - // (`parse_registry_url_string_impl`) doesn't touch log/source — only the - // not-yet-ported `parse_registry_object` / `parse_registry` paths do, and - // those need `js_ast::Expr` so they belong upstream in the bunfig parser - // anyway. + // `Parser` stays generic over `L` (Log) / `S` (Source) so this leaf + // schema crate doesn't need to name `bun_logger`. The lone live body + // (`parse_registry_url_string_impl`) doesn't touch log/source — only + // `parse_registry_object` / `parse_registry` would, and those need + // `js_ast::Expr` so they belong upstream in the bunfig parser anyway. pub struct Parser<'a, L, S> { pub log: &'a mut L, pub source: &'a S, @@ -79,5 +76,3 @@ pub mod npm_registry { } } } - -// ported from: src/options_types/schema.zig diff --git a/src/ast/Cargo.toml b/src/ast/Cargo.toml index 8ef63ef5259..279f490bb4f 100644 --- a/src/ast/Cargo.toml +++ b/src/ast/Cargo.toml @@ -29,4 +29,5 @@ bun_ptr.workspace = true bun_paths.workspace = true bun_collections.workspace = true bun_sys.workspace = true +bun_perf.workspace = true bun_wyhash.workspace = true diff --git a/src/ast/ast_memory_allocator.rs b/src/ast/ast_memory_allocator.rs index 536eb987d20..7a8f63c25c7 100644 --- a/src/ast/ast_memory_allocator.rs +++ b/src/ast/ast_memory_allocator.rs @@ -7,24 +7,19 @@ use bun_alloc::ast_alloc::{self, AstAllocState}; use crate::expr; use crate::stmt; -// PERF(port): Zig used `std.heap.StackFallbackAllocator(@min(8192, std.heap.page_size_min))` -// — a small inline stack buffer with heap fallback. `bun_alloc::Arena` -// (`MimallocArena`) has no stack buffer; instead the owned arena is recycled +// `bun_alloc::Arena` (`MimallocArena`) has no inline stack buffer with heap +// fallback; instead the owned arena is recycled // per thread via `ARENA_POOL` below so the per-module callers don't pay a fresh // `mi_heap_new` + first-segment page faults every file. (The `AstAlloc` side // *does* have an inline buffer now — see `bun_alloc::ast_alloc::AstAllocState`.) // ── Thread-local arena pool ────────────────────────────────────────────── // -// Zig's `ASTMemoryAllocator` was a `StackFallbackAllocator(8192, fallback)`: -// the 8 KB stack buffer absorbed most per-module AST scratch without touching -// the heap, and the spill went to a long-lived `fallback` arena whose pages -// stayed resident across modules. The Rust port collapsed that to one owned -// `MimallocArena` per `ASTMemoryAllocator`, so a fresh per-module instance -// (`RuntimeTranspilerStore::run`, `Bun.Transpiler.*`, the dev server) paid a -// fresh `mi_heap_new` + first-segment page faults every file, and `enter()`'s -// reset then destroyed-and-recreated that just-created heap before it was even -// used. +// With one owned `MimallocArena` per `ASTMemoryAllocator`, a fresh per-module +// instance (`RuntimeTranspilerStore::run`, `Bun.Transpiler.*`, the dev server) +// would pay a fresh `mi_heap_new` + first-segment page faults every file, and +// `enter()`'s reset would then destroy-and-recreate that just-created heap +// before it was even used. // // Instead, recycle one `MimallocArena` per thread: `Drop` cleans the arena // (`reset()` bulk-frees this module's nodes — leaving it pristine) and parks @@ -51,8 +46,6 @@ fn return_pooled_arena(arena: Arena) { } pub struct ASTMemoryAllocator { - // Zig fields `stack_arena: SFA` + `bump_std.mem.Allocator param` (the vtable into - // the SFA) collapse to a single bump arena. arena: Arena, /// When non-null, allocations route to this caller-owned arena instead of /// `self.arena` and `Drop`/`reset` never destroy or pool anything. Must @@ -229,16 +222,10 @@ impl ASTMemoryAllocator { } pub fn enter(&mut self) -> Scope<'_> { - // Zig: this.stack_allocator = SFA{ .fallback_allocator = arena, .. }; - // this.bump_allocator = this.stack_allocator.get(); - // The Zig spec OVERWRITES the entire SFA on every `enter()` (fresh - // 8 KB stack buffer + rewired fallback to the per-call arena), so any - // bytes bump-allocated by the previous `enter()` are released. The - // Rust port collapsed SFA+fallback into a single internal `Arena` - // owned by `self`, so the equivalent re-init is `arena.reset()` — - // otherwise a thread-local `ASTMemoryAllocator` reused across - // `RuntimeTranspilerStore::run()` calls grows unboundedly (one full - // AST worth of nodes per import). + // `enter()` must release any bytes bump-allocated by the previous + // `enter()`, i.e. `arena.reset()` — otherwise a thread-local + // `ASTMemoryAllocator` reused across `RuntimeTranspilerStore::run()` + // calls grows unboundedly (one full AST worth of nodes per import). // // ...but a *pristine* arena (fresh from `new()` / the thread-local // pool, or just `reset()`) has nothing to discard, so the @@ -265,8 +252,6 @@ impl ASTMemoryAllocator { } pub fn reset(&mut self) { - // Zig rebuilt the SFA against the stored fallback arena; Arena::reset is equivalent. - // PERF(port): was stack-fallback — profile // Skip the `mi_heap_destroy` + `mi_heap_new` when already pristine. if self.arena_dirty { // The AST state's spill pointer targets the arena's heap; null it @@ -352,15 +337,12 @@ impl ASTMemoryAllocator { #[inline] pub fn append(&self, value: T) -> crate::StoreRef { - // Zig: `this.bump_allocator.create(ValueType) catch unreachable; ptr.* = value;` - // bumpalo's `alloc` aborts on OOM, matching `catch unreachable`. + // bumpalo's `alloc` aborts on OOM. // SAFETY: bumpalo never returns null. crate::StoreRef::from_bump(self.arena().alloc(value)) } - /// Zig: `this.stack_allocator.get()` — the `std.mem.Allocator` vtable into - /// the stack-fallback buffer. In the Rust port both `stack_allocator` and - /// `bump_allocator` collapse to the single `Arena`, so this returns it. + /// Returns the single `Arena` backing this allocator. #[inline] pub fn stack_allocator(&self) -> &Arena { self.arena() @@ -461,8 +443,7 @@ impl<'a> Scope<'a> { } } -// Zig callers write `defer ast_scope.exit()` immediately after `enter()`; -// porting that as RAII so `let _scope = alloc.enter();` restores the previous +// RAII: `let _scope = alloc.enter();` restores the previous // `Expr/Stmt.Data.Store.memory_allocator` on every return path. `exit()` is // idempotent (guarded by `entered`), so an explicit `.exit()` followed by Drop // is harmless. @@ -471,5 +452,3 @@ impl<'a> Drop for Scope<'a> { self.exit(); } } - -// ported from: src/js_parser/ast/ASTMemoryAllocator.zig diff --git a/src/ast/ast_result.rs b/src/ast/ast_result.rs index 644b5fd49d6..1441338beed 100644 --- a/src/ast/ast_result.rs +++ b/src/ast/ast_result.rs @@ -1,4 +1,4 @@ -//! `js_parser/ast/Ast.zig` — the parser-output struct. +//! The parser-output struct. //! //! Moved down from `bun_js_parser` so `bun_js_printer` can consume it without //! a `bun_js_parser` dep. The previous blocker (`Target`/`ImportRecord` living @@ -51,9 +51,8 @@ pub struct Ast<'a> { /// they can be manipulated efficiently without a full AST traversal pub import_records: ImportRecordList<'a>, - // `hashbang`/`directive` are `[]const u8` slices into source text (not - // freed in Zig `deinit`). `StoreStr` records them under the same - // lifetime-erased contract as `StoreRef`. + // `hashbang`/`directive` are slices into source text. `StoreStr` records + // them under the same lifetime-erased contract as `StoreRef`. pub hashbang: StoreStr, pub directive: Option, pub parts: PartList<'a>, @@ -76,7 +75,6 @@ pub struct Ast<'a> { pub named_exports: NamedExports, pub export_star_import_records: AstVec, - // arena: std.mem.Allocator, pub top_level_symbols_to_parts: TopLevelSymbolToParts, pub commonjs_named_exports: CommonJSNamedExports, @@ -96,10 +94,6 @@ pub struct Ast<'a> { pub import_meta_ref: Ref, } -// PORT NOTE: Zig field defaults reference named constants (`Ref.None`, `logger.Range.None`, -// `ExportsKind.none`, `Target.browser`) whose equivalence to the Rust types' `Default::default()` -// is unverified across crates, so spell them out here instead of `#[derive(Default)]`. -// // `parts`/`symbols`/`import_records` are now `ArenaVec`s and need an allocator, // so `Default` no longer applies; use `Ast::empty_in(arena)`. impl<'a> Ast<'a> { @@ -186,13 +180,10 @@ impl<'a> Ast<'a> { } } - // Zig `deinit` only freed `parts`, `symbols`, `import_records` via `bun.default_allocator`, - // and was guarded by "Do not call this if it wasn't globally allocated!". - // In Rust those fields own their storage and free on Drop; no explicit body needed. - // TODO(port): Vec Drop semantics must distinguish arena-backed vs heap-backed to - // preserve the Zig conditional-free behavior. Revisit. + // `parts`/`symbols`/`import_records` are `ArenaVec`s (`BabyVec`) whose + // `Drop` deallocates through the allocator each instance was constructed + // with, so arena-vs-heap conditional-free is encoded in the type — no + // explicit body needed. } pub use crate::g::Class; - -// ported from: src/js_parser/ast/Ast.zig diff --git a/src/ast/b.rs b/src/ast/b.rs index f2d42ba501e..68d4316f2ce 100644 --- a/src/ast/b.rs +++ b/src/ast/b.rs @@ -3,7 +3,7 @@ use crate::base::Ref; use crate::binding::Binding; use crate::expr::Expr; use crate::{ExprNodeIndex, flags}; -// Re-exported so callers can spell `js_ast::b::ArrayBinding` (Zig: `B.Array.Item`). +// Re-exported so callers can spell `js_ast::b::ArrayBinding`. pub use crate::ArrayBinding; /// B is for Binding! Bindings are on the left side of variable @@ -27,8 +27,8 @@ pub use crate::ArrayBinding; /// ---------------- /// B.Object /// ``` -// Zig: `union(Binding.Tag)` — tag enum lives on `Binding::Tag`. -// PORT NOTE: arena values are referenced via `StoreRef` (LIFETIMES.tsv: ARENA) +// The tag enum lives on `Binding::Tag`. +// Arena values are referenced via `StoreRef` (LIFETIMES.tsv: ARENA) // rather than a threaded `&'bump mut T`. #[derive(Copy, Clone, bun_core::EnumTag)] #[enum_tag(existing = super::binding::Tag)] @@ -74,24 +74,23 @@ pub struct Property { pub value: Binding, pub default_value: Option, } -// TODO(port): partial defaults — Zig only defaults `flags`/`default_value`; `key`/`value` have none, so no `impl Default`. +// No `impl Default` on purpose: only `flags`/`default_value` have sensible +// defaults; `key`/`value` do not, so every constructor must supply them. pub struct Object { pub properties: crate::StoreSlice, pub is_single_line: bool, } -// Zig: `pub const Property = B.Property;` — inherent associated type alias. -// TODO(port): inherent associated types are unstable; callers use `B::Property` directly. -// TODO(port): partial defaults — Zig only defaults `is_single_line`; `properties` has none, so no `impl Default`. +// No `impl Default` on purpose: only `is_single_line` has a sensible default; +// `properties` does not, so every constructor must supply it. pub struct Array { pub items: crate::StoreSlice, pub has_spread: bool, pub is_single_line: bool, } -// Zig: `pub const Item = ArrayBinding;` — inherent associated type alias. -// TODO(port): inherent associated types are unstable; callers use `ArrayBinding` directly. -// TODO(port): partial defaults — Zig only defaults `has_spread`/`is_single_line`; `items` has none, so no `impl Default`. +// No `impl Default` on purpose: only `has_spread`/`is_single_line` have +// sensible defaults; `items` does not, so every constructor must supply it. #[derive(Default, Copy, Clone)] pub struct Missing {} @@ -127,18 +126,16 @@ impl B { where H: bun_core::Hasher + ?Sized, S: crate::base::SymbolTable + ?Sized, - // PORT NOTE: `symbol_table: anytype` — forwarded to `Ref::get_symbol` and + // `symbol_table: anytype` — forwarded to `Ref::get_symbol` and // `Expr::Data::write_to_hasher`; bound mirrors `Expr::Data::write_to_hasher`. { - // Local mirror of `bun.writeAnyToHasher`. Zig fed anonymous tuples - // through `std.mem.asBytes`, but Rust tuples have *uninitialized* - // padding bytes (e.g. `(Tag /*u8*/, usize)` has 7 on 64-bit), so - // forming a `&[u8]` over them is UB. Instead we feed each scalar - // field individually and bound on `NoUninit` so the compiler proves - // every byte is initialized — same pattern as `expr::Data::write_to_hasher`. - // The hash is only used in-process for React Fast Refresh, so the - // byte-stream change vs. Zig is immaterial (and the old stream was - // nondeterministic anyway). + // Local mirror of `bun.writeAnyToHasher`. Rust tuples have + // *uninitialized* padding bytes (e.g. `(Tag /*u8*/, usize)` has 7 on + // 64-bit), so forming a `&[u8]` over them is UB. Instead we feed each + // scalar field individually and bound on `NoUninit` so the compiler + // proves every byte is initialized — same pattern as + // `expr::Data::write_to_hasher`. The hash is only used in-process for + // React Fast Refresh. #[inline(always)] fn raw(h: &mut H, v: T) { h.update(bun_core::bytes_of(&v)); @@ -186,5 +183,3 @@ impl B { type _BindingTagHost = Binding; pub use crate::g::Class; - -// ported from: src/js_parser/ast/B.zig diff --git a/src/ast/base.rs b/src/ast/base.rs index 400ceeecc92..703747c118d 100644 --- a/src/ast/base.rs +++ b/src/ast/base.rs @@ -5,20 +5,17 @@ use crate::symbol; /// In some parts of Bun, we have many different IDs pointing to different things. /// It's easy for them to get mixed up, so we use this type to make sure we don't. // -// Zig: `packed struct(u32) { value: Int }` — single field fills the whole word, -// so `#[repr(transparent)]` over `u32` is bit-identical. Tuple-struct shape so -// the (many) bundler call sites that wrote `Index(n)` / `.0` keep compiling; -// `.value()` is provided for sites that read the Zig field name. +// `#[repr(transparent)]` over `u32`. Tuple-struct shape so the (many) bundler +// call sites that write `Index(n)` / `.0` keep compiling; `.value()` is +// provided for sites that prefer a named accessor. #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct Index(pub IndexInt); -/// Zig: `pub const Int = u32;` (nested in `Index`) pub type IndexInt = u32; -/// Zig `anytype` → `@truncate` adaptor for [`Index::source`]/[`Index::part`]. -/// Callers pass both `u32` and `usize`; this truncates wider inputs the way -/// Zig's `@as(Int, @truncate(num))` does. +/// Truncating adaptor for [`Index::source`]/[`Index::part`]. +/// Callers pass both `u32` and `usize`; wider inputs are truncated. pub trait IntoIndexInt { fn into_index_int(self) -> IndexInt; } @@ -47,7 +44,6 @@ impl Index { self.0 = val; } - /// Zig field-name accessor (`idx.value`). #[inline] pub const fn value(self) -> IndexInt { self.0 @@ -66,9 +62,8 @@ impl Index { pub const BAKE_SERVER_DATA: Index = Index(1); pub const BAKE_CLIENT_DATA: Index = Index(2); - // Zig: `source(num: anytype) Index { .value = @truncate(num) }` - // `@truncate` → `as` (intentional wrap). `anytype` callers pass both `u32` - // and `usize`; `IntoIndexInt` covers both with truncating semantics. + // Callers pass both `u32` and `usize`; `IntoIndexInt` covers both with + // truncating (intentional wrap) semantics. #[inline] pub fn source(num: impl IntoIndexInt) -> Index { Index(num.into_index_int()) @@ -79,9 +74,7 @@ impl Index { Index(num.into_index_int()) } - // Zig: `init(num: anytype)` — `@intCast` (checked narrow). The `@typeInfo == - // .pointer` auto-deref branch is Zig-only reflection; Rust callers pass by - // value. + // Checked narrowing; callers pass by value. #[inline] pub fn init(num: N) -> Index where @@ -133,15 +126,9 @@ impl Default for Index { /// all inner arrays from all parsed files. pub use crate::{Ref, RefInt, RefTag}; -// Zig: `comptime { bun.assert(None.isEmpty()); }` const _: () = assert!(Ref::NONE.is_empty()); -// ─────────────── getSymbol `anytype` dispatch → trait ─────────────── -// -// Zig switches on `@TypeOf(symbol_table)`: -// *const ArrayList(Symbol) | *ArrayList(Symbol) | []Symbol → index by -// `ref.innerIndex()` (parser: single flat array, source_index ignored) -// *Symbol.Map → `map.get(ref)` (bundler: 2D, both halves used) +// ─────────────── getSymbol dispatch trait ─────────────── // // Different parts of the bundler use different formats of the symbol table. // In the parser you only have one array, and .sourceIndex() is ignored. @@ -174,5 +161,3 @@ impl Ref { symbol_table.get_symbol(self) } } - -// ported from: src/js_parser/ast/base.zig diff --git a/src/ast/binding.rs b/src/ast/binding.rs index 889d8bf546e..700333eb006 100644 --- a/src/ast/binding.rs +++ b/src/ast/binding.rs @@ -11,23 +11,20 @@ use crate::expr::{Data as ExprData, Expr}; use crate::{ExprNodeList, flags}; use crate::{e as E, g as G}; -/// Zig: `Binding.Data` is the `union(Tag)` payload. In the Rust port that -/// union lives at `crate::b::B`; re-export it under the Zig-path name +/// `Binding`'s payload union lives at `crate::b::B`; re-export it as `Data` /// so downstream crates can `use crate::binding::Data`. pub use crate::b::B as Data; -// Zig file-as-struct: top-level fields `loc`, `data` define `Binding`. #[derive(Copy, Clone, Default)] pub struct Binding { pub loc: crate::Loc, pub data: B, } -// Zig: `enum(u5)` — Rust has no u5; use u8 repr (values fit). #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, strum::IntoStaticStr)] pub enum Tag { - // strum serialize = Zig @tagName output (JSON/snapshot compat). + // strum serialize = snake_case tag names (JSON/snapshot compat). #[strum(serialize = "b_identifier")] BIdentifier, #[strum(serialize = "b_array")] @@ -38,16 +35,14 @@ pub enum Tag { BMissing, } -// Zig: `pub var icount: usize = 0;` — mutable global counter, never read. // Debug-only so release doesn't pay a contended `lock xadd` per Binding. #[cfg(debug_assertions)] pub(crate) static ICOUNT: AtomicUsize = AtomicUsize::new(0); // ────────────────────────────────────────────────────────────────────────── -// `init` / `alloc` — Zig switched on `@TypeOf(t)` to pick the `B` variant. -// In Rust the comptime type-switch is a pair of small traits implemented for -// each payload type; `Binding::init` / `Binding::alloc` stay monomorphic -// per call-site like the Zig original. +// `init` / `alloc` — a pair of small traits implemented for each payload +// type pick the `B` variant; `Binding::init` / `Binding::alloc` stay +// monomorphic per call-site. // ────────────────────────────────────────────────────────────────────────── pub trait BindingInit { @@ -128,19 +123,15 @@ impl Binding { } // ────────────────────────────────────────────────────────────────────────── -// ToExpr — Zig: `fn ToExpr(comptime expr_type: type, comptime func_type: anytype) type` -// returns a struct holding `context: *ExprType` + `arena` whose -// `wrapIdentifier` calls the comptime `func_type`. -// -// Rust cannot store `*mut P<'a, const ..>` in a non-generic field nor take a -// fn item as a const generic, so the wrapper is type-erased: `wrap` is a plain -// fn pointer that casts the erased `ctx` back to the concrete `P` instantiation. -// Unlike Zig's struct, the `*ExprType` context is **not** stored — it is -// supplied at call time (`Binding::to_expr(.., ctx, ..)`) so the raw pointer's -// Stacked-Borrows tag is a child of the *live* `&mut P` at the call site rather -// than a stale tag captured during `prepare_for_visit_pass` (which every later -// `&mut self` retag would invalidate). The struct is `Copy` so the recursive -// `to_expr` can pass it by value like Zig's `wrapper: anytype`. +// ToExpr — Rust cannot store `*mut P<'a, const ..>` in a non-generic field +// nor take a fn item as a const generic, so the wrapper is type-erased: +// `wrap` is a plain fn pointer that casts the erased `ctx` back to the +// concrete `P` instantiation. The `*ExprType` context is **not** stored — it +// is supplied at call time (`Binding::to_expr(.., ctx, ..)`) so the raw +// pointer's Stacked-Borrows tag is a child of the *live* `&mut P` at the call +// site rather than a stale tag captured during `prepare_for_visit_pass` +// (which every later `&mut self` retag would invalidate). The struct is +// `Copy` so the recursive `to_expr` can pass it by value. // ────────────────────────────────────────────────────────────────────────── #[derive(Copy, Clone)] @@ -163,11 +154,10 @@ impl ToExprWrapper { } } - /// Zig: `Context.init(context)` — captures `*ExprType` and its arena. /// `ExprType` is erased to `c_void`; callers (P.rs) supply a trampoline /// closure that casts back to `*mut P<..>` and dispatches to /// `P::wrap_identifier_{namespace,hoisting}`. Non-capturing closures - /// coerce to fn pointers, so this stays zero-cost like Zig's comptime fn. + /// coerce to fn pointers, so this stays zero-cost. /// The `*mut P` itself is passed per-call via `Binding::to_expr`. #[inline] pub fn new(arena: &Arena, wrap: fn(*mut core::ffi::c_void, crate::Loc, Ref) -> Expr) -> Self { @@ -193,15 +183,12 @@ impl ToExprWrapper { } } -/// Zig: `Binding.ToExpr(expr_type, func_type)` returned a *type*; Rust callers -/// that want the same per-(P, func) nominal type use this alias and construct -/// via `ToExprWrapper::new`. Kept as a type alias (not a generic struct) so -/// `P` can store two of these without threading its own generics through. +/// Alias for `ToExprWrapper`; construct via `ToExprWrapper::new`. Kept as a +/// type alias (not a generic struct) so `P` can store two of these without +/// threading its own generics through. pub type ToExpr = ToExprWrapper; impl Binding { - /// Zig: `pub fn toExpr(binding: *const Binding, wrapper: anytype) Expr`. - /// /// `ctx` is the type-erased `*mut P<..>` derived from the *caller's live* /// `&mut P` (e.g. `core::ptr::addr_of_mut!(*p) as *mut c_void`). Threading /// it per-call keeps the raw pointer's provenance under the active Unique @@ -290,5 +277,3 @@ impl Binding { } } } - -// ported from: src/js_parser/ast/Binding.zig diff --git a/src/ast/char_freq.rs b/src/ast/char_freq.rs index e329705ea8e..dca51831233 100644 --- a/src/ast/char_freq.rs +++ b/src/ast/char_freq.rs @@ -7,18 +7,13 @@ pub(crate) struct CharAndCount { pub index: usize, } -// PORT NOTE: Zig `CharAndCount.Array` was an associated type alias; inherent -// associated types are unstable in Rust, so it's a free alias here. pub(crate) type CharAndCountArray = [CharAndCount; CHAR_FREQ_COUNT]; -// PERF(port): Zig used `@Vector(CHAR_FREQ_COUNT, i32)` for SIMD adds — profile +// PERF: candidate for SIMD adds — profile type Buffer = [i32; CHAR_FREQ_COUNT]; #[derive(Copy, Clone)] pub struct CharFreq { - // PORT NOTE: Zig field was `align(1)` (unaligned i32 array). Rust gives natural - // alignment; if the packed layout was load-bearing for an FFI/serialized struct, - // revisit. pub freqs: Buffer, } @@ -47,8 +42,7 @@ impl CharFreq { } pub fn include(&mut self, other: &CharFreq) { - // https://zig.godbolt.org/z/Mq8eK6K9s - // PERF(port): Zig used @Vector SIMD add — profile + // PERF: candidate for SIMD add — profile for (l, r) in self.freqs.iter_mut().zip(other.freqs.iter()) { *l += *r; } @@ -74,16 +68,14 @@ impl CharFreq { }; } - // std.sort.pdq → Rust's sort_unstable_by (pattern-defeating quicksort). - // PORT NOTE: do NOT route through `CharAndCount::less_than` and map + // Do NOT route through `CharAndCount::less_than` and map // false→Greater — that comparator never returns `Equal`, which // violates `sort_unstable_by`'s total-order contract (Rust 1.81+ // is permitted to panic on inconsistent comparators). `index` is // unique so equality is unreachable in practice, but keep the // comparator well-formed regardless. arr.sort_unstable_by(|a, b| { - // descending by count, then ascending by (index, char) — - // matches CharFreq.zig:12 `CharAndCount.lessThan`. + // descending by count, then ascending by (index, char). b.count .cmp(&a.count) .then_with(|| a.index.cmp(&b.index)) @@ -108,10 +100,8 @@ impl CharFreq { for item in array { if item.char < b'0' || item.char > b'9' { minifier.head.push(item.char); - // PERF(port): was `catch unreachable` (assume_capacity) } minifier.tail.push(item.char); - // PERF(port): was `catch unreachable` (assume_capacity) } minifier @@ -119,10 +109,7 @@ impl CharFreq { } fn scan_big(out: &mut Buffer, text: &[u8], delta: i32) { - // https://zig.godbolt.org/z/P5dPojWGK - // PORT NOTE: Zig copied `out.*` into a stack local and wrote back via `defer` to - // avoid unaligned (`align(1)`) loads in the hot loop. We operate on `out` directly; - // the field is naturally aligned in Rust. + // The field is naturally aligned, so operate on `out` directly. let mut deltas: [i32; 256] = [0; 256]; debug_assert!(text.len() >= SCAN_BIG_CHUNK_SIZE); @@ -131,7 +118,7 @@ fn scan_big(out: &mut Buffer, text: &[u8], delta: i32) { let (chunks, remain) = text.split_at(unrolled); for chunk in chunks.chunks_exact(SCAN_BIG_CHUNK_SIZE) { - // PERF(port): Zig used `inline for` to unroll 32 iterations — profile + // PERF: candidate for unrolling — profile for i in 0..SCAN_BIG_CHUNK_SIZE { deltas[chunk[i] as usize] += delta; } @@ -141,20 +128,11 @@ fn scan_big(out: &mut Buffer, text: &[u8], delta: i32) { deltas[c as usize] += delta; } - // PORT NOTE — INTENTIONAL SPEC DIVERGENCE: CharFreq.zig:64 writes - // `freqs[0..26].* = deltas[...]`, which *overwrites* the accumulator - // (`var freqs = out.*` is dead). That is an upstream bug: every ≥32-byte - // scan discards all prior counts, so the result is last-big-scan-wins - // rather than the histogram the NameMinifier expects. Zig's output is - // stable only because its StringHashMap iteration order is deterministic, - // so the *same* symbol name overwrites last on every run. The Rust - // `scope.members` map is RandomState-seeded, so a faithful overwrite port - // is nondeterministic (the observed `OV`/`OU` flap on three.js), and even - // a deterministic-iteration port wouldn't reproduce Zig's specific hash - // order. We accumulate (`+=`) instead — the algorithm's intent — which - // makes the freq table both correct and run-to-run stable. Minified - // output therefore differs from Zig by design here (three.js: 2 bytes - // smaller); byte-identical-vs-Zig is not a goal for this function. + // Accumulate (`+=`) — overwriting the accumulator instead would make + // every ≥32-byte scan discard all prior counts (last-big-scan-wins), and + // since `scope.members` is a RandomState-seeded map the result would be + // nondeterministic (an observed `OV`/`OU` flap on three.js). Accumulating + // keeps the freq table both correct and run-to-run stable. for i in 0..26 { out[i] += deltas[b'a' as usize + i]; } @@ -169,16 +147,15 @@ fn scan_big(out: &mut Buffer, text: &[u8], delta: i32) { } fn scan_small(out: &mut Buffer, text: &[u8], delta: i32) { - // PORT NOTE: Zig copied `out.*` into a stack local to avoid unaligned (`align(1)`) - // RMWs in the loop. The Rust field is naturally aligned, so operate on `out` directly + // The field is naturally aligned, so operate on `out` directly // (same treatment as `scan_big`). for &c in text { // Indices follow `NameMinifier::DEFAULT_TAIL` order // (`a-zA-Z0-9_$` → 0..63), matching `scan_big` which writes digits - // at `out[52 + i]`. The Zig original (`char_freq.zig:79`) used `53`, - // which shifted `'0'` to 53 and made `'9'` collide with `'_'` at 62, - // leaving slot 52 cold for `<32`-byte inputs and slightly skewing - // minified-name ranking when digits/underscores appear. + // at `out[52 + i]`. Starting digits at 53 instead would shift `'0'` + // to 53 and make `'9'` collide with `'_'` at 62, leaving slot 52 cold + // for `<32`-byte inputs and slightly skewing minified-name ranking + // when digits/underscores appear. let i: usize = match c { b'a'..=b'z' => c as usize - b'a' as usize, b'A'..=b'Z' => c as usize - (b'A' as usize - 26), @@ -190,5 +167,3 @@ fn scan_small(out: &mut Buffer, text: &[u8], delta: i32) { out[i] += delta; } } - -// ported from: src/js_parser/ast/CharFreq.zig diff --git a/src/ast/e.rs b/src/ast/e.rs index d1d101d4fef..8b3326904a8 100644 --- a/src/ast/e.rs +++ b/src/ast/e.rs @@ -1,6 +1,4 @@ //! E — expression node payloads for the JS AST. -//! -//! Port of `src/js_parser/ast/E.zig`. use core::cmp::Ordering; use core::fmt; @@ -16,7 +14,6 @@ use bun_core::strings; use crate::{Expr, ExprNodeIndex, ExprNodeList, G, OptionalChain, Ref, StoreRef}; use bun_alloc::ArenaVecExt as _; -// In Zig: `const string = []const u8;` // AST string fields are arena-owned (bulk-freed via Store/arena reset; never // individually freed). `StoreStr` is `StoreRef`'s `[u8]` sibling: a thin // lifetime-erased pointer with safe construction (no `transmute`) and @@ -63,9 +60,6 @@ impl Default for Array { } } } -// TODO(port): Array methods call `Vec::init_capacity(bump, n)` -// (signature mismatch: Vec takes only `n`; AST-crate variant with bump -// arena pending) and `Expr::Data::*` deep matches. Un-gate with parser round. // Live subset of `Array` accessors needed by downstream crates. impl Array { pub const EMPTY: Array = Array { @@ -77,7 +71,6 @@ impl Array { close_bracket_loc: crate::Loc::EMPTY, }; - /// Zig: `pub fn push(this: *Array, arena, item) !void`. /// `Vec::append` uses the global arena; `_bump` is kept /// for call-site shape parity and the eventual bump-arena Vec. pub fn push(&mut self, _bump: &Bump, item: Expr) -> Result<(), AllocError> { @@ -98,14 +91,13 @@ impl Array { estimated_count: usize, ) -> Result { // This over-allocates a little but it's fine - // PERF(port): Zig allocated in arena; this Vec uses the global arena. - // `Expr.data` is an enum (validity invariant), so the Zig - // `expandToCapacity` + index-walk pattern would form `&mut [Expr]` + // `Expr.data` is an enum (validity invariant), so an + // expand-to-capacity + index-walk pattern would form `&mut [Expr]` // over invalid bit patterns. Push into reserved capacity instead — // same allocation profile (one upfront `with_capacity`), no uninit. let mut out: ExprNodeList = ExprNodeList::init_capacity(estimated_count + self.items.len_u32() as usize); - // PORT NOTE: reshaped for borrowck — iterate items via index so the &mut + // Reshaped for borrowck — iterate items via index so the &mut // borrow of `out` does not overlap a shared borrow of `self`. let items_len = self.items.len_u32() as usize; for idx in 0..items_len { @@ -134,8 +126,7 @@ impl Array { Ok(out) } - // `pub const toJS = @import("../../js_parser_jsc/expr_jsc.zig").arrayToJS;` — deleted per - // PORTING.md (jsc extension trait lives in `js_parser_jsc` crate). + // `toJS` lives in the `js_parser_jsc` crate's extension trait. /// Assumes each item in the array is a string pub fn alphabetize_strings(&mut self) { @@ -265,11 +256,11 @@ pub enum Special { HotData, /// `import.meta.hot.accept` when HMR is enabled. Truthy. HotAccept, - /// Converted from `hot_accept` in P.zig's handleImportMetaHotAcceptCall - /// when passed strings. Printed as `hmr.acceptSpecifiers` + /// Converted from `hot_accept` by the parser's import.meta.hot.accept + /// handling when passed strings. Printed as `hmr.acceptSpecifiers` HotAcceptVisited, - /// Prints the resolved specifier string for an import record. - /// Zig: `resolved_specifier_string: ImportRecord.Index` (a `u32`). + /// Prints the resolved specifier string for an import record + /// (an `ImportRecord` index). ResolvedSpecifierString(u32), } @@ -315,7 +306,7 @@ impl Call { } } -#[repr(u8)] // Zig: enum(u2) — Rust has no u2, use u8 +#[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, Default)] pub enum CallUnwrap { #[default] @@ -327,7 +318,8 @@ pub enum CallUnwrap { pub struct Dot { // target is Node pub target: ExprNodeIndex, - // TODO(port): arena-owned slice + // Arena-owned slice (`StoreStr`: lifetime-erased arena ownership, bulk-freed + // at `Store::reset()` — see `nodes.rs` StoreStr docs). pub name: Str, pub name_loc: crate::Loc, pub optional_chain: Option, @@ -355,9 +347,7 @@ impl Default for Dot { } impl Dot { pub fn has_same_flags_as(&self, b: &Dot) -> bool { - // TODO(port): Zig refers to `a.is_direct_eval` which does not exist on Dot; - // mirroring the (likely buggy) Zig literally would not compile. Preserving - // the three fields that DO exist; revisit. + // Compare the three flag fields that exist on Dot. self.optional_chain == b.optional_chain && self.can_be_removed_if_unused == b.can_be_removed_if_unused && self.call_can_be_unwrapped_if_unused == b.call_can_be_unwrapped_if_unused @@ -385,7 +375,6 @@ pub struct Arrow { pub prefer_expr: bool, } impl Arrow { - // Zig `pub const noop_return_undefined: Arrow = .{ .body = .{ .stmts = &.{} } };` pub const NOOP_RETURN_UNDEFINED: Arrow = Arrow { args: crate::StoreSlice::EMPTY, body: G::FnBody { @@ -419,9 +408,8 @@ pub struct Function { /// 8-byte identifier expression payload. The three side-effect flags are packed /// into `Ref`'s user-bit lane (bits 28..31, masked out of `Ref` identity) so /// this — the most common `expr::Data` variant — fits in a single word, which -/// is what pulls `expr::Data` down to 16 bytes / `Expr` to 24. The Zig layout -/// stores them as discrete bools (16B with padding); the Rust port exploits -/// `noalias` + smaller nodes for the structural perf win. +/// is what pulls `expr::Data` down to 16 bytes / `Expr` to 24 (vs. 16B with +/// padding for discrete bools). /// /// `ref_` remains a public field so the ~100 existing `id.ref_` / /// `Identifier { ref_, ..Default::default() }` sites stay untouched; flag @@ -429,8 +417,8 @@ pub struct Function { /// /// **Hazard:** assigning a fresh `Ref` to `ref_` *clears the flags*. This is /// fine for `visit_expr`'s `e_identifier` (sets `ref_` first then re-derives -/// the flags), but any port of Zig `id.ref = new_ref` that expects the -/// surrounding bool fields to survive must instead write +/// the flags), but any `id.ref_ = new_ref` site that expects the +/// flags to survive must instead write /// `id.ref_ = new_ref.with_user_bits_from(id.ref_)` — see `handle_identifier`. #[derive(Clone, Copy)] pub struct Identifier { @@ -485,8 +473,7 @@ impl Identifier { self.ref_.set_user_bit(2, v); } - // Builder-style — replaces the Zig `.{ .ref = r, .can_be_removed = true }` - // struct-init pattern at the handful of sites that set flags up front. + // Builder-style setters for the handful of sites that set flags up front. #[inline] pub const fn with_must_keep_due_to_with_stmt(self, v: bool) -> Self { Self { @@ -687,8 +674,7 @@ pub enum JSXSpecialProp { Any, } impl JSXSpecialProp { - // PERF(port): Zig used `ComptimeStringMap` (length-prefix lookup, all - // resolved at comptime). A `phf::Map` here would compute a full SipHash + + // PERF: a `phf::Map` here would compute a full SipHash + // index + slice compare on every JSX prop name even though the // overwhelming majority of inputs (`className`, `onClick`, `style`, ...) // miss. With only 4 keys at 3 distinct lengths, a length-gated `match` @@ -760,7 +746,6 @@ impl Number { })); } - // std.fmt.allocPrint(arena, "{d}", .{@as(i32, @intCast(int_value))}) catch return null // i32 fits in 11 bytes ("-2147483648"); format on stack then bump-copy. let mut stack = [0u8; 16]; let Ok(s) = bun_core::fmt::buf_print(&mut stack, format_args!("{}", int_value as i32)) @@ -816,7 +801,6 @@ impl Number { } pub fn to(self) -> T { - // @as(T, @intFromFloat(@min(@max(@trunc(self.value), 0), comptime @min(floatMax(f64), maxInt(T))))) let clamped = self.value.trunc().max(0.0).min(T::MAX_AS_F64); T::from_f64(clamped) } @@ -824,7 +808,7 @@ impl Number { // `toJS` alias deleted — lives in `js_parser_jsc` extension trait. } -/// Helper trait for `Number::to()` — replaces Zig's `comptime T: type` param. +/// Helper trait for `Number::to()`. pub trait NumberCast: Copy { const MAX_AS_F64: f64; fn from_f64(v: f64) -> Self; @@ -843,7 +827,8 @@ macro_rules! impl_number_cast { impl_number_cast!(u16, u32, u64, usize); pub struct BigInt { - // TODO(port): arena-owned slice + // Arena-owned slice (`StoreStr`: lifetime-erased arena ownership, bulk-freed + // at `Store::reset()` — see `nodes.rs` StoreStr docs). pub value: Str, } impl BigInt { @@ -877,8 +862,7 @@ impl Default for Object { /// used in TOML parser to merge properties. /// /// Node types are lifetime-free, so `next` is a raw `*mut Rope` -/// into the bump arena (Zig: `next: ?*Rope`). Segments are bulk-freed at -/// arena reset. +/// into the bump arena. Segments are bulk-freed at arena reset. pub struct Rope { pub head: Expr, pub next: *mut Rope, @@ -911,7 +895,7 @@ impl Rope { #[inline] pub fn next_ref<'a>(&self) -> Option<&'a Rope> { // SAFETY: `next` is either null or a bump-arena allocation valid until - // arena reset (Zig: `?*Rope`). Read-only borrow; no `&mut` alias is + // arena reset. Read-only borrow; no `&mut` alias is // outstanding at any caller (the chain is fully built before walking). unsafe { self.next.cast_const().as_ref() } } @@ -1012,7 +996,7 @@ impl Object { } /// Walks `rope` segments, creating nested objects as needed, and returns - /// the leaf `E.Object` expression (Zig: `getOrPutObject`). + /// the leaf `E.Object` expression. pub fn get_or_put_object(&mut self, rope: &Rope, _bump: &Bump) -> Result { let head_key = match rope.head.data.e_string() { Some(s) => s.data, @@ -1082,8 +1066,7 @@ impl Object { if self.has_property(&head_key) { return Err(SetError::Clobber); } - // Zig takes `*const Object` here and mutates through Vec's interior pointer; - // in Rust we require `&mut self` so the borrow checker tracks the write. + // `&mut self` so the borrow checker tracks the write. VecExt::append( &mut self.properties, G::Property { @@ -1303,8 +1286,7 @@ fn package_json_sort_is_less_than(lhs: &G::Property, rhs: &G::Property) -> Order match lhs_key_size.cmp(&rhs_key_size) { Ordering::Equal => { - // PORT NOTE: Zig `cmpStringsAsc` is `std.mem.order(u8, a, b) == .lt`; lifted to - // a full `Ordering` so this is usable with `sort_by`. + // Full `Ordering` so this is usable with `sort_by`. let a = lhs .key .as_ref() @@ -1356,7 +1338,8 @@ pub struct EString { // A version of this where `utf8` and `value` are stored in a packed union, with len as a single u32 was attempted. // It did not improve benchmarks. Neither did converting this from a heap-allocated type to a stack-allocated type. // TODO: change this to *const anyopaque and change all uses to either .slice8() or .slice16() - // TODO(port): arena-owned slice + // Arena-owned slice (`StoreStr`: lifetime-erased arena ownership, bulk-freed + // at `Store::reset()` — see `nodes.rs` StoreStr docs). pub data: Str, pub prefer_template: bool, @@ -1368,7 +1351,7 @@ pub struct EString { pub rope_len: u32, pub is_utf16: bool, } -// Export under the Zig name `String` as well; `EString` avoids colliding with bun_core::String. +// Also exported as `String`; `EString` avoids colliding with bun_core::String. pub use EString as String; impl Default for EString { @@ -1438,9 +1421,9 @@ impl EString { } } /// Construct from a UTF-16 slice (arena-owned). The `data` slice's `.len()` - /// stores the **u16 element count** (not byte count) — Zig: - /// `@ptrCast(value.ptr)[0..value.len]`. `slice16()` and friends rely on - /// this. The pointer is reinterpreted to `*const u8` for storage only. + /// stores the **u16 element count** (not byte count); `slice16()` and + /// friends rely on this. The pointer is reinterpreted to `*const u8` for + /// storage only. pub fn init_utf16(data: &[u16]) -> Self { // `Str::new` only records `(ptr, len)`; we want the original `*const u16` // (reinterpreted as bytes) and the **u16 element count**. Safe-cast the @@ -1462,8 +1445,8 @@ impl EString { if strings::first_non_ascii(utf8).is_none() { Self::init(utf8) } else { - // PERF(port): Zig allocated directly in arena; here we transcode to a - // heap Vec then copy into the bump arena — profile. + // PERF: transcodes to a heap Vec then copies into the bump + // arena — profile. let utf16 = strings::to_utf16_alloc_for_real(utf8, false, false).expect("unreachable"); // fail_if_invalid=false → never errors let arena_slice: &mut [u16] = bump.alloc_slice_copy(&utf16); Self::init_utf16(arena_slice) @@ -1505,13 +1488,13 @@ impl EString { self.len() > 0 } - /// Zig `slice8()` alias used by some downstream callers as `.utf8()`. + /// Alias for `slice8()` used by some downstream callers. #[inline] pub fn utf8(&self) -> &[u8] { self.slice8() } - /// Zig: `slice(arena)` — flatten any rope and return UTF-8 bytes. + /// Flatten any rope and return UTF-8 bytes. /// Resolves the rope into the bump arena, then transcodes if UTF-16. pub fn slice<'b>(&mut self, bump: &'b Bump) -> &'b [u8] { self.resolve_rope_if_needed(bump); @@ -1569,13 +1552,12 @@ impl EString { self.next = None; } - /// Zig `string(arena)` — return UTF-8 bytes, transcoding if UTF-16. - /// The transcode allocates via the global arena then copies into - /// `bump` (Zig used the passed arena directly). + /// Return UTF-8 bytes, transcoding if UTF-16. + /// The transcode allocates via the global arena then copies into `bump`. pub fn string<'b>(&self, bump: &'b Bump) -> Result<&'b [u8], AllocError> { if self.is_utf8() { - // `self.data` is arena-owned with the same lifetime as `bump` - // (Zig invariant); StoreStr re-borrows under that contract. + // `self.data` is arena-owned with the same lifetime as `bump`; + // StoreStr re-borrows under that contract. Ok(self.data.slice()) } else { let v = strings::to_utf8_alloc(self.slice16()); @@ -1664,7 +1646,7 @@ impl EString { Some(self.slice16().len() as u32) } - // Zig `eql(comptime _t: type, other: anytype)` — split by operand type. + // `eql`, split by operand type. pub fn eql_string(&self, other: &EString) -> bool { if self.is_utf8() { if other.is_utf8() { @@ -1689,8 +1671,7 @@ impl EString { /// Shallow field-wise copy. `EString` is structurally `Copy` (slice ref + /// `Option` rope links + scalars) but does not derive it to keep - /// rope-ownership intent explicit; Zig sites that did `.* = other.*` use - /// this instead. + /// rope-ownership intent explicit; use this for field-wise copies. #[inline] pub fn shallow_clone(&self) -> EString { EString { @@ -1714,7 +1695,7 @@ impl EString { } } - /// Zig `E.String.push` — link `other` onto this string's rope tail. + /// Link `other` onto this string's rope tail. /// /// `other` MUST be Store/arena-allocated (callers pass /// `Expr::init(EString, ...).data.e_string_mut()` or a freshly @@ -1732,8 +1713,7 @@ impl EString { self.rope_len += other.rope_len; // Caller contract — `other` lives in the AST Store/arena and outlives - // the next reset; capturing its address as a `StoreRef` is the Zig - // `*E.String` semantics. + // the next reset, so capturing its address as a `StoreRef` is sound. let other_ref = StoreRef::from_bump(other); if self.next.is_none() { self.next = Some(other_ref); @@ -1784,8 +1764,7 @@ fn array_sorter_is_less_than(lhs: &Expr, rhs: &Expr) -> Ordering { impl EString { pub fn string_z<'b>(&self, bump: &'b Bump) -> Result<&'b bun_core::ZStr, AllocError> { - // Zig: `if (self.isUTF8()) self.data else strings.toUTF8AllocZ(...)`, NUL-terminated. - // Port: copy into the bump arena with a trailing NUL and wrap as `ZStr`. + // Copy into the bump arena with a trailing NUL and wrap as `ZStr`. let bytes: &[u8] = if self.is_utf8() { &self.data } else { @@ -1851,7 +1830,7 @@ pub struct TemplatePart { pub struct Template { pub tag: Option, - /// Arena-owned mutable slice (Zig: `[]TemplatePart`). Stored as a + /// Arena-owned mutable slice. Stored as a /// `StoreSlice` so writers (`substitute_single_use_symbol_in_expr`, the /// visit pass, `foldStringAddition`) retain mutable provenance. Use /// `parts()` / `parts_mut()` for ergonomic access; never null. @@ -1891,7 +1870,7 @@ impl TemplateContents { } impl TemplateContents { - /// Field-wise copy (Zig: `var part = part.*`). `EString` is structurally + /// Field-wise copy. `EString` is structurally /// `Copy` but does not derive it; use `shallow_clone` for the cooked arm. #[inline] pub(crate) fn shallow_clone(&self) -> TemplateContents { @@ -1909,8 +1888,8 @@ impl Template { || (matches!(self.head, TemplateContents::Cooked(_)) && !self.head.cooked().is_utf8()) { // we only fold utf-8/ascii for now - // `self` is Store/arena-allocated (Zig: `*Template`); capturing its - // address as a `StoreRef` mirrors `.{ .e_template = self }`. + // `self` is Store/arena-allocated, so capturing its address as a + // `StoreRef` is sound. return Expr { data: crate::expr::Data::ETemplate(StoreRef::from_bump(self)), loc, @@ -1927,7 +1906,7 @@ impl Template { bun_alloc::ArenaVec::::with_capacity_in(self.parts().len(), bump); let mut head = Expr::init(core::mem::take(self.head.cooked_mut()), loc); for part_src in self.parts() { - // Zig `var part = part.*` — field-wise copy (TemplatePart is not `Copy` only + // Field-wise copy (TemplatePart is not `Copy` only // because `EString` does not derive it; all fields are structurally `Copy`). let mut part = TemplatePart { value: part_src.value, @@ -2048,12 +2027,10 @@ impl Template { ); } } else { - // PERF(port): was appendAssumeCapacity — profile parts.push(part); } } } else { - // PERF(port): was appendAssumeCapacity — profile parts.push(part); } } @@ -2068,7 +2045,7 @@ impl Template { } // Arena-owned mutable slice; `into_bump_slice_mut()` preserves write - // provenance for downstream mutators (Zig: `parts.items`). + // provenance for downstream mutators. Expr::init( Template { tag: None, @@ -2086,7 +2063,8 @@ impl Template { } pub struct RegExp { - // TODO(port): arena-owned slice + // Arena-owned slice (`StoreStr`: lifetime-erased arena ownership, bulk-freed + // at `Store::reset()` — see `nodes.rs` StoreStr docs). pub value: Str, /// This exists for JavaScript bindings @@ -2172,7 +2150,8 @@ pub struct RequireResolveString { pub struct InlinedEnum { pub value: ExprNodeIndex, - // TODO(port): arena-owned slice + // Arena-owned slice (`StoreStr`: lifetime-erased arena ownership, bulk-freed + // at `Store::reset()` — see `nodes.rs` StoreStr docs). pub comment: Str, } @@ -2196,7 +2175,6 @@ impl Import { } pub fn import_record_loader(&self) -> Option { - // This logic is duplicated in js_printer.zig fn parsePath() let crate::ExprData::EObject(obj) = &self.options.data else { return None; }; @@ -2228,5 +2206,3 @@ impl Import { } pub use G::Class; - -// ported from: src/js_parser/ast/E.zig diff --git a/src/ast/expr.rs b/src/ast/expr.rs index f0980358545..be0f049a8b2 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -1,5 +1,3 @@ -//! Port of `src/js_parser/ast/Expr.zig`. -//! //! AST crate: arena-allocated nodes (`*mut E::*`) live in `Data::Store` //! (a typed slab) and are bulk-freed by `Store::reset()`. `Expr` and //! `Data` carry the arena lifetime. @@ -14,8 +12,8 @@ use bun_core::{self}; use crate::{DebugOnlyDisabler, E, G, Op, Ref, S, Stmt}; use bun_alloc::ArenaVecExt as _; -// Re-export so downstream crates can name `ast::expr::StoreRef` (the Zig path -// was `Expr.Data.Store` / `*E.Foo`; some callers route through `expr::`). +// Re-export so downstream crates can name `ast::expr::StoreRef` (some callers +// route through `expr::`). pub use crate::StoreRef; use crate::StoreStr as Str; @@ -93,7 +91,7 @@ impl Expr { } } - /// Zig: `Expr.Data.Store.reset()`. Associated wrapper so downstream crates + /// Associated wrapper so downstream crates /// can call `crate::Expr::data_store_reset()` without naming the /// thread-local Store module path. #[inline] @@ -101,22 +99,23 @@ impl Expr { data::Store::reset(); } - /// Zig: `Expr.Data.Store.create()`. + /// Initializes the thread-local expression-data `Store` for the current + /// thread; counterpart of `data_store_reset()`. #[inline] pub fn data_store_create() { data::Store::create(); } - /// Zig: `Expr.Data.Store.assert()` — debug-only re-entrancy guard. + /// Debug-only "Store must be init'd" guard. The re-entrancy `Disabler` + /// check lives in `Store::append`. #[inline] pub fn data_store_assert() { - crate::DebugOnlyDisabler::::assert(); + data::Store::assert(); } } impl Expr { pub fn clone_in(&self, bump: &Bump) -> Result { - // TODO(port): narrow error set Ok(Expr { loc: self.loc, data: Data::clone_in(self.data, bump)?, @@ -136,7 +135,6 @@ impl Expr { } pub fn wrap_in_arrow(this: Expr, bump: &Bump) -> Result { - // TODO(port): narrow error set let stmts: &mut [Stmt] = bump.alloc_slice_fill_with(1, |_| { Stmt::alloc(S::Return { value: Some(this) }, this.loc) }); @@ -153,7 +151,7 @@ impl Expr { )) } - // `Expr::fromBlob` (Zig) is JSC-tier — it parses JSON via `bun_parsers` and + // `Expr::fromBlob` is JSC-tier — it parses JSON via `bun_parsers` and // reads `jsc::webcore::Blob`. Lives at its sole call site: // `bun_js_parser_jsc::macro_::expr_from_blob`. } @@ -194,8 +192,7 @@ impl Expr { matches!(self.data, Data::EString(_)) } - /// Making this comptime bloats the binary and doesn't seem to impact - /// runtime performance. + /// Look up `name` among the properties of an object-literal expression. pub fn as_property(&self, name: &[u8]) -> Option { let Data::EObject(obj) = &self.data else { return None; @@ -369,8 +366,8 @@ impl Expr { /// - `foo[123].bar[456].baz.qux` // etc. /// /// This is not intended for use by the transpiler, instead by pretty printing JSON. - // PORT NOTE: Zig passed `bun.default_allocator` to getByIndex; Rust threads the arena - // explicitly because get_by_index allocates an E.String slice into &Bump. + // The arena is threaded explicitly because get_by_index allocates an + // E.String slice into &Bump. pub fn get_path_may_be_index(&self, bump: &Bump, name: &[u8]) -> Option { if name.is_empty() { return None; @@ -387,7 +384,7 @@ impl Expr { } let index_str: &[u8] = &name[idx + 1..end_idx]; - // std.fmt.parseInt(u32, index_str, 10) — path segments are bytes, not UTF-8. + // Path segments are bytes, not UTF-8. let index: u32 = bun_core::parse_unsigned(index_str, 10).ok()?; let rest: &[u8] = if name.len() > end_idx { &name[end_idx + 1..] @@ -568,7 +565,7 @@ impl Expr { } } - // PORT NOTE: `Query` holds `expr` by value (Copy). The iterator stores the + // `Query` holds `expr` by value (Copy). The iterator stores the // `StoreRef` directly (Copy, arena-backed) so no lifetime is tied // to a local temporary — `StoreRef::Deref` re-borrows the arena slot on use. pub fn get_array(&self, name: &[u8]) -> Option { @@ -668,7 +665,6 @@ impl Expr { continue; } - // PERF(port): was assume_capacity map.insert(key, value); } @@ -698,7 +694,7 @@ impl ArrayIterator { } } -// PORT NOTE: earlier drafts of `as_array`/`is_string`/`as_utf8_string_literal`/ +// Earlier drafts of `as_array`/`is_string`/`as_utf8_string_literal`/ // `as_string`/`as_string_cloned`/`as_bool`/`as_number` duplicated the live `&self` // implementations above (lines ~231-315) with worse signatures (`expr: &Expr`, // raw-ptr returns). Those drafts were dropped; only the methods without a live @@ -746,7 +742,7 @@ impl Expr { return Ok(Some(hash_fn(&str.data))); } let utf8_str = str.string(bump)?; - // PERF(port): was arena alloc + free; bump-allocated, freed on reset + // bump-allocated, freed on reset Ok(Some(hash_fn(utf8_str))) } _ => Ok(None), @@ -769,9 +765,9 @@ impl Expr { /// associative. For example, the "-" operator is not associative for /// floating-point numbers. // - // PERF(port): Zig took `comptime op: Op.Code`. `Op::Code` does not derive - // `ConstParamTy` (Op.rs owns the enum); pass at runtime here. Revisit once - // `Code` gains `ConstParamTy` — call sites are a handful of literal ops. + // PERF: `Op::Code` does not derive `ConstParamTy` (Op.rs owns the enum); + // pass at runtime here. Revisit once `Code` gains `ConstParamTy` — call + // sites are a handful of literal ops. pub fn join_with_left_associative_op(op: Op::Code, a: Expr, b: Expr) -> Expr { Self::join_with_left_associative_op_with_check(op, a, b, bun_core::StackCheck::init()) } @@ -827,9 +823,7 @@ impl Expr { ) } - // PORT NOTE: Zig threaded `_: std.mem.Allocator` (unused) so the caller's - // arena reached `Expr.init`. The Rust port uses the thread-local - // `data::Store` and drops the parameter. + // Uses the thread-local `data::Store`, so no allocator parameter is needed. pub fn join_with_comma(self, b: Expr) -> Expr { if self.is_missing() { return b; @@ -862,9 +856,8 @@ impl Expr { } } - // PORT NOTE: Zig threaded `ctx: anytype` and called `callback(ctx, ...)` on - // each element. Rust passes `ctx` by `&mut` so a single `&mut P` (the parser - // state) can be reborrowed for each callback invocation without `Copy`. + // `ctx` is passed by `&mut` so a single `&mut P` (the parser state) can be + // reborrowed for each callback invocation without `Copy`. pub fn join_all_with_comma_callback( all: &[Expr], ctx: &mut C, @@ -968,32 +961,24 @@ impl Expr { // Static state // ─────────────────────────────────────────────────────────────────────────── -// Zig: `pub var icount: usize = 0;` — a plain non-atomic global, never read -// (debug counter). Kept for parity but **debug-only**: in release the -// `lock xadd` per node was a contended cache line bouncing across the bundler -// worker pool on every Expr allocation. Zig's increment is a non-atomic store -// (i.e. racy garbage under threads) so a debug-gated atomic is strictly more -// faithful than the old unconditional one. +// Debug-only allocation counter: in release the `lock xadd` per node was a +// contended cache line bouncing across the bundler worker pool on every Expr +// allocation. #[cfg(debug_assertions)] pub(crate) static ICOUNT: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0); -// PORT NOTE: Zig `expr.zig` declares `true_bool`/`false_bool`/`bool_values` -// statics but never references them — `E.Boolean` is stored by value in -// `Data.e_boolean` (both `allocate` and `init` arms), not as a pointer to a -// pooled singleton. Dropped here; the comment "We don't need to dynamically -// allocate booleans" already holds because `E::Boolean` is inline in `Data`. +// We don't need to dynamically allocate booleans: `E::Boolean` is inline in +// `Data`, not a pointer to a pooled singleton. // ─────────────────────────────────────────────────────────────────────────── -// Expr::allocate / Expr::init — comptime-type dispatch → trait +// Expr construction via the IntoExprData trait // ─────────────────────────────────────────────────────────────────────────── /// Trait implemented by every `E::*` payload type to construct an `Expr`. -/// -/// Replaces Zig's `comptime Type: type` switch in `Expr.init` / `Expr.allocate`. pub trait IntoExprData: Sized { - /// Construct `Data` using the thread-local `Data::Store` arena (Zig: `Expr.init`). + /// Construct `Data` using the thread-local `Data::Store` arena. fn into_data_store(self) -> Data; - /// Construct `Data` using a caller-supplied arena (Zig: `Expr.allocate`). + /// Construct `Data` using a caller-supplied arena. /// Be careful to free the memory (or use an arena that does it for you). fn into_data_alloc(self, bump: &Bump) -> Data; } @@ -1068,8 +1053,8 @@ impl_into_expr_data_inline! { RequireString => ERequireString, } -// E::Identifier — Zig copies fields explicitly (normalization). With the -// packed-flag layout the struct is a single `Ref`, so the copy is trivial. +// E::Identifier — with the packed-flag layout the struct is a single `Ref`, +// so the copy is trivial. impl IntoExprData for E::Identifier { #[inline] fn into_data_store(self) -> Data { @@ -1099,8 +1084,7 @@ impl IntoExprData for E::CommonJSExportIdentifier { } #[inline] fn into_data_alloc(self, _bump: &Bump) -> Data { - // Packed layout collapses Zig's init()/allocate() distinction — `base` - // rides inside `ref_`, so a single-word copy carries both regardless. + // `base` rides inside `ref_`, so a single-word copy carries both. Data::ECommonjsExportIdentifier(self) } } @@ -1129,9 +1113,8 @@ impl IntoExprData for E::EString { } } -// *E.String — Zig allows passing a pointer to copy from. `EString` derives no -// `Clone` (rope `next` ptr); Zig copies the struct bytes. Mirror with a -// shallow field-copy. +// &E::EString — construct from a pointer to copy from. `EString` derives no +// `Clone` (rope `next` ptr), so this is a shallow field-copy. impl IntoExprData for &E::EString { #[inline] fn into_data_store(self) -> Data { @@ -1546,9 +1529,9 @@ impl Expr { // For example, "!(a < b)" is not the same as "a >= b" if a and/or b are // NaN (or undefined, or null, or possibly other problem cases too). // - // PORT: Zig captured `*E.Binary` and wrote through it; `StoreRef` is a - // `Copy` `NonNull` handle, so copying it out of the (immutable) `Data` - // and `DerefMut`-ing reaches the same arena slot. + // `StoreRef` is a `Copy` `NonNull` handle, so copying it out of + // the (immutable) `Data` and `DerefMut`-ing reaches the same + // arena slot. match ex.op { crate::OpCode::BinLooseEq => { // "!(a == b)" => "a != b" @@ -1692,8 +1675,7 @@ impl PrimitiveType { /// Tagged union of expression payloads. Pointer variants are arena-allocated /// `StoreRef` (thin `NonNull` into `expr::data::Store` / a bump arena); /// inline variants are stored by value. `StoreRef` is `Copy` + `Deref`, so -/// `Data` is `Copy` and `let Data::EBinary(b) = data; b.op` works (matching -/// Zig's `data.e_binary.op`). +/// `Data` is `Copy` and `let Data::EBinary(b) = data; b.op` works. #[derive(Clone, Copy, bun_core::EnumTag)] #[enum_tag(existing = Tag)] pub enum Data { @@ -1756,12 +1738,10 @@ pub enum Data { } // ── Layout guards ───────────────────────────────────────────────────────── -// Zig: `bun.assert_eql(@sizeOf(Data), 24)` (Expr.zig:2189). Rust packs the -// identifier-family flags into `Ref`'s spare bits (see `E::Identifier` doc), -// so every inline payload is ≤ 8 bytes; with the repr(Rust) discriminant -// that rounds to 16. `Expr` = `Data` (16, align 8) + `Loc` (i32) → 20 → 24 -// after tail padding — 25% smaller than the Zig layout, which is the -// structural noalias-shrink win this port targets. +// The identifier-family flags are packed into `Ref`'s spare bits (see +// `E::Identifier` doc), so every inline payload is ≤ 8 bytes; with the +// repr(Rust) discriminant that rounds to 16. `Expr` = `Data` (16, align 8) + +// `Loc` (i32) → 20 → 24 after tail padding. // // The `Option` assert proves Rust's niche optimization fires: the enum // has spare discriminant values (47 variants < 256, and every pointer variant @@ -1789,9 +1769,9 @@ const _: () = assert!(core::mem::size_of::() <= 8); const _: () = assert!(core::mem::size_of::() <= 8); const _: () = assert!(core::mem::size_of::>() == core::mem::size_of::()); -// Zig field-style union accessors (`data.e_string`, `data.e_object`). The -// match arms in this file use these heavily; keeping them as inherent methods -// avoids rewriting ~25 sites. Returns `Option>` (Copy). +// Field-style accessors (`data.e_string()`, `data.e_object()`). The match arms +// in this file use these heavily; keeping them as inherent methods avoids +// rewriting ~25 sites. Returns `Option>` (Copy). impl Data { #[inline] pub fn e_string(&self) -> Option> { @@ -1845,33 +1825,32 @@ impl Data { pub fn as_e_string(&self) -> Option> { self.e_string() } - /// Zig: `data.e_array` field-access. Mirrors `e_array()`; provided under - /// the `as_*` name for downstream crates ported from `.e_array.*`. + /// Mirrors `e_array()`; provided under the `as_*` name for downstream + /// crates. #[inline] pub fn as_e_array(&self) -> Option> { self.e_array() } - /// Zig: `data.e_object` field-access. Panics if not an object — callers - /// gate with `is_object()` / `is_e_object()` first (mirrors Zig union - /// access). + /// Panics if not an object — callers gate with `is_object()` / + /// `is_e_object()` first. #[inline] pub fn as_e_object(&self) -> StoreRef { self.e_object() .expect("ExprData::as_e_object on non-object") } - /// Zig: `data.e_object` field-access (mutable). + /// Mutable counterpart of `as_e_object()`; panics if not an object. #[inline] pub fn as_e_object_mut(&mut self) -> &mut E::Object { self.e_object_mut() .expect("ExprData::as_e_object_mut on non-object") } - /// Zig: `data == .e_object`. + /// True if this is an `EObject`. #[inline] pub fn is_e_object(&self) -> bool { matches!(self, Data::EObject(_)) } - /// Zig: `data.e_number` field-access. `E::Number` is an inline (non-Store) - /// payload, so this returns it by value. + /// `E::Number` is an inline (non-Store) payload, so this returns it by + /// value. #[inline] pub fn as_e_number(&self) -> Option { if let Data::ENumber(n) = *self { @@ -1880,21 +1859,20 @@ impl Data { None } } - /// Zig: `data == .e_string`. + /// True if this is an `EString`. #[inline] pub fn is_e_string(&self) -> bool { matches!(self, Data::EString(_)) } - /// Zig: `data == .e_number`. + /// True if this is an `ENumber`. #[inline] pub fn is_e_number(&self) -> bool { matches!(self, Data::ENumber(_)) } // ── Remaining StoreRef field-style accessors ────────────────── - // visitExpr / maybe.rs port from Zig's `data.e_dot.*` etc., which are - // unchecked union field reads. Rust callers `.unwrap()` (or pattern-match) - // — the `Option` is the cheapest sound encoding of Zig's UB-on-mismatch. + // Callers `.unwrap()` (or pattern-match) — the `Option` is the cheapest + // sound encoding of a tag-mismatch precondition. #[inline] pub fn e_unary(&self) -> Option> { if let Data::EUnary(v) = *self { @@ -2276,9 +2254,8 @@ impl Data { self.tag().into() } - // Zig: `pub fn as(data: Data, comptime tag: Tag) ?@FieldType(Data, @tagName(tag))` - // Rust has no field-by-tag reflection. Per-variant `as_*` accessors live - // alongside the enum decl above (`e_string`/`e_object`/...). + // Per-variant `as_*` accessors live alongside the enum decl above + // (`e_string`/`e_object`/...). pub fn as_e_identifier(&self) -> Option { if let Data::EIdentifier(i) = self { Some(*i) @@ -2300,20 +2277,18 @@ impl Data { impl Data { /// Shallow clone: re-allocate the boxed payload (so the caller owns a fresh - /// arena slot) but don't recurse into children. Zig: `Data.clone`. + /// arena slot) but don't recurse into children. /// - /// PORT NOTE: the `E::*` payloads do not derive `Clone` (they hold raw arena - /// pointers / `Vec`). Zig copied struct bytes (`el.*`); we mirror that - /// with a `core::ptr::read` of the payload, which is sound because every + /// The `E::*` payloads do not derive `Clone` (they hold raw arena + /// pointers / `Vec`), so this does a `core::ptr::read` of the payload, + /// which is sound because every /// payload is `Copy`-shaped (no `Drop`, no owned heap state — `Vec` /// stores a raw pointer + len/cap into the arena). pub fn clone_in(this: Data, bump: &Bump) -> Result { - // TODO(port): narrow error set macro_rules! shallow { ($variant:ident, $el:expr) => {{ // SAFETY: `$el` is a `StoreRef` deref to a live arena `T`; `T` is - // POD-shaped (no `Drop`). `ptr::read` performs a bitwise copy == - // Zig's `el.*` struct copy. + // POD-shaped (no `Drop`). `ptr::read` performs a bitwise copy. let copied = unsafe { core::ptr::read($el.as_ptr()) }; let item = bump.alloc(copied); return Ok(Data::$variant(StoreRef::from_bump(item))); @@ -2396,7 +2371,7 @@ impl Data { Ok(Data::EBinary(StoreRef::from_bump(item))) } Data::EClass(el) => { - // `properties` is an arena-owned `StoreSlice` (Zig: `[]Property`). + // `properties` is an arena-owned `StoreSlice`. let src_props: &[G::Property] = el.properties.slice(); let mut properties = bun_alloc::ArenaVec::with_capacity_in(src_props.len(), bump); for prop in src_props.iter() { @@ -2529,8 +2504,8 @@ impl Data { None => None, }, parts: el.parts, - // `TemplateContents` is POD-shaped; Zig copied `el.head` by - // value. `shallow_clone` is the safe field-wise copy. + // `TemplateContents` is POD-shaped; `shallow_clone` is the + // safe field-wise copy. head: el.head.shallow_clone(), }); Ok(Data::ETemplate(StoreRef::from_bump(item))) @@ -2605,11 +2580,9 @@ impl Data { /// `hasher` should be something with `fn update(&[u8])`; /// symbol table is passed to serialize `Ref` as identifier names instead of nondeterministic numbers. /// - /// Port of `Expr.Data.writeToHasher`. Zig fed raw bytes of anonymous tuples - /// (`std.mem.asBytes(&.{a, b, c})`) — including padding, which is undefined - /// in both languages. The Rust port hashes each scalar individually so the - /// output is deterministic (this is only consumed by React Refresh signature - /// generation; byte-for-byte parity with Zig is not required, only stability). + /// Hashes each scalar individually so the output is deterministic — no + /// padding bytes are fed to the hasher (this is only consumed by React + /// Refresh signature generation; only stability is required). pub fn write_to_hasher(&self, hasher: &mut H, symbol_table: &mut S) where H: bun_core::Hasher + ?Sized, @@ -2618,8 +2591,7 @@ impl Data { // Local mirror of `bun.writeAnyToHasher` for padding-free POD — // `bun_core::write_any_to_hasher` is bound by `AsBytes` (ints only) and // we cannot extend that trait from this crate-file scope. `NoUninit` - // bound lets `bytemuck::bytes_of` view the value's bytes safely - // (mirrors Zig `hasher.update(std.mem.asBytes(&thing))`). + // bound lets `bytemuck::bytes_of` view the value's bytes safely. #[inline(always)] fn raw(h: &mut H, v: T) { h.update(bytemuck::bytes_of(&v)); @@ -2689,9 +2661,7 @@ impl Data { e.value.data.write_to_hasher(hasher, symbol_table); } Data::EYield(e) => { - // TODO(port): Zig hashed the raw bytes of `.{ e.is_star, e.value }` (the full - // `?Expr` optional, including loc/data pointer). Rust `Option` layout is - // not byte-compatible, so we hash the discriminant here and recurse below. + // Hash the `Option` discriminant, then recurse into the value. raw(hasher, e.is_star); raw(hasher, e.value.is_some()); if let Some(value) = &e.value { @@ -2724,9 +2694,7 @@ impl Data { hasher.update(&e.value); } Data::EString(e) => { - // PORT NOTE: Zig declared `var next: ?*E.String = e;` and tested `if (next)` - // — i.e. it only ever hashes the *first* rope segment (the `next = current.next` - // store is dead). Preserved here. + // Only the *first* rope segment is hashed. let current: &E::String = e; if current.is_utf8() { hasher.update(¤t.data); @@ -3148,8 +3116,8 @@ impl EqlKindT for StrictEql { const STRICT: bool = true; } -/// Minimal parser surface needed by `Data::eql` — Zig wrote `p: anytype` and -/// touched only `p.arena` + `p.module_ref`. Kept separate from +/// Minimal parser surface needed by `Data::eql` — only the arena and the +/// module ref are touched. Kept separate from /// `ast::p::ParserLike` so this file does not grow that trait (out of scope); /// blanket-impl'd for every `P<...>` instantiation below. pub trait EqlParser { @@ -3278,8 +3246,7 @@ impl Data { } Data::EString(l) => { // `StoreRef` is a Copy pointer; rebind mutably so - // `DerefMut` gives `&mut EString` for in-place rope flattening - // (Zig wrote through `*E.String` here). + // `DerefMut` gives `&mut EString` for in-place rope flattening. let mut l = *l; match right { Data::EString(r) => { @@ -3352,8 +3319,6 @@ impl Data { // ─────────────────────────────────────────────────────────────────────────── // `new_store!` emits `pub mod expr_store { pub struct Store; ... }`. -// Type list mirrors Zig's `Data.Store = NewStore(&.{ E.Array, ... }, 512)` -// (Expr.zig:2550-2580). crate::new_store!( expr_store, [ @@ -3397,14 +3362,9 @@ pub mod data { pub use data::Store; // ─────────────────────────────────────────────────────────────────────────── -// StoredData / helpers +// Helpers // ─────────────────────────────────────────────────────────────────────────── -// Zig: `pub fn StoredData(tag: Tag) type` — comptime type-level function. -// Rust cannot return types from runtime tags. Callers should match on `Data` -// directly. -// TODO(port): if needed, expose as a macro mapping Tag → payload type. - fn string_to_equivalent_number_value(str: &[u8]) -> f64 { // +"" -> 0 if str.is_empty() { @@ -3413,7 +3373,6 @@ fn string_to_equivalent_number_value(str: &[u8]) -> f64 { if !bun_core::is_all_ascii(str) { return f64::NAN; } - // TODO(port): move to *_sys unsafe extern "C" { // NOT `safe fn`: callee dereferences `ptr` for `len` bytes — caller must // guarantee the (ptr,len) pair is a valid readable range. @@ -3424,5 +3383,3 @@ fn string_to_equivalent_number_value(str: &[u8]) -> f64 { // only (no mutation, no retention past return). unsafe { JSC__jsToNumber(str.as_ptr(), str.len()) } } - -// ported from: src/js_parser/ast/Expr.zig diff --git a/src/ast/fold_string_addition.rs b/src/ast/fold_string_addition.rs index a578a27ed58..5cab9429868 100644 --- a/src/ast/fold_string_addition.rs +++ b/src/ast/fold_string_addition.rs @@ -5,14 +5,14 @@ use bun_alloc::Arena; // bumpalo::Bump re-export // ── local rope helpers ───────────────────────────────────────────────────── // `EString::push` / `EString::clone_rope_nodes` are still gated in E.rs // (round-C draft); inline the minimal surface here so this file can un-gate -// without touching E.rs. These mirror the Zig bodies 1:1. +// without touching E.rs. #[inline] fn store_append_string(s: E::EString) -> StoreRef { data::Store::append(s) } -/// Zig `E.String.push` — link `other` onto `lhs`'s rope tail. +/// Link `other` onto `lhs`'s rope tail. fn estring_push(lhs: &mut E::EString, mut other: StoreRef) { debug_assert!(lhs.is_utf8()); debug_assert!(other.is_utf8()); @@ -41,8 +41,8 @@ fn estring_push(lhs: &mut E::EString, mut other: StoreRef) { } } -/// Zig `E.String.cloneRopeNodes` — deep-copy the `next` chain into fresh -/// Store nodes so mutating the result can't alias an inlined-enum's string. +/// Deep-copy the `next` chain into fresh Store nodes so mutating the result +/// can't alias an inlined-enum's string. fn clone_rope_nodes(s: &E::EString) -> E::EString { let mut root = s.shallow_clone(); if let Some(first) = root.next { @@ -111,8 +111,8 @@ fn join_strings( new } -/// `std.mem.concat(arena, E.TemplatePart, &.{a, b})` — concat into the bump -/// arena. `TemplatePart` is POD-shaped (no Drop) but not `Copy` because +/// Concat two `TemplatePart` slices into the bump arena. +/// `TemplatePart` is POD-shaped (no Drop) but not `Copy` because /// `EString` opted out; mirror `Template::fold`'s field-wise copy via /// `shallow_clone` instead of raw `copy_nonoverlapping`. fn concat_parts( @@ -122,7 +122,7 @@ fn concat_parts( ) -> crate::StoreSlice { let mut v = bun_alloc::ArenaVec::::with_capacity_in(a.len() + b.len(), bump); for p in a.iter().chain(b.iter()) { - // Zig `var part = part.*` — field-wise copy (all fields structurally `Copy`). + // Field-wise copy (all fields structurally `Copy`). v.push(e::TemplatePart { value: p.value, tail_loc: p.tail_loc, @@ -214,7 +214,7 @@ pub fn fold_string_addition( // "'x' + `y${z}`" => "`xy${z}`" if let Data::ETemplate(t) = rhs.data { if t.tag.is_none() { - // (intentionally empty — matches Zig) + // (intentionally empty) } } } @@ -253,7 +253,6 @@ pub fn fold_string_addition( right.get(), matches!(r.data, Data::EInlinedEnum(_)), )); - // Zig wrote `left.parts[i].tail = ...` in place. left.parts_mut()[i].tail = new_tail; return Some(lhs); } @@ -285,7 +284,6 @@ pub fn fold_string_addition( let new_parts = if right.parts().is_empty() { left.parts } else { - // std.mem.concat → bump-allocated concat concat_parts(bump, left.parts(), right.parts()) }; left.parts = new_parts; @@ -323,5 +321,3 @@ pub fn fold_string_addition( None } - -// ported from: src/js_parser/ast/foldStringAddition.zig diff --git a/src/ast/g.rs b/src/ast/g.rs index 9b557a9405e..c17113ef0b5 100644 --- a/src/ast/g.rs +++ b/src/ast/g.rs @@ -7,12 +7,12 @@ use crate::stmt::Stmt; use crate::ts as TypeScript; use crate::{ExprData, ExprNodeList, LocRef, StmtNodeList, StoreSlice, StoreStr, flags}; -/// Zig: `G.Fn.flags: Flags.Function.Set`. Downstream crates address the flag -/// enum via `G::FnFlags::IsExport` etc.; re-export the enum + set type here. +/// Downstream crates address the flag enum via `G::FnFlags::IsExport` etc.; +/// re-export the enum + set type here. pub use crate::flags::Function as FnFlags; pub use crate::flags::FunctionSet as FnFlagsSet; -// PORT NOTE: all `&'ast mut [T]` arena slices are `StoreSlice` (lifetime- +// All `&'ast mut [T]` arena slices are `StoreSlice` (lifetime- // erased arena-slice newtype). 'ast/'bump can be threaded crate-wide later by // adding a `PhantomData<&'arena ()>` to `StoreSlice` in one pass. @@ -22,8 +22,7 @@ pub struct Decl { pub value: Option, } -// Zig: `pub const List = Vec(Decl);` (nested decl) — inherent assoc types -// are nightly; free alias. +// Inherent assoc types are nightly; free alias. pub type DeclList = Vec; pub struct NamespaceAlias { @@ -155,8 +154,8 @@ pub struct Property { pub kind: PropertyKind, pub flags: flags::PropertySet, - // Arena-owned `?*ClassStaticBlock` (Zig). `StoreRef` centralises the - // raw-pointer deref so the accessors below stay safe. + // Arena-owned. `StoreRef` centralises the raw-pointer deref so the + // accessors below stay safe. pub class_static_block: Option>, pub ts_decorators: ExprNodeList, // Key is optional for spread @@ -168,7 +167,6 @@ pub struct Property { pub ts_metadata: TypeScript::Metadata, } -// Zig: nested `pub const List = Vec(Property);` — free alias. pub type PropertyList = Vec; impl Default for Property { @@ -224,7 +222,7 @@ impl Property { kind: self.kind, flags: self.flags, class_static_block, - // Zig: `try this.ts_decorators.deepClone(arena)` — Vec per-element deep clone. + // Vec per-element deep clone. ts_decorators: self .ts_decorators .try_deep_clone_with(|e| e.deep_clone(bump))?, @@ -241,7 +239,6 @@ impl Property { } } -// Zig: `enum(u3)` — Rust has no u3, use u8 #[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, strum::IntoStaticStr)] #[strum(serialize_all = "snake_case")] @@ -266,7 +263,6 @@ impl FnBody { bump: &bun_alloc::Arena, expr: ExprNodeIndex, ) -> core::result::Result { - // PERF(port): Zig used arena.dupe over a 1-elem array literal; bumpalo equivalent let stmts: &mut [Stmt] = bump.alloc_slice_fill_with(1, |_| { Stmt::alloc(crate::s::Return { value: Some(expr) }, expr.loc) }); @@ -313,7 +309,6 @@ impl Fn { &self, bump: &bun_alloc::Arena, ) -> core::result::Result { - // PERF(port): Zig arena.alloc + per-index assign; bumpalo equivalent. let src_args: &[Arg] = self.args.slice(); let args: &mut [Arg] = bump.alloc_slice_fill_default::(src_args.len()); for i in 0..args.len() { @@ -363,7 +358,7 @@ impl Arg { bump: &bun_alloc::Arena, ) -> core::result::Result { Ok(Arg { - // Zig: `try this.ts_decorators.deepClone(arena)` — Vec per-element deep clone. + // Vec per-element deep clone. ts_decorators: self .ts_decorators .try_deep_clone_with(|e| e.deep_clone(bump))?, @@ -377,5 +372,3 @@ impl Arg { }) } } - -// ported from: src/js_parser/ast/G.zig diff --git a/src/ast/import_record.rs b/src/ast/import_record.rs index 859236a64fe..95c4d2950ae 100644 --- a/src/ast/import_record.rs +++ b/src/ast/import_record.rs @@ -1,4 +1,4 @@ -//! `src/options_types/import_record.zig` — `ImportRecord` and friends. +//! `ImportRecord` and friends. //! //! Lives in `bun_ast` so `Ast` (which holds `Vec`) is //! self-contained and `bun_js_printer` can drop its `bun_js_parser` dep. @@ -9,13 +9,12 @@ use crate::Range; use bun_paths::fs::Path; // Re-exported here (canonical at crate root) so callers that path through -// `bun_ast::import_record::{ImportKind, Index, Loader}` — mirroring the Zig -// `import_record.zig` namespace — keep resolving. +// `bun_ast::import_record::{ImportKind, Index, Loader}` keep resolving. pub use crate::{ImportKind, Index, Loader}; pub struct ImportRecord { pub range: Range, - // TODO(port): lifetime — `bun_paths::fs::Path<'a>` borrows resolver-owned + // TODO: lifetime — `bun_paths::fs::Path<'a>` borrows resolver-owned // strings. Uses 'static (PORTING.md: no struct lifetime params). pub path: Path<'static>, pub kind: ImportKind, @@ -24,18 +23,16 @@ pub struct ImportRecord { pub source_index: Index, - /// `js_printer::printBundledImport` reads this. The Zig field was removed - /// from `ImportRecord` but the printer body referencing it is dead (never - /// analysed by Zig's lazy compilation). Kept here so the eagerly-compiled - /// Rust port of that body type-checks; always 0 in practice. - // TODO(port): delete once `printBundledImport` is confirmed dead and removed. + /// Dead field: the only reader (`js_printer`'s `print_bundled_import`) + /// has been deleted. Always 0. Remove together with the `module_id: 0` initializers + /// in the parser/bundler/css constructors when those files are next touched. pub module_id: u32, /// The original import specifier as written in source code (e.g., "./foo.js"). /// This is preserved before resolution overwrites `path` with the resolved path. /// Used for metafile generation. - // TODO(port): lifetime — Zig `[]const u8` defaulting to "", never freed in this file. - // Likely a borrow into parser-owned source text; using &'static [u8] as a placeholder. + // TODO: lifetime — likely a borrow into parser-owned source text; using + // &'static [u8] as a placeholder. pub original_path: &'static [u8], /// Pack all boolean flags into 2 bytes to reduce padding overhead. @@ -155,8 +152,6 @@ pub enum PrintMode { NapiModule, } -// NOTE: no `impl Default for ImportRecord` — Zig gives `range`, `path`, `kind` no defaults, -// so `.{}` is invalid there. Construction sites must supply required fields explicitly +// NOTE: no `impl Default for ImportRecord` — `range`, `path`, `kind` have no +// sensible defaults. Construction sites must supply required fields explicitly // (struct-update or a `new(range, path, kind)` helper). - -// ported from: src/options_types/import_record.zig diff --git a/src/ast/known_global.rs b/src/ast/known_global.rs index d50ac3bdd05..f7e718347e9 100644 --- a/src/ast/known_global.rs +++ b/src/ast/known_global.rs @@ -30,10 +30,7 @@ pub enum KnownGlobal { RegExp, } -// `pub const map = bun.ComptimeEnumMap(KnownGlobal);` -// -// PERF(port): Zig's `ComptimeEnumMap` lowers to a comptime-generated switch. -// An earlier port used `phf::Map<&[u8], _>`, which on every probe computes a 128-bit +// PERF: an earlier draft used `phf::Map<&[u8], _>`, which on every probe computes a 128-bit // SipHash of the name, two modular reductions, a bounds check, and a final // slice compare. `minify_global_constructor` calls this for every `new Ident` // expression in the input, and the overwhelming majority of probes are @@ -112,8 +109,7 @@ impl KnownGlobal { js_ast::Expr::init(call, loc) } - // PORT NOTE: `_bump` is kept for call-site shape parity with the Zig - // `std.mem.Allocator` arg. The `Vec` uses the global arena. + // `_bump` is unused; the `Vec` uses the global arena. #[inline(never)] pub fn minify_global_constructor( _bump: &Bump, @@ -251,7 +247,7 @@ impl KnownGlobal { let arg_loc = arg.loc; let mut list = e.args.move_to_list_managed(); list.clear(); - // PERF(port): was bun.handleOom(appendNTimes) — Vec::resize aborts on OOM + // Vec::resize aborts on OOM list.resize( val as usize, js_ast::Expr { @@ -550,5 +546,3 @@ mod tests { } } } - -// ported from: src/js_parser/ast/KnownGlobal.zig diff --git a/src/ast/lexer_log.rs b/src/ast/lexer_log.rs index 29c52666b9c..7346a5ee2ad 100644 --- a/src/ast/lexer_log.rs +++ b/src/ast/lexer_log.rs @@ -3,9 +3,8 @@ //! js_parser, json, and toml lexers each carried a near-identical 50-line block //! of `{syntax_error, add_error, add_range_error, add_default_error, //! add_syntax_error}` that gate on `is_log_disabled`, dedup against -//! `prev_error_loc`, push into `Log`, then record the loc. Zig has two copies -//! (the comptime-generic `NewLexer` covers JS+JSON, toml is hand-duplicated); -//! Rust grew a third when json was split out. This trait collapses all three. +//! `prev_error_loc`, push into `Log`, then record the loc. This trait +//! collapses all three. //! //! The trait carries a `'s` lifetime so `source()` can hand back the lexer's //! stored `&'s Source` *without* borrowing `self` — that is what lets the diff --git a/src/ast/lexer_tables.rs b/src/ast/lexer_tables.rs index a7a8487593a..0d4f57158c1 100644 --- a/src/ast/lexer_tables.rs +++ b/src/ast/lexer_tables.rs @@ -166,8 +166,8 @@ impl T { /// The `N <= 8` branch routes through a `u64` and widens with `as u128` so the /// upper half is the *literal* `0` rather than a stack-buffer read — LLVM /// InstCombine then narrows the resulting `icmp eq i128 (zext %lo), C` back to -/// a single `i64` compare. This is the codegen Zig's `ComptimeStringMap` emits -/// (`mov (%rsi),%rax; movabs $imm,%rcx; cmp %rcx,%rax`). Matching on +/// a single `i64` compare (`mov (%rsi),%rax; movabs $imm,%rcx; cmp %rcx,%rax`). +/// Matching on /// `&[u8; N]` directly does **not** get this: rustc lowers array patterns to a /// per-byte `cmpb`+`jne` decision tree (8 branches for `b"function"`), which /// is what the previous revision of `by_len!` produced. @@ -202,8 +202,8 @@ const fn kw_pack(arr: &[u8; N]) -> u128 { /// /// Replaces the `phf::Map` lookup for `KEYWORDS` (which hashes through /// SipHash13 and showed up as ~4% self-time under `phf_shared::hash` in -/// `perf record` on the three.js bundle). Mirrors Zig's `ComptimeStringMap` -/// strategy: bucket by length, then load the candidate once as a wide integer +/// `perf record` on the three.js bundle). Strategy: bucket by length, then +/// load the candidate once as a wide integer /// and compare against const-folded immediates — one `cmp` per candidate, no /// hash, no bounds checks, no `memcmp`, no per-byte ladder. /// @@ -441,7 +441,7 @@ pub fn is_type_script_accessibility_modifier(s: &[u8]) -> bool { /// `.rodata` `[&[u8]; T::COUNT]` indexed by [`T`] discriminant. Replaces an /// earlier `LazyLock>` so lookup is a plain array index with -/// zero init code (matches Zig `std.EnumArray`). +/// zero init code. #[repr(transparent)] pub struct TokenEnumType(pub [&'static [u8]; ::LENGTH]); @@ -454,7 +454,6 @@ impl core::ops::Index for TokenEnumType { } impl TokenEnumType { - /// Zig: `tokenToString.get(token)`. #[inline] pub fn get(&self, t: T) -> &'static [u8] { self.0[t as usize] @@ -897,8 +896,6 @@ pub static JSX_ENTITY: phf::Map<&'static [u8], CodePoint> = phf_map! { b"zwnj" => 0x200C, }; -// ported from: src/js_parser/lexer_tables.zig - #[cfg(test)] mod tests { use super::*; @@ -985,8 +982,7 @@ pub fn is_identifier_continue(codepoint: i32) -> bool { pub use bun_core::identifier::{is_identifier, is_identifier_utf16}; pub fn is_latin1_identifier>(name: B) -> bool { - // Zig `isLatin1Identifier(comptime Buffer, name)` is generic over `[]const u8` - // and `[]const u16`; the u16 instantiation is [`is_latin1_identifier_u16`]. + // The UTF-16 counterpart is [`is_latin1_identifier_u16`]. let name = name.as_ref(); if name.is_empty() { return false; @@ -1009,9 +1005,8 @@ pub fn is_latin1_identifier>(name: B) -> bool { true } -/// `JSLexer.isLatin1Identifier(comptime []const u16, name)` — UTF-16 overload -/// of [`is_latin1_identifier`]. Walks code units exactly as the Zig generic -/// does (no narrowing/alloc): any unit `> 0xFF` fails the predicate, otherwise +/// UTF-16 overload of [`is_latin1_identifier`]. Walks code units directly +/// (no narrowing/alloc): any unit `> 0xFF` fails the predicate, otherwise /// the byte rules apply. pub fn is_latin1_identifier_u16(name: &[u16]) -> bool { if name.is_empty() { diff --git a/src/ast/lib.rs b/src/ast/lib.rs index b2104c4a98b..82c672e5c41 100644 --- a/src/ast/lib.rs +++ b/src/ast/lib.rs @@ -2,20 +2,16 @@ // `#[thread_local]` for the per-node-allocation hot-path TLS // (`DATA_STORE_OVERRIDE`, `Expr/Stmt::data::Store::{INSTANCE, // MEMORY_ALLOCATOR, DISABLE_RESET}`, `store_ast_alloc_heap::ARENA`): bare -// `__thread` slot like Zig's `threadlocal var`, vs the `thread_local!` +// `__thread` slot, vs the `thread_local!` // macro's `LocalKey` wrapper. All are `Cell<*mut _>` / `Cell` (no // destructor, const init). #![feature(thread_local)] -//! Port of `src/logger/logger.zig`. -//! -//! TODO(port): OWNERSHIP — almost every `[]const u8` field in this module has -//! mixed/ambiguous ownership in the Zig original (see the comment on -//! `Location::deinit`: "don't really know what's safe to deinit here!"). Strings -//! are sometimes literals, sometimes `allocator.dupe` results, sometimes slices -//! into `Source.contents` or a `StringBuilder` arena. They are kept as -//! `&'static [u8]` to mirror the Zig `[]const u8` shape without lifetime params; -//! a real ownership story (likely `bun_core::String` or a `'source` lifetime -//! threaded through `Location`/`Data`/`Msg`) is still needed. +//! TODO: OWNERSHIP — almost every byte-slice field in this module has +//! mixed/ambiguous ownership. Strings are sometimes literals, sometimes heap +//! copies, sometimes slices into `Source.contents` or a `StringBuilder` arena. +//! They are kept as `&'static [u8]` to avoid lifetime params; a real ownership +//! story (likely `bun_core::String` or a `'source` lifetime threaded through +//! `Location`/`Data`/`Msg`) is still needed. use core::fmt; use std::borrow::Cow; @@ -24,7 +20,7 @@ use std::borrow::Cow; // infallible (`Vec::push` / `io::Write` on `Vec` cannot fail in Rust). use bun_core::Output; -// TODO(port): swap to `bun_core::StringBuilder` once `clone_with_builder` is +// TODO: swap to `bun_core::StringBuilder` once `clone_with_builder` is // reshaped to use `append_raw` (canonical's `append` borrows `&mut self`, which // breaks the `'static` slice pass-through this stub fakes). #[derive(Default)] @@ -39,8 +35,7 @@ impl StringBuilder { pub fn allocate(&mut self) {} } -// Variants mirror src/options_types/import_record.zig:1-25 exactly -// (discriminants are wire-stable for serialization). +// Discriminants are wire-stable for serialization. #[repr(u8)] #[derive( Clone, Copy, PartialEq, Eq, Hash, Debug, Default, enum_map::Enum, strum::IntoStaticStr, @@ -83,9 +78,8 @@ pub enum ImportKind { Internal = 11, } -// E0015: EnumMap indexing isn't const; Zig's `comptime brk: { ... }` initializer -// is folded into match arms inside label()/error_label() below — same lookup -// table, zero runtime init (PORTING.md §Concurrency: prefer no-lock over OnceLock +// E0015: EnumMap indexing isn't const; the lookup table is folded into match +// arms inside label()/error_label() below — zero runtime init (PORTING.md §Concurrency: prefer no-lock over OnceLock // when the data is pure const). // // If these are changed, make sure to update @@ -147,10 +141,9 @@ impl ImportKind { // ─────────────────────────────────────────────────────────────────────────── // Ref / Symbol -// Zig: src/js_parser/ast/{base,Symbol,G}.zig + js_parser.zig (ImportItemStatus). // ─────────────────────────────────────────────────────────────────────────── -/// Tag bits of `Ref` (Zig: anonymous `enum(u2)` field). +/// Tag bits of `Ref`. #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, strum::IntoStaticStr)] #[strum(serialize_all = "snake_case")] @@ -163,14 +156,11 @@ pub enum RefTag { /// Packed-u64 symbol reference: `{inner_index: u28, user: u3, tag: u2, source_index: u31}`. /// -/// Layout matches `src/js_parser/ast/base.zig:Ref` LSB-first packing for the -/// `tag`/`source_index` fields so `as_u64()` hashes identically to the Zig -/// original for all normally-constructed refs (user bits = 0). The Rust port -/// steals 3 bits from `inner_index` (Zig u31 → u28, max 268M symbols/file — +/// LSB-first packing for the `tag`/`source_index` fields, with 3 bits stolen +/// from `inner_index` (31 → 28 bits, max 268M symbols/file — /// three.js peaks at ~50K) so that `E::Identifier` / `E::ImportIdentifier` / /// `E::CommonJSExportIdentifier` can pack their boolean side-flags inline, -/// shrinking `expr::Data` from 24→16 bytes and `Expr` from 32→24. This is the -/// structural noalias-shrink advantage Rust has over the Zig layout: the +/// shrinking `expr::Data` from 24→16 bytes and `Expr` from 32→24. The /// rarely-set flags (`with`-stmt guard, known-pure-global hints) ride in /// otherwise-dead bits instead of forcing 8 bytes of struct padding on every /// identifier node. @@ -185,12 +175,12 @@ pub enum RefTag { #[derive(Clone, Copy)] pub struct Ref(u64); -/// Zig `Ref.Int = u31`; we mask to 31 bits for `source_index`, 28 for `inner_index`. +/// We mask to 31 bits for `source_index`, 28 for `inner_index`. pub type RefInt = u32; impl Ref { const INNER_MASK: u64 = (1u64 << 31) - 1; - /// `inner_index` width — Zig u31, narrowed to u28 to free 3 user bits. + /// `inner_index` width — 28 bits, leaving 3 user bits. /// `debug_assert!` in `pack()` catches any source large enough to overflow /// (would require >268M symbols or a >268MB source-contents-slice offset). const INNER_BITS: u64 = (1u64 << 28) - 1; @@ -306,7 +296,6 @@ impl Ref { } #[inline] pub fn hash64(self) -> u64 { - // Zig: `bun.hash(&@as([8]u8, @bitCast(key.asU64())))` — wyhash of the 8 bytes. bun_wyhash::hash(&self.as_u64().to_ne_bytes()) } @@ -346,9 +335,8 @@ impl Ref { } /// Replace the identity bits with those of `self` while keeping `src`'s /// user-bit lane. Used by `handle_identifier`'s `id_clone.ref_ = result.ref` - /// port — in Zig the flags are separate struct fields and survive the ref - /// assignment; here they ride in `ref_` and would be silently zeroed by a - /// whole-word write. + /// assignment — the flags ride in `ref_` and would otherwise be silently + /// zeroed by a whole-word write. #[inline] pub const fn with_user_bits_from(self, src: Ref) -> Ref { Ref((self.0 & !Self::USER_BITS_MASK) | (src.0 & Self::USER_BITS_MASK)) @@ -412,10 +400,10 @@ impl fmt::Debug for Ref { } } -// TODO(port): bun_paths must define `PathContentsPair` (TYPE_ONLY from bun_resolver::fs). -// Local mirror so init_file / init_recycled_file resolve until paths' move-in lands. +// Local mirror of `bun_resolver::fs`'s path+contents pair (canonical home would +// be `bun_paths`); defined here so init_file / init_recycled_file resolve. // `pub` so `bun_bundler::Transpiler::parse_maybe` can construct it for -// `Source::init_recycled_file` (transpiler.zig:852). +// `Source::init_recycled_file`. /// A [`Source`]'s path paired with its raw bytes (used by virtual-module /// injection: `BundleV2`'s `additional_files`, `Bun.build` inputs). #[derive(Clone, Copy)] @@ -423,21 +411,19 @@ pub struct PathContentsPair { pub path: bun_paths::fs::Path<'static>, pub contents: &'static [u8], } -// TODO(port): bun_schema::api — `to_api` methods gated behind . -// In Zig: `const string = []const u8;` type Str = &'static [u8]; -// TODO(port): lifetime — see module-level note. `Str` is a stand-in for the Zig -// `[]const u8` struct-field pattern; TODO(port): replace with the real type. +// `Str` is a lifetime-erased byte-slice alias; see the module-level OWNERSHIP +// note for the real ownership story. // ─────────────────────────────────────────────────────────────────────────── -// api — hand-ported slice of `bun.schema.api` (src/options_types/schema.zig -// :2295–2509) consumed by `Kind/Location/Data/Msg/Log::to_api`. The full +// api — hand-written slice of `bun.schema.api` consumed by +// `Kind/Location/Data/Msg/Log::to_api`. The full // peechy → .rs codegen (`bun_api`) will supersede this; field shapes are kept // faithful so the generated diff stays reviewable. Lives here (not `bun_api`) // ─────────────────────────────────────────────────────────────────────────── pub mod api { - /// schema.zig:2295 `MessageLevel` (u32 enum, 1-based; `_none` = 0). + /// `MessageLevel` — u32 enum, 1-based; `None` = 0. #[repr(u32)] #[derive(Clone, Copy, Default, PartialEq, Eq, Debug)] pub enum MessageLevel { @@ -450,7 +436,6 @@ pub mod api { Debug = 5, } - /// schema.zig:2319 `Location`. #[derive(Clone, Default, Debug)] pub struct Location { pub file: Vec, @@ -461,21 +446,18 @@ pub mod api { pub offset: u32, } - /// schema.zig:2360 `MessageData`. #[derive(Clone, Default, Debug)] pub struct MessageData { pub text: Option>, pub location: Option, } - /// schema.zig:2403 `MessageMeta`. #[derive(Clone, Default, Debug)] pub struct MessageMeta { pub resolve: Option>, pub build: Option, } - /// schema.zig:2446 `Message`. #[derive(Clone, Default, Debug)] pub struct Message { pub level: MessageLevel, @@ -484,7 +466,6 @@ pub mod api { pub on: MessageMeta, } - /// schema.zig:2477 `Log`. #[derive(Clone, Default, Debug)] pub struct Log { pub warnings: u32, @@ -495,8 +476,9 @@ pub mod api { /// `[]const u8` parameter shim — accepts `&str` / `&[u8]` (any lifetime) /// and erases to the crate-wide `Str` (`&'static [u8]`) lie so callers in either -/// string flavour compile against the same Zig-shaped signatures. -/// TODO(port): lifetime — remove with `Str` once `'source` is threaded through. +/// string flavour compile against the same signatures. +/// Removable together with `Str` once a `'source` lifetime is threaded through +/// (see the module-level OWNERSHIP note). pub trait IntoStr { fn into_str(self) -> Str; } @@ -658,8 +640,6 @@ impl Kind { // Loc // ─────────────────────────────────────────────────────────────────────────── -// Do not mark these as packed -// https://github.com/ziglang/zig/issues/15715 #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct Loc { pub start: i32, @@ -679,7 +659,6 @@ impl Loc { if self.start == -1 { None } else { Some(self) } } - // Zig: `pub const toUsize = i;` #[inline] pub fn to_usize(self) -> usize { self.i() @@ -712,9 +691,9 @@ pub struct Location { // - 4-byte fields last: i32 // This eliminates padding between differently-sized fields. // - // PORT NOTE: `file` / `line_text` are `Cow` (not `Str`) because - // `Location::clone()` must deep-dupe them (Zig: `allocator.dupe(u8, ..)`, - // logger.zig:113) so a `BuildMessage`/`ResolveMessage` that outlives the + // `file` / `line_text` are `Cow` (not `Str`) because + // `Location::clone()` must deep-dupe them so a + // `BuildMessage`/`ResolveMessage` that outlives the // `Source.contents` it borrowed from doesn't read poisoned memory. The // borrowed arm covers the common case where the slice points into // arena-owned source text. @@ -738,13 +717,12 @@ pub struct Location { pub column: i32, } -// PORT NOTE: NOT `#[derive(Clone)]`. `file` / `line_text` are +// NOT `#[derive(Clone)]`. `file` / `line_text` are // `Cow<'static, [u8]>` whose `Borrowed` arm may carry a lifetime-erased view // into `Source.contents` (see `init_or_null`, `css_parser.rs`, `error.rs`, // `JSBundler.rs`). The derived `Cow::clone` would re-borrow that pointer, so a // `BuildMessage` cloned via `Option::clone()` / `Vec::clone()` -// could outlive the source buffer and read poisoned memory. Mirror the Zig -// `Location.clone` (`allocator.dupe`, logger.zig:113) for the trait impl too — +// could outlive the source buffer and read poisoned memory. Instead, // every `Clone` of a `Location` deep-dupes its borrowed bytes. impl Clone for Location { fn clone(&self) -> Self { @@ -794,16 +772,14 @@ impl Location { } pub fn clone(&self) -> Location { - // Zig (logger.zig:113): `allocator.dupe(u8, this.file)` / - // `allocator.dupe(u8, this.line_text.?)` — the duped bytes outlive the - // original `Source.contents`. The trait `Clone` impl above does the - // deep-dupe; this inherent shim forwards to it. + // The trait `Clone` impl above does the deep-dupe (so the duped bytes + // outlive the original `Source.contents`); this inherent shim forwards + // to it. ::clone(self) } pub fn clone_with_builder(&self, _string_builder: &mut StringBuilder) -> Location { - // PORT NOTE: Zig's `string_builder.append` copies into a buffer owned - // by the destination `Log`'s allocator (StringBuilder.zig). The local + // The local // `StringBuilder` stub above is a no-op that returns its input, so a // `Cow::Borrowed(append(s))` would alias `self`'s storage and dangle // after `self.msgs.clear()` in `append_to_with_recycled`. Deep-copy @@ -831,9 +807,7 @@ impl Location { } } - // don't really know what's safe to deinit here! - // Zig: `pub fn deinit(_: *Location, _: std.mem.Allocator) void {}` - // → no Drop impl needed. + // No Drop impl needed. pub fn init( file: Str, @@ -906,14 +880,12 @@ impl Location { } else { 1 }, - // PORT NOTE: Zig borrows `source.contents` here and relies on the - // arena outliving the `Log` (transpiler.zig:853 — `entry.contents` - // is arena-allocated and never explicitly freed on `return null`). - // Rust's `source_backing` in `Transpiler::parse_*` is RAII and + // `source_backing` in `Transpiler::parse_*` is RAII and // drops on the parse-error path *before* `process_fetch_log` // clones the `Msg` into a `BuildMessage`, so own the bytes here - // instead. `full_line` is bounded (≤ ~120 bytes) and only - // materialized on diagnostic paths. + // instead of borrowing `source.contents`. `full_line` is + // bounded (≤ ~120 bytes) and only materialized on diagnostic + // paths. line_text: Some(Cow::Owned(bun_core::trim_left(full_line, b"\n\r").to_vec())), offset: usize::try_from(r.loc.start.max(0)).expect("int cast"), }); @@ -951,10 +923,8 @@ impl Data { cost } - // Zig `deinit` frees `text` and calls `location.deinit()` (no-op). - // `text` is `Cow<'static, [u8]>`: `Owned` frees on `Drop` (matches Zig - // `allocator.free(d.text)`), `Borrowed` is a `&'static` literal — nothing to - // free. No explicit `Drop` body needed. + // `text` is `Cow<'static, [u8]>`: `Owned` frees on `Drop`, `Borrowed` is a + // `&'static` literal — nothing to free. No explicit `Drop` body needed. pub fn clone_line_text(&self, should: bool) -> Data { if !should || self.location.is_none() || self.location.as_ref().unwrap().line_text.is_none() @@ -962,7 +932,6 @@ impl Data { return self.clone(); } - // Zig (logger.zig:217): `allocator.dupe(u8, this.location.?.line_text.?)`. let new_line_text = self .location .as_ref() @@ -982,7 +951,6 @@ impl Data { pub fn clone(&self) -> Data { Data { text: if !self.text.is_empty() { - // Zig (logger.zig:231): `try allocator.dupe(u8, this.text)`. // `Cow::clone` only deep-copies the `Owned` arm; force the dupe // so a `Borrowed` `text` (rare today, but the type permits it) // can't alias recycled storage in the cloned `Msg`. @@ -997,8 +965,7 @@ impl Data { pub fn clone_with_builder(&self, builder: &mut StringBuilder) -> Data { Data { text: if !self.text.is_empty() { - // Zig: `builder.append(this.text)` copies into the destination - // `Log`'s arena (StringBuilder.zig). The local `StringBuilder` + // The local `StringBuilder` // is a no-op stub (returns its input), so a bare `Cow::clone` // would leave a `Borrowed` arm aliasing `self`'s storage and // dangle after `self.msgs.clear()` in @@ -1040,9 +1007,8 @@ impl Data { } // Local wrapper around `bun_core::pretty_fmt!` so the const-generic - // `ENABLE_ANSI_COLORS` selects the right comptime template at each call + // `ENABLE_ANSI_COLORS` selects the right compile-time template at each call // site (the macro pattern-matches a literal `true`/`false` token). - // PERF(port): was comptime bool dispatch — profile. macro_rules! pretty_write { ($fmt:literal $(, $arg:expr)* $(,)?) => { if ENABLE_ANSI_COLORS { @@ -1079,7 +1045,6 @@ impl Data { if location.line > -1 { let bold = matches!(kind, Kind::Err | Kind::Warn); // bold the line number for error but dim for the attached note - // PERF(port): was comptime bool dispatch on `bold` — profile if bold { pretty_write!("{} | ", location.line)?; } else { @@ -1150,20 +1115,6 @@ impl Data { } else if location.line > -1 { pretty_write!(":{}", location.line)?; } - - if cfg!(debug_assertions) { - // TODO(port): the Zig gates this on - // `std.mem.indexOf(u8, @typeName(@TypeOf(to)), "fs.file") != null` — - // i.e. comptime reflection on the writer's type name to detect - // a real file writer (vs Bun.inspect). No Rust equivalent; - // TODO(port): plumb an explicit flag. - if false - && Output::ENABLE_ANSI_COLORS_STDERR - .load(core::sync::atomic::Ordering::Relaxed) - { - pretty_write!(" byte={}", location.offset)?; - } - } } } @@ -1171,7 +1122,6 @@ impl Data { } } -// Helper: Zig `to.splatByteAll(b, n)` fn write_n_bytes(to: &mut impl fmt::Write, b: u8, n: usize) -> fmt::Result { for _ in 0..n { to.write_char(b as char)?; @@ -1183,7 +1133,6 @@ fn write_n_bytes(to: &mut impl fmt::Write, b: u8, n: usize) -> fmt::Result { // BabyString // ─────────────────────────────────────────────────────────────────────────── -// Zig: `packed struct(u32) { offset: u16, len: u16 }` #[repr(transparent)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct BabyString(u32); @@ -1191,7 +1140,7 @@ pub struct BabyString(u32); impl BabyString { #[inline] pub const fn new(offset: u16, len: u16) -> Self { - // Zig packed-struct field order is LSB-first: offset = low 16, len = high 16. + // LSB-first packing: offset = low 16 bits, len = high 16. BabyString((offset as u32) | ((len as u32) << 16)) } @@ -1206,8 +1155,13 @@ impl BabyString { } pub fn r#in(parent: &[u8], text: &[u8]) -> BabyString { - // TODO(port): bun_core::index_of missing — inline bstr fallback. - let off = bstr::ByteSlice::find(parent, text).expect("unreachable"); + // bun_core::strings::index_of deliberately returns None for an empty + // needle, but an empty `text` reaches this path via resolve errors for + // `import ""`, so short-circuit it here to offset 0. + if text.is_empty() { + return BabyString::new(0, 0); + } + let off = bun_core::strings::index_of(parent, text).expect("unreachable"); BabyString::new(off as u16, text.len() as u16) // @truncate } @@ -1251,8 +1205,7 @@ impl Msg { cost } - // Zig: `pub const fromJS/toJS = @import("../logger_jsc/...")` - // → deleted; `to_js`/`from_js` live as extension-trait methods in `bun_logger_jsc`. + // `to_js`/`from_js` live as extension-trait methods in `bun_logger_jsc`. pub fn count(&self, builder: &mut StringBuilder) { self.data.count(builder); @@ -1285,8 +1238,6 @@ impl Msg { for (i, note) in self.notes.iter().enumerate() { notes[i] = note.clone_with_builder(builder); } - // TODO(port): lifetime — Zig returns a sub-slice of the - // caller-provided `notes` buffer; with `Box<[Data]>` we copy. break 'brk notes[0..self.notes.len()].to_vec().into_boxed_slice(); } } else { @@ -1309,10 +1260,9 @@ impl Msg { resolve: if let Metadata::Resolve(r) = &self.metadata { Some(r.specifier.slice(&self.data.text).to_vec()) } else { - // Zig (logger.zig:457): `else ""` — coerces to a NON-NULL - // `?[]const u8`, so peechy `MessageMeta.encode` still emits - // field-ID 1 with an empty string. `None` would skip the - // field entirely on the wire. + // NON-NULL empty string so peechy `MessageMeta.encode` + // still emits field-ID 1; `None` would skip the field + // entirely on the wire. Some(Vec::new()) }, build: Some(matches!(self.metadata, Metadata::Build)), @@ -1321,8 +1271,6 @@ impl Msg { } pub fn to_api_from_list(list: &[Msg]) -> Box<[api::Message]> { - // PORT NOTE: Zig took `comptime ListType: type, list: ListType` and read - // `list.items`; collapsed to `&[Msg]`. let mut out_list = Vec::with_capacity(list.len()); for item in list { out_list.push(item.to_api()); @@ -1330,8 +1278,7 @@ impl Msg { out_list.into_boxed_slice() } - // Zig `deinit` frees `data`, each `note`, and `notes` slice — all handled by Drop - // once ownership is real. No explicit Drop body needed beyond field drops. + // No explicit Drop body needed beyond field drops. pub fn write_format( &self, @@ -1359,7 +1306,6 @@ impl Msg { } pub fn format_writer(&self, writer: &mut impl fmt::Write) -> fmt::Result { - // PORT NOTE: Zig had an unused `comptime _: bool` param; dropped. if let Some(location) = &self.data.location { write!( writer, @@ -1424,8 +1370,6 @@ impl Default for MetadataResolve { // Range // ─────────────────────────────────────────────────────────────────────────── -// Do not mark these as packed -// https://github.com/ziglang/zig/issues/15715 #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Range { pub loc: Loc, @@ -1442,31 +1386,78 @@ impl Default for Range { } /// Was `bun_js_parser::lexer::rangeOfIdentifier`. -/// Moved into logger to break logger→js_parser. Mirrors lexer.zig:3113-3148. -/// TODO(port): full Unicode `isIdentifierStart/Continue` tables — currently -/// ASCII + `#`/`\` only; non-ASCII identifiers get a Range with len up to the -/// first non-ASCII byte (only affects error-highlight width, not correctness). +/// Moved into logger to break logger→js_parser. Includes the full Unicode +/// `isIdentifierStart/Continue` tables (via `bun_core::identifier`) and +/// `\u{...}` escape skipping. pub fn range_of_identifier(contents: &[u8], loc: Loc) -> Range { if loc.start < 0 || (loc.start as usize) >= contents.len() { return Range::NONE; } let text = &contents[loc.start as usize..]; - let mut i = 0usize; - if text.first() == Some(&b'#') { - i = 1; + let mut r = Range { loc, len: 0 }; + if text.is_empty() { + return r; + } + let end = text.len() as u32; + + let iter = bun_core::strings::CodepointIterator::init(text); + let mut cursor = bun_core::strings::Cursor::default(); + if !iter.next(&mut cursor) { + return r; } - let is_start = |c: u8| c.is_ascii_alphabetic() || c == b'_' || c == b'$' || c == b'\\'; - let is_cont = |c: u8| c.is_ascii_alphanumeric() || c == b'_' || c == b'$' || c == b'\\'; - if i < text.len() && is_start(text[i]) { - i += 1; - while i < text.len() && is_cont(text[i]) { - i += 1; + + // Handle private names + if cursor.c == '#' as i32 { + if !iter.next(&mut cursor) { + r.len = 1; + return r; } } - Range { - loc, - len: i32::try_from(i).expect("int cast"), + + // Skip over bracketed unicode escapes such as "\u{10000}". The cursor is + // positioned on a `\`; advance `cursor.i` one past the closing `}` and + // zero `cursor.width` so the next decode starts exactly there (the manual + // advance already consumed the backslash's width — leaving `width` set + // would skip the byte after `}`). + let skip_bracketed_escape = |cursor: &mut bun_core::strings::Cursor| { + if cursor.i + 2 < end + && text[cursor.i as usize + 1] == b'u' + && text[cursor.i as usize + 2] == b'{' + { + cursor.i += 2; + while cursor.i < end { + if text[cursor.i as usize] == b'}' { + cursor.i += 1; + break; + } + cursor.i += 1; + } + cursor.width = 0; + } + }; + + if bun_core::identifier::is_identifier_start(cursor.c) || cursor.c == '\\' as i32 { + // An identifier may *start* with an escape (e.g. `\u{61}bc`); skip it + // here too, or the loop below reads the `u`/`{` as ordinary codepoints + // and truncates the range at the `{`. + if cursor.c == '\\' as i32 { + skip_bracketed_escape(&mut cursor); + } + while iter.next(&mut cursor) { + if cursor.c == '\\' as i32 { + skip_bracketed_escape(&mut cursor); + } else if !lexer_tables::is_identifier_continue(cursor.c) { + r.len = i32::try_from(cursor.i).expect("int cast"); + return r; + } + } + + // EOF inside the identifier: the cursor holds the *start* offset and + // width of the last codepoint read — include that codepoint. + r.len = i32::try_from(cursor.i + cursor.width as u32).expect("int cast"); } + + r } impl Range { @@ -1501,7 +1492,7 @@ impl Range { } pub fn end_i(self) -> usize { - // std.math.lossyCast(usize, ...) — saturates negatives to 0. + // Saturates negatives to 0. (self.loc.start + self.len).max(0) as usize } } @@ -1519,10 +1510,7 @@ pub struct Log { pub clone_line_text: bool, /// Owned backing storage for `Location.{file,line_text}` (and similar) - /// that came from transient buffers (e.g. native-plugin C strings). Zig - /// `log.msgs.allocator.dupe(u8, …)` allocates from the Log's allocator and - /// stores a raw slice in `Location` (logger.zig `Location.deinit` is a - /// no-op, so the bytes live as long as the Log). Rust models that as a + /// that came from transient buffers (e.g. native-plugin C strings): a /// side-vector of `Box<[u8]>` owned by the `Log`; [`Log::dupe`] returns a /// lifetime-erased borrow into the just-pushed box. The borrow is valid /// for the life of `self` because `Box<[u8]>` is heap-stable across `Vec` @@ -1557,7 +1545,7 @@ impl Default for Log { } impl Log { - /// Port of Zig's `log.msgs.allocator.dupe(u8, s)` pattern: copy `s` into + /// Copy `s` into /// storage owned by this `Log` and return a `&'static [u8]` view. The /// returned slice is valid for as long as `self` lives (the box is never /// moved out of `owned_strings`); `'static` is a lifetime erasure matching @@ -1596,7 +1584,6 @@ impl Level { (self as i8) <= (other as i8) } - // Zig: `pub const label: std.EnumArray(Level, string)` pub const LABEL: std::sync::LazyLock> = std::sync::LazyLock::new(|| { enum_map::EnumMap::from_fn(|k| match k { @@ -1608,7 +1595,6 @@ impl Level { }) }); - // Zig: `pub const Map = bun.ComptimeStringMap(Level, ...)` pub const MAP: phf::Map<&'static [u8], Level> = phf::phf_map! { b"verbose" => Level::Verbose, b"debug" => Level::Debug, @@ -1617,11 +1603,9 @@ impl Level { b"error" => Level::Err, }; - // Zig: `pub const fromJS = @import("../logger_jsc/...")` - // → deleted; lives in `bun_logger_jsc`. + // `from_js` lives in `bun_logger_jsc`. } -// Zig: `pub var default_log_level = Level.warn;` // PORTING.md §Global mutable state: written by CLI startup, read by every // `Log::init()` (including from bundler worker threads). `AtomicCell` // — Acquire/Release, no `unsafe` at call sites. @@ -1703,8 +1687,7 @@ impl Log { } } - /// Zig: `pub fn init(std.mem.Allocator param) Log` — Rust callers spell - /// this `Log::new()`; the allocator parameter is dropped (global allocator). + /// Alias of [`Log::init`]. #[inline] pub fn new() -> Log { Log::init() @@ -1756,8 +1739,7 @@ impl Log { } } - // Zig: `pub const toJS/toJSAggregateError/toJSArray = @import("../logger_jsc/...")` - // → deleted; live in `bun_logger_jsc`. + // `to_js`/`to_js_aggregate_error`/`to_js_array` live in `bun_logger_jsc`. pub fn clone_to(&mut self, other: &mut Log) { let mut notes_count: usize = 0; @@ -1769,17 +1751,14 @@ impl Log { } if notes_count > 0 { - // TODO(port): lifetime — Zig allocates one shared `[Data; notes_count]` - // buffer in `other`'s allocator and re-slices each `msg.notes` into it. - // With `Box<[Data]>` per-Msg we instead deep-copy each notes slice. + // Deep-copy each notes slice (per-Msg `Box<[Data]>` ownership). for msg in &mut self.msgs { msg.notes = msg.notes.to_vec().into_boxed_slice(); } } other.msgs.extend(self.msgs.iter().map(Msg::clone)); - // PORT NOTE: reshaped for borrowck — Zig appendSlice moves the (now - // re-sliced) Msgs; here we clone since `self` retains them. + // Clone rather than move — `self` retains its msgs. other.warnings += self.warnings; other.errors += self.errors; } @@ -1814,8 +1793,8 @@ impl Log { let mut notes_buf = vec![Data::default(); notes_count]; let mut note_i: usize = 0; - // PORT NOTE: reshaped for borrowck — Zig zips `self.msgs` with the - // tail of `other.msgs`; index instead. + // Index instead of zipping `self.msgs` with the tail of + // `other.msgs` to satisfy borrowck. for (k, msg) in self.msgs.iter().enumerate() { let j = dest_start + k; other.msgs[j] = @@ -1850,9 +1829,8 @@ impl Log { } } -// PORT NOTE: Zig `Log.deinit` only does `msgs.clearAndFree()` — field-free-only, -// so per PORTING.md no `impl Drop` is emitted (Vec drops automatically). -// The mid-life semantic operation is exposed as `clear_and_free` above. +// No `impl Drop` is needed (Vec drops automatically). The mid-life +// semantic operation is exposed as `clear_and_free` above. impl Log { #[cold] @@ -1884,7 +1862,7 @@ impl Log { } /// Shared, non-generic tail for the `add*Fmt` family. The public wrappers - /// are `inline` and only do the per-call-site `allocPrint(fmt, args)`; the + /// are `inline` and only do the per-call-site formatting; the /// rest (counter bump, rangeData, cloneLineText, addMsg) lives here so it /// isn't re-stamped for every distinct format string. ~165 callers of /// `addErrorFmt` alone used to duplicate this body. @@ -1929,9 +1907,8 @@ impl Log { err: bun_core::Error, ) { let text = alloc_print(args); - // TODO: fix this. this is stupid, it should be returned in allocPrint. - // PORT NOTE: Zig reads `args.@"0"` (first tuple element) for the - // specifier; with `fmt::Arguments` that's opaque, so callers must pass + // TODO: fix this. this is stupid, the specifier should be returned by + // `alloc_print`. `fmt::Arguments` is opaque, so callers must pass // `specifier_arg` explicitly. let specifier = BabyString::r#in(&text, specifier_arg); if IS_ERR { @@ -1945,7 +1922,6 @@ impl Log { let mut _data = self.tracked_range_data(source, r, text); if let Some(loc) = &mut _data.location { if let Some(_line) = loc.line_text.as_deref() { - // Zig: `try log.msgs.allocator.dupe(u8, line)`. loc.line_text = Some(Cow::Owned(_line.to_vec())); } } @@ -1956,7 +1932,6 @@ impl Log { }; let msg = Msg { - // .kind = if (comptime error_type == .err) Kind.err else Kind.warn, kind: if IS_ERR { Kind::Err } else { Kind::Warn }, data, metadata: Metadata::Resolve(MetadataResolve { @@ -2175,8 +2150,8 @@ impl Log { let data = Data { text: alloc_print(args), location: Some(Location { - // TODO(port): lifetime — `Location.file` borrows `Str`; thread - // real ownership through (see module doc). + // `Location.file` borrows the lifetime-erased `Str`; see the + // module-level OWNERSHIP note. file: Cow::Borrowed(filepath), line: i32::try_from(line).expect("int cast"), column: i32::try_from(col).expect("int cast"), @@ -2193,8 +2168,6 @@ impl Log { }) } - // (Zig has a large commented-out `addWarningFmtLineColWithNote` here — omitted.) - #[inline] pub fn add_range_warning_fmt( &mut self, @@ -2391,8 +2364,6 @@ impl Log { self.warnings += 1; let data = self.tracked_range_data(source, r, text); self.add_msg(Msg { - // PORT NOTE: Zig has `.kind = .warning` here which doesn't exist in - // `Kind`; presumed dead code / typo for `.warn`. kind: Kind::Warn, data, notes, @@ -2538,12 +2509,11 @@ pub struct AddErrorOptions<'a> { } /// Downstream-compat alias: some callers (`bunfig.rs`, `PnpmMatcher.rs`) spell -/// the option-struct as `bun_ast::ErrorOpts { .. }` (Zig: `Log.addError*` opts -/// param). Same layout as `AddErrorOptions`; the canonical name is kept while -/// the Zig side still calls it `addErrorOpts`. +/// the option-struct as `bun_ast::ErrorOpts { .. }`. Same layout as +/// `AddErrorOptions`. pub type ErrorOpts<'a> = AddErrorOptions<'a>; -/// Call-site helper that mirrors Zig `allocPrint`: rewrites `..` markup +/// Call-site helper: rewrites `..` markup /// in the *literal* format string via `bun_core::pretty_fmt!` (compile-time), /// then formats. Expands to a `fmt::Arguments` so it drops in wherever a /// pre-built `fmt::Arguments` was previously passed to `alloc_print`. @@ -2572,8 +2542,8 @@ macro_rules! alloc_print { }; } -/// `add_error_pretty!(log, source, loc, "...", args..)` — call-site form -/// of Zig `addErrorFmt`: rewrites `` markup in the *literal* format string +/// `add_error_pretty!(log, source, loc, "...", args..)` — call-site +/// helper that rewrites `` markup in the *literal* format string /// at compile time (via `bun_core::pretty_fmt!`) before interpolation, then /// calls `Log::add_error_fmt`. Use this instead of /// `add_error_fmt(.., format_args!("..."))` so markup is converted/stripped @@ -2624,9 +2594,8 @@ macro_rules! add_warning_pretty { #[inline] pub fn alloc_print(args: fmt::Arguments<'_>) -> Cow<'static, [u8]> { - // Zig `allocPrint` runs `Output.prettyFmt(fmt, enable_ansi_colors)` at - // comptime over the *format-string literal only*, then interpolates args - // afterward — interpolated values are never inspected for `<..>` markup. + // Markup conversion happens over the *format-string literal only*; + // interpolated values are never inspected for `<..>` markup. // With `fmt::Arguments` the literal is opaque, so callers that need markup // conversion must go through `pretty_format_args!` / `alloc_print!` above // (which do the rewrite at the macro call site). The function form here @@ -2636,9 +2605,8 @@ pub fn alloc_print(args: fmt::Arguments<'_>) -> Cow<'static, [u8]> { use std::io::Write; let mut v = Vec::new(); let _ = write!(&mut v, "{}", args); - // Zig returns an allocator-owned slice that the Log takes ownership of via - // `Data.text` and frees in `Data.deinit`. `Cow::Owned` gives the same - // ownership: `Data` (via `Drop`) frees it. + // `Cow::Owned`: the `Log` takes ownership via `Data.text` and `Data`'s + // `Drop` frees it. Cow::Owned(v) } @@ -2657,10 +2625,10 @@ pub fn usize2loc(loc: usize) -> Loc { pub struct Source { pub path: bun_paths::fs::Path<'static>, - /// PORT NOTE: `Cow` so `source_from_file` / `File::to_source_at` can hand + /// `Cow` so `source_from_file` / `File::to_source_at` can hand /// back a heap buffer without leaking (PORTING.md §Forbidden). Borrowed - /// arm covers the Zig `[]const u8`-field default (parser/transpiler feed - /// arena slices via `IntoStr`). Prefer the `.contents()` accessor at + /// arm covers parser/transpiler-fed + /// arena slices (via `IntoStr`). Prefer the `.contents()` accessor at /// call-sites — it derefs to `&[u8]` regardless of arm. pub contents: Cow<'static, [u8]>, pub contents_is_recycled: bool, @@ -2668,10 +2636,9 @@ pub struct Source { /// Lazily-generated human-readable identifier name that is non-unique /// Avoid accessing this directly most of the time /// - /// PORT NOTE: `Cow` because the cached value is produced by - /// `MutableString::ensure_valid_identifier` (owned `Box<[u8]>`); the Zig - /// freed it in `deinit`, so per PORTING.md §Forbidden this cannot be - /// `&'static [u8]` + leak. + /// `Cow` because the cached value is produced by + /// `MutableString::ensure_valid_identifier` (owned `Box<[u8]>`); per + /// PORTING.md §Forbidden this cannot be `&'static [u8]` + leak. pub identifier_name: Cow<'static, [u8]>, pub index: Index, @@ -2897,8 +2864,7 @@ impl Source { &self.contents } - /// Owned copy of the source bytes. Mirrors the Zig pattern of - /// `allocator.dupe(u8, source.contents)` at call-sites that need to retain + /// Owned copy of the source bytes, for call-sites that need to retain /// the bytes past the `Source`'s lifetime. #[inline] pub fn contents_owned(&self) -> Vec { @@ -2910,7 +2876,6 @@ impl Source { } pub fn identifier_name(&mut self) -> Result<&[u8], bun_core::Error> { - // TODO(port): narrow error set if !self.identifier_name.is_empty() { return Ok(&self.identifier_name); } @@ -2924,8 +2889,7 @@ impl Source { } pub fn range_of_identifier(&self, loc: Loc) -> Range { - // Local impl mirrors src/js_parser/lexer.zig:range_of_identifier — scan from `loc` - // while bytes are JS identifier-part. + // Scan from `loc` while bytes are JS identifier-part. range_of_identifier(&self.contents, loc) } @@ -3141,7 +3105,7 @@ pub fn range_data(source: Option<&Source>, r: Range, text: impl IntoText) -> Dat // ─────────────────────────────────────────────────────────────────────────── // File → Source helpers — `bun_sys` (T1) cannot name `Source` (this crate), -// so the body of `src/sys/File.zig:toSourceAt/toSource` lives here as free fns. +// so the file→Source conversions live here as free fns. // ─────────────────────────────────────────────────────────────────────────── #[derive(Default, Clone, Copy)] @@ -3155,16 +3119,14 @@ pub type ToSourceOpts = ToSourceOptions; /// Read `path` (rooted at cwd) into memory and wrap it in a `Source`. /// -/// MOVE_DOWN from `bun_sys::File::to_source` (T1 cannot name T2). Zig source: -/// `src/sys/File.zig:toSource`. +/// MOVE_DOWN from `bun_sys::File::to_source` (T1 cannot name T2). pub fn source_from_file(path: &bun_core::ZStr, opts: ToSourceOptions) -> bun_sys::Maybe { source_from_file_at(bun_sys::Fd::cwd(), path, opts) } /// Read `path` (relative to `dir_fd`) into memory and wrap it in a `Source`. /// -/// MOVE_DOWN from `bun_sys::File::to_source_at`. Zig source: -/// `src/sys/File.zig:toSourceAt`. +/// MOVE_DOWN from `bun_sys::File::to_source_at` (T1 cannot name T2). pub(crate) fn source_from_file_at( dir_fd: bun_sys::Fd, path: &bun_core::ZStr, @@ -3270,7 +3232,7 @@ pub use ts::{TSNamespaceMember, TSNamespaceMemberMap, TSNamespaceScope}; pub use use_directive::UseDirective; /// `Part.{SymbolUseMap, SymbolPropertyUseMap, List}` — module-style alias so -/// `crate::part::{SymbolUseMap, List}` resolves at the Zig nested-decl path. +/// `crate::part::{SymbolUseMap, List}` resolves. pub mod part { pub use crate::nodes::{ Part, PartList as List, PartSymbolPropertyUseMap as SymbolPropertyUseMap, @@ -3324,8 +3286,6 @@ pub mod flags { /// Detected indentation of a [`Source`] (tab vs N-space). The JSON/TOML lexers /// record this so a `package.json` round-trip preserves the user's formatting; /// `bun_js_printer::Options.indent` consumes it. Default: 2 spaces. -/// -/// Zig: `src/js_printer/js_printer.zig:434` `Options.Indentation`. #[derive(Clone, Copy)] pub struct Indentation { pub scalar: usize, @@ -3349,28 +3309,61 @@ pub enum IndentationCharacter { Space, } -// ported from: src/logger/logger.zig - // ─────────────────────────────────────────────────────────────────────────── // Store helpers — debug guards + thread-local side-arena lifecycle for the // AST `NewStore` slabs. // ─────────────────────────────────────────────────────────────────────────── -/// `bun.DebugOnlyDisabler(T)` — debug-build re-entrancy guard around Store -/// access. No-op in release; in debug, asserts `!disabled`. +/// `DebugOnlyDisabler` — debug-build re-entrancy guard around Store +/// access. No-op in release; in debug, `assert()` panics while the per-type +/// disable count is non-zero. +/// +/// Rust cannot declare a generic `#[thread_local]` static, so the debug +/// build keeps one thread-local `TypeId` multiset (each `disable()` +/// pushes `TypeId::of::()`, each `enable()` pops one occurrence; the +/// membership count is the per-type counter). pub struct DebugOnlyDisabler(core::marker::PhantomData); -impl DebugOnlyDisabler { + +#[cfg(debug_assertions)] +mod debug_disabler_state { + std::thread_local! { + /// Multiset of currently-disabled store types (one entry per + /// outstanding `disable()`); debug builds only. + pub(super) static DISABLED: core::cell::RefCell> = + const { core::cell::RefCell::new(Vec::new()) }; + } +} + +impl DebugOnlyDisabler { #[inline] pub fn assert() { - // TODO(port): wire to a thread-local `disabled: bool` if any caller - // actually toggles it; Zig sites only call `assert()`. + #[cfg(debug_assertions)] + debug_disabler_state::DISABLED.with(|d| { + assert!( + !d.borrow().contains(&core::any::TypeId::of::()), + "[{}] called while disabled (did you forget to call enable?)", + core::any::type_name::(), + ); + }); } #[inline] - pub fn disable() {} + pub fn disable() { + #[cfg(debug_assertions)] + debug_disabler_state::DISABLED.with(|d| d.borrow_mut().push(core::any::TypeId::of::())); + } #[inline] - pub fn enable() {} - /// RAII scope: `disable()` now, `enable()` on drop. Replaces the Zig idiom - /// `Disabler.disable(); defer Disabler.enable();`. + pub fn enable() { + #[cfg(debug_assertions)] + debug_disabler_state::DISABLED.with(|d| { + let mut v = d.borrow_mut(); + let pos = v + .iter() + .rposition(|t| *t == core::any::TypeId::of::()) + .expect("DebugOnlyDisabler::enable without matching disable"); + v.remove(pos); + }); + } + /// RAII scope: `disable()` now, `enable()` on drop. #[inline] pub fn scope() -> DebugOnlyDisablerScope { Self::disable(); @@ -3380,8 +3373,8 @@ impl DebugOnlyDisabler { /// Guard returned by [`DebugOnlyDisabler::scope`]; re-enables on drop. #[must_use = "disabler is re-enabled on drop; bind to a named local"] -pub struct DebugOnlyDisablerScope(core::marker::PhantomData); -impl Drop for DebugOnlyDisablerScope { +pub struct DebugOnlyDisablerScope(core::marker::PhantomData); +impl Drop for DebugOnlyDisablerScope { #[inline] fn drop(&mut self) { DebugOnlyDisabler::::enable(); @@ -3463,7 +3456,7 @@ pub mod store_ast_alloc_heap { // ── DATA_STORE_OVERRIDE ──────────────────────────────────────────────────── // Thread-local override arena for `Expr`/`Stmt` boxed payloads. // -// Zig: `Expr.Data.Store.memory_allocator` — when non-null, `Expr::init` +// When non-null, `Expr::init` // allocates boxed payloads into this arena instead of the long-lived block // store, so a scoped caller (YAML/TOML/JSONC parse) can bulk-free the whole // tree by dropping the arena. Set/restored by `ASTMemoryAllocator::Scope`. @@ -3482,8 +3475,7 @@ pub(crate) fn set_data_store_override(p: *const bun_alloc::Arena) { /// Copy `bytes` into the active AST arena so the slice shares the same /// lifetime as the `StoreRef`-backed `Expr` nodes that reference it -/// (bulk-freed on Store reset). Mirrors Zig call sites that write -/// `Expr.init(E.String, .{ .data = try allocator.dupe(u8, …) }, …)`: callers +/// (bulk-freed on Store reset). Callers /// building an `EString` from a scratch buffer must intern the bytes here, not /// into a function-local bump, or `EString.data` dangles when that bump drops. /// The lifetime is erased per the `StoreStr` convention — arena ownership, not @@ -3559,9 +3551,6 @@ impl Drop for StoreResetGuard { /// no-op once the slab (or an `ASTMemoryAllocator` override) is installed, /// so no `Once` guard is needed (and a process-global `Once` would be wrong /// anyway: the backing `INSTANCE` is `#[thread_local]`). -/// -/// Zig: open-coded `Expr.Data.Store.create(); Stmt.Data.Store.create();` -/// at every CLI entry point (transpiler.zig, run_command.zig, …). #[inline] pub fn initialize_store() { expr::data::Store::create(); @@ -3572,8 +3561,6 @@ pub fn initialize_store() { /// subsequent call. Maps to `Store::begin()` (create-or-reset) on each /// slab, so callers that re-enter — e.g. the install pipeline parsing many /// `package.json`s — get a fresh arena each time without re-allocating. -/// -/// Zig: install.zig `initializeStore()` (`if (initialized_store) reset else create`). #[inline] pub fn initialize_store_or_reset() { expr::data::Store::begin(); @@ -3600,6 +3587,55 @@ impl Drop for DisableStoreReset { } } +#[cfg(test)] +mod range_of_identifier_tests { + use super::*; + + fn len_of(src: &[u8]) -> i32 { + range_of_identifier(src, Loc { start: 0 }).len + } + + #[test] + fn terminated_identifier() { + assert_eq!(len_of(b"foo = 1"), 3); + assert_eq!(len_of(b"x;"), 1); + } + + #[test] + fn identifier_at_eof_includes_last_codepoint() { + assert_eq!(len_of(b"foo"), 3); + assert_eq!(len_of(b"x"), 1); + // Multibyte final codepoint: 'é' is 2 bytes. + assert_eq!(len_of("aé".as_bytes()), 3); + } + + #[test] + fn private_names() { + assert_eq!(len_of(b"#"), 1); + assert_eq!(len_of(b"#foo = 1"), 4); + assert_eq!(len_of(b"#foo"), 4); + } + + #[test] + fn bracketed_escape_does_not_swallow_terminator() { + // `a\u{41}` is 7 bytes; the `.` must terminate the range. + assert_eq!(len_of(b"a\\u{41}.x"), 7); + assert_eq!(len_of(b"a\\u{41} = 1"), 7); + } + + #[test] + fn leading_bracketed_escape() { + assert_eq!(len_of(b"\\u{61}bc = 1"), 8); + assert_eq!(len_of(b"\\u{61}"), 6); + } + + #[test] + fn non_identifier_start() { + assert_eq!(len_of(b"1abc"), 0); + assert_eq!(len_of(b" foo"), 0); + } +} + #[cfg(test)] mod line_column_tracker_tests { use super::*; diff --git a/src/ast/loader.rs b/src/ast/loader.rs index 3759ed20baf..646bae167d8 100644 --- a/src/ast/loader.rs +++ b/src/ast/loader.rs @@ -1,4 +1,4 @@ -//! `bundler/options.zig` `Loader` + `SideEffects`. +//! `Loader` + `SideEffects`. //! //! Data-only enum + pure predicates. `to_api()` / `from_api()` / `API_NAMES` / //! `LoaderOptional::from_api` live in `bun_options_types::LoaderExt` (would @@ -14,8 +14,19 @@ use phf; /// - bun-native-bundler-plugin-api/bundler_plugin.h /// - src/jsc/bindings/headers-handwritten.h #[repr(u8)] -#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Enum, strum::IntoStaticStr)] -// Zig field names are lower_snake — `@tagName` is exposed to JS (HTMLImportManifest +#[derive( + Copy, + Clone, + Default, + Eq, + PartialEq, + Debug, + Hash, + Enum, + strum::IntoStaticStr, + strum::VariantNames, +)] +// The lower_snake names are exposed to JS (HTMLImportManifest // `"loader":`, BuildArtifact.loader) so the strum serialization must match exactly. #[strum(serialize_all = "snake_case")] pub enum Loader { @@ -24,6 +35,7 @@ pub enum Loader { Ts = 2, Tsx = 3, Css = 4, + #[default] File = 5, Json = 6, Jsonc = 7, @@ -46,7 +58,7 @@ pub enum Loader { // `OnBeforeParseArguments` / `OnBeforeParseResult` (`bundler_plugin.h`); lock // the discriminant width and the values native plugins observe. NB: the C // header's `BUN_LOADER_TOML = 7` etc. predate `Jsonc`'s insertion at 7 and are -// known-stale — Zig `options.zig` is the source of truth, which Rust matches. +// known-stale — this enum is the source of truth. bun_core::assert_ffi_discr!( Loader, u8; Jsx = 0, Js = 1, Ts = 2, Tsx = 3, Css = 4, File = 5, Json = 6, @@ -54,13 +66,6 @@ bun_core::assert_ffi_discr!( Text = 13, Bunsh = 14, Sqlite = 15, SqliteEmbedded = 16, Html = 17, ); -impl Default for Loader { - /// Mirrors Zig's `Loader = .file` default field initializer. - fn default() -> Self { - Loader::File - } -} - /// `Loader.Optional` — `enum(u8) { none = 254, _ }` niche-packed optional. #[repr(transparent)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -75,8 +80,7 @@ impl LoaderOptional { } pub fn unwrap(self) -> Option { - // Spec options.zig:594-596 uses `@enumFromInt(@intFromEnum(opt))` which is - // debug-checked. PORTING.md §Forbidden patterns bars transmute-to-enum; + // PORTING.md §Forbidden patterns bars transmute-to-enum; // exhaustive match so out-of-range tags are debug-asserted, never UB. match self.0 { 0 => Some(Loader::Jsx), @@ -221,7 +225,6 @@ impl Loader { } else { slice_ }; - // Zig: names.getWithEql(slice, strings.eqlCaseInsensitiveASCIIICheckLength) // phf is case-sensitive, so fall back to a case-insensitive scan over NAMES.entries(). Self::NAMES.get(slice).copied().or_else(|| { Self::NAMES @@ -246,7 +249,7 @@ impl Loader { matches!(self, Loader::Jsx | Loader::Js | Loader::Ts | Loader::Tsx) } - // PORT NOTE: spelling-aliases for the canonical `is_typescript` / + // Spelling-aliases for the canonical `is_typescript` / // `is_javascript_like*` (acronym-collapsing rule). Hoisted from // `bun_bundler::options::LoaderExt` so cross-crate callers (bun_jsc, // bun_runtime) resolve them as inherent methods without a trait import. @@ -293,7 +296,6 @@ impl Loader { } } -/// `resolver/resolver.zig` `SideEffects`. #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] pub enum SideEffects { @@ -317,5 +319,3 @@ pub enum SideEffects { // /// Removing the import would not call the plugin which is observable. // NoSideEffectsPureDataFromPlugin, } - -// ported from: src/options_types/BundleEnums.zig (Loader, SideEffects) diff --git a/src/ast/new_store.rs b/src/ast/new_store.rs index a28ec01048a..39c3c913af0 100644 --- a/src/ast/new_store.rs +++ b/src/ast/new_store.rs @@ -10,8 +10,21 @@ // Scope name distinct from the macro-generated `struct Store`. ::bun_core::declare_scope!(STORE_LOG, hidden); -/// Zig: `pub fn NewStore(comptime types: []const type, comptime count: usize) type` +/// Compile-time membership check for +/// the type list of a `new_store!`-generated store. /// +/// `new_store!` implements this for every `$T` in its list (with `S` = the +/// generated `Store` type), and bounds `Store::allocate`/`Store::append` (and +/// the `thread_local_ast_store!` front-end `append`) on it. Allocating a type +/// the store was not declared with is therefore a compile error. +pub trait StoredIn: sealed::Sealed {} + +#[doc(hidden)] +pub mod sealed { + /// Sealing supertrait — only `new_store!` expansions implement it. + pub trait Sealed {} +} + /// Rust cannot take a slice of types as a generic parameter, and the body /// derives array sizes and alignment from that list (which would require /// `generic_const_exprs`). Per PORTING.md this falls under the @@ -29,13 +42,11 @@ macro_rules! new_store { use ::core::mem::{align_of, size_of, MaybeUninit}; use ::core::ptr::{addr_of_mut, NonNull}; - // Zig: `const largest_size, const largest_align = brk: { ... }` const LARGEST_SIZE: usize = { let sizes = [$(size_of::<$T>()),+]; let mut largest_size = 0; let mut i = 0; while i < sizes.len() { - // Zig: `@compileError("NewStore does not support 0 size type: " ++ @typeName(T))` assert!(sizes[i] > 0, "NewStore does not support 0 size type"); if sizes[i] > largest_size { largest_size = sizes[i]; } i += 1; @@ -53,19 +64,18 @@ macro_rules! new_store { largest_align }; - // Zig: `const backing_allocator = bun.default_allocator;` - // (deleted — global mimalloc via #[global_allocator]; Box/alloc use it.) - - // Zig: `const log = Output.scoped(.Store, .hidden);` - // (declared once at crate level: `bun_output::declare_scope!(Store, hidden);`) + // Allocation goes through the global mimalloc allocator + // (#[global_allocator]); Box/alloc use it. + // The log scope is declared once at crate level: + // `bun_output::declare_scope!(Store, hidden);` pub struct Store { /// Lazily-allocated head of the block chain — `None` until the /// first [`Store::allocate`]. Owns the entire `Box` /// `next`-linked list; `Store`'s `Drop` walks it iteratively. /// - /// PERF(port): Zig co-allocated `Store` + the first `Block` in a - /// single `PreAlloc` so `create()` always paid one `~BLOCK_SIZE` + /// An earlier design co-allocated `Store` + the first + /// `Block` so `create()` always paid one `~BLOCK_SIZE` /// malloc. Splitting them lets a store that is `create()`d but /// never written to (e.g. the `Stmt` store during /// `Transpiler::configure_defines`, which only emits `E::String` @@ -81,14 +91,12 @@ macro_rules! new_store { debug_lock: ::core::cell::Cell, } - /// Zig: `pub const Block = struct { ... }` - // PORT NOTE: `buffer` needs `align(LARGEST_ALIGN)` but `#[repr(align(N))]` + // `buffer` needs `align(LARGEST_ALIGN)` but `#[repr(align(N))]` // requires a literal. Over-approximate with align(16) — every AST payload // type is `<= 16` aligned (asserted below). Switch to a // `#[repr(C)] union AlignUnion { $($T),+ }` element type if a >16-aligned // payload is ever introduced. const _: () = assert!(LARGEST_ALIGN <= 16, "NewStore payload type with align>16; bump Block repr(align)"); - /// Zig: `pub const size = largest_size * count * 2;` pub(crate) const BLOCK_SIZE: usize = LARGEST_SIZE * $count * 2; #[repr(C, align(16))] pub struct Block { @@ -97,11 +105,19 @@ macro_rules! new_store { next: Option>, } + // Only the types listed in this `new_store!` + // invocation may be allocated from this store. `allocate`/`append` + // are bounded on `StoredIn`, so an unsupported type is a + // compile error. + $( + impl $crate::new_store::sealed::Sealed for $T {} + impl $crate::new_store::StoredIn for $T {} + )+ + impl Block { pub const SIZE: usize = BLOCK_SIZE; - // Zig: `pub const Size = std.math.IntFittingRange(0, size + largest_size);` - // PERF(port): was IntFittingRange — picks smallest uN; using u32 (Block::SIZE + // PERF: could pick the smallest uN that fits; using u32 (Block::SIZE // for AST node stores fits comfortably). Profile. /// Initialize the non-buffer fields without touching the (large, @@ -119,7 +135,7 @@ macro_rules! new_store { } pub fn try_alloc(block: &mut Block) -> Option> { - // Zig: `std.mem.alignForward(usize, block.bytes_used, @alignOf(T))` + // Align `bytes_used` forward to `align_of::()`. let start = ((block.bytes_used as usize) + align_of::() - 1) & !(align_of::() - 1); if start + size_of::() > block.buffer.len() { @@ -133,13 +149,13 @@ macro_rules! new_store { let _ = &block.buffer[block.bytes_used as usize..][..size_of::()]; } - // Zig: `defer block.bytes_used = @intCast(start + @sizeOf(T));` block.bytes_used = BlockSize::try_from(start + size_of::()).unwrap(); // SAFETY: `start` is in-bounds (checked above) and aligned for T // (align_forward above). Buffer base alignment must be >= align_of::() - // — see TODO(port) on Block re: LARGEST_ALIGN. + // — guaranteed by `repr(align(16))` on Block plus the + // `LARGEST_ALIGN <= 16` const assert above. Some(unsafe { NonNull::new_unchecked( block.buffer.as_mut_ptr().add(start).cast::(), @@ -149,7 +165,6 @@ macro_rules! new_store { /// Heap-allocate a Block without placing the (large) buffer on the stack. fn new_boxed() -> Box { - // Zig: `backing_allocator.create(Block)` then `.zero()` let mut b: Box> = Box::new_uninit(); Block::zero(&mut b); // SAFETY: `zero` initialized every non-buffer field; `buffer` is @@ -158,7 +173,6 @@ macro_rules! new_store { } } - // Zig: `pub const Size = std.math.IntFittingRange(0, size + largest_size);` type BlockSize = u32; /// `Store` owns its `Box` chain (`head` → `next` → …). The @@ -171,7 +185,6 @@ macro_rules! new_store { while let Some(mut block) = it { #[cfg(debug_assertions)] { - // Zig: `@memset(block.buffer, undefined);` // SAFETY: poisoning a buffer that is being freed. unsafe { ::core::ptr::write_bytes( @@ -190,11 +203,10 @@ macro_rules! new_store { impl Store { pub fn init() -> *mut Store { /* scoped_log elided — debug_logs feature only */ - // PERF(port): the first `Block`'s ~`BLOCK_SIZE` heap buffer + // The first `Block`'s ~`BLOCK_SIZE` heap buffer // is *not* allocated here — only the small `Store` header. // `allocate()` lazily mallocs the first `Block` on the first - // `append()` (see the `head` field doc). Box aborts on OOM - // (matches Zig `bun.handleOom`). + // `append()` (see the `head` field doc). Box aborts on OOM. bun_core::heap::into_raw(Box::new(Store { head: None, current: ::core::ptr::null_mut(), @@ -230,7 +242,6 @@ macro_rules! new_store { // singly-linked list; walk it via `&mut` reborrows. let mut it: Option<&mut Block> = store.head.as_deref_mut(); while let Some(block) = it { - // Zig: `block.bytes_used = undefined; @memset(&block.buffer, undefined);` // SAFETY: poisoning; buffer is MaybeUninit. unsafe { ::core::ptr::write_bytes( @@ -257,10 +268,8 @@ macro_rules! new_store { store.current = head_ptr; } - fn allocate(store: &mut Store) -> NonNull { + fn allocate>(store: &mut Store) -> NonNull { debug_assert!(size_of::() > 0); // don't allocate! - // TODO(port): `comptime if (!supportsType(T)) @compileError(...)` — - // enforce via a sealed trait generated over `$($T),+`. // Lazily materialise the first `Block` on first use — this is // the only `~BLOCK_SIZE` allocation a never-written store @@ -304,7 +313,10 @@ macro_rules! new_store { } #[inline] - pub fn append(store: &mut Store, data: T) -> NonNull { + pub fn append>( + store: &mut Store, + data: T, + ) -> NonNull { let ptr = Store::allocate::(store); /* scoped_log elided — debug_logs feature only */ // SAFETY: `allocate` returned aligned, in-bounds, exclusive storage for T. @@ -332,9 +344,8 @@ macro_rules! new_store { let _ = store; } - // Zig: `fn supportsType(T: type) bool` - // TODO(port): comptime type-list membership check; replace with sealed - // trait `Stored` impl'd for each `$($T),+` and bound `allocate`. + // Type-list membership is enforced by the + // `StoredIn` impls generated above `impl Block`. } } }; @@ -348,8 +359,7 @@ macro_rules! new_store { // optional `ASTMemoryAllocator` override, `disable_reset` flag) plus the // twelve identical accessor/lifecycle fns. The two hand-written copies in // expr.rs / stmt.rs were byte-for-byte twins modulo the backing type and the -// "Expr"/"Stmt" panic-string label — and so are the Zig originals -// (expr.zig:3117-3196 vs stmt.zig:300-382). This macro stamps out one +// "Expr"/"Stmt" panic-string label. This macro stamps out one // `pub mod Store { … }` per call site so the duplication lives here once. // // Why a macro and not a generic struct: `#[thread_local] static` cannot be @@ -375,7 +385,7 @@ macro_rules! thread_local_ast_store { // read on every node `alloc` (the hottest TLS in the parser), and // the `thread_local!` macro's `LocalKey` wrapper showed up in // next-lint profiles. All three are `Cell` (no destructor, - // const init); matches Zig `threadlocal var`. + // const init). #[thread_local] pub(crate) static INSTANCE: Cell<*mut Backing> = Cell::new(::core::ptr::null_mut()); /// Back-reference to the `ASTMemoryAllocator` installed by the @@ -401,8 +411,9 @@ macro_rules! thread_local_ast_store { #[inline] fn instance_mut<'a>() -> Option<&'a mut Backing> { // SAFETY: `INSTANCE` is thread-local; the `*mut Backing` it holds - // is either null or was returned by `Backing::init()` (leaked - // `PreAlloc`) and remains valid until `deinit()` clears it. + // is either null or was returned by `Backing::init()` (a `Box` + // leaked via `bun_core::heap::into_raw`) and remains valid + // until `deinit()` clears it. // Single-threaded access — no other `&mut` to the slab is live. unsafe { INSTANCE.get().as_mut() } } @@ -449,7 +460,7 @@ macro_rules! thread_local_ast_store { ); } - /// Zig: `Data.Store.disable_reset = b;` — toggled by long-lived + /// Toggled by long-lived /// callers (transpiler, bundler) that want the Store to persist /// across multiple parse calls. #[inline] @@ -481,7 +492,9 @@ macro_rules! thread_local_ast_store { } #[inline] - pub fn append(value: T) -> $crate::StoreRef { + pub fn append>( + value: T, + ) -> $crate::StoreRef { if let Some(ma) = MEMORY_ALLOCATOR.get() { // `BackRef: Deref` — owning scope outlives this call. return ma.append(value); @@ -497,5 +510,3 @@ macro_rules! thread_local_ast_store { } }; } - -// ported from: src/js_parser/ast/NewStore.zig diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index c6f67e8f68b..e6deded40dc 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -19,17 +19,16 @@ pub use crate::flags as Flags; // // Thin `NonNull` newtype — `Copy`, `Deref`/`DerefMut`. The pointee lives // until the owning Store/arena is `reset()`; callers must not hold a `StoreRef` -// across that boundary. Matches Zig's `*T` payloads in `Expr.Data`. +// across that boundary. // ─────────────────────────────────────────────────────────────────────────── #[repr(transparent)] pub struct StoreRef(NonNull); -// SAFETY: `StoreRef` is a thin pointer into a single-threaded bump arena (Zig -// `*T`). We assert Send/Sync so payload types embedding `Option>` -// (e.g. `E::EString::next`) can sit in `static` tables — matches Zig where raw -// pointers carry no thread-affinity. Callers are responsible for not actually -// sharing a Store across threads (same contract as the Zig original). +// SAFETY: `StoreRef` is a thin pointer into a single-threaded bump arena. +// We assert Send/Sync so payload types embedding `Option>` +// (e.g. `E::EString::next`) can sit in `static` tables. Callers are +// responsible for not actually sharing a Store across threads. // // Bounded on `T` so `StoreRef` cannot launder a `!Send`/`!Sync` payload (e.g. // `StoreRef>`) past auto-trait inference: `Deref` yields `&T` (needs @@ -58,11 +57,10 @@ impl StoreRef { pub fn from_bump(r: &mut T) -> Self { StoreRef(NonNull::from(r)) } - /// Consume a `Box` whose payload must outlive every Store reset - /// (Zig `deepClone(default_allocator)` semantics). Ownership transfers to - /// the returned `StoreRef`; the allocation is process-lifetime by design - /// and is never dropped — mirrors `bun.default_allocator.create(T)` with - /// no paired `destroy`. Prefer `from_bump` for arena-backed nodes. + /// Consume a `Box` whose payload must outlive every Store reset. + /// Ownership transfers to the returned `StoreRef`; the allocation is + /// process-lifetime by design and is never dropped. Prefer `from_bump` + /// for arena-backed nodes. #[inline] pub fn from_box(b: Box) -> Self { StoreRef(bun_core::heap::into_raw_nn(b)) @@ -76,10 +74,9 @@ impl StoreRef { #[inline] pub const fn from_static(r: &'static T) -> Self { // SAFETY: `r` is a non-null, aligned, dereferenceable `'static` - // reference. Provenance is shared/read-only: this mirrors Zig - // `@constCast` on prefill tables. The pointee is *never* written - // through — `DerefMut` on a `StoreRef` produced here is UB and callers - // must not do so (audited: only `Deref`/`get()` reads occur). + // reference. Provenance is shared/read-only: the pointee is *never* + // written through — `DerefMut` on a `StoreRef` produced here is UB and + // callers must not do so (audited: only `Deref`/`get()` reads occur). StoreRef(unsafe { NonNull::new_unchecked(core::ptr::from_ref(r).cast_mut()) }) } /// Borrow the pointee (explicit form of `Deref`). @@ -108,8 +105,7 @@ impl DerefMut for StoreRef { fn deref_mut(&mut self) -> &mut T { // SAFETY: StoreRef invariant. AST nodes are mutated in-place during // visiting; no two `StoreRef` to the same node are deref'd `&mut` - // simultaneously in single-threaded parser/visitor passes — same - // contract as the Zig original. + // simultaneously in single-threaded parser/visitor passes. unsafe { self.0.as_mut() } } } @@ -119,8 +115,7 @@ impl From> for StoreRef { StoreRef(p) } } -/// Pointer-identity comparison (matches the `NonNull`/Zig `*T` semantics -/// of the field this type replaces). +/// Pointer-identity comparison. impl PartialEq for StoreRef { #[inline] fn eq(&self, other: &Self) -> bool { @@ -162,7 +157,7 @@ pub struct StoreStr { } // SAFETY: same rationale as `StoreRef` — points into a single-threaded bump -// arena (Zig `[]const u8`). Asserted Send/Sync so payload types can sit in +// arena. Asserted Send/Sync so payload types can sit in // `static` Prefill tables; callers must not actually share a Store across // threads (unchanged contract). unsafe impl Send for StoreStr {} @@ -318,8 +313,7 @@ impl core::fmt::Debug for StoreStr { // per-node `[Stmt]`/`[Expr]` views, …) that borrow from the parse arena. // Same contract as `StoreRef`/`StoreStr`: safe `::new`, // raw `NonNull` + `u32` length, `Deref`, valid until the -// owning arena resets. The `u32` length matches Zig's `[]T` (`u32` len under -// `-Dwasm32` and the AST's practical bounds) and keeps the field at 12 bytes +// owning arena resets. The `u32` length keeps the field at 12 bytes // on 64-bit instead of 16 — relevant for hot AST nodes. #[repr(C)] pub struct StoreSlice { @@ -421,8 +415,8 @@ impl StoreSlice { unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len as usize) } } - /// Shorten the slice in place. Panics if `new_len > len` (mirrors Zig - /// `slice[0..new_len]` bounds check). The arena still owns the trailing + /// Shorten the slice in place. Panics if `new_len > len`. + /// The arena still owns the trailing /// elements; they are simply no longer reachable through this view. #[inline] pub fn truncate(&mut self, new_len: usize) { @@ -430,8 +424,8 @@ impl StoreSlice { self.len = new_len as u32; } - /// Construct from a `BumpVec`/`ArenaVec` by leaking it into the bump arena - /// (Zig: `list.items` after `toOwnedSlice`). Convenience for the common + /// Construct from a `BumpVec`/`ArenaVec` by leaking it into the bump arena. + /// Convenience for the common /// `StoreSlice::new_mut(v.into_bump_slice_mut())` pattern. #[inline] pub fn from_bump<'b>(v: bun_alloc::ArenaVec<'b, T>) -> Self { @@ -534,7 +528,7 @@ pub type ExprNodeList = Vec; pub type StmtNodeList = StoreSlice; pub type BindingNodeList = StoreSlice; -#[repr(u8)] // Zig: enum(u2) +#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug, strum::IntoStaticStr)] #[strum(serialize_all = "snake_case")] pub enum ImportItemStatus { @@ -545,7 +539,7 @@ pub enum ImportItemStatus { Missing, } -#[repr(u8)] // Zig: enum(u2) +#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Default, strum::IntoStaticStr)] #[strum(serialize_all = "snake_case")] pub enum AssignTarget { @@ -614,7 +608,7 @@ impl Default for ClauseItem { } } -// EnumMap<_, u32>::default() zero-fills (Zig: SlotNamespace.CountsArray.initFill(0)). +// EnumMap<_, u32>::default() zero-fills. #[derive(Copy, Clone, Default)] pub struct SlotCounts { pub slots: symbol::SlotNamespaceCountsArray, @@ -688,7 +682,7 @@ impl NameMinifier { } } -#[repr(u8)] // Zig: enum(u1) +#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug, strum::IntoStaticStr)] #[strum(serialize_all = "snake_case")] pub enum OptionalChain { @@ -947,12 +941,10 @@ impl DeclaredSymbolList { } pub fn append_list_assume_capacity(&mut self, other: &DeclaredSymbolList) { - // PERF(port): was assume_capacity self.entries.append_list_assume_capacity(&other.entries); } pub fn append_assume_capacity(&mut self, entry: DeclaredSymbol) { - // PERF(port): was assume_capacity self.entries.append_assume_capacity(entry); } @@ -1010,7 +1002,6 @@ impl DeclaredSymbol { debug_assert_eq!(is_top_level.len(), refs.len()); for (top, ref_) in is_top_level.iter().zip(refs.iter()) { if *top { - // PERF(port): was @call(bun.callmod_inline, ...) — relies on inlining. f(ctx, *ref_); } } @@ -1045,8 +1036,8 @@ pub type DependencyList = bun_alloc::AstVec; pub type ExprList = Vec; pub type StmtList = Vec; pub type BindingList = Vec; -// PERF(port): Zig `std.array_list.Managed` — these may be arena-backed in -// callers; revisit with bumpalo::collections::Vec if profiling shows churn. +// PERF: these may be arena-backed in callers; revisit with +// bumpalo::collections::Vec if profiling shows churn. /// Each file is made up of multiple parts, and each part consists of one or /// more top-level statements. Parts are used for tree shaking and code @@ -1055,7 +1046,7 @@ pub type BindingList = Vec; /// splitting. pub struct Part { pub stmts: StoreSlice, - pub scopes: StoreSlice<*mut Scope>, // TODO(port): &'bump mut [&'bump mut Scope] + pub scopes: StoreSlice<*mut Scope>, // TODO: &'bump mut [&'bump mut Scope] /// Each is an index into the file-level import record list pub import_record_indices: PartImportRecordIndices, @@ -1113,8 +1104,6 @@ pub enum PartTag { ImportToConvertFromRequire, } -// Zig: std.ArrayHashMapUnmanaged(Ref, Symbol.Use, RefHashCtx, false) -// TODO(port): bun_collections::ArrayHashMap must accept a custom hasher ctx (RefHashCtx). pub type PartSymbolUseMap = ArrayHashMap; pub type PartSymbolPropertyUseMap = ArrayHashMap< Ref, @@ -1158,9 +1147,8 @@ impl StmtOrExpr { StmtOrExpr::Expr(expr) => expr, StmtOrExpr::Stmt(stmt) => match stmt.data { crate::stmt::Data::SFunction(mut s) => { - // PORT NOTE: Zig moved `func.func` out by value; StoreRef arena - // slot is never individually dropped, so `take` (replace with - // Default) is the safe Rust equivalent. + // The StoreRef arena slot is never individually dropped, so + // `take` (replace with Default) is safe here. let func = core::mem::take(&mut s.func); Expr::init(E::Function { func }, stmt.loc) } @@ -1224,7 +1212,7 @@ pub struct NamedExport { pub alias_loc: crate::Loc, } -#[repr(u8)] // Zig: enum(u4) +#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug, strum::IntoStaticStr)] #[strum(serialize_all = "snake_case")] pub enum StrictModeKind { @@ -1275,8 +1263,6 @@ impl Batcher { where T: Default, { - // TODO(port): bumpalo alloc_slice for uninit T — Zig `arena.alloc(Type, count)`. - // PERF(port): Zig left the slice uninitialized; bumpalo requires Default fill. let all = bump.alloc_slice_fill_default(count); Ok(Self { head: StoreSlice::new_mut(all), @@ -1288,14 +1274,12 @@ impl Batcher { } pub fn eat(&mut self, value: T) -> *mut T { - // PORT NOTE: Zig source `@ptrCast(&this.head.eat1(value).ptr)` appears to - // intend `this.eat1(value).ptr` cast to *T. Porting the apparent intent. self.eat1(value).as_ptr().cast_mut() } pub fn eat1(&mut self, value: T) -> StoreSlice { - // `head` has at least 1 element remaining (caller contract — Zig would - // panic on bounds); `Batcher` holds the unique view of the allocation. + // `head` has at least 1 element remaining (caller contract); + // `Batcher` holds the unique view of the allocation. let head = self.head.slice_mut(); let (prev, rest) = head.split_at_mut(1); prev[0] = value; @@ -1314,17 +1298,15 @@ impl Batcher { StoreSlice::new_mut(prev) } } -// Zig: `pub fn NewBatcher(comptime Type: type) type` → Rust generic struct above. pub type NewBatcher = Batcher; // ═════════════════════════════════════════════════════════════════════════ // Symbols pulled DOWN from higher-tier // crates so lower-tier callers (css, interchange, js_parser itself) can -// resolve them here without forming a cycle. Ground truth for each port is -// the named .zig file, NOT the sibling .rs (which may already forward-ref). +// resolve them here without forming a cycle. // ═════════════════════════════════════════════════════════════════════════ -// ─── from bun_jsc::math (src/jsc/jsc.zig) ─────────────────────────────────── +// ─── from bun_jsc::math ───────────────────────────────────────────────────── pub mod math { /// `Number.MAX_SAFE_INTEGER` (2^53 - 1) pub const MAX_SAFE_INTEGER: f64 = 9007199254740991.0; @@ -1332,7 +1314,6 @@ pub mod math { pub const MIN_SAFE_INTEGER: f64 = -9007199254740991.0; unsafe extern "C" { - // Zig: `extern "c" fn Bun__JSC__operationMathPow(f64, f64) f64;` // Pure FFI (value-type args, no pointers, no errno) → no caller preconditions. safe fn Bun__JSC__operationMathPow(x: f64, y: f64) -> f64; } @@ -1344,8 +1325,7 @@ pub mod math { Bun__JSC__operationMathPow(x, y) } } -// ─── from bun_bundler::v2::MangledProps (src/bundler/bundle_v2.zig) ───────── -// Zig: `std.AutoArrayHashMapUnmanaged(Ref, []const u8)` +// ─── from bun_bundler::v2::MangledProps ───────────────────────────────────── // LIFETIMES.tsv: value slices point into the parser arena → `StoreStr` // (arena-owned, no `'bump` cascade). pub type MangledProps = ArrayHashMap; diff --git a/src/ast/op.rs b/src/ast/op.rs index 62f4f3b4c3e..a860a5b9485 100644 --- a/src/ast/op.rs +++ b/src/ast/op.rs @@ -6,7 +6,7 @@ use crate::AssignTarget; // If you add a new token, remember to add it to "TABLE" too #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug, Enum, IntoStaticStr)] -#[strum(serialize_all = "snake_case")] // match Zig @tagName output (e.g. "bin_add") +#[strum(serialize_all = "snake_case")] pub enum Code { // Prefix UnPos, // +expr @@ -150,7 +150,7 @@ impl Code { } } -#[repr(u8)] // Zig: enum(u6) — Rust has no u6, u8 is the narrowest fit +#[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum Level { Lowest, @@ -214,8 +214,8 @@ impl Level { const fn from_raw(n: u8) -> Level { // Callers only pass values derived from a valid `Level` discriminant // ±1 (`sub`/`add_f`); decode by exhaustive match so an out-of-range - // shift traps in release too (matches Zig's safety-checked - // `@enumFromInt`) instead of fabricating an invalid discriminant. + // shift traps in release too instead of fabricating an invalid + // discriminant. match n { 0 => Level::Lowest, 1 => Level::Comma, @@ -263,8 +263,6 @@ impl Default for Op { } impl Op { - // PORT NOTE: Zig `init(triple: anytype)` took an anonymous tuple .{text, level, is_keyword} - // and accessed .@"0"/.@"1"/.@"2". Flattened to positional params. pub const fn init(text: &'static [u8], level: Level, is_keyword: bool) -> Op { Op { text, @@ -274,24 +272,19 @@ impl Op { } } -// Zig: `pub const TableType: std.EnumArray(Op.Code, Op) = undefined;` -// This declared an `undefined` value (vestigial / used only for @TypeOf at callsites). -// Ported as a type alias since Rust statics cannot be uninitialized. pub type TableType = Table; -/// `.rodata` `[Op; Code::COUNT]` indexed by [`Code`] discriminant. Exposes the -/// Zig `std.EnumArray` surface (`getPtrConst`/`get`/`[]`) so downstream -/// callers don't see the raw array. +/// `.rodata` `[Op; Code::COUNT]` indexed by [`Code`] discriminant. Exposes +/// `get_ptr_const`/`get`/`[]` accessors so downstream callers don't see the +/// raw array. #[repr(transparent)] pub struct Table(pub [Op; ::LENGTH]); impl Table { - /// Zig: `Op.Table.getPtrConst(code) -> *const Op`. #[inline] pub fn get_ptr_const(&'static self, code: Code) -> &'static Op { &self.0[code as usize] } - /// Zig: `Op.Table.get(code) -> Op`. #[inline] pub fn get(&self, code: Code) -> Op { self.0[code as usize] @@ -307,7 +300,7 @@ impl core::ops::Index for Table { } // Built at const-eval time so it lives in `.rodata` with zero init code on the -// startup path (matches the Zig `comptime` labeled block). +// startup path. pub static TABLE: Table = Table({ const NIL: Op = Op::init(b"", Level::Lowest, false); let mut t = [NIL; ::LENGTH]; @@ -379,5 +372,3 @@ pub static TABLE: Table = Table({ t }); - -// ported from: src/js_parser/ast/Op.zig diff --git a/src/ast/runtime.rs b/src/ast/runtime.rs index b234450197f..5778804b472 100644 --- a/src/ast/runtime.rs +++ b/src/ast/runtime.rs @@ -1,7 +1,7 @@ #![allow(unexpected_cfgs)] // `bun_codegen_embed` is set via RUSTFLAGS (scripts/build/rust.ts) for release/CI builds. -// REFACTOR_BUN_AST: this module holds only the data-shaped pieces of -// `runtime.zig` that the AST crate (and `bun_js_printer::Options`) need: +// REFACTOR_BUN_AST: this module holds only the data-shaped runtime pieces +// that the AST crate (and `bun_js_printer::Options`) need: // `Runtime::source_code`, `Imports`, `ReplaceableExport*`, `ServerComponentsMode`. // The `Features` struct (carries `&mut RuntimeTranspilerCache`) and // `Fallback` HTML rendering (needs `bun_options_types::schema`, `bun_io`, @@ -28,13 +28,16 @@ impl Runtime { } } -/// Zig: `Runtime.Features.ReplaceableExport` #[derive(Clone)] pub enum ReplaceableExport { Delete, Replace(Expr), - Inject { name: Box<[u8]>, value: Expr }, - // TODO(port): `name` was `string` (= []const u8). Ownership unclear; using Box<[u8]>. + /// Owns the name bytes (constructed from an owned slice in + /// `JSTranspiler`; the parser copies into its bump arena when consuming). + Inject { + name: Box<[u8]>, + value: Expr, + }, } impl ReplaceableExport { @@ -44,18 +47,14 @@ impl ReplaceableExport { } } -/// Zig: `bun.StringArrayHashMapUnmanaged(ReplaceableExport)`. -/// -/// Newtype (not a bare alias) so we can hang `get_ptr` (Zig spelling for -/// `getPtr`, which borrows immutably) and expose a `.entries` accessor that -/// satisfies the `replace_exports.entries.len` shape `visitStmt` ported -/// verbatim from Zig's `ArrayHashMap.entries`. +/// Newtype (not a bare alias) so we can hang `get_ptr` (which borrows +/// immutably) and expose a `.entries` accessor that satisfies the +/// `replace_exports.entries.len` shape used by `visitStmt`. #[derive(Default)] pub struct ReplaceableExportMap { - /// Backing map. Named `entries` so `replace_exports.entries.len()` — - /// the literal Zig spelling — resolves (Zig's `ArrayHashMap.entries` - /// is a `MultiArrayList` with `.len`; here `StringArrayHashMap` derefs - /// to `ArrayHashMap` which has `.len()`). + /// Backing map. Named `entries` so `replace_exports.entries.len()` + /// resolves (`StringArrayHashMap` derefs to `ArrayHashMap`, which has + /// `.len()`). pub entries: StringArrayHashMap, } @@ -78,10 +77,8 @@ impl ReplaceableExportMap { pub fn count(&self) -> usize { self.entries.count() } - /// Zig `getPtr` returns `?*V` from a `*const Self` — i.e. immutable - /// lookup yielding a (logically-mutable) pointer. Rust splits this into - /// `get_ptr` (`&V`) and `get_ptr_mut` (`&mut V`); call sites in the - /// visitor only read through it. + /// Immutable lookup (`&V`); `get_ptr_mut` is the `&mut V` form. Call + /// sites in the visitor only read through it. #[inline] pub fn get_ptr(&self, key: &[u8]) -> Option<&ReplaceableExport> { self.entries.get(key) @@ -96,7 +93,6 @@ impl ReplaceableExportMap { } } -/// Zig: `Runtime.Features.ServerComponentsMode` #[derive(Clone, Copy, PartialEq, Eq, Default)] pub enum ServerComponentsMode { /// Server components is disabled, strings "use client" and "use server" mean nothing. @@ -167,7 +163,7 @@ pub struct Imports { pub __decoratorMetadata: Option, pub __runInitializers: Option, pub __decorateElement: Option, - /// Zig field name: `@"$$typeof"` (not a valid Rust identifier). + /// The `$$typeof` runtime import (`$$typeof` is not a valid Rust identifier). pub dollar_dollar_typeof: Option, pub __using: Option, pub __callDispose: Option, @@ -204,8 +200,8 @@ impl Imports { b"__promiseAll", ]; - /// Zig computed this at comptime via `std.sort.pdq`. Rust stable cannot sort in - /// `const`; precomputed here and verified by `tests::all_sorted_matches_zig_comptime`. + /// Rust stable cannot sort in `const`; precomputed here and verified by + /// the test in `tests` below. #[cfg_attr(not(test), allow(dead_code))] const ALL_SORTED: [&'static [u8]; 25] = [ b"$$typeof", @@ -267,7 +263,7 @@ impl Imports { pub const NAME: &'static [u8] = b"bun:wrap"; - /// Index → field. Expansion of Zig `@field(this, all[i])`. + /// Index → field. #[inline] fn field(&self, i: usize) -> Option { match i { @@ -339,9 +335,8 @@ impl Imports { } } - /// Zig: `contains(imports, comptime key: string)`. - // TODO(port): comptime-string key — Rust callers should access the field directly - // (`imports.__foo.is_some()`). Runtime fallback provided for parity. + /// Callers that know the key statically can read the field directly + /// (`imports.__foo.is_some()`); this is the runtime-keyed equivalent. pub fn contains(&self, key: &[u8]) -> bool { Self::ALL .iter() @@ -359,8 +354,8 @@ impl Imports { false } - /// Zig: `put(imports, comptime key: string, ref: Ref)`. - // TODO(port): comptime-string key — Rust callers should assign the field directly. + /// Callers that know the key statically can assign the field directly; + /// this is the runtime-keyed equivalent. pub fn put(&mut self, key: &[u8], ref_: Ref) { if let Some(i) = Self::ALL.iter().position(|&k| k == key) { if let Some(slot) = self.field_mut(i) { @@ -369,8 +364,8 @@ impl Imports { } } - /// Zig: `at(imports, comptime key: string) ?Ref`. - // TODO(port): comptime-string key — Rust callers should read the field directly. + /// Callers that know the key statically can read the field directly; + /// this is the runtime-keyed equivalent. pub fn at(&self, key: &[u8]) -> Option { Self::ALL .iter() @@ -378,7 +373,7 @@ impl Imports { .and_then(|i| self.field(i)) } - /// Zig: `get(imports, key: anytype) ?Ref` where `key` is a runtime index. + /// Lookup by runtime index. pub fn get(&self, key: usize) -> Option { if key < Self::ALL.len() { self.field(key) @@ -398,7 +393,6 @@ impl Imports { } } -/// Zig: `Runtime.Imports.Iterator` pub struct ImportsIterator<'a> { pub i: usize, pub runtime_imports: &'a Imports, @@ -414,7 +408,7 @@ impl ImportsIterator<'_> { pub fn next(&mut self) -> Option { while self.i < Imports::ALL.len() { let t = self.i; - self.i += 1; // Zig: `defer this.i += 1;` + self.i += 1; if let Some(val) = self.runtime_imports.field(t) { return Some(ImportsIteratorEntry { key: u16::try_from(t).expect("int cast"), @@ -430,12 +424,10 @@ impl ImportsIterator<'_> { mod tests { use super::Imports; - /// Port of the Zig comptime block that derives `all_sorted` / `all_sorted_index`. - /// Rust stable cannot sort in `const`, so the tables above are hand-precomputed; - /// this test re-derives them at runtime and asserts they match. + /// The tables above are hand-precomputed (Rust stable cannot sort in + /// `const`); this test re-derives them at runtime and asserts they match. #[test] fn all_sorted_matches_zig_comptime() { - // const all_sorted = brk: { var list = all; std.sort.pdq(...); break :brk list; }; let mut list = Imports::ALL; list.sort_unstable(); assert_eq!( @@ -444,7 +436,6 @@ mod tests { "ALL_SORTED drifted from sorted(ALL)" ); - // pub const all_sorted_index = brk: { for (all) |name, i| for (all_sorted) |cmp, j| ... }; let mut out = [0usize; Imports::ALL.len()]; for (i, name) in Imports::ALL.iter().enumerate() { for (j, cmp) in list.iter().enumerate() { @@ -461,5 +452,3 @@ mod tests { ); } } - -// ported from: src/js_parser/runtime.zig diff --git a/src/ast/s.rs b/src/ast/s.rs index a8ce4377c39..9bc85057c2d 100644 --- a/src/ast/s.rs +++ b/src/ast/s.rs @@ -309,5 +309,3 @@ pub struct Break { pub struct Continue { pub label: Option, // = None } - -// ported from: src/js_parser/ast/S.zig diff --git a/src/ast/scope.rs b/src/ast/scope.rs index 74672d9f992..fe626fe87bc 100644 --- a/src/ast/scope.rs +++ b/src/ast/scope.rs @@ -10,13 +10,11 @@ use crate::ts::TSNamespaceScope; /// Backed by `AstAlloc` so the table allocation *and* the per-key boxes land /// in the thread-local AST `mi_heap` and are reclaimed by the same /// `mi_heap_destroy` that frees the arena-allocated `Scope` holding the map. -/// In Zig this was `bun.StringHashMapUnmanaged(Member)` whose backing array -/// lived in the parser arena; the original Rust port placed both on the -/// global heap, and since `Scope` itself sits in an arena slot whose `Drop` -/// never runs, every member map leaked. +/// `Scope` itself sits in an arena slot whose `Drop` never runs, so a +/// global-heap-backed map here would leak. pub(crate) type MemberHashMap = StringHashMap; -// PORT NOTE: Zig `Scope` is a value type — `Ast.module_scope` / `BundledAst.module_scope` +// `Scope` is a value type — `Ast.module_scope` / `BundledAst.module_scope` // hold it by value and `toAST` / `init` bitwise-copy it (`this.module_scope`). Vec no // longer derives `Clone` (private `origin` field); callers that need a shallow copy must // `core::mem::take` or `core::ptr::read` instead. @@ -27,12 +25,11 @@ pub struct Scope { // back-pointer with safe `Deref`/`DerefMut`) so callers don't open-code // `unsafe { &*parent.as_ptr() }` at every walk site. pub parent: Option>, - /// `AstVec` for the same reason as `members` above — Zig's - /// `ArrayListUnmanaged(*Scope)` was arena-backed. Elements are `StoreRef` + /// `AstVec` for the same reason as `members` above. Elements are `StoreRef` /// so iteration yields safe `Deref` instead of `unsafe { child.as_ref() }`. pub children: AstVec>, pub members: MemberHashMap, - /// `AstVec`: Zig `ArrayListUnmanaged(Ref)`, arena-backed. + /// `AstVec`: arena-backed. pub generated: AstVec, // This is used to store the ref of the label symbol for ScopeLabel scopes. @@ -111,16 +108,16 @@ impl Scope { name: &[u8], hash_value: u64, ) -> bun_collections::array_hash_map::StringHashMapGetOrPut<'_, Member> { - // PERF(port): `get_or_put_borrowed` doesn't accept a precomputed hash; + // PERF: `get_or_put_borrowed` doesn't accept a precomputed hash; // this path is once-per-declared-symbol (not per-scope-per-identifier), // so the redundant rehash is left as-is. let _ = hash_value; // SAFETY: `name` is always a slice into either the source-file contents // or the lexer string-table (the only producers of identifier text in // the parser). Both outlive the `AstAlloc` arena that owns this - // `Scope`, so storing the slice by reference (Zig's - // `StringHashMapUnmanaged` semantics) is sound — the map is freed by - // the same arena reset that would invalidate the source/string-table. + // `Scope`, so storing the slice by reference is sound — the map is + // freed by the same arena reset that would invalidate the + // source/string-table. // This avoids one `mi_heap_malloc` per declared identifier per scope, // which profiling showed as the parser's hottest slow-path allocation. unsafe { self.members.get_or_put_borrowed(name) } @@ -268,7 +265,6 @@ pub struct Member { impl Member { #[inline] pub fn eql(a: Member, b: Member) -> bool { - // PERF(port): Zig used @call(bun.callmod_inline, Ref.eql, ...) — forced inline. a.ref_.eql(b.ref_) && a.loc.start == b.loc.start } } @@ -300,5 +296,3 @@ pub enum Kind { FunctionBody, ClassStaticInit, } - -// ported from: src/js_parser/ast/Scope.zig diff --git a/src/ast/server_component_boundary.rs b/src/ast/server_component_boundary.rs index 9528108ff2e..fd717a21736 100644 --- a/src/ast/server_component_boundary.rs +++ b/src/ast/server_component_boundary.rs @@ -55,9 +55,9 @@ pub struct List { pub map: Map, } -// Zig: `std.ArrayHashMapUnmanaged(void, void, struct {}, true)` — a keyless -// array-hash-map used purely as an index table; all lookups go through the -// `Adapter` which hashes/compares against `list.items(.source_index)`. +// A keyless array-hash-map used purely as an index table; all lookups go +// through the `Adapter` which hashes/compares against the `source_index` +// column of `list`. type Map = ArrayHashMap<(), ()>; impl List { @@ -75,9 +75,8 @@ impl List { reference_source_index, ssr_source_index, })?; - // PORT NOTE: reshaped for borrowck — Zig built `Adapter` from - // `m.list.slice()` while also borrowing `m.map` mutably. Here we hand - // the adapter just the `source_index` column it needs. + // For borrowck we hand the adapter just the `source_index` column + // it needs. let gop = self.map.get_or_put_adapted( &source_index, &Adapter { @@ -130,9 +129,6 @@ impl<'a> Slice<'a> { source_indices: self.list.items::<"source_index", IndexInt>(), }, )?; - // Zig: `bun.unsafeAssert(l.list.capacity > 0)` — optimization hint for - // `MultiArrayList.Slice.items`. The Rust `items()` already short-circuits - // on `capacity == 0`, so the assert is dropped. Some(self.list.items::<"reference_source_index", IndexInt>()[i]) } @@ -148,9 +144,8 @@ impl<'a> Slice<'a> { } } -// PORT NOTE: Zig stored the full `MultiArrayList.Slice` and called -// `.items(.source_index)` on each compare. The Rust `Slice` is not `Copy`, -// so we cache just the `source_index` column the adapter actually needs. +// Caches just the `source_index` column the adapter actually needs +// (`Slice` is not `Copy`). pub(crate) struct Adapter<'a> { pub source_indices: &'a [IndexInt], } @@ -165,5 +160,3 @@ impl<'a> ArrayHashAdapter for Adapter<'a> { *a == self.source_indices[b_index] } } - -// ported from: src/js_parser/ast/ServerComponentBoundary.zig diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index defe492bb31..4d6df898f75 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -14,24 +14,26 @@ pub struct Stmt { pub type Batcher = NewBatcher; impl Stmt { - /// Zig: `Stmt.Data.Store.reset()`. Associated wrapper so downstream crates - /// can call `crate::Stmt::data_store_reset()` without naming the - /// thread-local Store module path. + /// Associated wrapper so downstream crates can call + /// `crate::Stmt::data_store_reset()` without naming the thread-local + /// Store module path. #[inline] pub fn data_store_reset() { data::Store::reset(); } - /// Zig: `Stmt.Data.Store.create()`. + /// Initializes the thread-local statement-data `Store` for the current + /// thread; counterpart of `data_store_reset()`. #[inline] pub fn data_store_create() { data::Store::create(); } - /// Zig: `Stmt.Data.Store.assert()` — debug-only re-entrancy guard. + /// Debug-only "Store must be init'd" guard. The re-entrancy `Disabler` + /// check lives in `Store::append`. #[inline] pub fn data_store_assert() { - crate::DebugOnlyDisabler::::assert(); + data::Store::assert(); } pub fn assign(a: Expr, b: Expr) -> Stmt { @@ -82,8 +84,8 @@ impl Stmt { } impl Default for Stmt { - /// Zig: `nullStmtData = Stmt.Data{ .s_empty = s_missing }` (P.zig) — used to - /// zero-init `loop_body` and bulk-fill stmt slices before population. + /// Used to zero-init `loop_body` and bulk-fill stmt slices before + /// population. #[inline] fn default() -> Self { Stmt { @@ -95,25 +97,20 @@ impl Default for Stmt { const NONE: S::Empty = S::Empty {}; -// PORT NOTE: Zig `pub var icount: usize = 0;` is a plain mutable global (not -// threadlocal), never read. Debug-only here so release doesn't pay a contended -// `lock xadd` per Stmt across the bundler worker pool. +// Debug-only so release doesn't pay a contended `lock xadd` per Stmt across +// the bundler worker pool. #[cfg(debug_assertions)] pub static ICOUNT: AtomicUsize = AtomicUsize::new(0); -/// Trait absorbing the Zig `switch (comptime StatementType)` tables in -/// `init` / `alloc` / `allocate`. Each `S::*` payload type implements this to -/// map itself onto the corresponding `Data` variant. -/// -/// The Zig used three near-identical 32-arm comptime switches; in Rust the -/// dispatch is the trait impl and the arm list is the `impl_statement_data!` -/// invocation below — diff that list against the Zig switch. +/// Each `S::*` payload type implements this to map itself onto the +/// corresponding `Data` variant; the arm list is the `impl_statement_data!` +/// invocation below. pub trait StatementData: Sized { - /// Wrap an already-allocated payload (Zig `Stmt.init` / `comptime_init`). + /// Wrap an already-allocated payload. fn wrap_ref(ptr: StoreRef) -> Data; - /// Store-append `self` and wrap (Zig `Stmt.alloc` / `comptime_alloc`). + /// Store-append `self` and wrap. fn store_alloc(self) -> Data; - /// Arena-allocate `self` and wrap (Zig `Stmt.allocate` / `allocateData`). + /// Arena-allocate `self` and wrap. fn arena_alloc(self, bump: &bun_alloc::Arena) -> Data; } @@ -128,8 +125,6 @@ impl Stmt { } } - // Zig `comptime_alloc` — folded into `StatementData::store_alloc`; kept as a - // private helper for diff parity. #[inline] fn comptime_alloc(orig_data: T, loc: crate::Loc) -> Stmt { Stmt { @@ -138,24 +133,17 @@ impl Stmt { } } - // Zig `allocateData` — folded into `StatementData::arena_alloc`. fn allocate_data( bump: &bun_alloc::Arena, orig_data: T, loc: crate::Loc, ) -> Stmt { - // `arena.create(@TypeOf(origData)) catch unreachable; value.* = origData;` - // → bump.alloc(orig_data), performed inside arena_alloc. Stmt { loc, data: orig_data.arena_alloc(bump), } } - // Zig `comptime_init` — `@unionInit(Data, tag_name, origData)`. In Rust the - // variant constructor IS the union-init; this helper collapses to identity - // and is absorbed by `StatementData::wrap_ref`. - #[inline] pub fn alloc(orig_data: T, loc: crate::Loc) -> Stmt { data::Store::assert(); @@ -194,7 +182,7 @@ impl Stmt { } } -// ─── StatementData impls (mirrors the 32-arm comptime switches) ──────────── +// ─── StatementData impls ─────────────────────────────────────────────────── macro_rules! impl_statement_data { // Pointer-payload variants: stored via Store / arena. @@ -210,7 +198,10 @@ macro_rules! impl_statement_data { } #[inline] fn arena_alloc(self, bump: &bun_alloc::Arena) -> Data { - // TODO(port): StoreRef vs &'bump — unify the arena ref type. + // `StoreRef::from_bump` is the settled crate-wide arena-ref + // convention (see its docs in nodes.rs); expr.rs + // `arena_alloc` does the same. No separate `&'bump` ref + // type is planned. Data::$variant(StoreRef::from_bump(bump.alloc(self))) } } @@ -359,7 +350,7 @@ pub enum Data { } // ── Layout guards ───────────────────────────────────────────────────────── -// Zig: `if (@sizeOf(Stmt) > 24) @compileLog(...)` (Stmt.zig:295). Every payload +// Every payload // variant is either a `StoreRef` (`#[repr(transparent)] NonNull`, 8 bytes, // niche-carrying) or a ZST, so the union is one pointer word and the repr(Rust) // discriminant packs alongside it for `Data` = 16. `Stmt` = `Data` (16, align 8) @@ -383,9 +374,9 @@ const _: () = assert!( ); const _: () = assert!(core::mem::size_of::>() == core::mem::size_of::()); -/// Zig: `std.meta.eql(p.loop_body, stmt.data)` (visitStmt.zig) — tag compare, -/// then payload compare. Payloads here are arena pointers (`StoreRef`) or -/// ZSTs, so this is tag + pointer-identity, never a deep structural compare. +/// Tag compare, then payload compare. Payloads here are arena pointers +/// (`StoreRef`) or ZSTs, so this is tag + pointer-identity, never a deep +/// structural compare. impl PartialEq for Data { fn eq(&self, other: &Self) -> bool { use Data::*; @@ -429,10 +420,9 @@ impl PartialEq for Data { } impl Eq for Data {} -// Zig field-style union accessors (`data.s_function`, `data.s_local`, …). -// visitStmt and the printer port from Zig's `data.s_local.*` etc., which are -// unchecked union field reads. Rust callers `.unwrap()` (or pattern-match) — -// the `Option` is the cheapest sound encoding of Zig's UB-on-mismatch. +// Field-style union accessors (`data.s_function()`, `data.s_local()`, …). +// Callers `.unwrap()` (or pattern-match) — the `Option` is the cheapest +// sound encoding of a tag mismatch. // Mirrors `expr::Data::e_*()`. Returns `Option>` (Copy) for // pointer-payload variants and `Option` by value for inline ZST variants. impl Data { @@ -949,7 +939,7 @@ impl Data { } // `new_store!` emits `pub mod stmt_store { pub struct Store; ... }` with -// `init/append/reset/destroy`. Type list mirrors Zig's `Data.Store = NewStore(&.{...}, 128)`. +// `init/append/reset/destroy`. crate::new_store!( stmt_store, [ @@ -991,11 +981,6 @@ pub mod data { crate::thread_local_ast_store!(stmt_store::Store, "Stmt"); } -// Zig `pub fn StoredData(tag: Tag) type` — returns the payload type for a tag, -// dereferencing pointer variants. Rust has no type-returning fns. -// TODO(port): callers should use the `StatementData` trait or a per-variant -// associated type; revisit once call sites are known. - impl Stmt { pub fn cares_about_scope(&self) -> bool { match &self.data { @@ -1024,5 +1009,3 @@ impl Stmt { } } } - -// ported from: src/js_parser/ast/Stmt.zig diff --git a/src/ast/symbol.rs b/src/ast/symbol.rs index aef56f39de7..08df9761b9c 100644 --- a/src/ast/symbol.rs +++ b/src/ast/symbol.rs @@ -166,11 +166,8 @@ pub struct Symbol { pub has_been_assigned_to: bool, } -// TODO(port): Zig asserts @sizeOf(Symbol) == 88 and @alignOf(Symbol) == @alignOf([]const u8). -// Rust default repr reorders fields and Option niche may differ -// (likely needs #[repr(C)] or manual packing if the size is load-bearing). -// const _: () = assert!(core::mem::size_of::() == 88); -// const _: () = assert!(core::mem::align_of::() == core::mem::align_of::()); +// The size of `Symbol` is not load-bearing (no FFI, no serialization), so +// there is intentionally no layout assert here. const INVALID_CHUNK_INDEX: u32 = u32::MAX; pub const INVALID_NESTED_SCOPE_SLOT: u32 = u32::MAX; @@ -205,7 +202,6 @@ pub enum SlotNamespace { MangledProp, } -// Zig: `pub const CountsArray = std.EnumArray(SlotNamespace, u32);` (nested decl). // Inherent associated types are nightly-only; expose as a free alias. pub(crate) type SlotNamespaceCountsArray = enum_map::EnumMap; @@ -251,7 +247,6 @@ impl Symbol { #[inline] pub fn has_link(&self) -> bool { - // Zig: `self.link.tag != .invalid` self.link.get().is_valid() } } @@ -522,8 +517,7 @@ impl Map { // Returns a raw *mut Symbol because callers (merge/follow/assign_chunk_index/ // get_with_link) hold aliasing pointers into the NestedList and/or recurse through - // &mut self while holding the pointer. Mirrors Zig's `*const Map -> ?*Symbol` - // (interior mutability via Vec's raw `[*]T` ptr field). + // &mut self while holding the pointer. // // SOUNDNESS: the *mut is derived directly from `Vec.ptr: NonNull` — a raw // pointer field whose provenance is independent of the `&self` borrow used to read @@ -575,7 +569,6 @@ impl Map { } pub fn init(source_count: usize) -> Map { - // Zig: `arena.alloc([]Symbol, sourceCount)` (default_allocator) then NestedList.init. let mut v: NestedList = Vec::with_capacity(source_count); v.resize_with(source_count, Vec::new); Map { @@ -583,11 +576,9 @@ impl Map { } } - // PORT NOTE: Zig aliased the caller's stack `[1]List` slot directly; that's - // unsound in Rust (would dangle on return). Take ownership of `list` and - // box it into a one-element NestedList instead. - // PERF(port): one extra allocation vs Zig — profile (single - // caller is the printer one-shot, cold). + // Takes ownership of `list` and boxes it into a one-element NestedList. + // PERF: one extra allocation — profile if needed (single caller is the + // printer one-shot, cold). // OWNERSHIP: returned `Map` is *owned*; the `Vec` allocated here leaks if a // consumer parks it in `ManuallyDrop` (e.g. renamer.rs `MinifyRenamer.symbols`). pub fn init_with_one_list(list: Vec) -> Map { @@ -633,7 +624,8 @@ impl Map { } pub fn follow_all(&mut self) { - // TODO(port): bun_perf::trace("Symbols.followAll") — RAII guard + // The returned `Ctx` is RAII and ends the span on drop. + let _trace = bun_perf::trace(bun_perf::PerfEvent::SymbolsFollowAll); // `link` is `Cell`, so we can iterate the table by shared ref and // mutate `link` in place; `follow()` only takes `&self` and only touches // `link`, so the nested shared borrows coexist. @@ -650,12 +642,11 @@ impl Map { /// Equivalent to followSymbols in esbuild. /// - /// PORT NOTE: Zig's body is naturally recursive (`follow(symbol.link)`). - /// Reshaped to an iterative two-phase walk so the per-hop work is just two - /// raw pointer adds and a load — no call frame, no `Option` unwrap, no - /// repeated tag/null guards. Semantics are identical to Zig's: every node - /// on the path from `ref_` to the union-find root has its `link` rewritten - /// to the root (full path compression). + /// An iterative two-phase walk so the per-hop work is just two raw + /// pointer adds and a load — no call frame, no `Option` unwrap, no + /// repeated tag/null guards. Every node on the path from `ref_` to the + /// union-find root has its `link` rewritten to the root (full path + /// compression). pub fn follow(&self, ref_: Ref) -> Ref { // Entry guard — `ref_` may be `Ref::None` / a SourceContentsSlice ref // (callers pass arbitrary Refs read out of AST nodes). After this, @@ -696,20 +687,17 @@ impl Map { } // Phase 2: path compression. Rewrite `link` on the entry node and every - // intermediate node to point directly at `root` (matches the Zig - // recursion's post-order `symbol.link = link` writes). The `!=` gate - // mirrors Zig's `if (!symbol.link.eql(link))` to avoid a redundant - // store when the chain was already length-1. `link` is `Cell`, so - // writes go through `&Symbol` safely. + // intermediate node to point directly at `root`. The `!=` gate avoids + // a redundant store when the chain was already length-1. `link` is + // `Cell`, so writes go through `&Symbol` safely. if !link.eql(root) { symbol.link.set(root); loop { let p = lookup(link); let next = p.link.get(); // `next.eql(root)` ⇔ `p.link` already points at root — - // mirrors Zig's post-order `if (!symbol.link.eql(link))` gate - // and saves a redundant store on the last intermediate plus - // the otherwise-wasted lookup of `root` itself. + // saves a redundant store on the last intermediate plus the + // otherwise-wasted lookup of `root` itself. if next.eql(root) || !next.is_valid() { break; } @@ -728,7 +716,6 @@ impl Symbol { Symbol::is_kind_hoisted(self.kind) } - // Zig: pub const isKindFunction = Symbol.Kind.isFunction; (etc.) // Rust cannot alias inherent methods; forward explicitly. #[inline] pub fn is_kind_function(kind: Kind) -> bool { @@ -747,5 +734,3 @@ impl Symbol { kind.is_private() } } - -// ported from: src/js_parser/ast/Symbol.zig diff --git a/src/ast/target.rs b/src/ast/target.rs index 6554d2ffc43..8ed486ed9ea 100644 --- a/src/ast/target.rs +++ b/src/ast/target.rs @@ -1,4 +1,4 @@ -//! `bundler/options.zig` `Target` — bundle target platform. +//! Bundle target platform. //! //! Data-only enum + pure predicates. `to_api()` / `from(api::Target)` live in //! `bun_options_types::TargetExt` (would back-edge into the schema crate). @@ -6,8 +6,7 @@ use enum_map::Enum; use phf; -/// Zig field default is `.browser` (`Target = .browser` in BundleOptions); -/// keep `Default` so resolver can field-default it. +/// Defaults to `Browser`; keep `Default` so resolver can field-default it. #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Enum, strum::IntoStaticStr, Default)] pub enum Target { @@ -74,8 +73,6 @@ impl Target { ]; pub fn default_main_fields(self) -> &'static [&'static str] { - // Zig: `std.EnumArray(Target, []const string)` initialized at comptime. - // See bundler/options.zig for the rationale comments on each ordering. const NODE: &[&str] = &[Target::MAIN_FIELD_NAMES[2], Target::MAIN_FIELD_NAMES[1]]; const BROWSER: &[&str] = &[ Target::MAIN_FIELD_NAMES[0], @@ -96,9 +93,8 @@ impl Target { } pub fn default_conditions(self) -> &'static [&'static [u8]] { - // PORT NOTE: Zig `default_conditions` is `std.EnumArray(Target, []const string)` - // — `string` is `[]const u8`. Callers (`ESMConditions::init`) take byte - // slices, so surface bytes directly rather than `&str`. + // Callers (`ESMConditions::init`) take byte slices, so surface + // bytes directly rather than `&str`. match self { Target::Node => &[b"node"], Target::Browser => &[b"browser", b"module"], @@ -108,5 +104,3 @@ impl Target { } } } - -// ported from: src/options_types/BundleEnums.zig (Target) diff --git a/src/ast/transpiler_cache.rs b/src/ast/transpiler_cache.rs index 44d8e998d52..6ed85a06036 100644 --- a/src/ast/transpiler_cache.rs +++ b/src/ast/transpiler_cache.rs @@ -1,4 +1,4 @@ -//! `bun.jsc.RuntimeTranspilerCache` (src/jsc/RuntimeTranspilerCache.zig). +//! Runtime transpiler cache. //! //! Single canonical struct, lowered to `bun_ast` so both the parser //! (`Features.runtime_transpiler_cache`) and the printer @@ -19,8 +19,8 @@ pub struct RuntimeTranspilerCache { pub features_hash: Option, pub exports_kind: ExportsKind, /// Set by `put()` / `get()` when a cache hit returns transpiled output. - /// Zig: `?bun.String` — bundler/parser only store/read the bytes; T6 owns - /// the `bun.String` wrapper when surfacing to JS. + /// Bundler/parser only store/read the bytes; T6 owns the string wrapper + /// when surfacing to JS. pub output_code: Option>, /// Opaque storage for `bun_bundler::cache::RuntimeTranspilerCacheEntry` — /// the concrete type lives a tier up and is round-tripped via cast. diff --git a/src/ast/ts.rs b/src/ast/ts.rs index b528bf9f481..e5ac4cdf55d 100644 --- a/src/ast/ts.rs +++ b/src/ast/ts.rs @@ -37,8 +37,8 @@ use crate::e::String as EString; /// hierarchical scope-based identifier lookup in JavaScript. Lookup now needs /// to search sibling scopes in addition to parent scopes. This is accomplished /// by sharing the map of exported members between all matching sibling scopes. -// PORT NOTE: 'arena lifetime dropped — `EnumString` payload is a `StoreRef` -// rather than an arena-borrowed reference (see the TODO on that variant). +// The `EnumString` payload is a `StoreRef` (AST-store back-pointer) +// rather than an arena-borrowed reference, matching the rest of the AST crate. pub struct TSNamespaceScope { /// This is specific to this namespace block. It's the argument of the /// immediately-invoked function expression that the namespace block is @@ -131,7 +131,6 @@ pub enum Data { EnumNumber(f64), /// "enum ns { it = 'it' }" // LIFETIMES.tsv: ARENA — assigned from Expr.Data.e_string payload (AST Expr store). - // TODO(port): &'bump EString once 'bump threaded crate-wide. EnumString(crate::nodes::StoreRef), /// "enum ns { it = something() }" EnumProperty, @@ -139,8 +138,6 @@ pub enum Data { impl Data { pub fn is_enum(&self) -> bool { - // PORT NOTE: Zig used `inline else` + comptime `@tagName` prefix check ("enum_"). - // Expanded to an explicit match over the enum_* variants. matches!( self, Data::EnumNumber(_) | Data::EnumString(_) | Data::EnumProperty @@ -174,9 +171,8 @@ pub enum Metadata { MSymbol, MPromise, MIdentifier(Ref), - // TODO(port): Zig used `std.ArrayListUnmanaged(Ref)`. This is an AST crate; - // if this list is arena-backed in practice, switch to - // `bun_alloc::ArenaVec<'bump, Ref>`. + // A heap `Vec` is used here because `Metadata` is lifetime-free. + // Decorator metadata is rare and the lists are tiny. MDot(Vec), } @@ -223,7 +219,7 @@ impl Metadata { _ => Metadata::MObject, }; } else { - // PORT NOTE: reshaped for borrowck — copy Ref out before reassigning *result + // Reshaped for borrowck — copy Ref out before reassigning *result if let Metadata::MIdentifier(r) = result { let r = *r; if let Metadata::MIdentifier(l) = left { @@ -282,7 +278,7 @@ impl Metadata { _ => Metadata::MObject, }; } else { - // PORT NOTE: reshaped for borrowck — copy Ref out before reassigning *result + // Reshaped for borrowck — copy Ref out before reassigning *result if let Metadata::MIdentifier(r) = result { let r = *r; if let Metadata::MIdentifier(l) = left { @@ -302,7 +298,4 @@ impl Metadata { } } -// Zig file ends with `pub const Class = G.Class;` — re-export. pub use crate::g::Class; - -// ported from: src/js_parser/ast/TS.zig diff --git a/src/ast/use_directive.rs b/src/ast/use_directive.rs index d3079854422..4106d784fac 100644 --- a/src/ast/use_directive.rs +++ b/src/ast/use_directive.rs @@ -1,6 +1,6 @@ use bun_core::strings; -#[repr(u8)] // Zig: enum(u2) +#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum UseDirective { // TODO: Remove this, and provide `UseDirective.Optional` instead @@ -49,5 +49,3 @@ impl UseDirective { None } } - -// ported from: src/js_parser/ast/UseDirective.zig diff --git a/src/ast_jsc/lib.rs b/src/ast_jsc/lib.rs index 57b5f9fb7ee..d31a6548088 100644 --- a/src/ast_jsc/lib.rs +++ b/src/ast_jsc/lib.rs @@ -89,5 +89,3 @@ pub fn log_to_js_array(this: &Log, global: &JSGlobalObject) -> JsResult let msgs: &[Msg] = this.msgs.as_slice(); JSValue::create_array_from_iter(global, msgs.iter(), |msg| msg_to_js(msg.clone(), global)) } - -// ported from: src/logger_jsc/logger_jsc.zig diff --git a/src/base64/lib.rs b/src/base64/lib.rs index 55a68d6e6f9..01910dc153e 100644 --- a/src/base64/lib.rs +++ b/src/base64/lib.rs @@ -3,10 +3,10 @@ use bun_simdutf_sys::simdutf::{self, SIMDUTFResult}; pub use zig_base64::STANDARD_ALPHABET_CHARS; // ASCII control codes used in the ignore set below. -const VT: u8 = 0x0B; // std.ascii.control_code.vt -const FF: u8 = 0x0C; // std.ascii.control_code.ff +const VT: u8 = 0x0B; // vertical tab +const FF: u8 = 0x0C; // form feed -// PORT NOTE: Zig evaluates this at comptime; const-initialized static lands in `.rodata` +// Const-initialized static lands in `.rodata` // (no `Once` atomic on the `Integrity::parse` hot path). static MIXED_DECODER: zig_base64::Base64DecoderWithIgnore = { let mut decoder = @@ -124,7 +124,6 @@ pub fn encode_alloc(source: &[u8]) -> Vec { let len = encode_len(source); let mut destination = vec![0u8; len]; let encoded_len = encode(&mut destination, source); - // PORT NOTE: Zig built Vec from ptr/len/cap; here Vec already carries cap == len. destination.truncate(encoded_len); destination } @@ -199,8 +198,7 @@ pub const fn url_safe_encode_len(source: &[u8]) -> usize { } // ────────────────────────────────────────────────────────────────────────── -// VLQ — moved from bun_sourcemap. Ground truth: src/sourcemap/VLQ.zig. -// Lives here because the encoding is pure +// VLQ — moved from bun_sourcemap. Lives here because the encoding is pure // base64-alphabet bit-packing with zero sourcemap-specific deps; bun_sourcemap // re-exports this for its own consumers. // ────────────────────────────────────────────────────────────────────────── @@ -215,7 +213,6 @@ pub mod vlq { #[derive(Copy, Clone)] pub struct VLQ { pub bytes: [u8; VLQ_MAX_IN_BYTES], - /// This is a u8 and not a u4 because non^2 integers are really slow in Zig. pub len: u8, } @@ -236,8 +233,8 @@ pub mod vlq { &self.bytes[0..self.len as usize] } - // PORT NOTE: Zig took `writer: anytype`. `std::io::Write` is used as the - // byte-sink trait; base64 stays a tier-0 leaf with no bun_io dep. + // `std::io::Write` is used as the byte-sink trait so base64 stays a + // tier-0 leaf with no bun_io dep. pub fn write_to(self, writer: &mut impl std::io::Write) -> Result<(), bun_core::Error> { writer.write_all(&self.bytes[0..self.len as usize])?; Ok(()) @@ -255,13 +252,12 @@ pub mod vlq { } } - // Module-level alias so `bun_base64::vlq::encode(..)` mirrors the Zig file-scope fn. + // Module-level alias for `VLQ::encode`. #[inline] pub const fn encode(value: i32) -> VLQ { VLQ::encode(value) } - // PERF(port): was comptime-evaluated table in Zig — Rust const-eval matches. const VLQ_LOOKUP_TABLE: [VLQ; 256] = { let mut entries = [VLQ { bytes: [0; VLQ_MAX_IN_BYTES], @@ -304,7 +300,6 @@ pub mod vlq { }; // source mappings are limited to i32 - // PERF(port): was `inline for` (unrolled) — profile if hot. let mut iter = 0; while iter < VLQ_MAX_IN_BYTES { let mut digit = vlq & 31; @@ -336,7 +331,7 @@ pub mod vlq { const BASE64: &[u8; 64] = &crate::zig_base64::STANDARD_ALPHABET_CHARS; - /// `std.math.maxInt(u7)` — Rust has no native u7. + /// Maximum value of a 7-bit integer (Rust has no native u7). const U7_MAX: u8 = 127; // base64 stores values up to 7 bits @@ -350,11 +345,10 @@ pub mod vlq { bytes }; - // Shared body for `decode` / `decode_assume_valid`. The two .zig originals - // (src/sourcemap/VLQ.zig:104/135) differ only by two `bun.assert` lines; - // const-generic `ASSERT_VALID` is const-folded so codegen matches the - // hand-duplicated bodies. - // PERF(port): loop was `inline for` (unrolled) — profile if hot. + // Shared body for `decode` / `decode_assume_valid` (which differ only by + // two asserts); const-generic `ASSERT_VALID` is const-folded so codegen + // matches hand-duplicated bodies. + // PERF: loop is not unrolled — profile if hot. #[inline(always)] fn decode_impl(encoded: &[u8], start: usize) -> VLQResult { let mut shift: u8 = 0; @@ -368,7 +362,7 @@ pub mod vlq { if ASSERT_VALID { debug_assert!(encoded_[i] < U7_MAX); // invalid base64 character } - // `@as(u7, @truncate(...))` → mask to 7 bits + // mask to 7 bits let index = BASE64_LUT[(encoded_[i] & 0x7f) as usize] as u32; if ASSERT_VALID { debug_assert!(index != U7_MAX as u32); // invalid base64 character @@ -467,7 +461,7 @@ pub mod zig_base64 { } /// Standard Base64 codecs, with padding - // PORT NOTE: Zig comptime → const-initialized `static` (lives in `.rodata`, no `Once`). + // Const-initialized `static` (lives in `.rodata`, no `Once`). pub static STANDARD: Codecs = Codecs { alphabet_chars: STANDARD_ALPHABET_CHARS, pad_char: Some(b'='), @@ -488,9 +482,6 @@ pub mod zig_base64 { pub(crate) const URL_SAFE_ALPHABET_CHARS: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; - // PORT NOTE: dropped `standard_pad_char`/`standard_encoder`/`standard_decoder` - // @compileError deprecation stubs — no Rust equivalent for use-site compile errors. - #[derive(Copy, Clone)] pub struct Base64Encoder { pub alphabet_chars: [u8; 64], @@ -544,7 +535,6 @@ pub mod zig_base64 { } pub fn encode_without_size_check(&self, dest: &mut [u8], source: &[u8]) -> usize { - // PORT NOTE: Zig used u12/u4; Rust uses u16/u8 with explicit masking. let mut acc: u16 = 0; let mut acc_len: u8 = 0; let mut out_idx: usize = 0; @@ -641,7 +631,6 @@ pub mod zig_base64 { if self.pad_char.is_some() && !source.len().is_multiple_of(4) { return Err(Error::InvalidPadding); } - // PORT NOTE: Zig used u12/u4; Rust uses u16/u8 with explicit masking. let mut acc: u16 = 0; let mut acc_len: u8 = 0; let mut dest_idx: usize = 0; @@ -740,11 +729,10 @@ pub mod zig_base64 { wrote: &mut usize, ) -> Result<(), Error> { let decoder = &self.decoder; - // PORT NOTE: Zig used u12/u4; Rust uses u16/u8 with explicit masking. let mut acc: u16 = 0; let mut acc_len: u8 = 0; - // PORT NOTE: reshaped `defer { wrote.* = dest_idx; }` into direct mutation - // of `*wrote` so it is always current on every return path. + // `*wrote` is mutated directly (rather than once before return) + // so it is always current on every return path. *wrote = 0; let mut leftover_idx: Option = None; @@ -1003,7 +991,7 @@ pub mod zig_base64 { // `LinkerContext.rs::css_modules_hash_shim`). `bun_css` re-exports this as // `css_modules::hash` for its in-crate callers. // -// Spec: `src/css/css_modules.zig:hash` — wyhash(u64) of the formatted args, +// Behavior: wyhash(u64) of the formatted args, // truncated to u32, url-safe-base64-encoded into a bump-allocated slice. If // `at_start` and the first encoded byte is a digit, prefix `_` (CSS idents // can't start with a digit). @@ -1017,10 +1005,8 @@ pub fn wyhash_url_safe<'a>( ) -> &'a [u8] { use std::io::Write as _; - // PERF(port): was stack-fallback alloc (StackFallbackAllocator 128B) — profile if hot. let mut hasher = bun_wyhash::Wyhash11::init(0); - // PORT NOTE: std.fmt.count + allocPrint collapsed; write into a scratch - // Vec then hash. Freed immediately (Zig used stack-fallback for this). + // Write into a scratch Vec then hash; freed immediately. let mut fmt_str: Vec = Vec::with_capacity(128); write!(&mut fmt_str, "{}", args).expect("unreachable"); hasher.update(&fmt_str); @@ -1030,9 +1016,8 @@ pub fn wyhash_url_safe<'a>( let encode_len = simdutf_encode_len_url_safe(h_bytes.len()); - // PORT NOTE: Zig reused fmt_str buffer when encode_len > 128 - at_start; arena makes the - // distinction moot (both arms allocate from bump). Always alloc fresh slice here. - // PERF(port): was buffer reuse for large encode_len — profile if hot. + // Always alloc a fresh slice from the arena. + // PERF: no buffer reuse for large encode_len — profile if hot. let slice_to_write: &mut [u8] = bump.alloc_slice_fill_default(encode_len + usize::from(at_start)); @@ -1045,7 +1030,7 @@ pub fn wyhash_url_safe<'a>( && base64_encoded_hash[0] >= b'0' && base64_encoded_hash[0] <= b'9' { - // std.mem.copyBackwards: overlapping copy, dest > src → copy_within + // Overlapping copy, dest > src → copy_within. slice_to_write.copy_within(0..base64_encoded_hash_len, 1); slice_to_write[0] = b'_'; return &slice_to_write[0..base64_encoded_hash_len + 1]; @@ -1053,5 +1038,3 @@ pub fn wyhash_url_safe<'a>( &slice_to_write[0..base64_encoded_hash_len] } - -// ported from: src/base64/base64.zig diff --git a/src/boringssl/lib.rs b/src/boringssl/lib.rs index 62af27833c7..65852405cf7 100644 --- a/src/boringssl/lib.rs +++ b/src/boringssl/lib.rs @@ -1,5 +1,5 @@ // TODO: move all custom functions from the translated file into this file, then -// the translated file can be provided by `zig translate-c` +// the translated file can be generated by bindgen. #![warn(unused_must_use)] use core::ffi::{c_int, c_void}; @@ -10,8 +10,8 @@ pub use bun_boringssl_sys as boring; use bun_cares_sys as c_ares; use bun_core::strings; -// MOVE_DOWN: ported from `src/runtime/api/bun/x509.zig::isSafeAltName`. -// Lives here so `boringssl` does not depend on `bun_runtime` (tier-6). +// MOVE_DOWN: lives here so `boringssl` does not depend on `bun_runtime` +// (tier-6). pub mod x509 { /// Returns `true` iff `name` contains no characters that would require /// escaping in a subjectAltName entry. @@ -77,7 +77,7 @@ pub fn load() { // ────────────────────────────────────────────────────────────────────────── // Extra FFI surface not yet exposed by `bun_boringssl_sys` (hand-curated -// subset). Ground truth: src/boringssl_sys/boringssl.zig + openssl/ssl.h. +// subset). Ground truth: openssl/ssl.h. // Remove once the bindgen pipeline lands these in the sys crate. // ────────────────────────────────────────────────────────────────────────── @@ -127,16 +127,14 @@ unsafe impl Sync for CtxStore {} static CTX_STORE: std::sync::OnceLock = std::sync::OnceLock::new(); std::thread_local! { - // Zig: `threadlocal var auto_crypto_buffer_pool: ?*CRYPTO_BUFFER_POOL = null` - // (boringssl.zig:19225). One pool per thread, lazily allocated on first - // `SSL_CTX.setup()` call from that thread. + // One pool per thread, lazily allocated on the first `ssl_ctx_setup()` + // call from that thread. static AUTO_CRYPTO_BUFFER_POOL: Cell<*mut CRYPTO_BUFFER_POOL> = const { Cell::new(ptr::null_mut()) }; } -/// Zig: `SSL_CTX.setup(ctx)` (boringssl.zig:19204) — install the per-thread -/// `CRYPTO_BUFFER_POOL` and set the cipher list to BoringSSL's -/// `SSL_DEFAULT_CIPHER_LIST` (`"ALL"`). +/// Install the per-thread `CRYPTO_BUFFER_POOL` and set the cipher list to +/// BoringSSL's `SSL_DEFAULT_CIPHER_LIST` (`"ALL"`). /// /// # Safety /// `ctx` must be a live `SSL_CTX*`. @@ -156,9 +154,8 @@ pub unsafe fn ssl_ctx_setup(ctx: *mut boring::SSL_CTX) { } pub fn init_client() -> *mut boring::SSL { - // SAFETY: BoringSSL FFI; single-threaded startup assumption (matches Zig). + // SAFETY: BoringSSL FFI; single-threaded startup assumption. unsafe { - // Zig: `if (ctx_store != null) _ = boring.SSL_CTX_up_ref(ctx_store.?);` // Bump the refcount on every call after the first; the first call's // `SSL_CTX_new` already returns refcount = 1. if let Some(stored) = CTX_STORE.get() { @@ -166,7 +163,7 @@ pub fn init_client() -> *mut boring::SSL { } let ctx = CTX_STORE .get_or_init(|| { - // Zig: `SSL_CTX.init()` — see boringssl.zig:19197. Three steps: + // Three steps: // 1. SSL_CTX_new(TLS_with_buffers_method()) // 2. setCustomVerify(noop_custom_verify) → SSL_CTX_set_custom_verify(ctx, 0, cb) // 3. setup() → CRYPTO_BUFFER_POOL_new + set0_buffer_pool + set_cipher_list("ALL") @@ -178,9 +175,7 @@ pub fn init_client() -> *mut boring::SSL { .0 .as_ptr(); - // Zig: `SSL.init(ctx)` = `SSL_new(ctx)` let ssl = boring::SSL_new(ctx); - // Zig: `setIsClient(true)` = `SSL_set_connect_state(ssl)` boring::SSL_set_connect_state(ssl); ssl @@ -460,9 +455,3 @@ pub fn check_server_identity(ssl_ptr: &mut boring::SSL, hostname: &[u8]) -> bool } false } - -// NOTE: `pub const ERR_toJS = @import("../runtime/crypto/boringssl_jsc.zig").ERR_toJS;` -// is intentionally dropped — *_jsc alias; in Rust the JS conversion lives in the -// `bun_runtime`/`*_jsc` crate as an extension method. - -// ported from: src/boringssl/boringssl.zig diff --git a/src/boringssl_sys/boringssl.rs b/src/boringssl_sys/boringssl.rs index ffa1a3ef806..771747b0c54 100644 --- a/src/boringssl_sys/boringssl.rs +++ b/src/boringssl_sys/boringssl.rs @@ -1,12 +1,9 @@ //! Hand-rolled BoringSSL FFI surface. //! -//! Ground truth: `src/boringssl_sys/boringssl.zig` (translate-c output) and -//! `vendor/boringssl/include/openssl/*.h`. This file exposes only the subset -//! of symbols Bun's Rust crates actually consume — it is **not** a full -//! bindgen dump. When the bindgen pipeline lands this module is replaced -//! wholesale. -// -// ported from: src/boringssl_sys/boringssl.zig +//! Ground truth: `vendor/boringssl/include/openssl/*.h`. This file exposes +//! only the subset of symbols Bun's Rust crates actually consume — it is +//! **not** a full bindgen dump. When the bindgen pipeline lands this module +//! is replaced wholesale. use core::ffi::{c_char, c_int, c_long, c_uint, c_void}; @@ -147,7 +144,7 @@ pub union env_md_ctx_md_data { /// `struct env_md_ctx_st` — laid out to match /// `vendor/boringssl/include/openssl/digest.h` so it can live by-value on the -/// Rust side (the Zig port stores it inline, not behind `EVP_MD_CTX_new`). +/// Rust side (stored inline, not behind `EVP_MD_CTX_new`). #[repr(C)] #[derive(Copy, Clone)] pub struct EVP_MD_CTX { @@ -439,12 +436,10 @@ unsafe extern "C" { // Typed STACK_OF(...) inline wrappers // // BoringSSL defines these as `static inline` in C, so they have no exported -// symbol — they bottom out on the untyped `sk_*` ABI above. Mirrors the -// translate-c bodies in `boringssl.zig`. +// symbol — they bottom out on the untyped `sk_*` ABI above. // ═══════════════════════════════════════════════════════════════════════════ -/// Per-stack free callback type used by `sk_GENERAL_NAME_pop_free` -/// (matches Zig's `stack_GENERAL_NAME_free_func`). +/// Per-stack free callback type used by `sk_GENERAL_NAME_pop_free`. pub(crate) type sk_GENERAL_NAME_free_func = unsafe extern "C" fn(*mut struct_stack_st_GENERAL_NAME); #[inline] @@ -570,7 +565,7 @@ pub const RSA_PKCS1_OAEP_PADDING: c_int = 4; pub(crate) type CRYPTO_refcount_t = u32; /// `ossl_ssize_t` — signed counterpart of `size_t` for BoringSSL "length or -1" -/// parameters. Mirrors the `isize` definition in `boringssl.zig`. +/// parameters. pub(crate) type ossl_ssize_t = isize; /// `bio_info_cb` — callback type for `BIO_METHOD.callback_ctrl`. @@ -594,7 +589,7 @@ pub struct BIO_METHOD { pub callback_ctrl: Option c_long>, } -/// `struct bio_st` — exposed by-value because the Zig side reaches into +/// `struct bio_st` — exposed by-value because callers reach into /// `flags`/`num`/`ptr` directly when implementing custom BIO backends. #[repr(C)] #[derive(Copy, Clone)] diff --git a/src/brotli/lib.rs b/src/brotli/lib.rs index debf1039bde..636e087a4b0 100644 --- a/src/brotli/lib.rs +++ b/src/brotli/lib.rs @@ -23,9 +23,7 @@ pub struct DecoderOptions { pub params: DecoderParams, } -/// Zig: `std.enums.EnumFieldStruct(c.BrotliDecoderParameter, bool, false)` — -/// one `bool` per `BrotliDecoderParameter` variant, default `false`. -// TODO(port): if BrotliDecoderParameter grows more variants, mirror them here. +/// One `bool` per `BrotliDecoderParameter` variant, default `false`. #[derive(Default)] pub struct DecoderParams { pub large_window: bool, @@ -49,10 +47,6 @@ impl Default for DecoderOptions { pub struct BrotliReaderArrayList<'a> { pub input: &'a [u8], - // PORT NOTE: reshaped for borrowck — Zig kept a by-value copy of the - // ArrayListUnmanaged in `list` and wrote it back to `*list_ptr` on every - // `readAll` (defer). `Vec` is not `Copy`, so we operate on `list_ptr` - // directly and drop the redundant `list` + `list_allocator` fields. pub list_ptr: &'a mut Vec, pub brotli: *mut c::BrotliDecoder, pub state: ReaderState, @@ -69,7 +63,6 @@ pub struct BrotliReaderArrayList<'a> { pub use bun_core::compress::State as ReaderState; impl<'a> BrotliReaderArrayList<'a> { - // Zig: `pub const new = bun.TrivialNew(BrotliReaderArrayList);` #[inline] pub fn new(value: Self) -> Box { Box::new(value) @@ -98,7 +91,6 @@ impl<'a> BrotliReaderArrayList<'a> { list: &'a mut Vec, options: &DecoderOptions, ) -> Result, Error> { - // TODO(port): narrow error set Ok(Self::new(Self::init_with_options( input, list, @@ -117,7 +109,6 @@ impl<'a> BrotliReaderArrayList<'a> { finish_flush_op: c::BrotliEncoderOperation, full_flush_op: c::BrotliEncoderOperation, ) -> Result { - // TODO(port): narrow error set if !BrotliDecoder::initialize_brotli() { return Err(err!("BrotliFailedToLoad")); } @@ -166,10 +157,6 @@ impl<'a> BrotliReaderArrayList<'a> { } pub fn read_all(&mut self, is_done: bool) -> Result<(), Error> { - // TODO(port): narrow error set - // PORT NOTE: Zig's `defer this.list_ptr.* = this.list;` is gone — we - // mutate through `list_ptr` directly (see field note above). - if self.state == ReaderState::End || self.state == ReaderState::Error { return Ok(()); } @@ -266,8 +253,6 @@ impl<'a> Drop for BrotliReaderArrayList<'a> { // Created by BrotliDecoder::create_instance; destroyed exactly once here. BrotliDecoder::destroy_instance(self.brotli_mut()); } - // PORT NOTE: Zig's `bun.destroy(this)` is implicit — callers hold a - // `Box` and dropping it frees the allocation. } } @@ -298,7 +283,6 @@ impl BrotliCompressionStream { finish_flush_op: c::BrotliEncoderOperation, full_flush_op: c::BrotliEncoderOperation, ) -> Result { - // TODO(port): narrow error set // SAFETY: brotli FFI constructor; alloc/free are valid extern "C" // fns and opaque is null (unused by our allocator). let instance = unsafe { @@ -336,7 +320,6 @@ impl BrotliCompressionStream { // next compress_stream/destroy call. Tying it to `&mut self` prevents // overlapping calls that would invalidate it. pub fn write_chunk(&mut self, input: &[u8], last: bool) -> Result<&[u8], Error> { - // TODO(port): narrow error set self.total_in += input.len(); let op = if last { self.finish_flush_op @@ -360,7 +343,6 @@ impl BrotliCompressionStream { } pub fn write(&mut self, input: &[u8], last: bool) -> Result<&[u8], Error> { - // TODO(port): narrow error set if self.state == CompressionState::End || self.state == CompressionState::Error { return Ok(b""); } @@ -369,12 +351,9 @@ impl BrotliCompressionStream { } pub fn end(&mut self) -> Result<&[u8], Error> { - // TODO(port): narrow error set - // Zig: `defer this.state = .End` — runs on BOTH ok and error paths. - // PORT NOTE: reshaped for borrowck — `compress_stream`'s output borrows - // `&mut *self.brotli`, so we set `self.state` first and inline - // write/write_chunk("", true). Net state matches Zig (defer overrides - // any intermediate `Error` back to `End`). + // `state` ends up `End` on both ok and error paths; set it before + // calling `compress_stream` because its output borrows + // `&mut *self.brotli`. if matches!(self.state, CompressionState::End | CompressionState::Error) { self.state = CompressionState::End; return Ok(b""); @@ -395,8 +374,8 @@ impl BrotliCompressionStream { BrotliWriter::init(self, writable) } - // TODO(port): Zig's `writer()` returned a `std.Io.GenericWriter` adapter. - // Rust callers should use `writer_context()` directly (it impls Write). + // The returned `BrotliWriter` implements `bun_io::Write` itself, so this + // is just an alias for `writer_context()`. pub fn writer(&mut self, writable: W) -> BrotliWriter<'_, W> { self.writer_context(writable) } @@ -411,16 +390,12 @@ impl Drop for BrotliCompressionStream { } } -// Zig: `fn NewWriter(comptime InputWriter: type) type { return struct {...} }` pub struct BrotliWriter<'a, W> { pub compressor: &'a mut BrotliCompressionStream, pub input_writer: W, } impl<'a, W: bun_io::Write> BrotliWriter<'a, W> { - // Zig: `WriteError = error{BrotliCompressionError} || InputWriter.Error` - // PORT NOTE: error-set union collapsed to `bun_core::Error`. - pub fn init(compressor: &'a mut BrotliCompressionStream, input_writer: W) -> Self { Self { compressor, @@ -435,15 +410,24 @@ impl<'a, W: bun_io::Write> BrotliWriter<'a, W> { } pub fn end(&mut self) -> Result<(), Error> { - // PORT NOTE: Zig declared `!usize` but the body has no return — the - // Zig fn would fail to compile if ever instantiated. Port as `()`. let decompressed = self.compressor.end()?; self.input_writer.write_all(decompressed)?; Ok(()) } - - // TODO(port): `std.Io.GenericWriter` adapter — provide `impl bun_io::Write` - // if any caller needs the trait object. } -// ported from: src/brotli/brotli.zig +impl bun_io::Write for BrotliWriter<'_, W> { + fn write_all(&mut self, buf: &[u8]) -> Result<(), Error> { + self.write(buf).map(|_| ()) + } + + fn flush(&mut self) -> Result<(), Error> { + // Drain the encoder first so compressed-so-far bytes reach the sink: + // an empty write runs `compress_stream` with the stream's configured + // `flush_op` (emits pending output for FLUSH-configured streams; a + // no-op for PROCESS). `end()` is still required to finalize. + let out = self.compressor.write(b"", false)?; + self.input_writer.write_all(out)?; + self.input_writer.flush() + } +} diff --git a/src/brotli_sys/brotli_c.rs b/src/brotli_sys/brotli_c.rs index a8c78320334..846e7ebede6 100644 --- a/src/brotli_sys/brotli_c.rs +++ b/src/brotli_sys/brotli_c.rs @@ -16,9 +16,8 @@ pub const BROTLI_SHARED_DICTIONARY_SERIALIZED: c_int = 1; pub type enum_BrotliSharedDictionaryType = c_uint; pub type BrotliSharedDictionaryType = enum_BrotliSharedDictionaryType; -// pub extern fn BrotliSharedDictionaryCreateInstance(alloc_func: brotli_alloc_func, free_func: brotli_free_func, opaque: ?*anyopaque) ?*BrotliSharedDictionary; -// pub extern fn BrotliSharedDictionaryDestroyInstance(dict: ?*BrotliSharedDictionary) void; -// pub extern fn BrotliSharedDictionaryAttach(dict: ?*BrotliSharedDictionary, type: BrotliSharedDictionaryType, data_size: usize, data: [*]const u8) c_int; +// Not bound: BrotliSharedDictionaryCreateInstance, BrotliSharedDictionaryDestroyInstance, +// BrotliSharedDictionaryAttach (unused). unsafe extern "C" { // Opaque handle by reference + scalars only. @@ -109,7 +108,6 @@ impl BrotliDecoder { decoded_ptr, ) }; - // PORT NOTE: reshaped for borrowck — Zig mutated `decoded.len` in place via `*[]u8` // SAFETY: decoded_ptr points to the same allocation; decoded_size <= original len per brotli contract *decoded = unsafe { core::slice::from_raw_parts_mut(decoded_ptr, decoded_size) }; result @@ -183,7 +181,7 @@ impl BrotliDecoder { } } -#[repr(u32)] // Zig: enum(c_uint) +#[repr(u32)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum BrotliDecoderResult { err = 0, @@ -192,14 +190,12 @@ pub enum BrotliDecoderResult { needs_more_output = 3, } -// NOTE: brotli_c.zig (translate-c output) defines this error-code table three times -// (loose `BROTLI_DECODER_*` consts, a subset `BrotliDecoderErrorCode` enum, and the -// full `BrotliDecoderErrorCode2` enum). They are intentionally collapsed here into the -// single complete enum below; `BrotliDecoderErrorCode` is kept as an alias for FFI -// signatures. Do not re-add the duplicates on a mechanical re-port. +// NOTE: the duplicate error-code tables the upstream brotli headers define are +// intentionally collapsed into the single enum below; `BrotliDecoderErrorCode` +// is kept as an alias so FFI signatures keep their upstream names. pub type BrotliDecoderErrorCode = BrotliDecoderErrorCode2; -#[repr(i32)] // Zig: enum(c_int) +#[repr(i32)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum BrotliDecoderErrorCode2 { NO_ERROR = 0, @@ -237,7 +233,7 @@ pub enum BrotliDecoderErrorCode2 { pub const BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION: c_int = 0; pub const BROTLI_DECODER_PARAM_LARGE_WINDOW: c_int = 1; -#[repr(u32)] // Zig: enum(c_uint) +#[repr(u32)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum BrotliDecoderParameter { DISABLE_RING_BUFFER_REALLOCATION = 0, @@ -256,7 +252,7 @@ pub const BROTLI_MODE_GENERIC: c_int = 0; pub const BROTLI_MODE_TEXT: c_int = 1; pub const BROTLI_MODE_FONT: c_int = 2; -#[repr(u32)] // Zig: enum(c_uint) +#[repr(u32)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum BrotliEncoderMode { generic = 0, @@ -280,7 +276,7 @@ pub const BROTLI_PARAM_NPOSTFIX: c_int = 7; pub const BROTLI_PARAM_NDIRECT: c_int = 8; pub const BROTLI_PARAM_STREAM_OFFSET: c_int = 9; -#[repr(u32)] // Zig: enum(c_uint) +#[repr(u32)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum BrotliEncoderParameter { mode = 0, @@ -363,7 +359,7 @@ bun_opaque::opaque_ffi! { pub struct BrotliEncoder; } -#[repr(u32)] // Zig: enum(c_uint) +#[repr(u32)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum BrotliEncoderOperation { process = 0, @@ -389,8 +385,8 @@ impl<'a> Default for CompressionResult<'a> { } } -// PORT NOTE: Zig's `BrotliEncoder.Operation` flattened to module-level alias -// (inherent associated types are unstable in Rust). +// `BrotliEncoder.Operation` is a module-level alias because inherent +// associated types are unstable in Rust. pub type Operation = BrotliEncoderOperation; impl BrotliEncoder { @@ -490,5 +486,3 @@ pub const BROTLI_MAX_QUALITY: c_int = 11; pub const BROTLI_DEFAULT_QUALITY: c_int = 11; pub const BROTLI_DEFAULT_WINDOW: c_int = 22; pub const BROTLI_DEFAULT_MODE: c_int = BROTLI_MODE_GENERIC; - -// ported from: src/brotli_sys/brotli_c.zig diff --git a/src/bun.js.rs b/src/bun.js.rs index 971b9d5d46d..a4d802653ae 100644 --- a/src/bun.js.rs +++ b/src/bun.js.rs @@ -1,8 +1,8 @@ -//! Port of src/bun.js.zig — entry point for `bun run ` / standalone executables. +//! Entry point for `bun run ` / standalone executables. //! //! The `Run` struct (the per-process VM driver) is defined once in //! `crate::cli::run_command` so the CLI dispatch path can call it directly -//! without a crate-cycle; this module re-exports it under the Zig namespace +//! without a crate-cycle; this module re-exports it as //! `bun.js.Run` and hosts the handful of helpers that other crates reach for //! (`apply_standalone_runtime_flags`, `fail_with_build_error`, the //! `Bun__on{Resolve,Reject}EntryPointResult` host fns). @@ -14,7 +14,6 @@ use bun_standalone_graph::StandaloneModuleGraph::{Flags as GraphFlags, Standalon // Thin re-exports (mirrors `pub const X = @import(...)` at file top). pub use crate::api; pub use crate::webcore; -pub use bun_jsc as jsc_mod; // TODO(port): naming — Zig exposed this as `bun.js.jsc` /// Canonical `Run` lives in `cli::run_command`; re-export so callers that /// expect `bun.js.Run` resolve to the single definition. diff --git a/src/bun_alloc/BufferFallbackAllocator.rs b/src/bun_alloc/BufferFallbackAllocator.rs index 1138e6d7f80..fd8af352912 100644 --- a/src/bun_alloc/BufferFallbackAllocator.rs +++ b/src/bun_alloc/BufferFallbackAllocator.rs @@ -1,6 +1,6 @@ //! An allocator that attempts to allocate from a provided buffer first, //! falling back to another allocator when the buffer is exhausted. -//! Unlike `std.heap.StackFallbackAllocator`, this does not own the buffer. +//! The buffer is borrowed, not owned. use core::ffi::c_void; @@ -92,5 +92,3 @@ unsafe fn free(ctx: *mut c_void, buf: &mut [u8], alignment: Alignment, ra: usize } self_.fallback.raw_free(buf, alignment, ra) } - -// ported from: src/bun_alloc/BufferFallbackAllocator.zig diff --git a/src/bun_alloc/MaxHeapAllocator.rs b/src/bun_alloc/MaxHeapAllocator.rs index 71546a6b600..ed94fcc2cf6 100644 --- a/src/bun_alloc/MaxHeapAllocator.rs +++ b/src/bun_alloc/MaxHeapAllocator.rs @@ -6,8 +6,7 @@ use core::ptr::NonNull; use crate::MAX_ALIGN_T as MAX_ALIGN; use crate::{Alignment, Allocator}; -/// Zig backed `array_list` with `std.array_list.AlignedManaged(u8, .of(std.c.max_align_t))` -/// so the returned pointer is guaranteed aligned to `max_align_t`. Rust `Vec` +/// The returned pointer must be aligned to `max_align_t`. Rust `Vec` /// allocates with align 1, which would violate the `alignment <= MAX_ALIGN` /// contract. Store a raw `MAX_ALIGN`-aligned buffer instead. pub struct MaxHeapAllocator { @@ -25,12 +24,10 @@ unsafe impl Send for MaxHeapAllocator {} unsafe impl Sync for MaxHeapAllocator {} impl MaxHeapAllocator { - /// Zig: `fn alloc(ptr, len, alignment, _) ?[*]u8` pub fn alloc(&mut self, len: usize, alignment: Alignment, _ret_addr: usize) -> Option<*mut u8> { debug_assert!(alignment.to_byte_units() <= MAX_ALIGN); - // Zig: `self.array_list.items.len = 0;` — reuse the existing buffer. + // Reuse the existing buffer. self.len = 0; - // Zig: `ensureTotalCapacity(len) catch return null` if self.capacity < len { // Grow (or first-allocate) to at least `len`, MAX_ALIGN-aligned. let new_layout = Layout::from_size_align(len, MAX_ALIGN).ok()?; @@ -53,7 +50,6 @@ impl MaxHeapAllocator { Some(self.ptr?.as_ptr()) } - /// Zig: `fn resize(...) bool { @panic("not implemented") }` pub fn resize( &mut self, _buf: &mut [u8], @@ -64,7 +60,7 @@ impl MaxHeapAllocator { panic!("not implemented"); } - /// Zig: `fn free(...) void {}` — no-op (single owned buffer freed on Drop). + /// No-op (single owned buffer freed on Drop). pub fn free(&mut self, _buf: &mut [u8], _alignment: Alignment, _ret_addr: usize) {} pub fn reset(&mut self) { @@ -72,15 +68,13 @@ impl MaxHeapAllocator { } /// Borrow the allocator for a scope; `reset()` is called automatically when - /// the returned guard drops. Mirrors Zig's `defer max_heap_allocator.reset()` - /// at loop-iteration scope without an ad-hoc `scopeguard`. + /// the returned guard drops. pub fn scope(&mut self) -> MaxHeapScope<'_> { MaxHeapScope { inner: self } } - // PORT NOTE: reshaped out-param constructor. Zig's `init(self: *Self, allocator) -> std.mem.Allocator` - // both initialized `self` and returned a vtable+ptr pair. In Rust the caller constructs - // `MaxHeapAllocator::init()` and obtains `&dyn Allocator` by borrowing the result. + // The caller constructs `MaxHeapAllocator::init()` and obtains + // `&dyn Allocator` by borrowing the result. pub fn init() -> Self { Self { ptr: None, @@ -89,7 +83,6 @@ impl MaxHeapAllocator { } } - /// Zig: `pub fn isInstance(allocator) bool { return allocator.vtable == &vtable; }` pub fn is_instance(alloc: &dyn Allocator) -> bool { alloc.is::() } @@ -133,7 +126,6 @@ impl Allocator for MaxHeapAllocator {} impl Drop for MaxHeapAllocator { fn drop(&mut self) { - // Zig: `pub fn deinit` — freed `array_list`. if let Some(ptr) = self.ptr.take() { // SAFETY: `ptr`/`capacity` were produced by `alloc`/`realloc` above // with `MAX_ALIGN` alignment. @@ -146,5 +138,3 @@ impl Drop for MaxHeapAllocator { } } } - -// ported from: src/bun_alloc/MaxHeapAllocator.zig diff --git a/src/bun_alloc/MimallocArena.rs b/src/bun_alloc/MimallocArena.rs index 2b0b1795e1d..84c46d1421e 100644 --- a/src/bun_alloc/MimallocArena.rs +++ b/src/bun_alloc/MimallocArena.rs @@ -1,15 +1,13 @@ -//! Port of `src/bun_alloc/MimallocArena.zig`. -//! //! A per-heap mimalloc allocator. Unlike `bumpalo::Bump`, every allocation //! made through this arena is individually freeable (via `mi_free`) and //! resizable (via `mi_heap_realloc_aligned`), so `Vec` //! does **not** leak the old buffer on grow. `Drop`/`reset()` bulk-free the -//! whole heap with `mi_heap_destroy`, matching Zig's `deinit`. +//! whole heap with `mi_heap_destroy`. // // PERF NOTE: a previous iteration layered a bump-chunk allocator and a cached // `mi_theap_t*` here. Reverted: the theap is per-OS-thread while // `MimallocArena: Send`, and the bump layer caused #53599's UAF when mimalloc -// recycled a destroyed `mi_heap_t*` slot. Zig parity is plain `mi_heap_malloc`. +// recycled a destroyed `mi_heap_t*` slot. Plain `mi_heap_malloc` is correct. // If `_mi_heap_theap` thrash resurfaces in profiles, the supported fix is // `mi_heap_set_default(heap)` for the parse scope, not manual theap caching. //! @@ -39,11 +37,11 @@ pub(crate) static HEAP_NEW_COUNT: core::sync::atomic::AtomicUsize = pub(crate) static HEAP_DESTROY_COUNT: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0); -// ── Debug-only thread-ownership guard (Zig: `bun.safety.ThreadLock`) ────── +// ── Debug-only thread-ownership guard ───────────────────────────────────── // // `bun_alloc` sits below `bun_core` in the crate graph, so we cannot reuse -// `bun_core::ThreadLock`. This is the minimal subset needed to mirror Zig's -// `ci_assert` same-thread check on the `mi_heap_*` allocation paths: a per- +// `bun_core::ThreadLock`. This is the minimal subset needed for a same-thread +// check on the `mi_heap_*` allocation paths: a per- // thread monotone id stamped at `MimallocArena::new()` and asserted on every // alloc/realloc. `mi_free` is documented thread-safe and is left unchecked. @@ -65,7 +63,7 @@ fn debug_thread_stamp() -> u64 { } /// A mimalloc heap. Owns a `mi_heap_t`; all allocations are bulk-freed on -/// `Drop` (Zig: `MimallocArena.deinit` → `mi_heap_destroy`). +/// `Drop` via `mi_heap_destroy`. /// /// Implements [`core::alloc::Allocator`] for `&MimallocArena`, so it can back /// `Vec` / `Box` with real per-allocation @@ -75,11 +73,11 @@ pub struct MimallocArena { /// `true` when `heap` came from `mi_heap_new()` and must be /// `mi_heap_destroy`ed on `Drop`/`reset()`. `false` when borrowing the /// process-wide `mi_heap_main()` (see [`Self::borrowing_default`]) — Drop - /// is then a no-op and allocations live for the process lifetime, matching - /// Zig's `default_allocator` shape for callers that just need an `&Arena` + /// is then a no-op and allocations live for the process lifetime — for + /// callers that just need an `&Arena` /// without paying `mi_heap_new` + `mi_heap_destroy`. owns: bool, - /// Zig: `thread_lock: bun.safety.ThreadLock` (debug-only). Stamped on + /// Debug-only thread-ownership stamp. Stamped on /// `new()`/`reset()`; asserted on every `mi_heap_*` alloc/realloc path. /// Compiles out in release so the struct stays one pointer wide. #[cfg(debug_assertions)] @@ -87,17 +85,17 @@ pub struct MimallocArena { } // SAFETY: mimalloc heaps are not generally thread-safe for allocation from -// multiple threads, but `mi_free` may be called from any thread, and the -// Zig original guards same-thread use via `ThreadLock` in debug. We expose -// `Send` so an arena can be moved into a worker thread (matching Zig's -// `MimallocArena` being passed to thread-pool workers); concurrent `&self` -// allocation across threads is the caller's responsibility, same as Zig. +// multiple threads, but `mi_free` may be called from any thread, and +// same-thread allocation is enforced by `assert_owning_thread()` in debug +// builds. We expose `Send` so an arena can be moved into a worker thread +// (e.g. passed to thread-pool workers); concurrent `&self` +// allocation across threads is the caller's responsibility. unsafe impl Send for MimallocArena {} // SAFETY: `Sync` is required because the bundler embeds `&MimallocArena` in // `Send + Sync` contexts (worker tasks hold a shared ref for `mi_free` / // `owns_ptr`), but `mi_heap_malloc*`/`mi_heap_realloc*` are NOT safe under // concurrent `&self`. The contract — enforced by `assert_owning_thread()` in -// debug builds, mirroring Zig's `ci_assert` `ThreadLock` — is that only the +// debug builds — is that only the // thread that constructed (or last `reset()`) the arena may allocate from it. // Cross-thread `deallocate` is permitted (mimalloc `mi_free` is thread-safe). unsafe impl Sync for MimallocArena {} @@ -110,7 +108,6 @@ impl Default for MimallocArena { } impl MimallocArena { - /// Zig: `MimallocArena.init()` — `mi_heap_new() orelse bun.outOfMemory()`. #[inline] pub fn new() -> Self { #[cfg(debug_assertions)] @@ -132,8 +129,8 @@ impl MimallocArena { /// `mi_malloc`/`mi_free` and live until individually freed (or process /// exit for `into_bump_slice`-style leaks). /// - /// Use this where Zig threads `bun.default_allocator` through an - /// `Allocator`-shaped parameter and the Rust port needs an `&Arena` but + /// Use this where a `default_allocator`-shaped parameter needs an + /// `&Arena` but /// the `mi_heap_new` + `mi_heap_destroy` pair is measurable overhead on a /// hot, short-lived path (e.g. `Bunfig::parse` on `bun -e ''` startup). #[inline] @@ -153,7 +150,7 @@ impl MimallocArena { } } - /// Zig: `Borrowed.assertThreadLock()` — debug-only check that the calling + /// Debug-only check that the calling /// thread is the one that constructed (or last `reset()`) this arena. /// Guards every `mi_heap_*` allocation path so the over-broad `Sync` impl /// cannot silently corrupt mimalloc's per-heap free lists. @@ -176,13 +173,13 @@ impl MimallocArena { } } - /// Alias for [`Self::new`] — matches the Zig spelling. + /// Alias for [`Self::new`]. #[inline] pub fn init() -> Self { Self::new() } - /// Raw `mi_heap_t*` (Zig: `Borrowed.getMimallocHeap`). + /// Raw `mi_heap_t*`. /// /// This is the sole accessor for the `heap` field. A `&Heap`-returning /// accessor is intentionally **not** provided: `mimalloc::Heap` is an @@ -232,29 +229,22 @@ impl MimallocArena { self.heap = NonNull::new(heap).unwrap_or_else(|| crate::out_of_memory()); // `&mut self` proves exclusive access; re-stamp the debug thread-lock // so an arena `Send`-moved to a worker and then reset there may - // allocate on that worker (Zig has no equivalent because its - // `MimallocArena` is not moved post-init). + // allocate on that worker. #[cfg(debug_assertions)] self.owning_thread .store(debug_thread_stamp(), Ordering::Relaxed); } - /// Zig: `std.heap.ArenaAllocator.reset(.{.retain_with_limit = limit})`. - /// /// Retains the warm heap while its in-use footprint is `<= limit`, and /// only then falls back to a full [`Self::reset`] (`mi_heap_destroy` + /// `mi_heap_new`). Returns `true` when the heap was retained, `false` when - /// it was recycled — mirroring `ArenaAllocator.reset`'s "reuse succeeded" - /// boolean. + /// it was recycled. /// - /// **Why this isn't a no-op-or-full-reset.** Zig's `std.heap.ArenaAllocator` - /// is a bump allocator: `.retain_with_limit` rewinds the bump cursor to - /// offset 0 (logically freeing every allocation) but keeps up to `limit` - /// bytes of *backing buffer* committed, so the next cycle's allocations - /// reuse those warm pages instead of faulting in (and zeroing) fresh ones. - /// A `mi_heap_t` has no "free all blocks but keep the pages" primitive — + /// **Why this isn't a no-op-or-full-reset.** The ideal behavior would be + /// "free every block but keep the warm pages (up to `limit`)" — but + /// a `mi_heap_t` has no "free all blocks but keep the pages" primitive — /// the only bulk-free is `mi_heap_destroy`, which also hands the pages back - /// to mimalloc (which may purge/decommit them). So the faithful mapping is + /// to mimalloc (which may purge/decommit them). So the policy is /// "keep the *whole* heap — blocks and all — while it is still small; /// recycle it once it grows past `limit`". The retained blocks are dead /// (arena callers never `mi_free`; `AstAlloc::deallocate` is a no-op by @@ -265,13 +255,12 @@ impl MimallocArena { /// small cycles. `limit` is the RSS/CPU knob: a tighter cap trades warm /// pages for lower steady-state RSS; a looser one does the reverse. /// - /// (An earlier port made this an unconditional `reset()` on the theory + /// (An earlier iteration made this an unconditional `reset()` on the theory /// that mimalloc's per-thread segment cache already keeps pages warm /// across `mi_heap_destroy`/`mi_heap_new`. In practice, with a lower RSS /// ceiling and mimalloc's purge timer, the recycled page is often already /// decommitted by the time the next cycle touches it, so the round-trip - /// re-commits and re-zeroes it — exactly the cost `.retain_with_limit` - /// exists to avoid. Hence the cap-gated retain.) + /// re-commits and re-zeroes it. Hence the cap-gated retain.) /// /// **Why `mi_heap_collect` is not an alternative to the cap.** When the /// caller's allocations are individually freed before the cycle ends (most @@ -309,14 +298,14 @@ impl MimallocArena { false } - /// Zig: `MimallocArena.gc()` → `mi_heap_collect(heap, false)`. + /// `mi_heap_collect(heap, false)`. #[inline] pub fn gc(&self) { // SAFETY: `self.heap` is a live heap. unsafe { mimalloc::mi_heap_collect(self.heap_ptr(), false) }; } - /// Zig: `MimallocArena.helpCatchMemoryIssues()` — debug-only collect of + /// Debug-only collect of /// both this heap and the global mimalloc state to surface UAF early. #[inline] pub fn help_catch_memory_issues(&self) { @@ -359,7 +348,7 @@ impl MimallocArena { total } - /// Zig: `MimallocArena.ownsPtr()` → `mi_heap_contains(heap, p)`. + /// `mi_heap_contains(heap, p)`. /// `mi_heap_contains` only tests address-range membership and never /// dereferences `addr`, so this takes the address as `usize` (not a raw /// pointer — there is no caller precondition to uphold). @@ -369,7 +358,7 @@ impl MimallocArena { unsafe { mimalloc::mi_heap_contains(self.heap_ptr(), core::ptr::without_provenance(addr)) } } - /// Zig: `Borrowed.alignedAlloc` — `mi_heap_malloc[_aligned]` on this + /// `mi_heap_malloc[_aligned]` on this /// arena's heap. #[inline] fn aligned_alloc(&self, len: usize, align: usize) -> *mut u8 { @@ -379,7 +368,7 @@ impl MimallocArena { unsafe { heap_alloc_maybe_aligned(self.heap_ptr(), len, align) } } - /// Zig: `vtable_resize` — in-place expand/shrink, no relocation. + /// In-place expand/shrink, no relocation. /// Returns `true` if the block now has at least `new_len` bytes. #[inline] pub fn resize_in_place(&self, ptr: NonNull, _old_len: usize, new_len: usize) -> bool { @@ -519,17 +508,17 @@ impl MimallocArena { unsafe { core::slice::from_raw_parts_mut(dst.as_ptr(), len) } } - // ── StdAllocator vtable bridge (Zig: `heap_allocator_vtable`) ──────── + // ── StdAllocator vtable bridge ──────────────────────────────────────── - /// Zig: `MimallocArena.arena()` — erase to the fat `{ptr, vtable}` + /// Erase to the fat `{ptr, vtable}` /// `StdAllocator` so this arena can flow through code that still threads - /// the Zig-style allocator handle. + /// an allocator handle. #[inline] pub fn std_allocator(&self) -> crate::StdAllocator { // `ctx` is `*const MimallocArena` (not the inner `*mut Heap`) so the // vtable thunks can reach `assert_owning_thread()`. They load - // `heap_ptr()` from it on every call; this is one extra indirection vs - // Zig (`ctx == heap`). The only consumer of `ctx` is this vtable; + // `heap_ptr()` from it on every call; this is one extra indirection + // vs `ctx == heap`. The only consumer of `ctx` is this vtable; // `is_instance()` compares the *vtable* pointer, not `ctx`. crate::StdAllocator { ptr: ptr::from_ref(self).cast_mut().cast(), @@ -537,7 +526,7 @@ impl MimallocArena { } } - /// Zig: `MimallocArena.isInstance` — does `alloc` dispatch through one of + /// Does `alloc` dispatch through one of /// this module's vtables (per-heap or process-global mimalloc)? #[inline] pub fn is_instance(alloc: &crate::StdAllocator) -> bool { @@ -545,7 +534,7 @@ impl MimallocArena { || core::ptr::eq(alloc.vtable, &raw const GLOBAL_MIMALLOC_VTABLE) } - /// Zig: `MimallocArena.getThreadLocalDefault()` — a `StdAllocator` that + /// A `StdAllocator` that /// routes through the process-wide `mi_malloc`/`mi_free` (no per-heap ctx). /// In mimalloc v3 these are already thread-local-fast, so there is no /// separate per-thread default heap to cache. @@ -568,7 +557,7 @@ impl Drop for MimallocArena { } #[cfg(debug_assertions)] HEAP_DESTROY_COUNT.fetch_add(1, Ordering::Relaxed); - // Zig: `deinit` → `mi_heap_destroy`. Destroys the heap and bulk-frees + // `mi_heap_destroy` destroys the heap and bulk-frees // every block still allocated in it without running per-block free. // SAFETY: `self.heap` is a live heap obtained from `mi_heap_new` and // is destroyed exactly once here. @@ -668,7 +657,7 @@ unsafe impl Allocator for &MimallocArena { } } -/// Zig's `Borrowed.alignedAlloc` body — pick `mi_heap_malloc_aligned` only +/// Pick `mi_heap_malloc_aligned` only /// when `align > MI_MAX_ALIGN_SIZE`, otherwise the cheaper `mi_heap_malloc`, /// then debug-assert the returned block's usable size covers `len`. /// @@ -695,12 +684,12 @@ unsafe fn heap_alloc_maybe_aligned(heap: *mut mimalloc::Heap, len: usize, align: p.cast() } -// ── StdAllocator vtable (Zig: `heap_allocator_vtable`) ─────────────────── +// ── StdAllocator vtable (per-arena) ────────────────────────────────────── unsafe fn vtable_alloc(ctx: *mut c_void, len: usize, a: crate::Alignment, _ra: usize) -> *mut u8 { // SAFETY: `ctx` is the `*const MimallocArena` stashed by // `std_allocator()`; the `StdAllocator` borrow it was built from is - // still live (Zig contract: an `Allocator` does not outlive its backing). + // still live (contract: an `Allocator` does not outlive its backing). let arena = unsafe { &*ctx.cast::() }; arena.aligned_alloc(len, a.to_byte_units()) } @@ -746,7 +735,7 @@ unsafe fn vtable_free(_ctx: *mut c_void, buf: &mut [u8], a: crate::Alignment, _r unsafe { crate::basic::mi_free_checked(buf.as_mut_ptr().cast(), buf.len(), a.to_byte_units()) } } -/// Zig: `heap_allocator_vtable` — per-arena thunks; `ctx` is the +/// Per-arena thunks; `ctx` is the /// `*const MimallocArena` stashed by `std_allocator()`. pub(crate) static HEAP_ALLOCATOR_VTABLE: crate::AllocatorVTable = crate::AllocatorVTable { alloc: vtable_alloc, @@ -755,7 +744,7 @@ pub(crate) static HEAP_ALLOCATOR_VTABLE: crate::AllocatorVTable = crate::Allocat free: vtable_free, }; -// ── Global-mimalloc vtable (Zig: `global_mimalloc_vtable`) ─────────────── +// ── Global-mimalloc vtable ──────────────────────────────────────────────── // Process-wide `mi_malloc`/`mi_free` — no heap ctx. Used by // `get_thread_local_default()` / `Default::allocator()`. @@ -768,7 +757,6 @@ unsafe fn global_vtable_alloc( crate::default_alloc::malloc_aligned(len, a.to_byte_units()).cast() } -/// Zig: `global_mimalloc_vtable`. pub(crate) static GLOBAL_MIMALLOC_VTABLE: crate::AllocatorVTable = crate::AllocatorVTable { alloc: global_vtable_alloc, resize: crate::basic::MimallocAllocator::resize_with_default_allocator, @@ -914,5 +902,3 @@ impl<'a, T> ArenaVecExt<'a, T> for crate::BabyVec<'a, T> { *self.allocator() } } - -// ported from: src/bun_alloc/MimallocArena.zig diff --git a/src/bun_alloc/NullableAllocator.rs b/src/bun_alloc/NullableAllocator.rs index f3ee890f10c..7c3ab364ec7 100644 --- a/src/bun_alloc/NullableAllocator.rs +++ b/src/bun_alloc/NullableAllocator.rs @@ -1,13 +1,11 @@ -//! A nullable allocator the same size as `std.mem.Allocator`. +//! A nullable allocator the same size as `StdAllocator`. use core::ffi::c_void; use crate::{Alignment, AllocatorVTable, StdAllocator}; -/// PORT NOTE: Zig stored `{ ptr: *anyopaque, vtable: ?*const VTable }` and -/// recovered the `Allocator` by null-checking the vtable. Rust models the same -/// thing directly — `vtable: Option<&'static AllocatorVTable>` carries the -/// niche, so the struct is identical in size to `StdAllocator`. +/// `vtable: Option<&'static AllocatorVTable>` carries the niche, +/// so the struct is identical in size to `StdAllocator`. #[derive(Clone, Copy)] pub struct NullableAllocator { ptr: *mut c_void, @@ -84,9 +82,8 @@ impl NullableAllocator { pub fn free(&self, bytes: &[u8]) { if let Some(allocator) = self.get() { if crate::String::is_wtf_allocator(allocator) { - // avoid calling `std.mem.Allocator.free` as it sets the memory to undefined // SAFETY: `bytes` is reborrowed mutably only for the vtable signature; the - // WTF deallocator treats it as opaque (Zig passes `[]u8`). + // WTF deallocator treats it as opaque and never writes through it. let buf = unsafe { core::slice::from_raw_parts_mut(bytes.as_ptr().cast_mut(), bytes.len()) }; @@ -103,5 +100,3 @@ const _: () = assert!( core::mem::size_of::() == core::mem::size_of::(), "Expected the sizes to be the same." ); - -// ported from: src/bun_alloc/NullableAllocator.zig diff --git a/src/bun_alloc/ast_alloc.rs b/src/bun_alloc/ast_alloc.rs index dc3e38bca46..894c5913993 100644 --- a/src/bun_alloc/ast_alloc.rs +++ b/src/bun_alloc/ast_alloc.rs @@ -2,7 +2,7 @@ //! //! Strategy B for the require-cache ESM leak (docs/BABYLIST_REPLACEMENT.md): //! `G::DeclList` / `G::PropertyList` / `ExprNodeList` / `ClassStaticBlock::stmts` -//! were ported from Zig `BabyList` to global-heap `Vec`. The AST *nodes* +//! use global-heap `Vec`. The AST *nodes* //! that embed those `Vec` headers live in `ASTMemoryAllocator`'s `MimallocArena` //! and are bulk-freed (no `Drop`) on `enter()` → `arena.reset()`, so the global //! buffers leak — one full AST's worth of `Vec` backing storage per imported @@ -36,8 +36,8 @@ use core::ptr::NonNull; use crate::{MimallocArena, mimalloc}; // The parser builds thousands of tiny `AstVec`s; allocations `<= BUMP_MAX` are -// carved from a 16 KB buffer stored inline in the state (mirroring Zig's -// `StackFallbackAllocator`), so the small case never touches mimalloc. The +// carved from a 16 KB buffer stored inline in the state, so the small case +// never touches mimalloc. The // cursor, the buffer, and the spill target live in one struct, so none of them // can outlive the others. @@ -388,8 +388,8 @@ fn heap_alloc(layout: Layout) -> *mut u8 { // the "pointers may be freed by any clone" requirement is satisfied. // - `Send + Sync` (auto-derived for a fieldless ZST) is sound: each call reads // the *calling* thread's `AST_ALLOC`, and allocation is gated to that thread -// by `ASTMemoryAllocator`'s single-threaded contract (mirrored from Zig's -// `ThreadLock`; see `MimallocArena::assert_owning_thread`). The no-op +// by `ASTMemoryAllocator`'s single-threaded contract (see +// `MimallocArena::assert_owning_thread`). The no-op // `deallocate` removes the only cross-thread hazard a `Vec<_,A>: Send` would // otherwise introduce. unsafe impl Allocator for AstAlloc { @@ -439,8 +439,7 @@ unsafe impl Allocator for AstAlloc { // bookkeeping) *without* moving the block — so it stays in whatever heap // owns it and never thrashes the `heap → theap` TLS lookup. When it // succeeds there is no allocation, no `memcpy`, and no abandoned block, - // matching `MimallocArena`'s `resize_in_place` (Zig's arena `remap` is - // `mi_expand`-then-`mi_realloc`). + // matching `MimallocArena`'s `resize_in_place`. // // Gated on: // - `old.size() > BUMP_MAX`: smaller blocks may be bump-chunk interior @@ -512,7 +511,7 @@ impl AstAlloc { Vec::with_capacity_in(cap, AstAlloc) } - /// `<[T]>::to_vec` parity (Zig: `BabyList.fromSlice`). + /// `<[T]>::to_vec` parity. #[inline] pub fn vec_from_slice(items: &[T]) -> AstVec { let mut v = Vec::with_capacity_in(items.len(), AstAlloc); diff --git a/src/bun_alloc/baby_vec.rs b/src/bun_alloc/baby_vec.rs index 1938e6a824e..4cb2acc0208 100644 --- a/src/bun_alloc/baby_vec.rs +++ b/src/bun_alloc/baby_vec.rs @@ -1,10 +1,8 @@ //! `BabyVec<'a, T>` — arena-backed growable array with `u32` length/capacity. //! -//! Port target: `BabyList(T)` (collections/baby_list.zig) = -//! `(ptr: [*]T, len: u32, cap: u32)` = 16 B. The Rust port stores the owning -//! `&'a MimallocArena` inline (lifetime-checked allocator vs Zig passing the -//! allocator at every `append(allocator, ..)` call site), so 24 B instead of -//! 16. Still 8 B smaller than `Vec` (32 B), which +//! `(ptr: [*]T, len: u32, cap: u32)` plus the owning +//! `&'a MimallocArena` stored inline (lifetime-checked allocator), so 24 B. +//! Still 8 B smaller than `Vec` (32 B), which //! matters for AST node lists embedded in `Part` / `BundledAst` columns. //! //! `len`/`cap` are stored as `u32` (`usize` on the public API for ergonomics). @@ -304,7 +302,7 @@ impl<'a, T> BabyVec<'a, T> { /// Drain all elements. Only the full range is supported — the `RangeBounds` /// parameter exists for drop-in `ArenaVec` alias parity with `Vec::drain(..)`. - /// Zig `BabyList` has no partial drain and no caller needs one. + /// No caller needs a partial drain. pub fn drain>(&mut self, range: R) -> IntoIter<'a, T> { use core::ops::Bound::*; // Const-folded for `..`; guards release builds against partial ranges. diff --git a/src/bun_alloc/basic.rs b/src/bun_alloc/basic.rs index 17f072e7f93..12284df6fb8 100644 --- a/src/bun_alloc/basic.rs +++ b/src/bun_alloc/basic.rs @@ -4,10 +4,6 @@ use crate::{default_alloc, mimalloc}; // TODO(refactor): consider reshaping the vtable struct into `trait Allocator` impls. use crate::{Alignment, AllocatorVTable, StdAllocator}; -// Zig: `const log = bun.Output.scoped(.mimalloc, .hidden);` — `Output.scoped` -// lives in `bun_core`, which depends on this crate, so the hidden-scope debug -// tracing is dropped here rather than re-declared as a no-op stub. - /// # Safety /// `ptr` must have been allocated by mimalloc with the given `size`/`align`. #[inline(always)] @@ -204,5 +200,3 @@ pub unsafe fn free_without_size(ptr: *mut c_void) { // SAFETY: caller contract — ptr is null or was allocated by mimalloc; mi_free accepts null unsafe { mimalloc::mi_free(ptr) } } - -// ported from: src/bun_alloc/basic.zig diff --git a/src/bun_alloc/fallback.rs b/src/bun_alloc/fallback.rs index 43fdd0e6af4..7322bd350e0 100644 --- a/src/bun_alloc/fallback.rs +++ b/src/bun_alloc/fallback.rs @@ -4,9 +4,8 @@ use crate::Alignment; pub mod z; -/// `std.heap.c_allocator` — a `std.mem.Allocator` value backed by libc -/// malloc/free. Exposed as a ZST with inherent `raw_*` methods mirroring the -/// Zig vtable so the zeroing wrapper in `z.rs` can layer on top. +/// An allocator backed by libc malloc/free. Exposed as a ZST with inherent +/// `raw_*` methods so the zeroing wrapper in `z.rs` can layer on top. #[derive(Clone, Copy, Default)] pub struct CAllocator; @@ -16,7 +15,7 @@ impl CAllocator { #[inline] pub fn raw_alloc(&self, len: usize, alignment: Alignment, _ret_addr: usize) -> Option<*mut u8> { // libc malloc guarantees alignment to `max_align_t`; for larger - // alignments use the aligned variant (Zig's `CAllocator` does the same). + // alignments use the aligned variant. let align = alignment.to_byte_units(); // SAFETY: libc malloc/aligned_alloc are sound for any nonzero size. let ptr = unsafe { @@ -48,8 +47,8 @@ impl CAllocator { new_len: usize, _ret_addr: usize, ) -> bool { - // Zig `CAllocator.resize`: in-place only — succeed on shrink or if the - // backing allocation already has enough usable size; never relocate. + // In-place only — succeed on shrink or if the backing allocation + // already has enough usable size; never relocate. if new_len <= buf.len() { return true; } @@ -67,8 +66,7 @@ impl CAllocator { } #[cfg(windows)] { - // Zig's `std.heap.CAllocator` probes `_msize` on Windows. Our - // over-aligned path uses `_aligned_malloc`, and MSDN forbids + // Our over-aligned path uses `_aligned_malloc`, and MSDN forbids // `_msize` on those blocks — must use `_aligned_msize` instead. unsafe extern "C" { fn _msize(p: *mut c_void) -> usize; @@ -127,8 +125,6 @@ pub use z::ALLOCATOR as z_allocator; /// and must go through [`CAllocator::raw_free`] so `_aligned_free` is used. pub unsafe fn free_without_size(ptr: *mut c_void) { // SAFETY: caller contract — ptr is null or a plain malloc-family allocation; - // libc free accepts null. Same precondition as Zig `std.c.free`. + // libc free accepts null. unsafe { libc::free(ptr) } } - -// ported from: src/bun_alloc/fallback.zig diff --git a/src/bun_alloc/fallback/z.rs b/src/bun_alloc/fallback/z.rs index 5fae0a21981..7d3f185db88 100644 --- a/src/bun_alloc/fallback/z.rs +++ b/src/bun_alloc/fallback/z.rs @@ -2,28 +2,25 @@ use core::ffi::c_void; use core::ptr; use crate::{Alignment, Allocator}; -// `std.heap.c_allocator` — the libc-malloc-backed allocator. +// The libc-malloc-backed allocator. use super::C_ALLOCATOR as c_allocator; /// A fallback zero-initializing allocator. // -// Zig: `pub const allocator = Allocator{ .ptr = undefined, .vtable = &vtable };` -// `std.mem.Allocator` is a `{ptr, vtable}` fat struct — the Rust mapping is -// `&dyn bun_alloc::Allocator`, so the public export is a ZST implementing the -// trait. Consumers borrow `&ALLOCATOR` (coerces to `&dyn Allocator`). +// The public export is a ZST implementing the `Allocator` trait. Consumers +// borrow `&ALLOCATOR` (coerces to `&dyn Allocator`). pub static ALLOCATOR: Z = Z; #[derive(Clone, Copy, Default)] pub struct Z; -// `Allocator` is a marker trait carrying `type_id()`; the Zig vtable methods +// `Allocator` is a marker trait carrying `type_id()`; the allocation methods // are inherent on `Z` below. impl Allocator for Z {} -// Zig: `const vtable = Allocator.VTable{ .alloc, .resize, .remap = noRemap, .free }` impl Z { pub fn alloc( - &self, // Zig: `_: *anyopaque` (unused vtable ctx) + &self, len: usize, alignment: Alignment, return_address: usize, @@ -35,7 +32,7 @@ impl Z { } pub fn resize( - &self, // Zig: `_: *anyopaque` + &self, buf: &mut [u8], alignment: Alignment, new_len: usize, @@ -44,12 +41,9 @@ impl Z { if !c_allocator.raw_resize(buf, alignment, new_len, return_address) { return false; } - // PORT NOTE: reshaped for borrowck — capture len before re-deriving the - // tail pointer (Zig: `buf.ptr[buf.len..new_len]`). let old_len = buf.len(); // Only zero on grow. On shrink (`new_len < old_len`), `new_len - old_len` // would underflow to ~usize::MAX and `write_bytes` would corrupt the heap. - // Zig's slice safety check panics in Debug here; preserve that guard. if new_len > old_len { // SAFETY: `raw_resize` succeeded in-place, so `buf.ptr[old_len..new_len]` // is now valid uninitialized memory owned by this allocation. @@ -69,18 +63,10 @@ impl Z { None } - pub fn free( - self, // Zig: `_: *anyopaque` - buf: &mut [u8], - alignment: Alignment, - return_address: usize, - ) { + pub fn free(self, buf: &mut [u8], alignment: Alignment, return_address: usize) { c_allocator.raw_free(buf, alignment, return_address); } } -// `*anyopaque` in the Zig vtable signatures maps to `*mut c_void`, but since the -// ctx pointer is unused (`.ptr = undefined`) it collapses to `&self` on a ZST. +// Keeps the `c_void`/`ptr` imports referenced from every cfg combination. const _: fn() -> *mut c_void = || ptr::null_mut(); - -// ported from: src/bun_alloc/fallback/z.zig diff --git a/src/bun_alloc/hashbrown_bridge.rs b/src/bun_alloc/hashbrown_bridge.rs index 1694b2f7328..9bef3be0933 100644 --- a/src/bun_alloc/hashbrown_bridge.rs +++ b/src/bun_alloc/hashbrown_bridge.rs @@ -65,8 +65,8 @@ macro_rules! bridge_allocator_api2 { }; } -// `crate::DefaultAlloc` is the existing ZST marker for `bun.default_allocator` -// (Zig); here it gains `core::alloc::Allocator` (forwarding to +// `crate::DefaultAlloc` is the existing ZST marker for `bun.default_allocator`; +// here it gains `core::alloc::Allocator` (forwarding to // `std::alloc::Global`, since `#[global_allocator] = Mimalloc` makes that the // process default) plus the api2 bridge, so a single `A` type can back both a // `hashbrown::HashMap<_, _, _, A>` table and its `Box<[u8], A>` keys. diff --git a/src/bun_alloc/heap_breakdown.rs b/src/bun_alloc/heap_breakdown.rs index d04b2709245..4ad0fa97cbf 100644 --- a/src/bun_alloc/heap_breakdown.rs +++ b/src/bun_alloc/heap_breakdown.rs @@ -7,21 +7,16 @@ use core::ffi::{c_int, c_uint}; type vm_size_t = usize; // Environment.allow_assert and Environment.isMac and !Environment.enable_asan -// TODO(port): `enable_asan` mapped to a cargo feature; verify the build wires this the same way. +// (`bun_asan` is set via RUSTFLAGS `--cfg=bun_asan` in scripts/build/rust.ts.) pub const ENABLED: bool = cfg!(debug_assertions) && cfg!(target_os = "macos") && !cfg!(bun_asan); -/// Zig: `pub fn getZone(comptime name: [:0]const u8) *Zone` -/// -/// Each comptime instantiation in Zig gets its own `static var zone` + `std.once`. -/// The faithful Rust translation is the crate-root `get_zone!` macro in lib.rs -/// that expands a fresh `OnceLock` per call site (per literal name). Not +/// The crate-root `get_zone!` macro in lib.rs +/// expands a fresh `OnceLock` per call site (per literal name). Not /// duplicated here to avoid path-export collisions on macOS. /// Runtime `getZone(name)` — looks up (or creates) the per-name zone. The /// `get_zone!` macro is the zero-cost form. This runtime path keys a /// process-global map for callers that pass a non-literal name. -// TODO(port): could be replaced with a `#[heap_label]` derive that expands -// `get_zone!` directly. #[allow(clippy::assertions_on_constants)] pub fn get_zone(name: &[u8]) -> &'static Zone { debug_assert!( @@ -46,7 +41,7 @@ pub fn get_zone(name: &[u8]) -> &'static Zone { if let Some((_, _, z)) = zones.iter().find(|(k, _, _)| k.as_slice() == name) { return *z; } - // `name` verbatim (no prefix — matches Zig `getZone`), NUL-terminated. + // `name` verbatim (no prefix), NUL-terminated. let mut owned = Vec::with_capacity(name.len() + 1); owned.extend_from_slice(name); owned.push(0); @@ -62,29 +57,25 @@ pub fn get_zone(name: &[u8]) -> &'static Zone { } bun_opaque::opaque_ffi! { - /// Zig: `pub const Zone = opaque { ... };` - /// /// Opaque FFI handle for a macOS `malloc_zone_t`. /// /// The `UnsafeCell` field makes `Zone: !Freeze`, so a `&Zone` does not assert /// immutability of the pointee. This is required because every malloc-zone FFI /// call (`malloc_zone_memalign`, `malloc_zone_free`, …) mutates the zone's - /// internal state, and Zig models the handle as a freely-aliasing `*Zone`. + /// internal state. /// Without `UnsafeCell`, casting `&Zone as *const _ as *mut _` and writing /// through it (via FFI) is UB under Stacked Borrows. pub struct Zone; } // SAFETY: `malloc_zone_t` is internally synchronized by libmalloc; sharing -// `&Zone` across threads is the documented usage (matches Zig `*Zone` via `std.once`). +// `&Zone` across threads is the documented usage. unsafe impl Sync for Zone {} // SAFETY: `Zone` is an opaque libmalloc handle with no thread-affine state; the // zone API is callable from any thread, so transferring the handle is sound. unsafe impl Send for Zone {} impl Zone { - /// Zig: `pub fn init(comptime name: [:0]const u8) *Zone` - /// /// # Safety /// `name` must point to a NUL-terminated C string that remains valid for /// the entire process lifetime — `malloc_set_zone_name` stores the pointer @@ -149,16 +140,6 @@ impl Zone { Zone::aligned_alloc(unsafe { &*(zone.cast::()) }, len, alignment) } - // Zig exposed a `pub const vtable: std.mem.Allocator.VTable` with - // { alloc, resize, remap = noRemap, free }. In Rust the equivalent is an - // `impl crate::Allocator for Zone` (see below); the raw vtable struct is a - // Zig-ism and is not materialized here, so the `resize`/`free` vtable thunks - // (and the `malloc_size` helper they used) are not ported. - // TODO(port): if `bun_alloc::Allocator` ever becomes a literal vtable struct - // (to match `std.mem.Allocator` ABI), reintroduce a `pub static VTABLE` - // along with the `resize`/`raw_free` thunks. - - /// Zig: `pub fn allocator(zone: *Zone) std.mem.Allocator` pub fn allocator(&'static self) -> &'static dyn crate::Allocator { self } @@ -174,12 +155,11 @@ impl Zone { #[inline] pub fn try_create(&self, data: T) -> Result<*mut T, crate::AllocError> { let alignment = core::mem::align_of::(); - // TODO(port): Zig passed `@returnAddress()` as the ret_addr hint; Rust has no - // stable equivalent. Passing 0 — the macOS zone API ignores it anyway. + // Pass 0 as the ret_addr hint — the macOS zone API ignores it anyway. let raw = Zone::raw_alloc( // SAFETY: vtable context pointer — `as_mut_ptr()` yields the // interior-mutable `*mut Zone`, erased to `*mut c_void` to match - // the Zig `*anyopaque` allocator-vtable signature. + // the allocator-vtable signature. self.as_mut_ptr().cast::(), core::mem::size_of::(), alignment, @@ -201,23 +181,21 @@ impl Zone { unsafe { malloc_zone_free(self.as_mut_ptr(), ptr.cast()) }; } - /// Zig: `pub fn isInstance(allocator_: std.mem.Allocator) bool` - /// - /// Zig: `return allocator_.vtable == &vtable;` — implemented as a `TypeId` + /// Implemented as a `TypeId` /// identity check via the `Allocator::type_id()` hook. pub fn is_instance(allocator_: &dyn crate::Allocator) -> bool { allocator_.is::() } } -// `crate::Allocator` is a marker trait carrying `type_id()`; the Zig vtable +// `crate::Allocator` is a marker trait carrying `type_id()`; the allocation // methods (`alloc`/`resize`/`free`) are inherent on `Zone` above (`raw_alloc`, // `resize`, `raw_free`). This impl makes `Zone` usable as `&dyn Allocator` // for `is_instance` identity checks. impl crate::Allocator for Zone {} -// TODO(port): move to bun_alloc_sys (or keep here gated `#[cfg(target_os = "macos")]` -// since these are macOS-only libc symbols). +// macOS-only libmalloc symbols, kept here (gated on `target_os = "macos"`) +// since heap_breakdown is their only consumer. #[cfg(target_os = "macos")] unsafe extern "C" { /// No preconditions; returns the process default malloc zone. @@ -286,5 +264,3 @@ mod stubs { use stubs::malloc_zone_memalign; #[cfg(not(target_os = "macos"))] pub use stubs::{malloc_zone_calloc, malloc_zone_free, malloc_zone_malloc}; - -// ported from: src/bun_alloc/heap_breakdown.zig diff --git a/src/bun_alloc/lib.rs b/src/bun_alloc/lib.rs index 4227c90a4b3..8d09e42c23c 100644 --- a/src/bun_alloc/lib.rs +++ b/src/bun_alloc/lib.rs @@ -1,4 +1,3 @@ -//! Port of `src/bun_alloc/bun_alloc.zig`. // bun_alloc is the T0 foundation crate that bun_threading and bun_collections // depend on; importing either to satisfy the disallowed-types lint would create // a dependency cycle. @@ -8,8 +7,7 @@ // `#[thread_local]` (vs the `thread_local!` macro) compiles to a bare // `__thread` slot — single `mov reg, fs:[OFFSET]` access, no `LocalKey` // `__getit()` wrapper, no lazy-init flag check, no dtor-registration probe. -// Used for the per-allocation hot-path TLS in `ast_alloc::AST_ALLOC`; matches -// Zig's `threadlocal var` semantics exactly. +// Used for the per-allocation hot-path TLS in `ast_alloc::AST_ALLOC`. #![feature(thread_local)] use core::fmt::Write as _; @@ -19,16 +17,16 @@ use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; use std::collections::HashMap; // ────────────────────────────────────────────────────────────────────────── -// Re-exports (thin — match Zig `pub const X = @import(...)` lines) +// Re-exports // ────────────────────────────────────────────────────────────────────────── pub use bun_mimalloc_sys::mimalloc; pub mod c_thunks; -// ── Allocator vtable (mirrors std.mem.Allocator) ────────────────────────── +// ── Allocator vtable ─────────────────────────────────────────────────────── #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct Alignment(pub u8); // log2 of byte alignment, like std.mem.Alignment +pub struct Alignment(pub u8); // log2 of byte alignment impl Alignment { #[inline] pub const fn of() -> Self { @@ -44,10 +42,10 @@ impl Alignment { } } -// ── `std.c.max_align_t` alignment ───────────────────────────────────────── +// ── `max_align_t` alignment ──────────────────────────────────────────────── // The `libc` crate does not expose `max_align_t` on every target Bun ships // (missing on Windows MSVC and on FreeBSD aarch64), so those targets carry a -// local mirror of the Zig definition. Remaining non-Windows targets keep +// local mirror of `max_align_t`. Remaining non-Windows targets keep // `libc::max_align_t` (which carries `long double`, align 16 on x86_64/aarch64; // the {f64,i64,*const ()} fallback would silently downgrade to 8). #[cfg(windows)] @@ -59,7 +57,7 @@ struct MaxAlignT { } #[cfg(windows)] pub const MAX_ALIGN_T: usize = core::mem::align_of::(); -// Zig: `extern struct { a: c_longlong, b: c_longdouble }` — on AArch64 +// On AArch64 // AAPCS64 `long double` is IEEE binary128, 16-byte aligned. The `libc` crate // only defines `max_align_t` for FreeBSD on x86_64, so hardcode the ABI value // for the aarch64 port. @@ -77,9 +75,7 @@ pub struct AllocatorVTable { impl AllocatorVTable { /// `alloc` impl that always fails. For vtables that only ever `free` an /// externally-produced buffer (mmap region, plugin-owned memory, refcounted - /// foreign string) and never allocate or grow it. Zig has no `std.mem. - /// Allocator.noAlloc`; every Zig site hand-rolls `fn alloc(...) ?[*]u8 { - /// return null; }`. This is the Rust-side improvement. + /// foreign string) and never allocate or grow it. pub const NO_ALLOC: unsafe fn(*mut core::ffi::c_void, usize, Alignment, usize) -> *mut u8 = |_, _, _, _| core::ptr::null_mut(); pub const NO_RESIZE: unsafe fn( @@ -112,7 +108,7 @@ impl AllocatorVTable { } } -/// `std.mem.Allocator` — fat (ptr + vtable). Distinct from the `Allocator` trait below. +/// Fat allocator handle (ptr + vtable). Distinct from the `Allocator` trait below. #[derive(Clone, Copy)] pub struct StdAllocator { pub ptr: *mut core::ffi::c_void, @@ -121,16 +117,15 @@ pub struct StdAllocator { /// Legacy alias for `AllocatorVTable`. pub type VTable = AllocatorVTable; -// SAFETY: `ptr` is an opaque tag/context handle (Zig: `*anyopaque`); the -// vtable is `&'static`. Thread-safety of dispatch is the implementor's -// concern (mimalloc is thread-safe; FixedBufferAllocator is not — same as Zig). +// SAFETY: `ptr` is an opaque tag/context handle; the vtable is `&'static`. +// Thread-safety of dispatch is the implementor's concern (mimalloc is +// thread-safe; FixedBufferAllocator is not). unsafe impl Send for StdAllocator {} // SAFETY: see the `Send` impl directly above. unsafe impl Sync for StdAllocator {} impl Default for StdAllocator { - /// Zig: `bun.memory.initDefault(std.mem.Allocator)` → `bun.default_allocator` - /// (mimalloc-backed `c_allocator`). + /// The mimalloc-backed `c_allocator`. #[inline] fn default() -> Self { basic::C_ALLOCATOR @@ -138,14 +133,12 @@ impl Default for StdAllocator { } impl StdAllocator { - /// Zig: `Allocator.rawAlloc`. #[inline] pub fn raw_alloc(&self, len: usize, alignment: Alignment, ra: usize) -> Option<*mut u8> { // SAFETY: vtable invariant — `alloc` callee respects (ptr, len, alignment, ra) contract. let p = unsafe { (self.vtable.alloc)(self.ptr, len, alignment, ra) }; if p.is_null() { None } else { Some(p) } } - /// Zig: `Allocator.rawResize`. #[inline] pub fn raw_resize( &self, @@ -157,7 +150,6 @@ impl StdAllocator { // SAFETY: see `raw_alloc`. unsafe { (self.vtable.resize)(self.ptr, buf, alignment, new_len, ra) } } - /// Zig: `Allocator.rawRemap`. #[inline] pub fn raw_remap( &self, @@ -170,27 +162,26 @@ impl StdAllocator { let p = unsafe { (self.vtable.remap)(self.ptr, buf, alignment, new_len, ra) }; if p.is_null() { None } else { Some(p) } } - /// Zig: `Allocator.rawFree`. #[inline] pub fn raw_free(&self, buf: &mut [u8], alignment: Alignment, ra: usize) { // SAFETY: see `raw_alloc`. unsafe { (self.vtable.free)(self.ptr, buf, alignment, ra) } } - /// Zig: `Allocator.free` — `rawFree` with `ret_addr = 0`, byte-aligned. + /// `raw_free` with `ret_addr = 0`, byte-aligned. #[inline] pub fn free(&self, bytes: &[u8]) { if bytes.is_empty() { return; } // SAFETY: `bytes` is reborrowed mutably only for the vtable signature; the - // callee treats it as opaque (Zig passes `[]u8`). + // callee treats it as opaque. let buf = unsafe { core::slice::from_raw_parts_mut(bytes.as_ptr().cast_mut(), bytes.len()) }; self.raw_free(buf, Alignment::from_byte_units(1), 0); } } -/// `std.heap.FixedBufferAllocator` — bump allocator over a caller-owned buffer. +/// Bump allocator over a caller-owned buffer. pub struct FixedBufferAllocator<'a> { end: usize, buffer: &'a mut [u8], @@ -259,9 +250,9 @@ impl<'a> FixedBufferAllocator<'a> { } // PORTING.md §Allocators: AST crates thread an `Arena`; non-AST use Vec/Box -// (global mimalloc). `Arena` is now the real per-heap `MimallocArena` (matching -// Zig's `bun.allocators.MimallocArena`) — unlike `bumpalo::Bump`, it supports -// per-allocation free + realloc, so `ArenaVec` no longer leaks on grow. +// (global mimalloc). `Arena` is the real per-heap `MimallocArena` — unlike +// `bumpalo::Bump`, it supports per-allocation free + realloc, so `ArenaVec` +// no longer leaks on grow. // // `bumpalo::Bump` is kept as `Bump` for genuinely bump-only scratch (parser // node stores that are never resized and where the no-op `deallocate` is the @@ -272,8 +263,8 @@ pub type Arena = MimallocArena; pub type Bump = bumpalo::Bump; mod baby_vec; pub use baby_vec::BabyVec; -/// Arena-backed `Vec` with `u32` length/capacity — port of Zig's -/// `BabyList(T)`. 24 B (vs 32 B for `Vec`); the +/// Arena-backed `Vec` with `u32` length/capacity. +/// 24 B (vs 32 B for `Vec`); the /// allocator handle is kept inline for lifetime checking. Growth/free route /// through `<&MimallocArena as Allocator>` (= `mi_heap_realloc_aligned` / /// `mi_free`); reclaimed on arena `reset`/`Drop`. @@ -295,11 +286,6 @@ where /// Re-tag an [`ArenaVec`]'s allocator handle to `dst` without copying data. /// -/// Zig parity: `BabyList.transferOwnership` (collections/baby_list.zig). Zig's -/// `BabyList` is allocator-erased — the linker passes a different allocator at -/// each `append(allocator, ..)` call site; the Rust port stores `&'a Arena` in -/// the `Vec`, so the equivalent is swapping that field. -/// /// Sound because `<&MimallocArena as Allocator>` is heap-agnostic on the /// existing buffer: /// - `deallocate` → `mi_free(ptr)`: looks up the owning heap from the pointer's @@ -332,16 +318,11 @@ pub const USE_MIMALLOC: bool = cfg!(not(bun_asan)); // ── Allocator-vtable modules: per-module disposition (PORTING.md §Allocators) ── // -// These modelled Zig's `std.mem.Allocator` vtable. With `#[global_allocator]` -// + `Arena = bumpalo::Bump`, most callers should drop the allocator param -// PORTING.md §Forbidden) so the .zig↔.rs diff pass has a real body to compare; -// callers are migrated incrementally. -// // MimallocArena → prefer `bun_alloc::Arena` (= bumpalo::Bump) // NullableAllocator → prefer `Option<&Arena>` or drop the param // MaxHeapAllocator → debug-only cap (single-allocation arena) // BufferFallbackAllocator → PORTING.md "StackFallbackAllocator → just use the heap" -// fallback → libc-malloc + zeroing wrapper (Zig std.heap.c_allocator) +// fallback → libc-malloc + zeroing wrapper // maybe_owned → prefer `std::borrow::Cow` / `bun_ptr::Owned` // heap_breakdown → macOS malloc_zone_* per-tag heaps (debug builds) // basic → `impl GlobalAlloc for Mimalloc` above is the canonical impl @@ -542,15 +523,14 @@ pub use allocator_api2::alloc::Allocator as HashbrownAllocator; // canonical tier-0 definitions, re-exported by higher tiers (`bun_paths::SEP_STR`, // `bun_core::strings::trim_right`, `bun_core::strings::trim_right`). -/// Zig: `std.fs.path.sep_str` — `"\\"` on Windows, `"/"` elsewhere. +/// `"\\"` on Windows, `"/"` elsewhere. /// Canonical tier-0 definition; re-exported by `bun_paths::SEP_STR`. pub const SEP_STR: &str = if cfg!(windows) { "\\" } else { "/" }; -/// Zig: `std.fs.path.sep` — `b'\\'` on Windows, `b'/'` elsewhere. +/// `b'\\'` on Windows, `b'/'` elsewhere. /// Canonical tier-0 definition; re-exported by `bun_paths::SEP` / `bun_core::SEP`. pub const SEP: u8 = if cfg!(windows) { b'\\' } else { b'/' }; -/// Zig: `std.mem.trimRight(u8, s, chars)`. /// Canonical tier-0 definition; re-exported by `bun_core::strings::trim_right`. #[inline] pub fn trim_right<'a>(s: &'a [u8], chars: &[u8]) -> &'a [u8] { @@ -561,7 +541,6 @@ pub fn trim_right<'a>(s: &'a [u8], chars: &[u8]) -> &'a [u8] { &s[..end] } -/// Zig: `std.mem.trimLeft(u8, s, chars)`. /// Canonical tier-0 definition; re-exported by `bun_core::strings::trim_left`. #[inline] pub fn trim_left<'a>(s: &'a [u8], chars: &[u8]) -> &'a [u8] { @@ -572,7 +551,7 @@ pub fn trim_left<'a>(s: &'a [u8], chars: &[u8]) -> &'a [u8] { &s[begin..] } -/// Zig: `std.mem.trim(u8, s, chars)` — strip `chars` from both ends. +/// Strip `chars` from both ends. /// Canonical tier-0 definition; re-exported by `bun_core::strings::trim`. #[inline] pub fn trim<'a>(s: &'a [u8], chars: &[u8]) -> &'a [u8] { @@ -586,13 +565,13 @@ pub fn trim<'a>(s: &'a [u8], chars: &[u8]) -> &'a [u8] { // `bun_core::strings::copy_lowercase` / `bun_core::immutable::copy_lowercase` // keep compiling unchanged. -/// Zig: `strings.copyLowercase` (src/string/immutable.zig). ASCII-lowercase +/// ASCII-lowercase /// `in_` into `out` (which must be at least `in_.len()`), returning the /// written prefix. Memcpy-runs + per-uppercase-byte fixup; identical output /// to a byte-at-a-time `to_ascii_lowercase` zip. pub fn copy_lowercase<'a>(in_: &[u8], out: &'a mut [u8]) -> &'a [u8] { let mut in_slice = in_; - // PORT NOTE: reshaped for borrowck — track output offset instead of reslicing &mut. + // Reshaped for borrowck — track output offset instead of reslicing &mut. let mut out_off: usize = 0; 'begin: loop { @@ -614,7 +593,7 @@ pub fn copy_lowercase<'a>(in_: &[u8], out: &'a mut [u8]) -> &'a [u8] { &out[0..in_.len()] } -/// Zig: `strings.copyLowercaseIfNeeded` (src/string/immutable.zig:664). If +/// If /// `in_` contains no ASCII uppercase byte, returns `in_` unchanged and leaves /// `out` UNTOUCHED. Otherwise identical to [`copy_lowercase`]: writes the /// lowercased bytes into `out[..in_.len()]` and returns that prefix. Both @@ -654,7 +633,7 @@ pub(crate) fn alloc_result( .ok_or(core::alloc::AllocError) } -/// Port of `std.fmt.count`: number of bytes the formatted args would produce. +/// Number of bytes the formatted args would produce. /// /// Drives a discarding `fmt::Write` that only sums `s.len()` — no allocation, /// no UTF-8 validation beyond what the formatter already did. Lives here in @@ -671,8 +650,7 @@ pub fn fmt_count(args: core::fmt::Arguments<'_>) -> usize { } } let mut w = Discarding(0); - // Infallible: our `write_str` never errors, mirroring Zig's - // `error.WriteFailed => unreachable`. + // Infallible: our `write_str` never errors. let _ = core::fmt::write(&mut w, args); w.0 } @@ -680,10 +658,9 @@ pub fn fmt_count(args: core::fmt::Arguments<'_>) -> usize { /// `core::fmt::Write` adapter over a borrowed `&mut [u8]` — the engine behind /// [`buf_print`] / [`buf_print_len`] (and `bun_core::fmt::buf_print_z`). /// -/// This is the single port of Zig `std.fmt.bufPrint`'s internal cursor. It -/// lives at T0 so `bun_alloc` itself can use it (`BSSStringList::print`); T1 -/// `bun_core::fmt` re-exports it and adds an `io::Write` impl so the same -/// struct also serves as Zig's `std.io.fixedBufferStream` for write-only sites. +/// Lives at T0 so `bun_alloc` itself can use it (`BSSStringList::print`); T1 +/// `bun_core::fmt` re-exports it and adds an `io::Write` impl for write-only +/// sites. pub struct SliceCursor<'a> { pub buf: &'a mut [u8], pub at: usize, @@ -708,7 +685,7 @@ impl core::fmt::Write for SliceCursor<'_> { } } -/// Port of `std.fmt.bufPrint` — render into `buf`, return the written sub-slice. +/// Render the formatted args into `buf`, returning the written sub-slice. /// Fails (`fmt::Error`) when `buf` is too short. pub fn buf_print<'a>( buf: &'a mut [u8], @@ -720,7 +697,7 @@ pub fn buf_print<'a>( Ok(&c.buf[..len]) } -/// [`buf_print`] returning only the byte count — `std.fmt.bufPrint(..).len`. +/// [`buf_print`] returning only the byte count. #[inline] pub fn buf_print_len( buf: &mut [u8], @@ -732,8 +709,7 @@ pub fn buf_print_len( } // ── RAII Mutex ──────────────────────────────────────────────────────────── -// Zig's `bun.Mutex` exposes bare `lock()`/`unlock()` (no guard). The BSS -// containers below need to hold the lock across `&mut self` method calls, so +// The BSS containers below need to hold the lock across `&mut self` method calls, so // the returned [`MutexGuard`] deliberately erases its borrow of `self` — it // stores the `std::sync::MutexGuard` lifetime-extended to `'static` (lifetimes // are erased at codegen, so this is a layout no-op). This is sound because @@ -785,7 +761,6 @@ impl Default for Mutex { pub struct AllocError; impl AllocError { - /// Port of Zig `@errorName(error.OutOfMemory)`. #[inline] pub const fn name(self) -> &'static str { "OutOfMemory" @@ -794,7 +769,7 @@ impl AllocError { /// Stamp out `impl From for $t { → $t::OutOfMemory }` for one or /// more local error enums. Expansion is byte-identical to the hand-written -/// 3-line impls this replaces (PORTING.md: Zig `error{OutOfMemory,…}` sets). +/// 3-line impls this replaces. #[macro_export] macro_rules! oom_from_alloc { ($($t:ty),+ $(,)?) => { $( @@ -811,7 +786,7 @@ macro_rules! oom_from_alloc { /// `#[global_allocator] static ALLOC: bun_alloc::Mimalloc = bun_alloc::Mimalloc;` /// must be set at the binary root before any `Box`/`Rc`/`Arc`/`Vec` mapping is valid. /// -/// Mirrors `src/bun_alloc/basic.zig` `c_allocator` vtable, using mimalloc's +/// Uses mimalloc's /// `MI_MAX_ALIGN_SIZE` (16) fast-path: alignments ≤16 go through `mi_malloc`, /// larger through `mi_malloc_aligned`. `mi_free` handles both. pub struct Mimalloc; @@ -864,7 +839,7 @@ unsafe impl core::alloc::GlobalAlloc for Mimalloc { } } -/// `bun.default_allocator.realloc(slice, new_size)` — resize a mimalloc-owned +/// Resize a mimalloc-owned /// byte allocation in place when possible, returning the (possibly moved) slice. /// /// # Safety @@ -917,8 +892,6 @@ pub fn usable_size(ptr: *const u8) -> usize { // ────────────────────────────────────────────────────────────────────────── // ── out_of_memory ───────────────────────────────────────────────────────── -// Source: src/bun.zig `outOfMemory()` → `crash_handler.crashHandler(.out_of_memory, ..)`. -// // `bun_alloc` is T0 and cannot depend on `bun_crash_handler`, so the upward // call is routed through a link-time `extern "Rust"` symbol defined by // `bun_crash_handler`. Resolved at link time → the target lives in read-only @@ -947,7 +920,7 @@ pub fn out_of_memory() -> ! { } // ── page_size ───────────────────────────────────────────────────────────── -// Source: Zig `std.heap.pageSize()` (used by LinuxMemFdAllocator / standalone_graph). +// Used by LinuxMemFdAllocator / standalone_graph. // Cached via OnceLock per PORTING.md §Concurrency (was lazy-init in std). static PAGE_SIZE: std::sync::OnceLock = std::sync::OnceLock::new(); @@ -1000,7 +973,6 @@ pub fn page_size() -> usize { } // ── wtf (FastMalloc thread-cache release) ───────────────────────────────── -// Source: src/jsc/WTF.zig `releaseFastMallocFreeMemoryForThisThread`. // MOVE_DOWN from bun_jsc so bun_threading (T2) can call it without a T6 dep. pub mod wtf { unsafe extern "C" { @@ -1011,19 +983,17 @@ pub mod wtf { #[inline] pub fn release_fast_malloc_free_memory_for_this_thread() { - // Zig: jsc.markBinding(@src()) — debug-only binding marker, dropped at T0. WTF__releaseFastMallocFreeMemoryForThisThread() } } -// ── String (bun.String) — TYPE_ONLY landing ─────────────────────────────── -// Source: src/string/string.zig + src/jsc/ZigString.zig + src/string/wtf.zig. +// ── String — TYPE_ONLY landing ───────────────────────────────────────────── // Layout-only (#[repr(C)]) so T0/T1 crates can name the type; rich methods // (toJS, toUTF8, WTF refcounting) remain in bun_str via extension traits. // PORTING.md: "#[repr(C)] struct { tag: u8, value: StringValue } — NOT a Rust // enum (C++ mutates tag and value independently across FFI)." -/// Port of `bun.String.Tag`. +/// Discriminant for [`String`]'s representation. #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Tag { @@ -1034,7 +1004,7 @@ pub enum Tag { Empty = 4, } -// `ZigString` pointer-tag scheme (ZigString.zig:629) — single source of truth. +// `ZigString` pointer-tag scheme — single source of truth. // Flag bits live in the POINTER's high byte; untagging truncates to 53 bits. pub const ZS_STATIC_BIT: usize = 1usize << 60; pub const ZS_UTF8_BIT: usize = 1usize << 61; @@ -1042,7 +1012,7 @@ pub const ZS_GLOBAL_BIT: usize = 1usize << 62; pub const ZS_16BIT_BIT: usize = 1usize << 63; pub const ZS_UNTAG_MASK: usize = (1usize << 53) - 1; -/// Port of `jsc.ZigString` — extern struct `{ ptr: [*]const u8, len: usize }`. +/// FFI string slice — `{ ptr: *const u8, len: usize }`. /// /// **Canonical storage layout.** `bun_core::string::ZigString` is a /// `#[repr(transparent)]` newtype over this struct (so the FFI layout has ONE @@ -1148,7 +1118,7 @@ impl ZigString { ((self._unsafe_ptr_do_not_use as usize) | ZS_STATIC_BIT) as *const u8; } - /// Zig `untagged`: `@ptrFromInt(@as(u53, @truncate(@intFromPtr(ptr))))`. + /// Strip the flag bits — truncate to the low 53 bits. #[inline] pub fn untagged(ptr: *const u8) -> *const u8 { ((ptr as usize) & ZS_UNTAG_MASK) as *const u8 @@ -1160,13 +1130,12 @@ impl ZigString { if self.len == 0 { return &[]; } - // ZigString.zig:637 — only panics when `len > 0 and is16Bit()`. debug_assert!( !self.is_16bit(), "ZigString::slice() on UTF-16 string; use to_slice()" ); - // SAFETY: constructor stored a valid ptr/len; flag bits stripped. Zig - // caps at u32::MAX (ZigString.zig:642). + // SAFETY: constructor stored a valid ptr/len; flag bits stripped. + // Length is capped at `u32::MAX`. unsafe { core::slice::from_raw_parts( Self::untagged(self._unsafe_ptr_do_not_use), @@ -1181,7 +1150,6 @@ impl ZigString { if self.len == 0 { return &[]; } - // ZigString.zig:436 — only panics when `len > 0 and !is16Bit()`. debug_assert!(self.is_16bit()); // SAFETY: 16-bit-tagged constructor stored a 2-byte-aligned ptr valid // for `self.len` u16 units; flag bits stripped via `ZS_UNTAG_MASK` @@ -1285,8 +1253,8 @@ impl WTFStringImplStruct { /// /// Cross-language LTO does not inline the `Bun__WTFStringImpl__ref` C++ /// shim into Rust callers (2151 out-of-line `callq` sites in the release - /// binary vs 0 in the Zig build), so the one-instruction body is - /// reimplemented here. `Relaxed` matches WebKit's + /// binary), so the one-instruction body is reimplemented here. + /// `Relaxed` matches WebKit's /// `m_refCount.fetch_add(s_refCountIncrement, std::memory_order_relaxed)`. #[inline] pub fn r#ref(&self) { @@ -1404,7 +1372,7 @@ unsafe extern "C" { // `destroy` path crosses FFI. `*const` + `unsafe`: it frees the // allocation backing the pointer. pub fn Bun__WTFStringImpl__destroy(this: *const WTFStringImplStruct); - // Kept for Zig callers (`src/string/wtf.zig`); Rust no longer calls these. + // Rust no longer calls these. pub safe fn Bun__WTFStringImpl__ref(this: &WTFStringImplStruct); pub fn Bun__WTFStringImpl__deref(this: *const WTFStringImplStruct); safe fn WTFStringImpl__isThreadSafe(this: &WTFStringImplStruct) -> bool; @@ -1416,14 +1384,12 @@ unsafe extern "C" { ) -> bool; } -/// Port of `bun.String.StringImplAllocator` (src/string/wtf.zig). -/// -/// A `std.mem.Allocator` vtable whose `ptr` is a `WTFStringImpl`; `alloc` bumps +/// An [`AllocatorVTable`] whose ctx `ptr` is a `WTFStringImpl`; `alloc` bumps /// the refcount, `free` derefs. Hoisted into `bun_alloc` (which already owns /// `AllocatorVTable` and the `WTFStringImplStruct` layout) so the /// `is_wtf_allocator` vtable-identity check is a local pointer compare — no /// upward dependency on `bun_string` and no runtime fn-ptr hook. -#[allow(non_snake_case)] // Zig namespace `bun.String.StringImplAllocator` +#[allow(non_snake_case)] pub mod StringImplAllocator { use super::{Alignment, AllocatorVTable, WTFStringImplStruct}; @@ -1449,8 +1415,8 @@ pub mod StringImplAllocator { // ctx pointer; `byte_slice`/`byte_length`/`deref` are safe `&self` methods. let this = unsafe { &*ptr.cast::() }; debug_assert!(this.byte_slice().as_ptr() == buf.as_ptr()); - // Zig: `bun.assert(this.latin1Slice().len == buf.len)` — `latin1Slice().len` is - // `byteLength()` (i.e. `m_length * 2` for UTF-16), not the code-unit count. + // The buffer length is `byte_length()` (i.e. `m_length * 2` for + // UTF-16), not the code-unit count. debug_assert!(this.byte_length() == buf.len()); this.deref(); } @@ -1465,7 +1431,7 @@ pub mod StringImplAllocator { pub const VTABLE_PTR: &AllocatorVTable = &VTABLE; } -/// Port of `bun.String.StringImpl` — `extern union`. +/// C-layout untagged union over [`String`]'s payload representations. #[repr(C)] #[derive(Clone, Copy)] pub union StringImpl { @@ -1474,9 +1440,9 @@ pub union StringImpl { // .StaticZigString aliases .zig_string; .Dead/.Empty are zero-width. } -/// Port of `bun.String` (a.k.a. `BunString` in C++). +/// Known as `BunString` in C++. /// -/// 5-variant tagged union over WTF-backed and Zig-slice-backed strings. NOT a +/// 5-variant tagged union over WTF-backed and `ZigString`-backed strings. NOT a /// Rust `enum` because C++ mutates `tag` and `value` independently across FFI. #[repr(C)] #[derive(Clone, Copy)] @@ -1488,7 +1454,7 @@ pub struct String { impl String { pub const NAME: &'static str = "BunString"; - /// Port of `bun.String.isWTFAllocator` — vtable-identity check against + /// Vtable-identity check against /// [`StringImplAllocator::VTABLE`]. #[inline] pub fn is_wtf_allocator(alloc: StdAllocator) -> bool { @@ -1562,9 +1528,8 @@ impl String { } } - /// Zig `eqlComptime` — compare against a (typically literal) byte slice. - /// PERF(port): Zig dispatched to SIMD `bun.strings.eqlComptime*`; this T0 - /// version uses scalar `==` / widening compare. Re-route to + /// Compare against a (typically literal) byte slice. + /// PERF: this T0 version uses scalar `==` / widening compare. Re-route to /// `bun_core::strings` via inlining if it shows up on a hot path. pub fn eql_comptime(&self, other: &[u8]) -> bool { let zs = self.to_zig_string(); @@ -1585,9 +1550,8 @@ impl String { impl core::fmt::Display for String { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - // Port of `ZigString.format`: utf8 → write bytes; utf16 → transcode; + // utf8 → write bytes; utf16 → transcode; // latin1 → widen each byte to a Unicode scalar. - // PERF(port): was `bun.fmt.formatUTF16Type` / `formatLatin1` (SIMD). let zs = self.to_zig_string(); if zs.len == 0 { return Ok(()); @@ -1598,7 +1562,7 @@ impl core::fmt::Display for String { } Ok(()) } else if zs.is_utf8() { - // Zig wrote raw bytes; BStr renders them without allocating. + // BStr renders raw bytes without allocating. write!(f, "{}", bstr::BStr::new(zs.slice())) } else { for &b in zs.slice() { @@ -1628,14 +1592,12 @@ pub fn is_slice_in_buffer(slice: &[u8], buffer: &[u8]) -> bool { is_slice_in_buffer_t::(slice, buffer) } -/// Zig: `bun.rangeOfSliceInBuffer` (`src/bun.zig`). /// Returns `[offset, len]` if `slice` lies within `buffer`, else `None`. pub fn range_of_slice_in_buffer(slice: &[u8], buffer: &[u8]) -> Option<[u32; 2]> { if !is_slice_in_buffer(slice, buffer) { return None; } let r = [ - // Zig: `@truncate(@intFromPtr(slice.ptr) -| @intFromPtr(buffer.ptr))` (slice.as_ptr() as usize).saturating_sub(buffer.as_ptr() as usize) as u32, slice.len() as u32, ]; @@ -1643,13 +1605,10 @@ pub fn range_of_slice_in_buffer(slice: &[u8], buffer: &[u8]) -> Option<[u32; 2]> Some(r) } -/// Zig: `bun.freeSensitive` (`src/bun.zig`). -/// -/// Zig: `bun.default_allocator.free(slice)` for raw `[]u8` not owned by a -/// `Vec`/`Box` (e.g. duped via `mi_malloc` on the C side, or via -/// [`StdAllocator::free`] on the Zig side). With `#[global_allocator] = -/// Mimalloc` this is `mi_free`; the `len` is accepted for size-asserting -/// builds and to mirror the Zig signature. +/// Free a raw `[u8]` allocation not owned by a `Vec`/`Box` (e.g. duped via +/// `mi_malloc` on the C side, or via [`StdAllocator::free`]). With +/// `#[global_allocator] = Mimalloc` this is `mi_free`; the `len` is accepted +/// for size-asserting builds. /// /// # Safety /// `ptr` must be null or point to a live allocation of `len` bytes obtained @@ -1664,7 +1623,7 @@ pub unsafe fn default_free(ptr: *mut u8, len: usize) { basic::C_ALLOCATOR.raw_free(buf, Alignment::from_byte_units(1), 0); } -/// Zig: `bun.default_allocator.dupe(u8, src)` for raw `[]u8` not owned by a +/// Duplicate `src` into a raw allocation not owned by a /// `Vec`/`Box` — symmetric with [`default_free`]. Returns a `&'static [u8]` /// view onto a fresh mimalloc allocation; caller is responsible for pairing /// with `default_free(ptr, len)`. @@ -1688,7 +1647,7 @@ pub fn default_dupe(src: &[u8]) -> &'static [u8] { } } -/// Port of `std.crypto.secureZero` — `@memset(@volatileCast(s), 0)`. Zeros +/// Zeros /// `len` bytes at `p` in a way the optimizer cannot elide. Uses bulk /// `write_bytes` (lowers to `memset`) instead of a per-byte volatile loop so /// debug builds don't pay O(len) iteration overhead — the SSLConfig leak test @@ -1712,9 +1671,7 @@ pub unsafe fn secure_zero(p: *mut u8, len: usize) { /// information kept in memory can be read until the OS decommits it or the /// allocator reuses it. Zero it before dropping. /// -/// Zig used `std.crypto.secureZero` then `allocator.free`; Rust drops the -/// allocator param (global mimalloc) and uses [`secure_zero`] so the zeroing -/// cannot be elided by the optimizer. +/// Uses [`secure_zero`] so the zeroing cannot be elided by the optimizer. pub fn free_sensitive(mut slice: Box<[T]>) { // SAFETY: `slice` is exclusively owned; writing `size_of_val` zero bytes // over its storage is sound for `T: Copy` (no drop glue, no invariants on @@ -1726,7 +1683,7 @@ pub fn free_sensitive(mut slice: Box<[T]>) { drop(slice); } -/// Port of `bun.freeSensitive(bun.default_allocator, slice)` for the C-string +/// [`free_sensitive`] for the C-string /// case used by http SSLConfig. Zeros the allocation before freeing /// (defence-in-depth for keys/passphrases). /// @@ -1750,7 +1707,7 @@ pub unsafe fn free_sensitive_cstr(p: *const core::ffi::c_char) { // ────────────────────────────────────────────────────────────────────────── // IndexType — `packed struct(u32) { index: u31, is_overflow: bool = false }` -// Zig packed-struct fields are LSB-first: bits 0..=30 = index, bit 31 = is_overflow. +// Bits 0..=30 = index, bit 31 = is_overflow. // ────────────────────────────────────────────────────────────────────────── #[repr(transparent)] @@ -1787,7 +1744,7 @@ impl IndexType { pub const NOT_FOUND: IndexType = IndexType::new(u32::MAX >> 1, false); // maxInt(u31) pub const UNASSIGNED: IndexType = IndexType::new((u32::MAX >> 1) - 1, false); // maxInt(u31) - 1 -#[repr(u8)] // Zig: enum(u3) +#[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq)] pub enum ItemStatus { Unknown, @@ -1804,10 +1761,10 @@ pub enum ItemStatus { // ────────────────────────────────────────────────────────────────────────── // ────────────────────────────────────────────────────────────────────────── -// `bun.allocators` namespace shim +// `allocators` namespace shim // -// Zig exposed this file as `bun.allocators.*`; downstream crates were ported -// against that path (`use bun_alloc::allocators;`). Re-export the crate root +// Downstream crates use the `bun_alloc::allocators` path +// (`use bun_alloc::allocators;`). Re-export the crate root // so `allocators::IndexType`, `allocators::BSSMapInner`, etc. resolve without // rewriting every callsite. // ────────────────────────────────────────────────────────────────────────── @@ -1818,8 +1775,7 @@ pub mod allocators { // ────────────────────────────────────────────────────────────────────────── // Per-monomorphization singleton macros // -// Zig defines `pub var instance: *Self = undefined; pub var loaded = false;` -// *inside* the generic type, giving one static per instantiation. Rust forbids +// Each instantiation needs its own singleton. Rust forbids // generic statics, so the storage is emitted at the *declare site* instead: // // bss_string_list! { pub dirname_store: 4096, 129 } @@ -1827,27 +1783,26 @@ pub mod allocators { // // pub fn dirname_store() -> *mut BSSStringList<4096,129> // // The accessor lazily field-initializes via `init_at` under `std::sync::Once`. -// Returning `&'static mut` is the same aliasing contract as Zig's global -// `instance` pointer — callers must not hold overlapping unique borrows. +// Returning `&'static mut` means callers must not hold overlapping unique +// borrows. // ────────────────────────────────────────────────────────────────────────── /// Emit a process-lifetime singleton accessor for any type with an /// `unsafe fn init_at(*mut Self)` in-place initializer. Storage is a single /// `AtomicPtr` (8 bytes) per declare site; the value itself is heap-allocated -/// on first call (Zig spec: `default_allocator.create(Self)`). +/// on first call. #[macro_export] macro_rules! bss_singleton { ($(#[$m:meta])* $vis:vis fn $name:ident() -> $ty:ty) => { $(#[$m])* #[inline(always)] $vis fn $name() -> *mut $ty { - // Zig's spec is `default_allocator.create(Self)` on first access - // (heap, process-lifetime). Store an 8-byte heap pointer and - // allocate on first call, matching the spec. + // Store an 8-byte heap pointer and allocate on first call + // (heap, process-lifetime). // // Hot path: this accessor is hit per-append/get from the resolver - // (`DirnameStore::append`, `EntriesMap::get`, …). Zig reads a - // plain `*Self` global; the previous `Once::call_once` fast-path + // (`DirnameStore::append`, `EntriesMap::get`, …). The previous + // `Once::call_once` fast-path // is an Acquire load + cmp + branch + Relaxed load that *cannot* // inline across crates (it's a call into `std::sys::sync::once`). // Open-code the double-checked-init so the post-init path is one @@ -1887,17 +1842,16 @@ macro_rules! bss_singleton { /// Heap-allocate a fresh `T` via mimalloc and run its in-place `init_at` initializer. /// -/// Shared body of the `BSSList`/`BSSStringList`/`BSSMapInner`/`BSSMap` `init()` shims — -/// Zig's `default_allocator.create(Self)` followed by field-init. The once-guard -/// (Zig's `loaded` flag) is the *caller's* responsibility; use the `bss_*!` macros +/// Shared body of the `BSSList`/`BSSStringList`/`BSSMapInner`/`BSSMap` `init()` shims. +/// The once-guard is the *caller's* responsibility; use the `bss_*!` macros /// for the canonical per-monomorphization singleton. #[doc(hidden)] // Public only for the `bss_singleton!` macro expansion in dependent crates. #[inline] pub fn bss_heap_init(init_at: unsafe fn(*mut T)) -> NonNull { let ptr = bss_lazy_bytes(size_of::(), core::mem::align_of::()).cast::(); // SAFETY: ptr is a fresh, exclusively-owned, properly-aligned, all-zeros-on-read - // allocation; lives for process lifetime (singleton; never freed/unmapped, - // matching Zig). `init_at` is therefore free to skip writing any field whose + // allocation; lives for process lifetime (singleton; never freed/unmapped). + // `init_at` is therefore free to skip writing any field whose // all-zeros bit pattern is already a valid initial value (e.g. `OverflowList`'s // 32 KiB `[Option>; 4096]` array — `None` is the null niche). unsafe { init_at(ptr.as_ptr()) }; @@ -1913,11 +1867,10 @@ pub fn bss_heap_init(init_at: unsafe fn(*mut T)) -> NonNull { /// instead of all 130. On Windows this falls back to `mi_zalloc_aligned` /// (eager commit, but still all-zeros so callers may rely on that uniformly). /// -/// The mapping is **never freed** — these are Zig-port `.bss`-semantics +/// The mapping is **never freed** — these are `.bss`-semantics /// singletons. Do not call from code paths that need to release the storage. /// -/// **Coalesced arena.** In Zig these singletons are linker-adjacent `.bss` -/// globals: one VMA, demand-faulted page-by-page. The original Rust port +/// **Coalesced arena.** An earlier version /// `mmap`ed each one separately, costing 6 `mmap` syscalls + 6 VMAs on the /// `bun run ` path (≈2 MiB total across `entry_store_backing`, /// `dirname_store_backing`, `hash_map_instance`, …) before any user code @@ -2066,7 +2019,7 @@ fn bss_mmap_noreserve(len: usize) -> *mut u8 { /// /// Returns `NonNull<[MaybeUninit]>`: bytes are zero-on-read but treated as /// logically uninitialized — callers must gate reads on a separate `used` -/// counter (Zig leaves the array `undefined` and never reads past `used`). +/// counter — never read past `used`. #[doc(hidden)] #[inline] pub fn bss_lazy_slice(count: usize) -> NonNull<[MaybeUninit]> { @@ -2151,7 +2104,7 @@ macro_rules! get_zone { type HashKeyType = u64; -/// Zig `IndexMapContext` — identity hash on a u64 key. Keys here are already +/// Identity hash on a u64 key. Keys here are already /// `bun_wyhash` outputs, so rehashing with std's SipHash just costs cycles. #[derive(Default, Clone, Copy)] pub struct IdentityU64Hasher(u64); @@ -2206,7 +2159,6 @@ pub trait OverflowBlock { const OVERFLOW_GROUP_MAX: usize = 4095; const OVERFLOW_GROUP_SLOTS: usize = OVERFLOW_GROUP_MAX + 1; -// Zig: `UsedSize = std.math.IntFittingRange(0, max + 1)` → u13. Rust has no u13; use u16. type OverflowUsedSize = u16; pub struct OverflowGroup { @@ -2251,9 +2203,8 @@ impl OverflowGroup { if self.allocated <= self.used { debug_assert!((self.allocated as usize) < OVERFLOW_GROUP_SLOTS); - // Zig: default_allocator.create(Block) catch unreachable // SAFETY: Box → zero() initializes the `used` counter; payload array - // is `[MaybeUninit; N]` and stays uninit exactly as Zig does. + // is `[MaybeUninit; N]` and intentionally stays uninit. let mut b: Box> = Box::new_uninit(); // SAFETY: `b.as_mut_ptr()` is a valid, exclusive, aligned `*mut Block`. unsafe { Block::zero(b.as_mut_ptr()) }; @@ -2275,14 +2226,12 @@ impl OverflowGroup { // OverflowList // ────────────────────────────────────────────────────────────────────────── -// TODO(port): const-generic arithmetic (`[ValueType; COUNT]` inside a generic struct) requires -// `feature(generic_const_exprs)` on stable Rust. Pin COUNT per instantiation site -// or use a heap `Box<[ValueType]>` with debug_assert on len. +// Const-generic arithmetic (deriving COUNT from another const param) requires +// `feature(generic_const_exprs)` on stable Rust, so COUNT is pinned per instantiation site. pub struct OverflowListBlock { - // Zig: `SizeType = std.math.IntFittingRange(0, count)`; use u32 here. pub used: u32, - // Zig leaves `items` undefined and overwrites by raw memcpy (no drop). + // Only `[0..used]` is initialized; writes are raw (no drop glue). pub items: [MaybeUninit; COUNT], } @@ -2295,7 +2244,7 @@ impl OverflowListBlock { pub fn append(&mut self, value: ValueType) -> &mut ValueType { debug_assert!((self.used as usize) < COUNT); let index = self.used as usize; - // Raw write — slot may be uninit; Zig assignment has no drop glue. + // Raw write — slot may be uninit; no drop glue runs. self.items[index].write(value); self.used = self.used.wrapping_add(1); // SAFETY: just initialized on the line above. @@ -2318,7 +2267,7 @@ impl OverflowBlock for OverflowListBlock { pub list: OverflowGroup>, - pub count: u32, // Zig: u31 + pub count: u32, } impl OverflowList { @@ -2429,40 +2378,41 @@ impl OverflowList { /// We do keep a pointer to it globally, but because the data is not zero-initialized, it ends up /// taking space in the object file. We don't want to spend 1-2 MB on these structs. /// -/// TODO(port): const-generic arithmetic (`COUNT = _COUNT * 2`) and per-monomorphization -/// a raw mutable INSTANCE static are not expressible on stable Rust. Instantiate per use-site -/// via `macro_rules!` or pin concrete `COUNT` constants. +/// Const-generic arithmetic (`COUNT = _COUNT * 2`) and a per-monomorphization +/// raw mutable INSTANCE static are not expressible on stable Rust; callers +/// pin concrete `COUNT` constants per use-site. /// /// `#[repr(C)]` with the small mutated scalars (`mutex`, `head`, `used`, /// `tail`'s header) laid out *before* the giant `backing_buf` array. Storage /// comes from [`bss_lazy_bytes`] (anonymous mmap, demand-zero), so each page /// faults only on first write. With default repr rustc placed `used: u32` /// *after* `backing_buf` (~1.2 MB into the largest instantiation), so -/// `init_at`'s startup writes faulted tail pages Zig never touches. With this +/// `init_at`'s startup writes faulted tail pages needlessly. With this /// layout every startup write lands in page 0 of the mapping; subsequent pages /// fault only as `append` actually fills them. #[repr(C)] pub struct BSSList { pub mutex: Mutex, // LIFETIMES.tsv: dual semantics — points at sibling `tail` OR a heap alloc. - // TODO(port): lifetime — keep raw NonNull; self-referential when `head == &self.tail`. + // Kept as a raw NonNull: self-referential when `head == &self.tail`, so a safe + // borrow cannot express it. pub head: Option>>, pub used: u32, pub tail: BSSListOverflowBlock, - // Zig leaves `backing_buf` undefined; only `[0..used]` is initialized. + // Only `[0..used]` is initialized. pub backing_buf: [MaybeUninit; COUNT], } // SAFETY: `head` is a self-referential `NonNull` into `self.tail` or a heap block owned by // `self`; all mutation goes through `self.mutex`. The raw pointer is the only `!Sync` field; -// the type is logically a mutex-guarded global (matches Zig's threadsafe singleton). +// the type is logically a mutex-guarded global singleton. unsafe impl Send for BSSList {} // SAFETY: see the `Send` impl directly above — all access is mutex-serialized. unsafe impl Sync for BSSList {} const BSS_LIST_CHUNK_SIZE: usize = 256; -/// Zig's per-store overflow-block size is `count / 4`; this shared constant must +/// The per-store overflow-block size is `count / 4`; this shared constant must /// be >= the largest store's, i.e. the filename store's `8192 / 4`. pub const BSS_OVERFLOW_BLOCK_SIZE: usize = 2048; @@ -2474,7 +2424,7 @@ pub const BSS_OVERFLOW_BLOCK_SIZE: usize = 2048; pub struct BSSListOverflowBlock { pub used: AtomicU16, pub prev: Option>>, - // Zig leaves `data` undefined; only `[0..used]` is initialized. + // Only `[0..used]` is initialized. pub data: [MaybeUninit; BSS_LIST_CHUNK_SIZE], } @@ -2483,9 +2433,6 @@ impl BSSListOverflowBlock { /// SAFETY: `this` must point to writable, properly-aligned storage of `Self`. #[inline] pub unsafe fn zero(this: *mut Self) { - // Avoid struct initialization syntax. - // This makes Bun start about 1ms faster. - // https://github.com/ziglang/zig/issues/24313 // SAFETY: caller guarantees `this` points to writable, aligned storage of // `Self`. Raw `ptr::write` because `*this` may be uninit — assignment // would run drop glue on garbage (`prev: Option>`). @@ -2500,7 +2447,7 @@ impl BSSListOverflowBlock { if index as usize >= BSS_LIST_CHUNK_SIZE { return Err(AllocError); } - // Raw write — slot may be uninit; Zig assignment has no drop glue. + // Raw write — slot may be uninit; no drop glue runs. self.data[index as usize].write(item); // SAFETY: just initialized on the line above. Ok(unsafe { self.data[index as usize].assume_init_mut() }) @@ -2528,7 +2475,6 @@ impl BSSList { pub const CHUNK_SIZE: usize = BSS_LIST_CHUNK_SIZE; const MAX_INDEX: usize = COUNT - 1; - // Zig: `pub var instance: *Self = undefined; pub var loaded = false;` // Rust cannot define generic statics, so the per-monomorphization storage is // emitted at the *declare site* via `bss_list! { name: T, N }` (see macro // below), which owns a `SyncUnsafeCell>` + `Once` and @@ -2552,36 +2498,35 @@ impl BSSList { /// and the non-zero self-referential `head = &tail`. Both fields lead the /// `#[repr(C)]` layout, so every startup write stays within page 0 of the /// singleton mapping (see the layout note on [`BSSList`]). `backing_buf` - /// and `tail.data` are intentionally left uninitialized (Zig leaves them - /// `undefined`); only `[0..used]` is read. + /// and `tail.data` are intentionally left uninitialized; only `[0..used]` + /// is read. pub unsafe fn init_at(slot: *mut Self) { // SAFETY: caller contract — `slot` is a valid, exclusive, aligned, // all-zeros `*mut Self`. unsafe { addr_of_mut!((*slot).mutex).write(Mutex::new()); - // Zig: `instance.head = &instance.tail` — self-referential; raw NonNull. + // Self-referential `head = &tail`; raw NonNull. let tail_ptr = addr_of_mut!((*slot).tail); addr_of_mut!((*slot).head).write(Some(NonNull::new_unchecked(tail_ptr))); } } - /// Heap-allocate and initialize a fresh instance. The once-guard (Zig's - /// `loaded` flag) is the *caller's* responsibility — use `bss_list!` for + /// Heap-allocate and initialize a fresh instance. The once-guard is the + /// *caller's* responsibility — use `bss_list!` for /// the canonical per-monomorphization singleton. pub fn init() -> NonNull { bss_heap_init(Self::init_at) } - // Zig `deinit` → `impl Drop for BSSList` below (PORTING.md: never expose `pub fn deinit`). - // The `instance.destroy()` + `loaded = false` half is singleton teardown — the - // `bss_list!` singleton wrapper owns that; Drop only frees the heap-allocated head chain. + // Singleton teardown belongs to the `bss_list!` singleton wrapper; + // Drop only frees the heap-allocated head chain. pub fn is_overflowing(instance: &Self) -> bool { instance.used as usize >= COUNT } pub fn exists(&self, value: &[u8]) -> bool { - // Zig: `isSliceInBuffer(value, &instance.backing_buf)` — pointer-range check + // Pointer-range check // against the backing storage as raw bytes. Done with addresses rather // than forming a `&[u8]` over `MaybeUninit` storage (which would // assert byte-validity of uninitialized memory). @@ -2601,8 +2546,7 @@ impl BSSList { self.used += 1; // SAFETY: head is always non-null after init() (points at self.tail or heap block). let mut head_ptr = self.head.unwrap(); - // Zig: `self.head.append(value) catch { allocate new block; retry }`. - // Restructured to check capacity first, allocate the new block if + // Check capacity first, allocate the new block if // needed, then reserve exactly one slot. Safe under `self.mutex`. // SAFETY: `head_ptr` is a valid exclusive ref (mutex held). let head_full = unsafe { @@ -2616,7 +2560,7 @@ impl BSSList { unsafe { BSSListOverflowBlock::zero(new_block.as_mut_ptr()) }; // SAFETY: all non-`MaybeUninit` fields are now initialized. let mut new_block = unsafe { new_block.assume_init() }; - // Preserve the chain (Zig: `new_block.prev = self.head`). The inline `self.tail` + // Preserve the chain (`new_block.prev` = old head). The inline `self.tail` // is not Boxed, so represent it as `prev = None`; heap heads were // `Box::into_raw`'d by an earlier call here and are reclaimed as `Box`. let tail_ptr: *const BSSListOverflowBlock = core::ptr::addr_of!(self.tail); @@ -2642,8 +2586,8 @@ impl BSSList { /// accounted in `used`, so leaving it uninitialized is UB on later read. /// /// This is the slot-reservation primitive: it lets large `ValueType`s be - /// constructed directly in the destination, matching Zig's result-location - /// semantics. The by-value `append` below forces a stack temporary + + /// constructed directly in the destination (result-location + /// semantics). The by-value `append` below forces a stack temporary + /// memcpy into the slot which Rust does not reliably NRVO across a /// non-inlined call boundary; `append_uninit` exposes the slot pointer so /// the caller's struct literal lowers straight into it. @@ -2651,8 +2595,8 @@ impl BSSList { /// Takes `*mut Self` (not `&mut self`) so callers can pass the raw /// `bss_list!` singleton pointer directly without first materializing a /// `&mut Self` — which would be aliased UB if two threads did so - /// concurrently *before* reaching the inner `self.mutex.lock()`. This - /// matches Zig's `*Self` receiver: the inner mutex is the sole + /// concurrently *before* reaching the inner `self.mutex.lock()`. The + /// inner mutex is the sole /// serialization point, so no caller-side outer lock is needed. /// /// SAFETY: `this` must point to a live, initialized `BSSList` (typically @@ -2696,17 +2640,14 @@ impl BSSList { // this thread (index already bumped under the mutex). unsafe { Ok(core::ptr::from_mut((*slot).write(value))) } } - - // Zig: `pub const Pair = struct { index: IndexType, value: *ValueType };` - // LIFETIMES.tsv: ARENA → *const ValueType. Type appears unused. } impl Drop for BSSList { fn drop(&mut self) { - // Zig `deinit`: `self.head.deinit()` walks `prev` and frees each heap block. + // Free the heap-allocated head chain. // The inline `self.tail` is not Boxed and must not be Box-dropped; the // `prev: Option>` chain stops at `None` before reaching it - // (see `append_overflow_uninit`). Singleton `loaded = false` reset belongs to the + // (see `append_overflow_uninit`). Singleton teardown belongs to the // `bss_list!` singleton wrapper, not here. if let Some(head) = self.head.take() { let tail_ptr: *const BSSListOverflowBlock = core::ptr::addr_of!(self.tail); @@ -2733,21 +2674,20 @@ pub struct BSSListPair { /// Stores an initial count in .bss section of the object file. /// Overflows to heap when count is exceeded. /// -/// TODO(port): same const-generic-arithmetic and per-type-static caveats as `BSSList`. +/// Same const-generic-arithmetic and per-type-static caveats as `BSSList`. pub struct BSSStringList< const COUNT: usize, /* = _COUNT * 2 */ const ITEM_LENGTH: usize, /* = _ITEM_LENGTH + 1 */ > { - // Zig keeps both arrays *inline* in the struct (`[count*item_length]u8`, - // `[count][]const u8`) so they live in the same demand-faulted allocation - // as the rest of the singleton and `init()` writes only the four scalar + // Inline arrays would live in the same demand-faulted allocation + // as the rest of the singleton with `init()` writing only the four scalar // fields — pages are committed lazily as `append` writes bytes. Stable // Rust can't spell `[u8; COUNT*ITEM_LENGTH]` without `generic_const_exprs`, // so we store fat pointers to *separate* `bss_lazy_bytes` mappings instead. // Same laziness guarantee (MAP_NORESERVE), same lifetime (process-static, // never freed), no eager memset. // - // `MaybeUninit` because Zig leaves both arrays `undefined`; only + // `MaybeUninit` because both arrays are logically uninitialized; only // `[..backing_buf_used]` / `[..slice_buf_used]` are ever read. pub backing_buf: NonNull<[MaybeUninit]>, // len == COUNT * ITEM_LENGTH pub backing_buf_used: u64, @@ -2762,7 +2702,7 @@ struct EmptyType { len: usize, } -/// Trait modeling Zig's `comptime AppendType` switch in `doAppend`. +/// Byte sources accepted by the `append*` methods. pub trait BSSAppendable { /// Total byte length (excluding sentinel). fn total_len(&self) -> usize; @@ -2817,11 +2757,9 @@ impl BSSStringList()` bytes that lives for `'static`. pub unsafe fn init_at(slot: *mut Self) { - // Zig (`bun_alloc.zig` BSSStringList.init): writes ONLY `allocator`, - // `backing_buf_used = 0`, `slice_buf_used = 0`, `overflow_list.zero()`, - // `mutex = .{}` — `backing_buf`/`slice_buf` are left `undefined` so the - // ~1.4 MiB of array storage stays unfaulted until `append` writes a byte. - // Match that exactly: lazy-map the arrays, write the four scalars, and + // `backing_buf`/`slice_buf` are left uninitialized so the + // ~1.4 MiB of array storage stays unfaulted until `append` writes a byte: + // lazy-map the arrays, write the four scalars, and // zero only the three OverflowList counters (its 32 KiB `ptrs` array is // already `[None; 4096]` because `slot` came from `bss_heap_init`). // SAFETY: caller contract — `slot` is a valid, exclusive, aligned @@ -2842,7 +2780,7 @@ impl BSSStringList bool { @@ -2859,8 +2797,6 @@ impl BSSStringList &mut [u8]` (instant UB under stacked borrows), /// so this takes raw parts instead. Callers that held a `&[u8]` must drop that borrow /// before calling and pass `(ptr, len)` derived from a `&mut`-provenance pointer. @@ -2880,7 +2816,7 @@ impl BSSStringList BSSStringList, ) -> core::result::Result<&'a [u8], AllocError> { - // Zig's `std.fmt.count` + `std.fmt.bufPrint` are both comptime-expanded - // straight-line writes, so the count-then-write double pass is free - // there. Rust's `core::fmt::write` drives a `dyn fmt::Write` vtable per - // argument piece, so a literal port pays that dispatch *twice* — the + // `core::fmt::write` drives a `dyn fmt::Write` vtable per + // argument piece, so a count-then-write double pass pays that dispatch *twice* — the // dominant cost in `extract_tarball::build_url`, which is called once // per lockfile package with 6+ args. // @@ -3040,8 +2974,8 @@ impl BSSStringList( &mut self, @@ -3073,7 +3007,7 @@ impl BSSStringList(); if ptr.is_null() { return Err(AllocError); @@ -3100,8 +3034,7 @@ impl BSSStringList BSSStringList BSSStringList // ────────────────────────────────────────────────────────────────────────── -// Zig returns one of two *different* struct types depending on `comptime store_keys: bool`. +// Two different struct shapes depending on `store_keys`. // Rust cannot return different types from one generic; we expose both: // - `BSSMapInner` (the `store_keys = false` shape) // - `BSSMap` (the `store_keys = true` wrapper) -// TODO(port): callers that passed `store_keys=false` should name `BSSMapInner` directly. +// Callers that passed `store_keys=false` should name `BSSMapInner` directly. pub struct BSSMapInner { pub index: IndexMap, pub overflow_list: OverflowList, pub mutex: Mutex, - // Zig leaves `backing_buf` undefined; only `[0..backing_buf_used]` is initialized. + // Only `[0..backing_buf_used]` is initialized. pub backing_buf: [MaybeUninit; COUNT], pub backing_buf_used: u16, } @@ -3167,7 +3100,7 @@ impl // `*mut Self` in all-zeros storage from `bss_heap_init`. The 32 KiB // `overflow_list.list.ptrs` array is already `[None; 4096]` (null // niche), so write only the three counters; `backing_buf` is - // intentionally left uninitialized (Zig: `undefined`). + // intentionally left uninitialized. unsafe { addr_of_mut!((*slot).mutex).write(Mutex::new()); addr_of_mut!((*slot).index).write(IndexMap::default()); @@ -3182,7 +3115,6 @@ impl bss_heap_init(Self::init_at) } - // Zig `deinit`: `self.index.deinit(allocator)` then free instance. // With `IndexMap = HashMap`, Drop frees it; singleton Box drop frees instance. pub fn is_overflowing(instance: &Self) -> bool { @@ -3236,7 +3168,7 @@ impl pub fn get(&mut self, denormalized_key: &[u8]) -> Option<&mut ValueType> { let _key = Self::key_hash(denormalized_key); - // Hold the lock across `at_index` (Zig: `defer self.mutex.unlock()` at fn scope) — + // Hold the lock across `at_index` — // a concurrent `put()` could otherwise mutate `overflow_list`/`backing_buf` while // we dereference `index`. `MutexGuard` holds a raw pointer (see [`Mutex`] docs), // so it does not conflict with the `&mut self` borrow in `at_index`. @@ -3295,7 +3227,7 @@ impl } } else { let idx = result.index.index() as usize; - // Raw write — fresh slots are uninit; Zig assignment has no drop glue. + // Raw write — fresh slots are uninit; no drop glue runs. self.backing_buf[idx].write(value); // SAFETY: just initialized on the line above. unsafe { self.backing_buf[idx].assume_init_mut() } @@ -3309,7 +3241,6 @@ impl let _guard = self.mutex.lock(); let _key = Self::key_hash(denormalized_key); self.index.remove(&_key).is_some() - // (Zig has commented-out per-slot deinit code here; intentionally not ported.) } pub fn values(&mut self) -> &mut [ValueType] { @@ -3338,15 +3269,12 @@ pub struct BSSMap< // allocator's `dealloc`). map: NonNull>, // Same lazy-fault treatment as `BSSStringList::backing_buf` — see the - // struct-level comment there. Zig keeps these inline; we map separately + // struct-level comment there. Mapped separately // because `[u8; COUNT*ESTIMATED_KEY_LENGTH]` needs `generic_const_exprs`. pub key_list_buffer: NonNull<[MaybeUninit]>, // len == COUNT * ESTIMATED_KEY_LENGTH pub key_list_buffer_used: usize, pub key_list_slices: NonNull<[MaybeUninit<&'static [u8]>]>, // len == COUNT - // TODO(port): Zig declares this as `OverflowList([]u8, count / 4)` but then calls - // `.items[...]` and `.append(allocator, slice)` on it — those are `std.ArrayListUnmanaged` - // methods, NOT `OverflowList` methods. Likely dead code or a latent bug upstream. - // Ported as `Vec<&'static [u8]>` to match the *called* API. + // Indexed by the *absolute* index (not overflow-relative) in `key_at_index`. pub key_list_overflow: Vec<&'static [u8]>, } @@ -3395,7 +3323,7 @@ impl< unsafe { self.map.as_mut() } } - // Zig `deinit`: `self.map.deinit()` then free instance — process-lifetime; never freed. + // Process-lifetime; never freed. pub fn is_overflowing(instance: &Self) -> bool { instance.map().backing_buf_used as usize >= COUNT @@ -3406,12 +3334,10 @@ impl< } pub fn get(&mut self, key: &[u8]) -> Option<&mut ValueType> { - // PERF(port): Zig uses @call(bun.callmod_inline, ...) — profile if hot. self.map_mut().get(key) } pub fn at_index(&mut self, index: IndexType) -> Option<&mut ValueType> { - // PERF(port): Zig uses @call(bun.callmod_inline, ...) — profile if hot. self.map_mut().at_index(index) } @@ -3429,7 +3355,7 @@ impl< // a process-lifetime mapping of `COUNT` slots. Some(unsafe { *self.key_list_slices.cast::<&'static [u8]>().as_ptr().add(i) }) } else { - // TODO(port): see key_list_overflow note — Zig indexes `.items` here. + // See the `key_list_overflow` field note. Some(self.key_list_overflow[index.index() as usize]) } } @@ -3442,8 +3368,8 @@ impl< result: &mut Result, value: ValueType, ) -> core::result::Result<&mut ValueType, AllocError> { - // PORT NOTE: reshaped for borrowck — Zig returns `ptr` from map.put then calls put_key; - // Rust can't hold &mut ValueType across &mut self.put_key. Stash as raw, re-borrow after. + // Reshaped for borrowck — Rust can't hold &mut ValueType across + // &mut self.put_key. Stash as raw, re-borrow after. let ptr: *mut ValueType = self.map_mut().put(result, value)?; if STORE_KEY { self.put_key(key, result)?; @@ -3495,7 +3421,7 @@ impl< // SAFETY: points into self.key_list_buffer (singleton-static lifetime). slice = unsafe { core::slice::from_raw_parts(dst.as_ptr(), dst.len()) }; } else { - // Zig: `slice = try self.map.allocator.dupe(u8, key);` — propagate OOM. Route + // Propagate OOM. Route // through mimalloc directly (PORTING.md forbids `Box::leak`) so the // size-agnostic `mi_free` below stays valid even after `trim_right` shortens // the stored slice. @@ -3521,7 +3447,7 @@ impl< debug_assert!(i < COUNT); // SAFETY: `key_list_slices` is a process-lifetime mapping of // `COUNT` slots; `i < COUNT`; we hold `&mut self`. Raw write — - // slot may be uninit (Zig leaves it `undefined`). + // slot may be uninit. unsafe { self.key_list_slices .as_ptr() @@ -3530,12 +3456,12 @@ impl< .write(MaybeUninit::new(slice)); } } else { - // TODO(port): see key_list_overflow note above re: `.items` / `.append(alloc, _)`. + // See the `key_list_overflow` field note. let idx = result.index.index() as usize; if self.key_list_overflow.len() > idx { let existing_slice = self.key_list_overflow[idx]; if !self.is_key_statically_allocated(existing_slice) { - // Zig: self.map.allocator.free(existing_slice). `mi_free` is + // `mi_free` is // size-agnostic, so a trimmed (shorter) stored slice is fine. // SAFETY: existing_slice was `mi_malloc`'d by a prior put_key call // (the only non-static-buffer source above) and not yet freed. @@ -3572,8 +3498,8 @@ impl< // Allocator-trait surface — OBSOLETE per PORTING.md §Allocators // ────────────────────────────────────────────────────────────────────────── // -// Zig's `std.mem.Allocator` / `GenericAllocator` interface threaded an allocator -// param through every fn because Zig has no global allocator. Rust does +// The legacy allocator interface threaded an allocator +// param through every fn. Rust has a global allocator // (`#[global_allocator] = Mimalloc` above), so per PORTING.md: // // - Non-AST crates: DELETE the `allocator` param. `Box`/`Vec`/`String` use @@ -3585,10 +3511,10 @@ impl< // it; do not add methods. Callers should be rewritten to drop the param // entirely. -/// Marker trait standing in for Zig `std.mem.Allocator`. See module note. +/// Legacy allocator marker trait. See module note. /// -/// Provides a `type_id()` hook so `is_instance`-style checks (Zig: -/// `allocator.vtable == &vtable`) can be expressed as concrete-type identity +/// Provides a `type_id()` hook so `is_instance`-style vtable-identity checks +/// can be expressed as concrete-type identity /// on the trait object — every implementor gets a default `type_id()` that /// returns its monomorphized `TypeId`. pub trait Allocator: 'static { @@ -3601,7 +3527,7 @@ pub trait Allocator: 'static { impl dyn Allocator { /// Is the concrete type behind this `&dyn Allocator` exactly `T`? /// - /// Zig's `allocator.vtable == &T.vtable` check, expressed as `TypeId` + /// A vtable-identity check, expressed as `TypeId` /// identity via the trait's `type_id()` hook (dynamic dispatch on the /// dyn receiver — NOT `Any::type_id`). All per-type /// `Foo::is_instance(alloc)` associated fns delegate here. @@ -3613,7 +3539,7 @@ impl dyn Allocator { /// Checks whether `allocator` is the default allocator. /// -/// Zig: `return allocator.vtable == c_allocator.vtable;` — compare identity +/// Compares identity /// against the global mimalloc-backed allocator. With `#[global_allocator] = /// Mimalloc`, the Rust default is `DefaultAlloc`; vtable-identity becomes a /// `TypeId` comparison. @@ -3622,7 +3548,7 @@ pub fn is_default(alloc: &dyn Allocator) -> bool { alloc.is::() } -/// Legacy ZST naming `bun.default_allocator`. With `#[global_allocator]` set, +/// Legacy default-allocator ZST. With `#[global_allocator]` set, /// this is just a unit marker. #[derive(Clone, Copy, Default)] pub struct DefaultAlloc; @@ -3630,7 +3556,7 @@ impl Allocator for DefaultAlloc {} static DEFAULT_ALLOC: DefaultAlloc = DefaultAlloc; -/// Zig: `bun.default_allocator` — global mimalloc-backed allocator. With +/// Global mimalloc-backed allocator handle. With /// `#[global_allocator] = Mimalloc`, this is a marker handle; callers that /// thread it should be rewritten to use `Box`/`Vec` directly. Kept so ported /// call sites that still pass an `&dyn Allocator` resolve. @@ -3640,18 +3566,14 @@ pub fn default_allocator() -> &'static dyn Allocator { } // `GenericAllocator` / `Borrowed` / `Nullable` are dropped — they modelled -// Zig's allocator-borrowing discipline (avoid double-deinit), which Rust's -// ownership already enforces. Drafts that referenced them are gated under -// `` and will be rewritten to drop the param when un-gated. +// an allocator-borrowing discipline (avoid double-free), which Rust's +// ownership already enforces. // ────────────────────────────────────────────────────────────────────────── // `basic` module selection // ────────────────────────────────────────────────────────────────────────── -// `basic.zig` ported as `impl GlobalAlloc for Mimalloc` above (the real impl). -// Draft kept for diff-pass only. +// The real impl is `impl GlobalAlloc for Mimalloc` above. #[path = "basic.rs"] pub mod basic; pub mod memory; - -// ported from: src/bun_alloc/bun_alloc.zig diff --git a/src/bun_alloc/maybe_owned.rs b/src/bun_alloc/maybe_owned.rs index 65411d46868..88f0f0a4a0b 100644 --- a/src/bun_alloc/maybe_owned.rs +++ b/src/bun_alloc/maybe_owned.rs @@ -11,20 +11,15 @@ //! drop(borrowed_foo); // no-op //! ``` //! -//! This type is a `GenericAllocator`; see `src/allocators.zig`. -//! -//! PORT NOTE: Zig modelled this over `Nullable` / `Borrowed` allocator -//! adaptors. With `#[global_allocator]`, "owned" reduces to "drop the box, -//! borrowed = leak"; the generic allocator threading is dropped. The struct -//! keeps the `Option` shape so callers that pattern-match on -//! `is_owned()` keep working. +//! With `#[global_allocator]`, "owned" reduces to "drop the box"; borrowed +//! drops are a no-op. The struct keeps the `Option` shape so callers that +//! pattern-match on `is_owned()` keep working. /// See module docs. pub struct MaybeOwned { _parent: Option, } -// Zig: `pub const Borrowed = MaybeOwned(BorrowedParent);` // Rust has no stable inherent associated types, so expose as a free alias. // `Borrowed` collapsed to `()` — borrows carry no allocator state. pub(crate) type MaybeOwnedBorrowed = MaybeOwned<()>; @@ -34,7 +29,6 @@ impl MaybeOwned { /// /// Allocations are forwarded to a default-initialized `A`. pub fn init() -> Self { - // Zig: `bun.memory.initDefault(Allocator)` Self::init_owned(A::default()) } } @@ -78,9 +72,6 @@ impl MaybeOwned { } } -// Zig `deinit` only forwarded to `bun.memory.deinit(parent_alloc)` on the owned field. -// Per PORTING.md (Idiom map: `pub fn deinit`), that is exactly field drop glue on -// `_parent: Option`, so no explicit `Drop` impl — keeping one would also forbid -// moving `self._parent` out in `into_parent(self)`. - -// ported from: src/bun_alloc/maybe_owned.zig +// Cleanup is exactly field drop glue on `_parent: Option`, so no explicit +// `Drop` impl — keeping one would also forbid moving `self._parent` out in +// `into_parent(self)`. diff --git a/src/bun_alloc/memory.rs b/src/bun_alloc/memory.rs index bc97888d15d..860e6919f75 100644 --- a/src/bun_alloc/memory.rs +++ b/src/bun_alloc/memory.rs @@ -1,34 +1,5 @@ //! Basic utilities for working with memory and objects. -// ────────────────────────────────────────────────────────────────────────────── -// PORT NOTE: `exemptedFromDeinit`, `deinitIsVoid`, and `deinit` are intentionally -// NOT ported as functions. -// -// Zig's `bun.memory.deinit(ptr_or_slice)` walked `@typeInfo` to: -// - recurse into slices/arrays/optionals/error-unions, -// - call `.deinit()` on struct / tagged-union pointees (unless the type set -// `pub const deinit = void;` or was in an exemption list), and -// - finally write `undefined` over the memory if the pointer was mutable. -// -// Rust's `Drop` already does the recursive part automatically: dropping a value -// drops every field, every `Vec`/`Box` element, every `Option`/`Result` payload. -// The "write undefined" poisoning has no safe Rust equivalent (and is a debug aid, -// not semantics). -// -// Call sites: -// - `bun.memory.deinit(&x)` → delete (let `x` drop at scope exit). -// - `bun.memory.deinit(slice)` → delete (slice elements drop with their owner). -// - explicit early release → `drop(x)` or a type-specific `close(self)`. -// -// `@typeInfo` has no Rust equivalent (§Comptime reflection), so a faithful generic -// port is not possible — and per §Idiom map, `deinit` definitions become `impl Drop` -// on the target type, not a free function here. -// -// TODO(port): if any caller relied on the `*x = undefined` poisoning to catch UAF in -// debug, add `#[cfg(debug_assertions)] unsafe { ptr::write_bytes(p, 0xAA, 1) }` at -// that call site. -// ────────────────────────────────────────────────────────────────────────────── - /// Rebase a slice from one memory buffer to another buffer. /// /// Given a slice which points into a memory buffer with base `old_base`, return a @@ -65,5 +36,3 @@ pub unsafe fn rebase_slice<'a>(slice: &[u8], old_base: *const u8, new_base: *con // `slice.len()` bytes. unsafe { core::slice::from_raw_parts(new_base.add(offset), slice.len()) } } - -// ported from: src/bun_alloc/memory.zig diff --git a/src/bun_alloc/stack_fallback.rs b/src/bun_alloc/stack_fallback.rs index 69880418b7b..22427f6a97d 100644 --- a/src/bun_alloc/stack_fallback.rs +++ b/src/bun_alloc/stack_fallback.rs @@ -1,16 +1,11 @@ -//! `StackFallback` — port of Zig `std.heap.StackFallbackAllocator(N)` -//! (vendor/zig/lib/std/heap.zig:376-481), inlining `FixedBufferAllocator` -//! (vendor/zig/lib/std/heap/FixedBufferAllocator.zig). The `&mut [u8]` self-ref -//! Zig keeps in `fixed_buffer_allocator.buffer` is replaced by computing -//! `buf.get().cast::()` on demand, so the Rust struct is **not** +//! `StackFallback` — bump-allocate from an inline stack buffer, spilling +//! to a fallback allocator on overflow. The buffer base is computed via +//! `buf.get().cast::()` on demand, so the struct is **not** //! self-referential and may be moved freely until the first `allocate`. //! //! ### Relationship to `AstAlloc` //! `StackFallback` is a **standalone** [`Allocator`]; it is **not** composed -//! under [`crate::ast_alloc::AstAlloc`]. In Zig, `stackFallback` and -//! `ASTMemoryAllocator` are orthogonal — none of the 20 `stackFallback` -//! callsites route AST-node allocation through it (the two `js_parser` uses -//! pass `bun.default_allocator` as fallback, not the AST arena). `AstAlloc` +//! under [`crate::ast_alloc::AstAlloc`]. `AstAlloc` //! has its own inline-buffer-with-heap-fallback state //! ([`crate::ast_alloc::AstAllocState`]). Under this standalone design //! `AstVec` stays 24 B; only vecs that explicitly want stack-first storage @@ -18,8 +13,6 @@ //! //! ### Callsite shape //! ```ignore -//! // Zig: var sf = std.heap.stackFallback(4096, bun.default_allocator); -//! // var list = std.ArrayList(u8).initCapacity(sf.get(), 256); //! let sf = StackFallback::<4096>::with_global(); //! let mut list: Vec = Vec::with_capacity_in(256, sf.get()); //! // `&sf` borrows ⇒ `sf` is pinned for `list`'s lifetime; `Vec` is 32 B. @@ -32,8 +25,8 @@ use core::ptr::{self, NonNull}; use crate::{MimallocArena, alloc_result, mimalloc}; -/// `std.heap.StackFallbackAllocator(N)` — bump-allocate from an inline -/// `[u8; N]` stack buffer; spill to `fallback` when it doesn't fit. +/// Bump-allocate from an inline `[u8; N]` stack buffer; spill to `fallback` +/// when it doesn't fit. /// `deallocate`/`grow` dispatch by address-range check ([`Self::owns`]). /// /// Lives on the caller's stack frame; single-threaded by construction @@ -41,14 +34,14 @@ use crate::{MimallocArena, alloc_result, mimalloc}; /// cross threads with a stack pointer inside it). /// /// `N` guidance: default to **1024** for "format a small string / build a -/// short list" (modal Zig choice; well under the 8 KB Windows `__chkstk` +/// short list" (well under the 8 KB Windows `__chkstk` /// threshold). **4096** for path-ish buffers. Cap at **16 KB** — anything /// larger should go straight to `MimallocArena`/`Global`. #[repr(C)] // keep `buf` at a fixed offset; `align_of::() == align_of::().max(word)` pub struct StackFallback { /// Bump cursor into `buf`. `Cell` so `Allocator::allocate(&self)` can advance it. cur: Cell, - /// `get_called` debug guard (Zig heap.zig:398) — trips on second `get()` + /// Debug guard — trips on second `get()` /// without an intervening `reset()`, catching the "two Vecs share one /// buffer" footgun. #[cfg(debug_assertions)] @@ -60,7 +53,7 @@ pub struct StackFallback { } impl StackFallback { - /// Zig: `std.heap.stackFallback(N, fallback)`. `const` — `MaybeUninit: + /// `const` — `MaybeUninit: /// Copy`, so `[MaybeUninit::uninit(); N]` needs no inline-const; /// `Cell::new`/`UnsafeCell::new` are `const fn`. #[inline] @@ -74,9 +67,9 @@ impl StackFallback { } } - /// Zig: `StackFallbackAllocator.get()` — reset the bump region and return - /// the allocator handle. Debug-asserts single call (heap.zig:404). In Rust - /// the "handle" is just `&self` (blanket `impl Allocator for &Self` below), + /// Reset the bump region and return the allocator handle. + /// Debug-asserts single call. The "handle" is just `&self` + /// (blanket `impl Allocator for &Self` below), /// so callers may equivalently write `Vec::new_in(&sf)` directly and skip /// this. #[inline] @@ -89,7 +82,6 @@ impl StackFallback { self } - /// Zig: `fixed_buffer_allocator.reset()` (FixedBufferAllocator.zig:145). /// `&mut self` proves no live borrows into `buf`. #[inline] pub fn reset(&mut self) { @@ -109,7 +101,6 @@ impl StackFallback { self.buf.get().cast::() } - /// Zig: `FixedBufferAllocator.ownsPtr` (FixedBufferAllocator.zig:46). /// Integer-address compare (NOT `offset_from` — `p` may belong to /// `fallback`, a different allocation). #[inline] @@ -119,14 +110,12 @@ impl StackFallback { q >= base && q < base.wrapping_add(N) } - /// Zig: `FixedBufferAllocator.isLastAllocation` (FixedBufferAllocator.zig:58). #[inline(always)] fn is_last(&self, p: *const u8, len: usize) -> bool { p.addr().wrapping_add(len) == self.buf_base().addr().wrapping_add(self.cur.get()) } - /// Zig: `FixedBufferAllocator.alloc` (FixedBufferAllocator.zig:62) — align - /// `cur` up, carve `len` bytes, or `None` if it doesn't fit. + /// Align `cur` up, carve `len` bytes, or `None` if it doesn't fit. #[inline] fn bump(&self, layout: Layout) -> Option> { let base = self.buf_base().addr(); @@ -149,7 +138,7 @@ impl StackFallback { } /// `bumpalo::Bump::alloc` parity — move `val` into the bump front (or the - /// fallback on overflow). Aborts on OOM, matching Zig's `catch unreachable`. + /// fallback on overflow). Aborts on OOM. #[inline] #[allow(clippy::mut_from_ref)] pub fn alloc(&self, val: T) -> &mut T { @@ -167,8 +156,7 @@ impl StackFallback { } impl StackFallback { - /// `std.heap.stackFallback(N, bun.default_allocator)` — the 90 % case - /// (15 of 20 Zig callsites pass `default_allocator`/`bun.default_allocator`). + /// `StackFallback` backed by the global allocator — the common case. #[inline] pub const fn with_global() -> Self { Self::new(std::alloc::Global) @@ -177,8 +165,7 @@ impl StackFallback { // Implemented on `&Self` (NOT `Self`) so the buffer cannot be moved into an // owning container by value (`Box::new_in(x, sf)` would dangle). Mirrors -// `unsafe impl Allocator for &MimallocArena` (MimallocArena.rs:652) and Zig's -// `get()`-returns-borrowing-vtable shape. +// `unsafe impl Allocator for &MimallocArena` (MimallocArena.rs:652). // // SAFETY: // - `allocate`: returns either (a) a slice of `self.buf` aligned to @@ -193,11 +180,10 @@ impl StackFallback { // - `grow`/`shrink`: see per-method notes; old block is always either left // valid (returned same ptr) or fully copied-then-deallocated before return. // `StackFallback` is `!Sync` (via `Cell`/`UnsafeCell`), enforcing single- -// thread use of the cursor — same constraint as Zig's SFA. +// thread use of the cursor. unsafe impl Allocator for &StackFallback { #[inline] fn allocate(&self, layout: Layout) -> Result, AllocError> { - // Zig heap.zig:432 — try fixed buffer, else fallback. if let Some(p) = self.bump(layout) { return Ok(NonNull::slice_from_raw_parts(p, layout.size())); } @@ -207,7 +193,7 @@ unsafe impl Allocator for &StackFallback { #[inline] unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { if self.owns(ptr.as_ptr()) { - // Zig FixedBufferAllocator.free:125 — rewind only the last alloc. + // Rewind only the last alloc. if self.is_last(ptr.as_ptr(), layout.size()) { self.cur.set(self.cur.get() - layout.size()); } @@ -225,7 +211,7 @@ unsafe impl Allocator for &StackFallback { new: Layout, ) -> Result, AllocError> { if self.owns(ptr.as_ptr()) { - // Zig FixedBufferAllocator.resize:86-101 — last-alloc bump-in-place. + // Last-alloc bump-in-place. if self.is_last(ptr.as_ptr(), old.size()) { let add = new.size() - old.size(); if self.cur.get() + add <= N { @@ -234,7 +220,6 @@ unsafe impl Allocator for &StackFallback { } } // Spill: alloc new (stack-or-fallback), memcpy, free old. - // Mirrors Zig `Allocator.realloc` slow path after `remap` returns null. let newp = self.allocate(new)?; // SAFETY: `newp` is fresh ≥`new.size()` bytes; `old.size()` bytes at // `ptr` are init per `grow` contract; `old.size() <= new.size()`. If @@ -260,7 +245,7 @@ unsafe impl Allocator for &StackFallback { new: Layout, ) -> Result, AllocError> { if self.owns(ptr.as_ptr()) { - // FixedBufferAllocator.resize:86-94 — last-alloc shrink rewinds + // Last-alloc shrink rewinds // `cur`; non-last shrink keeps the slot (already holds ≥new bytes // at ≥old.align()). if self.is_last(ptr.as_ptr(), old.size()) { @@ -395,9 +380,6 @@ unsafe impl Allocator for ArenaPtr { } } -// ported from: vendor/zig/lib/std/heap.zig (stackFallback / StackFallbackAllocator) -// ported from: vendor/zig/lib/std/heap/FixedBufferAllocator.zig (inlined) - #[cfg(test)] mod tests { use super::*; diff --git a/src/bun_bin/lib.rs b/src/bun_bin/lib.rs index 9ec80f18f1d..bb441925155 100644 --- a/src/bun_bin/lib.rs +++ b/src/bun_bin/lib.rs @@ -1,11 +1,11 @@ //! `libbun_rust.a` — the Rust-port staticlib. //! //! Built by `cargo build -p bun_bin` (emitted from `scripts/build/rust.ts`) -//! and linked into the final `bun-debug` executable by ninja's link step, -//! occupying the slot `bun-zig.o` used to. The clang++ driver supplies the +//! and linked into the final `bun-debug` executable by ninja's link step. +//! The clang++ driver supplies the //! C runtime startup (`_start` → `main`); `main` below is the process entry. //! -//! Init order mirrors `src/main.zig`: +//! Init order: //! 1. crash handler / signal masks //! 2. allocator wiring (mimalloc as `#[global_allocator]`) //! 3. argv / start-time capture @@ -45,8 +45,7 @@ use bun_core::Global; use bun_core::StackCheck; use bun_core::output; -/// mimalloc as the process allocator — matches Zig's `bun.default_allocator` -/// and the `uv_replace_allocator(mi_*)` call in `main.zig` on Windows. +/// mimalloc as the process allocator. #[cfg(not(bun_asan))] #[global_allocator] static ALLOC: bun_alloc::Mimalloc = bun_alloc::Mimalloc; @@ -76,7 +75,7 @@ pub extern "C" fn __asan_default_options() -> *const core::ffi::c_char { // detect_leaks=0: off by default (Linux defaults it on); CI opts in via // ASAN_OPTIONS with a suppressions file. // - // PORT NOTE: matches `src/safety/asan.zig` exactly. Do NOT add `symbolize=0` + // Do NOT add `symbolize=0` // here — LSAN's function-name suppression matching (`test/leaksan.supp`) // requires symbolized stacks; with symbolization disabled every entry like // `leak:uws_create_app` silently stops matching and CI reports the @@ -86,16 +85,12 @@ pub extern "C" fn __asan_default_options() -> *const core::ffi::c_char { } /// LSAN built-in suppressions, merged with whatever `LSAN_OPTIONS=suppressions=` -/// the CI runner passes (`test/leaksan.supp`). That file's entries were written -/// against Zig's symbol mangling (`runtime.node.zlib.NativeZlib.Context.init`, -/// `jsc.web_worker.create`, …); LSAN matches by *substring on a symbolized -/// frame*, so after the Rust port renamed every frame to `bun_::` -/// none of the Zig-named rules fire and CI reports the same intentionally- -/// leaked-at-exit allocations the suppressions were authored for. Baking the -/// Rust spellings into the binary keeps `leaksan.supp` as the C/C++/JSC list -/// and lets the Rust list ride with the code that produces the symbols. +/// the CI runner passes (`test/leaksan.supp`). LSAN matches by *substring on a +/// symbolized frame*; baking the Rust symbol spellings into the binary keeps +/// `leaksan.supp` as the C/C++/JSC list and lets the Rust list ride with the +/// code that produces the symbols. /// -/// Also covers one Rust-only false positive that has no Zig analogue: +/// Also covers one Rust-only false positive: /// `std::thread::Builder::spawn` allocates an `Arc` that the /// detached thread holds in TLS for its lifetime; LSAN does not scan other /// threads' TLS roots at exit, so every long-lived detached thread (HTTP @@ -141,7 +136,7 @@ pub extern "C" fn __lsan_default_suppressions() -> *const core::ffi::c_char { } /// Process entry point. `extern "C"` so the linker resolves crt1.o's -/// undefined `main` against this symbol — same role as Zig's `pub fn main`. +/// undefined `main` against this symbol. /// /// `argc`/`argv` are forwarded to `bun_core::init_argv` immediately: on /// glibc/macOS/Windows libstd also captures them via a `.init_array` hook / @@ -149,8 +144,7 @@ pub extern "C" fn __lsan_default_suppressions() -> *const core::ffi::c_char { /// receives no arguments (musl's `__libc_start_main` does not pass /// `(argc,argv,envp)` to constructors), so `std::env::args_os()` returns /// empty and the binary would see argc=0. Capturing the C-runtime-provided -/// pair here is the only portable source — same contract as Zig's -/// `bun.initArgv` wrapping `std.os.argv`. +/// pair here is the only portable source. /// /// # Safety /// `argv` must point to `argc` valid NUL-terminated C strings that live for @@ -166,7 +160,7 @@ pub unsafe extern "C" fn main(argc: c_int, argv: *const *const c_char) -> c_int // 1. Crash handler first so anything below gets a usable trace. bun_crash_handler::init(); - // SIGPIPE/SIGXFSZ → SIG_IGN, like main.zig's posix block. + // SIGPIPE/SIGXFSZ → SIG_IGN. // SAFETY: `SIGPIPE`/`SIGXFSZ` are valid signal numbers and `SIG_IGN` is a // valid disposition; called once on the main thread before any other // thread is spawned, so there is no concurrent sigaction. @@ -176,10 +170,9 @@ pub unsafe extern "C" fn main(argc: c_int, argv: *const *const c_char) -> c_int libc::signal(libc::SIGXFSZ, libc::SIG_IGN); } - // main.zig:40-50 — Windows-only startup. Must run BEFORE the first libuv + // Windows-only startup. Must run BEFORE the first libuv // call (uv allocator) and before anything reads `Bun.env`/`process.env` - // (env conversion). The Zig spec orders these between sigaction and - // `start_time`/`initArgv`. + // (env conversion). #[cfg(windows)] { // SAFETY: mimalloc fns match the libuv allocator signatures; called @@ -207,7 +200,7 @@ pub unsafe extern "C" fn main(argc: c_int, argv: *const *const c_char) -> c_int output::stdio::init(); let _flush = output::flush_guard(); - // main.zig: `bun_warn_avx_missing(...)` — x86_64 + SIMD + posix only. + // `bun_warn_avx_missing(...)` — x86_64 + SIMD + posix only. #[cfg(all(target_arch = "x86_64", unix))] if bun_core::Environment::ENABLE_SIMD { unsafe extern "C" { @@ -229,8 +222,7 @@ pub unsafe extern "C" fn main(argc: c_int, argv: *const *const c_char) -> c_int // 6. Push high-tier allocator vtable addresses into the // `bun_safety::alloc::has_ptr` registry so debug-only allocator-mismatch - // checks can identify `LinuxMemFdAllocator`/`MimallocArena` instances - // (Zig: inline `isInstance` chain in `safety/alloc.zig:hasPtr`). + // checks can identify `LinuxMemFdAllocator`/`MimallocArena` instances. // Runs once; reads are lock-free Relaxed. bun_runtime::allocators::register_safety_vtables(); diff --git a/src/bun_bin/phase_c_exports.rs b/src/bun_bin/phase_c_exports.rs index 32c96970307..42151cd22b2 100644 --- a/src/bun_bin/phase_c_exports.rs +++ b/src/bun_bin/phase_c_exports.rs @@ -1,7 +1,7 @@ //! PHASE-C link bridge — **transient**, not a permanent grab-bag. //! -//! Every symbol that used to be stubbed here now has a real home (the `.rs` -//! sibling of the Zig `export fn`) inside `bun_jsc` / `bun_runtime` / +//! Every symbol that used to be stubbed here now has a real home inside +//! `bun_jsc` / `bun_runtime` / //! `bun_http_jsc` / `bun_bundler_jsc`. As of this revision `bun_runtime` (and //! transitively `bun_jsc`) is a real dependency of this binary crate, so any //! `#[no_mangle]` definition that compiles in either of those crates is now @@ -10,7 +10,7 @@ //! What remains is the small set of symbols that are either (a) defined here //! directly because this is their proper home, (b) safe-default placeholders //! whose real body still depends on a gated crate, or (c) genuinely -//! unimplemented anywhere (no Zig `export fn`, no C++ body) — those are +//! unimplemented anywhere (no C++ body) — those are //! `unreachable!` so a stray call is loud rather than silent garbage. //! //! `__wrap_gettid` is NOT here — it lives in `bun_core` (its proper, @@ -36,7 +36,7 @@ type JSValue = i64; // JSC::EncodedJSValue type VirtualMachine = c_void; // ════════════════════════════════════════════════════════════════════════════ -// Exported variables (Zig: `export var` / `@export(&var, …)`) +// Exported variables // ════════════════════════════════════════════════════════════════════════════ // REAL: now provided by bun_jsc (src/jsc/VirtualMachine.rs). @@ -61,7 +61,7 @@ type VirtualMachine = c_void; // Real-body exports (no gated-crate dependency) // ════════════════════════════════════════════════════════════════════════════ -// PHASE-C: C++ callback — Zig: `pub export fn Bun__panic(msg, len) noreturn` +// PHASE-C: C++ callback // REAL: src/main.rs (binary-level export; defined here directly) #[unsafe(no_mangle)] pub(crate) extern "C" fn Bun__panic(msg: *const u8, len: usize) -> ! { @@ -196,7 +196,7 @@ pub(crate) extern "C" fn Bun__VM__scriptExecutionStatus(_vm: *const VirtualMachi // Blob__getSize // Bun__Blob__getSizeForBindings -// .classes.ts hooks (build/debug/codegen/ZigGeneratedClasses.zig) +// .classes.ts hooks // REAL: now provided by bun_runtime::generated_classes // (build/debug/codegen/generated_classes.rs via generateRust()). // Blob__estimatedSize @@ -251,7 +251,7 @@ pub(crate) extern "C" fn Bun__VM__scriptExecutionStatus(_vm: *const VirtualMachi // zig__renderDiff // ════════════════════════════════════════════════════════════════════════════ -// Genuinely unimplemented — no Zig `export fn`, no C++ body. Kept so the +// Genuinely unimplemented — no C++ body. Kept so the // extern ref in the rlib resolves; loud crash if ever called. // ════════════════════════════════════════════════════════════════════════════ @@ -266,7 +266,7 @@ pub(crate) extern "C" fn JSC__JSValue__parseJSON( ) } -// Imported by bun_jsc/bun_sys_jsc as extern but no provider in C++ or Zig. +// Imported by bun_jsc/bun_sys_jsc as extern but no provider in C++. #[unsafe(no_mangle)] pub(crate) extern "C" fn BunString__toErrorInstance( _this: *const c_void, @@ -276,7 +276,7 @@ pub(crate) extern "C" fn BunString__toErrorInstance( } // Declared `extern` in InspectorLifecycleAgent.cpp:47-48 but never defined in -// C++ nor Zig (Debugger.zig declares it `extern "c"` too). The agent's +// C++. The agent's // preventExit/stopPreventingExit protocol commands are no-ops in the inspector // build today. #[unsafe(no_mangle)] diff --git a/src/bun_core/Global.rs b/src/bun_core/Global.rs index 1352feac513..e2743eb5fe2 100644 --- a/src/bun_core/Global.rs +++ b/src/bun_core/Global.rs @@ -5,9 +5,9 @@ use core::sync::atomic::{AtomicBool, Ordering}; use const_format::{concatcp, formatcp}; -use crate::env; // @import("./env.zig") +use crate::env; use crate::env::version_string; -use crate::output as Output; // @import("./output.zig") +use crate::output as Output; use crate::USE_MIMALLOC; #[cfg(debug_assertions)] @@ -61,14 +61,14 @@ pub static WINDOWS_SEGFAULT_HANDLE: core::sync::atomic::AtomicPtrcore). // ────────────────────────────────────────────────────────────────────────── -/// Zig: `std.builtin.StackTrace` — slice of return addresses + cursor. +/// Slice of return addresses + cursor. #[derive(Clone, Copy)] pub struct StackTrace<'a> { pub index: usize, pub instruction_addresses: &'a [usize], } -/// Zig: src/crash_handler/crash_handler.zig::StoredTrace — fixed 31-frame buffer. +/// Fixed 31-frame stack-trace buffer. #[derive(Clone, Copy)] pub struct StoredTrace { pub data: [usize; 31], @@ -109,7 +109,7 @@ impl StoredTrace { }; let n = crate::capture_stack_trace(begin, &mut stored.data); stored.index = n; - // Trim trailing nulls (matches Zig loop). + // Trim trailing nulls. for (i, &addr) in stored.data[..n].iter().enumerate() { if addr == 0 { stored.index = i; @@ -135,7 +135,6 @@ impl StoredTrace { } } -/// Zig: `WriteStackTraceLimits`. #[derive(Copy, Clone, Debug)] pub struct DumpStackTraceOptions { pub frame_count: usize, @@ -155,13 +154,13 @@ impl Default for DumpStackTraceOptions { } } } -/// Zig-spec name (`crash_handler.WriteStackTraceLimits`); also re-exported from `bun_crash_handler`. +/// Alias for `DumpStackTraceOptions`; also re-exported from `bun_crash_handler`. pub type WriteStackTraceLimits = DumpStackTraceOptions; -/// Zig: `crash_handler.dumpStackTrace`. T0 fallback prints raw return +/// T0 fallback prints raw return /// addresses — **no symbolication** (the `backtrace` crate is not a T0 dep, /// and `std::backtrace` cannot resolve a stored address list). This is a -/// deliberate debug-UX downgrade vs the Zig spec for the *stored*-trace path +/// deliberate debug-UX downgrade for the *stored*-trace path /// (ref_count leak reports); the *current*-stack path below /// uses `std::backtrace` and stays symbolicated. Crash-report paths that need /// llvm-symbolizer / pdb-addr2line call `bun_crash_handler::dump_stack_trace` @@ -187,9 +186,8 @@ pub fn dump_stack_trace(trace: &StackTrace<'_>, limits: DumpStackTraceOptions) { } /// Capture and dump the current call stack. Dispatches to -/// `bun_crash_handler::dump_current_stack_trace` (matching Zig -/// `fd.zig`/`ref_count.zig` which call `bun.crash_handler.dumpCurrentStackTrace` -/// directly). The upward call is routed through a link-time `extern "Rust"` +/// `bun_crash_handler::dump_current_stack_trace`. +/// The upward call is routed through a link-time `extern "Rust"` /// symbol defined by `bun_crash_handler` so the function pointer lives in /// read-only `.text` instead of a writable `AtomicPtr` slot — memory corruption /// cannot redirect it. Under `cfg(test)` (this crate's standalone test binary @@ -217,7 +215,7 @@ pub fn dump_current_stack_trace(first_address: Option, limits: DumpStackT } // ─── panicking state (from bun_crash_handler) ───────────────────────────── -// Zig: `var panicking = std.atomic.Value(u8).init(0)`. Owned here so the +// Owned here so the // crash handler crate writes to `bun_core::PANICKING` (forward dep, allowed). pub static PANICKING: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0); @@ -226,20 +224,18 @@ pub fn is_panicking() -> bool { PANICKING.load(Ordering::Relaxed) > 0 } -/// Zig: crash_handler.sleepForeverIfAnotherThreadIsCrashing. pub fn sleep_forever_if_another_thread_is_crashing() { if PANICKING.load(Ordering::Acquire) > 0 { - // Sleep forever without hammering the CPU. Zig used `bun.Futex.waitForever`; - // `std::thread::park()` is the moral equivalent (never unparked). + // Sleep forever without hammering the CPU — + // `std::thread::park()` is never unparked. loop { std::thread::park(); } } } -// ─── SignalCode — single source of truth (Zig: src/sys/SignalCode.zig) ──── -// Zig declares ONE `enum(u8) { …, _ }` and derives the name table via -// `@tagName` + `ComptimeEnumMap`. Rust has no enum reflection, so the 31 +// ─── SignalCode — single source of truth ────────────────────────────────── +// Rust has no enum reflection, so the 31 // (name,number) pairs live in ONE X-macro below; every consumer — the closed // enum here, the open newtype in `bun_sys`, `SIGNAL_NAMES`, `from_raw`, // `from_name` — is generated from it. Never re-spell a signal pair elsewhere. @@ -259,7 +255,7 @@ macro_rules! for_each_signal { macro_rules! __define_signal_code { ($($name:ident = $n:literal),* $(,)?) => { - /// `@tagName` surrogate. Index = POSIX signal number; `[0]` is "" sentinel + /// Signal name table. Index = POSIX signal number; `[0]` is "" sentinel /// (callers range-check `1..=31`). Generated from `for_each_signal!`. pub const SIGNAL_NAMES: [&str; 32] = ["", $(stringify!($name),)*]; @@ -273,18 +269,18 @@ macro_rules! __define_signal_code { impl SignalCode { pub const DEFAULT: SignalCode = SignalCode::SIGTERM; - /// Zig `@enumFromInt` for the closed `1..=31` range; `None` for `0` - /// or the open enum's `_` tail. + /// Raw signal number → variant for the closed `1..=31` range; + /// `None` for `0` or anything outside it. #[inline] pub const fn from_raw(n: u8) -> Option { match n { $($n => Some(Self::$name),)* _ => None } } - /// Zig `@tagName` — every variant is named (enum is exhaustive). + /// Signal name — every variant is named (enum is exhaustive). #[inline] pub fn name(self) -> &'static str { SIGNAL_NAMES[self as u8 as usize] } - /// Zig `bun.ComptimeEnumMap(SignalCode).get` — name-bytes → variant. + /// Name-bytes → variant. /// 31-arm match; the optimizer turns it into a small string switch. #[inline] pub fn from_name(s: &[u8]) -> Option { @@ -296,8 +292,7 @@ macro_rules! __define_signal_code { for_each_signal!(__define_signal_code); // ─── analytics::features (MOVE_DOWN from bun_analytics) ─────────────────── -// Zig: src/analytics/analytics.zig::Features — bag of `pub var X: usize`. -// Port as atomic counters so cross-thread `.fetch_add` is sound. Only the +// Atomic counters so cross-thread `.fetch_add` is sound. Only the // counters are tier-0; `builtin_modules` (EnumSet over jsc HardcodedModule) // stays in bun_analytics (depends on tier-6). pub mod features { @@ -355,7 +350,7 @@ pub mod features { LOCKFILE_MIGRATION_FROM_PACKAGE_LOCK.fetch_add(1, core::sync::atomic::Ordering::Relaxed); } /// jsc crate calls `bun_core::analytics::Features::jsc_inc()` from - /// `initialize()` (spec jsc.zig:251 `bun.analytics.Features.jsc += 1`). + /// `initialize()`. #[inline] pub fn jsc_inc() { JSC.fetch_add(1, core::sync::atomic::Ordering::Relaxed); @@ -369,7 +364,6 @@ pub mod analytics { } // ─── mark_binding! (MOVE_DOWN from bun_jsc, for aio/event_loop/http_jsc) ── -// Zig: jsc.zig::markBinding(@src()) → scoped_log!(JSC, "{fn} ({file}:{line})"). // Pure logging; no jsc dep. Declares the JSC scope on first use. crate::declare_scope!(JSC, hidden); #[macro_export] @@ -378,8 +372,7 @@ macro_rules! mark_binding { $crate::mark_binding!(::core::panic::Location::caller().file()) }; ($fn_name:expr) => { - // Zig: `Output.scoped(.JSC, .hidden)` (jsc.zig:169) — opt-in via - // BUN_DEBUG_JSC=1. The `JSC` scope is owned by bun_core. Gate on + // Opt-in via BUN_DEBUG_JSC=1. The `JSC` scope is owned by bun_core. Gate on // `debug_assertions` (== `Environment::ENABLE_LOGS`) — never on a Cargo // feature, since `cfg!(feature = ..)` is resolved against the *calling* // crate and would warn (or silently no-op) in crates without it. @@ -398,7 +391,7 @@ pub static JSC_SCOPE: crate::output::ScopedLogger = crate::output::ScopedLogger::new("JSC", crate::output::Visibility::Hidden); // ─── debug_flags (MOVE_DOWN from bun_cli, for bun_resolver) ─────────────── -// Zig: src/cli/cli.zig::debug_flags — debug-build-only breakpoint matchers. +// Debug-build-only breakpoint matchers. pub mod debug_flags { #[cfg(debug_assertions)] pub(crate) static RESOLVE_BREAKPOINTS: crate::Once<&'static [&'static [u8]]> = @@ -444,8 +437,7 @@ pub const package_json_version: &str = if cfg!(debug_assertions) { }; /// `package_json_version` with a trailing `\n` baked in, so -/// `print_version_and_exit` is a single `write_all` (one syscall) — matches -/// Zig's `writeAll(version ++ "\n")`. +/// `print_version_and_exit` is a single `write_all` (one syscall). pub const package_json_version_nl: &str = concatcp!(package_json_version, "\n"); /// This is used for `bun` without any arguments, it `package_json_version` but with canary if it is a canary build. @@ -458,9 +450,6 @@ pub const package_json_version_with_canary: &str = if cfg!(debug_assertions) { version_string }; -// PORT NOTE: Zig sliced `git_sha[0..@min(len, 8)]` inline; we use the -// pre-computed `GIT_SHA_SHORT` (same value) since const slicing of a const -// `&str` by a runtime-ish min() is awkward in stable Rust. /// The version and a short hash in parenthesis. pub const package_json_version_with_sha: &str = if env::GIT_SHA.is_empty() { package_json_version @@ -541,11 +530,12 @@ pub const arch_name: &str = if cfg!(target_arch = "x86_64") { // ────────────────────────────────────────────────────────────────────────── pub fn set_thread_name(name: &ZStr) { - // Zig `Environment.isLinux` is true on Android (linux OS + android ABI); - // Rust's `target_os = "linux"` is not, so include android explicitly. + // Android needs the same path as Linux, but Rust's `target_os = "linux"` + // excludes it — include android explicitly. #[cfg(any(target_os = "linux", target_os = "android"))] { - // SAFETY: PR_SET_NAME takes a NUL-terminated byte string; `name` is `[:0]const u8`. + // SAFETY: PR_SET_NAME takes a NUL-terminated byte string; `&ZStr` guarantees + // `ptr[len] == 0`. unsafe { let _ = libc::prctl(libc::PR_SET_NAME, name.as_ptr() as usize); } @@ -581,8 +571,8 @@ pub fn set_thread_name(name: &ZStr) { // no memory-safety preconditions, so the call site needs no `unsafe` block. pub type ExitFn = extern "C" fn(); -// PORT NOTE: Zig used an unsynchronized global `ArrayListUnmanaged`. Registration -// can happen from any thread (FFI `Bun__atexit`), so guard with a Mutex. +// Registration can happen from any thread (FFI `Bun__atexit`), so this is +// guarded with a Mutex. static ON_EXIT_CALLBACKS: crate::Mutex> = crate::Mutex::new(Vec::new()); #[unsafe(no_mangle)] @@ -597,9 +587,9 @@ pub fn add_exit_callback(function: ExitFn) { Bun__atexit(function); } -/// Callbacks `Bun__onExit` runs BEFORE `run_exit_callbacks()`. Spec -/// `Global.zig:220` hard-codes `bun.jsc.Node.FSEvents.closeAndWait()` ahead of -/// `runExitCallbacks()`; that crate sits above us, so it pushes its callback +/// Callbacks `Bun__onExit` runs BEFORE `run_exit_callbacks()`. FSEvents must +/// close-and-wait ahead of the generic exit-callback list; that crate sits +/// above us, so it pushes its callback /// here at first-loop creation (data moved down — same `Vec` shape as /// `ON_EXIT_CALLBACKS`, no fn-ptr type-erase). static PRE_EXIT_CALLBACKS: crate::Mutex> = crate::Mutex::new(Vec::new()); @@ -650,7 +640,6 @@ unsafe extern "C" { /// Flushes stdout and stderr (in exit/quick_exit callback) and exits with the given code. pub fn exit(code: u32) -> ! { IS_EXITING.store(true, Ordering::Relaxed); - // _ = @atomicRmw(usize, &bun.analytics.Features.exited, .Add, 1, .monotonic); // MOVE_DOWN: bun_analytics::features → bun_core (move-in pass). crate::features::EXITED.fetch_add(1, Ordering::Relaxed); @@ -689,10 +678,9 @@ pub fn raise_ignoring_panic_handler(sig: crate::SignalCode) -> ! { raise_ignoring_panic_handler_raw(sig as c_int) } -/// Re-raise `sig` (raw `c_int`) after restoring TTY/crash state. Zig's -/// `SignalCode` is a *non-exhaustive* `enum(u8)`, so callers may forward any -/// signal byte (incl. Linux RT signals 32..=64) that has no `crate::SignalCode` -/// discriminant. Mirrors `raiseIgnoringPanicHandler(@enumFromInt(sig))`. +/// Re-raise `sig` (raw `c_int`) after restoring TTY/crash state. Callers may +/// forward any signal byte (incl. Linux RT signals 32..=64) that has no +/// `crate::SignalCode` discriminant. pub fn raise_ignoring_panic_handler_raw(sig: c_int) -> ! { Output::flush(); Output::source::stdio::restore(); @@ -700,8 +688,7 @@ pub fn raise_ignoring_panic_handler_raw(sig: c_int) -> ! { // Clear the crash handler's segfault hooks so the re-raised signal goes to // SIG_DFL instead of recursing into the panic handler. Storage moved down // from `bun_crash_handler` — it sets `CRASH_HANDLER_INSTALLED` on init and - // we do the libc reset ourselves (no fn-ptr hook). Mirrors - // `crash_handler.zig::resetSegfaultHandler`: skip when ASAN owns the + // we do the libc reset ourselves (no fn-ptr hook). Skip when ASAN owns the // signals (we never installed over them); on Windows remove the VEH. #[cfg(unix)] if CRASH_HANDLER_INSTALLED.load(Ordering::Relaxed) && !crate::env::ENABLE_ASAN { @@ -796,8 +783,7 @@ pub(crate) static Bun__userAgent: SyncCStr = SyncCStr(concatcp!(user_agent, "\0").as_ptr().cast::()); /// Prevent the linker from dead-code-eliminating `#[no_mangle]` symbols that are -/// only ever called from C/C++ (so rustc sees no Rust caller). Port of Zig's -/// `std.mem.doNotOptimizeAway` pattern (Global.zig:224). Expands to one +/// only ever called from C/C++ (so rustc sees no Rust caller). Expands to one /// `core::hint::black_box(f as *const ())` per path — purely a side-effect, so /// invoke inside a `fix_dead_code_elimination()` fn wired from `run_command`. #[macro_export] @@ -809,9 +795,8 @@ macro_rules! keep_symbols { #[unsafe(no_mangle)] pub(crate) extern "C" fn Bun__onExit() { - // `bun.jsc.Node.FSEvents.closeAndWait()` (spec `Global.zig:220`) — runs - // BEFORE the generic exit-callback list, matching Zig ordering. fs_events - // pushes into `PRE_EXIT_CALLBACKS` on first loop create. + // FSEvents close-and-wait runs BEFORE the generic exit-callback list. + // fs_events pushes into `PRE_EXIT_CALLBACKS` on first loop create. let pre: Vec = core::mem::take(&mut *PRE_EXIT_CALLBACKS.lock()); for callback in &pre { callback(); @@ -822,5 +807,3 @@ pub(crate) extern "C" fn Bun__onExit() { Output::source::stdio::restore(); } - -// ported from: src/bun_core/Global.zig diff --git a/src/bun_core/Progress.rs b/src/bun_core/Progress.rs index 51dd8d71468..10ec264abe5 100644 --- a/src/bun_core/Progress.rs +++ b/src/bun_core/Progress.rs @@ -1,11 +1,8 @@ -//! This is a snapshot of the Zig std.Progress API before it's rewrite in 0.13 -//! We use this API for the progress in Bun install and some other places. +//! Terminal progress indicator used for Bun install and some other places. //! //! TODO: It would be worth considering using our own progress indicator for //! Bun install, as this bar only shows the most recent action. //! -//! https://github.com/ziglang/zig/blob/0.12.0/lib/std/Progress.zig -//! //! This API is non-allocating, non-fallible, and thread-safe. //! The tradeoff is that users of this API must provide the storage //! for each `Progress.Node`. @@ -69,7 +66,7 @@ pub use crate::output::File; use crate::output::output_sink; impl File { - /// `std.io.tty.supportsAnsiEscapeCodes()` — on unix this is `isatty()`; + /// Whether ANSI escape codes are supported — on unix this is `isatty()`; /// on Windows it requires `ENABLE_VIRTUAL_TERMINAL_PROCESSING` (set by /// `Output.Source.init`). We route through the sink so the platform check /// lives in `bun_sys`. @@ -77,7 +74,7 @@ impl File { pub fn supports_ansi_escape_codes(self) -> bool { #[cfg(windows)] { - // Zig std.fs.File.supportsAnsiEscapeCodes(): query the live console + // Query the live console // mode for ENABLE_VIRTUAL_TERMINAL_PROCESSING — a *capability* // check. Do NOT proxy through ENABLE_ANSI_COLORS_STDERR: that is a // color-*preference* flag (NO_COLOR/FORCE_COLOR/tty) and never @@ -166,23 +163,18 @@ pub struct Progress { impl Default for Progress { fn default() -> Self { Self { - // Zig: `= undefined` — overwritten in `start()` terminal: None, is_windows_terminal: false, supports_ansi_escape_codes: false, dont_print_on_dumb: false, - // Zig: `= undefined` — overwritten in `start()` root: Node::default(), timer: None, - // Zig: `= undefined` prev_refresh_timestamp: 0, - // Zig: `= undefined` output_buffer: [0; 100], refresh_rate_ns: 50 * NS_PER_MS, initial_delay_ns: 500 * NS_PER_MS, done: true, update_mutex: Mutex::new(()), - // Zig: `= undefined` columns_written: 0, } } @@ -201,8 +193,10 @@ pub enum Unit { pub struct Node { pub context: *mut Progress, pub parent: *mut Node, - // TODO(port): lifetime — caller-borrowed slice, Zig is non-allocating; using - // 'static here as a placeholder (callers in install/ pass string literals). + // The non-allocating design means `Node` cannot own the bytes. `'static` + // is the chosen simplification because all current callers (install/, + // cli/) pass string literals; the alternative would be threading a + // lifetime through `Node`/`Progress`. pub name: &'static [u8], pub unit: Unit, /// Must be handled atomically to be thread-safe. @@ -263,9 +257,7 @@ impl Node { /// Create a new child progress node. Thread-safe. /// Call `Node.end` when done. - /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this - /// API to set `self.parent.recently_updated_child` with the return value. - /// Until that is fixed you probably want to call `activate` on the return value. + /// You probably want to call `activate` on the return value. /// Passing 0 for `estimated_total_items` means unknown. pub fn start(&mut self, name: &'static [u8], estimated_total_items: usize) -> Node { Node { @@ -312,7 +304,7 @@ impl Node { } parent.complete_one(); } else { - // PORT NOTE: reshaped for borrowck — guard borrows context.update_mutex; + // Reshaped for borrowck — guard borrows context.update_mutex; // we capture a raw ptr first so the &mut access goes through *mut. let ctx_ptr = std::ptr::from_mut::(context); let _g = context.update_mutex.lock(); @@ -368,9 +360,6 @@ impl Node { /// Thread-safe. pub fn set_unit(&mut self, unit: Unit) { - // TODO(port): Zig signature was `unit: []const u8` assigned to an enum field — - // dead code in Zig (lazy compilation never type-checked it). Ported with the - // enum type to keep it well-typed; revisit if any caller appears. let ctx_ptr = self.context_ptr(); // SAFETY: see `context_ptr` — `&mut Progress` would alias the node tree. let progress = unsafe { &mut *ctx_ptr }; @@ -412,12 +401,8 @@ impl Node { impl Progress { /// Create a new progress node. /// Call `Node.end` when done. - /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this - /// API to return Progress rather than accept it as a parameter. /// `estimated_total_items` value of 0 means unknown. pub fn start(&mut self, name: &'static [u8], estimated_total_items: usize) -> &mut Node { - // TODO(port): std.fs.File.stderr() / supportsAnsiEscapeCodes() / isTty() — - // map to bun_sys::File equivalents. let stderr = File::stderr(); self.terminal = None; if stderr.supports_ansi_escape_codes() { @@ -446,7 +431,6 @@ impl Progress { }; self.columns_written = 0; self.prev_refresh_timestamp = 0; - // Zig: std.time.Timer.start() catch null — Instant::now() is infallible. self.timer = Some(Instant::now()); self.done = false; &mut self.root @@ -454,9 +438,9 @@ impl Progress { /// Updates the terminal if enough time has passed since last update. Thread-safe. pub fn maybe_refresh(&mut self) { - // PORT NOTE: reshaped for borrowck — Instant is Copy, captured by value. + // Reshaped for borrowck — Instant is Copy, captured by value. if let Some(timer) = self.timer { - // PORT NOTE: reshaped for borrowck — capture *mut self before the + // Reshaped for borrowck — capture *mut self before the // guard borrows update_mutex. let ctx_ptr = std::ptr::from_mut::(self); let Some(_g) = self.update_mutex.try_lock() else { @@ -468,7 +452,6 @@ impl Progress { } fn maybe_refresh_with_held_lock(&mut self, timer: Instant) { - // Zig: timer.read() returns ns since start. let now = u64::try_from(timer.elapsed().as_nanos()).expect("int cast"); if now < self.initial_delay_ns { return; @@ -519,7 +502,6 @@ impl Progress { 'winapi: { debug_assert!(self.is_windows_terminal); - // TODO(port): verify bun_sys::windows::CONSOLE_SCREEN_BUFFER_INFO layout & kernel32 bindings. let mut info: windows::CONSOLE_SCREEN_BUFFER_INFO = crate::ffi::zeroed(); if GetConsoleScreenBufferInfo(file.console_handle(), &mut info) != windows::TRUE { @@ -611,9 +593,7 @@ impl Progress { // and advance `maybe_node` *before* any `self.buf_write` call: // on the first iteration `maybe_node` is `&raw mut self.root`, // and `buf_write`'s `&mut self` reborrow would invalidate any - // tag derived from it under Stacked Borrows. (Zig has no - // aliasing model, so Progress.zig:313-345 holds `node: *Node` - // across `self.bufWrite` freely; Rust must not.) + // tag derived from it under Stacked Borrows. unsafe { name = (*maybe_node).name; unit = (*maybe_node).unit; @@ -647,8 +627,8 @@ impl Progress { &mut end, format_args!("[{}/{} files] ", current_item, eti), ), - // TODO(port): Zig `{Bi:.2}` is std.fmt binary-bytes formatter (e.g. "1.50KiB"). - // Need a bun_core::fmt::BytesBi helper. + // Raw byte counts are printed until an IEC-units + // (KiB/MiB) formatting helper lands. Unit::Bytes => self .buf_write(&mut end, format_args!("[{}/{}] ", current_item, eti)), } @@ -664,7 +644,7 @@ impl Progress { Unit::Files => { self.buf_write(&mut end, format_args!("[{} files] ", current_item)) } - // TODO(port): Zig `{Bi:.2}` binary-bytes formatter. + // Raw byte counts; see the note above. Unit::Bytes => { self.buf_write(&mut end, format_args!("[{}] ", current_item)) } @@ -693,7 +673,6 @@ impl Progress { let _ = File::stderr().write_fmt(args); return; }; - // TODO(port): Zig `file.writerStreaming(&.{})` — map to bun_sys::File writer. self.refresh(); if file.write_fmt(args).is_err() { self.terminal = None; @@ -706,13 +685,13 @@ impl Progress { /// called. During the lock, the progress information is cleared from the /// terminal. /// - /// PORT NOTE: Zig splits the lock/unlock across fn boundaries. - /// `crate::Mutex` (std::sync wrapper) has no raw `unlock()`, and storing a - /// guard on `self` is self-referential. There are currently **no callers** - /// of `lock_stderr`/`unlock_stderr` in either the Zig or Rust trees, so - /// this clears the terminal under a scoped lock and `unlock_stderr` is a - /// no-op. If a caller materializes, refactor to return the guard (or move - /// `update_mutex` to a raw `bun_threading::Mutex` once layering allows). + /// `crate::Mutex` (std::sync wrapper) has no + /// raw `unlock()`, and storing a guard on `self` is self-referential. There + /// are currently **no callers** of `lock_stderr`/`unlock_stderr`, + /// so this clears the terminal under a scoped lock + /// and `unlock_stderr` is a no-op. If a caller materializes, refactor to + /// return the guard (or move `update_mutex` to a raw `bun_threading::Mutex` + /// once layering allows) and route stderr through a shared global mutex. pub fn lock_stderr(&mut self) { let ctx_ptr = std::ptr::from_mut::(self); let _g = self.update_mutex.lock(); @@ -727,13 +706,11 @@ impl Progress { this.terminal = None; } } - // `_g` drops here; lock is NOT held past return — see PORT NOTE above. - // TODO(port): std.debug.getStderrMutex().lock() — need a global stderr mutex in bun_core. + // `_g` drops here; lock is NOT held past return — see the doc comment above. } pub fn unlock_stderr(&mut self) { - // TODO(port): std.debug.getStderrMutex().unlock() — see lock_stderr. - // No-op; see PORT NOTE on `lock_stderr`. + // No-op; see the doc comment on `lock_stderr`. let _ = self; } @@ -807,7 +784,7 @@ mod tests { ); node.activate(); thread::sleep(Duration::from_nanos(10 * speed_factor)); - // PORT NOTE: reshaped for borrowck — cannot borrow `progress` while `root_node` + // Reshaped for borrowck — cannot borrow `progress` while `root_node` // (a &mut into progress.root) is live; refresh via the node's context backref. // SAFETY: see `context_ptr` — `&mut Progress` would alias the node tree. unsafe { (*node.context_ptr()).refresh() }; @@ -817,5 +794,3 @@ mod tests { root_node.end(); } } - -// ported from: src/bun_core/Progress.zig diff --git a/src/bun_core/atomic_cell.rs b/src/bun_core/atomic_cell.rs index 5a1137b204c..e9deba87e03 100644 --- a/src/bun_core/atomic_cell.rs +++ b/src/bun_core/atomic_cell.rs @@ -2,8 +2,7 @@ //! [`RacyCell`](crate::RacyCell) when state crosses (or is asserted *not* to //! cross) a thread boundary. //! -//! `RacyCell` was the mechanical port of Zig's "trust me" globals; it is now -//! overloaded for three unrelated invariants. This module splits two of them +//! `RacyCell` is overloaded for three unrelated invariants. This module splits two of them //! into types the compiler / debug build can check: //! //! | Invariant | Type | diff --git a/src/bun_core/bounded_array.rs b/src/bun_core/bounded_array.rs index 6bc4a06e03c..834633161d9 100644 --- a/src/bun_core/bounded_array.rs +++ b/src/bun_core/bounded_array.rs @@ -1,12 +1,7 @@ -//! Removed from the Zig standard library in https://github.com/ziglang/zig/pull/24699/ -//! -//! Modifications: -//! - `len` is a field of integer-size instead of usize. This reduces memory usage. -//! //! A structure with an array and a length, that can be used as a slice. //! //! Useful to pass around small arrays whose exact size is only known at -//! runtime, but whose maximum size is known at comptime, without requiring +//! runtime, but whose maximum size is known at compile time, without requiring //! an `Allocator`. //! //! ```ignore @@ -30,36 +25,32 @@ crate::named_error_set!(OverflowError); /// A structure with an array and a length, that can be used as a slice. /// /// Useful to pass around small arrays whose exact size is only known at -/// runtime, but whose maximum size is known at comptime, without requiring +/// runtime, but whose maximum size is known at compile time, without requiring /// an `Allocator`. pub type BoundedArray = BoundedArrayAligned; -// PORT NOTE: Zig's `BoundedArray` delegates to `BoundedArrayAligned` with `@alignOf(T)`. -// In Rust the natural alignment of `[T; N]` is already `align_of::()`, so the alias is +// The natural alignment of `[T; N]` is already `align_of::()`, so the alias is // transparent. The explicit `alignment` const-param is dropped (see below). /// A structure with an array, length and alignment, that can be used as a /// slice. /// /// Useful to pass around small explicitly-aligned arrays whose exact size is -/// only known at runtime, but whose maximum size is known at comptime, without +/// only known at runtime, but whose maximum size is known at compile time, without /// requiring an `Allocator`. -// TODO(port): Zig takes `comptime alignment: Alignment` and applies it via -// `align(alignment.toByteUnits())` on the buffer field. Stable Rust cannot express -// `#[repr(align(N))]` with a const-generic `N`. All in-tree callers use the default -// `@alignOf(T)` via `BoundedArray`, so the param is dropped. Revisit if a -// caller needs over-alignment (would require a wrapper type per alignment). +// Stable Rust cannot express +// `#[repr(align(N))]` with a const-generic `N`. All in-tree callers use the +// default natural alignment via `BoundedArray`; a caller needing +// over-alignment would require a wrapper type per alignment. pub struct BoundedArrayAligned { buffer: [MaybeUninit; BUFFER_CAPACITY], - // TODO(port): Zig uses `Length = std.math.ByteAlignedInt(std.math.IntFittingRange(0, buffer_capacity))` - // (smallest byte-aligned uint that fits `0..=BUFFER_CAPACITY`) to shrink this field. - // Stable Rust const generics cannot pick an integer type from a const value without - // `generic_const_exprs`. Using `usize` for now. - // PERF(port): was size-optimized integer field — profile if it shows up on a hot path + // Stable Rust const generics cannot pick a smaller integer type + // (the smallest byte-aligned uint that fits `0..=BUFFER_CAPACITY`) + // from a const value without `generic_const_exprs`, so `usize` is used. + // PERF: could be a size-optimized integer field — profile if it shows up on a hot path len: usize, } -// `const Length = std.math.ByteAlignedInt(std.math.IntFittingRange(0, buffer_capacity));` -// — see TODO above; collapsed to `usize`. +// See the `len` field note above; collapsed to `usize`. type Length = usize; impl Default for BoundedArrayAligned { @@ -77,8 +68,7 @@ impl Drop for BoundedArrayAligned = [MaybeUninit; N]; impl BoundedArrayAligned { @@ -94,8 +84,7 @@ impl BoundedArrayAligned { } /// View the internal array as a slice whose size was previously set. - // PORT NOTE: Zig's `slice(self: anytype)` is mut/const-polymorphic via `@TypeOf`. - // Rust splits this into `slice(&mut self)` and `const_slice(&self)`. + // Mut/const access is split into `slice(&mut self)` and `const_slice(&self)`. pub fn slice(&mut self) -> &mut [T] { let len = self.len; // SAFETY: elements `[0..len]` are initialized by the public API's invariants. @@ -138,7 +127,7 @@ impl BoundedArrayAligned { list.slice().copy_from_slice(m); Ok(list) } - // TODO(port): Zig `@memcpy` works for non-Copy `T` (bitwise). If a non-`Copy` caller + // If a non-`Copy` caller // appears, add a `from_slice_clone` or use `ptr::copy_nonoverlapping`. /// Return the element at index `i` of the slice. @@ -180,7 +169,7 @@ impl BoundedArrayAligned { self.len += 1; let i = self.len - 1; // SAFETY: index `i` is within `[0..len)`; caller treats the slot as uninitialized - // and must write before reading (matches Zig `addOneAssumeCapacity` contract). + // and must write before reading. unsafe { &mut *self.buffer[i].as_mut_ptr() } } @@ -191,7 +180,7 @@ impl BoundedArrayAligned { self.resize(self.len + N)?; let ptr = self.buffer[prev_len..][..N].as_mut_ptr().cast::<[T; N]>(); // SAFETY: `[prev_len .. prev_len+N]` is within capacity after resize; caller must - // initialize before reading (Zig returns `*[n]T` over undefined storage). + // initialize before reading. Ok(unsafe { &mut *ptr }) } @@ -224,8 +213,8 @@ impl BoundedArrayAligned { pub fn unused_capacity_slice(&mut self) -> &mut [MaybeUninit] { &mut self.buffer[self.len..] } - // PORT NOTE: returns `&mut [MaybeUninit]` instead of `&mut [T]` because the region is - // uninitialized by definition; Zig's `[]T` over undefined memory has no safe Rust equivalent. + // Returns `&mut [MaybeUninit]` instead of `&mut [T]` because the region is + // uninitialized by definition. /// Insert `item` at index `i` by moving `slice[n .. slice.len]` to make room. /// This operation is O(N). @@ -234,9 +223,8 @@ impl BoundedArrayAligned { return Err(OverflowError::Overflow); } let _ = self.add_one()?; - // PORT NOTE: reshaped for borrowck — Zig aliases `s[i+1..]` and `s[i..len-1]` from one slice. + // Reshaped for borrowck. let s_len = self.len; - // mem.copyBackwards(T, s[i + 1 .. s.len], s[i .. s.len - 1]); // SAFETY: ranges are within `[0..len)`; src and dst overlap, hence `ptr::copy` (memmove). unsafe { let base = self.buffer.as_mut_ptr(); @@ -254,7 +242,6 @@ impl BoundedArrayAligned { { self.ensure_unused_capacity(items.len())?; self.len += Length::try_from(items.len()).expect("int cast"); - // mem.copyBackwards(T, self.slice()[i + items.len .. self.len], self.constSlice()[i .. self.len - items.len]); let len = self.len; // SAFETY: ranges are within `[0..len)` after the length bump; overlapping memmove. unsafe { @@ -282,7 +269,7 @@ impl BoundedArrayAligned { T: Copy, { let after_range = start + len; - // PORT NOTE: reshaped for borrowck — Zig holds `range` borrow across `insertSlice`. + // Reshaped for borrowck. let range_len = after_range - start; if range_len == new_items.len() { @@ -295,25 +282,23 @@ impl BoundedArrayAligned { } else { self.slice()[start..after_range][..new_items.len()].copy_from_slice(new_items); let after_subrange = start + new_items.len(); - // PORT NOTE: reshaped for borrowck — Zig reads `constSlice()[after_range..]` while - // writing `slice()[after_subrange..]` in the same loop body. + // Reshaped for borrowck — read and write per element instead of + // holding overlapping `const_slice()`/`slice()` borrows. let tail_len = self.len - after_range; for i in 0..tail_len { let item = self.const_slice()[after_range + i]; self.slice()[after_subrange..][i] = item; } self.len = Length::try_from(self.len - len + new_items.len()).expect("int cast"); - // PORT NOTE: Zig source had `self.len - len - new_items.len`, which over-shrinks - // (and underflows when the replacement is non-empty). Removing `len` items and - // inserting `new_items.len()` items yields `self.len - len + new_items.len()`. + // Removing `len` items and inserting `new_items.len()` items + // yields `self.len - len + new_items.len()`. } Ok(()) } /// Extend the slice by 1 element. pub fn append(&mut self, item: T) -> Result<(), OverflowError> { - // PORT NOTE: Zig's `new_item_ptr.* = item` is a raw bitwise store. The naive Rust - // transliteration `*new_item_ptr = item` would drop the (uninitialized) prior occupant + // A plain `*slot = item` write would drop the (uninitialized) prior occupant // of the slot first — UB that manifests as a bad free when `T` owns heap memory. self.ensure_unused_capacity(1)?; self.append_assume_capacity(item); @@ -327,7 +312,7 @@ impl BoundedArrayAligned { let i = self.len; self.len += 1; // Write into the `MaybeUninit` slot directly so no drop runs on the previous - // (uninitialized) contents — matches Zig's raw `ptr.* = item` semantics. + // (uninitialized) contents. self.buffer[i].write(item); } @@ -344,12 +329,12 @@ impl BoundedArrayAligned { return self.pop().unwrap(); } let old_item = self.get(i); - // PORT NOTE: reshaped for borrowck — Zig writes through `*b` while calling `self.get()`. + // Reshaped for borrowck. for j in 0..(newlen - i) { let v = self.get(i + 1 + j); self.slice()[i + j] = v; } - // self.set(newlen, undefined); — no-op in Rust (slot is past new len, left as-is) + // The slot past the new len is left as-is. self.len = newlen; old_item } @@ -418,8 +403,7 @@ impl BoundedArrayAligned { } } -// Rust-idiom aliases (Vec-like surface) so callers don't need to know the -// Zig-style names. Thin delegations; no behavior change. +// Vec-like aliases. Thin delegations; no behavior change. impl BoundedArrayAligned { #[inline] pub fn len(&self) -> usize { @@ -462,8 +446,7 @@ impl core::ops::DerefMut for BoundedArrayAligned { } } -// `pub const Writer = ... std.io.GenericWriter(*Self, error{Overflow}, appendWrite);` -// Only defined for `T == u8` (Zig `@compileError`s otherwise). +// Only defined for `T == u8`. impl crate::io::Write for BoundedArrayAligned { #[inline] fn write_all(&mut self, buf: &[u8]) -> Result<(), crate::Error> { @@ -489,5 +472,3 @@ impl BoundedArrayAligned { self } } - -// ported from: src/collections/bounded_array.zig diff --git a/src/bun_core/debug.rs b/src/bun_core/debug.rs index c1dd9ef92be..eeeb02d04de 100644 --- a/src/bun_core/debug.rs +++ b/src/bun_core/debug.rs @@ -1,9 +1,8 @@ -//! Port of the `std.debug` subset Zig used: `SourceLocation`/`SymbolInfo` and -//! the frame-pointer stack unwinder (`@frameAddress`, `MemoryAccessor`, -//! `StackIterator`). Lives in `bun_core` (libc/std/bun_alloc only) so the crash +//! `SourceLocation`/`SymbolInfo` and the frame-pointer stack unwinder +//! (`MemoryAccessor`, `StackIterator`). +//! Lives in `bun_core` (libc/std/bun_alloc only) so the crash //! handler, `StoredTrace`, and `btjs` can all share one implementation. -/// Zig: `std.debug.SourceLocation`. #[derive(Clone)] pub struct SourceLocation { pub file_name: Box<[u8]>, @@ -11,7 +10,6 @@ pub struct SourceLocation { pub column: u32, } -/// Zig: `std.debug.SymbolInfo`. pub struct SymbolInfo { pub name: Box<[u8]>, pub compile_unit_name: Box<[u8]>, @@ -19,9 +17,8 @@ pub struct SymbolInfo { } // ────────────────────────────────────────────────────────────────────── -// Frame-pointer stack unwinder (port of the `std.debug` subset Zig used: -// `@frameAddress`, `MemoryAccessor`, `StackIterator`). The Rust port had -// briefly routed capture through libc `backtrace()` / `RtlCaptureStackBackTrace`, +// Frame-pointer stack unwinder. Capture had +// briefly been routed through libc `backtrace()` / `RtlCaptureStackBackTrace`, // which are CFI/unwind-table based — but release builds strip the unwind tables // (`-fno-asynchronous-unwind-tables` + `--no-eh-frame-hdr`) and the POSIX // signal handler runs on an `SA_ONSTACK` altstack, so those APIs captured only @@ -30,7 +27,7 @@ pub struct SymbolInfo { // the correct mechanism. Lives in `bun_core` (libc/std/bun_alloc only) so the // crash handler, `StoredTrace`, and `btjs` can all share one implementation. // ────────────────────────────────────────────────────────────────────── -/// Port of Zig `@frameAddress()`. Reads the frame-pointer register directly. +/// Reads the frame-pointer register directly. #[inline(always)] pub fn frame_address() -> usize { #[cfg(target_arch = "x86_64")] @@ -53,7 +50,7 @@ pub fn frame_address() -> usize { } #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] { - // @frameAddress() — approximate with a stack local's addr on arches + // Approximate with a stack local's addr on arches // without an asm! mapping yet. fp-walk will fail its alignment sanity // check and terminate cleanly. let probe = 0u8; @@ -62,8 +59,7 @@ pub fn frame_address() -> usize { } /// Reads memory from any address of the current process, tolerating unmapped -/// or corrupt pages so a damaged stack can't fault the walker itself. Port of -/// `std.debug.MemoryAccessor`. +/// or corrupt pages so a damaged stack can't fault the walker itself. struct MemoryAccessor { #[cfg(any(target_os = "linux", target_os = "android"))] mem: core::ffi::c_int, // -1 = uninit, -2 = unavailable, else /proc//mem fd @@ -211,7 +207,7 @@ fn is_valid_memory(address: usize) -> bool { } } -/// Port of `std.debug.StackIterator`. Walks the frame-pointer chain. +/// Walks the frame-pointer chain. pub struct StackIterator { pub fp: usize, ma: MemoryAccessor, diff --git a/src/bun_core/deprecated.rs b/src/bun_core/deprecated.rs index ebb085c7b07..93628795339 100644 --- a/src/bun_core/deprecated.rs +++ b/src/bun_core/deprecated.rs @@ -4,9 +4,10 @@ use core::ptr; // BufferedReader // ────────────────────────────────────────────────────────────────────────── -// TODO(port): Zig's `ReaderType` only needs `.read(&mut [u8]) -> Result` -// and an associated `Error` type. There is no `bun_io::Read` trait yet; introduce one -// (or reuse whatever the `std.Io.GenericReader` port lands as) and bound `R` on it. +// Plain storage for a buffered reader. The only +// in-tree consumer (`pack_command::BufferedFileReader`) supplies its own read shim +// over `bun_sys::read`, so this stays a bare struct: no reader trait, no methods. +// (The dedicated stdin instance lives at `output::BufferedStdin`.) pub struct BufferedReader { pub unbuffered_reader: R, pub buf: [u8; BUFFER_SIZE], @@ -14,66 +15,6 @@ pub struct BufferedReader { pub end: usize, } -impl BufferedReader -where - // TODO(port): replace with the real reader trait once it exists. - R: DeprecatedRead, -{ - // Zig: `pub const Error = R.Error;` — inherent assoc types are nightly-only - // (E0658). Callers name `R::Error` directly; this alias was sugar. - // TODO(port): `pub const Reader = std.Io.GenericReader(*Self, Error, read);` — - // depends on the Rust port of `std.Io.GenericReader`. Left unported; `reader()` - // below is stubbed accordingly. - - pub fn read(&mut self, dest: &mut [u8]) -> Result { - // First try reading from the already buffered data onto the destination. - let current = &self.buf[self.start..self.end]; - if !current.is_empty() { - let to_transfer = current.len().min(dest.len()); - dest[0..to_transfer].copy_from_slice(¤t[0..to_transfer]); - self.start += to_transfer; - return Ok(to_transfer); - } - - // If dest is large, read from the unbuffered reader directly into the destination. - if dest.len() >= BUFFER_SIZE { - return self.unbuffered_reader.read(dest); - } - - // If dest is small, read from the unbuffered reader into our own internal buffer, - // and then transfer to destination. - self.end = self.unbuffered_reader.read(&mut self.buf)?; - let to_transfer = self.end.min(dest.len()); - dest[0..to_transfer].copy_from_slice(&self.buf[0..to_transfer]); - self.start = to_transfer; - Ok(to_transfer) - } - - pub fn reader(&mut self) -> &mut Self { - // TODO(port): Zig returned a `std.Io.GenericReader` adapter wrapping `self`. - // Until the generic-reader port exists, hand back `&mut Self` (which already - // exposes `read`). Wire to the real adapter type once it exists. - self - } -} - -// TODO(port): placeholder trait standing in for `ReaderType` duck-typing. Remove once -// the shared reader trait exists and bound `R` on that instead. -pub trait DeprecatedRead { - type Error; - fn read(&mut self, dest: &mut [u8]) -> Result; -} - -pub fn buffered_reader(reader: R) -> BufferedReader<4096, R> { - BufferedReader { - unbuffered_reader: reader, - // PERF(port): Zig left `buf` undefined; zero-init here is an extra 4 KiB memset. - buf: [0u8; 4096], - start: 0, - end: 0, - } -} - // ────────────────────────────────────────────────────────────────────────── // SinglyLinkedList // ────────────────────────────────────────────────────────────────────────── @@ -106,7 +47,6 @@ pub struct DoublyLinkedList { } /// Node inside the linked list wrapping the actual data. -// In Zig this is `DoublyLinkedList(T).Node`. pub struct DoublyLinkedNode { pub prev: *mut DoublyLinkedNode, pub next: *mut DoublyLinkedNode, @@ -463,5 +403,3 @@ mod tests { // RapidHash test vectors live alongside the canonical impl in `bun_hash::rapidhash`. } - -// ported from: src/bun_core/deprecated.zig diff --git a/src/bun_core/env.rs b/src/bun_core/env.rs index 85fe5aadd21..4c1f555671f 100644 --- a/src/bun_core/env.rs +++ b/src/bun_core/env.rs @@ -1,8 +1,6 @@ use phf::phf_map; -// PORT NOTE: `build_options` was Zig's build-system-injected module. In Rust it -// is a generated module (build.rs consts). -// Zig: `pub const build_options = @import("build_options");` — public re-export. +// `build_options` is a generated module (build.rs consts), re-exported publicly. pub use crate::build_options; #[repr(u8)] @@ -30,11 +28,10 @@ pub const IS_WINDOWS: bool = cfg!(windows); pub(crate) const IS_POSIX: bool = !IS_WINDOWS && !IS_WASM; pub const IS_DEBUG: bool = cfg!(debug_assertions); pub(crate) const IS_TEST: bool = cfg!(test); -// Zig's `Environment.isLinux` is `builtin.target.os.tag == .linux`, which is -// TRUE on Android (Zig models Android as `os.tag == .linux, abi == .android`). -// Rust splits them into two `target_os` values, so this const has to OR them -// to keep the Zig semantics — otherwise `OS` (below) panics at const-eval on -// the `*-linux-android` cross targets and Linux-only code paths are skipped. +// Android is a Linux kernel target, but Rust splits the two into separate +// `target_os` values, so this const has to OR them — otherwise `OS` (below) +// panics at const-eval on the `*-linux-android` cross targets and Linux-only +// code paths are skipped. pub const IS_LINUX: bool = cfg!(any(target_os = "linux", target_os = "android")); pub(crate) const IS_FREEBSD: bool = cfg!(target_os = "freebsd"); /// kqueue-based event loop (macOS + FreeBSD share most of this path). @@ -50,8 +47,7 @@ pub const SHOW_CRASH_TRACE: bool = IS_DEBUG || IS_TEST || ENABLE_ASAN; pub const REPORTED_NODEJS_VERSION: &str = build_options::REPORTED_NODEJS_VERSION; pub const BASELINE: bool = build_options::BASELINE; -/// Zig disabled SIMD under `-Dno_llvm` (self-hosted backend lacked vector -/// lowering); Rust always uses LLVM, so only `BASELINE` gates it. +/// Only `BASELINE` gates SIMD. pub const ENABLE_SIMD: bool = !BASELINE; pub const GIT_SHA: &str = build_options::SHA; pub const GIT_SHA_SHORT: &str = if !build_options::SHA.is_empty() { @@ -102,9 +98,7 @@ pub enum OperatingSystem { Wasm, } -/// Port of the subset of Zig's `std.Target.Os.Tag` that Bun targets. -/// Variant names match the Zig stdlib tags (`.macos`, `.linux`, `.freebsd`, -/// `.windows`) so cross-references in ported code stay 1:1. +/// OS tag for the targets Bun builds for. #[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq)] pub enum StdOsTag { @@ -257,5 +251,3 @@ const fn const_str_slice(s: &'static str, start: usize, end: usize) -> &'static Err(_) => panic!("const_str_slice: not at a UTF-8 boundary"), } } - -// ported from: src/bun_core/env.zig diff --git a/src/bun_core/env_var.rs b/src/bun_core/env_var.rs index 84129898328..142987436a4 100644 --- a/src/bun_core/env_var.rs +++ b/src/bun_core/env_var.rs @@ -26,14 +26,8 @@ //! everything. This means that we potentially scan through envp a lot of //! times, even though we could only do it once. -// TODO(port): The Zig original uses comptime type-returning functions (`New`, `PlatformSpecificNew`) -// that take comptime string keys + option structs and return a unique type per env var with an -// embedded `static` cache. Rust cannot parameterize a generic type on `&'static str` + a struct -// value in stable, so this port models `New`/`PlatformSpecificNew` as `macro_rules!` that emit a -// module per env var. In Zig the declarations come first and the type-generator fns come last; -// here the macros must be defined (or `#[macro_use]`d) before the declarations. The macro -// definitions could move into a sibling `env_var_impl.rs` and be `#[macro_use]`d to restore Zig -// declaration order in this file. +// `New`/`PlatformSpecificNew` are `macro_rules!` that emit a module per env var; the macros +// must be defined (or `#[macro_use]`d) before the declarations. use core::sync::atomic::{AtomicPtr, AtomicU8, AtomicU64, AtomicUsize, Ordering}; @@ -74,13 +68,13 @@ new!(pub BUN_DEBUG_QUIET_LOGS: boolean, "BUN_DEBUG_QUIET_LOGS", {}); new!(pub BUN_DEBUG_TEST_TEXT_LOCKFILE: boolean, "BUN_DEBUG_TEST_TEXT_LOCKFILE", { default: false }); new!(pub BUN_DEV_SERVER_TEST_RUNNER: string, "BUN_DEV_SERVER_TEST_RUNNER", {}); // Debug-only: when set, `NumberRenamer` dumps the symbol table before -// renaming (`src/js_printer/renamer.zig`). Presence-checked, value ignored. +// renaming (`src/js_printer/renamer.rs`). Presence-checked, value ignored. new!(pub BUN_DUMP_SYMBOLS: string, "BUN_DUMP_SYMBOLS", {}); new!(pub BUN_ENABLE_CRASH_REPORTING: boolean, "BUN_ENABLE_CRASH_REPORTING", {}); // Opt-in: when truthy, Bun watches its original parent pid and exits as soon // as that process dies (even if the parent was SIGKILLed and couldn't forward // a signal), and on its own clean exit recursively SIGKILLs every descendant -// so nothing it spawned outlives it. See `src/ParentDeathWatchdog.zig`. +// so nothing it spawned outlives it. See `src/io/ParentDeathWatchdog.rs`. new!(pub BUN_FEATURE_FLAG_NO_ORPHANS: boolean, "BUN_FEATURE_FLAG_NO_ORPHANS", { default: false }); new!(pub BUN_FEATURE_FLAG_DUMP_CODE: string, "BUN_FEATURE_FLAG_DUMP_CODE", {}); // TODO(markovejnovic): It's unclear why the default here is 100_000, but this was legacy behavior @@ -144,9 +138,9 @@ new!(pub JENKINS_URL: string, "JENKINS_URL", {}); new!(pub MI_VERBOSE: boolean, "MI_VERBOSE", { default: false }); new!(pub NO_COLOR: boolean, "NO_COLOR", { default: false }); new!(pub NODE_CHANNEL_FD: string, "NODE_CHANNEL_FD", {}); -// Set by HostProcess.zig when spawning the WebView host subprocess. The -// child's cli.zig checks this before anything else and hands off to C++ -// Bun__WebView__hostMain. Never returns — no JSC, no VM. +// Set by HostProcess.rs when spawning the WebView host subprocess. The +// child's CLI entrypoint checks this before anything else and hands off to +// C++ Bun__WebView__hostMain. Never returns — no JSC, no VM. new!(pub BUN_INTERNAL_WEBVIEW_HOST: string, "BUN_INTERNAL_WEBVIEW_HOST", {}); new!(pub NODE_PRESERVE_SYMLINKS_MAIN: boolean, "NODE_PRESERVE_SYMLINKS_MAIN", { default: false }); new!(pub NODE_USE_SYSTEM_CA: boolean, "NODE_USE_SYSTEM_CA", { default: false }); @@ -157,7 +151,6 @@ new!(pub RUNNER_DEBUG: boolean, "RUNNER_DEBUG", { default: false }); platform_specific_new!(pub SDKROOT: string, posix = "SDKROOT", windows = None, {}); platform_specific_new!(pub SHELL: string, posix = "SHELL", windows = None, {}); // C:\Windows, for example. -// Note: Do not use this variable directly -- use os.zig's implementation instead. platform_specific_new!(pub SYSTEMROOT: string, posix = None, windows = "SYSTEMROOT", {}); platform_specific_new!(pub TEMP: string, posix = "TEMP", windows = "TEMP", {}); new!(pub TERM: string, "TERM", {}); @@ -169,7 +162,6 @@ new!(pub TODIUM: string, "TODIUM", {}); platform_specific_new!(pub USER: string, posix = "USER", windows = "USERNAME", {}); new!(pub WANTS_LOUD: boolean, "WANTS_LOUD", { default: false }); // The same as system_root. -// Note: Do not use this variable directly -- use os.zig's implementation instead. // TODO(markovejnovic): Perhaps we could add support for aliases in the library, so you could // specify both WINDIR and SYSTEMROOT and the loader would check both? platform_specific_new!(pub WINDIR: string, posix = None, windows = "WINDIR", {}); @@ -260,7 +252,6 @@ pub(crate) enum CacheOutput { Value(V), } -// Zig: `fn CacheConfigurationType(comptime CtorOptionsType: type) type` pub(crate) struct CacheConfiguration { pub var_name: &'static [u8], pub opts: O, @@ -289,18 +280,16 @@ pub(crate) mod kind { pub(crate) type ValueType = &'static [u8]; pub(crate) type Output = CacheOutput; - // Zig: `fn Cache(comptime ip: Input) type` — `ip` is unused (`_ = ip;`). - // Rust: a single Cache struct; per-var uniqueness comes from each var owning its own + // A single Cache struct; per-var uniqueness comes from each var owning its own // `static CACHE: Cache`. pub(crate) struct Cache { ptr_value: AtomicPtr, len_value: AtomicUsize, } - type PointerType = *mut u8; // Zig: ?[*]const u8 — AtomicPtr requires *mut + type PointerType = *mut u8; // AtomicPtr requires *mut type LenType = usize; - // Zig nested `not_loaded_sentinel` / `not_set_sentinel` constants: const NOT_LOADED_PTR: PointerType = core::ptr::null_mut(); const NOT_LOADED_LEN: LenType = LenType::MAX; const NOT_SET_PTR: PointerType = core::ptr::null_mut(); @@ -503,14 +492,10 @@ pub(crate) mod kind { } } - // Zig: `fn Cache(comptime ip: Input) type` — Rust: store `ip` (var_name + opts) on the - // struct so handle_error can read it. Zig passes it as a comptime param; we pass it at - // `const fn new()` time. + // `ip` (var_name + opts) lives on the struct so handle_error can read it; it is + // passed at `const fn new()` time. pub(crate) struct Cache { value: AtomicU64, - // TODO(port): in Zig `ip` is a comptime param baked into the type; here it lives as - // runtime data on the static. The `default_fallback` arm in handle_error was a - // `@compileError` when no default was set — that compile-time check is lost. ip: Input, } @@ -560,8 +545,7 @@ pub(crate) mod kind { } } - // Zig: `std.fmt.parseInt(u64, raw_env, 10)` — distinguishes Overflow vs - // InvalidCharacter. Exact parity incl. '-0'→0, '-N'→Overflow, + // Distinguishes Overflow vs InvalidCharacter; '-0'→0, '-N'→Overflow, // leading/trailing-`_` reject. let formatted = match crate::fmt::parse_int::(raw_env, 10) { Ok(v) => v, @@ -582,9 +566,6 @@ pub(crate) mod kind { } fn handle_error(&self, raw_env: &[u8], reason: &'static str) -> Option { - // Zig built `fmt` at comptime via string concatenation: - // "Environment variable '{s}' has value '{s}' which " ++ reason ++ "." - // We pass `reason` as a third argument instead. match self.ip.opts.deser.error_handling { ErrorHandling::DebugWarn => { crate::output::debug_warn(format_args!( @@ -622,8 +603,6 @@ pub(crate) mod kind { /// Technically, none of the operations here are thread-safe, so writing to environment variables /// does not guarantee that other threads will see the changes. You should avoid writing to /// environment variables. -// Zig: `fn New(comptime VariantType: type, comptime key: [:0]const u8, comptime opts) type` -// → `PlatformSpecificNew(VariantType, key, key, opts)` #[macro_export] #[doc(hidden)] macro_rules! new { @@ -640,15 +619,12 @@ pub(crate) use new; /// If the current platform does not have a key specified, all methods that attempt to read the /// environment variable will fail at compile time, except for `platform_get` and `platform_key`, /// which will return None instead. -// Zig: `fn PlatformSpecificNew(comptime VariantType, comptime posix_key: ?[:0]const u8, -// comptime windows_key: ?[:0]const u8, comptime opts) type` #[macro_export] #[doc(hidden)] macro_rules! platform_specific_new { - // TODO(port): this macro is a draft of the Zig comptime type-generator. It expands to a - // `pub mod $name { pub fn get() / key() / platform_get() / ... }` so call sites read - // `env_var::HOME::get()` like Zig's `env_var.HOME.get()`. The opts-parsing arms below cover - // exactly the option shapes used in this file; harden / generalize if new shapes appear. + // Expands to a `pub mod $name { pub fn get() / key() / platform_get() / ... }` so call + // sites read `env_var::HOME::get()`. The opts-parsing arms below cover + // exactly the option shapes used in this file; new shapes need new arms. ( $vis:vis $name:ident : $kind:ident, posix = $posix:tt, windows = $windows:tt, @@ -662,17 +638,14 @@ macro_rules! platform_specific_new { use $crate::env_var::kind::$kind as K; use $crate::env_var::CacheOutput; - // Zig: `const comptime_key: []const u8 = posix_key orelse windows_key orelse ""` - // (Compile-error when both null is enforced by having no matching macro arm.) - // Zig: `var cache: VariantType.Cache(.{ .var_name = comptime_key, .opts = opts }) = .{};` + // (Compile-error when both keys are None is enforced by having no matching macro arm.) static CACHE: K::Cache = $crate::env_var::__make_cache!( $kind, $crate::env_var::__first_key!($posix, $windows), { $($opts)* } ); - // Zig computed `DefaultType`/`ReturnType` at comptime from whether `opts.default` is - // set. We expose the default + a const HAS_DEFAULT and always return Option; - // a thin `pub fn get() -> ValueType` wrapper that `.unwrap()`s is added when a default - // exists. TODO(port): restore the non-nullable `get()` return type for defaulted vars. + // A `macro_rules!` expansion can't vary the return type on an optional opt, so + // `get()` always returns `Option` (always `Some` when `DEFAULT` is + // `Some`). pub(crate) const DEFAULT: Option = $crate::env_var::__default_opt!($kind, { $($opts)* }); @@ -726,9 +699,8 @@ macro_rules! platform_specific_new { None } - // TODO(port): `getNotEmpty` only makes sense for string-kind vars (it calls `.len`). - // In Zig, lazy compilation means it simply isn't instantiated for non-string vars. - // Could gate this fn on `$kind == string` via a separate macro arm. + // `get_not_empty` only makes sense for string-kind vars (it calls `.len`). The + // `HasLen` bound gates this — calls on non-string kinds fail to compile. pub fn get_not_empty() -> Option where K::ValueType: $crate::env_var::HasLen, @@ -786,11 +758,10 @@ macro_rules! platform_specific_new { /// /// It is safe to compare the result of .get() to default to test if the variable is set to /// its default value. - // Zig: `pub const default: DefaultType = if (opts.default) |d| d else {};` // Exposed above as `DEFAULT: Option`. - /// Unit value so call sites read `env_var::FOO.get()` (matching Zig - /// `bun.env_var.FOO.get()`). The module-path form `FOO::get()` also works. + /// Unit value so call sites read `env_var::FOO.get()`. The module-path form + /// `FOO::get()` also works. pub struct Accessor; impl Accessor { #[inline] pub fn get(&self) -> Option { get() } @@ -802,18 +773,13 @@ macro_rules! platform_specific_new { } fn assert_platform_supported() { - // Zig: `@compileError` when the current platform's key is null. - // TODO(port): Rust cannot `compile_error!` from inside a const-evaluated `if cfg!` - // without separate macro arms per (posix=None / windows=None) combination. Could - // split the macro so e.g. `posix = None` emits `#[cfg(unix)] compile_error!`. + // A `compile_error!` here would fire unconditionally on the unsupported + // platform even when nothing calls `get()`, so this is a debug assertion. debug_assert!( platform_key().is_some(), - concat!( - "Cannot retrieve the value of ", - // TODO(port): COMPTIME_KEY is &[u8]; concat! wants literals - "", - " since no key is associated with it on this platform." - ) + "Cannot retrieve the value of {} since no key is associated with it on this platform.", + ::core::str::from_utf8($crate::env_var::__first_key!($posix, $windows)) + .unwrap_or("") ); } } @@ -822,8 +788,6 @@ macro_rules! platform_specific_new { pub(crate) use platform_specific_new; // ─── helper macros for platform_specific_new! ─── -// TODO(port): these are scaffolding for the draft macro; could be replaced with a cleaner -// trait-based design once the call-site shape is settled. #[doc(hidden)] #[macro_export] @@ -945,8 +909,6 @@ impl HasLen for u64 { } } -// Zig: `fn newFeatureFlag(comptime env_var: [:0]const u8, comptime opts: FeatureFlagOpts) type` -// → `New(kind.boolean, env_var, .{ .default = opts.default })` #[macro_export] #[doc(hidden)] macro_rules! new_feature_flag { @@ -962,5 +924,3 @@ macro_rules! new_feature_flag { }; } pub(crate) use new_feature_flag; - -// ported from: src/bun_core/env_var.zig diff --git a/src/bun_core/external_shared.rs b/src/bun_core/external_shared.rs index a7650784f2e..5418e4a9851 100644 --- a/src/bun_core/external_shared.rs +++ b/src/bun_core/external_shared.rs @@ -2,13 +2,10 @@ use core::ptr::NonNull; /// Protocol for types whose reference count is managed externally (e.g., by extern functions). /// -/// In Zig this is duck-typed via `T.external_shared_descriptor = struct { ref, deref }`. -/// In Rust the type implements this trait directly. -/// /// # Safety /// Implementors guarantee that `ext_ref`/`ext_deref` operate on a valid externally-owned /// reference count, and that the pointee remains alive while the count is > 0. -// PORT NOTE: Zig names are `ref`/`deref`; renamed to avoid the `ref` keyword and +// Named `ext_ref`/`ext_deref` to avoid the `ref` keyword and // `core::ops::Deref::deref` confusion. pub unsafe trait ExternalSharedDescriptor { unsafe fn ext_ref(this: *mut Self); @@ -17,11 +14,9 @@ pub unsafe trait ExternalSharedDescriptor { /// A shared pointer whose reference count is managed externally; e.g., by extern functions. /// -/// `T` must implement [`ExternalSharedDescriptor`] (the Rust equivalent of Zig's -/// `T.external_shared_descriptor` struct with `ref(*T)` / `deref(*T)`). +/// `T` must implement [`ExternalSharedDescriptor`]. #[repr(transparent)] pub struct ExternalShared { - // Zig: `#impl: *T` (private, non-null) ptr: NonNull, } @@ -33,7 +28,8 @@ impl ExternalShared { /// ownership of is being transferred to the returned `ExternalShared`. pub unsafe fn adopt(incremented_raw: *mut T) -> Self { Self { - // SAFETY: Zig `*T` is non-null by construction. + // SAFETY: caller contract requires `incremented_raw` to be a valid + // (hence non-null) pointer. ptr: unsafe { NonNull::new_unchecked(incremented_raw) }, } } @@ -56,7 +52,8 @@ impl ExternalShared { // SAFETY: caller contract. unsafe { T::ext_ref(raw) }; Self { - // SAFETY: Zig `*T` is non-null. + // SAFETY: caller contract requires `raw` to be a valid (hence + // non-null) pointer. ptr: unsafe { NonNull::new_unchecked(raw) }, } } @@ -108,10 +105,9 @@ impl Drop for ExternalShared { } } -/// Optional variant of [`ExternalShared`] (Zig: `ExternalShared(T).Optional`). +/// Optional variant of [`ExternalShared`]. #[repr(transparent)] pub struct ExternalSharedOptional { - // Zig: `#impl: ?*T = null` ptr: Option>, } @@ -205,5 +201,3 @@ unsafe impl ExternalSharedDescriptor for bun_alloc::WTFStringImplStruct { /// Behaves like `WTF::Ref`. pub type WTFString = ExternalShared; - -// ported from: src/ptr/external_shared.zig diff --git a/src/bun_core/feature_flags.rs b/src/bun_core/feature_flags.rs index e95397b987b..2015363e3e5 100644 --- a/src/bun_core/feature_flags.rs +++ b/src/bun_core/feature_flags.rs @@ -131,5 +131,3 @@ pub fn bake() -> bool { /// Additional debugging features for bake.DevServer, such as the incremental visualizer. /// To use them, extra flags are passed in addition to this one. pub const BAKE_DEBUGGING_FEATURES: bool = env::IS_CANARY || env::IS_DEBUG; - -// ported from: src/bun_core/feature_flags.zig diff --git a/src/bun_core/fmt.rs b/src/bun_core/fmt.rs index 930c51973cf..5c94c455603 100644 --- a/src/bun_core/fmt.rs +++ b/src/bun_core/fmt.rs @@ -1,4 +1,4 @@ -//! Port of src/bun_core/fmt.zig — formatter newtypes and Display impls. +//! Formatter newtypes and Display impls. use core::cell::Cell; use core::fmt::{self, Display, Formatter, Write as _}; @@ -20,14 +20,14 @@ const SHA512_DIGEST: usize = 64; // ════════════════════════════════════════════════════════════════════════════ pub mod js_lexer { - /// Zig: js_lexer.isIdentifierStart — ASCII fast path; bun_js_parser extends - /// with the full Unicode ID_Start table. + /// ASCII fast path; bun_js_parser extends with the full Unicode ID_Start + /// table. #[inline] pub fn is_identifier_start(c: i32) -> bool { matches!(c, 0x24 /* $ */ | 0x5F /* _ */) || (c >= b'a' as i32 && c <= b'z' as i32) || (c >= b'A' as i32 && c <= b'Z' as i32) - || c > 0x7F // PERF(port): defer Unicode table to bun_js_parser + || c > 0x7F // non-ASCII: the full Unicode table lives in bun_js_parser } #[inline] pub fn is_identifier_continue(c: i32) -> bool { @@ -38,7 +38,7 @@ pub mod js_lexer { pub mod js_printer { use super::strings::Encoding; use core::fmt; - /// Zig: js_printer.writeJSONString — minimal escape set for fmt.rs quoting. + /// Minimal escape set for fmt.rs quoting. /// bun_js_printer overrides with the full (ctrl-char, \u escape, encoding-aware) impl. pub fn write_json_string(input: &[u8], f: &mut impl fmt::Write, enc: Encoding) -> fmt::Result { f.write_char('"')?; @@ -52,18 +52,20 @@ pub mod js_printer { input: &[u8], f: &mut impl fmt::Write, quote: u8, - _allow_backtick: bool, + ascii_only: bool, enc: Encoding, ) -> fmt::Result { - // TODO(port): full impl in bun_js_printer; this tier only needs the - // "already quoted" passthrough for fmt.rs JS-string display. - // Zig writePreQuotedString writes the escaped body WITHOUT surrounding - // quotes — delegate to the canonical chars-only escaper. - let _ = quote; - match enc { - Encoding::Latin1 => super::encode_json_string_chars_latin1(f, input), - _ => super::encode_json_string_chars(f, input), - } + // Writes the escaped body WITHOUT surrounding quotes. Delegate to the + // canonical impl in `string::printer` (a byte-sink writer) and bridge + // the result into the `fmt::Write`. In JSON mode (`json = true`) every + // non-printable scalar (including lone surrogates) is emitted as an + // ASCII escape. + let mut buf: Vec = Vec::with_capacity(input.len() + 8); + crate::string::printer::write_pre_quoted_string( + input, &mut buf, quote, ascii_only, true, enc, + ) + .map_err(|_| fmt::Error)?; + f.write_str(&String::from_utf8_lossy(&buf)) } } use strum::IntoStaticStr; @@ -252,7 +254,7 @@ impl Display for RedactedNpmUrlFormatter<'_> { // Emit the run of bytes up to the next position where a uuid/npm // secret could possibly start, so multi-byte UTF-8 sequences are - // written intact (Zig writes raw bytes, not Latin-1→UTF-8 chars). + // written intact (raw bytes, not Latin-1→UTF-8 chars). let mut next = i + 1; while next < self.url.len() { let b = self.url[next]; @@ -292,8 +294,8 @@ impl Display for RedactedSourceFormatter<'_> { } // Batch the non-secret span so multi-byte UTF-8 sequences pass - // through intact (Zig writes raw bytes; per-byte `as char` would - // re-encode each >=0x80 byte as a 2-byte sequence). + // through intact (per-byte `as char` would re-encode each >=0x80 + // byte as a 2-byte sequence). let mut next = i + 1; while next < self.text.len() && strings::starts_with_secret(&self.text[next..]).is_none() @@ -464,7 +466,7 @@ thread_local! { /// On construction: takes (or allocates) the buffer and nulls the thread-local /// cell so any recursive borrow allocates a fresh one instead of aliasing this /// one. On drop: restores the buffer to the cell, or frees it if recursion has -/// already restored a different buffer (mirrors fmt.zig's `defer` block). +/// already restored a different buffer. struct SharedTempBufferBorrow { ptr: NonNull, } @@ -719,14 +721,13 @@ pub fn fmt_path(path: &[u8], options: PathFormatOptions) -> FormatUTF8<'_> { /// Non-validating `Display` adapter for a `&[u8]` known to be valid UTF-8. /// -/// Port of Zig's `{s}` format specifier on a `[]const u8`: Zig writes the bytes -/// straight through with no codepoint check. `bstr::BStr`'s `Display` impl walks +/// Writes the bytes straight through with no codepoint check. `bstr::BStr`'s `Display` impl walks /// the input via `Utf8Chunks` to substitute U+FFFD on invalid sequences, which /// shows up in install-hot-path profiles (registry hosts, package names, semver /// pre/build tags — all pre-validated ASCII). Use this where the bytes are /// already known-good and you just want `f.write_str` semantics. /// -/// Prefer the [`s`] alias at call sites — it reads like Zig's `{s}`. +/// Prefer the [`s`] alias at call sites. #[derive(Copy, Clone)] #[repr(transparent)] pub struct Raw<'a>(pub &'a [u8]); @@ -734,11 +735,11 @@ impl fmt::Display for Raw<'_> { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { // SAFETY: caller contract — `self.0` is valid UTF-8 (in practice ASCII: - // npm package names, registry URLs, semver tags). Matches Zig `{s}`. + // npm package names, registry URLs, semver tags). f.write_str(unsafe { core::str::from_utf8_unchecked(self.0) }) } } -/// Shorthand constructor for [`Raw`]. Prefer [`s`] (same thing, Zig-style name). +/// Shorthand constructor for [`Raw`]. Prefer [`s`] (same thing, shorter name). #[inline(always)] pub const fn raw(bytes: &[u8]) -> Raw<'_> { Raw(bytes) @@ -747,7 +748,7 @@ pub const fn raw(bytes: &[u8]) -> Raw<'_> { // Canonical `SliceCursor` / `buf_print` / `buf_print_len` live in T0 // `bun_alloc` so that crate can use them too; re-exported here for the // `bun_core::fmt::` callers and extended with an `io::Write` face so the same -// struct also serves as Zig's `std.io.fixedBufferStream` for write-only sites. +// struct also serves as a fixed-buffer write sink for write-only sites. pub use bun_alloc::{SliceCursor, buf_print, buf_print_len}; impl crate::io::Write for SliceCursor<'_> { @@ -767,7 +768,7 @@ impl crate::io::Write for SliceCursor<'_> { } } -/// Port of `std.fmt.bufPrintZ` — [`buf_print`] then append a NUL terminator and +/// [`buf_print`] then append a NUL terminator and /// return a [`ZStr`](crate::ZStr) borrowing `buf`. Fails if the formatted output /// *plus* the trailing NUL doesn't fit. pub fn buf_print_z<'a>( @@ -784,8 +785,7 @@ pub fn buf_print_z<'a>( Ok(crate::ZStr::from_buf(c.buf, n)) } -/// [`buf_print`] that panics on overflow — mirrors Zig's -/// `std.fmt.bufPrint(buf, fmt, args) catch unreachable`. Use when the +/// [`buf_print`] that panics on overflow. Use when the /// caller-supplied stack buffer is sized so overflow is a programmer error. #[inline] #[track_caller] @@ -793,8 +793,7 @@ pub fn buf_print_infallible<'a>(buf: &'a mut [u8], args: core::fmt::Arguments<'_ buf_print(buf, args).expect("buf_print: buffer too small") } -/// [`buf_print_z`] that panics on overflow — mirrors Zig's -/// `std.fmt.bufPrintZ(buf, fmt, args) catch unreachable`. +/// [`buf_print_z`] that panics on overflow. #[inline] #[track_caller] pub fn buf_print_z_infallible<'a>( @@ -811,8 +810,8 @@ pub fn buf_print_z_infallible<'a>( /// `core::fmt::Write` adapter for `Vec`. /// /// Rust's `Vec` only implements `std::io::Write` (banned in lower crates -/// per PORTING.md), not `core::fmt::Write`. This is the port of Zig's -/// `std.ArrayList(u8).writer()` — infallible (Vec growth aborts on OOM). +/// per PORTING.md), not `core::fmt::Write`. Infallible (Vec growth aborts on +/// OOM). /// /// Both the tuple constructor and `::new()` are public so call sites can pick /// whichever reads better: `write!(VecWriter(&mut buf), ...)` or @@ -834,17 +833,14 @@ impl core::fmt::Write for VecWriter<'_> { } // ════════════════════════════════════════════════════════════════════════════ -// std.fmt.parseInt / parseUnsigned — canonical &[u8] integer parsers. +// parse_int / parse_unsigned — canonical &[u8] integer parsers. // -// Zig has exactly one impl (`std.fmt.parseIntWithSign`, vendor/zig/lib/std/ -// fmt.zig:409) plus thin no-sign wrapper `std.fmt.parseUnsigned` (:488). Every -// Zig caller invoked those directly on `[]const u8`; the Rust port spawned ~15 -// local digit loops solely to dodge `core::str::from_utf8`. These restore 1:1 -// parity. bun_string re-exports `parse_int` so existing `strings::parse_int` -// callers keep working. Re-exported via bun_core::lib.rs. +// Replaces ~15 local digit loops that existed solely to dodge +// `core::str::from_utf8`. bun_string re-exports `parse_int` so existing +// `strings::parse_int` callers keep working. Re-exported via bun_core::lib.rs. // ════════════════════════════════════════════════════════════════════════════ -/// Error from [`parse_int`] / [`parse_unsigned`] (`std.fmt.ParseIntError` port). +/// Error from [`parse_int`] / [`parse_unsigned`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ParseIntError { InvalidCharacter, @@ -852,7 +848,7 @@ pub enum ParseIntError { } impl ParseIntError { - /// Zig `@errorName(e)` — for callers that bubble the tag verbatim + /// For callers that bubble the tag verbatim /// (e.g. Postgres `CommandTag` Debug fmt). #[inline] pub const fn name(self) -> &'static str { @@ -864,9 +860,9 @@ impl ParseIntError { } /// Shared digit loop behind [`parse_int`] / [`parse_unsigned`]. `digits` has -/// any sign already stripped; `radix` is post-auto-detect (2..=36). Mirrors -/// `std.fmt.parseIntWithSign` body: skips embedded `_` separators, rejects -/// leading/trailing `_`, accumulates in `u128` with checked overflow. +/// any sign already stripped; `radix` is post-auto-detect (2..=36). Skips +/// embedded `_` separators, rejects leading/trailing `_`, accumulates in +/// `u128` with checked overflow. #[inline] fn parse_with_sign(digits: &[u8], radix: u8) -> Result { debug_assert!((2..=36).contains(&radix)); @@ -897,7 +893,7 @@ fn parse_with_sign(digits: &[u8], radix: u8) -> Result { } /// Strip an optional `0x`/`0o`/`0b` prefix when `radix == 0`; otherwise pass -/// through unchanged. Mirrors `std.fmt.parseIntWithSign` radix-0 branch. +/// through unchanged. #[inline] fn auto_radix(digits: &[u8], radix: u8) -> (&[u8], u8) { if radix != 0 { @@ -914,12 +910,12 @@ fn auto_radix(digits: &[u8], radix: u8) -> (&[u8], u8) { (digits, 10) } -/// `std.fmt.parseInt(T, buf, radix)` — parse an integer of type `T` from `buf`. +/// Parse an integer of type `T` from `buf`. /// /// `radix` ∈ 2..=36, or `0` to auto-detect from a `0x`/`0o`/`0b` prefix /// (defaulting to 10). Accepts an optional leading `+`/`-`. Embedded `_` -/// separators are skipped; leading/trailing `_` are rejected. Port keeps Zig's -/// error set: `Overflow` on range error, `InvalidCharacter` otherwise. +/// separators are skipped; leading/trailing `_` are rejected. Errors: +/// `Overflow` on range error, `InvalidCharacter` otherwise. /// /// Works directly on `&[u8]` so callers never need an intermediate /// `core::str::from_utf8` round-trip. @@ -951,7 +947,7 @@ where } } -/// `std.fmt.parseUnsigned(T, buf, radix)` — [`parse_int`] without sign +/// [`parse_int`] without sign /// handling: a leading `+`/`-` is `InvalidCharacter`. Use when the grammar /// being parsed forbids signs (semver components, HTTP status codes, /// content-length, etc.). @@ -965,10 +961,9 @@ where T::try_from(acc).map_err(|_| ParseIntError::Overflow) } -/// `std.fmt.parseInt(T, s, 10) catch null` — decimal convenience wrapper over +/// Decimal convenience wrapper over /// [`parse_int`]. Replaces the ~12 file-local -/// `fn parse_T(s:&[u8])->Option{ parse_int(s,10).ok() }` thin wrappers the -/// port spawned (Zig calls `std.fmt.parseInt` inline at every site). +/// `fn parse_T(s:&[u8])->Option{ parse_int(s,10).ok() }` thin wrappers. #[inline] pub fn parse_decimal(s: &[u8]) -> Option where @@ -978,7 +973,7 @@ where } // ────────────────────────────────────────────────────────────────────────── -// parse_double — `WTF.parseDouble` (src/jsc/WTF.zig:20 / bun.zig:1150) +// parse_double — `WTF::parseDouble` // // Partial-match JS-semantics double parse over Latin-1 bytes. Unlike // [`parse_f64`] this accepts a numeric *prefix* (`b"1.5x"` → `Ok(1.5)`) and @@ -986,15 +981,10 @@ where // // Lives in tier-0 `bun_core` (not `bun_jsc`) so `bun_interchange` (yaml/toml), // `bun_js_parser::lexer`, and `bun_install` can call it without taking a -// `bun_jsc` edge. `bun_core::wtf`, `bun_jsc::wtf`, and `bun::` re-export it -// to preserve the Zig namespace shape. -// -// TODO(port): Zig `bun.parseDouble` falls back to `std.fmt.parseFloat` under -// `comptime Environment.isWasm` (no WebKit link). Restore when wasm target is -// brought up. +// `bun_jsc` edge. `bun_core::wtf`, `bun_jsc::wtf`, and `bun::` re-export it. // ────────────────────────────────────────────────────────────────────────── -/// Error from [`parse_double`] — Zig `error{InvalidCharacter}`. +/// Error from [`parse_double`]. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct InvalidCharacter; @@ -1036,14 +1026,13 @@ unsafe extern "C" { fn WTF__parseDouble(bytes: *const u8, length: usize, counted: *mut usize) -> f64; } -/// `std.fmt.parseFloat(f64, buf)` — full-match parse of `s` as an `f64`. +/// Full-match parse of `s` as an `f64`. /// Returns `None` on empty input, trailing garbage (`b"1.5x"`), or non-numeric /// input. Backed by `WTF__parseDouble` (no `&str` round-trip — digits are /// ASCII, validation is wasted work). /// /// `WTF::parseDouble` rejects `inf`/`nan` (JS-number semantics); those are -/// special-cased here so callers ported from `std.fmt.parseFloat` keep the -/// same surface. +/// special-cased here. pub fn parse_f64(s: &[u8]) -> Option { if s.is_empty() { return None; @@ -1055,7 +1044,7 @@ pub fn parse_f64(s: &[u8]) -> Option { return Some(res); } if count == 0 { - // WTF__parseDouble doesn't recognise inf/nan; std.fmt.parseFloat does. + // WTF__parseDouble doesn't recognise inf/nan; handle them here. let (neg, rest) = match s[0] { b'-' => (true, &s[1..]), b'+' => (false, &s[1..]), @@ -1076,7 +1065,7 @@ pub fn parse_f64(s: &[u8]) -> Option { None // partial match → trailing garbage } -/// `parse_f64` truncated to `f32`. (Zig `std.fmt.parseFloat(f32, ..)`.) +/// `parse_f64` truncated to `f32`. #[inline] pub fn parse_f32(s: &[u8]) -> Option { parse_f64(s).map(|v| v as f32) @@ -1231,8 +1220,7 @@ impl Display for HostFormatter<'_> { // FormatValidIdentifier // ─────────────────────────────────────────────────────────────────────────── -/// Format a string to an ECMAScript identifier. -/// Unlike the string_mutable.zig version, this always allocate/copy +/// Format a string to an ECMAScript identifier. Always allocates/copies. pub fn fmt_identifier(name: &[u8]) -> FormatValidIdentifier<'_> { FormatValidIdentifier { name } } @@ -1457,7 +1445,7 @@ pub fn fmt_java_script( QuickAndDirtyJavaScriptSyntaxHighlighter { text, opts } } -/// snake_case alias of `fmt_java_script` (Zig: `fmtJavaScript`). Several +/// snake_case alias of `fmt_java_script`. Several /// downstream crates spell it `fmt_javascript`. #[inline] pub fn fmt_javascript( @@ -1513,8 +1501,7 @@ impl ColorCode { #[derive(Clone, Copy, PartialEq, Eq, IntoStaticStr)] #[strum(serialize_all = "snake_case")] -// bun.ComptimeEnumMap(Keyword) — Zig builds a comptime perfect-hash map keyed by @tagName. -// Mapped to `phf::Map<&'static [u8], Keyword>` in `Keywords::get` below. +// Perfect-hash map keyed by tag name: `phf::Map<&'static [u8], Keyword>` in `Keywords::get` below. pub enum Keyword { Abstract, As, @@ -1733,7 +1720,8 @@ pub enum RedactedKeyword { pub struct RedactedKeywords; impl RedactedKeywords { - // TODO(port): replace with phf::Map. + // 5 entries — a `matches!` chain beats a hash map at this size (the big + // keyword table in `Keywords::get` is where `phf` pays off). pub fn has(s: &[u8]) -> bool { matches!( s, @@ -1780,12 +1768,9 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { let code = keyword.color_code(); write!( writer, - // TODO(port): Output.prettyFmt("{s}{s}", true) - "{}{}{}{}", - Output::RESET, + crate::pretty_fmt!("{s}{s}", true), code.color(), bstr::BStr::new(&text[..i]), - Output::RESET, )?; } else { should_redact_value = @@ -1797,14 +1782,10 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { prev_keyword = None; if i < text.len() && text[i] == b'(' { - // TODO(port): Output.prettyFmt("{s}", true) write!( writer, - "{}{}{}{}", - Output::RESET, - Output::BOLD, + crate::pretty_fmt!("{s}", true), bstr::BStr::new(&text[..i]), - Output::RESET, )?; break 'write; } @@ -1814,15 +1795,10 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { | Keyword::Declare | Keyword::Type | Keyword::Interface => { - // TODO(port): Output.prettyFmt("{s}", true) write!( writer, - "{}{}{}{}{}", - Output::RESET, - Output::BOLD, - ColorCode::Blue.color(), + crate::pretty_fmt!("{s}", true), bstr::BStr::new(&text[..i]), - Output::RESET, )?; prev_keyword = None; break 'write; @@ -1887,8 +1863,7 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { let end = crate::strings_impl::index_of_char(text, b'\n') .unwrap_or(text.len()); text = &text[end..]; - // TODO(port): Output.prettyFmt("***", true) - write!(writer, "{}\x1b[33m***{}", Output::RESET, Output::RESET)?; + write!(writer, crate::pretty_fmt!("***", true))?; continue; } @@ -1926,13 +1901,10 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { } } - // TODO(port): Output.prettyFmt("{s}", true) write!( writer, - "{}\x1b[33m{}{}", - Output::RESET, + crate::pretty_fmt!("{s}", true), bstr::BStr::new(&text[..i]), - Output::RESET, )?; text = &text[i..]; } @@ -1954,13 +1926,10 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { i += 1; } - // TODO(port): Output.prettyFmt("{s}", true) write!( writer, - "{}\x1b[32m{}{}", - Output::RESET, + crate::pretty_fmt!("{s}", true), bstr::BStr::new(&text[..curly_start]), - Output::RESET, )?; writer.write_str("${")?; let mut opts = self.opts; @@ -1981,13 +1950,7 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { text = &text[i..]; i = 0; if !text.is_empty() && text[0] == char_ { - // TODO(port): Output.prettyFmt("`", true) - write!( - writer, - "{}\x1b[32m`{}", - Output::RESET, - Output::RESET - )?; + write!(writer, crate::pretty_fmt!("`", true))?; text = &text[1..]; continue 'outer; } @@ -2009,8 +1972,11 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { should_redact_value = false; if i > 2 && text[i - 1] == char_ { let len = i - 2; - // TODO(port): Output.prettyFmt("{c}", true) - write!(writer, "{}\x1b[32m{}", Output::RESET, char_ as char)?; + write!( + writer, + crate::pretty_fmt!("{s}", true), + char_ as char + )?; splat_byte_all(writer, b'*', len)?; write!(writer, "{}{}", char_ as char, Output::RESET)?; } else { @@ -2125,13 +2091,10 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { } if self.opts.redact_sensitive_information { - // TODO(port): Output.prettyFmt("{f}", true) write!( writer, - "{}\x1b[2m{}{}", - Output::RESET, + crate::pretty_fmt!("{f}", true), redacted_source(remain_to_print), - Output::RESET, )?; } else { write!( @@ -2227,8 +2190,7 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { text = &text[len..]; continue; } - // TODO(port): Output.prettyFmt(";", true) - write!(writer, "{}\x1b[2m;{}", Output::RESET, Output::RESET)?; + write!(writer, crate::pretty_fmt!(";", true))?; text = &text[1..]; } b'.' => { @@ -2255,14 +2217,10 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { } if i < text.len() && text[i] == b'(' { - // TODO(port): Output.prettyFmt("{s}", true) write!( writer, - "{}\x1b[3m{}{}{}", - Output::RESET, - Output::BOLD, + crate::pretty_fmt!("{s}", true), bstr::BStr::new(&text[..i]), - Output::RESET, )?; text = &text[i..]; continue; @@ -2292,11 +2250,9 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { } prev_keyword = None; - // Zig `while (cond) { i += 1 } else { i = 1; break :jsx; }` — Zig's - // while-else runs the else branch whenever the condition becomes false - // (i.e. on normal loop exit, since the body has no `break`). So the - // else ALWAYS fires here and the code below is dead in Zig too. - // TODO(port): Zig while-else always fires here — likely upstream bug, worth verifying. + // The identifier scan's result is intentionally + // discarded: `i` resets to 1 below, so only the + // leading `<` (or ` { impl Display for EnumTagListFormatter { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // PERF(port): Zig computed this at comptime as a single &'static str. + // PERF: could be precomputed as a single &'static str. let names = E::VARIANTS; for (i, name) in names.iter().enumerate() { if LIST { @@ -2389,13 +2345,13 @@ pub fn enum_tag_list() -> EnumTagListF // formatIp // ─────────────────────────────────────────────────────────────────────────── -// TODO(port): `std.net.Address` — bun_core stays I/O-free; this should accept a -// bun_sys/bun_net Address type. Logic preserved against a placeholder Display. +// bun_core stays I/O-free, so this accepts any `Display` (callers pass their +// own address type). pub fn format_ip<'a>( address: &impl Display, into: &'a mut [u8], ) -> Result<&'a mut [u8], crate::Error> { - // std.net.Address.format includes `:` and square brackets (IPv6) + // The `Display` form includes `:` and square brackets (IPv6) // while Node does neither. This uses format then strips these to bring // the result into conformance with Node. use std::io::Write; @@ -2403,10 +2359,10 @@ pub fn format_ip<'a>( write!(cursor, "{}", address).map_err(|_| crate::err!("NoSpaceLeft"))?; let written = cursor.position() as usize; - // PORT NOTE: reshaped for borrowck — compute (start, end) offsets against - // `into` instead of iteratively reborrowing a `result` slice, so the final + // 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. Semantics match Zig's `result = result[a..b]` chain exactly. + // cleanly. let mut start = 0usize; let mut end = written; @@ -2423,12 +2379,12 @@ pub fn format_ip<'a>( } // ─────────────────────────────────────────────────────────────────────────── -// count (std.fmt.count) +// count // ─────────────────────────────────────────────────────────────────────────── // ───────────────────────── CountingWriter / Null ───────────────────────── -// One type subsumes Zig's `std.Io.Writer.Discarding` (null sink) and the -// removed `std.io.countingWriter(inner)` (forwarding wrapper). Implements +// One type subsumes a pure discarding sink and a counting forwarding +// wrapper. Implements // `core::fmt::Write` so it can replace the per-crate private `CountingWriter` // reinventions (clap). The byte-level `bun_io::Write` counting sink stays in // `bun_io::DiscardingWriter` (different trait, sits above bun_core). @@ -2443,7 +2399,7 @@ impl fmt::Write for Null { } /// Counts every byte written; optionally forwards to a wrapped `fmt::Write`. -/// `inner: None` ⇒ pure discarding sink (Zig `Writer.Discarding`). +/// `inner: None` ⇒ pure discarding sink. pub struct CountingWriter<'a, W: fmt::Write = Null> { inner: Option<&'a mut W>, /// Total bytes written so far (counted before forwarding). @@ -2489,12 +2445,10 @@ impl fmt::Write for CountingWriter<'_, W> { } } -/// Port of `std.fmt.count`: number of bytes the formatted args would produce. +/// Number of bytes the formatted args would produce. /// -/// Zig drives a `Writer.Discarding` (64-byte scratch buffer that drops writes -/// and tallies length); Rust's `fmt::Arguments` plugs into the same shape via -/// a `fmt::Write` impl that only sums `s.len()`. No allocation, no UTF-8 -/// validation beyond what the formatter already did. +/// `fmt::Arguments` drives a `fmt::Write` impl that only sums `s.len()`. +/// No allocation, no UTF-8 validation beyond what the formatter already did. #[inline] pub fn count(args: fmt::Arguments<'_>) -> usize { // Implementation sunk to T0 so `bun_alloc` (which sits below `bun_core`) @@ -2510,8 +2464,6 @@ pub fn count(args: fmt::Arguments<'_>) -> usize { // • fast_digit_count(u64)->u64 — Lemire 32-entry table; PANICKED on x ≥ 2³² // (table OOB) despite its u64 signature. // • count_int(i64)->usize — /=10 loop, full i64 incl. MIN, +1 for '-'. -// Zig has the same split (bun.fmt.fastDigitCount vs std.fmt.count("{d}",..)); -// unifying here improves on the original. // ─────────────────────────────────────────────────────────────────────────── /// Decimal digit count of an unsigned 64-bit integer — i.e. the byte length @@ -2671,7 +2623,7 @@ impl Display for SizeFormatter { const MAGS_SI: &[u8] = b" KMGTPEZY"; let log2 = (usize::BITS - 1 - value.leading_zeros()) as usize; - // comptime math.log2(1000) == 9 (integer log2) + // integer log2(1000) == 9 let magnitude = (log2 / 9).min(MAGS_SI.len() - 1); let new_value = value as f64 / 1000f64.powf(magnitude as f64); let suffix = MAGS_SI[magnitude]; @@ -2698,13 +2650,11 @@ impl Display for SizeFormatter { } } -// TODO(port): Zig `size(bytes: anytype, ...)` switched on @TypeOf(bytes) for -// f64/f32/f128 (intFromFloat) and i64/isize (intCast). Expose typed helpers. pub fn size(bytes: usize, opts: SizeFormatterOptions) -> SizeFormatter { SizeFormatter { value: bytes, opts } } -/// Short-name alias of `size(.., default)` for `{B}`-style formatting -/// (Zig: `bun.fmt.bytes`). Downstream: `bun_fmt::bytes(rss)`. +/// Short-name alias of `size(.., default)` for `{B}`-style formatting. +/// Downstream: `bun_fmt::bytes(rss)`. #[inline] pub fn bytes(n: usize) -> SizeFormatter { SizeFormatter { @@ -2713,8 +2663,8 @@ pub fn bytes(n: usize) -> SizeFormatter { } } -/// Lowercase hex encode into `out` (must be `2 * input.len()`). Port of -/// `std.fmt.bytesToHex(.., .lower)` as used by Bun's hash printers. +/// Lowercase hex encode into `out` (must be `2 * input.len()`). Used by +/// Bun's hash printers. pub fn bytes_to_hex_lower(input: &[u8], out: &mut [u8]) -> usize { debug_assert!(out.len() >= input.len() * 2); for (i, &b) in input.iter().enumerate() { @@ -2738,9 +2688,9 @@ pub fn size_f64(bytes: f64, opts: SizeFormatterOptions) -> SizeFormatter { } } pub fn size_i64(bytes: i64, opts: SizeFormatterOptions) -> SizeFormatter { - // PORT NOTE: Zig's `@intCast(bytes)` is unchecked in release (UB-wraps negative); - // clamp to 0 instead of panicking so release builds never crash on a transiently - // negative size, while keeping the safe-build trap via debug_assert. + // Clamp to 0 instead of panicking so release builds never crash on a + // transiently negative size, while keeping the safe-build trap via + // debug_assert. debug_assert!(bytes >= 0); SizeFormatter { value: bytes.max(0) as usize, @@ -2752,7 +2702,7 @@ pub fn size_i64(bytes: i64, opts: SizeFormatterOptions) -> SizeFormatter { // Hex formatters // ─────────────────────────────────────────────────────────────────────────── -/// Port of Zig `std.fmt`'s `{x}` / `{X}` on a `[]const u8` — prints each byte +/// Prints each byte /// as two hex digits with no separator. `LOWER == true` → lowercase, else /// uppercase. Used by `Lockfile::MetaHashFormatter` and tmp-lockfile naming. pub struct HexBytes<'a, const LOWER: bool>(pub &'a [u8]); @@ -2775,16 +2725,14 @@ impl<'a, const LOWER: bool> Display for HexBytes<'a, LOWER> { } } -/// Ergonomic constructor for the lowercase `HexBytes` Display adapter — port of -/// Zig `std.fmt.bytesToHex(.., .lower)` at format-arg call sites (`{x}` on -/// `[]const u8`). Avoids turbofish at every caller. +/// Ergonomic constructor for the lowercase `HexBytes` Display adapter. +/// Avoids turbofish at every caller. #[inline] pub fn hex_lower(bytes: &[u8]) -> HexBytes<'_, true> { HexBytes(bytes) } -/// Ergonomic constructor for the uppercase `HexBytes` Display adapter — port of -/// Zig `std.fmt.bytesToHex(.., .upper)` / the `{X}` format spec on `[]const u8`. +/// Ergonomic constructor for the uppercase `HexBytes` Display adapter. /// Pairs with [`hex_lower`]; avoids turbofish at every caller. #[inline] pub fn hex_upper(bytes: &[u8]) -> HexBytes<'_, false> { @@ -2821,8 +2769,6 @@ pub const HEX_DECODE_TABLE: [u8; 256] = { /// Returns `None` for any other byte. Callers needing a wider int cast with `as u16/u32` /// or `.map(u32::from)`; callers needing `Result` use `.ok_or(..)`; callers with a /// pre-validated byte use `.unwrap()`. -/// -/// Zig precedent: `std.fmt.charToDigit(c, 16)` / `bun.strings.toASCIIHexValue`. #[inline] pub const fn hex_digit_value(b: u8) -> Option { match b { @@ -2852,8 +2798,6 @@ pub const fn hex_digit_value_u32(c: u32) -> Option { /// Returns `None` if either byte is not `[0-9a-fA-F]`. Callers adapt the /// error channel exactly as for [`hex_digit_value`]: `.ok_or(..)?` for /// `Result`, `.unwrap()` when pre-validated, `?` in `Option` context. -/// -/// Zig precedent: inner loop of `std.fmt.hexToBytes`. #[inline] pub const fn hex_pair_value(hi: u8, lo: u8) -> Option { match (hex_digit_value(hi), hex_digit_value(lo)) { @@ -2898,9 +2842,6 @@ pub const fn parse_hex4(input: &[u8]) -> Option { /// This is the *prefix* primitive — it never fails. Callers needing exact-N /// semantics (e.g. `\uHHHH`) check `digits_consumed == N` afterward; callers /// needing a narrower result cast (`value as u8`, `value as i32`). -/// -/// Zig precedent: none (each module hand-rolls); analogous to a bounded -/// `std.fmt.parseInt(u32, prefix, 16)` that also reports how much it ate. #[inline] pub fn parse_hex_prefix(input: &[u8], max_digits: usize) -> (u32, usize) { let mut value: u32 = 0; @@ -2918,9 +2859,8 @@ pub fn parse_hex_prefix(input: &[u8], max_digits: usize) -> (u32, usize) { } /// Decode a `2 * size_of::()`-char ASCII hex slice into `T` via native-endian -/// byte reinterpretation. Mirrors Zig's `parseHexToInt` (DevServer.zig:961): -/// `std.fmt.hexToBytes` into `[@sizeOf(T)]u8` then `@bitCast` — i.e. pairwise -/// hex-decode then `from_ne_bytes`, **not** a big-endian numeric accumulator. +/// byte reinterpretation — i.e. pairwise hex-decode then `from_ne_bytes`, +/// **not** a big-endian numeric accumulator. /// `"0100000000000000"` → `1u64` on little-endian. /// /// Returns `None` if `slice.len() != 2 * size_of::()` or any byte is not @@ -2955,9 +2895,7 @@ pub const fn hex_char_upper(n: u8) -> u8 { } /// Encode a single byte as two lowercase ASCII hex digits `[hi, lo]`. -/// Port of the open-coded `CHARSET[(b>>4)] / CHARSET[(b&0xF)]` pair found -/// throughout the Zig sources. For contiguous full-slice output prefer -/// [`bytes_to_hex_lower`]. +/// For contiguous full-slice output prefer [`bytes_to_hex_lower`]. #[inline] pub const fn hex_byte_lower(b: u8) -> [u8; 2] { [ @@ -3024,10 +2962,8 @@ pub const fn hex_u16(v: u16) -> [u8; 4] { ] } -// TODO(port): Zig parameterizes on `comptime Int: type` and computes -// `BufType = [@bitSizeOf(Int) / 4]u8`. Rust const generics can't derive an array -// length from a type's bit-width. Represent as a generic over u64 with explicit -// nibble count; add per-width helpers if this shows up on a hot path. +// Const generics can't derive an array length from a type's bit-width, so +// this is generic over u64 with an explicit nibble count. pub struct HexIntFormatter { pub value: u64, } @@ -3040,7 +2976,6 @@ impl HexIntFormatter { &UPPER_HEX_TABLE }; let mut buf = [0u8; NIBBLES]; - // PERF(port): Zig used `inline for`; plain loop here. for (i, c) in buf.iter_mut().enumerate() { // value relative to the current nibble let shift = ((NIBBLES - i - 1) * 4) as u32; @@ -3073,8 +3008,7 @@ pub fn hex_int_upper(value: u64) -> HexIntFormatter(v: u64) -> [u8; N] { HexIntFormatter::::get_out_buf(v) @@ -3082,8 +3016,7 @@ pub fn u64_hex_fixed(v: u64) -> [u8; N] { /// Format a 6-byte MAC address as `xx:xx:xx:xx:xx:xx` (lowercase hex, /// colon-separated). Returns a fixed 17-byte ASCII buffer; borrow as `&[u8]` -/// for `ZigString::init`. Port of the inline `std.fmt.bufPrint(.., "{x:0>2}:..")` -/// pattern duplicated at `node_os.zig:686` and `:800`. +/// for `ZigString::init`. #[inline] pub fn mac_address_lower(mac: [u8; 6]) -> [u8; 17] { let mut out = [b':'; 17]; @@ -3097,8 +3030,7 @@ pub fn mac_address_lower(mac: [u8; 6]) -> [u8; 17] { } /// `{:0N}` — zero-padded fixed-width decimal of a `u64` into `[u8; N]`. -/// Decimal sibling of [`u64_hex_fixed`] / [`hex_byte_upper`]. Port of Zig -/// `std.fmt.printInt(.., .{.width=N, .fill='0'})`. Caller guarantees +/// Decimal sibling of [`u64_hex_fixed`] / [`hex_byte_upper`]. Caller guarantees /// `val < 10^N`; excess high digits are silently dropped (debug-asserted). #[inline(always)] pub fn itoa_padded(mut val: u64) -> [u8; N] { @@ -3148,9 +3080,8 @@ impl Display for TrimmedPrecisionFormatter { let rem = self.num - whole; if rem != 0.0 { // buf size = "0." + PRECISION digits - // PORT NOTE: Zig used `[2 + precision]u8` stack array; Rust const-generic array - // length arithmetic is unstable, so use a small fixed upper bound and - // const-assert it suffices (matches Zig's compile-time sizing guarantee). + // Const-generic array length arithmetic is unstable, so use a small + // fixed upper bound and const-assert it suffices. const { assert!( PRECISION + 3 <= 32, @@ -3193,7 +3124,7 @@ use crate::time::{ NS_PER_DAY, NS_PER_HOUR, NS_PER_MIN, NS_PER_MS, NS_PER_S, NS_PER_US, NS_PER_WEEK, }; -/// This is copied from std.fmt.formatDuration, except it will only print one decimal instead of three +/// Format a duration, printing one decimal place instead of three. fn format_duration_one_decimal( data: FormatDurationData, writer: &mut impl fmt::Write, @@ -3222,7 +3153,6 @@ fn format_duration_one_decimal( } let mut ns_remaining = data.ns; - // PERF(port): Zig used `inline for` over a tuple of structs. const COARSE: [(u64, u8); 5] = [ (365 * NS_PER_DAY, b'y'), (NS_PER_WEEK, b'w'), @@ -3296,8 +3226,7 @@ pub enum ConnTimeoutKind { /// Render the canonical SQL connection-timeout error message. /// /// `ms` is converted to nanoseconds with a saturating multiply and rendered -/// through [`fmt_duration_one_decimal`] (matching the Zig backends' inline -/// `bun.fmt.fmtDurationOneDecimal(ms * std.time.ns_per_ms)`). `suffix` is +/// through [`fmt_duration_one_decimal`]. `suffix` is /// appended verbatim — used for the per-status `(sent startup message…)` / /// `(during authentication)` tails. pub fn fmt_conn_timeout(kind: ConnTimeoutKind, ms: u32, suffix: &str) -> impl Display + '_ { @@ -3327,7 +3256,6 @@ pub fn fmt_slice<'a, T: AsRef<[u8]>>(data: &'a [T], delim: &'static str) -> Form pub struct FormatSlice<'a, T: AsRef<[u8]>> { pub slice: &'a [T], - // PERF(port): Zig `delim` was a comptime []const u8 — runtime here. pub delim: &'static str, } @@ -3360,7 +3288,6 @@ pub struct FormatDouble { pub number: f64, } -// TODO(port): move to _sys unsafe extern "C" { // `&mut [u8; 124]` is ABI-identical to the C `char *` argument (thin // non-null pointer to 124 writable bytes); the type encodes WTF__dtoa's @@ -3393,17 +3320,16 @@ impl Display for FormatDouble { } /// Downstream alias — several callers (ConsoleObject) refer to this as -/// `bun_core::fmt::DoubleFormatter` (matching the Zig "formatter" naming -/// convention rather than the `Format*` struct convention used here). +/// `bun_core::fmt::DoubleFormatter` (the "formatter" naming convention +/// rather than the `Format*` struct convention used here). pub type DoubleFormatter = FormatDouble; // ─── Integer → ASCII ─────────────────────────────────────────────────────── // One path for every base-10 integer write. Backed by the `itoa` crate (LUT // 2-digits-at-a-time — same code serde_json/cssparser ship), already in the -// workspace link graph. Replaces the three competing impls the port grew: +// workspace link graph. Replaces three competing impls: // `core::fmt`-via-SliceCursor (slow + silent-truncate footgun), the hand- // rolled `itoa_u64` reverse-fill, and tcc_sys's private `itoa::Buffer` use. -// Zig has exactly one path (`std.fmt.printInt`); this restores that parity. /// Stack scratch for [`itoa`]. 40 bytes — fits `i128::MIN`. `::new()` is a /// const no-op (uninit array), so declare it inline at the call site. @@ -3435,7 +3361,7 @@ pub const fn pow10_exp_1e4_to_1e9(val: u64) -> Option { } } -/// Port of `std.fmt.printInt(buf, value, 10, .lower, .{})`: format `value` +/// Format `value` /// into `buf` as base-10 ASCII and return the number of bytes written. /// Panics if `buf` is too small — callers size the buffer by the type's max /// digit count. Use [`itoa`] directly when you can own a fresh [`ItoaBuf`]; @@ -3448,8 +3374,7 @@ pub fn print_int(buf: &mut [u8], value: T) -> usize { s.len() } -/// [`print_int`] returning the written sub-slice of `buf` — the moral -/// equivalent of Zig's `std.fmt.bufPrint(&buf, "{d}", .{v}) catch unreachable`. +/// [`print_int`] returning the written sub-slice of `buf`. /// Use this when the caller wants the bytes; use [`print_int`] directly when /// writing at an offset and only the byte-count is needed. #[inline] @@ -3461,8 +3386,7 @@ pub fn int_as_bytes(buf: &mut [u8], value: T) -> &[u8] { /// NUL-terminated decimal `u64` → ASCII into a caller-owned scratch buffer, /// returning a [`CStr`] borrowing the head of `buf`. /// -/// This is the Rust analogue of Zig's `std.fmt.bufPrintZ(&buf, "{d}", .{n})` -/// — used when handing an integer to a C API that wants a `*const c_char` +/// Used when handing an integer to a C API that wants a `*const c_char` /// service/port string (e.g. `getaddrinfo`, `ares_getaddrinfo`). 21 bytes /// covers `u64::MAX` (20 digits) + NUL; `u16`/`u32` callers widen via `as u64`. #[inline] @@ -3476,8 +3400,8 @@ pub fn itoa_z(buf: &mut [u8; 21], n: u64) -> &core::ffi::CStr { unsafe { core::ffi::CStr::from_bytes_with_nul_unchecked(&buf[..=s.len()]) } } -/// Byte length of `n` formatted with the default `{}` Display — moral -/// equivalent of Zig's `std.fmt.count("{d}", .{n})`. Used by ConsoleObject +/// Byte length of `n` formatted with the default `{}` Display. Used by +/// ConsoleObject /// width tracking for `%f` substitutions. #[inline] pub fn count_float(n: f64) -> usize { @@ -3540,7 +3464,7 @@ fn escape_powershell_impl(str: &[u8], writer: &mut impl fmt::Write) -> fmt::Resu write_bytes(writer, remain) } -// js_bindings (fmtString for highlighter.test.ts) lives in src/jsc/fmt_jsc.zig +// js_bindings (fmtString for highlighter.test.ts) lives in src/jsc/fmt_jsc.rs // alongside fmt_jsc.bind.ts; bun_core/ stays JSC-free. // ─────────────────────────────────────────────────────────────────────────── @@ -3703,7 +3627,7 @@ fn truncated_hash32_impl(int: u64, writer: &mut impl fmt::Write) -> fmt::Result } /// Const-fn core of [`truncated_hash32`] / [`TruncatedHash32`]: the 8-byte -/// base32-ish encoding (native-endian, matches Zig `@bitCast([8]u8, int)`). +/// base32-ish encoding (native-endian byte reinterpretation). /// Exposed so const contexts (e.g. `js_parser::generated_symbol_name!`) can /// share the single alphabet table instead of copy-pasting it. pub const fn truncated_hash32_bytes(int: u64) -> [u8; 8] { @@ -3721,8 +3645,8 @@ pub const fn truncated_hash32_bytes(int: u64) -> [u8; 8] { ] } -/// Zero-validation `&[u8] -> impl Display` adapter — alias of [`raw`] named to -/// read like Zig's `{s}` specifier at call sites (`bun_fmt::s(name)`). +/// Zero-validation `&[u8] -> impl Display` adapter — short alias of [`raw`] +/// for terse call sites (`bun_fmt::s(name)`). #[inline(always)] pub const fn s(bytes: &[u8]) -> Raw<'_> { Raw(bytes) @@ -3734,7 +3658,7 @@ pub const fn s(bytes: &[u8]) -> Raw<'_> { #[inline] fn write_bytes(w: &mut impl fmt::Write, bytes: &[u8]) -> fmt::Result { - // SAFETY: see `s()` above — Zig's `{s}` path, callers feed ASCII/utf8. + // SAFETY: see `s()` above — callers feed valid ASCII/UTF-8. w.write_str(unsafe { core::str::from_utf8_unchecked(bytes) }) } @@ -3748,17 +3672,15 @@ fn splat_byte_all(w: &mut impl fmt::Write, byte: u8, count: usize) -> fmt::Resul } // ════════════════════════════════════════════════════════════════════════════ -// std.json.encodeJsonString — single canonical port. -// Zig: vendor/zig/lib/std/json/Stringify.zig:670 (encodeJsonString → -// encodeJsonStringChars → outputSpecialEscape). Every Rust copy that was -// hand-ported from a Zig `std.json.fmt(...)` call funnels through here. +// encode_json_string — single canonical impl. +// Every duplicated copy of this JSON-string escaping logic funnels through +// here. // ════════════════════════════════════════════════════════════════════════════ -/// Port of Zig stdlib `std.json.encodeJsonStringChars` with default options -/// (`escape_unicode = false`): writes the escaped body of a JSON string -/// **without** surrounding quotes. +/// Writes the escaped body of a JSON string **without** surrounding quotes +/// (`escape_unicode = false` semantics). /// -/// Escape set (matches `outputSpecialEscape` exactly): +/// Escape set: /// - `\"` `\\` `\b` `\f` `\n` `\r` `\t` /// - other `0x00..=0x1F` → `\u00XX` (lowercase hex) /// - `0x20..=0xFF` → emitted verbatim in run-batched `write_str` calls @@ -3846,13 +3768,10 @@ pub fn encode_json_string_chars_latin1(w: &mut impl fmt::Write, s: &[u8]) -> fmt Ok(()) } -/// Port of Zig stdlib `std.json.encodeJsonString`: surrounding `"` quotes -/// around [`encode_json_string_chars`]. +/// Surrounding `"` quotes around [`encode_json_string_chars`]. #[inline] pub fn encode_json_string(w: &mut impl fmt::Write, s: &[u8]) -> fmt::Result { w.write_char('"')?; encode_json_string_chars(w, s)?; w.write_char('"') } - -// ported from: src/bun_core/fmt.zig diff --git a/src/bun_core/heap.rs b/src/bun_core/heap.rs index 304bd155a79..ffc41a79a3b 100644 --- a/src/bun_core/heap.rs +++ b/src/bun_core/heap.rs @@ -1,15 +1,13 @@ //! Centralized heap-pointer round-trip helpers. //! -//! Zig's `bun.TrivialNew(@This())` / `bun.destroy(this)` / `bun.new(T, init)` -//! pattern was ported file-by-file as open-coded `Box::into_raw(Box::new(..))` -//! / `drop(Box::from_raw(..))` pairs (~1.8k occurrences). Per -//! `docs/RUST_PATTERNS.md` §5, every one of those is an unchecked ownership -//! transfer with the invariant restated in a `// SAFETY:` comment at every -//! site. +//! Open-coded `Box::into_raw(Box::new(..))` / `drop(Box::from_raw(..))` pairs +//! (~1.8k occurrences) are unchecked ownership transfers with the invariant +//! restated in a `// SAFETY:` comment at every site (per +//! `docs/RUST_PATTERNS.md` §5). //! //! These are **thin aliases** — they do not reduce per-site proof //! obligations (each `take`/`destroy` is still its own `unsafe { }` block). -//! They exist for vocabulary consistency with the Zig spelling and as the +//! They exist for vocabulary consistency and as the //! shared primitive *inside* the typed shims; they are NOT the safety //! deliverable. The deliverable is the typed `Box`-taking entry points //! that own BOTH halves of the round-trip: @@ -27,9 +25,8 @@ //! All four are `#[inline(always)]` no-ops — identical machine code to the //! open-coded `Box::into_raw`/`from_raw`. -/// Heap-allocate `value` and return the raw pointer (Zig: `bun.new(T, init)` / -/// `bun.TrivialNew`). Ownership transfers to the caller; pair with [`destroy`] -/// or [`take`]. +/// Heap-allocate `value` and return the raw pointer. +/// Ownership transfers to the caller; pair with [`destroy`] or [`take`]. #[inline(always)] pub fn alloc(value: T) -> *mut T { Box::into_raw(Box::new(value)) @@ -85,8 +82,7 @@ pub unsafe fn take(ptr: *mut T) -> Box { unsafe { Box::from_raw(ptr) } } -/// Drop a heap allocation previously produced by [`alloc`] / [`leak`] -/// (Zig: `bun.destroy(this)` / `bun.TrivialDeinit`). +/// Drop a heap allocation previously produced by [`alloc`] / [`leak`]. /// /// # Safety /// Same as [`take`]. diff --git a/src/bun_core/hint.rs b/src/bun_core/hint.rs index c299887c813..8ec6e309541 100644 --- a/src/bun_core/hint.rs +++ b/src/bun_core/hint.rs @@ -1,7 +1,6 @@ -//! Branch-prediction hints — Rust port of Zig's `@branchHint`. +//! Branch-prediction hints. -/// Mark the surrounding branch as cold/unlikely. Port of Zig -/// `@branchHint(.unlikely)` / `@branchHint(.cold)` (docs/PORTING.md:211). +/// Mark the surrounding branch as cold/unlikely. /// /// Calling a `#[cold]` callee makes LLVM treat the *call site's* basic block /// as cold and lay it out off the hot path. `#[inline(never)]` is required: diff --git a/src/bun_core/lib.rs b/src/bun_core/lib.rs index 3f554c8f547..4434af71e54 100644 --- a/src/bun_core/lib.rs +++ b/src/bun_core/lib.rs @@ -23,9 +23,7 @@ pub mod util; pub use atomic_cell::{Atom, AtomicCell, ThreadCell}; /// Shared state-machine tag for the streaming (de)compressors in -/// `bun_brotli` / `bun_zlib` / `bun_zstd`. Mirrors the identical -/// `pub const State = enum { Uninitialized, Inflating, End, Error }` -/// nested in each Zig reader/compressor struct. +/// `bun_brotli` / `bun_zlib` / `bun_zstd`. pub mod compress { #[derive(Clone, Copy, PartialEq, Eq)] pub enum State { @@ -80,8 +78,8 @@ pub use external_shared::{ pub mod bounded_array; pub use bounded_array::{BoundedArray, BoundedArrayAligned}; -/// Bit-cast between fn-pointer types. Replaces Zig `@ptrCast` on a function -/// pointer when erasing only the *pointee type* of one or more thin-pointer +/// Bit-cast between fn-pointer types — for erasing only the *pointee type* +/// of one or more thin-pointer /// parameters (e.g. `extern "C" fn(*mut Ctx, …)` ↔ `extern "C" fn(*mut c_void, /// …)`). Const-generic `transmute` rejects fn types; an as-cast can't change /// arity. This stays one audited helper and rejects non-pointer-sized @@ -105,8 +103,8 @@ pub const unsafe fn cast_fn_ptr(f: F) -> G { assert!(core::mem::align_of::() == core::mem::align_of::()); } // SAFETY: caller contract — `F` and `G` are ABI-identical fn pointers. - // `read` of a pointer-sized `Copy` value through a same-size cast is the - // bitwise reinterpretation `@ptrCast` performs. + // `read` of a pointer-sized `Copy` value through a same-size, same-align + // cast is a defined bitwise reinterpretation of the fn pointer. unsafe { (&raw const f).cast::().read() } } @@ -215,7 +213,7 @@ unsafe impl Send for RawSlice {} // SAFETY: same reasoning as the `Send` impl above — `&[T]: Sync ⇔ T: Sync`. unsafe impl Sync for RawSlice {} -/// Port of Zig's `std.os.environ` global (`[][*:0]u8`). On Windows the +/// Process-global envp slice. On Windows the /// startup path `bun_sys::windows::env::convert_env_to_wtf8` overwrites this /// with a WTF-8-encoded envp slice; `getenvZ` then reads it via /// `os::environ()`. POSIX builds leave it empty and use libc's `environ`. @@ -224,14 +222,12 @@ pub mod os { use core::ffi::c_char; // Stored as raw (ptr, len) — NOT `&'static mut [_]` — so `environ()` (which - // hands out a shared `&[_]`) never aliases a live `&mut`. Zig's - // `std.os.environ` is a plain slice global with no exclusivity guarantee; - // mirroring that with `&'static mut` would be UB the moment a reader + // hands out a shared `&[_]`) never aliases a live `&mut`. A `&'static mut` + // global would be UB the moment a reader // borrows while a writer holds the swapped-out `&mut`. static mut ENVIRON: (*mut *mut c_char, usize) = (core::ptr::null_mut(), 0); - /// Swap in a new envp slice; returns the previous (ptr, len) pair (Zig: - /// `orig_environ = std.os.environ; std.os.environ = new`). + /// Swap in a new envp slice; returns the previous (ptr, len) pair. /// SAFETY: single-threaded startup only. pub unsafe fn take_environ() -> (*mut *mut c_char, usize) { // `&raw mut` (no intermediate `&mut`) — `static_mut_refs` is hard-denied @@ -245,7 +241,7 @@ pub mod os { core::ptr::write(&raw mut ENVIRON, (ptr, len)); } } - /// Borrowed view of the current envp slice (read side of `std.os.environ`). + /// Borrowed view of the current envp slice (read side of the `ENVIRON` global). /// SAFETY: caller must not race with `set_environ`. pub unsafe fn environ() -> &'static [*mut c_char] { unsafe { @@ -272,7 +268,7 @@ pub mod path_sep { // ─── u8 const fns (kept const for match-guard / const-eval callers) ───── - /// Zig: `bun.path.isSepAny` — `/` **or** `\` on every target. + /// `/` **or** `\` on every target. /// Use for parsing user-supplied / cross-platform path strings (tsconfig, /// archive entries, Windows drive prefixes). #[inline(always)] @@ -314,9 +310,9 @@ pub mod path_sep { } } - /// Host-OS-native absolute-path predicate (Zig: `std.fs.path.isAbsolute`). + /// Host-OS-native absolute-path predicate. /// POSIX: leading `/`. Windows: leading `/` or `\`, or 3-byte `X:/`|`X:\` - /// — faithful to Zig std: **no** alphabetic gate on the drive byte, and a + /// — **no** alphabetic gate on the drive byte, and a /// bare `X:` with no trailing separator is **not** absolute. /// /// Sunk from `bun_paths::is_absolute` so tier-0 (`util::which`) and @@ -341,8 +337,8 @@ pub mod path_sep { } // ─── libm shims ─────────────────────────────────────────────────────────────── -// Canonical extern for libm's `powf`/`pow` (Zig: `bun.zig` `pub extern "c" fn -// powf`). Hot CSS color-space conversion paths (gam_srgb, lab, prophoto) call +// Canonical extern for libm's `powf`/`pow`. +// Hot CSS color-space conversion paths (gam_srgb, lab, prophoto) call // the safe wrapper below; keep `#[inline]` so cross-crate use stays a direct // libm call. unsafe extern "C" { @@ -365,9 +361,7 @@ pub mod vec { /// /// Equivalent to `for i in 0..n { v.push(f(i)) }` but reserves once and /// writes through `spare_capacity_mut()` so no per-element capacity check - /// or length bump occurs in the hot loop. Replaces the Zig-ported - /// `reserve; ptr::write…; set_len` blocks where the fill is a pure - /// per-index function (constant, default, or `i`-derived). + /// or length bump occurs in the hot loop. /// /// Panic-safety: if `f` panics at index `k`, `v.len()` is left at its /// original value plus `k` — every exposed element is initialized, and the @@ -395,8 +389,7 @@ pub mod vec { unsafe { v.set_len(prev + n) }; } - /// Append `n` copies of `value` to `v`. Zig: `std.ArrayList.appendNTimes` — - /// `ensureUnusedCapacity(n); @memset(unusedCapacitySlice()[0..n], value); len += n`. + /// Append `n` copies of `value` to `v`. /// /// Unlike `v.extend(repeat_n(value, n))` or a `for _ { v.push(value) }` loop, /// this reserves once and fills via `[MaybeUninit]::fill` (lowers to @@ -416,9 +409,7 @@ pub mod vec { /// Extend `v` by `n` `T::default()` elements and return a mutable slice /// of the newly-appended tail (`&mut v[prev_len .. prev_len + n]`). /// - /// Replaces the Zig-ported `reserve(n); set_len(len+n); &mut v[len..]` - /// pattern (S022) where the tail is immediately overwritten by a clone/ - /// fill loop — the default-fill keeps every exposed `T` valid even if the + /// The default-fill keeps every exposed `T` valid even if the /// caller bails partway through writing. #[inline] pub fn grow_default(v: &mut Vec, n: usize) -> &mut [T] { @@ -428,7 +419,7 @@ pub mod vec { } /// Reserve `additional`, advance `len` by `additional`, and return the - /// newly-exposed (uninitialized) tail. Zig `ArrayList.addManyAsSlice`. + /// newly-exposed (uninitialized) tail. /// Generic free-fn form of `bun_collections::VecExt::writable_slice` so /// `bun_core::string` can call it without a `bun_collections` edge. /// @@ -445,8 +436,7 @@ pub mod vec { } /// As [`writable_slice`] but skips `reserve`; caller must guarantee - /// `len + additional <= capacity` (debug-asserted). Zig: - /// `ArrayList.addManyAsSliceAssumeCapacity`. + /// `len + additional <= capacity` (debug-asserted). /// /// # Safety /// `v.len() + additional <= v.capacity()`, and the returned slice must be @@ -469,9 +459,7 @@ pub mod vec { /// /// `n == 0` is a no-op; `n >= len` degenerates to `clear()` (capacity /// retained). All current callers are `Vec` ring/line buffers - /// shifting a consumed prefix down after a partial write/parse — the Zig - /// port open-coded `std.mem.copyForwards` + `items.len -= n` at every - /// site. + /// shifting a consumed prefix down after a partial write/parse. #[inline] pub fn drain_front(v: &mut Vec, n: usize) { if n == 0 { @@ -486,10 +474,9 @@ pub mod vec { v.truncate(len - n); } - // ── Zig `ArrayList(u8).unusedCapacitySlice()` family ────────────────── - // The Zig stdlib has ONE helper and ~30 call sites; the Rust port had - // grown 11 hand-rolled `spare_capacity_mut().as_mut_ptr().cast::()` - // + `set_len(len+n)` copies because `spare_capacity_mut` returns + // ── Spare-capacity fill helpers ──────────────────────────────────────── + // Consolidates 11 hand-rolled `spare_capacity_mut().as_mut_ptr().cast::()` + // + `set_len(len+n)` copies: `spare_capacity_mut` returns // `MaybeUninit` and every C-ABI fill site (read/recv/pread, simdutf, // zlib, zstd, libdeflate, base64) needs `*mut u8` / `&mut [u8]`. @@ -526,9 +513,8 @@ pub mod vec { unsafe { spare_bytes_mut(v) } } - /// View the **entire** allocation `v[0..capacity]` as `&mut [u8]` (Zig: - /// `ArrayList.allocatedSlice()`). For the spare-only `[len..capacity]` - /// view use [`spare_bytes_mut`]. + /// View the **entire** allocation `v[0..capacity]` as `&mut [u8]`. + /// For the spare-only `[len..capacity]` view use [`spare_bytes_mut`]. /// /// # Safety /// Bytes in `[len, capacity)` are uninitialized; treat that tail as @@ -628,6 +614,9 @@ bun_dispatch::link_interface! { pub ErrnoNames[Sys] { fn name(errno: i32) -> Option<&'static str>; fn max_dense() -> u32; + // Raw Win32 `GetLastError()` code → `SystemErrno` tag name. + // Always `None` on non-Windows. + fn win32_name(code: u32) -> Option<&'static str>; } } @@ -642,7 +631,7 @@ impl ErrnoNames { /// so `$crate::pretty_fmt!` resolves from the wrapper macros in `output.rs`. pub use bun_core_macros::{EnumTag, pretty_fmt}; -/// Stand-in for Zig's `@import("build_options")`. Values are written at +/// Build-time configuration values. Written at /// configure time by `scripts/build/buildOptionsRs.ts` from the resolved /// `Config` and `include!()`'d here; `build.rs` exports `BUN_CODEGEN_DIR` /// and fingerprints the file so a sha/version change recompiles this crate. @@ -669,7 +658,7 @@ pub use util::*; // ── intrusive-container parent recovery ─────────────────────────────────── // -// Port of Zig's parent-from-field intrinsic. Intrusive data structures (task +// Intrusive data structures (task // queues, timer heaps, linked lists) hand callbacks a `*mut Field` and expect // the callee to walk back to the owning `*mut Parent`. Earlier ports open-coded // this at ~150 sites as `ptr.cast::().sub(offset_of!(P, f)).cast::, @@ -1331,7 +1284,7 @@ pub struct BundleOptions<'a> { pub target: Target, pub main_fields: Box<[Box<[u8]>]>, /// TODO: remove this in favor accessing bundler.log - /// PORT NOTE: raw `*mut` (not `&'a mut`) — Zig aliases the same `*Log` + /// raw `*mut` (not `&'a mut`) — the same `*Log` is aliased /// into `Transpiler.log` / `Resolver.log` / `Linker.log`. A stored /// `&'a mut` here would assert uniqueness for `'a` and make every access /// through those sibling raw pointers UB under stacked borrows. @@ -1380,7 +1333,7 @@ pub struct BundleOptions<'a> { pub global_cache: GlobalCache, pub prefer_offline_install: bool, pub prefer_latest_install: bool, - /// Spec `options.zig:1753`: `?*const Api.BunInstall`. Stored as a raw + /// Stored as a raw /// `NonNull` (not `Option<&'a _>`) because every CLI caller borrows the /// process-lifetime `ctx.install: Box` whose lifetime is /// unrelated to `'a`; a typed reference forced an `unsafe { &*(p as *const _) }` @@ -1422,7 +1375,6 @@ pub struct BundleOptions<'a> { // directly — all access goes through crate::dispatch::DevServerVTable. pub dev_server: *const (), /// Set when Bake is bundling. Affects module resolution. - // TODO(b0): bake::Framework arrives from move-in (TYPE_ONLY → bundler) pub framework: Option<&'a crate::bake_types::Framework>, pub serve_plugins: Option]>>, @@ -1489,8 +1441,7 @@ impl<'a> BundleOptions<'a> { /// them on the worker dropped the parent's allocation). Every owned field /// is `Clone`d; raw-pointer / `Copy` / `&'a` fields copy directly. /// - /// PERF(port): Zig's `transpiler.* = from.*` is a shallow struct copy - /// (slices alias the parent's arena). The Rust port owns these as `Box`, + /// These fields are owned as `Box`, /// so a per-worker clone allocates. Profile if it shows up on a hot path; /// the hot fields (`define`, `loaders`, `conditions`) are O(dozens) entries. pub fn for_worker(&self) -> BundleOptions<'a> { @@ -1619,8 +1570,9 @@ impl<'a> BundleOptions<'a> { /// Shared-borrow the per-Transpiler `Log`. /// - /// SAFETY: `self.log` is non-null once `from_api` / `Transpiler::init` has - /// run (Zig spec `options.zig:1714`: `log: *logger.Log`, non-optional). + /// SAFETY: `self.log` is non-null: `Transpiler::init_in_place` validates + /// the pointer via `NonNull::new(log).expect(..)` before storing it and + /// before calling `from_api` (which has no other callers). /// The pointee is the caller-owned arena `Log` which outlives `self`. The /// same allocation is aliased into `Transpiler.log` / `Resolver.log` / /// `Linker.log` as raw `*mut`; a `&` here is sound so long as no caller @@ -1690,17 +1642,13 @@ impl<'a> BundleOptions<'a> { !self.defines_loaded } - // TODO(b2-blocked): defines_from_transform_options (see above) + - // api::TransformOptions.define field (peechy `TransformOptions` body still - // opaque). pub fn load_defines( &mut self, arena: &bun_alloc::Arena, loader_: Option<&mut DotEnv::Loader>, ) -> Result<(), bun_core::Error> { - // PORT NOTE: spec `loadDefines(..., env: ?*const options.Env)` had its - // sole caller pass `&this.options.env` (transpiler.zig:334). Forwarding - // that as `Option<&Env>` forced the caller into an aliased-`&mut` UB + // Forwarding the env as an `Option<&Env>` parameter forced the + // caller into an aliased-`&mut` UB // raw-pointer dance under Stacked Borrows. Dropped the param and read // `&self.env` here instead — disjoint from the `self.define` / // `self.defines_loaded` writes below, so borrowck splits it cleanly. @@ -1712,7 +1660,7 @@ impl<'a> BundleOptions<'a> { if self.defines_loaded { return Ok(()); } - // PERF(port): the spec uses borrowed static literals for the three + // PERF: borrowed static literals for the three // constant cases; only the env-loader case needs an owned copy (it has // to outlive the `&mut loader_` we pass below, so it can't stay a borrow // into the loader). `Cow` keeps the literals zero-alloc — matters because @@ -1734,7 +1682,7 @@ impl<'a> BundleOptions<'a> { Some(Cow::Borrowed(b"\"development\"".as_slice())) }; - // PORT NOTE: reshaped for borrowck — node_env computed before passing self.log + // reshaped for borrowck — node_env computed before passing self.log self.define = defines_from_transform_options( // No other `&mut Log` is live across this call (see `log_mut` // caller contract). @@ -1744,7 +1692,8 @@ impl<'a> BundleOptions<'a> { loader_, Some(&self.env), node_env.as_deref(), - // TODO(port): &self.drop is Box<[Box<[u8]>]>, callee wants &[&[u8]] + // `&self.drop` is `Box<[Box<[u8]>]>`; the callee wants `&[&[u8]]`, + // so re-borrow per call (cold path: once per options build). &self.drop.iter().map(|s| s.as_ref()).collect::>(), self.dead_code_elimination && self.minify_syntax, arena, @@ -1780,13 +1729,11 @@ impl<'a> BundleOptions<'a> { .collect::>(), ); - // TODO(port): many fields below have Zig defaults via `= ...`; in Rust we initialize - // each explicitly. Could add a `Default`-ish builder. let mut opts = BundleOptions { footer: Cow::Borrowed(b""), banner: Cow::Borrowed(b""), log, - // PORT NOTE: `define` is `undefined` in Zig and filled by `loadDefines` later; + // `define` is filled by `load_defines` later; // initialize empty so the struct is well-formed before `load_defines` runs. define: Box::new(defines::Define { identifiers: Default::default(), @@ -1891,7 +1838,6 @@ impl<'a> BundleOptions<'a> { optimize_imports: None, }; - // TODO(b2-blocked): bun_analytics dep not yet wired in bundler/Cargo.toml { analytics::features::define .fetch_add(usize::from(transform.define.is_some()), Ordering::Relaxed); @@ -1912,9 +1858,7 @@ impl<'a> BundleOptions<'a> { opts.env.disable_default_env_files = transform.disable_default_env_files; if let Some(origin) = &transform.origin { - // PORT NOTE: ownership — `URL<'_>` borrows its input. The Zig - // `URL.parse` borrowed `transform.origin` (a sibling of - // `opts.transform_options`); here `OwnedURL` owns the href and + // ownership — `URL<'_>` borrows its input; `OwnedURL` owns the href and // callers borrow via `.url()`. opts.origin = bun_url::OwnedURL::from_href(origin.clone()); } @@ -1991,7 +1935,7 @@ impl<'a> BundleOptions<'a> { .collect(); } - // PORT NOTE: Zig passed `log` directly; reborrow the raw `*mut Log` + // Reborrow the raw `*mut Log` // for the duration of this call only. opts.external = init_external_modules( &mut fs.fs, @@ -2024,8 +1968,7 @@ impl<'a> BundleOptions<'a> { if opts.write && !opts.output_dir.is_empty() { let handle = open_output_dir(&opts.output_dir)?; opts.output_dir_handle = Some(handle); - // PORT NOTE: Zig `fs.getFdPath(.fromStdDir(handle))` interns into - // `dirname_store`; the inline `bun_resolver::fs::FileSystem` does + // The inline `bun_resolver::fs::FileSystem` does // not yet expose `get_fd_path`, so resolve via `bun_sys` and box. let mut buf = bun_paths::PathBuffer::uninit(); let dir = bun_sys::get_fd_path(handle, &mut buf).map_err(bun_core::Error::from)?; @@ -2038,7 +1981,6 @@ impl<'a> BundleOptions<'a> { opts.tsconfig_override = Some(tsconfig.clone()); } - // TODO(b2-blocked): bun_analytics dep not yet wired in bundler/Cargo.toml { analytics::features::macros.fetch_add( usize::from(opts.target == Target::BunMacro), @@ -2057,9 +1999,7 @@ impl Drop for BundleOptions<'_> { fn drop(&mut self) { // self.define dropped automatically (Box). // - // bundler_feature_flags: Zig compared the pointer to - // `&Runtime.Features.empty_bundler_feature_flags` and freed iff distinct. - // In Rust the field is `Option>`; `None` ≡ the static + // bundler_feature_flags is `Option>`; `None` ≡ the static // empty set (nothing to free), `Some` drops the Box here automatically. } } @@ -2099,12 +2039,11 @@ pub mod bundle_options_defaults { } pub fn open_output_dir(output_dir: &[u8]) -> Result { - // PORT NOTE: Zig used `std.fs.cwd().openDir/.makeDir`; routed through - // `bun_sys` per CLAUDE.md (never `std::fs`). + // Routed through `bun_sys` per CLAUDE.md (never `std::fs`). match bun_sys::open_dir_at(bun_sys::Fd::cwd(), output_dir) { Ok(d) => Ok(d), Err(_) => { - // Zig: `std.fs.cwd().makeDir(output_dir)` — single-level mkdir + // Single-level mkdir // (fails ENOENT if parent missing). Do NOT use `make_path` (the // recursive `mkdir -p` variant) here. let mut buf = bun_paths::PathBuffer::uninit(); @@ -2138,8 +2077,8 @@ pub fn open_output_dir(output_dir: &[u8]) -> Result { } } -/// Port of `fs.zig` `Fs.File` (path + contents pair). `bun_resolver::fs` -/// does not surface this type yet (TODO(b2-blocked)); local mirror keeps +/// Path + contents pair. `bun_resolver::fs` +/// does not surface this type; local mirror keeps /// `TransformOptions.entry_point` self-contained. pub struct EntryPointFile { pub path: bun_paths::fs::Path<'static>, @@ -2178,7 +2117,6 @@ impl TransformOptions { }; let mut _cwd: Box<[u8]> = Box::from(b"/".as_slice()); - // TODO(port): Environment.isWasi #[cfg(any(target_os = "wasi", windows))] { // `getcwd_alloc` returns a NUL-terminated `ZBox`; strip the NUL @@ -2190,7 +2128,6 @@ impl TransformOptions { let mut define = StringHashMap::>::default(); define.reserve(1); - // PERF(port): was assume_capacity define.put_assume_capacity(b"process.env.NODE_ENV", b"development".as_slice().into()); let entry_point_name = entry_point.path.name(); @@ -2207,7 +2144,8 @@ impl TransformOptions { loader, resolve_dir: Box::from(entry_point_name.dir), entry_point, - // TODO(port): resolve_dir borrows from entry_point in Zig; cloned here + // resolve_dir is cloned so + // `TransformOptions` stays lifetime-free. main_fields: Target::default_main_fields_map()[Target::Browser], jsx: if loader.is_jsx() { Some(jsx::Pragma::default()) @@ -2274,7 +2212,7 @@ pub struct EnvEntry { type EnvList = MultiArrayList; -// PORT NOTE: `Debug` derive dropped — `MultiArrayList` is not `Debug`. +// `Debug` derive dropped — `MultiArrayList` is not `Debug`. pub struct Env { pub behavior: api::DotEnvBehavior, pub prefix: Box<[u8]>, @@ -2327,7 +2265,6 @@ impl Env { self.defaults.ensure_total_capacity(defaults.keys.len())?; for (i, key) in defaults.keys.iter().enumerate() { - // PERF(port): was assume_capacity self.defaults.append(EnvEntry { key: key.clone(), value: defaults.values[i].clone(), @@ -2406,7 +2343,7 @@ impl Env { } } -// PORT NOTE: `Debug` derive dropped — `Env` is not `Debug` (MultiArrayList). +// `Debug` derive dropped — `Env` is not `Debug` (MultiArrayList). #[derive(Default)] pub struct EntryPoint { pub path: Box<[u8]>, @@ -2542,9 +2479,7 @@ pub(crate) fn path_template_needs(data: &[u8], field: PlaceholderField) -> bool } // Shared body for PathTemplate::print / PathTemplateConst::print (D064). -// PORT NOTE: Zig `format(self, comptime _, _, writer: anytype)` writes raw path bytes via -// writer.writeAll; mapped to a byte-writer free fn (not `core::fmt::Display`) per -// PORTING.md "(comptime X: type, arg: X) writer → &mut impl bun_io::Write (bytes)". +// Writes raw path bytes via a byte-writer free fn (not `core::fmt::Display`). pub(crate) fn path_template_print( writer: &mut W, data: &[u8], @@ -2717,7 +2652,7 @@ pub struct Placeholder { pub target: Box<[u8]>, } -// PORT NOTE: hoisted from `impl Placeholder` — Rust forbids `static` in inherent impls. +// hoisted from `impl Placeholder` — Rust forbids `static` in inherent impls. pub static PLACEHOLDER_MAP: phf::Map<&'static [u8], PlaceholderField> = phf::phf_map! { b"dir" => PlaceholderField::Dir, b"name" => PlaceholderField::Name, @@ -2753,8 +2688,8 @@ impl PlaceholderConst { } impl PathTemplateConst { - /// Byte-writer form mirroring [`PathTemplate::print`] (Zig - /// `PathTemplate.format`). Kept as an inherent method so callers writing + /// Byte-writer form mirroring [`PathTemplate::print`]. + /// Kept as an inherent method so callers writing /// to `Vec` via `write!(.., "{}", template)` resolve through the /// blanket [`core::fmt::Display`] impl below. pub fn print(&self, writer: &mut W) -> bun_io::Result<()> { @@ -2804,5 +2739,3 @@ impl From for PathTemplate { } } } - -// ported from: src/bundler/options.zig diff --git a/src/bundler/transpiler.rs b/src/bundler/transpiler.rs index 219d05f478a..8b9ed7db971 100644 --- a/src/bundler/transpiler.rs +++ b/src/bundler/transpiler.rs @@ -16,15 +16,14 @@ use bun_router::Router; use crate::options; -/// Port of `transpiler.zig:ResolveResults` — keyed by source path hash. +/// Keyed by source path hash. pub(crate) type ResolveResults = HashMap; -/// Port of `transpiler.zig:ResolveQueue` — `std.fifo.LinearFifo(resolver.Result, .Dynamic)`. -// PORT NOTE: `bun_collections::LinearFifo>` would be exact, +// `bun_collections::LinearFifo>` would be exact, // but `DynamicBuffer` isn't re-exported from `bun_collections` yet. `VecDeque` // is structurally equivalent (growable ring buffer); swap once the re-export lands. pub(crate) type ResolveQueue = std::collections::VecDeque; -/// Spec `JSGlobalObject.BunPluginTarget` (JSGlobalObject.zig:265). Defined at +/// Defined at /// this tier (lowest crate that needs to name it) and re-exported from /// `bun_jsc::BunPluginTarget` so there is exactly one enum (no bridge between /// mirror types). @@ -39,11 +38,11 @@ pub enum BunPluginTarget { // Crosses FFI by-value to `JSBundlerPlugin__create` / `Bun__runOn*Plugins` // (C++: `typedef uint8_t BunPluginTarget`, `headers-handwritten.h`). NB: the // C++ header's *named* constants (`BunPluginTargetBrowser = 1`, `Node = 2`) -// disagree with Zig `JSGlobalObject.zig:265` (`node = 1`, `browser = 2`); Rust -// matches the Zig spec. The width (`u8`) is what matters at the ABI. +// disagree with the Rust enum (`Node = 1`, `Browser = 2`). The width (`u8`) +// is what matters at the ABI. bun_core::assert_ffi_discr!(BunPluginTarget, u8; Bun = 0, Node = 1, Browser = 2); -/// Spec PluginRunner.zig:34 `onResolve` — the JSC-aware resolve hook. +/// The JSC-aware resolve hook. /// /// The body calls `JSGlobalObject.runOnResolvePlugins`, so it cannot be /// defined at this tier (`bun_jsc` depends on this crate). `bun_jsc` provides @@ -62,14 +61,15 @@ pub trait PluginResolver { ) -> Result>, bun_core::Error>; } -/// Spec PluginRunner.zig — namespace for the static byte-level helpers +/// Namespace for the static byte-level helpers /// (`extractNamespace` / `couldBePlugin`). The stateful struct (with /// `global_object`) lives in `bun_jsc::PluginRunner` where `JSGlobalObject` is /// nameable; only the JSC-free helpers stay at this tier. pub struct PluginRunner; impl PluginRunner { - /// Spec PluginRunner.zig:14 `extractNamespace`. + /// Returns the `namespace:` prefix of `specifier`, or `b""` if it has none + /// (Windows drive-letter prefixes are not namespaces). pub fn extract_namespace(specifier: &[u8]) -> &[u8] { let Some(colon) = bun_core::index_of_char(specifier, b':') else { return b""; @@ -87,7 +87,7 @@ impl PluginRunner { &specifier[..colon] } - /// Spec PluginRunner.zig:22 `couldBePlugin` — cheap pre-filter that rules + /// Cheap pre-filter that rules /// out `./` / `../` / absolute paths before hitting the resolve hook. pub fn could_be_plugin(specifier: &[u8]) -> bool { if let Some(last_dot) = bun_core::last_index_of_char(specifier, b'.') { @@ -105,11 +105,10 @@ impl PluginRunner { } } -/// Spec `transpiler.zig:5` — `pub const MacroJSCtx = @import("../bundler_jsc/PluginRunner.zig").MacroJSCtx`. /// The canonical newtype lives in `bun_ast::Macro` (the lowest tier that -/// stores it, in `MacroContext.javascript_object`); re-exported here per spec. +/// stores it, in `MacroContext.javascript_object`); re-exported here. pub use js_ast::Macro::MacroJSCtx; -/// Spec `transpiler.zig:1433 default_macro_js_value` (= `JSValue.zero`). +/// The default (zero) macro JS context value. #[inline] pub const fn default_macro_js_value() -> MacroJSCtx { MacroJSCtx::ZERO @@ -123,18 +122,16 @@ pub const fn default_macro_js_value() -> MacroJSCtx { /// on every VM so that the options can be used for transpilation. pub struct Transpiler<'a> { pub options: options::BundleOptions<'a>, - // PORT NOTE: raw ptr — Zig aliased the same `*Log` into `linker.log` and + // Raw ptr — the same `*Log` is aliased into `linker.log` and // `resolver.log` (see `set_log`). `&'a mut` would forbid that aliasing. - // TODO(port): lifetime — restructure once linker/resolver own their logs. pub log: *mut bun_ast::Log, - // TODO(port): arena — bundler is an AST crate per PORTING.md so we - // thread an arena, but callers usually pass `bun.default_allocator`. - // Confirm whether this should be removed (global mimalloc) or kept. + // Bundler is an AST crate per PORTING.md, so an arena is threaded even + // though callers usually pass `bun.default_allocator`. pub arena: &'a Arena, pub result: options::TransformResult, pub resolver: Resolver<'a>, - // TODO(port): lifetime — Zig used the global `Fs.FileSystem.instance` - // singleton (`&'static mut`). Raw ptr until the singleton accessor lands. + // Raw ptr — points at the shared global `FileSystem` singleton; a stored + // `&'a mut` would assert uniqueness it cannot have. pub fs: *mut Fs::FileSystem, pub output_files: Vec, pub resolve_results: Box, @@ -149,7 +146,7 @@ pub struct Transpiler<'a> { // by `configure_linker` below; `set_log` keeps `linker.log` in sync. pub linker: crate::linker::Linker, pub timer: SystemTimer, - // TODO(port): lifetime — Zig stored `&DotEnv.Loader` (global singleton). + // Raw ptr — the global `DotEnv::Loader` singleton. pub env: *mut dot_env::Loader<'a>, pub macro_context: Option, @@ -158,25 +155,22 @@ pub struct Transpiler<'a> { impl<'a> Transpiler<'a> { pub const IS_CACHE_ENABLED: bool = false; - /// Port of `transpiler.zig:95 setLog`. - /// - /// PORT NOTE: takes `*mut Log` (not `&'a mut`) because Zig aliased the same - /// `*Log` into `linker.log` / `resolver.log`; the un-gated struct field is + /// Takes `*mut Log` (not `&'a mut`) because the same + /// `*Log` is aliased into `linker.log` / `resolver.log`; the un-gated struct field is /// already a raw pointer for that reason. pub fn set_log(&mut self, log: *mut bun_ast::Log) { self.log = log; self.linker.log = log; // SAFETY: caller (`ThreadPool::Worker::create`) passes the per-worker - // arena-allocated `Log`, which outlives this `Transpiler<'a>`. Zig - // aliased the same `*Log` into `resolver.log`. + // arena-allocated `Log`, which outlives this `Transpiler<'a>`. self.resolver.log = core::ptr::NonNull::new(log).expect("set_log: log is non-null"); } - /// Port of `transpiler.zig:102 setAllocator`. + /// Replace the transpiler's arena and re-init the macro context against it. // TODO: remove this method. it does not make sense pub fn set_arena(&mut self, arena: &'a Arena) { self.arena = arena; - // PORT NOTE: `crate::Linker` is the unit stub — no `.arena` field. + // `crate::Linker` is the unit stub — no `.arena` field. // `Resolver` dropped its `arena` field (global mimalloc; see // resolver/lib.rs `// arena: dropped`), so nothing left to thread. } @@ -196,8 +190,8 @@ impl<'a> Transpiler<'a> { /// `&'static mut`. Owned `Transpiler`s from [`Self::for_worker`] must use /// normal `Drop` instead. pub unsafe fn deinit(&mut self) { - // The lazily-created `Box` was - // intentionally process-lifetime under Zig (`default_allocator`), but + // The lazily-created `Box` is + // process-lifetime by default, but // worker VMs run `destroy()` on thread exit and would otherwise strand // one box per worker. The box only owns a `MacroMap` and an optional // `bun_alloc::Arena` — no JSC handles — so freeing it from either @@ -260,7 +254,7 @@ impl<'a> Transpiler<'a> { /// other `self.*` fields as arguments without a borrow-checker conflict; /// callers must not hold two results live at once, nor hold a result /// across a `self.{resolver,linker}` call that itself writes to the - /// aliased `*mut Log` (see field PORT NOTE — same allocation is threaded + /// aliased `*mut Log` (see field comment — same allocation is threaded /// into `linker.log` / `resolver.log`). #[inline] #[allow(clippy::mut_from_ref)] @@ -289,7 +283,7 @@ impl<'a> Transpiler<'a> { /// Reborrow the `DotEnv::Loader`. Returned lifetime is decoupled from /// `&self` so call sites in `configure_defines` / `run_env_loader` can /// hold it across disjoint `&mut self.options` / `&mut self.resolver` - /// borrows (matching Zig's free `this.env.*` access). + /// borrows. #[inline] #[allow(clippy::mut_from_ref)] pub fn env_mut(&self) -> &'a mut dot_env::Loader<'a> { @@ -301,11 +295,9 @@ impl<'a> Transpiler<'a> { unsafe { &mut *self.env } } - /// Per-worker / client-transpiler constructor — port of Zig's - /// `transpiler.* = from.*` (ThreadPool.zig:308, bundle_v2.zig:204). + /// Per-worker / client-transpiler constructor. /// - /// Zig structs have no destructors, so a bitwise struct copy that aliases - /// heap-owning fields is sound there. In Rust the prior bitwise + /// The prior bitwise /// `ptr::copy_nonoverlapping` aliased every `Box`/`Vec` between parent and /// worker; reassigning any of them on the worker (e.g. /// `resolver.caches = ...`) ran `Drop` on the parent's allocation. This @@ -353,8 +345,7 @@ impl<'a> Transpiler<'a> { options, log, arena, - // Per-worker scratch — Zig's bitwise copy carried these too, but - // workers never read the parent's accumulated state. + // Per-worker scratch — workers never read the parent's accumulated state. result: options::TransformResult::default(), resolver, fs: from.fs, @@ -381,7 +372,7 @@ impl<'a> Transpiler<'a> { // SAFETY: lifetime-widen the `Loader<'from>` raw pointer to `'a` // (process-lifetime singleton; see fn doc). env: from.env.cast(), - // Spec ThreadPool.zig:311 `MacroContext.init(transpiler)` takes the + // `MacroContext::init(transpiler)` takes the // transpiler's *address*; deferred to `wire_after_move`. macro_context: None, } @@ -389,22 +380,19 @@ impl<'a> Transpiler<'a> { /// Wire the self-referential `linker` back-pointers and `macro_context` /// after this `Transpiler` has reached its final address (post-move into - /// `WorkerData` / arena slot). Port of the post-copy fixups in - /// ThreadPool.zig:309-313 / bundle_v2.zig:228-232. + /// `WorkerData` / arena slot). pub fn wire_after_move(&mut self) { - // Spec: `transpiler.setLog(log)` already ran inside `for_worker` via - // direct field init; re-thread into `options.log` / `resolver.log` / + // `self.log` was already set inside `for_worker` via direct field + // init; re-thread into `options.log` / `resolver.log` / // `linker.log` here so all four aliases agree. let log = self.log; self.options.log = log; self.resolver.log = core::ptr::NonNull::new(log).expect("wire_after_move: log is non-null"); self.resolver.fs = self.fs; - // Spec ThreadPool.zig:310 `transpiler.linker.resolver = &transpiler.resolver`. // Only reseat the back-pointers — do NOT `Linker::init` here: that // would clobber `import_counter` / `plugin_runner` / - // `tagged_resolutions` / `any_needs_runtime`, which the spec - // preserves across the move (bundle_v2.zig:230 only assigns - // `linker.resolver`). + // `tagged_resolutions` / `any_needs_runtime`, which must be + // preserved across the move. self.linker.reseat_self_refs( log, core::ptr::addr_of_mut!(self.resolve_queue), @@ -413,17 +401,17 @@ impl<'a> Transpiler<'a> { core::ptr::addr_of_mut!(*self.resolve_results), self.fs, ); - // Spec ThreadPool.zig:311 `transpiler.macro_context = MacroContext.init(transpiler)`. self.macro_context = Some(js_ast::Macro::MacroContext::init(self)); } - /// Port of `transpiler.zig:91 getPackageManager`. + /// Returns the resolver's auto-install package-manager handle. #[inline] pub fn get_package_manager(&mut self) -> *mut dyn bun_resolver::install_types::AutoInstaller { self.resolver.get_package_manager() } - /// Port of `transpiler.zig:358 resetStore`. + /// Reset the thread-local AST block stores (`Expr`/`Stmt`) and the side + /// `AstAlloc` arena. pub fn reset_store(&self) { bun_ast::Expr::data_store_reset(); bun_ast::Stmt::data_store_reset(); @@ -449,7 +437,6 @@ impl<'a> Transpiler<'a> { } } - /// Port of `transpiler.zig:108 _resolveEntryPoint`. fn _resolve_entry_point( &mut self, entry_point: &[u8], @@ -467,12 +454,10 @@ impl<'a> Transpiler<'a> { if !bun_paths::is_absolute(entry_point) && !(entry_point.starts_with(b"./") || entry_point.starts_with(b".\\")) { - // Spec: `strings.append(arena, "./", entry_point)`. let mut prefixed = Vec::with_capacity(2 + entry_point.len()); prefixed.extend_from_slice(b"./"); prefixed.extend_from_slice(entry_point); - // PORT NOTE: spec leaks the prefixed slice (arena-freed in - // Zig). `Resolver::resolve` interns the path internally, + // `Resolver::resolve` interns the path internally, // so the heap buffer can drop after the call. if let Ok(r) = self.resolver.resolve( top_level_dir, @@ -488,7 +473,8 @@ impl<'a> Transpiler<'a> { } } - /// Port of `transpiler.zig:130 resolveEntryPoint`. + /// Resolve an entry-point specifier, busting the directory cache and + /// retrying once on failure before reporting the error to the log. pub fn resolve_entry_point( &mut self, entry_point: &[u8], @@ -499,8 +485,8 @@ impl<'a> Transpiler<'a> { let mut cache_bust_buf = bun_paths::PathBuffer::uninit(); // Bust directory cache and try again - // PORT NOTE: reshaped for borrowck — Zig's labelled-block - // returned a slice that aliases either `entry_point` (via + // 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. @@ -524,7 +510,7 @@ impl<'a> Transpiler<'a> { } } - // Spec: `bun.pathLiteral("..")` — `".."` is sep-agnostic. + // `".."` needs no platform separator rewrite. let parts: [&[u8]; 2] = [entry_point, b".."]; let top_level_dir = self.fs().top_level_dir; @@ -562,7 +548,8 @@ impl<'a> Transpiler<'a> { } } - /// Port of `transpiler.zig:314 configureDefines`. + /// Load env files and build `options.define`. Idempotent — a no-op once + /// `options.defines_loaded` is set. pub fn configure_defines(&mut self) -> Result<(), bun_core::Error> { if self.options.defines_loaded { return Ok(()); @@ -578,10 +565,8 @@ impl<'a> Transpiler<'a> { let env_loader = self.env_mut(); let mut is_production = env_loader.is_production(); - // PORT NOTE: spec (`transpiler.zig:314`) eagerly did - // `Expr.Data.Store.create()` / `Stmt.Data.Store.create()` plus a - // `defer Store.reset()` here, purely so `defines.zig`'s `parse_env_json` - // had a thread-local AST store to build `E::String` nodes in. That work + // `parse_env_json` needs a thread-local AST store to build + // `E::String` nodes in. That work // is now done lazily inside `DefineData::parse`, only on the JSON-parse // slow path — the common case (`bun run` with no user `--define`) // resolves every define through the literal fast path and never @@ -619,9 +604,7 @@ impl<'a> Transpiler<'a> { Ok(()) } - /// Port of the spec idiom `out.resolver.opts = out.options` (transpiler.zig - /// passes the same `BundleOptions` value to both struct fields; bake.zig:788 - /// re-assigns after mutating `out.options`). In the Rust port the resolver + /// The resolver /// crate carries a FORWARD_DECL subset of `BundleOptions`, so re-project /// rather than `Clone`. Called after `init_transpiler_with_options` mutates /// `self.options` so the resolver sees the same conditions/target/public_path. @@ -629,17 +612,17 @@ impl<'a> Transpiler<'a> { self.resolver.opts = resolver_bundle_options_subset(&self.options); } - /// Port of `transpiler.zig:363 dumpEnvironmentVariables`. + /// Print the loaded environment variables to stdout as 2-space-indented + /// JSON. #[cold] #[inline(never)] pub fn dump_environment_variables(&self) { use bun_js_printer::{Encoding, write_json_string}; - // PORT NOTE: spec uses `std.json.Stringify` (`.whitespace = .indent_2`) - // to dump `env.map.*`. The Rust `bun_dotenv::Map` doesn't impl - // `serde::Serialize`, so iterate and emit the object by hand. Keys and - // values go through `write_json_string` (the same escaper the printer - // uses for metafile/HTML-manifest JSON) so `"` / `\` / control bytes - // are escaped exactly as `std.json.Stringify` does. + // Dump `env.map.*` as 2-space-indented JSON. `bun_dotenv::Map` doesn't + // impl `serde::Serialize`, so iterate and emit the object by hand. + // Keys and values go through `write_json_string` (the same escaper the + // printer uses for metafile/HTML-manifest JSON) so `"` / `\` / control + // bytes are escaped as standard JSON requires. bun_core::Output::flush(); let env = self.env_mut(); let w = bun_core::Output::writer(); @@ -688,19 +671,20 @@ fn merge_tsconfig_jsx_into(tsconfig: &TSConfigJSON, out: &mut crate::options_imp } impl<'a> Transpiler<'a> { - /// Port of `transpiler.zig:233 configureLinkerWithAutoJSX`. + /// Initialize `self.linker` with back-pointers into this `Transpiler`, + /// optionally auto-configuring JSX from the nearest `tsconfig.json`. pub fn configure_linker_with_auto_jsx(&mut self, auto_jsx: bool) { - // PORT NOTE: `Linker::init` dropped its `arena` arg (linker.rs:172 - // — global mimalloc). Zig stored borrowed `*T` into the linker; the - // un-gated `crate::linker::Linker` mirrors that with raw pointers so + // `Linker::init` dropped its `arena` arg (linker.rs:172 + // — global mimalloc). The + // un-gated `crate::linker::Linker` stores raw pointers so // `&mut self.options` etc. coerce directly. Self-reference is // load-bearing — `linker.link()` reads back through these into the // owning `Transpiler` — hence raw `*mut`, not `&'a mut` (would alias // `&mut self` on every call). - // PORT NOTE: `.cast()` on the `options`/`resolver` pointers erases the + // `.cast()` on the `options`/`resolver` pointers erases the // `<'a>` lifetime parameter — `Linker` stores them as // `*mut BundleOptions` / `*mut Resolver` with an (implicit) distinct - // lifetime. Raw-pointer storage is the Zig contract; the linker never + // lifetime. The linker never // outlives its owning `Transpiler<'a>`. self.linker = crate::linker::Linker::init( self.log, @@ -727,18 +711,18 @@ impl<'a> Transpiler<'a> { } } - /// Port of `transpiler.zig:259 configureLinker`. + /// [`Self::configure_linker_with_auto_jsx`] with `auto_jsx = true`. #[inline] pub fn configure_linker(&mut self) { self.configure_linker_with_auto_jsx(true); } - /// Port of `transpiler.zig:263 runEnvLoader`. + /// Load `.env` files into the env loader according to + /// `options.env.behavior`. pub fn run_env_loader(&mut self, skip_default_env: bool) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set use bun_options_types::schema::api::DotEnvBehavior; // Derived once up front; no other live `&mut` to this `Loader` exists - // for the duration of this call (Zig accessed `this.env.*` freely). + // for the duration of this call. let env: &mut dot_env::Loader<'_> = self.env_mut(); match self.options.env.behavior { @@ -754,7 +738,6 @@ impl<'a> Transpiler<'a> { let has_production_env = env.is_production(); if !was_production && has_production_env { self.options.set_production(true); - // Spec transpiler.zig:275 `this.resolver.opts.setProduction(true)`. // The resolver's FORWARD_DECL `BundleOptions` now exposes // `set_production` (flips `production` + `jsx.development` // and self-guards on `force_node_env`; resolver/lib.rs). @@ -789,10 +772,9 @@ impl<'a> Transpiler<'a> { // sole `&mut` for the call. let dir: &mut bun_resolver::fs::DirEntry = unsafe { &mut *dir }; - // PORT NOTE: `Env.files: Box<[Box<[u8]>]>` but `Loader::load` + // `Env.files: Box<[Box<[u8]>]>` but `Loader::load` // wants `&[&[u8]]`. Re-borrow into a small Vec; the explicit // `--env-file` list is bounded (CLI args), not hot-path. - // PERF(port): one tiny alloc — Zig passed the slice directly. let env_files: Vec<&[u8]> = self.options.env.files.iter().map(|f| &**f).collect(); let suffix = if self.options.is_test() || env.is_test() { @@ -808,7 +790,7 @@ impl<'a> Transpiler<'a> { env.load_process()?; if env.is_production() { self.options.set_production(true); - // Spec transpiler.zig:302 — see note in the `.prefix` arm. + // See note in the `.prefix` arm. self.resolver.opts.set_production(true); } } @@ -838,7 +820,8 @@ use bun_core::strings; use bun_resolver::package_json::MacroMap as MacroRemap; use bun_sys::Fd as FD; -/// Port of `transpiler.zig:ParseResult.AlreadyBundled` (tagged union). +/// How a parsed source was found to be pre-bundled (plain source vs. cached +/// bytecode, ESM vs. CJS). #[derive(Default)] pub enum AlreadyBundled { #[default] @@ -872,9 +855,10 @@ impl AlreadyBundled { } } -/// Port of `transpiler.zig:ParseResult`. -// PORT NOTE: lifetime-free — `runtime_transpiler_cache` is a raw pointer (Zig -// `?*RuntimeTranspilerCache`) so `AsyncModule.parse_result` / `JSTranspiler` +/// Output of the transpiler's parse step: the parsed AST plus its source and +/// loader metadata. +// lifetime-free — `runtime_transpiler_cache` is a raw pointer +// so `AsyncModule.parse_result` / `JSTranspiler` // can store this by value without threading a borrow lifetime. pub struct ParseResult<'a> { pub source: bun_ast::Source, @@ -883,18 +867,17 @@ pub struct ParseResult<'a> { pub already_bundled: AlreadyBundled, pub input_fd: Option, pub empty: bool, - // PORT NOTE: Zig `_resolver.PendingResolution.List` is - // `MultiArrayList(PendingResolution)`. `PendingResolution` does not yet + // `PendingResolution` does not yet // derive `MultiArrayElement` (lives in `bun_resolver`, derive macro is in // `bun_collections_macros` — orphan rules forbid impl-ing it here), so the // SoA `len()`/column accessors aren't reachable. Use AoS `Vec` for now; // `is_pending_import` only scans `import_record_id`, so the layout // difference is observable only as a SoA→AoS perf delta. - // TODO(b3): switch back to `MultiArrayList` once the - // derive lands upstream in `bun_resolver`. + // Once the `MultiArrayElement` derive lands upstream in `bun_resolver`, + // this can switch back to `MultiArrayList`. pub pending_imports: Vec, - /// Zig: `?*bun.RuntimeTranspilerCache`. SAFETY: erased — bundler stores it + /// SAFETY: erased — bundler stores it /// and hands it back to the runtime side; never dereferenced here. pub runtime_transpiler_cache: Option>, @@ -910,15 +893,15 @@ pub struct ParseResult<'a> { } impl<'a> ParseResult<'a> { - /// Spec transpiler.zig — `ParseResult` is value-copied (e.g. + /// `ParseResult` is value-copied (e.g. /// `AsyncModule.resumeLoadingModule` reads/writes `this.parse_result` by /// value). `Default` lets the Rust port `mem::take` it across that /// boundary; see `AsyncModule::resume_loading_module`. pub fn empty(arena: &'a bun_alloc::Arena) -> Self { ParseResult { source: Default::default(), - // PORT NOTE: `options::Loader` has no `Default`; Zig field had no - // initializer either. `File` is the resolver's neutral fallback + // `options::Loader` has no `Default`. + // `File` is the resolver's neutral fallback // (BundleEnums.rs:353), and `Default` here exists only for // `mem::take` in `AsyncModule::resume_loading_module`. loader: options::Loader::File, @@ -956,8 +939,7 @@ impl<'a> ParseResult<'a> { } pub fn is_pending_import(&self, id: u32) -> bool { - // Spec transpiler.zig:43-47: scan `pending_imports.items(.import_record_id)` for `id`. - // PORT NOTE: AoS scan (see field comment); SoA column iteration restored + // AoS scan (see field comment); SoA column iteration restored // when `PendingResolution: MultiArrayElement` lands. self.pending_imports .iter() @@ -965,7 +947,7 @@ impl<'a> ParseResult<'a> { } } -/// Port of `transpiler.zig:Transpiler.ParseOptions`. +/// Per-file inputs to the transpiler's parse step. pub struct ParseOptions<'a, 'b> { pub arena: &'a Arena, pub dirname_fd: FD, @@ -983,7 +965,6 @@ pub struct ParseOptions<'a, 'b> { pub macro_remappings: MacroRemap, pub macro_js_ctx: MacroJSCtx, pub virtual_source: Option<&'b bun_ast::Source>, - /// Zig: `runtime.Runtime.Features.ReplaceableExport.Map`. pub replace_exports: bun_collections::StringArrayHashMap, pub inject_jest_globals: bool, pub set_breakpoint_on_first_line: bool, @@ -1020,7 +1001,7 @@ use bun_options_types::schema::api; // `bun_options_types::jsx::Pragma`). Only the `_None → Automatic` fold is // applied so parser-side `== Automatic` checks in visitExpr/parseJSXElement // keep their pre-unification semantics (parser only ever sees a resolved -// runtime; options.zig:1199 default). +// runtime). #[inline] pub(crate) fn to_parser_jsx_pragma( mut p: crate::options_impl::jsx::Pragma, @@ -1042,20 +1023,17 @@ fn to_parser_module_type( m } -/// Spec: `fs.zig:FileSystem.init`. -/// -/// PORT NOTE: the inline `bun_resolver::fs` module exposes the `FileSystem` +/// The inline `bun_resolver::fs` module exposes the `FileSystem` /// struct + `INSTANCE`/`INSTANCE_LOADED` statics (resolver/lib.rs:120,129) but /// not the `init` constructor (that lives in the still-gated file-backed /// `resolver/fs.rs`). All fields are `pub` and `EntriesMap`/`Mutex` have -/// public constructors, so reproduce the singleton-init here. Matches Zig -/// semantics: first call sets `top_level_dir` (defaulting to getcwd), +/// public constructors, so reproduce the singleton-init here: +/// first call sets `top_level_dir` (defaulting to getcwd), /// subsequent calls return the existing instance untouched. fn init_file_system( top_level_dir: Option<&'static [u8]>, ) -> Result<*mut Fs::FileSystem, bun_core::Error> { - // Spec fs.zig:90-108 — delegate to `FileSystem.init`, which routes through - // `Implementation.init` (fs.zig:823-837): that path calls `adjustUlimit()` + // `FileSystem` initialization calls `adjustUlimit()` // to raise RLIMIT_NOFILE and stores the returned limit in // `file_limit`/`file_quota`, and touches the `DirEntry.EntryStore` // singleton. The previous hand-built `Implementation { file_limit: 0, .. }` @@ -1071,18 +1049,16 @@ fn init_file_system( /// The two are nominally distinct until MOVE_DOWN to `bun_options_types` /// unifies them (resolver/lib.rs `mod options` note). /// -/// Spec transpiler.zig:214 passes the SAME `bundle_options` value to -/// `Resolver.init1`, so `resolver.opts` must carry user-configured +/// `resolver.opts` must carry the same user-configured /// `--external`, `--conditions`, `--main-fields`, and the extension order. /// Every field the resolver reads is now projected (clone of owned data, no /// `Box::leak`); the resolver-side FORWARD_DECL types were widened to owned /// `Box<[Box<[u8]>]>`/`StringSet`/`StringArrayHashMap` so this is a faithful /// value copy rather than a `Default` stub. /// -/// TODO(b3): drop this once `bun_options_types::BundleOptions` exists and both +/// This projection can be dropped once `bun_options_types::BundleOptions` exists and both /// crates re-export it — `Resolver::init1` will then take the canonical type -/// directly and Zig's `bundle_options` value can flow through unchanged -/// (transpiler.zig:209 passes the same `options` to both struct fields). +/// directly. /// /// `#[cold]`/`#[inline(never)]`: this is a ~100-line struct-construction blob /// run exactly once per `Transpiler::init` (i.e. once per VM bring-up). Keeping @@ -1149,15 +1125,13 @@ pub(crate) fn resolver_bundle_options_subset( } }), global_cache: src.global_cache, - // Spec `options.zig:1753`: `?*const Api.BunInstall` — both sides store + // Both sides store // `Option>`, so this is a straight copy. install: src.install, load_package_json: src.load_package_json, load_tsconfig_json: src.load_tsconfig_json, main_field_extension_order: ropts::owned_string_list(src.main_field_extension_order), - // Spec resolver.zig `auto_main` compares the pointer of - // `opts.main_fields` against the per-target default; with owned - // storage that pointer test can't hold, so project the predicate as a + // `auto_main` is projected as a // bool: it's "default" iff the user did not pass `--main-fields` // (`from_api` overwrites `main_fields` only when // `transform.main_fields` is non-empty — options.rs:2231). @@ -1172,9 +1146,8 @@ pub(crate) fn resolver_bundle_options_subset( production: src.production, force_node_env: src.force_node_env, // FORWARD_DECL: bundler-only fields read via `c.resolver.opts` in - // `linker_context/*` (Zig stores the full `BundleOptions` on the - // resolver). Project them so the linker sees the same values it would - // have read off the spec's shared struct. + // `linker_context/*`. Project them so the linker sees the same values + // the bundler configured. output_dir: src.output_dir.clone(), root_dir: src.root_dir.clone(), public_path: src.public_path.clone(), @@ -1186,15 +1159,13 @@ pub(crate) fn resolver_bundle_options_subset( } impl<'a> Transpiler<'a> { - /// Port of `transpiler.zig:Transpiler.init`. - /// - /// Called by [`init_runtime_state`](../runtime/jsc_hooks.rs) (spec - /// `VirtualMachine.zig:1241`) to write `vm.transpiler`. Builds on: + /// Called by [`init_runtime_state`](../runtime/jsc_hooks.rs) + /// to write `vm.transpiler`. Builds on: /// * [`options::BundleOptions::from_api`] — `bun_bundler::options` /// * [`Resolver::init1`] — `bun_resolver` /// - /// PORT NOTE: `log` / `env_loader_` are raw pointers (not `&'a mut`) to - /// match the un-gated struct field types — Zig aliased the same `*Log` + /// `log` / `env_loader_` are raw pointers (not `&'a mut`) to + /// match the un-gated struct field types — the same `*Log` is aliased /// into `linker.log` / `resolver.log` (see `set_log`). pub fn init( arena: &'a Arena, @@ -1230,7 +1201,6 @@ impl<'a> Transpiler<'a> { // deref sites below go through `NonNull` rather than the raw argument. let log_nn = core::ptr::NonNull::new(log).expect("Transpiler::init_in_place: log is non-null"); - // TODO(port): narrow error set bun_ast::expr::data::Store::create(); bun_ast::stmt::data::Store::create(); // These two `create()`s are eager (not deferred to the first `parse()`) @@ -1253,8 +1223,7 @@ impl<'a> Transpiler<'a> { // `reset()` branches to `enter()` on null ARENA), so per-file ASTs // *do* get the side arena from the first parsed file onward. - // PORT NOTE: `FileSystem::init` wants `&'static [u8]`; Zig passed a - // borrowed slice (transpiler.zig:179). Intern via `DirnameStore` + // `FileSystem::init` wants `&'static [u8]`. Intern via `DirnameStore` // (the same path `FileSystem::init` already uses for the // `None`/getcwd case — fs.rs:222) so the cwd lives in the // process-lifetime BSS string store without `Box::leak`. PORTING.md @@ -1280,9 +1249,6 @@ impl<'a> Transpiler<'a> { // Transfer ownership of both allocations into the global // singleton via `heap::alloc` (the AtomicPtr becomes the // owner; matches `MiniEventLoop::init_global`). - // TODO(port): replace with a `OnceLock`-backed - // `bun_dotenv::instance_or_init()` accessor once - // `bun_dotenv` grows one (PORTING.md §Concurrency). let map: *mut dot_env::Map = bun_core::heap::into_raw(Box::new(dot_env::Map::init())); // SAFETY: `map` is a fresh heap allocation with no other @@ -1312,7 +1278,7 @@ impl<'a> Transpiler<'a> { // `log` stays raw — `from_api` stores it in `BundleOptions.log: *mut` // and the same pointer is aliased into `Resolver::init1` / `Linker` - // / the struct field below (Zig aliased `*Log` everywhere). No `&'a + // / the struct field below. No `&'a // mut Log` is materialized here, so the sibling raw pointers don't // invalidate a long-lived unique borrow under stacked borrows. // SAFETY: `fs` is the process-lifetime `Fs::FileSystem` singleton from @@ -1333,7 +1299,7 @@ impl<'a> Transpiler<'a> { // Construct directly into the caller-owned storage instead of building a // stack temporary and returning it. All fallible work is done; every // field below is written exactly once. `Linker::init` gets null - // back-pointers (Zig used `undefined`) — `core::mem::zeroed()` is NOT a + // back-pointers — `core::mem::zeroed()` is NOT a // valid analogue (`Linker.hashed_filenames: HashMap` carries a `NonNull` // niche, so all-zeroes is instant UB); the value fields get their proper // defaults and `configure_linker_with_auto_jsx` overwrites the @@ -1407,10 +1373,8 @@ impl<'a> Transpiler<'a> { >( &mut self, mut this_parse: ParseOptions<'a, '_>, - // TODO(port): Zig `anytype` + `@hasField(.., "source")` — only ever - // called with `?*EntryPoints.ClientEntryPoint` in this file. If other - // callers pass a different type, introduce a `ClientEntryPointLike` - // trait with `fn source() -> Option<&Source>`. + // The only caller passes `EntryPoints::ClientEntryPoint`, so the + // param is typed concretely. client_entry_point_: Option<&mut EntryPoints::ClientEntryPoint>, ) -> Option> { let arena = this_parse.arena; @@ -1434,15 +1398,12 @@ impl<'a> Transpiler<'a> { // (`Drop` is a no-op). let mut source_backing: resolver::cache::Contents = resolver::cache::Contents::Empty; - // PORT NOTE: Zig `&brk: { ... }` took the address of a temporary; Rust - // owns the value and borrows it after the block. let source: &'a bun_ast::Source = arena.alloc('brk: { if let Some(virtual_source) = this_parse.virtual_source { break 'brk virtual_source.clone(); } if let Some(client_entry_point) = client_entry_point_ { - // Zig: if (@hasField(Child, "source")) — ClientEntryPoint always has it. break 'brk client_entry_point.source.clone(); } @@ -1454,7 +1415,7 @@ impl<'a> Transpiler<'a> { break 'brk bun_ast::Source::init_path_string(path.text, b""); } - // Spec transpiler.zig:826-835. The decoded body is owned in + // The decoded body is owned in // `source_backing` (below) so `source.contents` re-borrows it // without leaking; never falls through to `read_file_with_allocator` // (which would try to open `data:...` as a filesystem path). @@ -1501,8 +1462,7 @@ impl<'a> Transpiler<'a> { break 'brk bun_ast::Source::init_path_string(path.text, contents); } - // Zig (`transpiler.zig:838-839`): `if (use_shared_buffer) - // bun.default_allocator else this_parse.allocator`. Thread + // Thread // `this_parse.arena` (the per-call `MimallocArena` from // `RuntimeTranspilerStore`) so the source bytes land in the // job-scoped heap that `TranspilerJob::run` `mi_heap_destroy`s on @@ -1533,7 +1493,7 @@ impl<'a> Transpiler<'a> { if let Some(file_fd_ptr) = this_parse.file_fd_ptr { *file_fd_ptr = entry.fd; } - // PORT NOTE: `Source.contents: &'static [u8]` (the AST crate's `Str` + // `Source.contents: &'static [u8]` (the AST crate's `Str` // convention). The bytes live either in the per-thread shared // buffer (`USE_SHARED_BUFFER` → `Contents::SharedBuffer`, no-op // drop) or in `this_parse.arena` (`Contents::Arena`, no-op drop — @@ -1541,9 +1501,7 @@ impl<'a> Transpiler<'a> { // recycled). Thread the // provenance-tagged backing alongside the `ParseResult` so it // drops when the result is recycled — no `mem::forget` - // (PORTING.md §Forbidden patterns). Spec transpiler.zig:853 hands - // `entry.contents` to `Source.initRecycledFile` by slice; Zig has - // no implicit drop, so ownership was already with the caller. + // (PORTING.md §Forbidden patterns). source_backing = core::mem::take(&mut entry.contents); // SAFETY: `source_backing` outlives every read through // `source.contents` (it is moved into the returned `ParseResult`, @@ -1606,7 +1564,7 @@ impl<'a> Transpiler<'a> { jsx.parse = loader.is_jsx(); let _ = &this_parse.macro_remappings; - // PORT NOTE: `ParserOptions::init` is hard-typed + // `ParserOptions::init` is hard-typed // `-> Options<'static>` and `Options<'a>` is *invariant* in // `'a` (it holds `Option<&'a mut MacroContext>`), so an // `Options<'static>` cannot be passed to @@ -1658,8 +1616,7 @@ impl<'a> Transpiler<'a> { opts.features.no_macros = self.options.no_macros; // `bun_ast::RuntimeTranspilerCache` is the single nominal // type on both sides; thread the pointer directly. - // Spec transpiler.zig:899/957 copies the same - // `?*RuntimeTranspilerCache` raw pointer to BOTH + // The same `RuntimeTranspilerCache` raw pointer goes to BOTH // `opts.features` and the returned `ParseResult`. Derive both // from a single reborrow so they share one provenance tag — // re-touching the parent `&mut` after the `*mut` cast would @@ -1686,12 +1643,10 @@ impl<'a> Transpiler<'a> { opts.features.minify_identifiers = self.options.minify_identifiers; opts.features.dead_code_elimination = self.options.dead_code_elimination; opts.features.remove_cjs_module_wrapper = this_parse.remove_cjs_module_wrapper; - // Spec transpiler.zig:925 forwards `transpiler.options - // .bundler_feature_flags`. Zig aliased a `*const StringSet`; // `Features.bundler_feature_flags` is currently owned // (`Option>`), so clone by value. - // TODO(refactor): change the parser-side field to - // `Option<&'a StringSet>` to avoid the clone. + // (Changing the parser-side field to `Option<&'a StringSet>` + // would avoid the clone.) // The clone drops with `opts` — no leak. opts.features.bundler_feature_flags = self .options @@ -1705,8 +1660,6 @@ impl<'a> Transpiler<'a> { opts.features.top_level_await = true; opts.features.is_macro_runtime = target == crate::options_impl::Target::BunMacro; - // Spec transpiler.zig:943: `opts.features.replace_exports = - // this_parse.replace_exports`. // `bun_ast::runtime::ReplaceableExport` IS // `js_ast::Runtime::ReplaceableExport`, so the inner // `StringArrayHashMap` moves directly into the newtype. @@ -1718,7 +1671,7 @@ impl<'a> Transpiler<'a> { let ctx = js_ast::Macro::MacroContext::init(self); self.macro_context = Some(ctx); } - // Spec transpiler.zig:938-940: thread the caller-supplied JS + // Thread the caller-supplied JS // context into the macro runtime so macros invoked during // runtime transpilation see it (instead of null). Written on // `self.macro_context` before reborrowing into `opts` so the @@ -1736,8 +1689,7 @@ impl<'a> Transpiler<'a> { // owned by the long-lived `Transpiler`; the parser borrows // them for `'a` (arena lifetime). Erase to `'a` so the // returned `Ast<'a>` is not pinned to the `&mut self` borrow - // — neither field is dropped while a parse is in flight - // (Zig held `*const Define` / `*MacroContext`). + // — neither field is dropped while a parse is in flight. unsafe { let define_ptr: *const js_ast::defines::Define = &raw const *self.options.define; @@ -1748,7 +1700,7 @@ impl<'a> Transpiler<'a> { .map(|m| &mut *core::ptr::from_mut(m)); } - // PORT NOTE: spec calls `transpiler.resolver.caches.js.parse`. + // spec calls `transpiler.resolver.caches.js.parse`. // The resolver-side `cache::JavaScript` is a fieldless // shell with no `parse` body (resolver/lib.rs:1664); // the real `parse` lives on `crate::cache::JavaScript`. Both @@ -1790,7 +1742,7 @@ impl<'a> Transpiler<'a> { js_ast::AlreadyBundled::BunCjs => AlreadyBundled::SourceCodeCjs, js_ast::AlreadyBundled::BytecodeCjs | js_ast::AlreadyBundled::Bytecode => 'brk: { - // Spec transpiler.zig:971-984: when the parser + // When the parser // saw `// @bun @bytecode`, attempt to load the // sidecar `.jsc` cached bytecode. Only // fall back to re-parsing source on read @@ -1805,8 +1757,7 @@ impl<'a> Transpiler<'a> { if this_parse.virtual_source.is_none() && this_parse.allow_bytecode_cache { - // PORT NOTE: `bun.bytecode_extension` - // (bun.zig:3502) — no Rust const re-export + // No shared const for the bytecode extension // in `bun_core` yet, so inline the literal. const BYTECODE_EXT: &[u8] = b".jsc"; let mut path_buf2 = bun_paths::PathBuffer::uninit(); @@ -1819,7 +1770,7 @@ impl<'a> Transpiler<'a> { // `path_buf2[total] == 0` already; safe to // borrow as a NUL-terminated ZStr. let zpath = bun_core::ZStr::from_buf(&path_buf2[..], total); - // PORT NOTE: spec calls + // spec calls // `bun.sys.File.toSourceAt(...)` which is // `read_from` + wrap-in-`bun_ast::Source`. // We only need `.contents`, so call @@ -1922,11 +1873,7 @@ fn parse_data_loader<'a>( log: &mut bun_ast::Log, keep_json_and_toml_as_one_statement: bool, ) -> Option> { - // PERF(port): was `inline .toml, .yaml, .json, .jsonc, .json5 - // => |kind|` — comptime monomorphization per loader; profile if it - // shows up on a hot path. - // - // PORT NOTE: `bun_parsers::*` parse into the T2 value AST + // `bun_parsers::*` parse into the T2 value AST // (`bun_ast::Expr`); lift into the full T4 // `bun_ast::Expr` via the deep-convert `From` bridge // (Expr.rs:1265) so the StoreRef-backed accessors below work. @@ -1964,8 +1911,7 @@ fn parse_data_loader<'a>( let mut symbols: Vec = Vec::new(); - // PORT NOTE: reshaped — Zig `arena.alloc(Part, 1)` returned - // an arena slice, but `Ast::from_parts` takes `Box<[Part]>` + // `Ast::from_parts` takes `Box<[Part]>` // (Vec owns its buffer). The single-part array is built on // the global heap; `stmts` stays arena-backed (`*mut [Stmt]`). let parts: Box<[bun_ast::Part]> = 'parts: { @@ -1978,7 +1924,6 @@ fn parse_data_loader<'a>( }, bun_ast::Loc { start: 0 }, ); - // PERF(port): was `arena.alloc(Stmt, 1) catch unreachable`. let stmts = bun_ast::StoreSlice::new_mut(arena.alloc_slice_copy(&[stmt])); break 'parts Box::new([bun_ast::Part { stmts, @@ -1990,19 +1935,17 @@ fn parse_data_loader<'a>( let properties: &mut [bun_ast::G::Property] = obj.properties.slice_mut(); if !properties.is_empty() { let n = properties.len(); - // PORT NOTE: Zig `expandToCapacity()` / `arena.alloc(Symbol, n)` - // leave slots uninitialized, which is inert in Zig. // The loop below writes sparsely at index `i` and // `continue`s on `"default"` / duplicate keys, so // some slots are never assigned. In Rust an uninit // live `Vec` element is UB the moment it is // observed (truncate/into_boxed_slice/index-assign), // so pre-fill every slot with `Default` instead of - // `set_len`. PERF(port): was `expandToCapacity()`. + // `set_len`. let mut decls: Vec = vec![bun_ast::G::Decl::default(); n]; symbols.resize_with(n, Default::default); - // PORT NOTE: `S::ExportClause.items: *mut [ClauseItem]` + // `S::ExportClause.items: *mut [ClauseItem]` // is arena-owned; `ClauseItem: Default` so // `alloc_slice_fill_default` is fine. let export_clauses = arena.alloc_slice_fill_default::(n); @@ -2010,13 +1953,13 @@ fn parse_data_loader<'a>( bun_collections::StringHashMap::default(); // duplicate_key_checker drops at end of scope (defer .deinit()) let mut count: usize = 0; - // PORT NOTE: reshaped for borrowck — cannot zip 4 + // reshaped for borrowck — cannot zip 4 // slices with one mutable borrow into `decls` and // also random-access `decls[prev]`. for i in 0..n { let prop = &mut properties[i]; // SAFETY: data-format parsers always emit - // `e_string` keys (Zig `.?.data.e_string`). + // `e_string` keys. let key = prop.key.as_mut().unwrap(); let key_loc = key.loc; let name: &[u8] = key @@ -2038,10 +1981,7 @@ fn parse_data_loader<'a>( Some(prop.value.expect("infallible: prop has value")); continue; } - // PORT NOTE: spec transpiler.zig:1030-1071 - // writes at `i` and shrinks to `count`, leaving - // holes when `"default"` / duplicates `continue` - // — a latent spec bug. Write densely at `count` + // Write densely at `count` // (and store `count` in the checker) so // `truncate(count)` / `[..count]` keep the // actually-populated entries. @@ -2050,9 +1990,7 @@ fn parse_data_loader<'a>( symbols[count] = bun_ast::Symbol { original_name: match bun_core::MutableString::ensure_valid_identifier(name) { - // Spec transpiler.zig:1049 calls - // `MutableString.ensureValidIdentifier(name, arena)` - // — the identifier lives in the + // The identifier lives in the // per-parse arena. Arena-copy the // owned `Box<[u8]>` so it is freed // with the arena instead of leaking @@ -2183,7 +2121,6 @@ fn parse_text_loader<'a>( }, bun_ast::Loc { start: 0 }, ); - // PERF(port): was `arena.alloc(Stmt, 1) catch unreachable`. let stmts = bun_ast::StoreSlice::new_mut(arena.alloc_slice_copy(&[stmt])); let parts: Box<[bun_ast::Part]> = Box::new([bun_ast::Part { stmts, @@ -2214,7 +2151,7 @@ fn parse_md_loader<'a>( log: &mut bun_ast::Log, ) -> Option> { let html: &'static [u8] = match bun_md::root::render_to_html(&source.contents) { - // Spec transpiler.zig:1162 allocates the rendered HTML via + // The rendered HTML is allocated via // `arena` (the per-parse arena), so it is freed with the // arena. Arena-copy the heap `Box<[u8]>` and let it drop; // PORTING.md §Forbidden patterns bars `Box::leak` here. @@ -2304,7 +2241,7 @@ fn parse_wasm_loader<'a>( #[cold] #[inline(never)] fn parse_unsupported_loader(loader: options::Loader, path: &bun_paths::fs::Path<'static>) -> ! { - // Spec transpiler.zig:1216 — programmer-error hard crash, NOT a + // Programmer-error hard crash, NOT a // silent `None` (PORTING.md §Forbidden: silent no-op). bun_core::Output::panic(format_args!( "Unsupported loader {:?} for path: {}", @@ -2318,26 +2255,26 @@ fn parse_unsupported_loader(loader: options::Loader, path: &bun_paths::fs::Path< // `ModuleLoader::transpile_source_code` (jsc_hooks.rs spec :525-539); the // dispatch shim that `RuntimeTranspilerStore` / `AsyncModule` link against. // -// PORT NOTE: `comptime format: js_printer.Format` demoted to a runtime arg — +// `format` is a runtime arg rather than a const generic — // `bun_js_printer::Format` doesn't derive `ConstParamTy` (and can't be added // from this crate). All callers pass a literal anyway; the inner -// `print_ast::<_, ASCII_ONLY, ENABLE_SOURCE_MAP>` keeps both real comptime +// `print_ast::<_, ASCII_ONLY, ENABLE_SOURCE_MAP>` keeps both const-generic // bools, so codegen monomorphizes the printer body identically. -// PERF(port): outer `match format` is one extra branch — profile if hot. +// PERF: outer `match format` is one extra branch — profile if hot. // ══════════════════════════════════════════════════════════════════════════ use bun_js_printer as js_printer; -// PORT NOTE: `module_info` threads the *printer's* `analyze_transpiled_module::ModuleInfo` +// `module_info` threads the *printer's* `analyze_transpiled_module::ModuleInfo` // (the producer), not `crate::analyze_transpiled_module::ModuleInfo` (the // richer consumer-side mirror). The print // path only ever fills the printer-owned one and hands its serialized bytes to -// T6, so unify on the printer type here. Spec: transpiler.zig:663. +// T6, so unify on the printer type here. use js_printer::analyze_transpiled_module; /// Map the bundler-local `Target` (options.rs:489) to the lower-tier /// `bun_ast::Target` consumed by `js_printer::Options`. /// The two enums are variant-for-variant identical but nominally distinct; -/// TODO(refactor): collapse them (see lib.rs `pub mod options` shadow note). +/// they could be collapsed (see lib.rs `pub mod options` shadow note). #[inline] fn to_bundle_enums_target(t: crate::options_impl::Target) -> bun_ast::Target { use bun_ast::Target as T; @@ -2360,8 +2297,8 @@ pub use js_printer::Format as PrintFormat; // `&mut BufferPrinter`. Leaving the public entry points generic forced each // downstream crate (bun_runtime / bun_jsc / bun_install / bun_bundler) to stamp // out its own copy of the 109-fn `Printer` recursion tree — -// `llvm-nm --print-size` showed `bun_js_printer` .text at 1,367 KB vs 594 KB on -// the Zig build, with both the `_11bun_runtime` and `_7bun_jsc` copies of +// `llvm-nm --print-size` showed `bun_js_printer` .text at 1,367 KB, +// with both the `_11bun_runtime` and `_7bun_jsc` copies of // `print_expr<…>` live in `perf` and thrashing icache against each other // (L1-icache-misses +5.1%, iTLB-misses +13.2%, IPC 1.40 vs 1.50). Pinning `W` // to the one concrete type and marking the public entry points @@ -2378,16 +2315,16 @@ impl<'a> Transpiler<'a> { runtime_transpiler_cache: Option>, module_info: Option<*mut analyze_transpiled_module::ModuleInfo>, ) -> Result { - // TODO(port): narrow error set - // TODO(port): `bun.perf.trace("JSPrinter.printWithSourceMap")` / - // `("JSPrinter.print")` — `bun_perf::trace` now takes a `PerfEvent` - // enum and neither variant is in `generated_perf_trace_events.rs` - // yet. Re-add once `scripts/generate-perf-trace-events.sh` runs - // against the Rust tree. - - // PORT NOTE: Zig built `Symbol.NestedList.fromBorrowedSliceDangerous( - // &.{ast.symbols})` — aliased the stack-one-slice into the map. Rust - // can't borrow `ast.symbols` while moving `ast` into `print_ast`, so + // Routed through the T0 ftrace subset like the + // other bundler spans (`Bundler.computeChunks` etc.) — + // `bun_perf::PerfEvent` has no JSPrinter variants yet. + let _trace = bun_core::perf::trace(if ENABLE_SOURCE_MAP { + "JSPrinter.printWithSourceMap" + } else { + "JSPrinter.print" + }); + + // We can't borrow `ast.symbols` while moving `ast` into `print_ast`, so // take the column out (the printer never reads `tree.symbols`; it // walks `symbols` exclusively — `rg tree.symbols js_printer/lib.rs` is // empty). `init_with_one_list` boxes the single inner list. @@ -2401,16 +2338,16 @@ impl<'a> Transpiler<'a> { // `runtime_imports` is now forwarded — after Round-G `Ast.runtime_imports` // is the real `parser::Runtime::Imports`, the same type // `js_printer::Options.runtime_imports` takes (via `js_ast::runtime`), - // so the seam is gone. Spec: zig:593/619/645. + // so the seam is gone. // `target` is now forwarded via `to_bundle_enums_target` below — it // *does* affect the EsmAscii/bun-runtime path (js_printer/lib.rs:6872 // gates the `var {require}=import.meta;` hoist on `target == Bun`; // regression of oven-sh/bun#15738 if left at the `Browser` default). // `runtime_transpiler_cache` is now forwarded — js_printer holds the - // `NonNull` directly. Spec: zig:601/627/662. + // `NonNull` directly. // `module_info` is now forwarded — this fn's parameter is the // printer-crate `analyze_transpiled_module::ModuleInfo` (see the `use` - // above), so the seam is gone. Spec: zig:663 — EsmAscii arm only. + // above), so the seam is gone (EsmAscii arm only). let exports_kind = ast.exports_kind; @@ -2451,10 +2388,9 @@ impl<'a> Transpiler<'a> { ), js_printer::Format::EsmAscii => { - // PORT NOTE: `switch (target.isBun()) { inline else => |is_bun| ... }` - // — runtime bool → comptime dispatch. Hoisted into the - // `print_ast_esm_ascii` helper so the const-generic IS_BUN can - // also drive `module_type`. + // Runtime `target.is_bun()` bool → const-generic dispatch, + // hoisted into the `print_ast_esm_ascii` helper so the + // const-generic IS_BUN can also drive `module_type`. if self.options.target.is_bun() { self.print_ast_esm_ascii::( print_arena, @@ -2482,7 +2418,6 @@ impl<'a> Transpiler<'a> { } } - // Spec transpiler.zig:672 `else => unreachable`. js_printer::Format::CjsAscii => unreachable!(), } } @@ -2608,9 +2543,8 @@ impl<'a> Transpiler<'a> { ) } - // PORT NOTE: hoisted from `inline else => |is_bun|` arm of - // print_with_source_map_maybe to express the comptime bool dispatch as a - // const generic. + // hoisted out of print_with_source_map_maybe to express the + // is-bun bool dispatch as a const generic. #[allow(clippy::too_many_arguments)] fn print_ast_esm_ascii( &mut self, @@ -2624,7 +2558,7 @@ impl<'a> Transpiler<'a> { runtime_transpiler_cache: Option, module_info: Option<*mut analyze_transpiled_module::ModuleInfo>, ) -> Result { - // Spec transpiler.zig:662-663 — both set on this (EsmAscii) arm only. + // Both set on this (EsmAscii) arm only. // SAFETY: `module_info` is `ModuleInfo::create`'s `heap::alloc` (or // null); it is exclusively owned by this print call until T6 reclaims // it after `print_with_source_map` returns. @@ -2655,7 +2589,7 @@ impl<'a> Transpiler<'a> { module_info, hmr_ref: ast.wrapper_ref, mangled_props: None, - // Spec transpiler.zig:664. The printer reads `opts.target` at + // The printer reads `opts.target` at // js_printer/lib.rs:6872 to gate the `var {require}=import.meta;` // hoist on `Target::Bun` — defaulting to `Browser` here regressed // oven-sh/bun#15738. @@ -2718,8 +2652,8 @@ impl<'a> Transpiler<'a> { handler: js_printer::SourceMapHandler<'_>, module_info: Option<*mut analyze_transpiled_module::ModuleInfo>, ) -> Result { - // PORT NOTE: env_var feature_flag getters return `Option` - // (Some(default) when unset); Zig's `.get()` is plain `bool`. + // env_var feature_flag getters return `Option` + // (Some(default) when unset). if bun_core::env_var::feature_flag::BUN_FEATURE_FLAG_DISABLE_SOURCE_MAPS .get() .unwrap_or(false) @@ -2779,14 +2713,12 @@ impl<'a> Transpiler<'a> { ) } - /// Port of `transpiler.zig:1225 normalizeEntryPointPath`. fn normalize_entry_point_path(&self, _entry: &[u8]) -> &'static [u8] { let fs = self.fs(); let entry = fs.abs(&[_entry]); - // Spec: `std.fs.accessAbsolute(entry, .{}) catch return _entry` — if the - // absolutized path does not exist on disk, return the original input - // unchanged so bare specifiers (`react`) and URLs are left alone. + // If the absolutized path does not exist on disk, return the original + // input unchanged so bare specifiers (`react`) and URLs are left alone. if !bun_sys::exists(entry) { return crate::linker::dupe(_entry); } @@ -2814,16 +2746,12 @@ impl<'a> Transpiler<'a> { crate::linker::dupe(entry) } - /// Port of `transpiler.zig:1254 enqueueEntryPoints`. - /// - /// PORT NOTE: the Zig version writes the resolved entry results into a - /// caller-provided `[]Result` slice; the only caller (`transform`) discards - /// that slice immediately, so the Rust port returns only the count and lets + /// Returns only the count and lets /// `linker.enqueue_resolve_result` push directly onto `resolve_queue`. fn enqueue_entry_points(&mut self) -> usize { let mut entry_point_i: usize = 0; - // PORT NOTE: snapshot entry points so the `&mut self` resolver call + // snapshot entry points so the `&mut self` resolver call // does not conflict with the `&self.options` borrow. let entries: Vec> = self.options.entry_points.to_vec(); let top_level_dir = self.fs().top_level_dir; @@ -2873,7 +2801,6 @@ impl<'a> Transpiler<'a> { entry_point_i } - /// Port of `transpiler.zig:1286 transform`. pub fn transform( &mut self, log: *mut bun_ast::Log, @@ -2947,8 +2874,8 @@ impl<'a> Transpiler<'a> { Ok(final_result) } - /// Port of `transpiler.zig:1344 processResolveQueue` (with - /// `wrap_entry_point = false`, the only value passed by the in-tree caller). + /// `wrap_entry_point` is fixed to `false`, the only value passed by the + /// in-tree caller. fn process_resolve_queue( &mut self, import_path_format: options::ImportPathFormat, @@ -2973,7 +2900,6 @@ impl<'a> Transpiler<'a> { Ok(()) } - /// Port of `transpiler.zig:380 buildWithResolveResultEager`. fn build_with_resolve_result_eager( &mut self, resolve_result: &resolver::Result, @@ -2988,7 +2914,7 @@ impl<'a> Transpiler<'a> { let Some(file_path_ref) = resolve_result.path_const() else { return Ok(None); }; - // PORT NOTE: `resolver::Result.path_pair` carries `bun_resolver::fs::Path<'_>`; + // `resolver::Result.path_pair` carries `bun_resolver::fs::Path<'_>`; // downstream `linker.link`/`get_hashed_filename` and `OutputFile.src_path` // expect `bun_paths::fs::Path<'_>` / `bun_paths::fs::Path<'static>`. Re-init via // `text` (the only field both shapes share semantically). @@ -2996,9 +2922,9 @@ impl<'a> Transpiler<'a> { let file_path_ext: &'static [u8] = crate::linker::dupe(file_path_ref.name().ext); // Step 1. Parse & scan - // Spec (transpiler.zig:397) keys the loader on the ORIGINAL resolve - // result's extension *before* the `client_entry_point` path override - // (line 400). Compute it here, then apply the override. + // Key the loader on the ORIGINAL resolve + // result's extension *before* the `client_entry_point` path override. + // Compute it here, then apply the override. let loader = self.options.loader(file_path_ext); // `client_entry_point_` is always `None` from the only in-tree caller; @@ -3038,15 +2964,17 @@ impl<'a> Transpiler<'a> { | options::Loader::Json5 | options::Loader::Text | options::Loader::Md => { - // PORT NOTE: borrowck — `parse` consumes `&mut self`, so capture + // borrowck — `parse` consumes `&mut self`, so capture // the option fields needed for `ParseOptions` first. let jsx = jsx_pragma_from_resolver(&resolve_result.jsx); let dirname_fd = resolve_result.dirname_fd; let emit_decorator_metadata = resolve_result.flags.emit_decorator_metadata(); let experimental_decorators = resolve_result.flags.experimental_decorators(); - // TODO(port): `MacroRemap` (StringArrayHashMap of StringArrayHashMap) - // has no nested `Clone` impl; the Zig copied it by value. Re-key - // shallowly here matching the build-command conversion. + // `MacroRemap` (StringArrayHashMap of StringArrayHashMap) has + // no nested `Clone` impl (the inner clone is fallible). + // Rebuild the outer map, deep-cloning + // each inner map (fallible), matching the build-command + // conversion. let macro_remappings = { let mut m = MacroRemap::default(); for (k, v) in self.options.macro_remap.iter() { @@ -3215,8 +3143,8 @@ impl<'a> Transpiler<'a> { // the CSS AST it backs is dropped before this fn returns // (only `result.code: Vec` escapes, which is // global-heap). `'static` matches the crate-wide erasure - // on `StyleSheet`/`ParserOptions` (see css_parser.rs - // TODO(port): 'bump threading). + // on `StyleSheet`/`ParserOptions` (see the css_parser.rs + // `'bump`-threading note). let alloc: &'static Arena = unsafe { bun_ptr::detach_lifetime_ref::(self.arena) }; let (mut sheet, extra) = match bun_css::StyleSheet::::parse( @@ -3301,14 +3229,11 @@ impl<'a> Transpiler<'a> { } } -/// Port of the `comptime Outstream: type` parameter to -/// `processResolveQueue` / `buildWithResolveResultEager` — Zig switched on -/// `bun.sys.File` vs `std.fs.Dir` at the type level; collapse to a runtime -/// enum since the only behavioural difference is unused (`_ = outstream`). +/// Outstream selector for `process_resolve_queue` / +/// `build_with_resolve_result_eager` — a runtime enum since the only +/// behavioural difference is unused (`_ = outstream`). #[derive(Clone, Copy)] enum TransformOutstream { Stdout, Dir(#[expect(dead_code)] bun_sys::Fd), } - -// ported from: src/bundler/transpiler.zig diff --git a/src/bundler_jsc/PluginRunner.rs b/src/bundler_jsc/PluginRunner.rs index fe427df7100..4395d2d1c0b 100644 --- a/src/bundler_jsc/PluginRunner.rs +++ b/src/bundler_jsc/PluginRunner.rs @@ -1,41 +1,38 @@ -//! Runtime plugin host (JS-side `Bun.plugin()` resolve hooks). Moved from -//! `bundler/transpiler.zig` so `bundler/` is free of `JSValue`/`JSGlobalObject`. +//! Runtime plugin host (JS-side `Bun.plugin()` resolve hooks). Lives here so +//! `bundler/` is free of `JSValue`/`JSGlobalObject`. pub use bun_resolver::fs::Path as FsPath; -/// Spec `PluginRunner.zig:MacroJSCtx` — re-export of the canonical newtype +/// Re-export of the canonical newtype /// (defined at the lowest tier that stores it, `bun_ast::Macro`). pub use bun_bundler::transpiler::MacroJSCtx as MacroJsCtx; -/// Spec `PluginRunner.zig:PluginRunner` — re-export of the concrete struct. +/// Re-export of the concrete struct. /// `extract_namespace` / `could_be_plugin` (pure byte parsing) live in /// `bun_bundler`; the stateful struct + `on_resolve` body live in /// `bun_jsc::plugin_runner`. `on_resolve_jsc` (below) is a free fn because it /// only reads the global, not the runner record. pub use bun_jsc::plugin_runner::PluginRunner; -/// Spec PluginRunner.zig:14 — re-export for callers that named this module. +/// Re-export for callers that named this module. #[inline] pub fn extract_namespace(specifier: &[u8]) -> &[u8] { PluginRunner::extract_namespace(specifier) } -/// Spec PluginRunner.zig:22 — re-export for callers that named this module. +/// Re-export for callers that named this module. #[inline] pub fn could_be_plugin(specifier: &[u8]) -> bool { PluginRunner::could_be_plugin(specifier) } -// `on_resolve` (the `Log`-reporting variant, PluginRunner.zig:34) lives at +// `on_resolve` (the `Log`-reporting variant) lives at // `bun_jsc::plugin_runner::PluginRunner` as the `PluginResolver` impl — // `bun_jsc` is the lowest tier that can name `JSGlobalObject` AND is reachable // from `Bun__onDidAppendPlugin`. -/// Spec PluginRunner.zig:121 `onResolveJSC`. /// LAYERING: body moved DOWN into `bun_jsc::virtual_machine` so the /// VM's `resolve_maybe_needs_trailing_slash` can consult it without a /// `bun_jsc → bun_bundler_jsc` cycle. Re-exported here for callers that /// still name this module. pub use bun_jsc::virtual_machine::plugin_runner_on_resolve_jsc as on_resolve_jsc; - -// ported from: src/bundler_jsc/PluginRunner.zig diff --git a/src/bundler_jsc/analyze_jsc.rs b/src/bundler_jsc/analyze_jsc.rs index d4313d4eb65..7648b1b3763 100644 --- a/src/bundler_jsc/analyze_jsc.rs +++ b/src/bundler_jsc/analyze_jsc.rs @@ -2,7 +2,7 @@ //! `ModuleInfoDeserialized` into a `JSC::JSModuleRecord`. Aliased back so the //! `extern "C"` symbol names are still discoverable from C++. //! -//! Note: the `zig__renderDiff` export from `analyze_jsc.zig` lives in +//! Note: the `zig__renderDiff` export lives in //! `bun_runtime::test_runner::diff_format` instead — `DiffFormatter` is a //! higher-tier type this crate cannot depend on, and the C++ caller only needs //! the symbol at link time, not a particular crate. @@ -28,10 +28,9 @@ pub(crate) extern "C" fn zig__ModuleInfoDeserialized__toJSModuleRecord( // SourceProvider cache. // Slice-field validity / alignment caveats are documented on the - // `ModuleInfoDeserialized` accessors. - // TODO(port): switch element reads to `read_unaligned` per the upstream - // note in `analyze_transpiled_module.rs` if a strict-alignment target is - // ever added. + // `ModuleInfoDeserialized` accessors. If a strict-alignment target is ever + // added, switch element reads to `read_unaligned` per the upstream note in + // `analyze_transpiled_module.rs`. let strings_buf: &[u8] = res.strings_buf(); let strings_lens: &[u32] = res.strings_lens(); let requested_modules_keys: &[StringID] = res.requested_modules_keys(); @@ -56,8 +55,8 @@ pub(crate) extern "C" fn zig__ModuleInfoDeserialized__toJSModuleRecord( } let identifiers = IdentifierArray::create(strings_lens.len()); - // SAFETY: `identifiers` is non-null (returned by `create`); destroyed exactly once at scope exit, - // mirroring Zig's `defer identifiers.destroy()` (runs on both success and early-return paths). + // SAFETY: `identifiers` is non-null (returned by `create`); the scopeguard destroys it + // exactly once at scope exit (on both success and early-return paths). let _identifiers_guard = scopeguard::guard(identifiers, |p| unsafe { IdentifierArray::destroy(p); }); @@ -142,7 +141,6 @@ pub(crate) extern "C" fn zig__ModuleInfoDeserialized__toJSModuleRecord( RequestedModuleValue::Json => { module_record.add_requested_module_json(identifiers, reqk, phase_defer) } - // Zig open-enum tail: `else => |uv| @enumFromInt(@intFromEnum(uv))` — // FetchParameters and StringID are both `#[repr(transparent)] u32`, so this // is a bitcast of the raw discriminant back into the interned-string index. uv => module_record.add_requested_module_host_defined( @@ -217,7 +215,6 @@ pub(crate) extern "C" fn zig__ModuleInfoDeserialized__toJSModuleRecord( } // ─── opaque FFI types ───────────────────────────────────────────────────────── -// TODO(port): move to bundler_jsc_sys bun_opaque::opaque_ffi! { pub struct VariableEnvironment; } unsafe extern "C" { @@ -411,8 +408,7 @@ impl JSModuleRecord { } // Thin method shims over the raw `*mut JSModuleRecord` returned by `create`. -// These take `*mut Self` because the Zig side calls them as `module_record.addX(...)` -// on a raw pointer; we keep raw-ptr receivers to avoid materializing `&mut` aliases. +// These take `*mut Self` raw-ptr receivers to avoid materializing `&mut` aliases. trait JSModuleRecordExt { fn add_indirect_export( self, @@ -676,5 +672,3 @@ impl JSModuleRecordExt for *mut JSModuleRecord { } } } - -// ported from: src/bundler_jsc/analyze_jsc.zig diff --git a/src/bundler_jsc/lib.rs b/src/bundler_jsc/lib.rs index 1afeea21373..92966512bd1 100644 --- a/src/bundler_jsc/lib.rs +++ b/src/bundler_jsc/lib.rs @@ -16,7 +16,7 @@ pub mod options_jsc; #[path = "PluginRunner.rs"] pub mod PluginRunner; -// LAYERING: `output_file_jsc` (port of `src/bundler_jsc/output_file_jsc.zig`) +// LAYERING: `output_file_jsc` // constructs `webcore::Blob`/`Store`, `api::BuildArtifact`, and // `node::PathOrFileDescriptor`. Those types live in `bun_runtime`, which is // not a dependency of this crate. The module has been moved to diff --git a/src/bundler_jsc/options_jsc.rs b/src/bundler_jsc/options_jsc.rs index 7fcd92a0b3e..c83418c1c71 100644 --- a/src/bundler_jsc/options_jsc.rs +++ b/src/bundler_jsc/options_jsc.rs @@ -80,5 +80,3 @@ pub fn compile_target_from_slice( Ok(target_parsed) } - -// ported from: src/bundler_jsc/options_jsc.zig diff --git a/src/bundler_jsc/source_map_mode_jsc.rs b/src/bundler_jsc/source_map_mode_jsc.rs index f3841db6ddd..77f025b5b22 100644 --- a/src/bundler_jsc/source_map_mode_jsc.rs +++ b/src/bundler_jsc/source_map_mode_jsc.rs @@ -1,6 +1,5 @@ -//! `from_js` for `bun.schema.api.SourceMapMode` — kept out of -//! `options_types/schema.zig` so that file has no `JSGlobalObject`/`JSValue` -//! references. +//! `from_js` for `bun.schema.api.SourceMapMode` — kept here so the schema +//! module has no `JSGlobalObject`/`JSValue` references. use crate::{JSGlobalObject, JSValue, JsResult}; use bun_options_types::schema::api::SourceMapMode; @@ -27,5 +26,3 @@ pub fn source_map_mode_from_js( } Ok(None) } - -// ported from: src/bundler_jsc/source_map_mode_jsc.zig diff --git a/src/bunfig/arguments.rs b/src/bunfig/arguments.rs index 5bea346d3e9..a25da143c6d 100644 --- a/src/bunfig/arguments.rs +++ b/src/bunfig/arguments.rs @@ -1,5 +1,4 @@ -//! Port of `src/runtime/cli/Arguments.zig` — bunfig-loading subset. -//! +//! Bunfig-loading subset of CLI argument handling: these functions //! and their private helpers were lifted out of `bun_runtime::cli::Arguments` //! so that mid-tier crates (`bun_install`) can call them directly. The //! `bun_runtime` crate re-exports these for its own callers. @@ -62,8 +61,8 @@ fn load_bunfig( bun_ast::expr::data::Store::create(); let _store_reset = bun_ast::StoreResetGuard::new(); - // PORT NOTE: reshaped for borrowck — `defer { ctx.log.level = original }` - // would capture `&mut *ctx.log` past the `Bunfig::parse(.., ctx)` reborrow. + // A drop-guard borrowing `&mut *ctx.log` would conflict with the + // `Bunfig::parse(.., ctx)` reborrow. // Route through the raw `*mut Log` (process-lifetime, set in // `create_context_data()`); the guard restores `level` on unwind/return. let log_ptr: *mut bun_ast::Log = ctx.log; @@ -100,9 +99,10 @@ pub fn load_config_path( config_path: &ZStr, ctx: Context<'_>, ) -> Result<(), bun_core::Error> { - // PORT NOTE: `comptime cmd.readGlobalConfig()` demoted to runtime — see - // `parse()` PORT NOTE; `Tag::read_global_config` is a const-ish lookup so - // the dead arm is still a single branch. + // `cmd.read_global_config()` is evaluated at runtime (see + // the note on `Parser::parse` in src/bunfig/bunfig.rs); + // `Tag::read_global_config` is a const-ish + // lookup so the dead arm is still a single branch. if cmd.read_global_config() { if let Err(err) = load_global_bunfig(cmd, ctx) { if auto_loaded { @@ -202,7 +202,7 @@ pub fn load_config( ctx.args.absolute_working_dir = Some(Box::<[u8]>::from(&secondbuf[..cwd_len])); } - // PORT NOTE: reshaped for borrowck — `join_abs_string_buf` ties the + // Reshaped for borrowck: `join_abs_string_buf` ties the // returned slice's lifetime to both `cwd` (borrowed from `ctx.args`) // and `config_buf`. We only need the length to NUL-terminate and // re-wrap, so capture `joined.len()` and drop the `ctx` borrow before diff --git a/src/bunfig/bunfig.rs b/src/bunfig/bunfig.rs index 8ed18c0faf5..eca200865bc 100644 --- a/src/bunfig/bunfig.rs +++ b/src/bunfig/bunfig.rs @@ -1,4 +1,4 @@ -//! Port of `src/runtime/cli/bunfig.zig`. +//! Bunfig (configuration file) loading. //! //! `Bunfig::parse` and the inner `Parser` route through the real //! `bun_parsers::{toml,json}` parsers (which produce the value-shaped @@ -28,7 +28,6 @@ use bun_options_types::schema::api; use bun_options_types::command_tag::Tag as CommandTag; use bun_options_types::context::ContextData; -// Re-exports (Zig: `pub const OfflineMode = @import("../options_types/OfflineMode.zig").OfflineMode;`) pub use bun_options_types::offline_mode::OfflineMode; // TODO: replace api.TransformOptions with Bunfig @@ -40,9 +39,8 @@ fn estring_to_owned(s: &E::EString, bump: &Bump) -> Box<[u8]> { Box::<[u8]>::from(s.string(bump).expect("OOM")) } -/// Port of `resolver/package_json.zig` `PackageJSON.parseMacrosJSON`. -/// -/// Re-ported here against the value-shaped `bun_ast::Expr` (the +/// Macro-remap parsing (the `PackageJSON.parseMacrosJSON` shape), +/// implemented here against the value-shaped `bun_ast::Expr` (the /// tree produced by the TOML/JSON parsers) and returning the /// `bun_options_types::context::MacroMap` shape so the result slots directly /// into `ctx.debug.macros` without crossing the `bun_ast::Expr` / @@ -136,7 +134,7 @@ fn parse_macros_json( #[inline] fn num_to_u32(n: f64) -> u32 { - // Note: Rust `as` saturates on overflow/NaN where Zig @intFromFloat is UB. + // Note: Rust `as` saturates on overflow/NaN. n as u32 } @@ -148,9 +146,9 @@ pub(crate) struct Parser<'a> { json: Expr, source: &'a bun_ast::Source, log: &'a mut bun_ast::Log, - // PORT NOTE: Zig held both `bunfig: *api.TransformOptions` (= `&ctx.args`) - // and `ctx: *Command.Context` simultaneously. Rust forbids the overlapping - // borrow, so `bunfig` writes route through `self.ctx.args` directly. + // Holding both a `TransformOptions` pointer (= `&ctx.args`) and `ctx` + // would be an overlapping borrow, so `bunfig` writes route through + // `self.ctx.args` directly. ctx: &'a mut ContextData, /// Arena backing `EString::string()` UTF-16→UTF-8 transcodes; lifetime /// matches the `Expr` tree (same bump used for the TOML/JSON parse). @@ -232,7 +230,6 @@ impl<'a> Parser<'a> { fn load_log_level(&mut self, expr: &Expr) -> Result<(), bun_core::Error> { self.expect_string(expr)?; - // PERF(port): Zig used strings.ExactSizeMatcher(8). let level = match expr.as_string(self.bump).unwrap_or(b"") { b"debug" => api::MessageLevel::Debug, b"error" => api::MessageLevel::Err, @@ -258,7 +255,6 @@ impl<'a> Parser<'a> { self.expect_string(item)?; if let ExprData::EString(s) = &item.data { if s.len() > 0 { - // PERF(port): was appendAssumeCapacity preloads.push(estring_to_owned(s, self.bump)); } } @@ -348,10 +344,10 @@ impl<'a> Parser<'a> { Ok(api::StringMap { keys, values }) } - // PORT NOTE: `comptime cmd: Command.Tag` demoted to a runtime arg — + // `cmd` is a runtime arg rather than a const generic — // `bun_options_types::command_tag::Tag` does not derive `ConstParamTy` (it - // already derives `enum_map::Enum`, which conflicts). The Zig original - // monomorphised over `cmd` purely to dead-code-eliminate untaken arms; the + // already derives `enum_map::Enum`, which conflicts). Monomorphising over + // `cmd` would only dead-code-eliminate untaken arms; the // runtime branches below are equivalent and the few hot fields are tiny. pub(crate) fn parse(&mut self, cmd: CommandTag) -> Result<(), bun_core::Error> { bun_analytics::features::bunfig.fetch_add(1, Ordering::Relaxed); @@ -788,7 +784,7 @@ impl<'a> Parser<'a> { if let Some(elide_lines) = run_expr.get(b"elide-lines") { if let Some(n) = elide_lines.as_number() { - // Note: Rust `as` saturates on overflow/NaN where Zig @intFromFloat is UB + // Note: Rust `as` saturates on overflow/NaN self.ctx.bundler_options.elide_lines = Some(n as usize); } else { self.add_error(elide_lines.loc, b"Expected number")?; @@ -937,7 +933,6 @@ impl<'a> Parser<'a> { self.add_error(key_expr.loc, b"Expected package name")?; } - // PERF(port): was putAssumeCapacity self.ctx.debug.package_bundle_map.insert( path, if b.value { @@ -1120,16 +1115,14 @@ impl Bunfig { let log: &mut bun_ast::Log = unsafe { &mut *log_ptr }; let log_count = log.errors + log.warnings; - // Zig passes `bun.default_allocator` here — no side `mi_heap`. The Rust - // port previously called `Arena::new()` (= `mi_heap_new` + + // This previously called `Arena::new()` (= `mi_heap_new` + // `mi_heap_destroy` on drop), which perf attributed ~1.6% of // `bun -e ''` startup to. Borrow the process default heap instead so - // TOML/JSON parse allocations route through plain `mi_malloc`, matching - // Zig. Parsed config lives for the process lifetime either way. + // TOML/JSON parse allocations route through plain `mi_malloc`. + // Parsed config lives for the process lifetime either way. let bump = Bump::borrowing_default(); let ext = source.path.name().ext; - // Zig: `if (strings.eqlComptime(source.path.name.ext[1..], "toml"))` let is_toml = ext.len() > 1 && &ext[1..] == b"toml"; let expr = if is_toml { @@ -1168,9 +1161,9 @@ impl Bunfig { } }; - // PORT NOTE: reshaped for borrowck — Zig stored both `&mut ctx` and - // `&mut ctx.args` simultaneously inside Parser. In Rust we route bunfig - // writes through `self.ctx.args` directly. `log` is derived from the + // Parser cannot hold both `&mut ctx` and `&mut ctx.args` + // simultaneously, so bunfig + // writes route through `self.ctx.args` directly. `log` is derived from the // copied raw pointer above so it does not overlap the `&mut ctx` borrow. // SAFETY: Parser never reaches `ctx.log` (only `self.log`), so no two // live `&mut` to the same `Log` coexist. @@ -1259,7 +1252,6 @@ impl<'a> Parser<'a> { } fn parse_install(&mut self, install_obj: &Expr) -> Result<(), bun_core::Error> { - // PORT NOTE: Zig held `*BunInstall` and `*Parser` simultaneously. // The helper methods (`expect*`, `add_error`, `parse_registry`) take // `&mut self`, which under Stacked Borrows would invalidate any // long-lived `&mut` derived from `self.ctx.install`. Move the box @@ -1540,7 +1532,7 @@ impl<'a> Parser<'a> { } } - // bunfig.zig:824-839 — remap PnpmMatcher errors so callers (and the + // Remap PnpmMatcher errors so callers (and the // crash handler's `"Invalid Bunfig"` match) see the canonical // bunfig error; only OOM passes through unchanged. let remap = |e: FromExprError| -> bun_core::Error { @@ -1680,5 +1672,3 @@ impl<'a> Parser<'a> { Ok(()) } } - -// ported from: src/runtime/cli/bunfig.zig diff --git a/src/bunfig/lib.rs b/src/bunfig/lib.rs index 081e628aabf..12e96c1be16 100644 --- a/src/bunfig/lib.rs +++ b/src/bunfig/lib.rs @@ -1,8 +1,8 @@ //! `bun_bunfig` — bunfig.toml parser and `Arguments::loadConfig` entrypoints. //! -//! that `bun_install::PackageManager::init` can call -//! `bun.cli.Arguments.loadConfig(_, cli.config, ctx, .InstallCommand)` -//! (PackageManager.zig:801) without a tier-6 dependency or fn-pointer hook. +//! Split out so that `bun_install::PackageManager::init` can call +//! `Arguments::loadConfig` +//! without a tier-6 dependency or fn-pointer hook. //! Every dependency of this crate was already a transitive dependency of //! `bun_install` (via `bun_transpiler` → `bun_bundler`), so no cycle is //! introduced; this only makes the existing edge direct. diff --git a/src/c-headers-for-zig.h b/src/c-headers-for-zig.h index d08b86b7051..29d0b1fb6c2 100644 --- a/src/c-headers-for-zig.h +++ b/src/c-headers-for-zig.h @@ -1,14 +1,10 @@ -// This file is run through translate-c and exposed to Zig code -// under the namespace bun.c (lowercase c). Prefer adding includes -// to this file instead of manually porting struct definitions -// into Zig code. By using automatic translation, differences +// Aggregated C includes for automatic translation into bindings. +// Prefer adding includes +// to this file instead of manually porting struct definitions. +// By using automatic translation, differences // between platforms and subtle mistakes can be avoided. // -// One way to locate a definition for a given symbol is to open -// Zig's `lib` directory and run ripgrep on it. For example, -// `sockaddr_dl` is in `libc/include/any-macos-any/net/if_dl.h` -// -// When Zig is translating this file, it will define these macros: +// When this file is translated, these macros are defined: // - WINDOWS // - DARWIN // - LINUX diff --git a/src/cares_sys/c_ares.rs b/src/cares_sys/c_ares.rs index 06d933ddff8..efdc8ddd315 100644 --- a/src/cares_sys/c_ares.rs +++ b/src/cares_sys/c_ares.rs @@ -32,7 +32,7 @@ bun_opaque::opaque_ffi! { pub struct struct_apattern; } -/// Mirror of `std.posix.AF` in Zig — only the address families c-ares +/// Only the address families c-ares /// actually uses. Kept local so this `*_sys` crate stays leaf-level /// (no dependency on `bun_sys`). Canonical: `bun_sys::posix::AF`. pub mod AF { @@ -50,7 +50,7 @@ pub mod AF { pub const INET6: c_int = libc::AF_INET6; } -/// Mirror of `std.posix.system.EAI` in Zig. The `libc` crate is missing +/// `EAI_*` getaddrinfo error codes. The `libc` crate is missing /// `EAI_ADDRFAMILY` and the glibc-only async-getaddrinfo extensions, so we /// hardcode those raw values from ``. #[cfg(not(windows))] @@ -118,9 +118,9 @@ pub enum NSClass { ns_c_max = 65536, } -// Zig: `enum(c_int) { ..., _ }` (non-exhaustive). Values are only ever +// Values are only ever // constructed in Rust and passed *to* C, so a plain repr(i32) enum is sound. -// TODO(port): if c-ares ever returns an NSType, switch to a transparent newtype. +// If c-ares ever returns an NSType, this must become a transparent newtype. #[repr(i32)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum NSType { @@ -305,17 +305,11 @@ pub struct struct_hostent { pub h_addr_list: *mut *mut c_char, // NUL-terminated array } -// ─── callback-wrapper reshaping note ────────────────────────────────────── -// Zig: each reply type defines -// pub fn Callback(comptime Type) type = fn(*Type, ?Error, i32, ?*Reply) void -// pub fn callbackWrapper(comptime lookup_name, comptime Type, comptime fn) ares_callback -// which monomorphizes a unique `extern "C"` thunk per (Type, fn) pair via the -// anonymous-struct trick. Rust cannot take a fn pointer as a const generic on -// stable, so the wrappers below are reshaped to a trait: the implementing +// ─── callback-wrapper note ───────────────────────────────────────────────── +// Rust cannot take a fn pointer as a const generic on +// stable, so the wrappers below are expressed as a trait: the implementing // type provides the callback as a trait method, and the `extern "C"` thunk is // monomorphized per `T: Trait`. -// TODO(port): a proc-macro may be cleaner if many callsites need distinct -// callbacks on the same `Type`. // ────────────────────────────────────────────────────────────────────────── pub trait HostentHandler: Sized { @@ -340,7 +334,7 @@ impl struct_hostent { this.on_hostent(None, timeouts, hostent); } - // Zig branched on `comptime lookup_name`; split into one thunk per name. + // One `extern "C"` thunk per lookup name. pub unsafe extern "C" fn callback_wrapper_cname( ctx: *mut c_void, status: c_int, @@ -452,8 +446,7 @@ impl Default for hostent_with_ttls { pub trait HostentWithTtlsHandler: Sized { /// `hostent_with_ttls::parse_a` or `parse_aaaa` — selects the c-ares reply - /// parser for [`hostent_with_ttls::callback_wrapper`]. Mirrors the Zig - /// `callbackWrapper(comptime lookup_name, ...)` parameterization. + /// parser for [`hostent_with_ttls::callback_wrapper`]. const PARSE: fn(&[u8]) -> Result, Error>; fn on_hostent_with_ttls( @@ -467,24 +460,6 @@ pub trait HostentWithTtlsHandler: Sized { impl hostent_with_ttls { // toJSResponse alias deleted — lives in bun_runtime::dns_jsc. - pub unsafe extern "C" fn host_callback_wrapper( - ctx: *mut c_void, - status: c_int, - timeouts: c_int, - hostent: Option>, - ) { - // TODO(port): Zig declared this as `ares_host_callback` (4th arg - // `?*hostent_with_ttls`) but that signature mismatches the C - // `ares_host_callback` (`?*struct_hostent`). Appears unused; verify. - // SAFETY: ctx was passed as *mut T to the ares call that registered this thunk. - let this = unsafe { bun_core::callback_ctx::(ctx) }; - if status != ARES_SUCCESS { - this.on_hostent_with_ttls(Error::get(status), timeouts, None); - return; - } - this.on_hostent_with_ttls(None, timeouts, hostent); - } - pub unsafe extern "C" fn callback_wrapper( ctx: *mut c_void, status: c_int, @@ -574,9 +549,8 @@ impl Drop for hostent_with_ttls { } } -// Per-record-type newtype aliases. Zig instantiated the resolve machinery over -// the same `struct_hostent` / `hostent_with_ttls` with a comptime `type_name` -// string; Rust callers (`dns.rs`) need distinct type names to monomorphise the +// Per-record-type newtype aliases. +// Callers (`dns.rs`) need distinct type names to monomorphise the // `CAresRecordType` cache-field constant per record. For now these are plain // aliases — the trait impls live downstream. pub type NsHostent = struct_hostent; @@ -675,17 +649,7 @@ impl AddrInfo { unsafe { core::ffi::CStr::from_ptr(self.name_) }.to_bytes() } - #[inline] - pub fn cnames(&self) -> &[AddrInfo_node] { - // TODO(port): Zig used `bun.span` on a [*c]AddrInfo_cname (sentinel- - // terminated linked list), returning `[]const AddrInfo_node` — note - // the type mismatch (cname vs node) in the original. This appears - // unused; preserving the empty-slice fast path only. - if self.cnames_.is_null() { - return &[]; - } - &[] - } + // Consumers walk `cnames_` / `node` pointer chains directly. pub unsafe extern "C" fn callback_wrapper( ctx: *mut c_void, @@ -741,7 +705,7 @@ bun_opaque::opaque_ffi! { const _: () = assert!(core::mem::size_of::() == 0); /// Implemented by the type that owns a `*mut Channel` and receives socket- -/// state callbacks. Zig: `Container.onDNSSocketState` + `this.channel = ch`. +/// state callbacks. /// /// R-2: methods take `&self`. The c-ares `sock_state_cb` re-enters the /// container while a `&self` borrow may already be live in `on_dns_poll`; @@ -753,9 +717,7 @@ pub trait ChannelContainer: Sized { /// Trait for `Channel::resolve`: ties a lookup-name string to its NSType and /// the `extern "C"` parse-thunk used as the ares_callback. -/// TODO(port): Zig dispatched via `@field(NSType, "ns_t_" ++ lookup_name)` and -/// `cares_type.callbackWrapper(lookup_name, Type, callback)`. This trait is the -/// Rust-side reshaping; the dns_jsc consumer impls it per (T, record-type). +/// The dns_jsc consumer impls it per (T, record-type). pub trait ResolveHandler: Sized { const LOOKUP_NAME: &'static [u8]; const NS_TYPE: NSType; @@ -841,7 +803,7 @@ impl Channel { unsafe { ares_destroy(this) }; } - /// See c-ares `ares_getaddrinfo` documentation (mirrored in the Zig source). + /// See c-ares `ares_getaddrinfo` documentation. pub fn get_addr_info( &mut self, host: &[u8], @@ -1258,7 +1220,7 @@ pub struct struct_ares_addr6ttl { } // SAFETY: `#[repr(C)]` POD — 16-byte byte-array union + `c_int`. All-zero is a -// valid bit pattern (matches Zig `std.mem.zeroes`) (S021). +// valid bit pattern (S021). unsafe impl bun_core::ffi::Zeroable for struct_ares_addr6ttl {} impl Default for struct_ares_addr6ttl { #[inline] @@ -1655,7 +1617,6 @@ impl struct_any_reply { impl Drop for struct_any_reply { fn drop(&mut self) { - // Zig: `inline for (@typeInfo(..).fields)` — written out by hand. // a_reply / aaaa_reply are Box; their Drop frees the // inner hostent via ares_free_hostent. // SAFETY: each field is either null or a c-ares allocation matching its free fn. @@ -1972,10 +1933,8 @@ impl Error { EAI::MEMORY => Some(Error::ENOMEM), EAI::SERVICE => Some(Error::ESERVICE), EAI::SYSTEM => Some(Error::ESERVFAIL), - _ => { - // TODO(port): bun.todo(@src(), Error.ENOTIMP) - Some(Error::ENOTIMP) - } + // Any EAI code not mapped above is reported as "not implemented". + _ => Some(Error::ENOTIMP), } } } @@ -2221,9 +2180,10 @@ pub fn get_sockaddr(addr: &[u8], port: u16, sa: &mut sockaddr) -> c_int { -1 } -// Zig: `struct_in_addr = std.posix.sockaddr.in` — note this aliases the full -// sockaddr_in (not the 4-byte in_addr). Preserved for ABI parity in `Options.servers`. -// TODO(port): verify against c-ares header; this looks like a Zig-side misnomer. -type in_addr = sockaddr_in; - -// ported from: src/cares_sys/c_ares.zig +/// The C `struct in_addr` (4-byte IPv4 address), as c-ares' `ares_options.servers` +/// and the `ares_addr_node`/`ares_addr_port_node` unions declare it. +#[repr(C)] +#[derive(Copy, Clone)] +pub struct in_addr { + pub s_addr: u32, +} diff --git a/src/clap/args.rs b/src/clap/args.rs index c35b2147df1..d3d8b8a04e4 100644 --- a/src/clap/args.rs +++ b/src/clap/args.rs @@ -1,7 +1,7 @@ use core::convert::Infallible; use std::borrow::Cow; -/// Duck-typed arg-iterator surface (Zig used `anytype`). Implemented by +/// Arg-iterator surface. Implemented by /// `OsIterator` and `SliceIterator`; `ShellIterator` does not fit (fallible, /// owned results) and is used standalone. pub trait ArgIter<'a> { @@ -20,7 +20,7 @@ impl ExampleArgIterator { } /// Pop the first element of `remain`, advancing the slice. Shared body for -/// `SliceIterator::next` / `OsIterator::next` (the .zig spec duplicates them). +/// `SliceIterator::next` / `OsIterator::next`. #[inline] fn pop_first<'a>(remain: &mut &'a [&'a [u8]]) -> Option<&'a [u8]> { if remain.is_empty() { @@ -61,8 +61,7 @@ impl<'a> ArgIter<'a> for SliceIterator<'a> { /// An argument iterator which wraps the ArgIterator in ::std. /// On windows, this iterator allocates. pub struct OsIterator { - // PORT NOTE: the Zig `arena: bun.ArenaAllocator` field was dropped — non-AST crate, - // and `remain` borrows the process-global argv so nothing is allocated per-call. + // `remain` borrows the process-global argv, so nothing is allocated per-call. pub remain: &'static [&'static [u8]], /// The executable path (this is the first argument passed to the program) @@ -81,8 +80,6 @@ impl OsIterator { res } - // PORT NOTE: `deinit` dropped — it only freed the arena, which no longer exists. - pub fn next(&mut self) -> Option<&'static [u8]> { pop_first(&mut self.remain) } @@ -99,10 +96,8 @@ impl ArgIter<'static> for OsIterator { } } -/// Process argv as a `&'static` slice of `&'static [u8]`. -/// -/// Zig: `bun.argv: [][:0]const u8` — the process-global view that includes -/// `BUN_OPTIONS` injection. +/// Process argv as a `&'static` slice of `&'static [u8]` — the process-global +/// view that includes `BUN_OPTIONS` injection. /// /// This used to project `&ZStr → &[u8]` through a `OnceLock>`, /// which (a) allocated a Vec on the `--version` startup path and (b) emitted a @@ -127,8 +122,8 @@ pub enum ShellIteratorError { DanglingEscape, #[error("QuoteNotClosed")] QuoteNotClosed, - // PORT NOTE: Zig union included `mem.Allocator.Error` (OutOfMemory). Vec aborts on OOM - // under the global mimalloc allocator, so that variant is dropped. + // There is no OutOfMemory variant: Vec aborts + // on OOM under the global mimalloc allocator. } bun_core::named_error_set!(ShellIteratorError); @@ -136,7 +131,6 @@ bun_core::named_error_set!(ShellIteratorError); /// An argument iterator that takes a string and parses it into arguments, simulating /// how shells split arguments. pub struct ShellIterator<'a> { - // PORT NOTE: the Zig `arena: bun.ArenaAllocator` field was dropped (non-AST crate). // Allocated results are returned as `Cow::Owned` instead of arena-backed slices. pub str: &'a [u8], } @@ -157,8 +151,6 @@ impl<'a> ShellIterator<'a> { ShellIterator { str } } - // PORT NOTE: `deinit` dropped — it only freed the arena, which no longer exists. - pub fn next(&mut self) -> Result>, ShellIteratorError> { // Whenever possible, this iterator will return slices into `str` instead of // allocating. Sometimes this is not possible, for example, escaped characters @@ -167,8 +159,7 @@ impl<'a> ShellIterator<'a> { let mut start: usize = 0; let mut state = State::SkipWhitespace; - // PORT NOTE: reshaped for borrowck — copy the slice ref so we can reassign - // `self.str` before returning (Zig used `defer iter.str = ...`). + // Copy the slice ref so we can reassign `self.str` before returning. let s: &'a [u8] = self.str; for (i, &c) in s.iter().enumerate() { @@ -284,8 +275,8 @@ impl<'a> ShellIterator<'a> { // The state we end up when after the escape character (`\`). All these // states do is transition back into the previous state. // TODO: Are there any escape sequences that does transform the second - // character into something else? For example, in Zig, `\n` is - // transformed into the line feed ascii character. + // character into something else (e.g. `\n` into the line feed + // ascii character)? State::NoQuoteEscape => { state = State::NoQuote; } @@ -320,7 +311,6 @@ impl<'a> ShellIterator<'a> { // the rest we have to the list and return that. if !list.is_empty() { list.extend_from_slice(res); - // PERF(port): was arena-backed `toOwnedSlice()`. return Ok(Some(Cow::Owned(list))); } Ok(Some(Cow::Borrowed(res))) @@ -342,17 +332,23 @@ mod tests { } } - fn test_shell_iterator_ok(str: &[u8], allocations: usize, expect: &[&[u8]]) { - // TODO(port): Zig used `testing.FailingAllocator` to cap/count allocations. - // No allocator injection in the Rust port; `allocations` is unused. - let _ = allocations; + fn test_shell_iterator_ok(str: &[u8], expected_owned_results: usize, expect: &[&[u8]]) { + // There is no allocator injection to count raw allocations with, + // but every allocating result surfaces as a + // `Cow::Owned`, so counting owned results checks the same property: the + // borrowed (zero-copy) fast path is taken whenever possible. + let mut owned_results: usize = 0; let mut it = ShellIterator::init(str); for e in expect { match it.next() { Ok(actual) => { assert!(actual.is_some()); - assert_eq!(*e, &*actual.unwrap()); + let actual = actual.unwrap(); + if matches!(actual, Cow::Owned(_)) { + owned_results += 1; + } + assert_eq!(*e, &*actual); } Err(err) => panic!("expected {:?}, got error {:?}", e, err), } @@ -361,7 +357,7 @@ mod tests { match it.next() { Ok(actual) => { assert!(actual.is_none()); - // TODO(port): assert_eq!(allocations, allocator.allocations); + assert_eq!(expected_owned_results, owned_results); } Err(err) => panic!("expected end of iterator, got error {:?}", err), } @@ -433,5 +429,3 @@ mod tests { test_shell_iterator_err(b"a\\", ShellIteratorError::DanglingEscape); } } - -// ported from: src/clap/args.zig diff --git a/src/clap/comptime.rs b/src/clap/comptime.rs index 66f8257ff0b..345ddad2219 100644 --- a/src/clap/comptime.rs +++ b/src/clap/comptime.rs @@ -7,21 +7,20 @@ use crate::streaming::{self, StreamingClap}; use crate::{Names, Param, ParseOptions, Values}; // ───────────────────────────────────────────────────────────────────────────── -// Compile-time conversion (Zig parity) +// Compile-time conversion // -// Zig's `ComptimeClap(Id, params)` is a comptime type-generator: it iterates -// `params` *at comptime* to (a) re-index every param's `id` to its slot within -// its category (flag / single / multi) and (b) emit a struct with fixed-size -// array fields. `findParam` is `inline for`, so every `args.flag("--foo")` -// compiles to a constant index — zero runtime cost. +// The param-table conversion can run entirely at compile time: (a) re-index +// every param's `id` to its slot within +// its category (flag / single / multi) and (b) emit fixed-size +// array fields, so every `args.flag("--foo")` +// lookup folds to a constant index — zero runtime cost. // -// An earlier draft of this port did all of this at runtime: `convert_params` heap-allocated +// An earlier draft did all of this at runtime: `convert_params` heap-allocated // a `Vec>` on every CLI start, and `find_param` linear-scanned it // for every `flag()`/`option()` lookup (~190 lookups × ~100 params on the -// `bun run` path). perf put this at ~0.25 % of `bun --version` cycles vs ~0 % -// in Zig. +// `bun run` path). perf put this at ~0.25 % of `bun --version` cycles. // -// This module restores the comptime semantics on stable Rust: +// This module does the conversion at compile time on stable Rust: // // * `count_flags` / `count_single` / `count_multi` / `convert_params_array` // / `find_param_index` are all `const fn`, so a param table declared with @@ -29,8 +28,7 @@ use crate::{Names, Param, ParseOptions, Values}; // name lookup can be folded to a constant via `const { find_param_index(..) }`. // // * `comptime_table!` (in `lib.rs`) packages the const-fn output as a -// `&'static ConvertedTable` — the Rust analogue of the Zig comptime -// `converted_params` const baked into the generated type. +// `&'static ConvertedTable` baked into rodata. // // * Every per-subcommand table in `runtime/cli/Arguments.rs` is now a // `pub static *_TABLE: &ConvertedTable = comptime_table!(*_PARAMS)`, and @@ -48,7 +46,7 @@ const fn is_named(p: &Param) -> bool { p.names.long.is_some() || p.names.short.is_some() } -/// Count flag params (named, `takes_value == None`). Zig: comptime loop arm. +/// Count flag params (named, `takes_value == None`). pub const fn count_flags(params: &[Param]) -> usize { let mut n = 0; let mut i = 0; @@ -89,8 +87,8 @@ pub const fn count_multi(params: &[Param]) -> usize { n } -/// Compile-time equivalent of the Zig comptime conversion loop (comptime.zig -/// lines 6–32): re-indexes each param's `id` to its slot within its category. +/// Compile-time conversion loop: +/// re-indexes each param's `id` to its slot within its category. /// `N` must equal `params.len()` (asserted at const-eval). pub const fn convert_params_array(params: &[Param]) -> [Param; N] { const DUMMY: Param = Param { @@ -137,16 +135,14 @@ pub const fn convert_params_array(params: &[Param]) -> [ out } -/// Compile-time name → converted-param index. This is the Rust analogue of -/// Zig's `inline for` `findParam` and is intended to be called inside a +/// Compile-time name → converted-param index. Intended to be called inside a /// `const { }` block so the loop folds to a literal: /// /// ```ignore /// const IDX: usize = find_param_index(TABLE.converted, b"--help"); /// ``` /// -/// Panics (at const-eval, i.e. a build error) if `name` is not in `converted` -/// — matching Zig's `@compileError("no param '…'")`. +/// Panics (at const-eval, i.e. a build error) if `name` is not in `converted`. pub(crate) const fn find_param_index(converted: &[Param], name: &[u8]) -> usize { if name.len() > 2 && name[0] == b'-' && name[1] == b'-' { let (_, key) = name.split_at(2); @@ -300,8 +296,8 @@ impl ConvertedTable { /// Build a table entirely at compile time. All four arguments come from /// the `const fn`s above via [`comptime_table!`](crate::comptime_table), /// so the converted param array, category counts, sorted long-name hash - /// index, and short-name direct index all land in rodata — full Zig - /// `ComptimeClap` parity with zero runtime work. + /// index, and short-name direct index all land in rodata — + /// zero runtime work. pub const fn from_const( converted: &'static [Param], n_flags: usize, @@ -509,17 +505,15 @@ pub fn convert_params(params: &[Param]) -> (Vec>, usize, us /// Deprecated: Use `parse_ex` instead pub struct ComptimeClap { - // Field order matches comptime.zig. - // Inner `&'static [u8]` slices borrow argv (process-lifetime); never freed in Zig `deinit`. + // Inner `&'static [u8]` slices borrow argv (process-lifetime). pub single_options: Box<[Option<&'static [u8]>]>, pub multi_options: Box<[Box<[&'static [u8]]>]>, pub flags: Box<[bool]>, pub pos: Box<[&'static [u8]]>, pub passthrough_positionals: Box<[&'static [u8]]>, - // `mem.Allocator param` field deleted — global mimalloc (see PORTING.md §Allocators). - // Zig captures `converted_params` as a comptime const on the returned type. Rust - // carries it as a `&'static` table — either rodata (`comptime_table!`) or + // The converted params are + // carried as a `&'static` table — either rodata (`comptime_table!`) or // interned-once via the ptr-keyed registry — so `flag`/`option` resolve via // hashed lookup instead of an O(n) scan, and no per-parse `Vec` is allocated. table: &'static ConvertedTable, @@ -560,7 +554,6 @@ impl ComptimeClap { ) -> Result where I: ArgIter<'static>, - // TODO(port): narrow error set { // `opt.allocator` dropped — global mimalloc. let mut multis: Vec> = (0..table.n_multi).map(|_| Vec::new()).collect(); @@ -572,8 +565,6 @@ impl ComptimeClap { vec![None; table.n_single].into_boxed_slice(); let mut flags: Box<[bool]> = vec![false; table.n_flags].into_boxed_slice(); - // Zig: `StreamingClap(usize, @typeInfo(@TypeOf(iter)).pointer.child)` — the second - // type arg is the pointee of `iter`; in Rust that is just `I`. let mut stream = StreamingClap:: { params: table.converted, iter, @@ -588,9 +579,6 @@ impl ComptimeClap { pos.push(arg.value.unwrap()); if opt.stop_after_positional_at > 0 && pos.len() >= opt.stop_after_positional_at { let mut remaining_ = stream.iter.remain(); - // PORT NOTE: Zig called `bun.span` (NUL-scan) on `[:0]const u8` argv - // entries. Our `ArgIter` already yields sized `&[u8]`, so `span` is a - // no-op and is dropped. let first: &[u8] = if !remaining_.is_empty() { remaining_[0] } else { @@ -626,7 +614,6 @@ impl ComptimeClap { Ok(Self { single_options, - // PORT NOTE: Zig left these `undefined` and filled them post-loop. multi_options: multis.into_iter().map(Vec::into_boxed_slice).collect(), flags, pos: pos.into_boxed_slice(), @@ -636,10 +623,7 @@ impl ComptimeClap { }) } - // Zig `deinit` only freed `multi_options[*]` and `pos` (not `passthrough_positionals` — - // likely a leak in the deprecated Zig). All are owned here, so `Drop` handles it; - // body deleted per PORTING.md §Idiom map (`pub fn deinit` → `impl Drop`, empty body - // when it only frees owned fields). + // All fields are owned, so `Drop` handles cleanup; no explicit deinit needed. #[inline] pub fn flag(&self, name: &[u8]) -> bool { @@ -685,7 +669,7 @@ impl ComptimeClap { } /// Direct slot accessors — pair with [`find_param_index`] inside - /// `const { }` for true Zig-parity zero-cost lookup at the call site. + /// `const { }` for zero-cost lookup at the call site. #[inline] pub fn flag_at(&self, converted_idx: usize) -> bool { self.flags[self.table.converted[converted_idx].id] @@ -707,8 +691,7 @@ impl ComptimeClap { &self.passthrough_positionals } - /// Zig `hasFlag` is a comptime-only predicate over the captured table. - /// `const fn` here so `const { has_flag(PARAMS, b"--foo") }` folds. + /// `const fn` so `const { has_flag(PARAMS, b"--foo") }` folds. pub const fn has_flag(params: &[Param], name: &[u8]) -> bool { let mut i = 0; while i < params.len() { @@ -745,5 +728,3 @@ impl ComptimeClap { self.table.find(name) } } - -// ported from: src/clap/comptime.zig diff --git a/src/clap/lib.rs b/src/clap/lib.rs index 8c4d38d22d4..768234df2d6 100644 --- a/src/clap/lib.rs +++ b/src/clap/lib.rs @@ -19,8 +19,7 @@ pub use streaming::StreamingClap; pub use bun_clap_macros::{__parse_param_impl, __parse_params_impl}; /// Parse a single param spec string (e.g. `"-h, --help Display this help"`) -/// into a const `Param` literal at compile time. This is the Rust -/// equivalent of Zig's comptime `clap.parseParam(...) catch unreachable`. +/// into a const `Param` literal at compile time. /// /// The argument **must** be a string literal; parse errors surface as compile /// errors at the call site. @@ -31,8 +30,7 @@ macro_rules! parse_param { }; } -/// Alias for [`parse_param!`] matching the Zig call-site spelling -/// (`clap.parseParam` → `clap::param!`). +/// Alias for [`parse_param!`]. #[macro_export] macro_rules! param { ($lit:literal $(,)?) => { @@ -40,8 +38,8 @@ macro_rules! param { }; } -/// Const-time `Param` slice concatenation — the Rust analogue of Zig's -/// comptime `a ++ b ++ c` over param tables. Produces a `&'static [Param]` +/// Const-time `Param` slice concatenation +/// over param tables. Produces a `&'static [Param]` /// baked into rodata; no `LazyLock`, no heap, no init closure in `.text`. /// /// Every `$part` must be a `const`-evaluable `&[Param]` (a `const` item or @@ -57,8 +55,7 @@ macro_rules! concat_params { } /// Build a `&'static ConvertedTable` from a const-evaluable -/// `&[Param]` at compile time — the Rust analogue of Zig's -/// `ComptimeClap(Id, params)` type-generator. The converted `[Param; N]` +/// `&[Param]` at compile time. The converted `[Param; N]` /// array, the three category counts, the short-name index, *and* the sorted /// long-name hash index all land in rodata, so [`parse_with_table`] does no /// heap allocation, no sorting, no locking, and `args.flag(b"--foo")` @@ -206,7 +203,7 @@ pub struct Names { } impl Names { - /// `.{ .short = c }` + /// A name with only a short flag (`-c`). #[inline] pub const fn short(c: u8) -> Self { Self { @@ -216,7 +213,7 @@ impl Names { } } - /// `.{ .long = name }` + /// A name with only a long flag (`--name`). #[inline] pub const fn long(name: &'static [u8]) -> Self { Self { @@ -245,7 +242,6 @@ impl Names { /// long, or any long alias. Shared predicate for `has_flag`/`find_param`. pub fn matches(&self, name: &[u8]) -> bool { if let Some(s) = self.short { - // Zig: mem.eql(u8, name, "-" ++ [_]u8{s}) if name.len() == 2 && name[0] == b'-' && name[1] == s { return true; } @@ -298,8 +294,6 @@ pub struct Param { impl Default for Param { fn default() -> Self { - // SAFETY note: Zig used `std.mem.zeroes(Id)` / `std.mem.zeroes(Names)`. - // We require `Id: Default` instead — same effect for the `Help` payload. Self { id: Id::default(), names: Names::default(), @@ -328,10 +322,10 @@ fn expect_param(expect: Param, actual: Param) { } /// Optional diagnostics used for reporting useful errors -// PORT NOTE: Zig `Diagnostic` borrows `arg`/`name.long` from the arg iterator. Rust -// can't tie that lifetime through `&mut Diagnostic` without invariance headaches, and -// this is an error-path-only struct, so it owns its bytes instead. The `name: Names` -// field is flattened to `short`/`long` because `Names.long` is `&'static`. +// Borrowing `arg`/`name.long` from the arg iterator would tie +// that lifetime through `&mut Diagnostic` with invariance headaches, and this is +// an error-path-only struct, so it owns its bytes instead. The `name: Names` field +// is flattened to `short`/`long` because `Names.long` is `&'static`. #[derive(Default)] pub struct Diagnostic { pub arg: Vec, @@ -349,7 +343,6 @@ impl Diagnostic { #[cold] #[inline(never)] pub fn report(&self, _stream: W, err: bun_core::Error) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let mut name_buf = [0u8; 1024]; let name: &[u8] = if let Some(s) = self.short { name_buf[0] = b'-'; @@ -394,8 +387,8 @@ impl Diagnostic { #[derive(Clone, Copy)] pub struct Help { /// The description text exactly as written in the param spec — may still - /// contain `` colour markup. Used by [`help`]/[`help_ex`], which (like - /// Zig's `clap.help`) emit it verbatim, and as the source for the ANSI form + /// contain `` colour markup. Used by [`help`]/[`help_ex`], which + /// emit it verbatim, and as the source for the ANSI form /// built lazily by [`pretty_help_desc`] on the `bun --help` colour path. pub msg: &'static [u8], /// `msg` with `` colour markup stripped — the non-TTY / piped help form. @@ -419,8 +412,6 @@ impl Default for Help { /// Options that can be set to customize the behavior of parsing. #[derive(Default)] pub struct ParseOptions<'a> { - // PORT NOTE: `mem.Allocator param` field deleted — non-AST crate uses - // the global mimalloc. pub diagnostic: Option<&'a mut Diagnostic>, pub stop_after_positional_at: usize, } @@ -444,9 +435,8 @@ fn get_help_simple(param: &Param) -> &'static [u8] { /// output is actually requested. That ``→ANSI rewrite only ever runs on /// `bun --help` / `bun run --help`; `--print` and ordinary runs never reach this /// path, so they pay neither the per-invocation reparse nor the extra rodata a -/// baked-in `msg_ansi` array would cost on every flag and subcommand. (Zig did -/// the rewrite at `comptime` via `Output.prettyFmt` inside -/// `clap.simpleHelpBunTopLevel`; the colour case is rare enough that doing it +/// baked-in `msg_ansi` array would cost on every flag and subcommand. (The +/// colour case is rare enough that doing the rewrite /// lazily at runtime is the better trade for binary size.) #[cold] #[inline(never)] @@ -465,13 +455,7 @@ fn get_value_simple(param: &Param) -> &'static [u8] { param.id.value } -// TODO(port): `comptime params: []const Param(Id)` as a type parameter has no -// stable-Rust equivalent. `params` is carried at runtime; a proc-macro could -// restore the per-table monomorphization. pub struct Args { - // PORT NOTE: Zig stored `arena: bun.ArenaAllocator` here and `deinit` freed it. - // Non-AST crate → arena removed; `ComptimeClap` owns its allocations. - // PERF(port): was arena bulk-free — profile if hot. pub clap: ComptimeClap, pub exe_arg: Option<&'static [u8]>, } @@ -514,13 +498,9 @@ pub fn parse( params: &'static [Param], opt: ParseOptions<'_>, ) -> Result, bun_core::Error> { - // TODO(port): narrow error set let mut iter = args::OsIterator::init(); let exe_arg = iter.exe_arg; - // PORT NOTE: Zig reused `iter.arena` as the allocator for `parseEx` and - // moved it into `res.arena`. Arena removed in port; ownership flows through - // `ComptimeClap` directly. let clap = parse_ex::( params, &mut iter, @@ -566,7 +546,6 @@ pub fn parse_ex( where I: args::ArgIter<'static>, { - // TODO(port): narrow error set ComptimeClap::::parse(params, iter, opt) } @@ -590,7 +569,6 @@ where Id: Copy, E: Into, { - // TODO(port): narrow error set let max_spacing: usize = 'blk: { let mut res: usize = 0; for param in params { @@ -658,7 +636,7 @@ where } /// Shared by `print_param` and `usage_full`: emit the ` ` / ` ?` / -/// ` ...` suffix for a param's `takes_value`. Mirrors clap.zig:459/672. +/// ` ...` suffix for a param's `takes_value`. #[cold] #[inline(never)] fn write_takes_value_suffix( @@ -706,8 +684,6 @@ where pub fn help_ex( stream: &mut W, params: &[Param], - // TODO(port): LIFETIMES.tsv classifies these as `fn(Param) -> &'static str`; - // using `&'static [u8]` to stay consistent with the bytes-not-str rule. help_text: fn(&Param) -> &'static [u8], value_text: fn(&Param) -> &'static [u8], ) -> Result<(), bun_core::Error> @@ -743,7 +719,6 @@ where #[cold] #[inline(never)] pub fn simple_print_param(param: &Param) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set bun_core::pretty!("\n"); if let Some(s) = param.names.short { if param.takes_value != Values::None && param.names.long.is_none() { @@ -818,10 +793,7 @@ pub fn simple_help(params: &[Param]) { let spaces_after = vec![b' '; num_spaces_after]; simple_print_param(param).expect("unreachable"); - // Zig's `Output.pretty(" {s} {s}", …)` (clap.zig:567) only runs prettyFmt - // over the comptime template, so `` markers inside `desc_text` leak - // through verbatim there. That is observably wrong (`bun run --help` prints - // literal `$cwd`); `pretty_help_desc` resolves the `` markup + // `pretty_help_desc` resolves the `` markup // (ANSI on a colour TTY, stripped otherwise) so `--help` output is // tag-clean regardless of which helper a command uses. let desc = pretty_help_desc(param); @@ -836,22 +808,18 @@ pub fn simple_help(params: &[Param]) { #[cold] #[inline(never)] pub fn simple_help_bun_top_level(params: &[Param]) { - // TODO(port): Zig evaluates `computed_max_spacing` at `comptime` and emits - // `@compileError` on overflow, plus uses `inline for` + comptime string - // concat (`space_buf[..n] ++ desc_text`). None of that is const-evaluable - // in Rust over a slice param. Runtime equivalent below; could macro-gen. + // `computed_max_spacing` is not const-evaluable over a + // slice param, so the overflow check is a runtime debug_assert below. const MAX_SPACING: usize = 30; const SPACE_BUF: &[u8; MAX_SPACING] = b" "; let computed_max_spacing: usize = compute_max_help_spacing(params); - // Zig: @compileError; here a debug-time assert. debug_assert!( computed_max_spacing <= MAX_SPACING, "a parameter is too long to be nicely printed in `bun --help`" ); - // PERF(port): was `inline for` + comptime string concat — profile if hot. for param in params { if !(param.names.short.is_none() && param.names.long.is_none()) { let desc_text = get_help_simple(param); @@ -861,9 +829,7 @@ pub fn simple_help_bun_top_level(params: &[Param]) { let total_len = param_display_width(param); let num_spaces_after = MAX_SPACING - total_len; - // Zig: Output.pretty(space_buf[0..n] ++ desc_text, .{}) — the concat - // is the *format string*, so `` markers inside `desc_text` are - // rewritten at `comptime`. Mirror that via `pretty_help_desc`, which + // `pretty_help_desc` // resolves the markup (ANSI on a colour TTY, stripped otherwise). let desc = pretty_help_desc(param); bun_core::pretty!( @@ -901,7 +867,6 @@ where Id: Copy, E: Into, { - // TODO(port): narrow error set let mut cos = CountingWriter::wrap(stream); for param in params { let Some(name) = param.names.short else { @@ -912,8 +877,7 @@ where } if cos.count == 0 { - // PORT NOTE: Zig wrote "[-" to `stream` (not `cs`), bypassing the - // counter. Preserving that quirk by writing to the inner writer. + // "[-" goes to the inner writer (not `cs`), bypassing the counter. write!(cos.inner(), "[-")?; } cos.write_char(name as char)?; @@ -934,8 +898,7 @@ where b"--" }; - // Zig had a workaround `@as([*]const u8, @ptrCast(s))[0..1]` for taking - // a 1-byte slice of the short char. Rust expresses this as a 1-elem array. + // A 1-elem array gives a 1-byte slice of the short char. let short_buf; let name: &[u8] = if let Some(s) = param.names.short { short_buf = [s]; @@ -1190,7 +1153,7 @@ mod tests { assert_eq!(MACRO_PARAMS[1].takes_value, Values::OneOptional); assert_eq!(MACRO_PARAMS[1].id.value, b"STR"); - // Aliases — proc-macro restores the comptime alias array the runtime parser drops. + // Aliases — the proc-macro restores the static alias array the runtime parser drops. assert_eq!( MACRO_PARAMS[2].names.long, Some(b"test-name-pattern" as &[u8]) @@ -1231,5 +1194,3 @@ mod tests { assert_eq!(CT_TABLE.converted[CT_CONFIG_IDX].takes_value, Values::One); } } - -// ported from: src/clap/clap.zig diff --git a/src/clap/streaming.rs b/src/clap/streaming.rs index 320858f1c5e..28c69bd3b68 100644 --- a/src/clap/streaming.rs +++ b/src/clap/streaming.rs @@ -65,8 +65,8 @@ pub struct StreamingClap<'p, 'a, Id, ArgIterator> { pub diagnostic: Option<&'p mut clap::Diagnostic>, } -// PORT NOTE: ArgIterator was a comptime duck-typed param in Zig; expressed here as -// the `args::ArgIter<'a>` trait so `next()`/`remain()` resolve. +// ArgIterator is the +// `args::ArgIter<'a>` trait so `next()`/`remain()` resolve. impl<'p, 'a, Id, ArgIterator> StreamingClap<'p, 'a, Id, ArgIterator> where ArgIterator: ArgIter<'a>, @@ -110,7 +110,7 @@ where None }; - // PORT NOTE: reshaped for borrowck — copy slice ref so &mut self is free inside loop. + // Copy the slice ref so `&mut self` is free inside the loop. let params = self.params; for param in params { if !param.names.matches_long(name) { @@ -214,7 +214,7 @@ where let index = state.index; let next_index = index + 1; - // PORT NOTE: reshaped for borrowck — copy slice ref so &mut self is free inside loop. + // Copy the slice ref so `&mut self` is free inside the loop. let params = self.params; for param in params { let Some(short) = param.names.short else { @@ -224,9 +224,10 @@ where continue; } - // Before we return, we have to set the new state of the clap - // PORT NOTE: Zig `defer` hoisted — every path below returns, and nothing - // between here and those returns reads `self.state`. + // Before we return, we have to set the new state of the clap. + // (Hoisting this above the returns is fine — every path + // below returns, and nothing between here and those returns reads + // `self.state`.) if arg.len() <= next_index || param.takes_value != clap::Values::None { self.state = State::Normal; } else { @@ -331,8 +332,8 @@ where fn err(&mut self, arg: &[u8], short: Option, long: Option<&[u8]>, e: ArgError) -> ArgError { if let Some(d) = self.diagnostic.as_deref_mut() { - // PORT NOTE: Zig assigned borrowed `arg`/`name` slices; Rust `Diagnostic` - // owns its bytes (error path only) — see lib.rs. + // `Diagnostic` owns + // its bytes (error path only) — see lib.rs. d.arg = arg.to_vec(); d.short = short; d.long = long.map(|l| l.to_vec()); @@ -398,10 +399,33 @@ mod tests { Ok(Some(_)) => {} Ok(None) => break, Err(_err) => { - // TODO(port): io.fixedBufferStream + diag.report — `Diagnostic::report` - // currently routes through `bun_core::Output` (stderr) and ignores its - // writer arg, so we cannot capture output to compare against `expected`. - let _ = expected; + // Bun's `Diagnostic::report` deliberately ignores its writer arg + // and routes through `bun_core::Output` (stderr), + // so the rendered message can't be captured here. + // Instead, rebuild the flag name the + // same way `report` does from the diagnostic fields and assert + // the expected message names it in quotes. + let mut name_buf = [0u8; 1024]; + let captured: &[u8] = if let Some(s) = diag.short { + name_buf[0] = b'-'; + name_buf[1] = s; + &name_buf[..2] + } else if let Some(l) = diag.long.as_deref() { + name_buf[0] = b'-'; + name_buf[1] = b'-'; + name_buf[2..2 + l.len()].copy_from_slice(l); + &name_buf[..2 + l.len()] + } else { + &diag.arg + }; + assert!( + expected.windows(captured.len() + 2).any(|w| w[0] == b'\'' + && w[w.len() - 1] == b'\'' + && &w[1..w.len() - 1] == captured), + "expected message {:?} does not name captured arg {:?}", + bstr::BStr::new(expected), + bstr::BStr::new(captured), + ); return; } } @@ -790,5 +814,3 @@ mod tests { ); } } - -// ported from: src/clap/streaming.zig diff --git a/src/clap_macros/lib.rs b/src/clap_macros/lib.rs index a63db6707fe..0969a83031f 100644 --- a/src/clap_macros/lib.rs +++ b/src/clap_macros/lib.rs @@ -1,8 +1,8 @@ //! Proc-macro backend for `bun_clap::parse_param!` / `bun_clap::parse_params!`. //! -//! This is a 1:1 port of the comptime `parseParam` from `src/clap/clap.zig`, lifted into +//! Param-spec parsing implemented as //! a proc-macro so the resulting `Param` values are fully const and usable in -//! `static` tables (the Zig original ran at comptime via `@setEvalBranchQuota`). +//! `static` tables. //! //! Do **not** call these proc-macros directly — they take a leading crate-path argument //! (`$crate`) injected by the `macro_rules!` wrappers in `bun_clap`. Use @@ -76,10 +76,10 @@ impl ParseParamError { } // ───────────────────────────────────────────────────────────────────────────── -// Parser — direct port of clap.zig `parseParam` / `parseParamRest` / `parseLongNames` +// Parser — `parse_param` / `parse_param_rest` / `parse_long_names` // ───────────────────────────────────────────────────────────────────────────── -/// `std.mem.tokenizeAny` with a `.rest()` that skips leading delimiters. +/// Delimiter tokenizer with a `.rest()` accessor that skips leading delimiters. struct TokenizeAny<'a> { buf: &'a [u8], index: usize, @@ -272,8 +272,6 @@ fn byte_str_b(s: &[u8]) -> LitByteStr { /// in rodata. The ANSI form is *not* baked in — it is rare (only `bun --help` on /// a colour TTY) and would otherwise roughly triple the help-string rodata, so /// `bun_clap::pretty_help_desc` derives it from `Help::msg` on demand instead. -/// (Zig did the equivalent rewrite at `comptime` via `Output.prettyFmt` inside -/// `clap.simpleHelpBunTopLevel`.) fn pretty_rewrite(fmt: &[u8], is_enabled: bool) -> Vec { use bun_output_tags::{RESET, color_for_bytes}; let mut out: Vec = Vec::with_capacity(fmt.len() * 2); @@ -322,9 +320,8 @@ fn pretty_rewrite(fmt: &[u8], is_enabled: bool) -> Vec { is_reset = true; "" } else { - // Unknown tag: Zig's comptime `prettyFmt` would `@compileError` - // here, but `pretty_fmt_runtime` (the path this replaces) drops - // it silently and so does Zig's actual `clap.simpleHelp`. Match + // Unknown tag: `pretty_fmt_runtime` (the path this replaces) + // drops it silently. Match // the lenient runtime behaviour — a compile error would be // stricter than what shipped, and param specs don't carry // unknown tags anyway. diff --git a/src/codegen/bindgen-lib-internal.ts b/src/codegen/bindgen-lib-internal.ts index db0e6fb6abc..373bdbb27a5 100644 --- a/src/codegen/bindgen-lib-internal.ts +++ b/src/codegen/bindgen-lib-internal.ts @@ -23,7 +23,7 @@ export let structHashToSelf = new Map(); export const str = (v: any) => JSON.stringify(v); /** Capitalize */ export const cap = (s: string) => s[0].toUpperCase() + s.slice(1); -/** Escape a Zig Identifier */ +/** Escape an identifier */ export const zid = (s: string) => (s.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/) ? s : "@" + str(s)); /** Snake Case */ export const snake = (s: string) => @@ -39,7 +39,7 @@ export const camel = (s: string) => /** Pascal Case */ export const pascal = (s: string) => cap(camel(s)); -// Return symbol names of extern values (must be equivalent between C++ and Zig) +// Return symbol names of extern values (must be equivalent on both sides of the FFI boundary) /** The JS Host function, aka fn (*JSC.JSGlobalObject, *JSC.CallFrame) JSValue.MaybeException */ export const extJsFunction = (namespaceVar: string, fnLabel: string) => @@ -779,7 +779,7 @@ export type ArgStrategyChildItem = * In addition to moving a payload over, an additional bit of information * crosses the ABI boundary indicating if the function threw an exception. * - * For simplicity, the possibility of any Zig binding returning an error/calling + * For simplicity, the possibility of any native binding returning an error/calling * `throw` is assumed and there isnt a way to disable the exception check. */ export type ReturnStrategy = diff --git a/src/codegen/bindgen-lib.ts b/src/codegen/bindgen-lib.ts index 37a7d6cddb3..40edfd3d52f 100644 --- a/src/codegen/bindgen-lib.ts +++ b/src/codegen/bindgen-lib.ts @@ -212,7 +212,6 @@ export namespace t { * ``` * * ```zig - * // foo.zig * pub fn foo(bar: []const u8) void { * // ... * } @@ -288,7 +287,7 @@ export namespace t { } /** - * Equivalent to `stringEnum`, but using an enum sourced from the given Zig + * Equivalent to `stringEnum`, but using an enum sourced from the given * file. Use this to get an enum type that can have functions added. */ export function zigEnum(file: string, impl: string): Type { @@ -322,7 +321,6 @@ interface FuncOptionsWithVariant extends FuncMetadata { * ``` * * ```zig - * // foo.zig * pub fn foo1(a: i32) i32 { * return a; * } diff --git a/src/codegen/bindgen.ts b/src/codegen/bindgen.ts index e98e23ff8ea..2584f2a3808 100644 --- a/src/codegen/bindgen.ts +++ b/src/codegen/bindgen.ts @@ -1,7 +1,7 @@ // The binding generator to rule them all. -// Converts binding definition files (.bind.ts) into C++ and Zig code. +// Converts binding definition files (.bind.ts) into C++ and native binding code. // -// Generated bindings are available in `bun.generated..*` in Zig, +// Generated bindings are available as `bun.generated..*` on the native side, // or `Generated::::*` in C++ from including `Generated.h`. import assert from "node:assert"; import fs from "node:fs"; @@ -60,8 +60,8 @@ function resolveVariantStrategies(vari: Variant, name: string) { const abiType = !isNullable && arg.type.canDirectlyMapToCAbi(); if (abiType) { arg.loweringStrategy = { - // This does not work in release builds, possibly due to a Zig 0.13 bug - // regarding by-value extern structs in C functions. + // Passing by-value extern structs in C functions did not work + // reliably in release builds. // type: cAbiTypeInfo(abiType)[0] > 8 ? "c-abi-pointer" : "c-abi-value", // Always pass an argument by-pointer for now. type: abiType === "*anyopaque" || abiType === "*JSGlobalObject" ? "c-abi-value" : "c-abi-pointer", @@ -1376,7 +1376,7 @@ for (const [filename, { functions, typedefs }] of files) { cpp.line(`}`); cpp.line(); - // Generated Zig dispatch functions + // Generated dispatch functions variNum = 1; for (const vari of fn.variants) { const dispatchName = extDispatchVariant(namespaceVar, fn.name, variNum); diff --git a/src/codegen/bindgenv2/internal/base.ts b/src/codegen/bindgenv2/internal/base.ts index 4162f711ae1..93732f3fbd5 100644 --- a/src/codegen/bindgenv2/internal/base.ts +++ b/src/codegen/bindgenv2/internal/base.ts @@ -27,7 +27,7 @@ export abstract class Type { return this.bindgenType + ".ZigType"; } - /** This must be overridden if bindgen.zig defines a custom `OptionalZigType`. */ + /** This must be overridden if a custom `OptionalZigType` is defined. */ optionalZigType(style?: CodeStyle): string { return `?${this.zigType(style)}`; } diff --git a/src/codegen/bindgenv2/internal/dictionary.ts b/src/codegen/bindgenv2/internal/dictionary.ts index e2a7030c018..47598e9e50c 100644 --- a/src/codegen/bindgenv2/internal/dictionary.ts +++ b/src/codegen/bindgenv2/internal/dictionary.ts @@ -19,7 +19,7 @@ export interface DictionaryMember { type: Type; /** Optional default value to use when this member is missing or undefined. */ default?: any; - /** The name used in generated Zig/C++ code. Defaults to the public JS name. */ + /** The name used in generated code. Defaults to the public JS name. */ internalName?: string; /** Alternative JavaScript names for this member. */ altNames?: string[]; @@ -39,7 +39,7 @@ interface DictionaryOptions { name: string; /** Used in error messages. Defaults to `name`. */ userFacingName?: string; - /** Whether to generate a Zig `fromJS` function. */ + /** Whether to generate a `fromJS` conversion function. */ generateConversionFunction?: boolean; } diff --git a/src/codegen/bindgenv2/internal/union.ts b/src/codegen/bindgenv2/internal/union.ts index e452f8b1f08..737f8c4f6f5 100644 --- a/src/codegen/bindgenv2/internal/union.ts +++ b/src/codegen/bindgenv2/internal/union.ts @@ -31,7 +31,7 @@ export function union(name: string, alternatives: NamedAlternatives): NamedUnion /** * The order of types in this union is significant. Each type is tried in order, and the first one - * that successfully converts determines the active field in the corresponding Zig tagged union. + * that successfully converts determines the active field in the corresponding native tagged union. * * This means that it is an error to specify `RawAny` or `StrongAny` as anything other than the * last alternative, as conversion to any subsequent types would never be attempted. diff --git a/src/codegen/class-definitions.ts b/src/codegen/class-definitions.ts index 8049e12e5ae..86827d34c65 100644 --- a/src/codegen/class-definitions.ts +++ b/src/codegen/class-definitions.ts @@ -140,7 +140,7 @@ export class ClassDefinition { /** * Class constructor needs `this` value. * - * Makes the code generator call the Zig constructor function **after** the + * Makes the code generator call the native constructor function **after** the * JSValue is instantiated. Only use this if you must, as it probably isn't * good for GC since it means if the constructor throws the GC will have to * clean up the object that never reached JS. diff --git a/src/codegen/cppbind.ts b/src/codegen/cppbind.ts index 224277697ba..f5b3f479d78 100644 --- a/src/codegen/cppbind.ts +++ b/src/codegen/cppbind.ts @@ -1,8 +1,8 @@ /* -cppbind - C++ to Zig binding generator for Bun +cppbind - C++ binding generator for Bun -This tool automatically generates Zig bindings for C++ functions marked with [[ZIG_EXPORT(...)]] attributes. +This tool automatically generates Rust bindings for C++ functions marked with [[ZIG_EXPORT(...)]] attributes. It runs automatically when C++ files change during the build process. To run manually: @@ -18,7 +18,7 @@ To run manually: printf("hello world\n"); } ``` - Zig usage: `bun.cpp.hello_world();` + Rust usage: `bun_jsc::cpp::hello_world();` 2. **zero_is_throw** - Function returns JSValue, where .zero indicates an exception: ```cpp @@ -29,7 +29,7 @@ To run manually: return result; } ``` - Zig usage: `try bun.cpp.create_object(globalThis);` + Rust usage: `bun_jsc::cpp::create_object(global_this)?;` 3. **check_slow** - Function that may throw, performs runtime exception checking: ```cpp @@ -39,7 +39,7 @@ To run manually: RETURN_IF_EXCEPTION(scope, ); } ``` - Zig usage: `try bun.cpp.process_data(globalThis);` + Rust usage: `bun_jsc::cpp::process_data(global_this)?;` ### Parameters @@ -401,7 +401,7 @@ type ExportTag = "check_slow" | "zero_is_throw" | "false_is_throw" | "null_is_th // ─────────────────────────── Rust output (cpp.rs) ─────────────────────────── // -// Mirror of `generateZigFn` for the Rust port: each `[[ZIG_EXPORT(mode)]]` C++ +// Each `[[ZIG_EXPORT(mode)]]` C++ // function gets a typed `pub fn` in `bun_jsc::cpp` that wraps the raw extern in // the appropriate exception scope and converts to `JsResult`. The wrapper opens // the scope *before* calling into C++ so the callee's `DECLARE_THROW_SCOPE` dtor @@ -421,7 +421,7 @@ const rustSharedTypes: Record = { // Primitives "bool": "bool", // `char` signedness is platform-dependent (signed on x86_64-linux/windows, - // unsigned on aarch64); match Zig's `c_char` so a future by-value return + // unsigned on aarch64); use `core::ffi::c_char` so a future by-value return // doesn't silently sign-flip. "char": "core::ffi::c_char", "unsigned char": "u8", @@ -560,7 +560,7 @@ function generateRustType(type: CppType, parent: CppType | null): string { // Unknown opaque — only valid behind a pointer (the per-type shim casts the // pointee). Behind a pointer we degrade to c_void; in by-value position that // would emit `-> core::ffi::c_void` (a ZST in Rust → silent ABI corruption), - // so match the Zig generator and fail loudly at the C++ source location. + // so fail loudly at the C++ source location. if (parent?.type === "pointer") return "core::ffi::c_void"; throwError( type.position, @@ -661,7 +661,7 @@ function generateRustFn(fn: CppFn, rustRaw: string[], rustWrap: string[]): void const globalArg = fn.parameters.find(p => isGlobalObjectPtr(p.type)); if (!globalArg) { - // Same constraint as the Zig generator; emit a stub so the module still + // Emit a stub so the module still // compiles and the symbol name is greppable. rustWrap.push(`// skipped ${fn.name}: ${fn.tag} requires a JSGlobalObject* parameter`); return; @@ -672,8 +672,8 @@ function generateRustFn(fn: CppFn, rustRaw: string[], rustWrap: string[]): void // Inline the `top_scope!` body (rather than the `call_check_slow` *function* form, // which routes `SourceLocation::from_caller()` → thread-local intern probe per call // in debug builds). This is the highest-volume mode — keep it as cheap as the - // zero/false/null arms below. `src!()` resolves to the wrapper file/line, matching - // Zig's `cpp.zig` (`@src()` inside the wrapper); `#[track_caller]` would be a no-op + // zero/false/null arms below. `src!()` resolves to the wrapper file/line; + // `#[track_caller]` would be a no-op // against a syntactic `file!()`, so don't emit it. rustWrap.push( `#[inline]`, @@ -709,7 +709,7 @@ function generateRustFn(fn: CppFn, rustRaw: string[], rustWrap: string[]): void } else assertNever(fn.tag); // `validation_scope!` expands `src!()` syntactically (resolves to this generated - // file/line — parity with Zig's `@src()` inside cpp.zig wrappers). `#[track_caller]` + // file/line). `#[track_caller]` // can't influence a compile-time `file!()`, so don't emit it. rustWrap.push( `#[inline]`, diff --git a/src/codegen/generate-classes.ts b/src/codegen/generate-classes.ts index 09ee612b6ea..b659bb334bf 100644 --- a/src/codegen/generate-classes.ts +++ b/src/codegen/generate-classes.ts @@ -2740,7 +2740,7 @@ const rustModuleResolver = (() => { // `pub use bun_jsc::{BuildMessage, ResolveMessage};` so route there. return `crate::api::${name}`; }, - /** Resolve an absolute `.rs` (or `.zig`) file path to its `crate::…` module path. */ + /** Resolve an absolute `.rs` file path to its `crate::…` module path. */ resolveFile(absRs: string): string | null { return fileToMod.get(path.resolve(absRs)) ?? null; }, @@ -2844,8 +2844,7 @@ function generateRust( // ── exported #[no_mangle] thunks ───────────────────────────────────────── // Direct dispatch: each thunk calls an *inherent* method on the user's real // Rust struct (re-exported as `${typeName}` below). No trait, no opaque - // placeholder, no `unimplemented!()` — a missing method is a compile error, - // mirroring the Zig path's `@import("…").${T}.${fn}` behaviour. + // placeholder, no `unimplemented!()` — a missing method is a compile error. const thunks: string[] = []; const symbols: string[] = []; function thunk(sym: string, sig: string, body: string) { @@ -3071,9 +3070,9 @@ function generateRust( } // ── C++→Rust extern imports + safe wrappers ────────────────────────────── - // Emitted as free functions in a per-class `js_${T}` sub-module (mirrors - // Zig's `pub const js = jsc.Codegen.JS${T};`) so they don't collide with - // inherent `from_js`/`to_js` already defined on the real struct. + // Emitted as free functions in a per-class `js_${T}` sub-module so they + // don't collide with inherent `from_js`/`to_js` already defined on the + // real struct. const cachedExterns = gc_fields .map( ([name]) => @@ -3153,9 +3152,8 @@ ${gcAccessors} // ════════════════════════════════════════════════════════════════════════════ /// Native backing type for \`JS${typeName}.m_ctx\`. Re-export of the real -/// hand-ported struct so the thunks below call its inherent methods directly -/// (mirrors Zig's \`@import("…").${typeName}.\`). A missing method is a -/// compile error — fix it in \`${rustPath}\`, not here. +/// struct so the thunks below call its inherent methods directly. A missing +/// method is a compile error — fix it in \`${rustPath}\`, not here. pub use ${rustPath} as ${typeName}; ${thunks.join("\n\n")} @@ -3546,8 +3544,8 @@ function writeCppSerializers() { // ── Rust output: per-class thunks for `lang === "rust"` (default). ───────── // Zig output is still emitted for every class so the C++ side's `extern` -// declarations stay satisfied during incremental migration; the Zig thunks for -// rust-lang classes simply go unreferenced once the Rust crate links. +// declarations stay satisfied; the Zig thunks for rust-lang classes simply go +// unreferenced once the Rust crate links. { const rustClasses = classes.filter(a => (a.lang ?? "rust") === "rust"); let totalSyms = 0; @@ -3753,8 +3751,8 @@ if (!process.env.ONLY_ZIG) { } /** - * Generates a basic TypeScript type signature string and corresponding Zig source comment - * for a given property definition. + * Generates a basic TypeScript type signature string for a given property + * definition. * Returns null if the property should not be included in the types (e.g., private). */ function getPropertySignatureWithComment( diff --git a/src/codegen/generate-host-exports.ts b/src/codegen/generate-host-exports.ts index 2529465c25e..05cae8b8c8d 100644 --- a/src/codegen/generate-host-exports.ts +++ b/src/codegen/generate-host-exports.ts @@ -379,7 +379,7 @@ function emitThunk(e: Export): string { const loc = `${path.relative(repoRoot, e.file)}:${e.line}`; // `JsResult` impls need `to_js_host_call` (exception-scope assert + // panic barrier + Err→empty mapping). Plain-`JSValue` impls are bare - // `callconv(jsc.conv)` bodies in the .zig spec — wrap them and you trip + // host-call-ABI bodies (no exception-scope wrapper) — wrap them and you trip // `assert_exception_presence_matches(false)` whenever the body legitimately // leaves an exception pending while returning non-empty (e.g. // `Bun__drainMicrotasksFromJS`). Match `#[bun_jsc::host_call]`: deref + call, diff --git a/src/codegen/generate-js2native.ts b/src/codegen/generate-js2native.ts index 0a640d1b644..86bfed72577 100644 --- a/src/codegen/generate-js2native.ts +++ b/src/codegen/generate-js2native.ts @@ -254,7 +254,7 @@ export function getJS2NativeZig(gs2NativeZigPath: string) { // the C++ side declares in GeneratedJS2Native.h. The C++ output is invariant; // only the implementer of the symbol changes. // -// Two ABI shapes (mirroring the Zig output exactly): +// Two ABI shapes: // • nativeCalls (type "zig") → `${sym}_workaround(global) -> JSValue` // • wrapperCalls (type "zig") → `${sym}(global, callframe) -> JSValue` // diff --git a/src/codegen/generate-jssink.ts b/src/codegen/generate-jssink.ts index fc9e87f4745..c047c862e43 100644 --- a/src/codegen/generate-jssink.ts +++ b/src/codegen/generate-jssink.ts @@ -1033,16 +1033,14 @@ extern "C" void ${name}__onClose(JSC::EncodedJSValue controllerValue, JSC::Encod // `BUN_DECLARE_HOST_FUNCTION(${name}__{construct,write,end,flush,start})` plus // the two non-host-fn externs `${name}__getInternalFd` / `${name}__memoryCost`. // Each thunk calls an inherent method on the real sink struct in -// `crate::webcore` (mirroring Zig's `@import("…").${name}.`); a missing -// method is a compile error. +// `crate::webcore`; a missing method is a compile error. // // Calling convention: `BUN_DECLARE_HOST_FUNCTION` and `endWithSink` use // `SYSV_ABI` (= `extern "sysv64"` on win-x64, `"C"` elsewhere) — wrapped in // `bun_jsc::jsc_host_abi!`. The remaining `ZIG_DECL` / plain `extern "C"` // symbols (finalize/close/updateRef/getInternalFd/memoryCost) stay `extern "C"`. function rustSink() { - // All sink structs live (or will live) under `crate::webcore`; the Zig - // originals are `src/runtime/webcore/streams.zig::${name}`. + // All sink structs live (or will live) under `crate::webcore`. const sinkPaths: Record = { ArrayBufferSink: "crate::webcore::array_buffer_sink::ArrayBufferSink", FileSink: "crate::webcore::file_sink::FileSink", diff --git a/src/collections/StaticHashMap.rs b/src/collections/StaticHashMap.rs index c37c17d0553..4769089f1c2 100644 --- a/src/collections/StaticHashMap.rs +++ b/src/collections/StaticHashMap.rs @@ -7,7 +7,7 @@ use core::marker::PhantomData; use bun_alloc::AllocError; // ────────────────────────────────────────────────────────────────────────── -// Context trait — models Zig's `Context: type` with `.hash(k)` / `.eql(a, b)` +// Context trait — `.hash(k)` / `.eql(a, b)` // ────────────────────────────────────────────────────────────────────────── // Canonical definitions live in `crate::zig_hash_map`; re-exported here so the @@ -16,7 +16,7 @@ use bun_alloc::AllocError; pub use crate::zig_hash_map::{AutoHashContext as AutoContext, HashContext}; // ────────────────────────────────────────────────────────────────────────── -// Type aliases (Zig: AutoHashMap) +// Type aliases // ────────────────────────────────────────────────────────────────────────── pub type AutoHashMap = @@ -47,12 +47,11 @@ impl Entry { K: Copy + Default, V: Copy + Default, { - // PORT NOTE: Zig used `std.mem.zeroes(K)` / `undefined` — key/value of - // an empty entry (hash == EMPTY_HASH) are never read. Rust cannot use - // `mem::zeroed()` here: K may be `&[u8]` (or any `Copy` type with a - // niche), for which all-zero bytes violate the validity invariant - // regardless of whether the value is later read. Use `Default` for the - // unread placeholder instead. + // Key/value of an empty entry (hash == EMPTY_HASH) are never read. + // `mem::zeroed()` cannot be used here: K may be `&[u8]` (or any `Copy` + // type with a niche), for which all-zero bytes violate the validity + // invariant regardless of whether the value is later read. Use + // `Default` for the unread placeholder instead. Self { hash: EMPTY_HASH, key: K::default(), @@ -74,23 +73,20 @@ impl fmt::Display for Entry { pub use crate::hash_map::GetOrPutResult; // ────────────────────────────────────────────────────────────────────────── -// comptime helpers (Zig top-of-fn const expressions) +// const helpers // ────────────────────────────────────────────────────────────────────────── #[inline] const fn compute_shift(capacity: u64) -> u8 { - // Zig: 63 - math.log2_int(u64, capacity) + 1 (63 - capacity.ilog2() + 1) as u8 } #[inline] const fn compute_overflow(capacity: u64, shift: u8) -> u64 { - // Zig: capacity / 10 + (63 - @as(u64, shift) + 1) << 1 - // Zig precedence: `+` binds tighter than `<<`, so this is (a + b) << 1. (capacity / 10 + (63 - shift as u64 + 1)) << 1 } -/// Checked u64→usize narrowing for table indices (Zig indexes by u64 directly). +/// Checked u64→usize narrowing for table indices. #[inline] fn to_idx(x: u64) -> usize { usize::try_from(x).expect("int cast") @@ -108,8 +104,8 @@ pub const fn static_slots(capacity: usize) -> usize { // StaticHashMap // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: the inline `[Entry; CAPACITY + overflow]` array length depends on -// a const fn of `CAPACITY`, which requires nightly `feature(generic_const_exprs)`. +// The inline `[Entry; CAPACITY + overflow]` array length depends on a const fn +// of `CAPACITY`, which requires nightly `feature(generic_const_exprs)`. // Stable workaround (same as ArrayBitSet): callers pass `SLOTS = static_slots(CAPACITY)` // as a second const param; a const-assert in `Default::default()` checks they match. pub struct StaticHashMap< @@ -121,7 +117,7 @@ pub struct StaticHashMap< > { pub entries: [Entry; SLOTS], pub len: usize, - /// Zig `u6`; stored as u8. + /// Hash shift; always < 64, stored as u8. pub shift: u8, // put_probe_count: usize, // get_probe_count: usize, @@ -141,8 +137,6 @@ impl: Copy` const-init; - // may need `MaybeUninit` + loop if K/V aren't const-zeroable. entries: [Entry::empty(); SLOTS], len: 0, shift: compute_shift(CAPACITY as u64), @@ -179,7 +173,7 @@ impl Has pub struct HashMap { pub entries: Box<[Entry]>, pub len: usize, - /// Zig `u6`; stored as u8. + /// Hash shift; always < 64, stored as u8. pub shift: u8, // put_probe_count: usize, // get_probe_count: usize, @@ -222,7 +216,6 @@ impl< let overflow = compute_overflow(capacity, shift); let n = usize::try_from(capacity + overflow).expect("int cast"); - // Zig: gpa.alloc + @memset(.{}) let entries = vec![Entry::::empty(); n].into_boxed_slice(); Ok(Self { @@ -257,9 +250,6 @@ impl< let mut map = Self::init_capacity(capacity * 2)?; - // PORT NOTE: reshaped for borrowck — Zig walks raw `[*]Entry` pointers - // (`src`, `dst`, `end`); here we iterate by index over the old slice and - // index into the new boxed slice. let mut dst: usize = 0; let mut src: usize = 0; while src != end { @@ -270,7 +260,6 @@ impl< } else { 0 }; - // Zig: dst = if (@intFromPtr(p) >= @intFromPtr(dst)) p else dst; if i >= dst { dst = i; } @@ -280,7 +269,7 @@ impl< dst += 1; } - // Zig: self.deinit(gpa); — old Box drops on assignment below. + // Old Box drops on assignment below. self.entries = map.entries; self.shift = map.shift; Ok(()) @@ -314,8 +303,8 @@ impl< // HashMapMixin — shared method bodies for StaticHashMap & HashMap // ────────────────────────────────────────────────────────────────────────── -/// Mirrors Zig's `fn HashMapMixin(Self, K, V, Context) type`. Implementors -/// supply the backing storage; default methods provide the Robin-Hood logic. +/// Implementors supply the backing storage; default methods provide the +/// Robin-Hood logic. pub trait HashMapMixin { fn storage(&self) -> &[Entry]; fn storage_mut(&mut self) -> &mut [Entry]; @@ -331,11 +320,10 @@ pub trait HashMapMixin { *self.len_mut() = 0; } - /// Full backing slice (capacity + overflow). Matches Zig's `slice()`. + /// Full backing slice (capacity + overflow). fn slice(&mut self) -> &mut [Entry] { - // Zig recomputes `capacity + overflow` from `shift`; with Box<[T]>/[T; N] - // the storage already carries its exact length, so just return it. - // PORT NOTE: assert kept for parity with Zig's implicit invariant. + // The storage carries its exact length; the assert checks it stays + // consistent with the `shift`-derived `capacity + overflow` size. let capacity = 1u64 << (63 - self.shift() + 1); let overflow = compute_overflow(capacity, self.shift()); debug_assert_eq!( @@ -381,9 +369,8 @@ pub trait HashMapMixin { V: Copy + Default, Ctx: HashContext, { - // PORT NOTE: Zig left `value = undefined` (never read until the caller - // writes via `value_ptr`). Use `Default` for the placeholder — V may - // not be zero-valid. + // `value` is never read until the caller writes via `value_ptr`. Use + // `Default` for the placeholder — V may not be zero-valid. let mut it: Entry = Entry { hash: Ctx::ctx_hash(&key), key, @@ -396,8 +383,8 @@ pub trait HashMapMixin { let mut inserted_at: Option = None; loop { - // PORT NOTE: reshaped for borrowck — copy entry out, drop borrow, - // re-borrow mutably for write/return. + // Copy the entry out, drop the borrow, re-borrow mutably for + // write/return. let entry = self.storage()[i]; if entry.hash >= it.hash { if Ctx::ctx_eql(&entry.key, &key) { @@ -554,15 +541,84 @@ pub trait HashMapMixin { mod tests { use super::*; - // TODO(port): Zig tests use `std.rand.DefaultPrng` (xoshiro256++). Need a - // matching PRNG for byte-identical key sequences, or accept any PRNG since - // these tests only check sortedness/round-trip, not specific keys. + /// xoshiro256++ with the state seeded by splitmix64. `AutoHashContext` + /// routes through `bun_wyhash::auto_hash` (mum-mix). The 100%-load probe + /// bound of the static test was validated for this hash by exact + /// simulation of all 128 seeds: max slot index touched (incl. delete's + /// `i + 1` backshift read) is 548 of 632, with no 64-bit hash collisions + /// among any seed's 512 keys. + struct Xoshiro256PlusPlus { + s: [u64; 4], + } + + impl Xoshiro256PlusPlus { + fn init(seed: u64) -> Self { + fn splitmix64(state: &mut u64) -> u64 { + *state = state.wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut z = *state; + z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + z ^ (z >> 31) + } + let mut sm = seed; + let mut s = [0u64; 4]; + for slot in s.iter_mut() { + *slot = splitmix64(&mut sm); + } + Self { s } + } + + fn next(&mut self) -> u64 { + let s = &mut self.s; + let result = s[0].wrapping_add(s[3]).rotate_left(23).wrapping_add(s[0]); + let t = s[1] << 17; + s[2] ^= s[0]; + s[3] ^= s[1]; + s[1] ^= s[2]; + s[0] ^= s[3]; + s[2] ^= t; + s[3] = s[3].rotate_left(45); + result + } + } #[test] fn static_hash_map_put_get_delete_grow() { - // TODO(port): blocked on generic_const_exprs for StaticHashMap inline array. - // let mut map: StaticHashMap = Default::default(); - // for seed in 0..128 { ... } + const CAP: usize = 512; + const SLOTS: usize = static_slots(CAP); + // Boxed: ~15 KB of entries is fine on the heap, gratuitous on the stack. + let mut map: Box> = + Box::new(Default::default()); + + // Miri is ~100× slower; 2 seeds still cover the put/get/delete cycle. + const SEEDS: u64 = if cfg!(miri) { 2 } else { 128 }; + for seed in 0..SEEDS { + let mut rng = Xoshiro256PlusPlus::init(seed); + + let keys: Vec = (0..512).map(|_| rng.next() as usize).collect(); + + assert_eq!(map.shift, 55); + + for (i, &key) in keys.iter().enumerate() { + map.put_assume_capacity(key, i); + } + assert_eq!(map.len, keys.len()); + + let mut it: u64 = 0; + for entry in map.slice().iter() { + if !entry.is_empty() { + assert!(it <= entry.hash, "Unsorted"); + it = entry.hash; + } + } + + for (i, &key) in keys.iter().enumerate() { + assert_eq!(map.get(key).unwrap(), i); + } + for (i, &key) in keys.iter().enumerate() { + assert_eq!(map.delete(key).unwrap(), i); + } + } } #[test] @@ -570,18 +626,11 @@ mod tests { // Miri is ~100× slower; 2 seeds still exercises grow (`shift` assert below). const SEEDS: u64 = if cfg!(miri) { 2 } else { 128 }; for seed in 0..SEEDS { - // TODO(port): replace with xoshiro256++ to match Zig DefaultPrng. - let mut state = seed.wrapping_mul(0x9E37_79B9_7F4A_7C15).wrapping_add(1); - let mut next = || { - state ^= state << 13; - state ^= state >> 7; - state ^= state << 17; - state - }; + let mut rng = Xoshiro256PlusPlus::init(seed); let mut keys = vec![0usize; 512]; for k in keys.iter_mut() { - *k = next() as usize; + *k = rng.next() as usize; } let mut map = AutoHashMap::::init_capacity(16).unwrap(); @@ -612,5 +661,3 @@ mod tests { } } } - -// ported from: src/collections/StaticHashMap.zig diff --git a/src/collections/array_hash_map.rs b/src/collections/array_hash_map.rs index 00084593249..f7a8c3b2948 100644 --- a/src/collections/array_hash_map.rs +++ b/src/collections/array_hash_map.rs @@ -1,4 +1,4 @@ -//! Port of Zig's `std.ArrayHashMap` family + Bun's string-keyed wrappers +//! Insertion-ordered hash maps (`ArrayHashMap`) + Bun's string-keyed wrappers //! (`bun.StringArrayHashMap`, `bun.StringHashMap`, //! `bun.CaseInsensitiveASCIIStringArrayHashMap`, `bun.StringHashMapUnowned`). //! @@ -10,12 +10,11 @@ //! * `getOrPut` hands back a stable `key_ptr` / `value_ptr` / `index` triple //! so callers can fill the slot in-place after the lookup. //! -//! Zig builds a separate `index_header` (open-addressed `hash → entry_index` -//! table) once `len > 8` so lookups stay O(1). This port mirrors that with a -//! lazily-built `hashbrown::HashTable` keyed by the cached u32 hash: +//! A separate index — a lazily-built `hashbrown::HashTable` keyed by the +//! cached u32 hash — is built once `len > 8` so lookups stay O(1): //! linear scan below the threshold, indexed lookup above it. Point removals -//! (`pop`, `swap_remove`) patch the index in place (O(1), matching Zig's -//! `removeFromIndexByIndex`); wholesale permutations (`sort`, +//! (`pop`, `swap_remove`) patch the index in place (O(1)); +//! wholesale permutations (`sort`, //! `ordered_remove`) drop and immediately rebuild it so lookups never //! silently degrade to O(n). @@ -32,38 +31,36 @@ use core::ops::{Deref, DerefMut}; use bun_alloc::AllocError; // ────────────────────────────────────────────────────────────────────────── -// Free functions (Zig: `std.array_hash_map.hashString` / `std.hash_map.hashString`) +// Free functions // ────────────────────────────────────────────────────────────────────────── -/// `std.array_hash_map.hashString` — wyhash(seed=0) truncated to u32. +/// wyhash(seed=0) truncated to u32. #[inline] pub fn hash_string(s: &[u8]) -> u32 { - bun_wyhash::hash(s) as u32 // @truncate + bun_wyhash::hash(s) as u32 } // ────────────────────────────────────────────────────────────────────────── -// Context traits (Zig: `Context` / `Adapter` duck types) +// Context traits // ────────────────────────────────────────────────────────────────────────── -/// Hash/eql strategy for an `ArrayHashMap`. -/// Zig passes these as `anytype`; here it's a trait so the map can be generic -/// over the strategy without each method taking a `ctx` argument. +/// Hash/eql strategy for an `ArrayHashMap`. A trait so the map can be +/// generic over the strategy without each method taking a `ctx` argument. pub trait ArrayHashContext: Default { fn hash(&self, key: &K) -> u32; - /// `b_index` is the index of `b` in the entry array (Zig passes it so - /// adapted contexts can look at sibling storage). + /// `b_index` is the index of `b` in the entry array (so adapted contexts + /// can look at sibling storage). fn eql(&self, a: &K, b: &K, b_index: usize) -> bool; } /// Adapted lookup: hash a `Q` and compare it against the stored `K`s without -/// constructing a `K` first (Zig: `getOrPutAdapted` / `getOrPutContextAdapted`). +/// constructing a `K` first. pub trait ArrayHashAdapter { fn hash(&self, key: &Q) -> u32; fn eql(&self, a: &Q, b: &K, b_index: usize) -> bool; } -/// Default context: `Hash` + `Eq` driven through wyhash, mirroring Zig's -/// `AutoContext` / `getAutoHashFn`. +/// Default context: driven through `Hash` + `Eq`. #[derive(Default, Clone, Copy)] pub struct AutoContext; @@ -72,9 +69,8 @@ impl ArrayHashContext for AutoContext { fn hash(&self, key: &K) -> u32 { // Keys here are small POD (`Ref`, `u32`, indices). FxHash is a single // mul+rotate per word — measurably cheaper than wyhash's `mum` fold for - // 8-byte keys, and what rustc uses for the same workload shape. The Zig - // port used wyhash only because that's what `std.array_hash_map` defaults - // to; nothing persists these hashes across runs. + // 8-byte keys, and what rustc uses for the same workload shape; + // nothing persists these hashes across runs. use core::hash::Hasher; let mut h = rustc_hash::FxHasher::default(); key.hash(&mut h); @@ -86,7 +82,7 @@ impl ArrayHashContext for AutoContext { } } -/// `std.array_hash_map.StringContext` — byte-slice keys hashed with wyhash. +/// Byte-slice keys hashed with wyhash. #[derive(Default, Clone, Copy)] pub struct StringContext; @@ -101,8 +97,8 @@ impl ArrayHashContext<[u8]> for StringContext { } } -/// `bun.CaseInsensitiveASCIIStringContext` (src/bun.zig) — ASCII-lowercased -/// wyhash + ASCII-case-insensitive equality. Used for env-var maps on Windows. +/// ASCII-lowercased wyhash + ASCII-case-insensitive equality. Used for +/// env-var maps on Windows. #[derive(Default, Clone, Copy)] pub struct CaseInsensitiveAsciiStringContext; @@ -111,7 +107,8 @@ impl CaseInsensitiveAsciiStringContext { bun_wyhash::hash_ascii_lowercase(0, s) as u32 // @truncate } - /// `bun.CaseInsensitiveASCIIStringContext.pre` (src/bun.zig:1031). + /// Precompute the case-folded hash of `input` so repeated probes against + /// the same key skip the lowercasing pass. #[inline] pub fn pre(input: &[u8]) -> CaseInsensitiveAsciiPrehashed<'_> { CaseInsensitiveAsciiPrehashed { @@ -121,9 +118,8 @@ impl CaseInsensitiveAsciiStringContext { } } -/// `bun.CaseInsensitiveASCIIStringContext.Prehashed` (src/bun.zig:1035) — -/// caches the case-folded hash for `input` so repeated probes against the same -/// key skip the lowercasing pass. +/// Caches the case-folded hash for `input` so repeated probes against the +/// same key skip the lowercasing pass. pub struct CaseInsensitiveAsciiPrehashed<'a> { pub value: u32, pub input: &'a [u8], @@ -187,9 +183,8 @@ impl ArrayHashContext<[u8]> for CaseInsensitiveAsciiStringContext { // ────────────────────────────────────────────────────────────────────────── /// Result of `get_or_put*`. When `found_existing == false`, `*value_ptr` is a -/// freshly-defaulted slot the caller is expected to overwrite (Zig leaves it -/// `undefined`; Rust cannot, so the value type carries a `Default` bound on the -/// inserting paths). +/// freshly-defaulted slot the caller is expected to overwrite (the value type +/// carries a `Default` bound on the inserting paths). pub struct GetOrPutResult<'a, K, V> { pub found_existing: bool, pub index: usize, @@ -197,22 +192,19 @@ pub struct GetOrPutResult<'a, K, V> { pub value_ptr: &'a mut V, } -/// Zig: `std.ArrayHashMap.KV` — owned key/value pair returned by -/// `fetchSwapRemove` / `fetchOrderedRemove`. +/// Owned key/value pair returned by the `fetch_*_remove` methods. pub struct KV { pub key: K, pub value: V, } -/// Iterator entry — both halves mutable, matching Zig's `Entry { key_ptr: *K, -/// value_ptr: *V }`. +/// Iterator entry — both halves mutable. pub struct Entry<'a, K, V> { pub key_ptr: &'a mut K, pub value_ptr: &'a mut V, } -/// Insertion-order iterator yielding `Entry`. Resettable (Zig callers do -/// `it.reset()` to rewind; here `index = 0`). +/// Insertion-order iterator yielding `Entry`. Resettable via `reset()`. pub struct Iter<'a, K, V> { keys: *mut K, values: *mut V, @@ -263,10 +255,9 @@ pub trait ArrayHashMapExt { // ArrayHashMap // ────────────────────────────────────────────────────────────────────────── -/// Zig `index_header` threshold: at or below this many entries the -/// hash-prefiltered linear scan over `hashes` wins (the whole `Vec` fits -/// in one cache line); above it we build/maintain the SwissTable index. Same -/// `linear_scan_max` cut-off as `std/array_hash_map.zig`. +/// At or below this many entries the hash-prefiltered linear scan over +/// `hashes` wins (the whole `Vec` fits in one cache line); above it we +/// build/maintain the SwissTable index. const INDEX_THRESHOLD: usize = 8; /// Widen the cached `u32` entry hash to the `u64` hashbrown probes with. The @@ -328,8 +319,7 @@ fn index_insert_unique( /// [`index_insert_unique`] — keep the `reserve_rehash` monomorph in this crate /// rather than re-emitting it per `` instantiation. Called from the /// `reserve` / `ensure_*_capacity` paths so a caller that pre-sizes the map -/// (the Zig originals' `ensureTotalCapacityContext`, which also sizes the index -/// header) pays the SwissTable grow once instead of `O(log n)` times across the +/// pays the SwissTable grow once instead of `O(log n)` times across the /// following `push_entry` loop. #[inline(never)] fn index_reserve( @@ -448,8 +438,8 @@ pub struct ArrayHashMap { /// `INDEX_THRESHOLD` crossover. index: Option>, A>>, ctx: C, - // Zig `pointer_stability: std.debug.SafetyLock` — debug-only re-entrancy - // guard around operations that may invalidate entry pointers. `AtomicBool` + // Debug-only re-entrancy guard around operations that may invalidate + // entry pointers. `AtomicBool` // (not `Cell`) so the field doesn't strip `Sync` off the map in // debug builds — a debug-only diagnostic must not change the type's // auto-trait surface vs release (callers store maps in `static LazyLock`, @@ -465,7 +455,7 @@ impl Default for ArrayHashMap { } impl ArrayHashMap { - /// Zig `clone()` is fallible (OOM); kept as `Result` for API parity. + /// Fallible (OOM) clone; kept as `Result` for API stability. pub fn clone(&self) -> Result { Ok(Self { keys: self.keys.clone(), @@ -517,16 +507,14 @@ impl ArrayHashMap { self.keys.is_empty() } - /// Zig: `capacity()` — number of entries the backing storage can hold - /// without reallocating. + /// Number of entries the backing storage can hold without reallocating. #[inline] pub fn capacity(&self) -> usize { self.keys.capacity() } - /// Zig: `pop()` — remove and return the last entry in insertion order, or - /// `None` when empty. O(1); patches the index in place (Zig - /// `removeFromIndexByIndex`) so subsequent lookups stay O(1). + /// Remove and return the last entry in insertion order, or `None` when + /// empty. O(1); patches the index in place so subsequent lookups stay O(1). pub fn pop(&mut self) -> Option> { let key = self.keys.pop()?; // SAFETY: keys/values/hashes always share the same length. @@ -536,8 +524,8 @@ impl ArrayHashMap { Some(KV { key, value }) } - /// Zig: `clearAndFree(allocator)` — drop every entry and release the - /// backing allocations (capacity goes to zero). + /// Drop every entry and release the backing allocations (capacity goes to + /// zero). pub fn clear_and_free(&mut self) { self.keys = Vec::new_in(A::default()); self.values = Vec::new_in(A::default()); @@ -554,10 +542,9 @@ impl ArrayHashMap { Ok(()) } - /// Zig: `map.entries.len = n` after `ensureTotalCapacity(n)` — bulk-resize - /// the backing columns so callers can `keys_mut().copy_from_slice(...)` / - /// `values_mut().copy_from_slice(...)` and then `re_index()`. Mirrors the - /// pattern in `lockfile/bun.lockb.zig`'s `Serializer.load`. + /// Bulk-resize the backing columns so callers can + /// `keys_mut().copy_from_slice(...)` / `values_mut().copy_from_slice(...)` + /// and then `re_index()`. /// /// # Safety /// `n` must not exceed reserved capacity, and every element in @@ -569,7 +556,8 @@ impl ArrayHashMap { debug_assert!(n <= self.keys.capacity()); debug_assert!(n <= self.values.capacity()); debug_assert!(n <= self.hashes.capacity()); - // SAFETY: caller contract above; matches Zig `.entries.len = n`. + // SAFETY: caller contract above — `n` is within reserved capacity and + // the uninit window is filled before any read. unsafe { self.keys.set_len(n); self.values.set_len(n); @@ -579,11 +567,9 @@ impl ArrayHashMap { self.drop_index(); } - /// Zig `ensureTotalCapacityContext`: same as `ensure_total_capacity` but - /// takes an explicit `ctx` for the stored key type. This port maintains no - /// separate index header (lookup scans the cached `hashes` vec), so the - /// context is accepted and ignored — capacity reservation is purely a Vec - /// operation here. + /// Same as `ensure_total_capacity` but takes an explicit `ctx` for the + /// stored key type. Capacity reservation is purely a Vec operation here, + /// so the context is accepted and ignored. #[inline] pub fn ensure_total_capacity_context( &mut self, @@ -593,7 +579,7 @@ impl ArrayHashMap { self.ensure_total_capacity(n) } - /// Zig `putAssumeCapacityContext`: insert/replace using an externally-supplied + /// Insert/replace using an externally-supplied /// hash/eql context instead of the stored `C`. Used when `C = AutoContext` /// can't satisfy `K: Hash` (e.g. `bun_semver::String`, whose hash needs the /// owning `arg_buf`/`existing_buf`). Takes closures rather than an @@ -612,7 +598,6 @@ impl ArrayHashMap { self.values[i] = value; return; } - // PERF(port): was assume_capacity — Vec::push is amortized O(1) regardless. self.push_entry(key, value, h); } @@ -647,12 +632,12 @@ impl ArrayHashMap { } } - /// Zig: `shrinkAndFree(new_len)` — truncate to `new_len` entries (dropping - /// any tail) and release excess capacity. Insertion order is preserved, so - /// no rehash of the surviving prefix is needed. + /// Truncate to `new_len` entries (dropping any tail) and release excess + /// capacity. Insertion order is preserved, so no rehash of the surviving + /// prefix is needed. pub fn shrink_and_free(&mut self, new_len: usize) { - // Drop tail index slots first (Zig: removeFromIndexByIndex loop), so - // the surviving accelerator stays valid for O(1) lookups. + // Drop tail index slots first, so the surviving accelerator stays + // valid for O(1) lookups. if self.index.is_some() { for i in new_len..self.hashes.len() { let h = self.hashes[i]; @@ -739,9 +724,7 @@ impl ArrayHashMap { self.index = None; } - /// std-HashMap-compat alias for `clear_retaining_capacity`. Zig callers - /// frequently spell this `clearRetainingCapacity()`; ported call sites that - /// went through the std-alias path expect bare `clear()`. + /// std-HashMap-compat alias for `clear_retaining_capacity`. #[inline] pub fn clear(&mut self) { self.clear_retaining_capacity(); @@ -749,14 +732,13 @@ impl ArrayHashMap { /// std-HashMap-compat: shared iteration over `(key, value)` pairs in /// insertion order. Distinct from [`iterator`](Self::iterator) which yields - /// mutable `Entry { key_ptr, value_ptr }` (Zig shape) and requires - /// `&mut self`. + /// mutable `Entry { key_ptr, value_ptr }` and requires `&mut self`. #[inline] pub fn iter(&self) -> core::iter::Zip, core::slice::Iter<'_, V>> { self.keys.iter().zip(self.values.iter()) } - /// Zig `getIndexContext` for callers whose context is an inherent-method + /// Index lookup for callers whose context is an inherent-method /// struct (no `ArrayHashAdapter` impl). Takes the precomputed `u32` hash /// plus an `eql` closure so e.g. `bun_semver::String::ArrayHashContext` /// (which needs `arg_buf`/`existing_buf`) can drive a `&self` lookup. @@ -848,7 +830,7 @@ impl ArrayHashMap { } /// Remove the index slot pointing at `tail` (the just-popped last entry). - /// O(1); mirrors Zig `removeFromIndexByIndex` for the `pop`/`shrink` path. + /// O(1); used for the `pop`/`shrink` path. #[inline] fn index_remove_tail(&mut self, tail: usize, tail_hash: u32) { let Some(index) = self.index.as_deref_mut() else { @@ -862,7 +844,7 @@ impl ArrayHashMap { /// Patch the index after a `Vec::swap_remove(removed)`: drop the slot for /// `removed`, then retarget the slot that still says `old_last` (the /// pre-swap tail index, == `self.keys.len()` post-swap) to `removed`. - /// O(1); mirrors Zig `removeFromIndexByIndex` + `updateEntryIndex`. + /// O(1). #[inline] fn index_swap_remove(&mut self, removed: usize, removed_hash: u32) { let Some(index) = self.index.as_deref_mut() else { @@ -882,7 +864,7 @@ impl ArrayHashMap { } } - /// Zig `ArrayHashMap.sort` — stable in-place sort of keys/values/hashes by + /// Stable in-place sort of keys/values/hashes by /// a caller-supplied index comparator. The closure receives borrows of the /// key and value slices so it can compare on either without re-borrowing /// `self`. @@ -962,7 +944,7 @@ impl ArrayHashMap { Some((&mut self.keys[index], &mut self.values[index])) } - /// Zig `swapRemoveAt` — remove the entry at `index` by swapping in the last + /// Remove the entry at `index` by swapping in the last /// entry. O(1); does not preserve insertion order. Returns the removed pair. pub fn swap_remove_at(&mut self, index: usize) -> (K, V) { let k = self.keys.swap_remove(index); @@ -972,7 +954,7 @@ impl ArrayHashMap { (k, v) } - // ── adapted lookup (Zig: getAdapted / getIndexAdapted) ───────────────── + // ── adapted lookup ────────────────────────────────────────────────────── /// Look up by `key` using `adapter` for hash/eql, without constructing a `K`. #[inline] @@ -993,8 +975,7 @@ impl ArrayHashMap { .map(|i| &self.values[i]) } - /// Zig `getPtrContext` / `getPtrAdapted` — mutable value lookup using an - /// externally-supplied hash/eql adapter. + /// Mutable value lookup using an externally-supplied hash/eql adapter. #[inline] pub fn get_ptr_adapted(&mut self, key: &Q, adapter: &Ad) -> Option<&mut V> where @@ -1035,7 +1016,7 @@ impl, A: MapAllocator> ArrayHashMap { self.get_index(key).map(|i| &self.values[i]) } - /// Zig `getPtr` — mutable value lookup. + /// Mutable value lookup. pub fn get_ptr_mut(&mut self, key: &K) -> Option<&mut V> { let i = self.get_index(key)?; Some(&mut self.values[i]) @@ -1057,8 +1038,7 @@ impl, A: MapAllocator> ArrayHashMap { pub fn put(&mut self, key: K, value: V) -> Result<(), AllocError> { let h = self.ctx.hash(&key); if let Some(i) = self.find_hash(h, |k, idx| self.ctx.eql(&key, k, idx)) { - // Zig putContext (std/array_hash_map.zig:941): only assigns - // `result.value_ptr.*`; the original key is preserved. + // Only the value is assigned on hit; the original key is preserved. self.values[i] = value; } else { self.push_entry(key, value, h); @@ -1077,8 +1057,8 @@ impl, A: MapAllocator> ArrayHashMap { Ok(()) } - /// PERF(port): Zig skips the grow check; this port does too but `Vec::push` - /// will still reallocate if the caller lied about capacity. + /// PERF: skips the grow check, but `Vec::push` will still reallocate if + /// the caller lied about capacity. pub fn put_assume_capacity(&mut self, key: K, value: V) { let _ = self.put(key, value); } @@ -1087,7 +1067,7 @@ impl, A: MapAllocator> ArrayHashMap { pub fn insert(&mut self, key: K, value: V) -> Option { let h = self.ctx.hash(&key); if let Some(i) = self.find_hash(h, |k, idx| self.ctx.eql(&key, k, idx)) { - // std::HashMap::insert and Zig put: keep the original key on hit. + // Like std::HashMap::insert: keep the original key on hit. Some(core::mem::replace(&mut self.values[i], value)) } else { self.push_entry(key, value, h); @@ -1103,15 +1083,15 @@ impl, A: MapAllocator> ArrayHashMap { true } - /// Zig: `fetchSwapRemove` — swap-remove returning the removed `(K, V)` pair, - /// or `None` if `key` was not present. + /// Swap-remove returning the removed `(K, V)` pair, or `None` if `key` + /// was not present. pub fn fetch_swap_remove(&mut self, key: &K) -> Option<(K, V)> { let i = self.get_index(key)?; Some(self.swap_remove_at(i)) } - /// Zig: `orderedRemove` — preserves insertion order of remaining entries. - /// Returns `true` if the key was present (matching Zig's `bool` return). + /// Preserves insertion order of remaining entries. + /// Returns `true` if the key was present. #[inline] pub fn ordered_remove(&mut self, key: &K) -> bool { self.remove(key).is_some() @@ -1124,9 +1104,8 @@ impl, A: MapAllocator> ArrayHashMap { self.keys.remove(i); self.hashes.remove(i); // Ordered remove shifts every index ≥ i; rebuild rather than patching - // each slot. Immediate rebuild keeps subsequent lookups O(1) (Zig - // patches in place; this is the simpler-correct equivalent for the - // rare ordered path). + // each slot. Immediate rebuild keeps subsequent lookups O(1) on this + // rare path. self.drop_index(); if self.keys.len() > INDEX_THRESHOLD { self.rebuild_index(); @@ -1235,7 +1214,7 @@ impl<'a, K, V, C, A: MapAllocator> MapEntry<'a, K, V, C, A> { } impl, A: MapAllocator> ArrayHashMap { - /// Zig `getOrPut`: look up `key`; if absent, append it with a defaulted + /// Look up `key`; if absent, append it with a defaulted /// value slot and return `found_existing = false`. pub fn get_or_put(&mut self, key: K) -> Result, AllocError> { let h = self.ctx.hash(&key); @@ -1246,20 +1225,20 @@ impl, A: MapAllocator> ArrayHashMap GetOrPutResult<'_, K, V> { let h = self.ctx.hash(&key); if let Some(i) = self.find_hash(h, |k, idx| self.ctx.eql(&key, k, idx)) { return self.gop_at(i, true); } - // PERF(port): `push_within_capacity` is unstable; `push` is a no-grow + // PERF: `push_within_capacity` is unstable; `push` is a no-grow // when the prior `ensure_unused_capacity` reserved the slot. let i = self.push_entry(key, V::default(), h); self.gop_at(i, false) } - /// Zig `getOrPutValue`: like `get_or_put` but writes `value` when absent. + /// Like `get_or_put` but writes `value` when absent. pub fn get_or_put_value( &mut self, key: K, @@ -1271,8 +1250,8 @@ impl, A: MapAllocator> ArrayHashMap, A: MapAllocator> ArrayHashMap ArrayHashMap { - /// Zig `getOrPutAdapted`: look up by `key` using `adapter` for hash/eql; + /// Look up by `key` using `adapter` for hash/eql; /// on miss, append a *defaulted* `K`/`V` pair — caller fills both via /// `key_ptr` / `value_ptr`. pub fn get_or_put_adapted( @@ -1299,9 +1278,9 @@ impl ArrayHashMap { Ok(self.gop_at(i, false)) } - /// Zig `getOrPutContextAdapted`: same as `get_or_put_adapted` but takes an - /// explicit `ctx` for the *stored* key type. This port does not need `ctx` - /// for the index header (none yet), so it is accepted and ignored. + /// Same as `get_or_put_adapted` but takes an explicit `ctx` for the + /// *stored* key type. The index does not need `ctx`, so it is accepted + /// and ignored. #[inline] pub fn get_or_put_context_adapted( &mut self, @@ -1335,9 +1314,7 @@ impl ArrayHashMapExt for ArrayHashMap { /// `std.StringArrayHashMap(V)` / `bun.CaseInsensitiveASCIIStringArrayHashMap(V)`. /// /// Newtype (not an alias) so `get_or_put` / `get` / `put` can take `&[u8]` -/// borrows — the Zig API stores `[]const u8` keys and lets the caller decide -/// whether to dupe them; here keys are `Box<[u8]>` and the borrowing methods -/// box on insert. +/// borrows — keys are `Box<[u8]>` and the borrowing methods box on insert. pub struct StringArrayHashMap { inner: ArrayHashMap, V, BoxedSliceContext, A>, // The string context is consulted for hash/eql on `[u8]` borrows. The inner @@ -1346,7 +1323,7 @@ pub struct StringArrayHashMap { ctx: C, } -/// Windows env-var map (`src/bun.zig` `CaseInsensitiveASCIIStringArrayHashMap`). +/// Windows env-var map. pub type CaseInsensitiveAsciiStringArrayHashMap = StringArrayHashMap; @@ -1360,7 +1337,7 @@ impl Default for StringArrayHashMap { } impl StringArrayHashMap { - /// Zig `clone()` is fallible (OOM); kept as `Result` for API parity. + /// Fallible (OOM) clone; kept as `Result` for API stability. pub fn clone(&self) -> Result { Ok(Self { inner: self.inner.clone()?, @@ -1461,8 +1438,8 @@ impl + Default, A: MapAllocator> StringArrayHashMap true } - /// Zig: `StringArrayHashMap.fetchSwapRemove` — removes the entry (swapping - /// the last element into its slot) and returns the owned key/value pair. + /// Removes the entry (swapping the last element into its slot) and + /// returns the owned key/value pair. pub fn fetch_swap_remove(&mut self, key: &[u8]) -> Option, V>> { let i = self.find(key)?; let (k, v) = self.inner.swap_remove_at(i); @@ -1484,7 +1461,7 @@ impl + Default, A: MapAllocator> StringArrayHashMap impl + Default, A: MapAllocator> StringArrayHashMap { /// See `ArrayHashMap::get_or_put`. The key is boxed on insert; callers that /// then write `*gop.key_ptr = Box::from(key)` are doing a redundant alloc — - /// harmless, and lets the Zig-shaped call sites compile unchanged. + /// harmless. pub fn get_or_put( &mut self, key: &[u8], @@ -1527,21 +1504,21 @@ impl ArrayHashMapExt for StringArrayHashMap { // StringHashMap — unordered `[]const u8`-keyed map // ────────────────────────────────────────────────────────────────────────── -/// `std.StringHashMap(V)`. Thin newtype over `hashbrown::HashMap` that adds -/// the Zig `getOrPut` / `getOrPutValue` entry points while keeping the +/// Thin newtype over `hashbrown::HashMap` that adds +/// the `get_or_put` / `get_or_put_value` entry points while keeping the /// `hashbrown` surface (`.get`, `.contains_key`, `.reserve`, `.insert`, …) /// reachable via `Deref`. /// /// Allocator-generic so AST containers (`Scope::members` &c.) can route both /// the table *and* the owned-key boxes through `bun_alloc::AstAlloc`, -/// matching Zig's `Unmanaged` semantics where the map's backing store lives +/// so the map's backing store lives /// in the same arena as the AST nodes that hold it. The `A = Global` default /// keeps every existing `StringHashMap` site source-compatible. -// Hashed with seed-0 wyhash (matches Zig's `std.hash_map.StringContext`) — +// Hashed with seed-0 wyhash — // deterministic across runs and ~3-5× faster than `RandomState`/SipHash on // the short identifier keys the parser/printer/renamer churn. // -// The `A: Default` bound is the substitute for Zig's per-call `Allocator` +// The `A: Default` bound replaces a per-call allocator // parameter: hashbrown's `HashMap<_, _, _, A>` stores the allocator by value, // and every key `Box<[u8], A>` needs its own `A` too. For zero-sized // allocators (`Global`, `AstAlloc`) `A::default()` is a no-op constant; if a @@ -1561,14 +1538,12 @@ pub type StringHashMapInner = /// default produced by `put`/`get_or_put`) or a borrowed `&'static [u8]` /// (`Static`, produced by `put_static_key`). /// -/// Zig's `std.StringHashMap` always *borrows* the caller's `[]const u8` key — -/// the map never copies it. The Rust port originally heap-boxed every key on -/// `put` for safety, which profiling showed as the dominant cost of +/// Heap-boxing every key on `put` profiled as the dominant cost of /// `DirEntry::add_entry` (the resolver's per-file hot path): the key bytes /// there already live in the process-static `FilenameStore`/`EntryStore`, so /// the `Box<[u8]>` was a redundant second copy. The `Static` variant lets such -/// callers store the existing slice directly, matching Zig's zero-copy -/// behaviour without giving up owned-key safety for everyone else. +/// callers store the existing slice directly — zero-copy without giving up +/// owned-key safety for everyone else. /// /// `Deref` + `Borrow<[u8]>` keep `.get(&[u8])`, /// `.contains_key(&[u8])`, and `&**key` working unchanged at every call site, @@ -1578,7 +1553,7 @@ pub type StringHashMapInner = /// Packed `(ptr, len | OWNED_BIT)` instead of a 2-variant enum. The enum had /// no usable niche (both `Box<[u8]>` and `&[u8]` start with a non-null /// pointer), so it was 24 B; folding the owned/borrowed discriminant into the -/// top bit of `len` brings it to 16 B — same as Zig's `[]const u8`. For +/// top bit of `len` brings it to 16 B. For /// `Scope::members` (`hashbrown::RawTable<(StringHashMapKey, Member)>`) that /// shrinks the stored tuple 40 B → 32 B, cutting the module-scope table's /// page footprint (and `reserve_rehash` `memcpy` traffic) by ~20 %. @@ -1835,7 +1810,7 @@ impl StringHashMap self.inner.len() } - /// Zig `valueIterator()`. Inherent forwarder so callers can name + /// Inherent forwarder so callers can name /// `StringHashMap::values` without relying on `Deref` resolution. #[inline] pub fn values(&self) -> hashbrown::hash_map::Values<'_, StringHashMapKey, V> { @@ -1864,8 +1839,7 @@ impl StringHashMap } /// Insert `value` under `key` **without copying the key bytes**. This is - /// the zero-copy path that matches Zig's `StringHashMap.put` (which always - /// borrows). `key` is stored as `StringHashMapKey::Static`, so the caller + /// the zero-copy path: `key` is stored as `StringHashMapKey::Static`, so the caller /// must guarantee the bytes genuinely live for `'static` — in practice /// that means slices into a process-lifetime arena (`FilenameStore`, /// `EntryStore`, AST heap) where the `'static` was minted via an explicit @@ -1928,12 +1902,11 @@ impl StringHashMap } /// Insert `value` under `key` **without copying the key bytes** — the - /// arena-lifetime twin of [`put_static_key`]. Zig's - /// `StringHashMapUnmanaged.put` stores the caller's `[]const u8` slice by - /// value; the safe Rust [`put`] heap-boxes it instead, which profiling + /// arena-lifetime twin of [`put_static_key`]. The safe [`put`] heap-boxes + /// the key, which profiling /// flagged as the dominant `_mi_malloc_generic` caller in the parser /// (`Scope::members` takes one box per declared identifier per scope). - /// This entry point restores the Zig zero-copy behaviour for callers whose + /// This entry point provides zero-copy insertion for callers whose /// key bytes already live in an arena that outlives the map. /// /// # Safety @@ -1953,29 +1926,29 @@ impl StringHashMap } /// Insert a pre-boxed key without re-allocating it. Uses `try_reserve` so - /// OOM surfaces as `Err` instead of aborting (matches Zig `put` returning - /// `error.OutOfMemory`); callers can roll back side effects on failure. + /// OOM surfaces as `Err` instead of aborting; callers can roll back side + /// effects on failure. pub fn put_owned(&mut self, key: Box<[u8], A>, value: V) -> Result<(), AllocError> { self.inner.try_reserve(1).map_err(|_| AllocError)?; self.inner.insert(StringHashMapKey::owned(key), value); Ok(()) } - /// PERF(port): Zig skips the grow check; std::HashMap cannot, so this is + /// PERF: std::HashMap cannot skip the grow check, so this is /// just `put` without the `Result`. #[inline] pub fn put_assume_capacity(&mut self, key: &[u8], value: V) { self.inner.insert(owned_key::(key), value); } - /// Zig `putNoClobber` — asserts the key was not already present. + /// Asserts the key was not already present. pub fn put_no_clobber(&mut self, key: &[u8], value: V) -> Result<(), AllocError> { let prev = self.inner.insert(owned_key::(key), value); debug_assert!(prev.is_none(), "put_no_clobber: key already present"); Ok(()) } - /// Zig `getAdapted` — look up by `key` using `adapter` for hash/eql. + /// Look up by `key` using `adapter` for hash/eql. /// /// The adapter's precomputed hash is ignored; the lookup falls back to the /// normal `get(key)` path (correctness is preserved — `adapter.eql` is byte @@ -2001,7 +1974,7 @@ impl StringHashMap pub use crate::hash_map::GetOrPutResult as StringHashMapGetOrPut; impl StringHashMap { - /// PERF(port): the previous shape (`contains_key` + `entry(Box::from(key))`) + /// PERF: the previous shape (`contains_key` + `entry(Box::from(key))`) /// hashed `key` twice and unconditionally heap-allocated the `Box` even on /// hit. `Scope::members` calls this once per declared identifier during /// parse, so on three.js that was ~thousands of redundant `Box` @@ -2019,7 +1992,7 @@ impl StringHash Ok(self.inner.entry(owned_key::(key)).or_insert(value)) } - /// Zig `getOrPutContextAdapted` on `StringHashMap` — see `get_adapted` for + /// See `get_adapted` for /// why the adapter's precomputed hash is currently ignored. pub fn get_or_put_context_adapted( &mut self, @@ -2073,14 +2046,14 @@ impl StringHash } // ────────────────────────────────────────────────────────────────────────── -// StringHashMapContext + Prehashed adapters (src/bun.zig) +// StringHashMapContext + Prehashed adapters // ────────────────────────────────────────────────────────────────────────── /// `bun.StringHashMapContext` — wyhash(seed=0) over byte slices, full 64-bit. /// This is the *unordered* map context (vs. `StringContext` above which /// truncates to u32 for `ArrayHashMap`). /// -/// PORT NOTE: spelled as a module rather than a unit struct so callers can +/// Spelled as a module rather than a unit struct so callers can /// path-access the nested `Prehashed` / `PrehashedCaseInsensitive` types /// (`StringHashMapContext::Prehashed::…`) on stable Rust, which forbids /// inherent associated types. @@ -2103,10 +2076,10 @@ pub mod StringHashMapContext { pub use super::string_hash_map::{Prehashed, PrehashedCaseInsensitive, hash}; } -/// Namespace mirroring `std.hash_map` so call sites can write +/// String-hash helpers, namespaced so call sites can write /// `bun_collections::string_hash_map::{hash, Prehashed, GetOrPutResult}`. pub mod string_hash_map { - /// `std.hash_map.hashString` — wyhash(seed=0), full u64. + /// wyhash(seed=0), full u64. #[inline] pub fn hash(s: &[u8]) -> u64 { bun_wyhash::hash(s) @@ -2177,7 +2150,7 @@ pub mod string_hash_map { } // ────────────────────────────────────────────────────────────────────────── -// StringSet (src/bun.zig) — `StringArrayHashMap<()>` with key-duping insert +// StringSet — `StringArrayHashMap<()>` with key-duping insert // ────────────────────────────────────────────────────────────────────────── /// `bun.StringSet` — insertion-ordered set of owned byte-string keys. @@ -2192,7 +2165,7 @@ impl StringSet { Self::default() } - /// Zig `init(allocator)` — allocator dropped (global mimalloc). + /// Alias for `new()` (the global allocator is implicit). #[inline] pub fn init() -> Self { Self::default() @@ -2220,10 +2193,9 @@ impl StringSet { } /// Insert `key`, duping it on miss. Returns `Ok(())` whether or not the key - /// was already present (Zig signature). + /// was already present. pub fn insert(&mut self, key: &[u8]) -> Result<(), AllocError> { - // get_or_put already boxes `key` on miss; the Zig second-dupe is - // redundant under owned `Box<[u8]>` keys. + // get_or_put already boxes `key` on miss. let _ = self.map.get_or_put(key)?; Ok(()) } @@ -2241,7 +2213,7 @@ impl StringSet { pub fn clear_and_free(&mut self) { // Keys are `Box<[u8]>`; `clear` drops them. self.map.clear_retaining_capacity(); - // PORT NOTE: Zig also freed the backing arrays; Vec keeps capacity here + // This does not free the backing arrays; Vec keeps capacity here // (callers wanting that can drop the whole `StringSet`). } @@ -2249,13 +2221,13 @@ impl StringSet { } // ────────────────────────────────────────────────────────────────────────── -// StringHashMapUnowned (src/bun.zig) — pre-hashed string key +// StringHashMapUnowned — pre-hashed string key // ────────────────────────────────────────────────────────────────────────── /// `bun.StringHashMapUnowned.Key` — a string identity reduced to `(hash, len)` /// so the map never stores the string bytes. Collisions on both fields are -/// treated as equal (matches the Zig — used for side-effects globs where a -/// false positive is acceptable). +/// treated as equal (used for side-effects globs where a false positive is +/// acceptable). #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct StringHashMapUnownedKey { pub hash: u64, @@ -2276,8 +2248,7 @@ impl StringHashMapUnownedKey { pub mod string_hash_map_unowned { pub use super::StringHashMapUnownedKey as Key; - /// Adapter feeding `Key.hash` straight through (Zig - /// `bun.StringHashMapUnowned.Adapter`). + /// Adapter feeding `Key.hash` straight through. #[derive(Default, Clone, Copy)] pub struct Adapter; @@ -2293,8 +2264,6 @@ pub mod string_hash_map_unowned { } } -// ported from: vendor/zig/lib/std/array_hash_map.zig - #[cfg(test)] mod index_tests { use super::*; diff --git a/src/collections/array_list.rs b/src/collections/array_list.rs index 4446c5afa8d..510ddce2f96 100644 --- a/src/collections/array_list.rs +++ b/src/collections/array_list.rs @@ -1,16 +1,5 @@ #![forbid(unsafe_code)] //! Managed `ArrayList` wrappers. -//! -//! PORT NOTE: The Zig original wraps `std.ArrayListAlignedUnmanaged` to add two things: -//! 1. A stored allocator (managed vs unmanaged split). -//! 2. "Deep" semantics — `deinit`/`clear`/`shrink`/`replaceRange` call `deinit` on each -//! removed item, with `*Shallow` variants that skip that. -//! -//! In Rust, (1) disappears entirely — `Vec` uses the global mimalloc allocator and the -//! `Allocator` type parameter is dropped per §Allocators in PORTING.md. (2) is the *default* -//! behavior of `Vec`: removing/dropping elements runs their `Drop`. So the "deep" methods -//! map to ordinary `Vec` operations; the `*Shallow` variants had no in-tree callers and are -//! not ported. use core::mem; @@ -20,66 +9,40 @@ use super::vec_ext::VecExt; /// Managed `ArrayList` using an arbitrary allocator. /// Prefer using a concrete type, like `ArrayListDefault`. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// PORT NOTE: `std.mem.Allocator` type param dropped — global mimalloc (non-AST crate). pub type ArrayList = ArrayListAlignedIn; /// Managed `ArrayList` using the default allocator. No overhead compared to an unmanaged /// `ArrayList`. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// PORT NOTE: `bun.DefaultAllocator` type param dropped — global mimalloc. pub type ArrayListDefault = ArrayListAlignedIn; /// Managed `ArrayList` using a specific kind of allocator. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// PORT NOTE: `Allocator` type param dropped — global mimalloc. pub type ArrayListIn = ArrayListAlignedIn; /// Managed `ArrayListAligned` using an arbitrary allocator. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// TODO(port): const-generic alignment param. Rust `Vec` uses `align_of::()` and has no -// over-alignment knob; if any caller passes a non-null `alignment`, that call site needs a -// `#[repr(align(N))]` newtype wrapper around `T` instead. +// Rust `Vec` uses `align_of::()` and has no over-alignment knob. A call site that needs +// over-alignment must wrap `T` in a `#[repr(align(N))]` newtype instead. pub type ArrayListAligned = ArrayListAlignedIn; /// Managed `ArrayListAligned` using the default allocator. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. pub type ArrayListAlignedDefault = ArrayListAlignedIn; /// Managed `ArrayListAligned` using a specific kind of allocator. /// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// PORT NOTE: Zig's `fn(...) type` factory → generic struct (PORTING.md §Idiom map). -// Allocator type param dropped; alignment param dropped (see ArrayListAligned TODO above). +/// NOTE: dropping this type (and the aliases above) runs `Drop` on each of the items. #[derive(Default)] pub struct ArrayListAlignedIn { - /// Zig: `#unmanaged: Unmanaged = .empty` unmanaged: Unmanaged, - // Zig: `#std.mem.Allocator param` — dropped (global mimalloc). } -/// Zig: `Unmanaged = std.ArrayListAlignedUnmanaged(T, alignment)` pub(crate) type Unmanaged = Vec; -/// Zig: `Slice = Unmanaged.Slice` (= `[]align(alignment) T`, an owned slice when detached). -// TODO(port): Zig `Slice` is used both as a borrow (`items()`) and as an owned return -// (`toOwnedSlice`). Rust splits these: borrows are `&[T]`/`&mut [T]`, owned is `Box<[T]>`. pub type Slice = Box<[T]>; -// TODO(port): `SentinelSlice` — sentinel-terminated slices have no std Rust equivalent; only -// needed if a caller uses `fromOwnedSliceSentinel`. Map to `bun_core::ZStr`/`WStr` at call site. - impl ArrayListAlignedIn { pub fn items(&self) -> &[T] { self.unmanaged.as_slice() } - // PORT NOTE: Zig `items()` returns a mutable `[]T`; Rust splits const/mut borrows. pub fn items_mut(&mut self) -> &mut [T] { self.unmanaged.as_mut_slice() } @@ -89,7 +52,6 @@ impl ArrayListAlignedIn { } pub fn init() -> Self { - // Zig: `.initIn(bun.memory.initDefault(Allocator))` — allocator dropped. Self::init_in() } @@ -104,47 +66,34 @@ impl ArrayListAlignedIn { } pub fn init_capacity_in(num: usize /* allocator dropped */) -> Result { - // Zig: `try .initCapacity(bun.allocators.asStd(allocator_), num)` - // PERF(port): Vec::with_capacity aborts on OOM rather than returning Err. Could swap to + // Vec::with_capacity aborts on OOM rather than returning Err. Could swap to // `Vec::try_with_capacity` (nightly) or a fallible wrapper if OOM recovery matters. Ok(Self { unmanaged: Vec::with_capacity(num), }) } - // Zig `pub fn deinit` → `impl Drop` (see below). Body only deinits items + frees backing, - // both of which `Vec`'s `Drop` already does, so no explicit `Drop` impl is needed. - pub fn from_owned_slice(slice: Slice) -> Self { Self { unmanaged: Vec::from(slice), } } - // TODO(port): `from_owned_slice_sentinel` — sentinel-terminated owned slices are not a Rust - // type. If needed, accept `Box<[T]>` with the sentinel already stripped, or `ZStr`/`WStr`. + // Sentinel-terminated owned slices are not a Rust type, so this takes a `Box<[T]>` with + // the sentinel already stripped. pub fn from_owned_slice_sentinel(/* sentinel: T, */ slice: Slice) -> Self { Self { unmanaged: Vec::from(slice), } } - // TODO(port): `writer()` — Zig returns an `std.io.Writer` that appends bytes. For `T = u8` - // this is `impl std::io::Write for Vec` (already in std). For other `T` there is no - // meaningful writer. TODO(port): expose only on `ArrayListAlignedIn`. - pub fn writer(&mut self) -> &mut Vec { - &mut self.unmanaged - } - /// This method empties `self`. pub fn move_to_unmanaged(&mut self) -> Unmanaged { - // Zig: `defer self.#unmanaged = .empty; return self.#unmanaged;` mem::take(&mut self.unmanaged) } /// Unlike `move_to_unmanaged`, this method *consumes* `self`. pub fn into_unmanaged_with_allocator(self) -> (Unmanaged, ()) { - // Zig: returns `(Unmanaged, Allocator)`; allocator dropped → unit. (self.unmanaged, ()) } @@ -155,15 +104,10 @@ impl ArrayListAlignedIn { } pub fn to_owned_slice(self) -> Result, AllocError> { - // Zig: `self.#unmanaged.toOwnedSlice(...)` — shrinks cap→len then returns the slice. Ok(self.unmanaged.into_boxed_slice()) } /// Creates a copy of this `ArrayList` with copies of its items. - /// - /// PORT NOTE: Zig makes *bitwise* (shallow) copies regardless of whether `T` has a - /// `deinit`. Rust cannot bit-copy a non-`Copy` `T` safely. This is bound on `T: Clone`; - /// callers that relied on shallow-copy-then-`deinitShallow` need a redesign. pub fn clone(&self) -> Result where T: Clone, @@ -187,7 +131,6 @@ impl ArrayListAlignedIn { } pub fn insert_assume_capacity(&mut self, i: usize, item: T) { - // PERF(port): was assume_capacity — profile if hot. self.unmanaged.insert(i, item); } @@ -201,7 +144,6 @@ impl ArrayListAlignedIn { where T: Clone, { - // Zig: `addManyAt` reserves `count` uninit slots at `index`, then `@memset(result, value)`. self.unmanaged .splice(index..index, core::iter::repeat_n(value, count)); Ok(&mut self.unmanaged[index..index + count]) @@ -212,27 +154,23 @@ impl ArrayListAlignedIn { where T: Clone, { - // PERF(port): was assume_capacity — profile if hot. self.unmanaged .splice(index..index, core::iter::repeat_n(value, count)); &mut self.unmanaged[index..index + count] } - /// This method takes ownership of all elements in `new_items`. + /// Note that this `Clone`s each element of `new_items`. pub fn insert_slice(&mut self, index: usize, new_items: &[T]) -> Result<(), AllocError> where T: Clone, { - // TODO(port): Zig takes `[]const T` and bit-copies, transferring ownership. Rust must - // `Clone` from a borrowed slice. If callers own the data, change signature to - // `impl IntoIterator` to avoid the clone. self.unmanaged .splice(index..index, new_items.iter().cloned()); Ok(()) } /// This method `Drop`s the removed items. - /// This method takes ownership of all elements in `new_items`. + /// Note that this `Clone`s each element of `new_items` (see `insert_slice`). pub fn replace_range( &mut self, start: usize, @@ -242,20 +180,18 @@ impl ArrayListAlignedIn { where T: Clone, { - // PORT NOTE: Zig deinits `items[start..start+len]` then calls the shallow path. - // `Vec::splice` already drops the removed range, so deep == direct splice. + // `Vec::splice` drops the removed range. self.unmanaged .splice(start..start + len, new_items.iter().cloned()); Ok(()) } /// This method `Drop`s the removed items. - /// This method takes ownership of all elements in `new_items`. + /// Note that this `Clone`s each element of `new_items` (see `insert_slice`). pub fn replace_range_assume_capacity(&mut self, start: usize, len: usize, new_items: &[T]) where T: Clone, { - // PERF(port): was assume_capacity — profile if hot. let _ = self.replace_range(start, len, new_items); } @@ -265,7 +201,6 @@ impl ArrayListAlignedIn { } pub fn append_assume_capacity(&mut self, item: T) { - // PERF(port): was assume_capacity — profile if hot. self.unmanaged.push(item); } @@ -277,43 +212,39 @@ impl ArrayListAlignedIn { self.unmanaged.swap_remove(i) } - /// This method takes ownership of all elements in `new_items`. + /// Note that this `Clone`s each element of `new_items` (see `insert_slice`). pub fn append_slice(&mut self, new_items: &[T]) -> Result<(), AllocError> where T: Clone, { - // TODO(port): see `insert_slice` note re: Clone vs ownership transfer. self.unmanaged.extend_from_slice(new_items); Ok(()) } - /// This method takes ownership of all elements in `new_items`. + /// Note that this `Clone`s each element of `new_items` (see `insert_slice`). pub fn append_slice_assume_capacity(&mut self, new_items: &[T]) where T: Clone, { - // PERF(port): was assume_capacity — profile if hot. self.unmanaged.extend_from_slice(new_items); } - /// This method takes ownership of all elements in `new_items`. + /// Note that this `Clone`s each element of `new_items` (see `insert_slice`). pub fn append_unaligned_slice(&mut self, new_items: &[T]) -> Result<(), AllocError> where T: Clone, { - // TODO(port): Zig `[]align(1) const T` allows reading T from an under-aligned address. - // Rust `&[T]` is always naturally aligned. If a caller truly has unaligned bytes, it - // needs `ptr::read_unaligned` at the call site. Treat as aligned here. + // Rust `&[T]` is always naturally aligned, so this is identical to `append_slice`; a + // caller that truly has unaligned bytes needs `ptr::read_unaligned` at the call site. self.unmanaged.extend_from_slice(new_items); Ok(()) } - /// This method takes ownership of all elements in `new_items`. + /// Note that this `Clone`s each element of `new_items` (see `insert_slice`). pub fn append_unaligned_slice_assume_capacity(&mut self, new_items: &[T]) where T: Clone, { - // PERF(port): was assume_capacity — profile if hot. self.unmanaged.extend_from_slice(new_items); } @@ -333,7 +264,6 @@ impl ArrayListAlignedIn { where T: Clone, { - // PERF(port): was assume_capacity — profile if hot. self.unmanaged.extend(core::iter::repeat_n(value, n)); } @@ -345,10 +275,6 @@ impl ArrayListAlignedIn { where T: Clone, { - // PORT NOTE: Zig calls `resizeWithoutDeinit` first, *then* deinits the tail via a raw - // pointer past `len` (`items().ptr[new_len..len]`). That ordering is to avoid a failed - // realloc leaving already-deinited items in the list. `Vec::resize` already drops the - // truncated tail in the shrink case and never fails, so the ordering concern vanishes. self.unmanaged.resize(new_len, init_value); Ok(()) } @@ -356,7 +282,7 @@ impl ArrayListAlignedIn { /// This method `Drop`s the removed items. pub fn shrink_and_free(&mut self, new_len: usize) { self.prepare_for_deep_shrink(new_len); - // PORT NOTE: `prepare_for_deep_shrink` already truncated (dropping items); now free. + // `prepare_for_deep_shrink` already truncated (dropping items); now free. self.unmanaged.shrink_to_fit(); } @@ -368,14 +294,12 @@ impl ArrayListAlignedIn { /// This method `Drop`s all items. pub fn clear_retaining_capacity(&mut self) { - // Zig: `bun.memory.deinit(self.items()); self.clearRetainingCapacityShallow();` - // `Vec::clear` drops all items and retains capacity — exactly the deep semantics. + // `Vec::clear` drops all items and retains capacity. self.unmanaged.clear(); } /// This method `Drop`s all items. pub fn clear_and_free(&mut self) { - // Zig: `bun.memory.deinit(self.items()); self.clearAndFreeShallow();` self.unmanaged = Vec::new(); } @@ -401,7 +325,6 @@ impl ArrayListAlignedIn { { let len = self.unmanaged.len(); let cap = self.unmanaged.capacity(); - // Zig: `self.#unmanaged.expandToCapacity(); @memset(self.items()[len..], init_value);` self.unmanaged .extend(core::iter::repeat_n(init_value, cap - len)); debug_assert_eq!(self.unmanaged.len(), cap); @@ -412,13 +335,11 @@ impl ArrayListAlignedIn { } pub fn get_last(&self) -> &T { - // Zig: `&items_[items_.len - 1]` — panics on empty, same as `[len-1]` here. + // Panics on empty. let items = self.items(); &items[items.len() - 1] } - // PORT NOTE: Zig returns `*T` (mutable) from a `*const Self` receiver via interior aliasing. - // Rust splits this into `&T` / `&mut T` accessors. pub fn get_last_mut(&mut self) -> &mut T { let len = self.unmanaged.len(); &mut self.unmanaged[len - 1] @@ -442,18 +363,15 @@ impl ArrayListAlignedIn { new_len <= items_len, "new_len ({new_len}) cannot exceed current len ({items_len})", ); - // Zig: `bun.memory.deinit(items_[new_len..])` — drop the tail in place. - // `Vec::truncate` does exactly that and keeps capacity. + // `Vec::truncate` drops the tail in place and keeps capacity. self.unmanaged.truncate(new_len); } - - // Zig `getStdAllocator` — dropped (no allocator field). } -// PORT NOTE: Zig `pub fn deinit` → `impl Drop`. The Zig body is -// `bun.memory.deinit(self.items()); self.deinitShallow();` -// i.e. drop every item, then free the backing buffer. `Vec`'s own `Drop` does both, so per -// PORTING.md ("If the body only frees/deinits owned fields, delete the body entirely") no -// explicit `impl Drop for ArrayListAlignedIn` is written. - -// ported from: src/collections/array_list.zig +impl ArrayListAlignedIn { + /// Hands out the backing `Vec`, which already implements `std::io::Write`. + /// Only exposed for `T = u8` — there is no meaningful writer for other element types. + pub fn writer(&mut self) -> &mut Vec { + &mut self.unmanaged + } +} diff --git a/src/collections/bit_set.rs b/src/collections/bit_set.rs index fb2015c0a2f..c1b843e5139 100644 --- a/src/collections/bit_set.rs +++ b/src/collections/bit_set.rs @@ -1,8 +1,3 @@ -//! This is a fork of Zig standard library bit_set.zig -//! - https://github.com/ziglang/zig/pull/14129 -//! - AutoBitset which optimally chooses between a dynamic or static bitset. -//! Prefer our fork over std.bit_set. -//! //! This file defines several variants of bit sets. A bit set //! is a densely stored set of integers with a known maximum, //! in which each integer gets a single bit. Bit sets have very @@ -43,22 +38,21 @@ use bun_alloc::AllocError; // ───────────────────────────── helpers ───────────────────────────── -/// Equivalent to `std.math.boolMask(MaskInt, value)`: returns `~0` if `value` -/// else `0`, in the requested integer width. +/// Returns `usize::MAX` if `value`, else `0`. #[inline(always)] const fn bool_mask_usize(value: bool) -> usize { if value { usize::MAX } else { 0 } } /// `1 << (index % usize::BITS)` — selects the bit within a `usize` word. -/// Shared by `ArrayBitSet` and `DynamicBitSetUnmanaged` (Zig: `maskBit`). +/// Shared by `ArrayBitSet` and `DynamicBitSetUnmanaged`. #[inline(always)] const fn word_mask_bit(index: usize) -> usize { 1usize << ((index as u32) & (usize::BITS - 1)) // @truncate } /// `index / usize::BITS` — selects which `usize` word holds the bit. -/// Shared by `ArrayBitSet` and `DynamicBitSetUnmanaged` (Zig: `maskIndex`). +/// Shared by `ArrayBitSet` and `DynamicBitSetUnmanaged`. #[inline(always)] const fn word_mask_index(index: usize) -> usize { index >> usize::BITS.trailing_zeros() @@ -66,7 +60,7 @@ const fn word_mask_index(index: usize) -> usize { /// Shared multi-mask implementation of `set_range_value` over `&mut [usize]` /// storage. Used by both `ArrayBitSet` and `DynamicBitSetUnmanaged` so the -/// per-word range masking logic lives in one place (Zig: `setRangeValue`). +/// per-word range masking logic lives in one place. #[inline] fn set_range_value_masks(masks: &mut [usize], range: Range, value: bool) { const MASK_LEN: u32 = usize::BITS; @@ -117,13 +111,11 @@ fn set_range_value_masks(masks: &mut [usize], range: Range, value: bool) { /// can be copied by value, and does not require deinitialization. /// Both possible implementations fulfill the same interface. /// -// TODO(port): Zig's `StaticBitSet(size)` returns `IntegerBitSet(size)` when -// `size <= @bitSizeOf(usize)` and `ArrayBitSet(usize, size)` otherwise. Stable -// Rust cannot select a struct definition from a const generic. Callers should -// pick `IntegerBitSet` or `ArrayBitSet` directly; this alias resolves to -// the array form (always correct, possibly one word larger than needed for -// N <= 64). -pub type StaticBitSet = IntegerBitSet; // TODO(b2): callers needing >64 bits use ArrayBitSet directly +// Stable Rust cannot select a struct definition from a const generic, so this +// alias is the integer form and is only valid for `SIZE <= usize::BITS` +// (enforced by `IntegerBitSet`'s debug asserts). Callers needing more bits +// must use `ArrayBitSet` directly. +pub type StaticBitSet = IntegerBitSet; // ───────────────────────────── IntegerBitSet ───────────────────────────── @@ -131,10 +123,8 @@ pub type StaticBitSet = IntegerBitSet; // TODO(b2): cal /// This set is good for sets with a small size, but may generate /// inefficient code for larger sets, especially in debug mode. /// -// TODO(port): Zig uses `std.meta.Int(.unsigned, size)` for an exact-width -// backing integer (u0..u65535). Rust has no arbitrary-width ints; we back with -// `usize` and rely on `SIZE <= usize::BITS`. Phase B may swap to a trait that -// picks u8/u16/u32/u64/u128. +// Backed by `usize`; requires `SIZE <= usize::BITS` (misuse surfaces via +// `FULL_MASK` saturation + debug asserts). #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct IntegerBitSet { @@ -220,12 +210,10 @@ impl IntegerBitSet { let mut mask = bool_mask_usize(true) << start_bit; if range.end != Self::BIT_LENGTH { let end_bit = u32::try_from(range.end).expect("int cast"); - // Zig shifts a SIZE-bit MaskInt so `~0 >> (SIZE - end_bit)` yields the - // low `end_bit` bits. With a usize backing the shift must be relative - // to usize::BITS to get the same low-`end_bit`-bits mask. + // `~0 >> (usize::BITS - end_bit)` yields the low `end_bit` bits. mask &= bool_mask_usize(true) >> (usize::BITS - end_bit); } - // also clear bits above SIZE since our backing int is wider than Zig's + // also clear bits above SIZE since the backing `usize` may be wider than SIZE bits mask &= Self::FULL_MASK; self.mask &= !mask; @@ -390,7 +378,7 @@ impl IntegerBitSet { } /// Iterate indices of set bits in ascending order. - /// Convenience wrapper for `iterator::()` (Zig's `.iterator(.{ .kind = .set })`). + /// Convenience wrapper for `iterator::()`. #[inline] pub fn iter_set(self) -> SingleWordIterator { self.iterator::() @@ -443,9 +431,6 @@ pub const fn num_masks_for(bit_length: usize) -> usize { /// A bit set with static size, which is backed by an array of usize. /// This set is good for sets with a larger size, but may use /// more bytes than necessary if your set is small. -/// -// TODO(port): Zig is generic over `MaskIntType`; every in-tree caller uses -// `usize`. Dropped the type parameter. Phase B can re-generify if needed. #[repr(C)] #[derive(Clone, Copy)] pub struct ArrayBitSet { @@ -760,12 +745,11 @@ impl ArrayBitSet { /// A bit set with runtime-known size, backed by an allocated slice /// of usize. The allocator must be tracked externally by the user. /// -// TODO(port): the Zig type stores `masks: [*]MaskInt` where `masks[-1]` holds -// the true allocation length (needed because Zig's allocator API requires the -// original length on free). The Rust port keeps the same layout because -// `List` constructs borrowed views into a shared buffer that must look like -// freestanding `DynamicBitSetUnmanaged`s. Phase B may refactor to `Vec` -// once `List` is reworked. +// Layout invariant: `masks` is a raw pointer where `masks[-1]` holds the true +// allocation length (needed on free). This layout is load-bearing because +// `DynamicBitSetList` constructs borrowed views into a shared buffer that must +// look like freestanding `DynamicBitSetUnmanaged`s — do not swap the storage +// for `Vec` without reworking `DynamicBitSetList`. pub struct DynamicBitSetUnmanaged { /// The number of valid items in this bit set pub bit_length: usize, @@ -775,14 +759,12 @@ pub struct DynamicBitSetUnmanaged { pub masks: *mut usize, // This pointer is one usize after the actual allocation. // That slot holds the size of the true allocation, which - // is needed by Zig's allocator interface in case a shrink - // fails. + // is needed when freeing. } const DYN_MASK_BITS: u32 = usize::BITS; -// Never modified — the Zig comment about needing `static mut` was a Zig -// limitation (no const-ptr → mut-ptr cast at comptime). All writes through +// Never modified. All writes through // `self.masks` are guarded by `num_masks() > 0`, which is false for the empty // sentinel (bit_length == 0). Kept in a `RacyCell` (not `.rodata`) so that // forming a `*mut usize` to it remains a legally-mutable pointer target — @@ -814,10 +796,8 @@ impl Drop for DynamicBitSetUnmanaged { } impl DynamicBitSetUnmanaged { - pub const EMPTY: fn() -> Self = Self::default; - // TODO(port): Zig has `pub const empty: Self = .{ ... }` as a const value. - // Rust can't const-init a static-mut-derived pointer; callers should use - // `DynamicBitSetUnmanaged::default()`. + // There is no `const` empty value (the empty sentinel pointer is computed at + // runtime); use `Self::default()`. /// Borrow the mask words as a shared slice of length `num_masks(bit_length)`. #[inline(always)] @@ -1381,9 +1361,8 @@ impl Drop for DynamicBitSetList { // the owning struct between threads is as safe as moving a `Box<[usize]>`. unsafe impl Send for DynamicBitSetList {} -// Raw allocation helpers for DynamicBitSetUnmanaged. These mirror Zig's -// allocator.alloc/realloc/free with the size-at-[-1] header convention. -// TODO(port): move to bun_alloc if useful elsewhere. +// Raw allocation helpers for DynamicBitSetUnmanaged, using the size-at-[-1] +// header convention. unsafe fn dyn_free(base: *mut usize, len: usize) { if len == 0 { @@ -1421,7 +1400,7 @@ unsafe fn dyn_realloc( // ───────────────────────────── AutoBitSet ───────────────────────────── -/// Static arm size: `@bitSizeOf(DynamicBitSetUnmanaged) - 1`. +/// Static arm size: one less than the bit-size of `DynamicBitSetUnmanaged`. pub(crate) const AUTO_STATIC_BITS: usize = mem::size_of::() * 8 - 1; pub(crate) type AutoBitSetStatic = @@ -1433,11 +1412,9 @@ pub enum AutoBitSet { } // ─── two-arm forward helper ──────────────────────────────────────────── -// Zig had `switch (this.*) { inline else => |*b| b.method() }` for the -// symmetric arms (setAll/count/findFirstSet/Iterator.next). The Rust port -// regressed those to open-coded matches; this macro restores the collapse -// and is applied to every method whose Static/Dynamic arms are textually -// identical. Asymmetric arms (clone, raw_bytes, has_intersection, Drop) +// This macro forwards a call to whichever arm is active and is applied to +// every method whose Static/Dynamic arms are textually identical. +// Asymmetric arms (clone, raw_bytes, has_intersection, Drop) // stay open-coded — they genuinely differ. macro_rules! auto_forward { ($self:expr, |$b:ident| $body:expr) => { @@ -1504,7 +1481,6 @@ impl AutoBitSet { } pub fn eql(&self, b: &AutoBitSet) -> bool { - // TODO(b0): `strings` arrives in bun_core via move-in (was bun_core::strings). self.raw_bytes() == b.raw_bytes() } @@ -1559,10 +1535,6 @@ impl Drop for AutoBitSet { /// A bit set with runtime-known size, backed by an allocated slice /// of usize. Thin wrapper around DynamicBitSetUnmanaged which keeps /// track of the allocator instance. -/// -// TODO(port): in Rust the managed/unmanaged split disappears (global -// allocator). This wrapper is kept for diff parity; Phase B may collapse it -// into `DynamicBitSetUnmanaged` and re-export under both names. #[derive(Default)] pub struct DynamicBitSet { /// The number of valid items in this bit set @@ -1609,7 +1581,7 @@ impl DynamicBitSet { self.unmanaged.capacity() } - /// Zig spelling of `capacity()` (`.bit_length`). + /// Alias for `capacity()`. #[inline(always)] pub fn bit_length(&self) -> usize { self.unmanaged.capacity() @@ -1761,9 +1733,7 @@ pub enum IteratorDirection { // ───────────────────────────── BitSetIterator ───────────────────────────── -// The iterator is reusable between several bit set types -// TODO(port): Zig is generic over `MaskInt`; fixed to `usize` here since every -// in-tree caller uses `usize`. +// The iterator is reusable between several bit set types. pub struct BitSetIterator<'a, const KIND_SET: bool, const DIR_FWD: bool> { // all bits which have not yet been iterated over bits_remain: usize, @@ -1863,11 +1833,3 @@ pub struct Range { /// The index immediately after the last bit of interest. pub end: usize, } - -// ───────────────────────────── Tests ───────────────────────────── - -// TODO(port): the Zig source's test helpers (`testEql`, `testBitSet`, …) are -// dead code carried from the std fork — no `test` block invokes them. They -// rely on `anytype` duck-typing; re-author against a trait when adding tests. - -// ported from: src/collections/bit_set.zig diff --git a/src/collections/comptime_string_map.rs b/src/collections/comptime_string_map.rs index 59db5df0222..a2d68d57e4a 100644 --- a/src/collections/comptime_string_map.rs +++ b/src/collections/comptime_string_map.rs @@ -1,22 +1,16 @@ //! Comptime string map optimized for small sets of disparate string keys. -//! Works by separating the keys by length at comptime and only checking strings of +//! Works by separating the keys by length up front and only checking strings of //! equal length at runtime. //! //! `kvs` expects a list literal containing list literals or an array/slice of structs //! where `.0` is the `&[u8]` key and `.1` is the associated value of type `V`. -// TODO: https://github.com/ziglang/zig/issues/4335 - -// PORT NOTE: The Zig original is a `fn(comptime KeyType, comptime V, comptime kvs_list) type` -// that does heavy comptime work: sorts kvs by (len, bytes), builds a `len_indexes` table, -// then every lookup is an `inline while` over lengths × `inline for` over same-length keys -// with `eqlComptime` (length-known SIMD compare). Rust cannot replicate the per-callsite -// monomorphization without a proc-macro, so this models it as a const-constructed struct -// holding the precomputed tables plus a `comptime_string_map!` macro that callers use. -// Runtime loops replace `inline for`/`inline while`; each is tagged `PERF(port)`. + +// A struct holding precomputed tables (kvs sorted by (len, bytes), plus a `len_indexes` +// table), built via the `comptime_string_map!` macro that callers use. // -// Per PORTING.md crate-map, downstream callers of `bun.ComptimeStringMap(V, .{...})` should -// prefer `phf::phf_map!` directly when they only need `.get()`/`.has()`. This struct exists -// for the call sites that need `get_with_eql` / `get_any_case` / `index_of` / `get_key`. +// Downstream callers should prefer `phf::phf_map!` directly when they only need +// `.get()`/`.has()`. This struct exists for the call sites that need `get_with_eql` / +// `get_any_case` / `index_of` / `get_key`. use bun_core::strings; @@ -35,8 +29,7 @@ pub struct ComptimeStringMapWithKeyType< const N: usize, const LEN_TABLE: usize, > { - // PORT NOTE: in Zig these were `precomputed.{min_len,max_len,sorted_kvs,len_indexes}` - // computed in a `comptime blk:`. Here they are filled by the constructor macro. + // Filled by the constructor macro. min_len: usize, max_len: usize, /// Sorted ascending by (key.len, key bytes). @@ -48,8 +41,7 @@ pub struct ComptimeStringMapWithKeyType< pub type ComptimeStringMap = ComptimeStringMapWithKeyType; -/// Trait abstracting "has a length" for `get_with_eql` inputs — Zig used -/// `if (@hasField(Input, "len")) input.len else input.length()`. +/// Trait abstracting "has a length" for `get_with_eql` inputs. pub trait HasLength { fn length(&self) -> usize; } @@ -65,7 +57,6 @@ impl HasLength for &[T] { (*self).len() } } -// TODO(port): `String` arrives in bun_alloc via move-in (was bun_core::String — same-tier cycle). impl HasLength for &bun_alloc::String { #[inline] fn length(&self) -> usize { @@ -73,9 +64,6 @@ impl HasLength for &bun_alloc::String { } } -// PORT NOTE: `pub const Value = V;` (inherent assoc type) is nightly-only; -// callers can write `V` directly. - impl ComptimeStringMapWithKeyType where K: Copy + Eq + Ord + 'static, @@ -83,10 +71,8 @@ where { /// Builds the precomputed tables. Called by the `comptime_string_map!` macro. /// - /// Mirrors the `comptime blk:` in the Zig: sort by (len asc, bytes asc), then - /// fill `len_indexes[len]` = first index whose key.len >= len. - // TODO(port): make this a `const fn` once const-sort is stable, or move to build.rs. - // PERF(port): Zig did this at comptime (zero runtime cost); this runs once at init. + /// Sorts by (len asc, bytes asc), then fills `len_indexes[len]` = first index whose + /// key.len >= len. pub fn new(mut sorted_kvs: [KV; N]) -> Self { // lenAsc comparator sorted_kvs.sort_by(|a, b| { @@ -136,10 +122,6 @@ where } /// Contiguous range in `kvs` whose keys have exactly `len`. - /// - /// PORT NOTE: the .zig spec open-coded this at every lookup site because `len` was - /// `comptime` there and each needed its own `comptime brk:` block. In the Rust port - /// `len` is runtime, so the duplication is vestigial — extract once and inline. #[inline(always)] fn len_bucket(&self, len: usize) -> core::ops::Range { let start = self.len_indexes[len]; @@ -150,18 +132,15 @@ where start..end } - // PORT NOTE: `comptime len: usize` → runtime `len: usize`. The Zig used the comptime - // value to compute `end` at comptime and `inline for` the range; we loop at runtime. - // PERF(port): was comptime monomorphization — profile if hot. pub fn get_with_length(&self, str: &[K], len: usize) -> Option { let core::ops::Range { start, end } = self.len_bucket(len); // This benchmarked faster for both small and large lists of strings than using a big switch statement // But only so long as the keys are a sorted list. for i in start..end { - // PERF(port): Zig used `strings.eqlComptimeCheckLenWithType(K, str, kvs[i].key, false)` - // (length-known SIMD compare). Plain slice == here; could swap to - // `bun_core::strings::eql_comptime_check_len_with_type` if hot. + // PERF: plain slice == here; could swap to + // `bun_core::strings::eql_comptime_check_len_with_type` + // (length-known SIMD compare) if hot. if str == self.kvs[i].key { return Some(self.kvs[i].value); } @@ -183,7 +162,6 @@ where // This benchmarked faster for both small and large lists of strings than using a big switch statement // But only so long as the keys are a sorted list. - // PERF(port): was `inline for` — profile if hot. for i in start..end { if eqls(str, self.kvs[i].key) { return Some(self.kvs[i].value); @@ -198,8 +176,6 @@ where return None; } - // PERF(port): Zig `inline while (i <= max_len)` dispatched to a monomorphized - // `getWithLength(str, comptime i)`. We call the runtime version directly. self.get_with_length(str, str.len()) } @@ -214,14 +190,11 @@ where // This benchmarked faster for both small and large lists of strings than using a big switch statement // But only so long as the keys are a sorted list. - // PERF(port): was `inline for` over comptime range. (start..end).find(|&i| str == self.kvs[i].key) } - // TODO(port): move to *_jsc — `fromJS` / `fromJSCaseInsensitive` were thin shims to - // `jsc/comptime_string_map_jsc.zig`. In Rust these become extension-trait methods in - // `bun_jsc` (e.g. `impl ComptimeStringMapJsc for ComptimeStringMap`). - // The base `bun_collections` crate has no JSC dependency. + // `fromJS` / `fromJSCaseInsensitive` live in `src/jsc/comptime_string_map_jsc.rs` + // (the base `bun_collections` crate has no JSC dependency). pub fn get_with_eql(&self, input: I, eql: impl Fn(I, &'static [K]) -> bool) -> Option where @@ -232,7 +205,6 @@ where return None; } - // PERF(port): was `inline while` dispatch to comptime-len variant. self.get_with_length_and_eql(input, length, eql) } @@ -243,7 +215,6 @@ where where V: PartialEq, { - // PERF(port): was `inline for` — profile if hot. for kv in &self.kvs { if kv.value == value { return Some(kv.key); @@ -258,9 +229,8 @@ impl ComptimeStringMapWithKeyType Option { self.get_with_eql(str, bun_alloc::String::eql_comptime) } @@ -279,12 +249,17 @@ where return None; } - // PERF(port): Zig built a `[i]u8` stack buffer per comptime length; we use a - // bounded stack buffer sized to max_len. Profile if it shows up on a hot path. - // TODO(port): if max_len can exceed a small bound at any call site, revisit. - let mut buf = [0u8; 256]; - debug_assert!(length <= buf.len()); - let lowercased = bun_core::strings::copy_lowercase(input, &mut buf[..length]); + // Bounded stack buffer for the common case (every in-tree map has small keys); + // fall back to a heap buffer for maps whose keys exceed it. + let mut stack_buf = [0u8; 256]; + let mut heap_buf: Vec; + let buf: &mut [u8] = if length <= stack_buf.len() { + &mut stack_buf[..length] + } else { + heap_buf = vec![0u8; length]; + &mut heap_buf[..] + }; + let lowercased = bun_core::strings::copy_lowercase(input, buf); self.get_with_length_and_eql(lowercased, length, eql) } @@ -299,14 +274,12 @@ where /// ... /// ]); /// ``` -// TODO(port): proc-macro — Zig sorted + built len_indexes at comptime. A `macro_rules!` -// cannot sort; either (a) require callers pre-sort and compute LEN_TABLE, (b) use a -// proc-macro, or (c) lazy-init via `once_cell::Lazy` + `ComptimeStringMapWithKeyType::new`. -// Currently uses (c) for correctness; could upgrade to a proc-macro for true const. +// A `macro_rules!` cannot sort, so this lazy-inits via `once_cell::Lazy` + +// `ComptimeStringMapWithKeyType::new` — correct, with a one-time runtime init cost +// instead of true const construction. #[macro_export] macro_rules! comptime_string_map { ($V:ty, [ $( ($key:expr, $val:expr) ),* $(,)? ]) => {{ - // PERF(port): was comptime; now lazy runtime init. ::once_cell::sync::Lazy::new(|| { $crate::comptime_string_map::ComptimeStringMapWithKeyType::::new([ $( $crate::comptime_string_map::KV { key: $key, value: $val } ),* @@ -376,8 +349,6 @@ mod tests { #[test] fn comptime_string_map_array_of_structs() { - // PORT NOTE: Zig tested that anonymous-struct and named-struct kv inputs both work. - // In Rust there is one input shape (`KV`), so this collapses to the same test. let map = ComptimeStringMapWithKeyType::::new([ KV { key: b"these", @@ -495,9 +466,7 @@ mod tests { test_set(&map); } - // PORT NOTE: `TestEnum2` + its 39-entry `map`/`official` table existed only as a - // benchmark fixture against `std.ComptimeStringMap` (no `test` block references it). + // `TestEnum2` + its 39-entry `map`/`official` table existed only as a + // benchmark fixture (no `test` block references it). // Omitted; can re-add as a criterion bench if needed. } - -// ported from: src/collections/comptime_string_map.zig diff --git a/src/collections/hive_array.rs b/src/collections/hive_array.rs index 20761f9a912..4570ebd467a 100644 --- a/src/collections/hive_array.rs +++ b/src/collections/hive_array.rs @@ -8,8 +8,6 @@ use bun_core::asan; /// Fixed-width occupancy bitset for [`HiveArray`]. /// -/// PORT NOTE: Zig's `std.bit_set.IntegerBitSet(N)` is backed by an exact-width -/// `uN` integer (`u128`, `u256`, `u2048`, …). The Rust port's /// [`IntegerBitSet`](crate::bit_set::IntegerBitSet) is backed by a single /// `usize`, so for `N > 64` it silently held only 64 usable bits — every /// `HiveArray<_, 128/256/2048>` pool degraded to 64 effective slots and spilled @@ -171,8 +169,6 @@ impl HiveBitSetIter { /// All slot operations take `&self` and the buffer is `UnsafeCell` — slot /// pointers come from `UnsafeCell::get()` and so survive `&self` reborrows of /// the pool (the `bumpalo` / `typed-arena` shape). `HiveArray` is `!Sync`. -// PORT NOTE: Zig's `capacity: u16` is widened to `usize` here because Rust array -// lengths require a `usize` const generic on stable. pub struct HiveArray { buffer: UnsafeCell<[MaybeUninit; CAPACITY]>, pub used: HiveBitSet, @@ -181,10 +177,6 @@ pub struct HiveArray { impl HiveArray { pub const SIZE: usize = CAPACITY; - // PORT NOTE: Zig had `pub var empty: Self` as a mutable static to work around - // https://github.com/ziglang/zig/issues/22462 and /21988. Rust has no such - // limitation; callers should use `init()` (which is `const`). - pub const fn init() -> Self { Self { buffer: UnsafeCell::new([const { MaybeUninit::uninit() }; CAPACITY]), @@ -338,7 +330,7 @@ impl HiveArray { /// into this hive, returns `false` and is a no-op. Use when the caller has /// already moved the contents out / destructured them, or when `T` is POD /// and the slot is being released on an error path before it was fully - /// initialized (Zig `value.* = undefined`). + /// initialized. /// /// # Safety /// No live token ([`HiveSlot`], [`HiveBox`]) may exist for this slot — once @@ -403,9 +395,7 @@ impl HiveArray { debug_assert!(self.used.is_set(index as usize)); debug_assert!(self.ptr_at(index as usize).cast_const() == value.cast_const()); - // PORT NOTE: Zig wrote `value.* = undefined;` — Zig has no destructors, - // so the slot was simply marked logically uninitialized. In the Rust - // port several `T` carry owned heap data (e.g. `NumberScope.name_counts: + // Several `T` carry owned heap data (e.g. `NumberScope.name_counts: // StringHashMap`, `NetworkTask.url_buf: Box<[u8]>`); drop the slot // before recycling so the put/get cycle does not leak it. Callers that // pre-clean fields (`PooledSocket::release_parked_refs`) leave only @@ -589,15 +579,11 @@ impl Drop for HiveBox<'_, T, CAPACITY> { } } -// PORT NOTE: In Zig this was the nested type `HiveArray(T, capacity).Fallback`. -// Rust cannot nest a generic struct that captures outer generics, so it lives at -// module scope with the same parameters. The Zig field -// `hive: if (capacity > 0) Self else void` is always materialized here; the -// `CAPACITY > 0` checks below preserve the original gating. -// PERF(port): zero-capacity case carried a zero-size hive in Zig — profile in Phase B. +// Rust cannot nest a generic struct that captures outer generics, so this lives +// at module scope with the same parameters as `HiveArray`. The hive is always +// materialized; the `CAPACITY > 0` checks below gate its use. pub struct Fallback { pub hive: HiveArray, - // PORT NOTE: `std.mem.Allocator param` dropped — global mimalloc. } impl Fallback { @@ -635,7 +621,7 @@ impl Fallback { /// for `MaybeUninit`). /// /// The returned allocation is leaked — callers stash it in a per-thread - /// static for the process lifetime (Zig: `threadlocal var pool`). + /// static for the process lifetime. #[inline] pub fn new_boxed() -> NonNull { let mut boxed = Box::::new_uninit(); @@ -768,14 +754,12 @@ impl Fallback { // HiveRef // ────────────────────────────────────────────────────────────────────────── // -// PORT NOTE: ground truth is `bun.HiveRef` in src/bun.zig. It lives here (not -// in the `bun` crate) because every consumer names it through +// Lives here (not in the `bun` crate) because every consumer names it through // `bun_collections::HiveRef`, and its only collaborator is `Fallback` above. // -// Zig defines `const HiveAllocator = HiveArray(@This(), capacity).Fallback` -// inside the returned struct; Rust spells the self-referential pool type out -// as `Fallback, CAPACITY>`. CAPACITY is `usize` (widened -// from Zig's `u16`) to line up with `HiveArray`/`Fallback`'s const generic. +// The self-referential pool type is spelled out as +// `Fallback, CAPACITY>`. CAPACITY is `usize` to line up +// with `HiveArray`/`Fallback`'s const generic. /// Intrusive ref-counted slot allocated from a `HiveArray::Fallback` pool. /// `pool` is a BACKREF (LIFETIMES.tsv class) — the pool strictly outlives @@ -791,7 +775,8 @@ pub struct HiveRef { } impl HiveRef { - /// Zig: `pub fn init(value, allocator) !*@This()`. + /// Allocate a slot from `pool` (heap fallback when full) and initialize it + /// with `value` at refcount 1. /// /// # Safety /// `pool` must be valid for the entire lifetime of the returned @@ -816,8 +801,8 @@ impl HiveRef { self } - /// Zig: `pub fn unref(this) ?*@This()` — returns `None` when the count hit - /// zero and the slot was returned to the pool. + /// Returns `None` when the count hit zero and the slot was returned to + /// the pool. /// /// # Safety /// `this` must point at a live `HiveRef` produced by [`init`](Self::init). @@ -825,8 +810,7 @@ impl HiveRef { pub unsafe fn unref(this: *mut Self) -> Option<*mut Self> { // SAFETY: caller contract — `this` is a live `HiveRef` slot, and // `(*this).pool` outlives every slot it hands out (`init` contract). - // Zig's `if @hasDecl(T, "deinit") this.value.deinit()` maps to `T::drop`, - // which `Fallback::put` runs (drops the whole `HiveRef` in place). + // `Fallback::put` runs `T::drop` (drops the whole `HiveRef` in place). unsafe { let ref_count = (*this).ref_count.get(); (*this).ref_count.set(ref_count - 1); @@ -941,7 +925,6 @@ mod tests { const SIZE: usize = 64; // Choose an integer with a weird alignment - // PORT NOTE: Zig used `u127`; Rust has no arbitrary-width ints. `u128` is the closest. type Int = u128; let a = HiveArray::::init(); @@ -1486,5 +1469,3 @@ mod tests { assert!(unsafe { pool.box_at(999) }.is_none()); } } - -// ported from: src/collections/hive_array.zig diff --git a/src/collections/identity_context.rs b/src/collections/identity_context.rs index 0bfd37eac63..5539c7fab92 100644 --- a/src/collections/identity_context.rs +++ b/src/collections/identity_context.rs @@ -1,7 +1,6 @@ use core::marker::PhantomData; -/// Trait covering the Zig `@typeInfo(Key)` switch in `IdentityContext.hash`: -/// `.@"enum" => @intFromEnum(key)`, `.int => key`, `else => @compileError(...)`. +/// Trait for keys usable with `IdentityContext`. /// Implement for any int (`self as u64`) or `#[repr(uN)]` enum (`self as uN as u64`). pub trait IdentityHash: Copy + Eq { fn identity_hash(self) -> u64; @@ -22,7 +21,6 @@ pub struct IdentityContext(PhantomData); impl IdentityContext { pub fn hash(self, key: Key) -> u64 { - // Zig: switch (comptime @typeInfo(Key)) { .@"enum" => @intFromEnum(key), .int => key, else => @compileError } key.identity_hash() } @@ -59,8 +57,8 @@ impl ArrayIdentityContextU64 { } } -// Zig's `ArrayIdentityContext.U64` nesting — inherent assoc types are unstable, -// so expose as a free path alias instead. Callers: `identity_context::U64`. +// Inherent assoc types are unstable, so expose this as a free path alias. +// Callers: `identity_context::U64`. pub type U64 = ArrayIdentityContextU64; // ArrayHashMap requires `C: ArrayHashContext`, so wire the inherent impls @@ -88,5 +86,3 @@ impl crate::array_hash_map::ArrayHashContext for ArrayIdentityContextU64 { a == b } } - -// ported from: src/collections/identity_context.zig diff --git a/src/collections/lib.rs b/src/collections/lib.rs index 10e8ad091f9..4f24bb75e06 100644 --- a/src/collections/lib.rs +++ b/src/collections/lib.rs @@ -1,5 +1,4 @@ -//! bun_collections — crate root. -//! Thin re-export hub mirroring `src/collections/collections.zig`. +//! bun_collections — crate root. Thin re-export hub. #![feature( type_info, @@ -24,9 +23,6 @@ pub use bun_core::bounded_array; pub mod identity_context; pub mod linear_fifo; -// TODO(b2-large): heavy nightly-feature usage (adt_const_params for enum-typed -// const generics, generic_const_exprs, inherent assoc types). Rewrite to -// stable: enum const params → const usize/bool, inherent assoc → free aliases. pub mod bit_set; pub mod pool; pub use pool::{ObjectPool, ObjectPoolTrait, ObjectPoolType, PoolGuard}; @@ -55,16 +51,15 @@ pub use bit_set::{ // from here); canonical impl lives in `bun_core::strings`. pub use bun_core::strings::{const_bytes_eq, const_str_eq}; -/// `bun.bit_set` namespace alias (Zig: `bun.bit_set.List`). +/// Namespace alias for the bit-set types. pub mod dynamic_bit_set { pub use super::bit_set::DynamicBitSet; pub use super::bit_set::DynamicBitSetList as List; } // ────────────────────────────────────────────────────────────────────────── -// `PriorityQueue` — port of `std.PriorityQueue(T, Context, lessThan)`. -// Min-heap backed by a `Vec`; the comparator context is held by value so -// callers can rebind it (Zig stores `context: Context` directly on the queue). +// `PriorityQueue` — min-heap backed by a `Vec`; the comparator context is +// held by value so callers can rebind it. // ────────────────────────────────────────────────────────────────────────── pub trait PriorityCompare { fn compare(&self, a: &T, b: &T) -> core::cmp::Ordering; @@ -101,7 +96,7 @@ impl PriorityQueue { } } impl> PriorityQueue { - /// Zig: `add(elem) !void` — push and sift-up. + /// Push and sift-up. pub fn add(&mut self, elem: T) -> Result<(), bun_alloc::AllocError> { self.items.push(elem); let mut child = self.items.len() - 1; @@ -120,7 +115,7 @@ impl> PriorityQueue { } Ok(()) } - /// Zig: `removeOrNull()` — pop min, sift-down; `None` when empty. + /// Pop min, sift-down; `None` when empty. pub fn remove_or_null(&mut self) -> Option { if self.items.is_empty() { return None; @@ -190,10 +185,10 @@ pub use bun_ptr::tagged_pointer::{TaggedPtr as TaggedPointer, TaggedPtrUnion}; pub use bun_ptr::{RawSlice, detach_lifetime, detach_ref}; // ────────────────────────────────────────────────────────────────────────── -// SmallList — `bun.SmallList(T, N)` (Zig: src/css/small_list.zig). +// SmallList // // Thin `#[repr(transparent)]` newtype over `smallvec::SmallVec<[T; N]>` that -// preserves the Zig-named API surface (`append`, `slice`, `at`, `len()->u32`, +// preserves the existing API surface (`append`, `slice`, `at`, `len()->u32`, // `init_capacity`, …) so the ~300 CSS-parser call sites stay untouched. // Replaces the bespoke ~800-line `Data`/`HeapData` union + raw-ptr container // that previously lived in `bun_css::small_list` (which was itself a port of @@ -337,7 +332,7 @@ impl SmallList { // invariant of `from_raw_parts` is never exercised. Self(unsafe { smallvec::SmallVec::from_raw_parts(slab.as_mut_ptr(), len, len) }) } - /// Zig `fromList` / `fromBabyList` — adopt a `Vec` as the heap buffer + /// Adopt a `Vec` as the heap buffer /// (O(1) header transfer; no element copy). #[inline] pub fn from_list(list: Vec) -> Self { @@ -345,7 +340,7 @@ impl SmallList { } // ── access ───────────────────────────────────────────────────────────── - /// Zig `len()` returns `u32` (not `usize`); preserved so the ~300 call-site + /// Returns `u32` (not `usize`); preserved so the ~300 call-site /// integer arithmetic in `bun_css` stays unchanged. Inherent shadows the /// `[T]::len()->usize` reachable via `Deref`. #[inline] @@ -460,13 +455,17 @@ impl SmallList { self.0.reserve_exact(new_capacity as usize - cur); } } - /// Zig `setLen` — exposed as safe for API parity with the previous port - /// (whose only external caller shrinks to 0). Growing past the initialised - /// region is the caller's responsibility, same as before. + /// Exposed as safe: every current external caller only shrinks + /// (`new_len <= len`). Growing past the initialised region is the + /// caller's responsibility. #[inline] pub fn set_len(&mut self, new_len: u32) { - // SAFETY: matches the previous bun_css::SmallList::set_len contract - // (Zig callers treat this as a raw length store). + // SAFETY: a raw length store. All current external callers only + // shrink (`new_len <= self.len()`), so `[0..new_len]` stays + // initialised and `new_len <= capacity`, satisfying + // `SmallVec::set_len`'s preconditions (the truncated tail merely + // leaks). Any caller that grows takes responsibility for + // initialising the exposed region. unsafe { self.0.set_len(new_len as usize) } } @@ -487,7 +486,7 @@ impl SmallList { Self(self.0.clone()) } - // ── iteration helpers (Zig-named) ────────────────────────────────────── + // ── iteration helpers ─────────────────────────────────────────────────── #[inline] pub fn any(&self, predicate: impl Fn(&T) -> bool) -> bool { self.0.iter().any(predicate) @@ -501,12 +500,12 @@ impl SmallList { } // ────────────────────────────────────────────────────────────────────────── -// HashMap — `std.AutoHashMap(K, V)` / `std.HashMap(K, V, Ctx, max_load)`. +// HashMap // -// Ported linear-probe layout (open-addressing, tombstones, power-of-two cap, -// 80% load) so iteration order matches Zig exactly — required by callers that +// Linear-probe layout (open-addressing, tombstones, power-of-two cap, +// 80% load) with deterministic iteration order — required by callers that // snapshot the iteration sequence (lockfile debug stringify, etc.). The `Ctx` -// type parameter is now load-bearing: `AutoHashContext` wyhashes the key, +// type parameter is load-bearing: `AutoHashContext` wyhashes the key, // `IdentityContext` uses `k as u64` so pre-hashed keys aren't re-hashed. // ────────────────────────────────────────────────────────────────────────── @@ -526,21 +525,17 @@ pub mod hash_map { pub value_ptr: &'a mut V, } - /// Zig `std.HashMap.KV` — owned `{key, value}` pair returned from - /// `fetchRemove` / `fetchPut`. Identical to `std.ArrayHashMap.KV`; re-exported - /// from `array_hash_map` rather than duplicated. + /// Owned `{key, value}` pair returned from `fetchRemove` / `fetchPut`; + /// re-exported from `array_hash_map` rather than duplicated. pub use crate::array_hash_map::KV; } pub mod array_list; -// TODO(port): per PORTING.md the managed/unmanaged ArrayList split collapses to -// `Vec` (global mimalloc) outside AST crates; Phase B may drop most of these -// aliases once callers are migrated. -pub use array_list::ArrayList; // any `std.mem.Allocator` +// All of these aliases collapse to `Vec` (global mimalloc); they exist for +// back-compat at existing call sites. +pub use array_list::ArrayList; pub use array_list::ArrayListAligned; pub use array_list::ArrayListAlignedDefault; pub use array_list::ArrayListAlignedIn; -pub use array_list::ArrayListDefault; // always default allocator (no overhead) -pub use array_list::ArrayListIn; // specific type of generic allocator - -// ported from: src/collections/collections.zig +pub use array_list::ArrayListDefault; +pub use array_list::ArrayListIn; diff --git a/src/collections/linear_fifo.rs b/src/collections/linear_fifo.rs index 70149dde41e..33ed23d9bdf 100644 --- a/src/collections/linear_fifo.rs +++ b/src/collections/linear_fifo.rs @@ -1,6 +1,3 @@ -// clone of zig stdlib -// except, this one vectorizes - // FIFO of fixed size items // Usually used for e.g. byte buffers @@ -10,16 +7,14 @@ use core::ptr; use bun_alloc::AllocError; -// TODO(port): std.heap.page_size_min — Zig resolves this per-target; 4096 is the -// conservative minimum on every platform Bun ships on. +// 4096 is the conservative minimum page size on every platform Bun ships on. const PAGE_SIZE_MIN: usize = 4096; -/// Mirrors Zig's `LinearFifoBufferType = union(enum)`. +/// Selects the fifo's backing-storage strategy. /// -/// In the Zig original this is a *comptime* value that selects a struct layout -/// (`buf: [N]T` vs `buf: []T`, `std.mem.Allocator` param vs `void`). Rust cannot -/// branch struct layout on a const-generic enum payload, so dispatch is done -/// via the [`LinearFifoBuffer`] trait below; this enum is kept for API parity. +/// Rust cannot branch struct layout on a const-generic enum payload, so +/// dispatch is done via the [`LinearFifoBuffer`] trait below; this enum is +/// kept for API parity. pub enum LinearFifoBufferType { /// The buffer is internal to the fifo; it is of the specified size. Static(usize), @@ -29,12 +24,11 @@ pub enum LinearFifoBufferType { Dynamic, } -/// Backing-storage abstraction replacing Zig's `comptime buffer_type` switch. -/// `POWERS_OF_TWO` mirrors the Zig `powers_of_two` const inside the returned -/// struct; `DYNAMIC` mirrors `buffer_type == .Dynamic`. -// TODO(port): the Zig fn returns structurally different layouts per variant; -// trait+assoc-consts is the closest stable-Rust encoding. Phase B: confirm all -// in-tree callers are covered by the three impls below. +/// Backing-storage abstraction; `DYNAMIC` is true for the `.Dynamic` variant. +// Trait + assoc-consts encode the structurally different layouts per +// variant. No in-tree caller +// instantiates `SliceBuffer` directly; `threading::Channel::init_slice` wraps +// it for `.Slice` API parity. pub trait LinearFifoBuffer { const POWERS_OF_TWO: bool; const DYNAMIC: bool; @@ -61,9 +55,12 @@ pub trait LinearFifoBuffer { /// Reinterpret `&[MaybeUninit]` as `&[T]`. `MaybeUninit` has identical /// layout to `T`; exposing uninitialized bytes as `T` is sound only when any -/// bit pattern is a valid `T` (in-tree LinearFifo users are byte buffers — -/// see the `StaticBuffer` TODO below). Centralises the four per-buffer-kind -/// casts behind one audited block. +/// bit pattern is a valid `T`. NOT every in-tree element type satisfies this: +/// besides byte buffers and raw pointers, fifos today store `NonNull`-bearing +/// enums (`bun_test::RefDataValue`), `JSPromiseStrong`-bearing structs +/// (`ValkeyCommand::PromisePair`), and the `event_loop::Task` enum — see the +/// `StaticBuffer` note below for the pending MaybeUninit accessor rework. +/// Centralises the four per-buffer-kind casts behind one audited block. #[inline(always)] fn assume_init_slice(s: &[MaybeUninit]) -> &[T] { // SAFETY: see fn doc. @@ -112,10 +109,19 @@ fn poison(slice: &mut [T], n: usize) { // ── .Static ─────────────────────────────────────────────────────────────────── /// `buffer_type == .Static` — inline `[T; N]` storage. -// TODO(port): Zig leaves the array `undefined`; we use MaybeUninit and expose -// it as &[T] via pointer cast. Sound only for `T` whose any-bit-pattern is -// valid (in-tree users are byte buffers). Phase B: bound `T: Copy` or rework -// accessors to MaybeUninit if a non-POD T appears. +// INVARIANT: storage is MaybeUninit and exposed as &[T] via pointer cast +// (`assume_init_slice`). The public API +// (`writable_slice` hands out `&mut [T]` over not-yet-written slots) bakes in +// the same exposure for every buffer kind. Sound only for `T` whose +// any-bit-pattern is valid — and in-tree element types ALREADY violate that: +// `RefDataValue` (NonNull payload), `PromisePair` +// (JSPromiseStrong), and the `Task` enum are stored in fifos today, so +// materialising `&[T]` over uninitialized slots for those types is latent UB. +// The fix is reworking the accessors to operate on `&[MaybeUninit]` and +// only assume-init the logically-written subranges. That cannot be done by +// touching this file alone — `writable_slice`-family callers in other crates +// see the signature change — so it is deferred to a dedicated change with +// Miri coverage for a NonNull-bearing element type. pub struct StaticBuffer([MaybeUninit; N]); impl LinearFifoBuffer for StaticBuffer { @@ -153,10 +159,8 @@ impl<'a, T> LinearFifoBuffer for SliceBuffer<'a, T> { // ── .Dynamic ────────────────────────────────────────────────────────────────── -/// `buffer_type == .Dynamic` — heap-allocated, growable. -/// -/// Zig stores `std.mem.Allocator` param + `buf: []T`. Per §Allocators (non-AST -/// crate) the allocator param is dropped and global mimalloc backs `Box`. +/// `buffer_type == .Dynamic` — heap-allocated, growable. Global mimalloc +/// backs the `Box`. pub struct DynamicBuffer(Box<[MaybeUninit]>); impl LinearFifoBuffer for DynamicBuffer { @@ -173,7 +177,6 @@ impl LinearFifoBuffer for DynamicBuffer { } fn realloc(&mut self, new_size: usize) -> Result<(), AllocError> { - // Zig: `self.allocator.realloc(self.buf, size)` preserving prefix. let mut new = Box::<[T]>::new_uninit_slice(new_size); let n = self.0.len().min(new_size); // SAFETY: disjoint allocations; MaybeUninit copy is always sound. @@ -191,21 +194,16 @@ impl LinearFifoBuffer for DynamicBuffer { // ── LinearFifo ──────────────────────────────────────────────────────────────── pub struct LinearFifo> { - // Zig field `allocator` is folded into `B` (or dropped) — see DynamicBuffer. buf: B, head: usize, count: usize, _marker: PhantomData, } -// PORT NOTE: Zig's `SliceSelfArg = if (.Static) *Self else Self` exists because -// returning a slice into a by-value `Self` would dangle when buf is inline. In -// Rust every accessor takes `&self`/`&mut self`, so the distinction disappears. - -// TODO(port): Reader/Writer std.Io adapters. Zig exposes -// `pub const Reader = std.Io.GenericReader(*Self, error{}, readFn)` and a -// matching Writer. Phase B: impl `bun_io::Read`/`bun_io::Write` (and -// `core::fmt::Write`) on `LinearFifo`. +// Reader/Writer access is via the impls on `LinearFifo` after the +// inherent impl below: `bun_core::write::Write` (the canonical byte sink, +// re-exported as `bun_io::Write`), plus `std::io::Read`, `std::io::Write`, +// and `core::fmt::Write` for std interop. impl LinearFifo> { /// `init` for `.Static`. @@ -232,8 +230,7 @@ impl<'a, T> LinearFifo> { } impl LinearFifo> { - /// `init` for `.Dynamic`. Zig takes `std.mem.Allocator` param; dropped per - /// §Allocators (non-AST crate). + /// `init` for `.Dynamic`. pub fn init() -> Self { Self { buf: DynamicBuffer(Box::new([])), @@ -253,7 +250,7 @@ impl> LinearFifo { self.buf.len() } - /// Allocated capacity of the backing buffer (Zig: `fifo.buf.len`). + /// Allocated capacity of the backing buffer. /// Distinct from [`readable_length`] (live items) and /// [`writable_length`] (free slots) — `capacity == readable + writable`. /// Used by GC `memoryCost` reporting where the *allocation* size, not the @@ -265,8 +262,7 @@ impl> LinearFifo { /// Rewind `head` to 0 when the queue is empty so the next `write` can use /// the full contiguous buffer without wrapping. Perf-only micro-opt; a - /// no-op when items remain. Mirrors the `head = 0` post-drain idiom in - /// `src/jsc/Task.zig` `tickQueueWithCount`. + /// no-op when items remain. #[inline] pub fn reset_head_if_empty(&mut self) { if self.count == 0 { @@ -285,11 +281,9 @@ impl> LinearFifo { unsafe { ptr::copy(buf.as_ptr().add(head), buf.as_mut_ptr(), count) }; self.head = 0; } else { - // Zig: `var tmp: [page_size_min / 2 / @sizeOf(T)]T = undefined;` // Stable Rust cannot size a stack array by `size_of::()`, so use - // a fixed byte scratch and compute the element count at runtime. - // PERF(port): was stack array sized by page_size/2/sizeof(T) — same - // byte footprint here, no heap. + // a fixed byte scratch (page_size/2 bytes, no heap) and compute the + // element count at runtime. // // The scratch is a `[MaybeUninit; _]` (alignment 1). Reading or // writing through it as `*mut T` would violate @@ -327,7 +321,8 @@ impl> LinearFifo { { let count = self.count; let unused = &mut self.buf.as_mut_slice()[count..]; - // SAFETY: poisoning unused tail; matches Zig `@memset(unused, undefined)`. + // SAFETY: the tail past `count` is logically uninitialized; writing + // the 0xAA poison pattern there cannot invalidate live items. unsafe { ptr::write_bytes( unused.as_mut_ptr().cast::(), @@ -352,12 +347,10 @@ impl> LinearFifo { #[deprecated(note = "deprecated; call `ensure_unused_capacity` or `ensure_total_capacity`")] pub fn ensure_capacity(&mut self, _size: usize) { - // Zig: `pub const ensureCapacity = @compileError(...)` unreachable!("deprecated; call ensure_unused_capacity or ensure_total_capacity"); } /// Ensure that the buffer can fit at least `size` items - // TODO(port): narrow error set pub fn ensure_total_capacity(&mut self, size: usize) -> Result<(), AllocError> { if self.buf_len() >= size { return Ok(()); @@ -365,12 +358,10 @@ impl> LinearFifo { if B::DYNAMIC { self.realign(); let new_size = if B::POWERS_OF_TWO { - // math.ceilPowerOfTwo(usize, size) catch return error.OutOfMemory size.checked_next_power_of_two().ok_or(AllocError)? } else { size }; - // Zig: alloc new, memcpy readableSlice(0) bytes, free old. let count = self.count; let old = self.buf.alloc_swap(new_size)?; if count > 0 { @@ -447,7 +438,7 @@ impl> LinearFifo { #[cfg(debug_assertions)] { // set old range to undefined. Note: may be wrapped around - // PORT NOTE: reshaped for borrowck — capture len, then re-borrow. + // reshaped for borrowck — capture len, then re-borrow. let slice_len = self.readable_slice_mut(0).len(); if slice_len >= count { poison(self.readable_slice_mut(0), count); @@ -497,7 +488,7 @@ impl> LinearFifo { } let n = slice.len().min(dst_left.len()); dst_left[..n].copy_from_slice(&slice[..n]); - // PORT NOTE: NLL drops `slice` borrow here before `&mut self`. + // NLL drops `slice` borrow here before `&mut self`. self.discard(n); dst_left = &mut dst_left[n..]; } @@ -505,8 +496,6 @@ impl> LinearFifo { total - dst_left.len() } - // TODO(port): `pub fn reader(self: *Self) Reader` — see Reader/Writer note. - /// Returns number of items available in fifo #[inline] pub fn writable_length(&self) -> usize { @@ -535,12 +524,11 @@ impl> LinearFifo { /// Returns a writable buffer of at least `size` items, allocating memory as needed. /// Use `fifo.update` once you've written data to it. - // TODO(port): narrow error set pub fn writable_with_size(&mut self, size: usize) -> Result<&mut [T], AllocError> { self.ensure_unused_capacity(size)?; // try to avoid realigning buffer - // PORT NOTE: reshaped for borrowck — check len, drop borrow, maybe + // reshaped for borrowck — check len, drop borrow, maybe // realign, then take the final borrow. if self.writable_slice(0).len() < size { self.realign(); @@ -566,7 +554,7 @@ impl> LinearFifo { let mut src_left = src; while !src_left.is_empty() { - // PORT NOTE: reshaped for borrowck — scoped block drops the + // reshaped for borrowck — scoped block drops the // `writable` borrow before `self.update`. let n = { let writable = self.writable_slice(0); @@ -581,7 +569,6 @@ impl> LinearFifo { } /// Write a single item to the fifo - // TODO(port): narrow error set pub fn write_item(&mut self, item: T) -> Result<(), AllocError> { self.ensure_unused_capacity(1)?; self.write_item_assume_capacity(item); @@ -596,16 +583,15 @@ impl> LinearFifo { tail %= self.buf_len(); } // SAFETY: `tail` is in-bounds (capacity reserved by caller). The slot is - // logically uninitialized — `ptr::write` matches Zig assignment semantics - // (no drop of the prior bit-pattern), required for non-`Copy` `T` whose - // backing storage is `MaybeUninit`. + // logically uninitialized — `ptr::write` does not drop the prior + // bit-pattern, which is required for non-`Copy` `T` whose backing + // storage is `MaybeUninit`. unsafe { ptr::write(self.buf.as_mut_slice().as_mut_ptr().add(tail), item) }; self.update(1); } /// Appends the data in `src` to the fifo. /// Allocates more memory as necessary - // TODO(port): narrow error set pub fn write(&mut self, src: &[T]) -> Result<(), AllocError> where T: Copy, @@ -615,8 +601,6 @@ impl> LinearFifo { Ok(()) } - // TODO(port): `pub fn writer(self: *Self) Writer` — see Reader/Writer note. - /// Make `count` items available before the current read location fn rewind(&mut self, count: usize) { debug_assert!(self.writable_length() >= count); @@ -632,7 +616,6 @@ impl> LinearFifo { } /// Place data back into the read stream - // TODO(port): narrow error set pub fn unget(&mut self, src: &[T]) -> Result<(), AllocError> where T: Copy, @@ -641,7 +624,7 @@ impl> LinearFifo { self.rewind(src.len()); - // PORT NOTE: reshaped for borrowck — copy into first chunk in a scoped + // reshaped for borrowck — copy into first chunk in a scoped // block, drop borrow, then re-borrow for the wrapped chunk. let slice_len = { let s = self.readable_slice_mut(0); @@ -737,9 +720,10 @@ impl> LinearFifo { /// Pump data from a reader into a writer /// stops when reader returns 0 bytes (EOF) /// Buffer size must be set before calling; a buffer length of 0 is invalid. - // TODO(port): `src_reader: anytype, dest_writer: *std.Io.Writer`. Phase B: - // bind to `bun_io::Read`/`bun_io::Write` (or whatever the byte-stream traits - // land as). Stubbed with generic bounds matching the called methods. + // The closure bounds encode duck-typed streams: + // `src_reader(buf)` ≙ `reader.read(buf)`, `dest_writer(buf)` ≙ + // `writer.write(buf)`, both returning a count (`Ok(0)` from the reader + // means EOF). This keeps `pump` generic over `T`, which `std::io` cannot. pub fn pump(&mut self, mut src_reader: R, dest_writer: &mut W) -> Result<(), E> where R: FnMut(&mut [T]) -> Result, @@ -748,7 +732,7 @@ impl> LinearFifo { debug_assert!(self.buf_len() > 0); loop { if self.writable_length() > 0 { - // PORT NOTE: reshaped for borrowck. + // reshaped for borrowck. let n = { let ws = self.writable_slice(0); src_reader(ws)? @@ -776,6 +760,61 @@ impl> LinearFifo { } } +// ── Reader/Writer adapters ──────────────────────────────────────────────────── + +impl> std::io::Read for LinearFifo { + /// Drains up to `dst.len()` buffered bytes. `Ok(0)` means + /// the fifo is empty (EOF, never an error). + #[inline] + fn read(&mut self, dst: &mut [u8]) -> std::io::Result { + Ok(LinearFifo::read(self, dst)) + } +} + +impl> std::io::Write for LinearFifo { + /// Appends the buffer, growing if `.Dynamic`. Fixed-capacity buffers + /// follow the `std::io::Write` contract: write what fits and return the + /// count (`Ok(0)` when full — `write_all` turns that into `WriteZero`). + /// `ErrorKind::OutOfMemory` is reserved for dynamic-growth allocation + /// failure. + #[inline] + fn write(&mut self, src: &[u8]) -> std::io::Result { + let src = if B::DYNAMIC { + src + } else { + &src[..src.len().min(self.writable_length())] + }; + LinearFifo::write(self, src) + .map_err(|AllocError| std::io::Error::from(std::io::ErrorKind::OutOfMemory))?; + Ok(src.len()) + } + + #[inline] + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +/// Enables `write!(fifo, ...)`. +impl> core::fmt::Write for LinearFifo { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + LinearFifo::write(self, s.as_bytes()).map_err(|AllocError| core::fmt::Error) + } +} + +/// Canonical in-tree byte sink (re-exported as +/// `bun_io::Write`), so a `LinearFifo` can be passed to every +/// `impl bun_io::Write` consumer. `written_len` keeps its panicking default: +/// a fifo drains, so it does not track total bytes written. +impl> bun_core::write::Write for LinearFifo { + /// Appends the whole buffer, growing if `.Dynamic`. + #[inline] + fn write_all(&mut self, buf: &[u8]) -> Result<(), bun_core::Error> { + LinearFifo::write(self, buf)?; + Ok(()) + } +} + // ── tests ───────────────────────────────────────────────────────────────────── #[cfg(test)] @@ -854,45 +893,96 @@ mod tests { fifo.shrink(0); - // TODO(port): writer().print / reader().readUntilDelimiterOrEof tests - // depend on the std.Io adapter port. + { + use core::fmt::Write as _; + write!(fifo, "{}, {}!", "Hello", "World").unwrap(); + let mut result = [0u8; 30]; + let n = fifo.read(&mut result); + assert_eq!(b"Hello, World!", &result[..n]); + assert_eq!(0usize, fifo.readable_length()); + } - // TODO(port): pump test depends on std.io.fixedBufferStream port. + { + std::io::Write::write_all(&mut fifo, b"This is a test").unwrap(); + let mut drained = Vec::new(); + std::io::Read::read_to_end(&mut fifo, &mut drained).unwrap(); + let words: Vec<&[u8]> = drained.split(|&c| c == b' ').collect(); + assert_eq!(vec![&b"This"[..], b"is", b"a", b"test"], words); + } + + // Pump from an in-memory reader into a fixed output buffer. The + // closures play the reader/writer roles. + { + fifo.ensure_total_capacity(1).unwrap(); + let input: &[u8] = b"pump test"; + let mut cursor = 0usize; + let mut out: Vec = Vec::new(); + fifo.pump( + |buf: &mut [u8]| -> Result { + let n = buf.len().min(input.len() - cursor); + buf[..n].copy_from_slice(&input[cursor..cursor + n]); + cursor += n; + Ok(n) + }, + &mut |bytes: &[u8]| { + out.extend_from_slice(bytes); + Ok(bytes.len()) + }, + ) + .unwrap(); + assert_eq!(input, &out[..]); + } } - // `inline for ([_]type{u1,u8,u16,u64}) |T|` × `inline for (buffer types)` - // — expanded for one representative element type; the rest are mechanical. - // TODO(port): macro-generate the full T×buffer_type matrix in Phase B. - #[test] - fn linear_fifo_generic_u8_static() { - let mut fifo = LinearFifo::>::init(); + // Shared body for the T×buffer_type matrix below. + fn run_generic_fifo_test(mut fifo: LinearFifo) + where + T: Copy + PartialEq + core::fmt::Debug + From, + B: LinearFifoBuffer, + { + let zero = T::from(0u8); + let one = T::from(1u8); - fifo.write(&[0, 1, 1, 0, 1]).unwrap(); + fifo.write(&[zero, one, one, zero, one]).unwrap(); assert_eq!(5usize, fifo.readable_length()); { - assert_eq!(0u8, fifo.read_item().unwrap()); - assert_eq!(1u8, fifo.read_item().unwrap()); - assert_eq!(1u8, fifo.read_item().unwrap()); - assert_eq!(0u8, fifo.read_item().unwrap()); - assert_eq!(1u8, fifo.read_item().unwrap()); + assert_eq!(zero, fifo.read_item().unwrap()); + assert_eq!(one, fifo.read_item().unwrap()); + assert_eq!(one, fifo.read_item().unwrap()); + assert_eq!(zero, fifo.read_item().unwrap()); + assert_eq!(one, fifo.read_item().unwrap()); assert_eq!(0usize, fifo.readable_length()); } { - fifo.write_item(1).unwrap(); - fifo.write_item(1).unwrap(); - fifo.write_item(1).unwrap(); + fifo.write_item(one).unwrap(); + fifo.write_item(one).unwrap(); + fifo.write_item(one).unwrap(); assert_eq!(3usize, fifo.readable_length()); } { - let mut read_buf = [0u8; 3]; + let mut read_buf = [zero; 3]; let n = fifo.read(&mut read_buf); assert_eq!(3usize, n); // NOTE: It should be the number of items. } } + // The element types are crossed with all three buffer kinds. + #[test] + fn linear_fifo_generic_matrix() { + macro_rules! per_type { + ($($T:ty),* $(,)?) => {$( + run_generic_fifo_test(LinearFifo::<$T, StaticBuffer<$T, 32>>::init()); + run_generic_fifo_test(LinearFifo::<$T, DynamicBuffer<$T>>::init()); + let mut backing = [<$T>::from(0u8); 32]; + run_generic_fifo_test(LinearFifo::<$T, SliceBuffer<$T>>::init(&mut backing)); + )*}; + } + per_type!(u8, u16, u64); + } + // 16-slot static buffer: `POWERS_OF_TWO` is true, matching the in-tree // `weak_refs` FIFO in the dev server's source-map store (cap 16), the one // real caller of `ordered_remove_item`. `i32` elements make every shift @@ -1028,5 +1118,3 @@ mod tests { } } } - -// ported from: src/collections/linear_fifo.zig diff --git a/src/collections/multi_array_list.rs b/src/collections/multi_array_list.rs index 43061c51d03..883f3d72888 100644 --- a/src/collections/multi_array_list.rs +++ b/src/collections/multi_array_list.rs @@ -1,10 +1,8 @@ -//! Port of `std.MultiArrayList` with the following Bun-specific additions: +//! A struct-of-arrays `MultiArrayList` with the following Bun-specific additions: //! //! * `zero` method to zero-initialize memory. //! * `memory_cost` method, which returns the memory usage in bytes. //! -//! Synchronized with std as of Zig 0.14.1. -//! //! A MultiArrayList stores a list of a struct type. Instead of storing a //! single list of items, MultiArrayList stores separate lists for each field //! of the struct. This allows for memory savings if the struct has padding, @@ -58,7 +56,7 @@ use std::alloc::{Allocator, Global}; use bun_alloc::AllocError; /// Declares typed column-accessor extension traits for a `MultiArrayList<$T>` -/// element struct, mirroring Zig's `list.items(.field)` calling convention. +/// element struct. /// /// ```ignore /// multi_array_columns! { @@ -361,7 +359,7 @@ impl Reflected { out }; - /// Zig `sizes`: `(SIZES_BYTES, SIZES_FIELDS)` — field sizes sorted by + /// `(SIZES_BYTES, SIZES_FIELDS)` — field sizes sorted by /// alignment descending, paired with the original field index at each /// sorted position. Stable sort so equal-alignment fields keep order. const SIZES: ([usize; MAX_FIELDS], [usize; MAX_FIELDS]) = { @@ -539,7 +537,6 @@ impl<'a, F> ColMut<'a, F> { } /// Index-based comparison context for `sort` / `sort_span` / `sort_unstable`. -/// Zig: `ctx: anytype` with `fn lessThan(ctx, a_index: usize, b_index: usize) bool`. pub trait SortContext { fn less_than(&self, a_index: usize, b_index: usize) -> bool; } @@ -715,8 +712,7 @@ impl Slice { /// The returned value is a **bitwise copy** — the SoA storage retains /// ownership of every field. Dropping the gathered struct would free /// columns the storage still owns (double-free on next `get` / `Drop`), - /// so it is wrapped in `ManuallyDrop`. Zig has no destructors so the - /// by-value copy is harmless there. + /// so it is wrapped in `ManuallyDrop`. pub fn get(&self, index: usize) -> ManuallyDrop { assert!( index < self.len, @@ -985,7 +981,7 @@ impl MultiArrayList { Ok(()) } - /// Alias for [`push`] (Zig: `append`). + /// Alias for [`push`]. #[inline] pub fn append(&mut self, elem: T) -> Result<(), AllocError> { self.push(elem) @@ -1278,9 +1274,9 @@ impl MultiArrayList { impl Drop for MultiArrayList { fn drop(&mut self) { - // Zig `deinit(self, gpa)`: `gpa.free(self.allocatedBytes())` — slab - // only, no per-element destructors. This is **intentionally preserved**: - // [`clone`] is a bitwise SoA memcpy (Zig semantics), so two live lists + // Frees the slab only — no per-element destructors. This is + // **intentionally preserved**: + // [`clone`] is a bitwise SoA memcpy, so two live lists // can alias the same column heap pointers — see `bundle_v2.rs` // `clone_ast` / `deinit_without_freeing_arena`, which drains exactly // one alias and relies on the other dropping slab-only. Running @@ -1296,7 +1292,7 @@ impl Drop for MultiArrayList { // ───────────────────────────── helpers ───────────────────────────── -/// `std.atomic.cache_line` — **128** on x86_64 and aarch64, all native targets. +/// Conservative cache-line size — **128** on x86_64 and aarch64, all native targets. const CACHE_LINE: usize = 128; const fn init_capacity() -> usize { @@ -1565,5 +1561,3 @@ mod tests { assert_eq!(list.items::<"c", u64>(), &[0, 10, 20, 30, 40]); } } - -// ported from: src/collections/multi_array_list.zig diff --git a/src/collections/pool.rs b/src/collections/pool.rs index f23dde33c12..7fc46c48bd0 100644 --- a/src/collections/pool.rs +++ b/src/collections/pool.rs @@ -8,33 +8,20 @@ use bun_core::Error; // ────────────────────────────────────────────────────────────────────────── // SinglyLinkedList // ────────────────────────────────────────────────────────────────────────── -// -// PORT NOTE: Zig's `SinglyLinkedList(comptime T: type, comptime Parent: type)` -// threads `Parent` only so that `Node.release()` can call `Parent.release(node)`. -// In Rust the only `Parent` is `ObjectPool`, so `Node::release` is provided as -// an inherent method on `ObjectPool` instead and the `Parent` type param is -// dropped here. Diff readers: `node.release()` call sites become -// `ObjectPool::<..>::release(node)`. /// Node inside the linked list wrapping the actual data. #[repr(C)] pub struct Node { - // INTRUSIVE: pool.zig:7 — next link in singly-linked free list + // INTRUSIVE: next link in singly-linked free list pub next: *mut Node, - // PORT NOTE: Zig stored `std.mem.Allocator param` here so `destroyNode` - // could free via the originating allocator. In Rust the global mimalloc - // allocator owns every `Box>`, so the field is dropped and - // `destroy_node` uses `heap::take`. - // - // PORT NOTE: `MaybeUninit` not `T` — Zig's `else undefined` (pool.zig:203) - // is well-defined-until-read, but Rust's `assume_init()` on uninit bytes is + // `MaybeUninit` not `T`: `assume_init()` on uninit bytes is // immediate UB for any `T` with validity invariants. Callers that use // `INIT == None` write `data` before reading, so we keep the bytes // uninitialized and only `assume_init_*` at access sites. pub data: MaybeUninit, } -// PORT NOTE: `pub const Data = T;` (inherent assoc type) is nightly-only; +// `pub const Data = T;` (inherent assoc type) is nightly-only; // callers can write `T` directly. impl Node { @@ -119,14 +106,10 @@ impl Node { } count } - - // PORT NOTE: `pub inline fn release(node: *Node) void { Parent.release(node) }` - // is expressed as `ObjectPool::::release(node)` at call sites; see - // module-level note above. } pub struct SinglyLinkedList { - // INTRUSIVE: pool.zig:59 — list head; popFirst hands node to caller + // INTRUSIVE: list head; pop_first hands node to caller pub first: *mut Node, } @@ -176,8 +159,8 @@ impl SinglyLinkedList { // SAFETY: self.first is non-null (else the `==` above would have // matched the null `node`, which callers never pass) let mut current_elm = self.first; - // SAFETY: walk live list nodes; Zig's `.?` would panic on null — - // mirror that with an unchecked deref. + // SAFETY: `node` is in this list (caller contract), so the walk + // visits only live nodes and reaches `node` before hitting null. unsafe { while (*current_elm).next != node { current_elm = (*current_elm).next; @@ -219,35 +202,28 @@ impl SinglyLinkedList { const LOG_ALLOCATIONS: bool = false; -/// Behavior hooks the Zig version expressed via `comptime Init: ?fn(...)` and -/// `std.meta.hasFn(Type, "reset")`. Per PORTING.md §Comptime reflection, -/// optional-decl checks become a trait with default methods. +/// Behavior hooks for pooled types: optional initialization and per-reuse +/// reset. pub trait ObjectPoolType: Sized { - /// Mirrors `comptime Init: ?fn(allocator) anyerror!Type`. `None` ⇒ the - /// Zig path that left `data` as `undefined`. + /// Optional initializer for freshly allocated nodes. `None` ⇒ `data` + /// starts uninitialized. const INIT: Option Result> = None; - /// Mirrors `if (std.meta.hasFn(Type, "reset")) node.data.reset()`. - /// Default is a no-op; types that had `.reset()` in Zig override this. + /// Called when a node is reused from the free list. Default is a no-op. #[inline] fn reset(&mut self) {} } -/// Per-pool mutable state. Zig's `DataStruct`. +/// Per-pool mutable state. pub struct DataStruct { pub list: SinglyLinkedList, pub loaded: bool, - // PORT NOTE: Zig used `MaxCountInt = std.math.IntFittingRange(0, max_count)`. - // Rust const generics cannot pick an integer type from a const value; use - // `usize` and accept the few extra bytes. - // PERF(port): was IntFittingRange — narrow if it shows up on a hot path. pub count: usize, } impl Default for DataStruct { fn default() -> Self { Self { - // PORT NOTE: Zig had `list: LinkedList = undefined` — we zero it. list: SinglyLinkedList::default(), loaded: false, count: 0, @@ -268,7 +244,7 @@ pub struct ObjectPool< S = UnwiredStorage, >(core::marker::PhantomData<(T, S)>); -// PORT NOTE: `pub const List = SinglyLinkedList(T)` / `pub const Node = Node(T)` +// `pub const List = SinglyLinkedList(T)` / `pub const Node = Node(T)` // inherent assoc types are nightly-only; callers write `SinglyLinkedList` / // `Node` directly. @@ -309,8 +285,7 @@ impl ObjectPoolTrait } /// RAII handle for a pooled `T`. Derefs to the inner value; on `Drop`, the -/// node is returned to its pool. Replaces the Zig `get()` + `defer release()` -/// pair. +/// node is returned to its pool. pub struct PoolGuard<'a, T: ObjectPoolType + 'static> { node: *mut Node, release: unsafe fn(&mut Node), @@ -365,7 +340,7 @@ where // but we don't want to create 3 global variables per pool // instead, we create one global variable per pool // - // PORT NOTE: Rust cannot place a `static` / `thread_local!` inside a + // Rust cannot place a `static` / `thread_local!` inside a // generic `impl`; storage is supplied via the `S: PoolStorage` type // parameter (see `object_pool!` for the usual declaration). #[inline] @@ -385,8 +360,6 @@ where pub fn push(pooled: T) { if cfg!(debug_assertions) { - // PORT NOTE: Zig gated on `env.allow_assert`; that is - // `Environment.isDebug` ⇒ `cfg!(debug_assertions)`. debug_assert!(!Self::full()); } @@ -424,7 +397,7 @@ where unsafe { (*Self::get_node()).data.as_mut_ptr() } } - /// Zig `get()` — pop a node from the free list or allocate a fresh one. + /// Pop a node from the free list or allocate a fresh one. /// /// When `T::INIT == None` and a fresh node is allocated, the returned /// node's `data` is **uninitialized**; the caller must write a valid `T` @@ -450,12 +423,13 @@ where } if LOG_ALLOCATIONS { - // PORT NOTE: Zig wrote to stderr via std.fs; banned here. Route - // through `bun_core::Output` if this is ever flipped on. - // TODO(port): log "Allocate {type_name} - {size} bytes" + let _ = bun_core::output::File::stderr().write_fmt(format_args!( + "Allocate {} - {} bytes\n", + core::any::type_name::(), + core::mem::size_of::() + )); } - // Matches Zig's `data = if (Init) |i| i(..) else undefined` (pool.zig:203). // For `INIT == None` the bytes stay uninitialized; the caller MUST write // `data` before any read (and before `release()`, since `destroy_node` // assumes it is initialized when dropping). @@ -508,7 +482,11 @@ where let mut d = cell.borrow_mut(); if MAX_COUNT > 0 && d.count >= MAX_COUNT { if LOG_ALLOCATIONS { - // TODO(port): log "Free {type_name} - {size} bytes" + let _ = bun_core::output::File::stderr().write_fmt(format_args!( + "Free {} - {} bytes\n", + core::any::type_name::(), + core::mem::size_of::() + )); } return true; } @@ -550,12 +528,6 @@ where } fn destroy_node(node: *mut Node) { - // TODO(port): Zig special-cased `Type != bun.Vec` here to skip - // `bun.memory.deinit(&node.data)` for `Vec` (a known leak the Zig - // comment calls out). In Rust, dropping `T` is the moral equivalent of - // `bun.memory.deinit`. If `Vec` (the `Vec` port) must keep - // leaking for compat, gate its `Drop` there — not here. - // // SAFETY: `node` was created via `heap::alloc` in `push`/`get` and // is exclusively owned by the caller. `data` is initialized: `destroy_node` // is only reached from `release()` (caller had a usable node, so `data` @@ -584,7 +556,7 @@ where /// `pub type $Name = ObjectPool<$T, .., $Storage>` alias. `threadsafe` ⇒ /// `thread_local!` (one free list per thread); `global` ⇒ a single /// process-wide `RefCell` (caller is responsible for not touching it from -/// multiple threads — matches the Zig `threadsafe = false` mode). +/// multiple threads). #[macro_export] macro_rules! object_pool { ($vis:vis $name:ident : $ty:ty, threadsafe, $max:expr) => { @@ -637,11 +609,9 @@ macro_rules! __object_pool_storage { fn with( f: impl FnOnce(&::core::cell::RefCell<$crate::pool::DataStruct<$ty>>) -> R, ) -> R { - // PORT NOTE: Zig's `threadsafe = false` used a plain global - // `var data`; Rust forbids non-`Sync` statics, so this still + // Rust forbids non-`Sync` statics, so the "global" mode still // expands to a thread-local. Single-threaded callers see the - // same one cell; cross-thread callers get per-thread pools - // (a slight behaviour difference, but safe). + // same one cell; cross-thread callers get per-thread pools. ::std::thread_local! { static __OBJECT_POOL_DATA: ::core::cell::RefCell< $crate::pool::DataStruct<$ty> @@ -671,8 +641,7 @@ macro_rules! __paste_storage { // is in `bun_core` (post `bun_string` merge). // ────────────────────────────────────────────────────────────────────────── -/// Zig: `Npm.Registry.BodyPool = ObjectPool(MutableString, MutableString.init2048, true, 8)` -/// (src/install/npm.zig). Init = `init2048`; reuse = `.reset()`. +/// Init = `init2048`; reuse = `.reset()`. impl ObjectPoolType for bun_core::MutableString { const INIT: Option Result> = Some(|| bun_core::MutableString::init2048().map_err(Into::into)); @@ -681,5 +650,3 @@ impl ObjectPoolType for bun_core::MutableString { bun_core::MutableString::reset(self); } } - -// ported from: src/collections/pool.zig diff --git a/src/collections/string_map.rs b/src/collections/string_map.rs index 9f29691412d..bd7ee5dc3de 100644 --- a/src/collections/string_map.rs +++ b/src/collections/string_map.rs @@ -1,5 +1,3 @@ -//! Port of `bun.StringMap` (`src/bun.zig`). -//! //! A `StringArrayHashMap>` plus a `dupe_keys` flag controlling //! whether `insert` clones the key bytes. Values are always cloned. @@ -19,7 +17,6 @@ impl Default for StringMap { } impl StringMap { - /// Zig `init(allocator, dupe_keys)` — allocator dropped (global mimalloc). pub fn init(dupe_keys: bool) -> Self { Self { map: StringArrayHashMap::default(), @@ -49,20 +46,18 @@ impl StringMap { self.map.count() } - /// Zig `insert` / `put`: dupe `value`; dupe `key` only when `dupe_keys` - /// and the key is new. (When `dupe_keys == false` Zig stored a borrowed - /// slice; here `Box<[u8]>` forces a copy regardless — the flag is kept for - /// API parity and to skip the redundant second copy.) + /// Dupe `value`; `key` is duped on miss regardless (`Box<[u8]>` forces a + /// copy), so the `dupe_keys` flag is kept for API parity only. pub fn insert(&mut self, key: &[u8], value: &[u8]) -> Result<(), AllocError> { let entry = self.map.get_or_put(key)?; - // get_or_put already boxed `key` on miss; the Zig `dupe_keys` branch - // would dupe again here — that's the same allocation, so skip it. + // get_or_put already boxed `key` on miss; duping again here would be + // the same allocation, so skip it. let _ = self.dupe_keys; *entry.value_ptr = Box::from(value); Ok(()) } - /// Alias matching Zig `pub const put = insert;`. + /// Alias for [`insert`](Self::insert). #[inline] pub fn put(&mut self, key: &[u8], value: &[u8]) -> Result<(), AllocError> { self.insert(key, value) @@ -72,10 +67,15 @@ impl StringMap { self.map.get(key).map(|v| &**v) } - // Zig `sort` takes an `anytype` ctx; defer until a caller needs it. - // TODO(port): StringMap::sort — wire once ArrayHashMap::sort lands. + /// Forwards to the inner map's order-preserving sort. The closure + /// receives the parallel key/value slices plus the two indices so it can + /// compare on either column. + pub fn sort( + &mut self, + less_than: impl FnMut(&[Box<[u8]>], &[Box<[u8]>], usize, usize) -> bool, + ) { + self.map.sort(less_than); + } // `deinit` → Drop on the inner Vecs. } - -// ported from: src/bun.zig diff --git a/src/collections/vec_ext.rs b/src/collections/vec_ext.rs index bd7328df8c3..dc55044075a 100644 --- a/src/collections/vec_ext.rs +++ b/src/collections/vec_ext.rs @@ -1,8 +1,8 @@ -//! `VecExt` / `ByteVecExt` — Zig-ported method vocabulary on `Vec`. +//! `VecExt` / `ByteVecExt` — extension method vocabulary on `Vec`. //! //! Migration shim from the deleted `BabyList` (see //! `docs/BABYLIST_REPLACEMENT.md`): every former `BabyList` site is now a -//! plain `Vec`, and these traits supply the Zig method names (`.slice()`, +//! plain `Vec`, and these traits supply the legacy method names (`.slice()`, //! `.append()`, `.init_capacity()`, …) so call sites needed only a type-level //! rewrite. `Vec` aborts on OOM, so these methods are infallible and return //! `T` / `()` directly (the original `Result<_, AllocError>` shim has been @@ -30,8 +30,7 @@ pub trait VecExt: Sized { fn move_from_list(list: Vec) -> Self; fn from_owned_slice(items: Box<[T]>) -> Self; fn init_with_buffer_vec(buffer: Vec) -> Self; - /// Arena-builder → owned `Vec`. In Zig this was zero-copy (arena ptr - /// adopted as `Borrowed`); in the Rust port the linker always called + /// Arena-builder → owned `Vec`. The linker always calls /// `transfer_ownership` afterwards (full copy), so doing the copy up-front /// here is no worse and lets the arena round-trip disappear. /// @@ -131,8 +130,7 @@ pub trait VecExt: Sized { unsafe fn writable_slice(&mut self, additional: usize) -> &mut [T]; /// # Safety /// As [`writable_slice`] but skips `reserve`; caller must guarantee - /// `len + additional <= capacity` (debug-asserted). Zig: - /// `ArrayList.addManyAsSliceAssumeCapacity`. + /// `len + additional <= capacity` (debug-asserted). unsafe fn writable_slice_assume_capacity(&mut self, additional: usize) -> &mut [T]; /// # Safety /// As [`writable_slice`] but uses `reserve_exact` so the allocation grows @@ -217,7 +215,7 @@ impl VecExt for Vec { #[inline] fn move_from_list(list: Vec) -> Self { // Mirror of the `move_to_list` fast-path: when `A == Global` this is a - // pointer adopt (Zig `moveFromList`, baby_list.zig:46), not a realloc. + // pointer adopt, not a realloc. // Hot Global callers: `FileReader`, `ByteStream`, `shell::Cmd`. if core::any::TypeId::of::() == core::any::TypeId::of::() { let mut list = core::mem::ManuallyDrop::new(list); @@ -568,7 +566,7 @@ impl VecExt for Vec { } } -/// `Vec`-only helpers (Zig `Vec(u8)` extension methods). +/// `Vec`-only helpers. pub trait ByteVecExt { fn append_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<(), AllocError>; fn write(&mut self, str: &[u8]) -> Result; @@ -730,9 +728,8 @@ impl OffsetByteList { /// [`bun_alloc::ArenaVec`]) does not satisfy. `src` and `dst` may use /// distinct allocators. /// -/// Ports the open-coded `reserve → ptr::copy(shift) → copy_nonoverlapping → -/// set_len` pattern that translated Zig's `bun.copy`/`@memcpy` splice for -/// non-`Copy` element types. +/// Implemented as `reserve → ptr::copy(shift) → copy_nonoverlapping → +/// set_len`, which works for non-`Copy` element types. pub fn prepend_from(dst: &mut Vec, src: &mut Vec) { let src_len = src.len(); if src_len == 0 { diff --git a/src/collections/zig_hash_map.rs b/src/collections/zig_hash_map.rs index f8753b433a8..f50a7f21a7d 100644 --- a/src/collections/zig_hash_map.rs +++ b/src/collections/zig_hash_map.rs @@ -1,15 +1,12 @@ -//! Port of Zig's `std.HashMapUnmanaged` — open-addressing, linear-probe, -//! tombstone-on-delete, power-of-two capacity, 80% max load. Layout (and -//! therefore iteration order) must match the Zig spec exactly because callers -//! snapshot the iteration sequence (e.g. lockfile debug stringify). +//! Open-addressing hash map — linear-probe, tombstone-on-delete, power-of-two +//! capacity, 80% max load. Layout (and therefore iteration order) is +//! load-bearing: callers snapshot the iteration sequence (e.g. lockfile debug +//! stringify). //! -//! Storage differs from Zig's single-allocation `[Header][meta][keys][values]`: -//! `Vec` for metadata + `Vec>` for slots. This costs an -//! `Option` discriminant per slot but keeps the implementation in safe Rust; -//! slot indices and the metadata state machine are bit-identical so iteration -//! order matches. -//! -//! Spec: vendor/zig/lib/std/hash_map.zig +//! Storage is split: `Vec` for metadata + `Vec>` for +//! slots. This costs an `Option` discriminant per slot but keeps the +//! implementation in safe Rust; slot indices and the metadata state machine +//! determine iteration order. use core::borrow::Borrow; use core::hash::Hash; @@ -18,7 +15,6 @@ use core::marker::PhantomData; use crate::identity_context::{IdentityContext, IdentityHash}; // ─── Metadata byte ───────────────────────────────────────────────────────── -// Zig: `packed struct { fingerprint: u7, used: u1 }` — LSB-first packing, so // bit 7 = `used`, bits 0..7 = `fingerprint`. const SLOT_FREE: u8 = 0x00; // used=0, fp=0 const SLOT_TOMBSTONE: u8 = 0x01; // used=0, fp=1 @@ -39,7 +35,7 @@ fn meta_is_tombstone(m: u8) -> bool { fn meta_fingerprint(m: u8) -> u8 { m & 0x7F } -/// Zig `Metadata.takeFingerprint`: top 7 bits of the 64-bit hash. +/// Top 7 bits of the 64-bit hash. #[inline] fn take_fingerprint(hash: u64) -> u8 { (hash >> (64 - 7)) as u8 @@ -50,22 +46,20 @@ fn meta_fill(fp: u8) -> u8 { } const MINIMAL_CAPACITY: u32 = 8; -/// Zig `default_max_load_percentage`. All Bun-side `std.HashMap` instantiations -/// use 80; the const-generic load-factor parameter is dropped here. +/// Maximum load percentage before growth. All instantiations use 80, so this +/// is a const rather than a generic parameter. const MAX_LOAD_PERCENTAGE: u64 = 80; #[inline] fn capacity_for_size(size: u32) -> u32 { - // Zig: `((size * 100) / max_load + 1).ceilPowerOfTwo()` let new_cap = ((size as u64 * 100) / MAX_LOAD_PERCENTAGE + 1) as u32; new_cap.next_power_of_two() } // ─── HashContext ─────────────────────────────────────────────────────────── -// Zig threads a `Context` value with `hash(K) -> u64` / `eql(K, K) -> bool`. -// All Bun contexts are zero-sized, so model as a stateless trait keyed on the -// marker type. `AutoHashContext` covers `std.AutoHashMap`; `IdentityContext` -// covers the `hash(k) == k` case used for pre-hashed keys. +// The hash/eql strategy is a stateless trait keyed on a zero-sized marker +// type. `AutoHashContext` is the default; `IdentityContext` covers the +// `hash(k) == k` case used for pre-hashed keys. /// Hash/eql strategy for [`HashMap`]. Implement on a zero-sized marker type. pub trait HashContext { @@ -80,10 +74,8 @@ pub struct AutoHashContext; impl HashContext for AutoHashContext { #[inline] fn ctx_hash(key: &K) -> u64 { - // Zig autoHash for unique-repr types is `Wyhash.hash(0, asBytes(&key))`. - // Routing through `core::hash::Hash` + wyhash is the closest stable - // approximation without per-type byte-layout plumbing; exact AutoContext - // bucket order isn't relied on by any test today. + // wyhash routed through `core::hash::Hash`; exact bucket order for + // this context isn't relied on by any test today. bun_wyhash::auto_hash(key) } #[inline] @@ -133,7 +125,6 @@ impl HashMap { Self::default() } - /// Zig `count`/std `len`. #[inline] pub fn len(&self) -> usize { self.size as usize @@ -151,13 +142,13 @@ impl HashMap { self.metadata.len() } - /// Zig `deinit` — release storage. + /// Release storage. #[inline] pub fn deinit(&mut self) { *self = Self::default(); } - /// Zig `clearRetainingCapacity`. + /// Clear all entries, retaining capacity. pub fn clear(&mut self) { if self.metadata.is_empty() { return; @@ -172,12 +163,12 @@ impl HashMap { self.available = ((self.metadata.len() as u64 * MAX_LOAD_PERCENTAGE) / 100) as u32; } - /// Zig `lockPointers` — debug-mode pointer-stability assertion. No-op stub - /// kept so the Zig lock/unlock bracketing translates without `#[cfg]` noise - /// at every call site (see `SavedSourceMap`). + /// Debug-mode pointer-stability assertion. No-op stub kept so callers can + /// keep their lock/unlock bracketing without `#[cfg]` noise at every call + /// site (see `SavedSourceMap`). #[inline] pub fn lock_pointers(&self) {} - /// Zig `unlockPointers` — see [`lock_pointers`]. + /// See [`lock_pointers`](Self::lock_pointers). #[inline] pub fn unlock_pointers(&self) {} @@ -219,9 +210,8 @@ impl> HashMap { m } - /// Zig `ensureTotalCapacity` — grow so `new_size` elements fit without - /// further allocation. `Result` kept for call-site `?` symmetry with the - /// Zig OOM-propagating API. + /// Grow so `new_size` elements fit without further allocation. `Result` + /// kept for call-site `?` symmetry. pub fn ensure_total_capacity(&mut self, new_size: usize) -> Result<(), bun_alloc::AllocError> { let new_size = new_size as u32; if new_size > self.size { @@ -230,7 +220,6 @@ impl> HashMap { Ok(()) } - /// Zig `ensureUnusedCapacity`. pub fn ensure_unused_capacity( &mut self, additional: usize, @@ -293,8 +282,7 @@ impl> HashMap { *self = map; } - /// Zig `putAssumeCapacityNoClobber` — linear-probe insert assuming key - /// absent and `available > 0`. + /// Linear-probe insert assuming the key is absent and `available > 0`. fn put_assume_capacity_no_clobber(&mut self, key: K, value: V) { let cap = self.metadata.len(); let hash = C::ctx_hash(&key); @@ -314,7 +302,7 @@ impl> HashMap { self.size += 1; } - /// Zig `getIndex` — probe for `key`, stop on free, skip tombstones. + /// Probe for `key`, stop on free, skip tombstones. fn get_index(&self, key: &Q) -> Option where K: Borrow, @@ -385,13 +373,13 @@ impl> HashMap { self.get_index(key).is_some() } - /// Zig `contains` — `std::HashMap` spells this `contains_key`. + /// Alias for [`contains_key`](Self::contains_key). #[inline] pub fn contains(&self, key: &K) -> bool { self.get_index(key).is_some() } - /// Zig `getOrPutAssumeCapacityAdapted` lifted to grow-on-demand. Returns + /// Grow-on-demand insert-or-lookup probe. Returns /// the slot index and whether it was already occupied; the slot is left /// `None` on miss so the caller can write the (K, V) pair. fn get_or_put_slot(&mut self, key: &K) -> (usize, bool) { @@ -428,10 +416,9 @@ impl> HashMap { (idx, false) } - /// Zig `getOrPut`: single-probe insert-or-lookup. On miss the value slot is - /// left "undefined" in Zig; Rust cannot expose uninit through a `&mut V`, so - /// `V: Default` and the slot is default-initialised — callers overwrite - /// `*value_ptr` when `!found_existing`. + /// Single-probe insert-or-lookup. Uninit values cannot be exposed through + /// a `&mut V`, so `V: Default` and on miss the slot is default-initialised + /// — callers overwrite `*value_ptr` when `!found_existing`. pub fn get_or_put( &mut self, key: K, @@ -450,8 +437,8 @@ impl> HashMap { }) } - /// Zig `getOrPutContext` — alias kept for call-site parity; the context is - /// already bound by the type parameter. + /// Alias of [`get_or_put`](Self::get_or_put) kept for call-site parity; + /// the context is already bound by the type parameter. #[inline] pub fn get_or_put_context( &mut self, @@ -464,7 +451,7 @@ impl> HashMap { self.get_or_put(key) } - /// std `insert` / Zig `fetchPut` — returns the previous value if any. + /// std `insert` — returns the previous value if any. pub fn insert(&mut self, key: K, value: V) -> Option { let (idx, found_existing) = self.get_or_put_slot(&key); if found_existing { @@ -476,14 +463,14 @@ impl> HashMap { } } - /// Zig `put`: insert or overwrite. + /// Insert or overwrite. #[inline] pub fn put(&mut self, key: K, value: V) -> Result<(), bun_alloc::AllocError> { self.insert(key, value); Ok(()) } - /// Zig `putNoClobber`: insert asserting the key is new. + /// Insert asserting the key is new. pub fn put_no_clobber(&mut self, key: K, value: V) -> Result<(), bun_alloc::AllocError> { let prev = self.insert(key, value); debug_assert!(prev.is_none(), "putNoClobber: key already present"); @@ -498,7 +485,7 @@ impl> HashMap { kv } - /// std `remove` / Zig `fetchRemove` value half. + /// std `remove`. pub fn remove(&mut self, key: &Q) -> Option where K: Borrow, @@ -519,7 +506,7 @@ impl> HashMap { self.get_index(key).and_then(|i| self.remove_by_index(i)) } - /// Zig `fetchRemove` — remove and return the owned `{key, value}` pair. + /// Remove and return the owned `{key, value}` pair. pub fn fetch_remove(&mut self, key: &K) -> Option> { self.remove_entry(key) .map(|(k, v)| crate::hash_map::KV { key: k, value: v }) @@ -719,5 +706,3 @@ impl<'a, K, V, C: HashContext> VacantEntry<'a, K, V, C> { &self.key } } - -// ported from: vendor/zig/lib/std/hash_map.zig diff --git a/src/crash_handler/CPUFeatures.rs b/src/crash_handler/CPUFeatures.rs index c9deab1ab63..5736ea90433 100644 --- a/src/crash_handler/CPUFeatures.rs +++ b/src/crash_handler/CPUFeatures.rs @@ -1,7 +1,6 @@ // (no c types needed; kept for FFI clarity) use core::fmt; -// TODO(port): move to crash_handler_sys unsafe extern "C" { safe fn bun_cpu_features() -> u8; } @@ -11,11 +10,8 @@ pub(crate) struct CPUFeatures { pub flags: Flags, } -// Zig: `packed struct(u8)` per-arch. All semantic fields are `bool`; the trailing -// `padding: uN = 0` is unused bits. bitflags! models this directly (unknown bits -// = padding). Bit order matches Zig packed-struct LSB-first layout. -// PORT NOTE: guide says "bitflags! if every field is bool" — padding is uN, but -// it is pure padding, so bitflags is the correct shape here. +// Per-arch packed `u8`. All semantic fields are single bits; bitflags! models +// this directly (unknown bits = padding). Bit order is LSB-first. #[cfg(target_arch = "x86_64")] bitflags::bitflags! { @@ -51,8 +47,7 @@ bitflags::bitflags! { #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] compile_error!("CPUFeatures: unsupported target architecture"); -// Zig `inline for (@typeInfo(Flags).@"struct".fields)` — comptime reflection over -// field names, skipping "none" and "padding". Expanded to a const table per arch. +// Per-arch const table of flag names, skipping "none" and padding bits. #[cfg(target_arch = "x86_64")] const NAMED_FLAGS: &[(&str, Flags)] = &[ ("sse42", Flags::SSE42), @@ -108,8 +103,6 @@ impl CPUFeatures { #[cfg(target_arch = "x86_64")] { - // Zig: bun.analytics.Features.no_avx / no_avx2 are global mutable - // counters (`+= usize`). Rust port stores them as `AtomicUsize`. use core::sync::atomic::Ordering; bun_analytics::features::no_avx .fetch_add(usize::from(!flags.contains(Flags::AVX)), Ordering::Relaxed); @@ -120,5 +113,3 @@ impl CPUFeatures { CPUFeatures { flags } } } - -// ported from: src/crash_handler/CPUFeatures.zig diff --git a/src/crash_handler/handle_oom.rs b/src/crash_handler/handle_oom.rs index 4b16c972bdd..ac295d2fc0a 100644 --- a/src/crash_handler/handle_oom.rs +++ b/src/crash_handler/handle_oom.rs @@ -1,14 +1,10 @@ use bun_alloc::AllocError; use bun_core::Error; -// fn isOomOnlyError(comptime ErrorUnionOrSet: type) bool -// -// Zig's `isOomOnlyError` is pure comptime `@typeInfo` reflection over an -// error set: it iterates the set's members and checks every name == "OutOfMemory". -// Rust has no error-set reflection. The equivalent is encoded structurally in -// the `HandleOom` trait impls below — the `AllocError` impls ARE the -// "OOM-only" arm (Output = T / Output = !), and the `bun_core::Error` impls -// ARE the "other errors possible" arm (Output = Result / Output = E). +// "OOM-only" vs "other errors possible" is encoded structurally in the +// `HandleOom` trait impls below — the `AllocError` impls ARE the "OOM-only" +// arm (Output = T / Output = !), and the `bun_core::Error` impls ARE the +// "other errors possible" arm (Output = Result / Output = E). /// If `error_union_or_set` is `error.OutOfMemory`, calls `bun.outOfMemory`. Otherwise: /// @@ -29,7 +25,7 @@ use bun_core::Error; /// let thing = match allocate_thing() { Ok(v) => v, Err(err) => bun::handle_oom(err) }; /// ``` /// -/// PORT NOTE: In Rust, `Vec`/`Box` allocation already aborts on OOM via the +/// In Rust, `Vec`/`Box` allocation already aborts on OOM via the /// global allocator's `handle_alloc_error`. Per PORTING.md §Allocators, /// callsites of `bun.handleOom(expr)` translate to bare `expr`. This function /// remains for the residual cases where a `Result` is threaded @@ -38,10 +34,8 @@ pub fn handle_oom(error_union_or_set: A) -> A::Output { error_union_or_set.handle_oom() } -/// Encodes Zig's comptime return-type block (`return_type: { ... }`) of -/// `handleOom`. The Zig branched on `@typeInfo(ArgType)` (error_union vs -/// error_set) and on `isOomOnlyError(ArgType)`; each impl below is one arm of -/// that comptime switch. +/// Output-type selection for [`handle_oom`]: each impl below is one arm of +/// the input-shape × OOM-only matrix (see the section comments). pub trait HandleOom { type Output; fn handle_oom(self) -> Self::Output; @@ -69,8 +63,6 @@ impl HandleOom for AllocError { } // ── .error_union, mixed error set → same union with OOM subtracted ─────── -// Zig computed the narrowed type via -// `@TypeOf(switch (err) { error.OutOfMemory => unreachable, else => |e| e })`. // Rust error enums are nominal, not sets — there is no set subtraction. For // the catch-all `bun_core::Error` we compare against the interned tag and // return the same type. Per-crate `thiserror` enums that carry an @@ -97,5 +89,3 @@ impl HandleOom for Error { } } } - -// ported from: src/crash_handler/handle_oom.zig diff --git a/src/crash_handler/lib.rs b/src/crash_handler/lib.rs index 012fc03799a..0e9e4b57193 100644 --- a/src/crash_handler/lib.rs +++ b/src/crash_handler/lib.rs @@ -14,9 +14,6 @@ //! users from having to download symbols, which can be very large. //! //! The remapper is open source: https://github.com/oven-sh/bun.report -//! -//! A lot of this handler is based on the Zig Standard Library implementation -//! for std.debug.panicImpl and their code for gathering backtraces. // The cfg is the union of the two intrinsic call sites: `abort()` on the // non-Windows crash path (all profiles) and `breakpoint()` on Windows debug @@ -34,8 +31,7 @@ pub mod handle_oom; /// Link-time target for `bun_alloc::out_of_memory()` — declared /// `extern "Rust"` in `bun_alloc` (which is below this crate in the dep graph) -/// and defined here. Mirrors `src/bun.zig:outOfMemory()` → -/// `crash_handler.crashHandler(.out_of_memory, null, @returnAddress())`. +/// and defined here. /// `pub(crate)` so external callers route through the T0 `bun_alloc` entry /// rather than bypassing it. #[cold] @@ -70,9 +66,7 @@ pub use draft::*; // ────────────────────────────────────────────────────────────────────────── // Local shim for `bun_debug` (no such crate exists yet). These are -// std.debug.* placeholders the Zig side leaned on; the Rust port will replace -// them with a real debug-info backend in a later pass. -// TODO(port): bun_debug::SelfInfo / SourceLocation / TtyConfig / capture_stack_trace +// placeholders to be replaced with a real debug-info backend in a later pass. // ────────────────────────────────────────────────────────────────────────── pub mod debug { use super::draft::StackTrace; @@ -85,14 +79,14 @@ pub mod debug { bun_core::return_address() } - /// Zig: `std.debug.captureStackTrace`. Thin re-export of the canonical safe + /// Thin re-export of the canonical safe /// wrapper in bun_core so this crate's internal callers don't churn. #[inline] pub(crate) fn capture_stack_trace(begin: usize, addrs: &mut [usize]) -> usize { bun_core::capture_stack_trace(begin, addrs) } - /// Zig: `std.debug.panicImpl` fallback when ENABLE == false. + /// Fallback when ENABLE == false. pub fn panic_impl(_ert: Option<&StackTrace<'_>>, _begin: Option, msg: &[u8]) -> ! { panic!("{}", bstr::BStr::new(msg)) } @@ -100,8 +94,8 @@ pub mod debug { pub(crate) const HAVE_ERROR_RETURN_TRACING: bool = false; pub(crate) const STRIP_DEBUG_INFO: bool = !cfg!(debug_assertions); - // ── SelfInfo (vendor/zig/lib/std/debug/SelfInfo.zig) ───────────────── - // D104: canonical home for the dladdr-backed `std.debug.SelfInfo` shim. + // ── SelfInfo ────────────────────────────────────────────────────────── + // D104: canonical home for the dladdr-backed self-debug-info shim. // Previously lived in `bun_jsc::btjs::zig_std_debug`; relocated here so the // crash handler (lower-tier crate) gets real symbol names in debug builds // and `btjs` re-exports from this module. @@ -118,14 +112,11 @@ pub mod debug { address_map: HashMap>, } - /// Port of `SelfInfo.Module`. On Linux Zig uses `Dwarf.ElfModule`; on Darwin a - /// MachO symbol table reader. Both ultimately resolve `address → {name, CU, - /// source_location}`. The DWARF/MachO parsers are not ported; `dladdr(3)` + /// A loaded module, resolving `address → {name, CU, source_location}`. + /// There is no DWARF/MachO parser; `dladdr(3)` /// provides the symbol-name half (which is what `btjs` actually consumes for /// its `__`/`_llint_call_javascript` prefix checks). `source_location` is left /// `None`, which `print_line_info` already handles. - // PORT NOTE: full `readElfDebugInfo`/`readMachODebugInfo` (~2k LOC of DWARF) not - // ported — `dladdr` is the libc-level equivalent for symbol-name resolution. pub struct Module { base_address: usize, name: Box<[u8]>, @@ -223,8 +214,7 @@ pub mod debug { #[cfg(target_vendor = "apple")] fn lookup_module_dyld(&mut self, address: usize) -> Result<&mut Module, Error> { - // PORT NOTE: Zig walks `_dyld_get_image_header` + LoadCommandIterator. `dladdr` - // gives the same `{base_address, fname}` pair on Darwin without the MachO walk. + // `dladdr` gives the `{base_address, fname}` pair on Darwin without a MachO walk. let mut info: libc::Dl_info = bun_core::ffi::zeroed(); // SAFETY: dladdr only reads; out-param is a valid Dl_info. let rc = unsafe { libc::dladdr(address as *const c_void, &raw mut info) }; @@ -253,12 +243,10 @@ pub mod debug { /// Port of `Module.getSymbolAtAddress`. #[cfg(windows)] pub fn get_symbol_at_address(&mut self, address: usize) -> Result { - // TODO(port-windows): SPEC DIVERGENCE — Zig's `std.debug.SelfInfo` - // resolves symbols on Windows via the loaded PE's PDB - // (`dbghelp.dll` `SymFromAddr`). That path is not yet ported, so + // Windows symbol resolution via the loaded PE's PDB + // (`dbghelp.dll` `SymFromAddr`) is not implemented yet, so // every Windows backtrace currently prints bare addresses even - // when a PDB is shipped. This is NOT equivalent to the Zig spec - // for symbol-bearing builds; return the default-initialized + // when a PDB is shipped. Return the default-initialized // `Symbol` (`name = "???"`) so the caller still prints the // address line, but the dbghelp lookup must be implemented // before Windows crash reports are usable. @@ -277,7 +265,7 @@ pub mod debug { // SAFETY: dladdr only reads; out-param is a valid Dl_info. let rc = unsafe { libc::dladdr(address as *const c_void, &raw mut info) }; if rc == 0 || info.dli_sname.is_null() { - // Zig returns a default-initialized `Symbol` (`.{}` — name "???") here + // Return a default-initialized `Symbol` (name "???") here // rather than erroring, so the caller still prints the address line. return Ok(SymbolInfo { name: b"???".to_vec().into_boxed_slice(), @@ -301,7 +289,7 @@ pub mod debug { Ok(SymbolInfo { name, compile_unit_name, - // PORT NOTE: DWARF line-table lookup not ported; dladdr does not provide + // DWARF line-table lookup is not ported; dladdr does not provide // file:line. `print_line_info` handles `None` by printing `???:?:?`. source_location: None, }) @@ -327,18 +315,17 @@ pub mod debug { Some(bun_paths::basename(name).to_vec().into_boxed_slice()) } - // ── std.debug.getSelfDebugInfo ─────────────────────────────────────── + // ── self debug-info singleton ──────────────────────────────────────── // PORTING.md §Global mutable state: lazy debug-only singleton. RacyCell — // only called from a stopped/crashing process (lldb or the crash handler // after `panicking` has serialized), so no concurrent access; callers // reborrow the returned `*mut` per-access. static SELF_DEBUG_INFO: bun_core::RacyCell> = bun_core::RacyCell::new(None); - /// Port of `std.debug.getSelfDebugInfo`. NOT thread-safe (the Zig original - /// has the same `TODO multithreaded awareness` caveat). + /// NOT thread-safe. pub fn get_self_debug_info() -> Result<*mut SelfInfo, Error> { - // SAFETY: Zig's `var self_debug_info: ?SelfInfo = null` is also a plain - // mutable global; this is debug-only and invoked from a stopped process. + // SAFETY: this is debug-only and invoked from a stopped/crashing + // process (see SELF_DEBUG_INFO above), so no concurrent access. unsafe { let slot = &mut *SELF_DEBUG_INFO.get(); if let Some(info) = slot { @@ -348,7 +335,7 @@ pub mod debug { Ok(std::ptr::from_mut(slot.as_mut().unwrap())) } } - /// Zig: `std.io.tty.detectConfig(std.io.getStdErr())`. + /// Detect whether stderr supports ANSI color escapes. #[allow(dead_code)] pub(crate) fn detect_tty_config_stderr() -> TtyConfig { if bun_core::Output::ENABLE_ANSI_COLORS_STDERR.load(core::sync::atomic::Ordering::Relaxed) { @@ -357,7 +344,7 @@ pub mod debug { TtyConfig::NoColor } } - /// Port of `std.io.tty.Config` (vendor/zig/lib/std/Io/tty.zig). The + /// TTY color configuration. A /// `windows_api` variant is omitted: every consumer here writes into an /// in-memory buffer or raw fd 2, never the live `CONSOLE_SCREEN_BUFFER`, so /// `SetConsoleTextAttribute` would colour the wrong stream. @@ -366,7 +353,7 @@ pub mod debug { NoColor, EscapeCodes, } - /// Port of `std.io.tty.Color` — only the variants Bun actually emits. + /// Terminal colors — only the variants Bun actually emits. #[derive(Clone, Copy, PartialEq, Eq)] pub enum Color { Bold, @@ -378,7 +365,7 @@ pub mod debug { BrightCyan, } impl TtyConfig { - /// Port of `std.io.tty.Config.setColor`. + /// Write the escape sequence for `color` (no-op when colors are disabled). pub fn set_color( self, w: &mut W, @@ -423,8 +410,7 @@ impl Write for StderrWriter { fn write_all(&mut self, bytes: &[u8]) -> Result<(), bun_core::Error> { #[cfg(windows)] { - // Zig spec: `std.fs.File.stderr().writerStreaming(&.{})` — on - // Windows that is `GetStdHandle(STD_ERROR_HANDLE)` + kernel32 + // On Windows this is `GetStdHandle(STD_ERROR_HANDLE)` + kernel32 // `WriteFile`, NOT the CRT. Routing through MSVCRT `_write(2,…)` // would (1) text-mode-translate `\n`→`\r\n` and (2) take the CRT // per-fd lock, which can self-deadlock when the VEH crash handler @@ -500,15 +486,15 @@ mod draft { e } - /// Zig: `Output.enable_ansi_colors_stderr` — runtime flag, exposed in Rust as an - /// `AtomicBool` static. Re-exported here so call sites read like the Zig. + /// Runtime flag, exposed as an + /// `AtomicBool` static. Re-exported here for shorter call sites. use bun_core::output::enable_ansi_colors_stderr; - /// Zig: `std.posix.abort()`. On POSIX this is `libc::abort()` (async-signal-safe). - /// On Windows, Zig's `std.posix.abort()` is *not* MSVCRT `abort()` — it is - /// `if (Debug) @breakpoint(); kernel32.ExitProcess(3);`. UCRT `abort()` would + /// On POSIX this is `libc::abort()` (async-signal-safe). + /// On Windows this is *not* MSVCRT `abort()` — it is + /// `if (Debug) breakpoint(); kernel32.ExitProcess(3);`. UCRT `abort()` would /// raise SIGABRT, may print `R6010 - abort() has been called` to stderr, and - /// can pop a Watson/WER dialog — none of which the Zig spec does. + /// can pop a Watson/WER dialog — none of which we want here. #[inline(always)] fn abort() -> ! { #[cfg(windows)] @@ -526,8 +512,8 @@ mod draft { use super::cpu_features::CPUFeatures; use super::debug::{Color, SelfInfo, SourceLocation, TtyConfig}; - /// Zig: `bun.fmt.fmtArgv` — print an argv vector as a shell-ish line. - /// crash_handler.zig:1024 calls this when the addr2line spawn fails. + /// Print an argv vector as a shell-ish line. + /// Called when the addr2line spawn fails. #[cfg(any(windows, target_os = "linux", target_os = "android"))] fn fmt_argv(w: &mut W, argv: &[Vec]) -> Result<(), bun_core::Error> { for (i, a) in argv.iter().enumerate() { @@ -541,10 +527,10 @@ mod draft { Ok(()) } - // TODO(port): `Cli` arrives from move-in (MOVE_DOWN bun_runtime::cli::Cli → crash_handler). - // Only the two bits the crash handler needs — main-thread check and the - // one-byte command tag for the trace URL — land here as plain globals that - // `bun_runtime` populates at startup. + // The two bits of CLI state the crash handler needs — main-thread check and + // the one-byte command tag for the trace URL — live here as plain globals + // that `bun_runtime` populates at startup (the full `Cli` stays in + // `bun_runtime::cli`, a higher-tier crate). pub mod cli_state { use core::sync::atomic::{AtomicU8, AtomicU64, Ordering}; @@ -567,7 +553,7 @@ mod draft { } } - // std.builtin.StackTrace lives in bun_core (T0); the debug-info types are local + // `StackTrace` lives in bun_core (T0); the debug-info types are local // shims (see `super::debug`) until a real bun_debug crate exists. pub use bun_core::StackTrace; @@ -584,7 +570,7 @@ mod draft { /// Non-zero whenever the program triggered a panic. /// The counter is incremented/decremented atomically. - /// PORT NOTE: shared with bun_core::PANICKING so T0 callers see the same state. + /// Shared with bun_core::PANICKING so T0 callers see the same state. use bun_core::PANICKING; // D131: dedup — these read the shared `PANICKING` atomic and were byte-identical // to the bun_core (T0) copies. Re-export so `bun_crash_handler::{is_panicking, @@ -620,7 +606,7 @@ mod draft { // PORTING.md §Concurrency: `bun_threading::Guarded>` instead of bare Mutex + global Vec. // Stores a boxed type-erased closure (not a bare fn pointer) so that // `append_pre_crash_handler` can monomorphize a wrapper that actually invokes the - // caller's typed handler — mirroring Zig's `comptime handler` trampoline. + // caller's typed handler. struct CrashHandlerEntry(*mut c_void, Box); // SAFETY: only accessed under the mutex; the opaque ptr is never dereferenced // except by the registered callback on the crash thread. @@ -638,7 +624,6 @@ mod draft { pub enum CrashReason { /// From @panic() Panic(&'static [u8]), - // TODO(port): lifetime — Zig holds a borrowed []const u8; using &'static here as a placeholder. /// "reached unreachable code" Unreachable, @@ -720,7 +705,13 @@ mod draft { Parse(&'static [u8]), Visit(&'static [u8]), Print(&'static [u8]), - // TODO(port): lifetime — these slices borrow caller-owned paths; &'static is a placeholder. + // These slices are stored in the `CURRENT_ACTION` thread-local, so they + // are typed `'static`. Callers pass `Source.path` data whose + // `Path<'static>` is itself an upstream `into_static()` lifetime + // erasure of arena/resolver-owned bytes (see paths/lib.rs); the data + // is not truly `'static`. Correctness relies on the `scoped_action` + // RAII guard restoring the thread-local before the owning arena or + // resolver storage is freed. #[cfg(feature = "show_crash_trace")] BundleGenerateChunk(BundleGenerateChunk), #[cfg(not(feature = "show_crash_trace"))] @@ -780,7 +771,7 @@ mod draft { } /// RAII guard returned by [`scoped_action`] / [`set_current_action_resolver`]. - /// Restores the previous `CURRENT_ACTION` on drop (Zig: `defer current_action = old`). + /// Restores the previous `CURRENT_ACTION` on drop. pub struct ActionGuard(Option); impl Drop for ActionGuard { #[inline] @@ -791,8 +782,7 @@ mod draft { /// Scoped `CURRENT_ACTION = action`. Snapshots the previous value, installs /// `action`, and returns an [`ActionGuard`] that restores the previous value - /// on drop. Zig: `const old = current_action; defer current_action = old; - /// current_action = ...;`. + /// on drop. #[inline] #[must_use] pub fn scoped_action(action: Action) -> ActionGuard { @@ -801,13 +791,13 @@ mod draft { ActionGuard(prev) } - /// Scoped `CURRENT_ACTION = .resolver{...}`. Zig (resolver.zig:672-679) sets - /// this only under `Environment.show_crash_trace` because module resolution is - /// extremely hot and has a low crash rate; the cfg-gate here mirrors that. + /// Scoped `CURRENT_ACTION = Resolver{...}`. Set + /// only under `Environment.show_crash_trace` because module resolution is + /// extremely hot and has a low crash rate. /// /// `source_dir`/`import_path` are caller-interned (DirnameStore / source text) /// and outlive the guard; the `&'static` lifetime erasure matches the existing - /// `Action::Parse`/`Visit`/`Print` slice fields (see TODO(port) above). + /// `Action::Parse`/`Visit`/`Print` slice fields (see the comment on those fields). #[inline] pub fn set_current_action_resolver( source_dir: &[u8], @@ -841,7 +831,7 @@ mod draft { /// pointers from `fp` (POSIX) / RtlCapture and trim by `pc` (Windows). `pc` /// becomes frame 0. Fault { pc: usize, fp: usize }, - /// A trace was already captured upstream (Zig error return traces). + /// A trace was already captured upstream. ErrorReturn(&'a StackTrace<'a>), /// Walk the current stack and trim the capture machinery above this PC. BeginAddr(usize), @@ -881,8 +871,8 @@ mod draft { // // Output.errorWriter() is not used here because it may not be configured // if the program crashes immediately at startup. - // TODO(port): std.fs.File.stderr().writerStreaming — local raw StderrWriter (bun_sys - // FileWriter only impls std::io::Write, not the local byte-Write trait) + // A local raw StderrWriter is used + // because bun_sys's FileWriter only impls std::io::Write, not the local byte-Write trait. let writer = &mut stderr_writer(); // The format of the panic trace is slightly different in debug @@ -939,9 +929,10 @@ mod draft { { abort(); } - } else if UNSUPPORTED_UV_FUNCTION.with(|c| c.get()).is_some() { - // TODO(port): bun_analytics::Features::unsupported_uv_function — using - // the threadlocal as a stand-in for the global counter check. + } else if bun_analytics::features::unsupported_uv_function + .load(Ordering::Relaxed) + > 0 + { let name: &[u8] = UNSUPPORTED_UV_FUNCTION .with(|c| c.get()) .map(|p| { @@ -1045,8 +1036,7 @@ mod draft { abort(); } // NOTE: `GetThreadDescription` heap-allocates `name` and the - // caller is meant to `LocalFree` it. The Zig spec leaks it - // identically (crash_handler.zig:316-322) — this runs on a + // caller is meant to `LocalFree` it. This runs on a // `noreturn` crash path immediately before `ExitProcess(3)`, // so the leak is intentional. } else { @@ -1069,7 +1059,6 @@ mod draft { target_os = "freebsd" ))] { /* no-op */ } - // TODO(port): wasm @compileError("TODO") } if writer.write_all(b": ").is_err() { @@ -1171,8 +1160,10 @@ mod draft { "Bun has encountered a crash while running the \"{s}\" native plugin.\n\nTo send a redacted crash report to Bun's team,\nplease file a GitHub issue using the link below:\n\n", true, ), bstr::BStr::new(native_plugin_name)).is_err() { abort(); } - } else if UNSUPPORTED_UV_FUNCTION.with(|c| c.get()).is_some() { - // TODO(port): bun_analytics::Features::unsupported_uv_function + } else if bun_analytics::features::unsupported_uv_function + .load(Ordering::Relaxed) + > 0 + { let name: &[u8] = UNSUPPORTED_UV_FUNCTION .with(|c| c.get()) .map(|p| { @@ -1260,7 +1251,7 @@ mod draft { // so that a crash will actually crash. We need this because we want the process to // exit with a signal, and allow tools to be able to gather core dumps. // - // This is done so late (in comparison to the Zig Standard Library's panic handler) + // This is done this late // because if multiple threads segfault (more often the case on Windows), we don't // want another thread to interrupt the crashing of the first one. reset_segfault_handler(); @@ -1278,7 +1269,6 @@ mod draft { ); Output::flush(); - // TODO(port): comptime assert void == @TypeOf(bun.reloadProcess(...)) bun_core::reload_process(false, true); } } @@ -1315,12 +1305,12 @@ mod draft { crash(); } - /// This is called when `main` returns a Zig error. + /// This is called when `main` returns an error. /// We don't want to treat it as a crash under certain error codes. pub fn handle_root_error(err: bun_core::Error, error_return_trace: Option<&StackTrace>) -> ! { use bun_core::{err_generic, pretty_error}; - /// Zig: `std.posix.getrlimit(.NOFILE)`. bun_sys::posix has no rlimit yet — + /// bun_sys::posix has no rlimit yet — /// thin libc wrapper (POD out-param, never fails on supported targets). #[cfg(unix)] fn getrlimit_nofile() -> Option { @@ -1440,7 +1430,7 @@ mod draft { } } else if err == bun_core::err!("NotOpenForReading") || err == bun_core::err!("Unexpected") { - // The usage of `unreachable` in Zig's std.posix may cause the file descriptor problem to show up as other errors + // The file descriptor problem may show up as other errors #[cfg(unix)] { // SAFETY: zeroed rlimit is valid POD (integers). @@ -1497,7 +1487,7 @@ mod draft { err_generic!("Bun could not find a package.json file to install from"); bun_core::note!("Run \"bun init\" to initialize a project"); } else { - // PORT NOTE: Zig picked the format string at comptime; the macros need + // The macros need // `:literal`, so branch on the const and call separately. if Environment::SHOW_CRASH_TRACE { err_generic!( @@ -1541,7 +1531,6 @@ mod draft { if msg == b"reached unreachable code" { CrashReason::Unreachable } else { - // TODO(port): lifetime — Zig borrows msg; erased to &'static for the noreturn path. // SAFETY: process is about to abort; the borrow is never invalidated. CrashReason::Panic(unsafe { bun_collections::detach_lifetime(msg) }) }, @@ -1647,7 +1636,7 @@ mod draft { #[cfg(unix)] extern "C" fn handle_segfault_posix(sig: c_int, info: *mut libc::siginfo_t, ctx: *mut c_void) { // SAFETY: kernel provides a valid siginfo_t; `si_addr` reads the per-platform - // sigfault address field (Zig: `info.fields.sigfault.addr` / `info.addr`). + // sigfault address field. let addr: usize = unsafe { (*info).si_addr() as usize }; crash_handler( @@ -1723,7 +1712,6 @@ mod draft { if Environment::ENABLE_ASAN { return; } - // Zig: std.posix.Sigaction{ .handler = .{ .sigaction = handleSegfaultPosix }, ... }. // SAFETY: zeroed sigaction is valid POD; we overwrite the fields we need. let mut act: libc::sigaction = bun_core::ffi::zeroed(); act.sa_sigaction = handle_segfault_posix as *const () as usize; @@ -1768,7 +1756,6 @@ mod draft { { reset_on_posix(); } - // TODO(port): wasm @compileError("TODO") install_hooks(); } @@ -1783,15 +1770,14 @@ mod draft { // (`__bun_crash_handler_out_of_memory` / `__bun_crash_handler_dump_stack_trace`) // — no runtime registration needed. // - // Route Rust `panic!()` through the trace-string + report path. Zig wires - // `pub const panic = bun.crash_handler.panic` at the root so every - // `@panic()` reports; the Rust port's bare `panic!` was printing the std - // default hook + unwinding with no trace string and no upload. + // Route Rust `panic!()` through the trace-string + report path. Without + // this hook, a bare `panic!` would print the std + // default hook + unwind with no trace string and no upload. std::panic::set_hook(Box::new(rust_panic_hook)); } /// `std::panic` hook: emit the same trace-string + auto-report as the fatal - /// `crash_handler()` path, then **abort** (matches Zig's `noreturn` panic). + /// `crash_handler()` path, then **abort**. /// With `panic = "abort"` no unwind starts after this hook returns, so there /// are no `catch_unwind` boundaries to reach. #[cold] @@ -1813,8 +1799,7 @@ mod draft { PANIC_STAGE.with(|s| s.set(1)); // Just the panic message — no `(file:line:col)` suffix. The call site is - // captured in the backtrace and symbolized there (matching Zig, which - // never appended a location to the message). With `-Zlocation-detail=none` + // captured in the backtrace and symbolized there. With `-Zlocation-detail=none` // in release the location would be `:0:0` anyway. let mut msg_buf = BoundedArray::::default(); { @@ -1927,15 +1912,13 @@ mod draft { // A Rust `panic!` is a bug. The process must not continue — with // `panic = "abort"` no unwind starts, so `catch_unwind` boundaries are - // unreachable for Rust panics. This matches Zig's - // `pub const panic = bun.crash_handler.panic` (which is `noreturn`). + // unreachable for Rust panics. crash(); } /// Adapter for non-fatal `bun_core::dump_current_stack_trace` callers - /// (fd.rs EBADF debug-warn, ref_count leak reports). Zig routes these through - /// `dumpStackTrace` which on Linux debug spawns `llvm-symbolizer` — but the - /// Rust debug binary's .debug_info is large enough that the symbolizer parse + /// (fd.rs EBADF debug-warn, ref_count leak reports). The + /// debug binary's .debug_info is large enough that an llvm-symbolizer parse /// alone costs ~5s, which is unacceptable on a hot non-fatal path /// (`closeSync(EBADF)` was timing out fs.test.ts at the 5s budget). For these /// advisory dumps we honour `frame_count` and use WTF's dladdr-based printer @@ -2034,7 +2017,7 @@ mod draft { bun_sys::windows::EXCEPTION_ILLEGAL_INSTRUCTION => { // `ExceptionAddress` is the faulting RIP for `STATUS_ILLEGAL_ // INSTRUCTION` (winnt.h); avoids depending on the arch-specific - // `CONTEXT` layout (Zig reached `ContextRecord.Rip` directly). + // `CONTEXT` layout. CrashReason::IllegalInstruction( unsafe { (*info.ExceptionRecord).ExceptionAddress } as usize ) @@ -2089,7 +2072,6 @@ mod draft { { let cpu_features = CPUFeatures::get(); - // TODO(port): bun_analytics::GenerateHeader::GeneratePlatform { #[cfg(any( all(target_os = "linux", target_env = "gnu"), @@ -2243,7 +2225,7 @@ mod draft { ) .map_err(fmt_err)?; - // TODO(port): {B:<3.2} byte-size formatting — bun_fmt::bytes() doesn't take width/prec yet + // bun_fmt::bytes() — human-readable metadata, not the trace string. write!( writer, "RSS: {} | Peak: {} | Commit: {} | Faults: {}", @@ -2283,7 +2265,6 @@ mod draft { if PANICKING.fetch_sub(1, Ordering::SeqCst) != 1 { // Another thread is panicking, wait for the last one to finish // and call abort() - // TODO(port): builtin.single_threaded → unreachable // Sleep forever without hammering the CPU let futex = AtomicU32::new(0); @@ -2307,10 +2288,8 @@ mod draft { impl Platform { // Rust cannot concat ident names at const time without a proc-macro; spell out the cfg matrix. const CURRENT: u8 = { - // Android folds into the Linux variants — Zig's `@tagName(Environment.os)` - // (crash_handler.zig:1153) yields `"linux"` for Android because Zig keeps - // it under `os.tag == .linux`. bun.report decodes the same single-char - // codes; introducing new ones would break older decoders. + // Android folds into the Linux variants. bun.report decodes the same + // single-char codes; introducing new ones would break older decoders. #[cfg(all( any(target_os = "linux", target_os = "android"), target_arch = "x86_64", @@ -2384,7 +2363,7 @@ mod draft { /// '2' - same as '1' but this build is known to be a canary build const VERSION_CHAR: &str = if Environment::IS_CANARY { "2" } else { "1" }; - // Zig: `if (git_sha.len > 0) git_sha[0..7] else "unknown"` — the v1/v2 trace-string + // The v1/v2 trace-string // format encodes exactly 7 hex chars. `Environment::GIT_SHA_SHORT` is 9 chars and would // shift every following VLQ byte, making bun.report unable to decode the URL. const GIT_SHA: &str = { @@ -2405,7 +2384,7 @@ mod draft { address: i32, // None -> from bun.exe object: Option>, - // TODO(port): Zig stores a borrowed slice into caller's `name_bytes`; using Box<[u8]> here + // Box<[u8]> rather than a borrowed slice into caller's `name_bytes`, // since the only caller writes into a stack buffer and the value is consumed immediately. } @@ -2425,7 +2404,6 @@ mod draft { return Some(StackLine { // To remap this, `pdb-addr2line --exe bun.pdb 0x123456` - // Zig: `@intCast(addr - base_address)` — unchecked in ReleaseFast. // Use a wrapping cast so an oversize/underflowed module offset // produces a junk frame instead of panicking *inside* the crash // handler (which would escalate to a double-panic and lose the @@ -2434,7 +2412,7 @@ mod draft { object: if name != image_path.as_slice() { // GetModuleFileNameW output never has a trailing separator - // or bare drive prefix, so the std.fs.path.basenameWindows + // or bare drive prefix, so `basename_windows`'s // stripping is a no-op on this domain. let basename = bun_paths::basename_windows(name); Some(Box::<[u8]>::from( @@ -2447,8 +2425,6 @@ mod draft { } #[cfg(target_os = "macos")] { - // This code is slightly modified from std.debug.DebugInfo.lookupModuleNameDyld - // https://github.com/ziglang/zig/blob/215de3ee67f75e2405c177b262cb5c1cd8c8e343/lib/std/debug.zig#L1783 let address = if addr == 0 { 0 } else { addr - 1 }; let image_count = bun_sys::c::_dyld_image_count(); @@ -2531,8 +2507,6 @@ mod draft { } #[cfg(not(any(windows, target_os = "macos")))] { - // This code is slightly modified from std.debug.DebugInfo.lookupModuleDl - // https://github.com/ziglang/zig/blob/215de3ee67f75e2405c177b262cb5c1cd8c8e343/lib/std/debug.zig#L2024 let _ = name_bytes; let address = addr.saturating_sub(1); let m = bun_sys::elf::find_loaded_module(address)?; @@ -2719,7 +2693,7 @@ mod draft { writer: &mut impl Write, addr: usize, ) -> Result<(), bun_core::Error> { - // @bitCast(@as(u32, ...)) → reinterpret u32 as i32 + // `as u32 as i32` reinterprets the 32-bit halves, preserving bits. let first = VLQ::encode((((addr as u64) & 0xFFFFFFFF00000000) >> 32) as u32 as i32); let second = VLQ::encode(((addr as u64) & 0xFFFFFFFF) as u32 as i32); writer.write_all(first.slice())?; @@ -2751,9 +2725,8 @@ mod draft { return false; } - // Honor DO_NOT_TRACK - // TODO(port): bun_analytics::is_enabled - if env_var::DO_NOT_TRACK::get() == Some(true) { + // Honor DO_NOT_TRACK (and the bunfig telemetry setting) + if !bun_analytics::is_enabled() { return false; } @@ -2809,7 +2782,6 @@ mod draft { cmd_line.append_slice_assume_capacity(bun_core::w!( "powershell -ExecutionPolicy Bypass -Command \"try{Invoke-RestMethod -Uri '" )); - // PERF(port): was assume_capacity { // `unused_capacity_slice` is `&mut [MaybeUninit]`; // `from_raw_parts_mut::` over that storage would assert the @@ -2844,7 +2816,7 @@ mod draft { // SAFETY: we just wrote a NUL terminator at len-1 let end = cmd_line.len() - 1; let cmd_line_slice = &mut cmd_line.slice()[0..end]; - // TODO(port): need [:0] sentinel slice — pass raw pointer + // Rust has no [:0] sentinel slices — pass the raw pointer instead. // SAFETY: all pointer args are either null or point to stack-local buffers/structs valid for the duration of the call; cmd_line is NUL-terminated above let spawn_result = unsafe { windows::kernel32::CreateProcessW( @@ -2864,8 +2836,7 @@ mod draft { // we don't care what happens with the process // NOTE: on success `CreateProcessW` returns two open kernel handles in // `process.hProcess` / `process.hThread` that the caller is meant to - // `CloseHandle`. The Zig spec leaks them identically (crash_handler.zig: - // 1545-1546 `_ = spawn_result;`); `report()` runs immediately before + // `CloseHandle`. `report()` runs immediately before // `crash()` → `ExitProcess(3)`, so the kernel reclaims them anyway. let _ = spawn_result; let _ = url; @@ -2885,7 +2856,7 @@ mod draft { let Ok(cwd) = bun_core::getcwd(&mut buf2) else { return; }; - // PORT NOTE: reshaped for borrowck — capture cwd bytes by value (it + // Reshaped for borrowck — capture cwd bytes by value (it // borrows buf2, not buf, so no actual overlap; copy len for clarity). let cwd_bytes = cwd.as_bytes(); let Some(curl) = bun_which::which(&mut buf, path_env, cwd_bytes, b"curl") else { @@ -2933,7 +2904,6 @@ mod draft { _ => {} } } - // TODO(port): wasm @compileError("Not implemented") #[cfg(not(unix))] let _ = url; } @@ -2944,7 +2914,6 @@ mod draft { #[cfg(not(windows))] { // Install default handler so that the tkill below will terminate. - // Zig: std.posix.Sigaction{ .handler = SIG.DFL, .mask = sigemptyset(), .flags = 0 }. // bun_sys::posix has no Sigaction yet — use libc directly (async-signal-safe). // SAFETY: all-zero is a valid sigaction (handler = SIG_DFL = 0, flags = 0). let mut sigact: libc::sigaction = bun_core::ffi::zeroed(); @@ -2975,9 +2944,9 @@ mod draft { libc::sigaction(sig, &raw const sigact, core::ptr::null_mut()); } } - // Zig: `@trap()` — emits ud2 (x86_64 → SIGILL) / brk (aarch64 → SIGTRAP). - // `core::intrinsics::abort()` lowers to the same trap instruction, preserving - // the Zig exit signal. Do NOT use `libc::abort()` here — that raises SIGABRT + // `core::intrinsics::abort()` lowers to a trap instruction — + // ud2 (x86_64 → SIGILL) / brk (aarch64 → SIGTRAP). + // Do NOT use `libc::abort()` here — that raises SIGABRT // (exit 134), which is the *Windows* path's behaviour. core::intrinsics::abort(); } @@ -2986,9 +2955,8 @@ mod draft { // Node.js exits with code 134 (128 + SIGABRT) instead. We use abort() as it // includes a breakpoint which makes crashes easier to debug. // - // Zig spec (crash_handler.zig:1592): the `.windows` arm is literally - // `std.posix.abort();` — i.e. our same-module `abort()` helper, which on - // Windows is `@breakpoint()` (Debug only) then `kernel32.ExitProcess(3)`. + // The same-module `abort()` helper on + // Windows is `breakpoint()` (Debug only) then `kernel32.ExitProcess(3)`. // Do NOT call MSVCRT `libc::abort()` here — that raises SIGABRT, may print // the CRT `abort() has been called` message, and can invoke WER. abort() @@ -3003,7 +2971,8 @@ mod draft { err_int_workaround_for_zig_ccall_bug: u16, trace: &StackTrace, ) { - // TODO(port): std.meta.Int(.unsigned, @bitSizeOf(anyerror)) — bun_core::Error is errno-based + // bun_core::Error + // is errno-based, so the error round-trips through its u16 representation. let err = bun_core::Error::from_errno(err_int_workaround_for_zig_ccall_bug as i32); // The format of the panic trace is slightly different in debug @@ -3062,8 +3031,8 @@ mod draft { err: bun_core::Error, maybe_trace: Option<&StackTrace>, ) { - // TODO(port): builtin.have_error_return_tracing — Rust has no error-return tracing; - // decide whether to keep this entire mechanism or strip it. + // Rust has no error-return tracing; `HAVE_ERROR_RETURN_TRACING` is const + // false, so this path is currently dead. if !debug::HAVE_ERROR_RETURN_TRACING { return; } @@ -3119,7 +3088,7 @@ mod draft { // SAFETY: lazy debug-only singleton; sole `&mut` for the dump below. Ok(d) => unsafe { &mut *d }, Err(err) => { - // Zig: `stderr.print(..) catch return;` — if stderr write fails + // If the stderr write fails // (e.g. broken pipe), bail out entirely; don't fall through. if write!(stderr, "Unable to dump stack trace: Unable to open debug info: {}\nFallback trace:\n", bstr::BStr::new(err.name())).is_err() { return; } break 'attempt_dump; @@ -3205,7 +3174,6 @@ mod draft { ] }; for &program in programs { - // PERF(port): was arena bulk-free + StackFallbackAllocator — using global allocator here. // Only stop once a symbolizer actually ran and exited 0. Any failure // (not found, spawn error, or non-zero exit) tries the next program and // ultimately falls through to the WTF fallback below — a found-but-broken @@ -3216,8 +3184,7 @@ mod draft { } } let _ = limits; - // INTENTIONAL DIVERGENCE from Zig spec (crash_handler.zig:1749-1760 falls - // off the end of the `for (programs)` loop with no further fallback). On + // On // Windows, `spawn_sync_inherit` is stubbed and `pdb-addr2line` is rarely // installed, so without this the user would get *only* "Fallback trace:" // and nothing else. Hand the raw addresses to WTF (always linked) so there @@ -3235,15 +3202,13 @@ mod draft { program: &bun_core::ZStr, trace: &StackTrace, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let mut argv: Vec> = Vec::new(); argv.push(program.as_bytes().to_vec()); argv.push(b"--exe".to_vec()); argv.push({ #[cfg(windows)] { - // `to_utf8_alloc` is infallible (Vec); the Zig version returned - // `![]u8` only for OOM, which Rust handles via abort. + // `to_utf8_alloc` is infallible (Vec); OOM aborts. let image_path = strings::to_utf8_alloc(bun_sys::windows::exe_path_w()); let mut s = image_path[0..image_path.len() - 3].to_vec(); s.extend_from_slice(b"pdb"); @@ -3305,7 +3270,7 @@ mod draft { pub fn suppress_core_dumps_if_necessary() { #[cfg(unix)] { - // Zig: std.posix.getrlimit / setrlimit. bun_sys::posix has no rlimit + // bun_sys::posix has no rlimit // surface yet — go straight to libc (already a dep, async-signal-safe). // SAFETY: all-zero rlimit is valid POD; getrlimit/setrlimit only read/write the struct. let mut existing_limit: libc::rlimit = bun_core::ffi::zeroed(); @@ -3347,9 +3312,8 @@ mod draft { ptr: *mut T, handler: fn(&mut T) -> Result<(), bun_core::Error>, ) -> Result<(), bun_alloc::AllocError> { - // Zig monomorphizes a `wrap.onCrash` that casts the opaque ptr back to *T and calls - // `handler`. Rust can't capture `handler` in a bare `fn` item, so box a closure that - // performs the same cast+call. Errors are intentionally swallowed (best-effort dump). + // Rust can't capture `handler` in a bare `fn` item, so box a closure that + // performs the cast+call. Errors are intentionally swallowed (best-effort dump). let on_crash = Box::new(move |opaque_ptr: *mut c_void| { // SAFETY: `opaque_ptr` is the `ptr.cast()` stored below; it was a valid *mut T // when registered and remove_pre_crash_handler() unregisters it before drop. @@ -3380,14 +3344,12 @@ mod draft { pub source_location: Option, pub symbol_name: Box<[u8]>, pub compile_unit_name: Box<[u8]>, - // TODO(port): Zig stores borrowed slices owned by debug_info; using Box<[u8]> here. } - // PORT NOTE: Zig's `SourceAtAddress.deinit` only freed `source_location.file_name`; - // `Option` owns it as `Box<[u8]>` so Drop handles it — no explicit deinit. + // `Option` owns its file name as `Box<[u8]>` so Drop handles it — no explicit deinit. // D130: deduped — canonical def lives in bun_core (T0). Re-export under the - // Zig-spec name so internal use-sites and any downstream + // old name so internal use-sites and any downstream // `bun_crash_handler::WriteStackTraceLimits` importers keep compiling. pub use bun_core::DumpStackTraceOptions as WriteStackTraceLimits; @@ -3410,7 +3372,7 @@ mod draft { .index .min(stack_trace.instruction_addresses.len()); - // PORT NOTE: Zig's `while (...) : ({ frames_left -= 1; frame_index = ... })` continue-expression + // The `frames_left -= 1; frame_index = ...` continue-expression // is inlined at every `continue` site and at end-of-loop below. while frames_left != 0 { if frame_index >= limits.frame_count { @@ -3545,7 +3507,6 @@ mod draft { compile_unit_name: &[u8], tty_config: TtyConfig, ) -> Result<(), bun_core::Error> { - // Zig: `Environment.base_path ++ std.fs.path.sep_str` (comptime concat). // `Environment::BASE_PATH` is `&[u8]`, which `const_format::concatcp!` cannot // ingest. The constant is tiny and this path is debug-only — build it once // at runtime in a stack BoundedArray (no heap, async-signal-safe). @@ -3713,7 +3674,6 @@ mod draft { } break 'read_line; } - // unreachable in Zig (`return;` after the if/else above) } let line_without_newline = strings::trim_right(&line_buf[..fbs_len], b"\n"); if source_location.column as usize > line_without_newline.len() { @@ -3777,12 +3737,12 @@ mod draft { /// `name` must be a valid NUL-terminated C string. #[unsafe(no_mangle)] pub(crate) unsafe extern "C" fn CrashHandler__unsupportedUVFunction(name: *const c_char) { - // TODO(port): bun_analytics::Features::increment_unsupported_uv_function + bun_analytics::features::unsupported_uv_function.fetch_add(1, Ordering::Relaxed); UNSUPPORTED_UV_FUNCTION.with(|c| c.set(if name.is_null() { None } else { Some(name) })); if env_var::feature_flag::BUN_INTERNAL_SUPPRESS_CRASH_ON_UV_STUB::get() == Some(true) { suppress_reporting(); } - // SAFETY: name is non-null (Zig dereferences it unconditionally with `.?`) + // SAFETY: per the caller contract above, `name` is a valid NUL-terminated C string (non-null). let name_bytes = unsafe { bun_core::ffi::cstr(name) }.to_bytes(); // PORTING.md §Forbidden: no Box::leak. We're on the noreturn path, so a stack // buffer suffices — `panic_impl` erases to &'static for the abort path. @@ -3834,7 +3794,5 @@ mod draft { pub fn fix_dead_code_elimination() { bun_core::keep_symbols!(CrashHandler__unsupportedUVFunction); } - // In Zig: comptime { _ = &Bun__crashHandler; ... } — Rust links #[no_mangle] symbols unconditionally. + // Rust links #[no_mangle] symbols unconditionally. } // end mod draft - -// ported from: src/crash_handler/crash_handler.zig diff --git a/src/csrf/lib.rs b/src/csrf/lib.rs index 77285aedeae..06ff64b7e17 100644 --- a/src/csrf/lib.rs +++ b/src/csrf/lib.rs @@ -17,7 +17,6 @@ pub const DEFAULT_EXPIRATION_MS: u64 = 24 * 60 * 60 * 1000; pub const DEFAULT_ALGORITHM: Algorithm = Algorithm::Sha256; /// Error types for CSRF operations -// TODO(port): thiserror not in deps — manual Display/Error impl for now #[derive(strum::IntoStaticStr, Debug, Clone, Copy, PartialEq, Eq)] pub enum Error { InvalidToken, @@ -29,8 +28,8 @@ bun_core::impl_tag_error!(Error); bun_core::named_error_set!(Error); -/// Options for generating CSRF tokens -// TODO(port): Zig has per-field defaults; Rust callers must specify all fields +/// Options for generating CSRF tokens. Defaults are noted on +/// each field; callers must specify all fields. pub struct GenerateOptions<'a> { /// Secret key to use for signing pub secret: &'a [u8], @@ -45,8 +44,8 @@ pub struct GenerateOptions<'a> { pub algorithm: Algorithm, // = DEFAULT_ALGORITHM } -/// Options for validating CSRF tokens -// TODO(port): Zig has per-field defaults; Rust callers must specify all fields +/// Options for validating CSRF tokens. Defaults are noted on +/// each field; callers must specify all fields. pub struct VerifyOptions<'a> { /// The token to verify pub token: &'a [u8], @@ -162,9 +161,9 @@ pub fn verify(options: &VerifyOptions<'_>) -> bool { token = &token[0..token.len() - 1]; } - // PORT NOTE: reshaped for borrowck — compute decoded_len, then borrow buf immutably afterward + // Reshaped for borrowck — compute decoded_len, then borrow buf immutably afterward let decoded_len: usize = match encoding { - // shares same decoder but encoder is different see encoding.zig + // base64 and base64url share the same decoder, but the encoders differ TokenFormat::Base64Url | TokenFormat::Base64 => { // do the same as Buffer.from(token, "base64url" | "base64") // "\r\n\t " ++ VT (0x0b) @@ -276,8 +275,4 @@ pub fn verify(options: &VerifyOptions<'_>) -> bool { boring::constant_time_eq(received_signature, signature) } -// NOTE: the Zig file re-exports csrf__generate / csrf__verify from -// ../runtime/api/csrf_jsc.zig — per PORTING.md these *_jsc aliases are -// deleted; JS bindings live in the *_jsc crate as extension methods. - -// ported from: src/csrf/csrf.zig +// NOTE: JS bindings live in the *_jsc crate as extension methods. diff --git a/src/css/compat.rs b/src/css/compat.rs index 9626e55d803..2b556c4ddc7 100644 --- a/src/css/compat.rs +++ b/src/css/compat.rs @@ -1,5 +1,4 @@ // This file is autogenerated by build-prefixes.js. DO NOT EDIT! -// (Rust port: per-browser min-version tables emitted from src/css/compat.zig.) use crate::targets::Browsers; @@ -5472,8 +5471,8 @@ impl Feature { /// Returns whether *any* of the given browser targets supports this /// feature natively. pub fn is_partially_compatible(self, targets: &Browsers) -> bool { - // Generic implementation in terms of `is_compatible`, mirroring - // compat.zig:5327-5394 — probe each browser field one at a time. + // Generic implementation in terms of `is_compatible` — + // probe each browser field one at a time. macro_rules! probe { ($field:ident) => { if targets.$field.is_some() { @@ -5497,5 +5496,3 @@ impl Feature { false } } - -// ported from: src/css/compat.zig diff --git a/src/css/context.rs b/src/css/context.rs index fe7ed8ece08..0c8c53dff0b 100644 --- a/src/css/context.rs +++ b/src/css/context.rs @@ -18,8 +18,7 @@ pub struct SupportsEntry { pub important_declarations: Vec, } -// PORT NOTE: `deinit(this, arena)` deleted — all fields own their storage and drop -// automatically. `css.deepDeinit` over the Vecs is handled by `Vec`'s Drop. +// No explicit deinit — all fields own their storage and drop automatically. #[derive(Copy, Clone, PartialEq, Eq)] pub enum DeclarationContext { @@ -30,7 +29,7 @@ pub enum DeclarationContext { } pub struct PropertyHandlerContext<'a> { - // PORT NOTE: `arena` is the parser arena that owns the AST being + // `arena` is the parser arena that owns the AST being // minified; bound to `'a` alongside the other borrowed inputs. pub arena: &'a Bump, pub targets: css::targets::Targets, @@ -135,7 +134,6 @@ impl<'a> PropertyHandlerContext<'a> { let mut dest: Vec> = Vec::with_capacity(self.supports.len()); for entry in &self.supports { - // PERF(port): was appendAssumeCapacity dest.push(css::CssRule::Supports(css::SupportsRule { condition: entry.condition.deep_clone(self.arena), rules: css::CssRuleList { @@ -192,8 +190,6 @@ impl<'a> PropertyHandlerContext<'a> { media_type: css::media_query::MediaType::All, condition: Some(MediaCondition::Feature(Box::new_in( MediaFeature::Plain { - // TODO(port): verify exact MediaFeatureName / MediaFeatureValue - // variant shapes from css::media_query once ported. name: css::media_query::MediaFeatureName::Standard( MediaFeatureId::PrefersColorScheme, ), @@ -233,9 +229,7 @@ impl<'a> PropertyHandlerContext<'a> { dest } - // PORT NOTE: reshaped — Zig passed `comptime dir: []const u8` and `comptime decls: []const u8` - // and used `@field` to select the Direction variant and the self.ltr/self.rtl Vec by name. - // Rust has no @field; pass the Direction value and a borrow of the decls Vec directly. + // Takes the Direction value and a borrow of the decls Vec directly. pub fn get_additional_rules_helper( &self, dir: css::selector::parser::Direction, @@ -267,7 +261,7 @@ impl<'a> PropertyHandlerContext<'a> { impl<'a> PropertyHandlerContext<'a> { pub fn reset(&mut self) { - // PORT NOTE: per-element `deinit()` calls dropped — Vec::clear drops each element, + // Per-element `deinit()` calls dropped — Vec::clear drops each element, // and SupportsEntry / Property own their resources via Drop. self.supports.clear(); self.ltr.clear(); @@ -329,13 +323,13 @@ impl<'a> PropertyHandlerContext<'a> { } let fallbacks = unparsed.value.get_fallbacks(bump, &self.targets); - // PORT NOTE: Zig `for (fallbacks.slice()) |c|` copies by value; `SmallList` + // `SmallList` // has no `IntoIterator`, so spill to a Vec to preserve P3-before-LAB order. for condition_and_fallback in fallbacks.to_owned_slice().into_vec() { self.add_conditional_property( condition_and_fallback.0, css::Property::Unparsed(UnparsedProperty { - // `PropertyId` is `Copy`; Zig `deepClone` was identity. + // `PropertyId` is `Copy`. property_id: unparsed.property_id, value: condition_and_fallback.1, }), @@ -343,5 +337,3 @@ impl<'a> PropertyHandlerContext<'a> { } } } - -// ported from: src/css/context.zig diff --git a/src/css/css_modules.rs b/src/css/css_modules.rs index 1385fa555ea..2fa394cd183 100644 --- a/src/css/css_modules.rs +++ b/src/css/css_modules.rs @@ -5,12 +5,11 @@ use bun_alloc::{ArenaVec as BumpVec, ArenaVecExt as _}; use bun_collections::ArrayHashMap; use crate as css; -// TODO(port): narrow error set pub use crate::Error; // ───────────────────────────────────────────────────────────────────────── -// `reference_dashed`'s `dest.importRecord()` lookup is hoisted to the caller (see PORT NOTE on -// the method) to satisfy Rust borrowck (caller holds `&mut dest.css_module`). +// `reference_dashed`'s `dest.importRecord()` lookup is hoisted to the caller (see the comment +// on the method) to satisfy Rust borrowck (caller holds `&mut dest.css_module`). // ───────────────────────────────────────────────────────────────────────── pub struct CssModule<'a> { pub config: &'a Config, @@ -36,7 +35,6 @@ impl<'a> CssModule<'a> { let source: &[u8] = 'source: { // Make paths relative to project root so hashes are stable if let Some(root) = project_root { - // Zig: `bun.path.Platform.auto.isAbsolute(root)` if bun_paths::is_absolute(root) { alloced = true; break 'source bump.alloc_slice_copy( @@ -46,9 +44,8 @@ impl<'a> CssModule<'a> { } break 'source path.as_ref(); }; - // PORT NOTE: Zig `defer if (alloced) arena.free(source);` — arena-allocated, bulk-freed on bump.reset() + // `source` is arena-allocated, bulk-freed on bump.reset() let _ = alloced; - // PERF(port): was appendAssumeCapacity — profile if it shows up on a hot path hashes.push(hash( bump, format_args!("{}", bstr::BStr::new(source)), @@ -59,7 +56,6 @@ impl<'a> CssModule<'a> { }; let exports_by_source_index = 'exports_by_source_index: { let mut exports_by_source_index = BumpVec::with_capacity_in(sources.len(), bump); - // PERF(port): was appendNTimesAssumeCapacity — profile if it shows up on a hot path for _ in 0..sources.len() { exports_by_source_index.push(CssModuleExports::default()); } @@ -74,12 +70,9 @@ impl<'a> CssModule<'a> { } } - // PORT NOTE: `deinit` was a no-op (`// TODO: deinit`); Drop is implicit. No `impl Drop` needed. - pub fn get_reference(&mut self, bump: &'a Bump, name: &'a [u8], source_index: u32) { - // PORT NOTE: Zig `getOrPut` returns an uninitialized value slot; // bun_collections::ArrayHashMap::get_or_put requires `V: Default` - // (CssModuleExport can't be Default — BumpVec field). Reshaped to the + // (CssModuleExport can't be Default — BumpVec field), so use the // entry()-API instead. use bun_collections::array_hash_map::MapEntry; match self.exports_by_source_index[source_index as usize].entry(name) { @@ -102,8 +95,7 @@ impl<'a> CssModule<'a> { } } - // PORT NOTE: Zig `referenceDashed` took `*Printer` so it could read - // `dest.arena` and call `dest.importRecord(idx)`. In Rust the only + // This does not take `&mut Printer`: the only // caller (`DashedIdentReference::to_css`) already holds a `&mut` borrow of // `dest.css_module` (which *is* `self`), so threading `&mut Printer` in // here would alias. The caller pre-resolves the import-record path and @@ -134,9 +126,8 @@ impl<'a> CssModule<'a> { } None => { // Local export. Mark as used. - // PORT NOTE: Zig `getOrPut` returns an uninitialized value - // slot; `CssModuleExport` cannot be `Default` (BumpVec field), - // so reshape to the `entry()` API like `get_reference` above. + // `CssModuleExport` cannot be `Default` (BumpVec field), + // so use the `entry()` API like `get_reference` above. use bun_collections::array_hash_map::MapEntry; match self.exports_by_source_index[source_index as usize].entry(name) { MapEntry::Occupied(mut o) => { @@ -173,8 +164,8 @@ impl<'a> CssModule<'a> { false, ); - // PORT NOTE: std.fmt.allocPrint(arena, "--{s}", .{the_hash}) → bump Vec - // (bumpalo::Vec lacks io::Write; the format string was a pure concat anyway). + // Build `--{the_hash}` as a bump Vec — a plain concat + // (`bumpalo::Vec` lacks `io::Write`, and no formatting is needed). let mut k = BumpVec::with_capacity_in(2 + the_hash.len(), bump); k.extend_from_slice(b"--"); k.extend_from_slice(the_hash); @@ -478,10 +469,8 @@ impl<'a> CssModuleReference<'a> { /// (a leaf crate) so `bun_bundler::LinkerContext::mangle_local_css` can call /// the *same* hasher without depending on `bun_css`. Re-export here so /// in-crate callers (`dependencies.rs`, `rules/import.rs`) keep the -/// `css_modules::hash` path from the Zig spec. +/// `css_modules::hash` path. #[inline] pub fn hash<'a>(bump: &'a Bump, args: Arguments<'_>, at_start: bool) -> &'a [u8] { bun_base64::wyhash_url_safe(bump, args, at_start) } - -// ported from: src/css/css_modules.zig diff --git a/src/css/css_parser.rs b/src/css/css_parser.rs index 2f1e72e2dd7..cb3a3cb5304 100644 --- a/src/css/css_parser.rs +++ b/src/css/css_parser.rs @@ -1,8 +1,7 @@ -//! CSS parser — port of `src/css/css_parser.zig`. +//! CSS parser. //! //! This is an AST crate (see PORTING.md §Allocators): allocations are -//! arena-backed in the Zig original. The Rust port keeps `&'bump Bump` -//! threading where it matters and drops `Allocator` params elsewhere. +//! arena-backed, with `&'bump Bump` threaded where it matters. use bun_alloc::ArenaVecExt as _; use core::fmt; @@ -15,8 +14,7 @@ use bun_core::strings; // ───────────────────────────── re-exports ───────────────────────────── // -// The Zig css_parser hub re-exports the entire crate surface. The Rust port -// keeps that shape: cross-module re-exports + parser core live here. +// Cross-module re-exports + parser core live here. /// `bun.ast.Index` — bundler source-file index. Hoisted into /// `bun_options_types` to keep css below the parser tier. @@ -90,11 +88,9 @@ pub use crate::values::{ pub use gated_shims::*; -/// Minimal stand-ins for types that live in still-gated sibling *leaf* modules -/// (rules/{keyframes,page,container,...}, values::{number,string}). The hub -/// modules above are real; only the per-rule payload types `AtRulePrelude` -/// reaches into by name remain shimmed here. When a leaf un-gates, delete the -/// matching shim. +/// Re-exports of leaf-module payload types `AtRulePrelude` names directly, +/// plus crate-tier shims for `bun.ast` types that live above this crate's +/// dependency tier. mod gated_shims { // ── rules/ leaf-module payload re-exports ──────────────────────────── @@ -129,9 +125,6 @@ mod gated_shims { } } -// `Maybe` in Zig is `bun.jsc.Node.Maybe` — a tagged result. In Rust we use -// `core::result::Result` directly; callers `.ok()`/`.err()` instead of -// `.asValue()`/`.asErr()`. pub use core::result::Result as Maybe; // PrintErr is hoisted at crate root (single-variant `to_css` error signal); @@ -141,9 +134,6 @@ pub use crate::PrintErr; #[cold] #[inline(never)] pub fn oom(_e: bun_core::Error) -> ! { - if cfg!(debug_assertions) { - // Zig: assert(e == Allocator.Error.OutOfMemory) - } bun_core::out_of_memory(); } @@ -206,10 +196,6 @@ impl SourceLocation { } } - // PORT NOTE: Zig used `anytype` + `@TypeOf` to dispatch on - // `ParserError | BasicParseError | SelectorParseErrorKind`. In Rust this - // becomes a trait `IntoParserError` implemented by each live variant - // (the `BasicParseError` arm is dead/ill-typed in Zig — see note below). pub fn new_custom_error(self, err: impl IntoParserError) -> ParseError { ParseError { kind: errors_::ParserErrorKind::custom(err.into_parser_error()), @@ -218,8 +204,7 @@ impl SourceLocation { } } -/// Dispatch trait for `SourceLocation::new_custom_error` (replaces Zig -/// `anytype` switch on `@TypeOf`). +/// Dispatch trait for `SourceLocation::new_custom_error`. pub trait IntoParserError { fn into_parser_error(self) -> ParserError; } @@ -229,26 +214,19 @@ impl IntoParserError for ParserError { self } } -// PORT NOTE: Zig's `newCustomError` had a third `@TypeOf` arm for -// `BasicParseError`, but that arm is dead and ill-typed — it wraps -// `BasicParseError.intoDefaultParseError(err)` (a `ParseError(ParserError)`) -// in `.custom`, which expects a `ParserError`. No caller ever passes -// `BasicParseError`, so Zig's lazy comptime never instantiates it. We -// intentionally do NOT impl `IntoParserError` for `BasicParseError` here. +// We intentionally do NOT impl `IntoParserError` for `BasicParseError` here: +// no caller ever passes one. // `SelectorParseErrorKind` is impl'd in `selectors/parser.rs`. pub type Error = Err; pub type CssResult = Maybe>; -// Zig: `pub fn Result(comptime T: type) type { return Maybe(T, ParseError(ParserError)); }` -// Rust callers use `CssResult` directly. pub type PrintResult = Maybe; #[cold] pub fn todo(msg: &str) -> ! { - // bun.analytics.Features.todo_panic = 1; - // TODO(port): analytics counter + bun_core::Global::features::TODO_PANIC.store(1, core::sync::atomic::Ordering::Relaxed); panic!("TODO: {msg}"); } @@ -261,28 +239,21 @@ pub fn void_wrap( move |(), p| parsefn(p) } -// ───────────────────────── Derive*-style comptime helpers ───────────────────────── +// ───────────────────────── Derive*-style helpers ───────────────────────── // -// The Zig file defines `DefineListShorthand`, `DefineShorthand`, -// `DefineRectShorthand`, `DefineSizeShorthand`, `DeriveParse`, `DeriveToCss`, -// `DefineEnumProperty`, `DeriveValueType` — all of which use `@typeInfo` / -// `@field` comptime reflection to generate `parse`/`toCss`/etc. for arbitrary -// types. PORTING.md §Comptime reflection: the protocol becomes a trait +// PORTING.md §Comptime reflection: each protocol is a trait // (`ToCss`, `Parse`, `EnumProperty`, ...) and per-type impls are generated by // a `#[derive(...)]` proc-macro. We declare the traits here and stub the // helper bodies that callers in other files reference. /// Shorthand longhand-reconstruction helpers. /// -/// PORT NOTE: Zig's `DefineShorthand` bodies are `@compileError(todo_stuff.depth)` -/// — i.e. instantiating the comptime fn and reaching any method is a compile -/// error. The faithful Rust mapping is a trait with **no default bodies**: any -/// `impl DefineShorthand for T` that omits a method fails at compile time, same -/// as the Zig. Per-type bodies are emitted by `#[derive(DefineShorthand)]` -/// using the (currently commented-out) `PropertyFieldMap`/`VendorPrefixMap` -/// reflection algorithm in `css_parser.zig` lines 316–500. +/// Note: this trait has **no default bodies**: any `impl DefineShorthand for T` +/// that omits a method fails at compile time. Per-type bodies are emitted by +/// `#[derive(DefineShorthand)]` using the (currently commented-out) +/// `PropertyFieldMap`/`VendorPrefixMap` reflection algorithm. pub trait DefineShorthand: Sized { - /// The shorthand's own `PropertyIdTag` (Zig: `comptime property_name`). + /// The shorthand's own `PropertyIdTag`. const PROPERTY_NAME: PropertyIdTag; /// Returns a shorthand from the longhand properties defined in the given @@ -319,8 +290,8 @@ pub trait DefineShorthand: Sized { fn set_longhand(&mut self, property: &Property) -> bool; } -// PORT NOTE: Zig's `DefineListShorthand` / `DefineRectShorthand` / -// `DefineSizeShorthand` / `DeriveParse` / `DeriveToCss` comptime fns became +// Note: `DefineListShorthand` / `DefineRectShorthand` / `DefineSizeShorthand` +// / `DeriveParse` / `DeriveToCss` are // proc-macros (`bun_css_derive::*`, re-exported below) plus the // `impl_rect_shorthand!` / `impl_size_shorthand!` macros in // `properties/margin_padding.rs`. The placeholder trait stubs that previously @@ -352,12 +323,12 @@ pub mod enum_property_util { } } -// Derive macros for the comptime helpers above. Re-exported here as well as -// at crate root because some leaf modules alias `crate::css_parser as css` -// (Zig's `css.DefineEnumProperty(...)` lived in this file). +// Derive macros for the helpers above. Re-exported here as well as +// at crate root because some leaf modules alias `crate::css_parser as css`. pub use bun_css_derive::{DefineEnumProperty, Parse, ToCss}; -/// Replaces Zig's `DefineEnumProperty` comptime fn. +/// Keyword-enum CSS properties: case-insensitive parse from an ident plus a +/// canonical string form. pub trait EnumProperty: Sized + Copy + Into<&'static str> { fn from_ascii_case_insensitive(ident: &[u8]) -> Option; @@ -386,15 +357,15 @@ pub trait EnumProperty: Sized + Copy + Into<&'static str> { where Self: Into, { - // TODO(port): Zig hashed the raw enum int bytes. + // The hash value never leaves the process, so a fixed u32 tag width is fine. let tag: u32 = (*self).into(); hasher.update(&tag.to_ne_bytes()); } } -/// `DeriveValueType` — maps each enum variant to a `MediaFeatureType` via a -/// comptime field map. -/// TODO(port): proc-macro `#[derive(ValueType)]` with attribute annotations. +/// `DeriveValueType` — maps each enum variant to a `MediaFeatureType`. Impls +/// are written by hand (see `media_query.rs`, `rules/container.rs`), so no +/// derive macro is needed. pub trait DeriveValueType { fn value_type(&self) -> MediaFeatureType; } @@ -406,8 +377,7 @@ pub trait DeriveValueType { /// (the block is unclosed). #[cold] fn consume_until_end_of_block(block_type: BlockType, tokenizer: &mut Tokenizer) -> bool { - // PERF(port): was SmallList + appendAssumeCapacity — Vec is - // fine for the cold path. + // Vec is fine for the cold path. let mut stack: Vec = Vec::with_capacity(16); stack.push(block_type); @@ -492,7 +462,7 @@ fn parse_custom_at_rule_prelude( } } - // TODO(port): lifetime — `name` borrows the input arena. The detach is the + // TODO: lifetime — `name` borrows the input arena. The detach is the // same `'static` erasure already applied to `Token`/`AtRulePrelude::Unknown`. // SAFETY: `name` points into the parser's source/arena, which outlives every // `AtRulePrelude`/warning produced from this parser (see `src_str`). @@ -559,13 +529,10 @@ fn parse_until_before( parse_fn: impl FnOnce(C, &mut Parser) -> CssResult, ) -> CssResult { let delimiters = parser.stop_before | delimiters_; - // PORT NOTE: reshaped for borrowck — Zig held `parser.input` aliased - // between the outer Parser and a stack-local "delimited" Parser. In Rust // `&'a mut ParserInput<'a>` is invariant and cannot be reborrowed into a - // second `Parser<'a>` while the first lives. We instead temporarily swap + // second `Parser<'a>` while the first lives, so temporarily swap // `stop_before` on the *same* Parser, run the inner parse, and restore. - // `at_start_of` is *moved into* the inner parse (Zig moved it into the - // delimited Parser and left the outer null) — since we reuse the same + // `at_start_of` is moved into the inner parse — since we reuse the same // Parser it carries through unchanged, and is consumed/cleared below // rather than restored. let saved_stop_before = parser.stop_before; @@ -574,9 +541,7 @@ fn parse_until_before( let result = parser.parse_entirely(closure, parse_fn); if matches!(error_behavior, ParseUntilErrorBehavior::Stop) && result.is_err() { parser.stop_before = saved_stop_before; - // Match Zig: the delimited parser *moved* `at_start_of` out of the - // outer parser (`parser.at_start_of = null;`). Since we reuse the - // same Parser, explicitly clear it so the caller doesn't observe a + // Explicitly clear `at_start_of` so the caller doesn't observe a // stale block-start left behind by the failing inner parse. parser.at_start_of = None; return result; @@ -698,7 +663,7 @@ fn parse_nested_block( BlockType::SquareBracket => Delimiters::CLOSE_SQUARE_BRACKET, BlockType::Parenthesis => Delimiters::CLOSE_PARENTHESIS, }; - // PORT NOTE: reshaped for borrowck — same aliasing as parse_until_before. + // Note: reshaped for borrowck — same aliasing as parse_until_before. // Swap stop_before/at_start_of in place rather than constructing a second // Parser over the invariant `&'a mut ParserInput<'a>`. let saved_stop_before = parser.stop_before; @@ -718,9 +683,6 @@ fn parse_nested_block( } // ───────────────────────── parser-protocol traits ───────────────────────── -// -// Zig used `ValidQualifiedRuleParser(T)` etc. as comptime duck-type checks -// (`@hasDecl`). PORTING.md: trait bounds ARE that check. /// Qualified rules are rules that apply styles to elements in a document. pub trait QualifiedRuleParser { @@ -795,9 +757,8 @@ pub trait CustomAtRuleParser { fn bump_anon_layer_count(this: &mut Self, amount: i32); /// Move the registered `@layer` names accumulated via `on_layer_rule` out - /// of the parser. The Zig spec only populates `StyleSheet.layer_names` - /// when `P == BundlerAtRuleParser` (css_parser.zig:3324); Rust can't - /// type-specialize at the call site, so this is a trait hook with a + /// of the parser. Only `BundlerAtRuleParser` populates + /// `StyleSheet.layer_names`; this is a trait hook with a /// default no-op for parsers that don't track layers. fn take_layer_names(_this: &mut Self) -> Vec { Vec::new() @@ -875,13 +836,11 @@ impl CustomAtRuleParser for DefaultAtRuleParser { pub const ENABLE_TAILWIND_PARSING: bool = false; pub type BundlerAtRule = DefaultAtRule; -// TODO(port): when ENABLE_TAILWIND_PARSING == true, this is `TailwindAtRule`. pub struct BundlerAtRuleParser<'a> { pub arena: &'a Bump, /// Raw pointer aliasing the same `Vec` that `Parser.import_records` - /// points to (Zig passes one `*Vec` to both — see `parseBundler`, - /// css_parser.zig:3245). Both views are raw pointers sharing a single + /// points to. Both views are raw pointers sharing a single /// SharedRW provenance (see `parse_bundler`); each materialises a /// short-lived `&mut` only at the point of use, so accesses interleave /// soundly under Stacked Borrows. @@ -895,7 +854,6 @@ pub struct BundlerAtRuleParser<'a> { } impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { - // TODO(port): when ENABLE_TAILWIND_PARSING, Prelude = enum { Tailwind(TailwindAtRule) }. type Prelude = (); type AtRule = BundlerAtRule; @@ -905,7 +863,6 @@ impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { input: &mut Parser, _: &ParserOptions, ) -> CssResult { - // TODO(port): tailwind branch (gated on ENABLE_TAILWIND_PARSING). Err(input.new_error(BasicParseErrorKind::at_rule_invalid(name))) } @@ -927,7 +884,6 @@ impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { _: &ParserOptions, _: bool, ) -> Maybe { - // TODO(port): tailwind branch. Err(()) } @@ -979,8 +935,8 @@ impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { let mut cloned = LayerName { v: SmallList::default(), }; - // PERF(port): was appendSliceAssumeCapacity — `SmallList` has no - // public `reserve`, so two `append_slice` calls each grow once. + // `SmallList` has no public `reserve`, so two `append_slice` + // calls each grow once. cloned.v.append_slice(this.enclosing_layer.v.slice()); cloned.v.append_slice(layer.v.slice()); this.layer_names.append_assume_capacity(cloned); @@ -1022,9 +978,8 @@ impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { // ───────────────────────────── AtRulePrelude ───────────────────────────── // -// The few leaf-module payload types not yet exposed by `rules/mod.rs` -// (KeyframesName, PageSelector, ContainerName, ContainerCondition) come from -// `gated_shims` above. +// The leaf-module payload types (KeyframesName, PageSelector, ContainerName, +// ContainerCondition) are re-exported from `rules/` via `gated_shims` above. pub enum AtRulePrelude { FontFace, @@ -1032,14 +987,14 @@ pub enum AtRulePrelude { FontPaletteValues(DashedIdent), CounterStyle(CustomIdent), Import { - url: &'static [u8], // TODO(port): lifetime — arena-owned slice + url: &'static [u8], // TODO: lifetime — arena-owned slice media: MediaList, supports: Option, layer: Option>, }, Namespace { - prefix: Option<&'static [u8]>, // TODO(port): lifetime - url: &'static [u8], // TODO(port): lifetime + prefix: Option<&'static [u8]>, // TODO: lifetime + url: &'static [u8], // TODO: lifetime }, Charset, CustomMedia { @@ -1053,17 +1008,13 @@ pub enum AtRulePrelude { Supports(SupportsCondition), Viewport(VendorPrefix), Keyframes { - // TODO(port): real type is `css_rules::keyframes::KeyframesName` — - // leaf module gated in rules/mod.rs. name: KeyframesName, prefix: VendorPrefix, }, - // TODO(port): real type is `Vec` — gated. Page(Vec), MozDocument, Layer(SmallList), Container { - // TODO(port): real types in `css_rules::container` — gated. name: Option, condition: ContainerCondition, }, @@ -1074,7 +1025,7 @@ pub enum AtRulePrelude { scope_end: Option, }, Unknown { - name: &'static [u8], // TODO(port): lifetime + name: &'static [u8], // TODO: lifetime /// The tokens of the prelude tokens: TokenList, }, @@ -1112,7 +1063,6 @@ pub enum TopLevelState { } pub struct TopLevelRuleParser<'a, AtRuleParserT: CustomAtRuleParser> { - // PORT NOTE: Zig threaded `input.arena()` at every call site; the Rust // `DeclarationList = bumpalo::Vec<'bump, Property>` needs the arena up // front, so cache it here (same `'static`-erased borrow `DeclarationBlock` // already uses crate-wide). @@ -1179,13 +1129,12 @@ pub enum ComposesState { DisallowEntirely, } -/// Dispatch trait for `parse_declaration_impl` (replaces Zig -/// `composes_ctx: anytype`). Implemented by `NestedRuleParser`. +/// Dispatch trait for `parse_declaration_impl`. Implemented by `NestedRuleParser`. pub trait ComposesCtx { fn composes_state(&self) -> ComposesState; fn record_composes(&mut self, composes: &mut Composes); } -/// Unit `ComposesCtx` for callers that don't track `composes:` (Zig `void`). +/// Unit `ComposesCtx` for callers that don't track `composes:`. pub struct NoComposesCtx; impl ComposesCtx for NoComposesCtx { #[inline] @@ -1201,7 +1150,7 @@ pub struct NestedRuleParser<'a, T: CustomAtRuleParser> { pub options: &'a ParserOptions<'a>, pub at_rule_parser: &'a mut T, // todo_stuff.think_mem_mgmt - // PORT NOTE: `DeclarationList<'bump>` borrows the parser arena. Threading + // Note: `DeclarationList<'bump>` borrows the parser arena. Threading // `'bump` here cascades into every rule type; deferred (matches // `StyleRule`'s `'static` erasure in rules/style.rs). pub declarations: DeclarationList<'static>, @@ -1329,11 +1278,10 @@ mod rule_parsers { use super::*; use crate::selectors::parser as selector_parser; - // PORT NOTE: Zig threaded `composes_ctx: anytype` (pointer to the - // `NestedRuleParser`) directly into `parse_declaration`. Rust's borrow checker - // forbids passing `&mut *this` while also borrowing `this.declarations` / - // `this.important_declarations`, so split-borrow the three composes fields - // into a small adaptor that implements the `ComposesCtx` dispatch trait. + // The borrow checker forbids passing `&mut *this` while also borrowing + // `this.declarations` / `this.important_declarations`, so split-borrow the + // three composes fields into a small adaptor that implements the + // `ComposesCtx` dispatch trait. struct NestedComposesCtx<'a> { state: ComposesState, arena: &'a Bump, @@ -1366,13 +1314,12 @@ mod rule_parsers { input: &mut Parser, ) -> CssResult { // phf-style dispatch on at-rule name (case-insensitive). - // Zig used `bun.ComptimeEnumMap(PreludeEnum)`. crate::match_ignore_ascii_case! { name, { b"import" => { if (this.state as u8) > (TopLevelState::Imports as u8) { return Err(input.new_custom_error(ParserError::unexpected_import_rule)); } - // TODO(port): lifetime — arena-owned slice; same `'static` erasure + // TODO: lifetime — arena-owned slice; same `'static` erasure // as `Token` payloads. // SAFETY: the returned slice borrows `input.src`/arena, which outlives // the `AtRulePrelude` it is stored in (see `src_str`). @@ -1633,13 +1580,11 @@ mod rule_parsers { composes_refs: &mut *self.composes_refs, local_properties: &mut *self.local_properties, }; - // PORT NOTE: reshaped for borrowck — Zig held `self.*` aliased. Spell - // out the impl with a fresh lifetime so `nested_parser` isn't forced - // to borrow `rules` for `'a`. + // Spell out the impl with a fresh lifetime so `nested_parser` isn't + // forced to borrow `rules` for `'a`. let parse_declarations = as RuleBodyItemParser>::parse_declarations(&nested_parser); // TODO: think about memory management - // PERF(port): was arena bulk-free — profile if hot. let mut errors: Vec> = Vec::new(); let mut iter = RuleBodyParser::new(input, &mut nested_parser); @@ -1726,14 +1671,13 @@ mod rule_parsers { name: &[u8], input: &mut Parser, ) -> CssResult { - // TODO(port): lifetime — `name` borrows the input arena. Detach to + // TODO: lifetime — `name` borrows the input arena. Detach to // `'static` to feed `BasicParseErrorKind::at_rule_invalid` (matches the // `Token` payload erasure throughout this file). // SAFETY: `name` points into the parser's source/arena, which outlives // every prelude/error produced from this parser (see `src_str`). let name: &'static [u8] = unsafe { src_str(name) }; let result: Self::Prelude = 'brk: { - // Zig `ComptimeEnumMap(PreludeEnum)` ASCII-CI dispatch. crate::match_ignore_ascii_case! { name, { b"media" => break 'brk AtRulePrelude::Media(parse_media_list(input, this.options)?), b"supports" => break 'brk AtRulePrelude::Supports(SupportsCondition::parse(input)?), @@ -1751,10 +1695,9 @@ mod rule_parsers { break 'brk AtRulePrelude::Keyframes { name: keyframes_name, prefix }; }, b"page" => { - // Zig: tryParse(parseCommaSeparated(PageSelector.parse)) → on - // .err returns empty list. EOF inside `PageSelector::parse` + // EOF inside `PageSelector::parse` // (e.g. `@page foo` with nothing after) propagates here and is - // swallowed by `try_parse` — matches css_parser.zig:2073. + // swallowed by `try_parse`, yielding an empty list. let selectors: Vec = input .try_parse(|input2| { input2.parse_comma_separated(css_rules::page::PageSelector::parse) @@ -1878,7 +1821,6 @@ mod rule_parsers { let mut decl_parser = css_rules::font_face::FontFaceDeclarationParser; let mut parser = RuleBodyParser::new(input, &mut decl_parser); // todo_stuff.think_mem_mgmt - // PERF(port): was arena bulk-free — profile if hot. let mut properties: Vec = Vec::new(); while let Some(result) = parser.next() { @@ -1978,7 +1920,6 @@ mod rule_parsers { let mut parser = css_rules::keyframes::KeyframesListParser; let mut iter = RuleBodyParser::new(input, &mut parser); // todo_stuff.think_mem_mgmt - // PERF(port): was arena bulk-free — profile if hot. let mut keyframes: Vec = Vec::new(); while let Some(result) = iter.next() { if let Ok(keyframe) = result { @@ -2013,18 +1954,16 @@ mod rule_parsers { Ok(()) } AtRulePrelude::Layer(mut layer) => { - // PORT NOTE (css_parser.zig:2393): Zig reads - // `prelude.layer.at(0).*` — a struct copy that leaves the list - // intact — *then* calls `onLayerRule(&prelude.layer)` so the - // hook still observes the 1-element list. Mirror that: clone - // slot 0 for the rule's `name`, fire `on_layer_rule`, then + // Clone slot 0 for the rule's `name` (leaving the list + // intact so the `on_layer_rule` hook still observes the + // 1-element list), fire `on_layer_rule`, then // drain the original into `push_to_enclosing_layer`. let name = if layer.len() == 0 { None } else if layer.len() == 1 { // `LayerName` has no `Clone` impl yet; `deep_clone` is the // arena-threaded shallow copy (segments are arena-borrowed - // `&[u8]`, so this is the same field-walk Zig's `*` did). + // `&[u8]`). Some(layer.at(0).deep_clone(this.arena)) } else { return Err(input.new_error(BasicParseErrorKind::at_rule_body_invalid)); @@ -2192,7 +2131,6 @@ mod rule_parsers { input: &mut Parser, ) -> CssResult<()> { let loc = this.get_loc(start); - // PORT NOTE: Zig `defer this.composes_refs.clearRetainingCapacity();`. // `composes_refs` is `&mut SmallList<..>` borrowed from the parent // `TopLevelRuleParser`, so dropping `NestedRuleParser` on an error path // does NOT clear the underlying storage. A safe `scopeguard::guard` @@ -2203,7 +2141,7 @@ mod rule_parsers { // `TopLevelRuleParser`) strictly outlives this frame. let composes_refs_ptr: *mut SmallList = &raw mut *this.composes_refs; scopeguard::defer! { - // SAFETY: see PORT NOTE above — no aliasing borrow live at drop. + // SAFETY: see the note above — no aliasing borrow live at drop. unsafe { (*composes_refs_ptr).clear_retaining_capacity(); } } // allow composes if: @@ -2311,7 +2249,7 @@ mod rule_parsers { type Declaration = (); fn parse_value(this: &mut Self, name: &[u8], input: &mut Parser) -> CssResult<()> { - // PORT NOTE: split-borrow — see `NestedComposesCtx` above. + // Note: split-borrow — see `NestedComposesCtx` above. // SAFETY: `input.arena()` re-borrows the parser arena through `&self`; // detach that borrow so `input` can be re-borrowed mutably below. The // arena outlives the parser (it owns all parsed allocations). @@ -2333,8 +2271,7 @@ mod rule_parsers { } } - /// `MediaList::parse` thunk. The body lives in `media_query.rs` in Zig; the - /// Rust port hasn't landed it yet. Kept local so the rule-parser arms above + /// `MediaList::parse` thunk. Kept local so the rule-parser arms above /// type-check; becomes a one-line `MediaList::parse(input, options)` forwarder /// once `media_query::MediaList::parse` un-gates. // blocked_on: media_query::{MediaList,MediaQuery}::parse @@ -2351,7 +2288,7 @@ pub struct ToCssResult { pub code: Vec, /// A map of CSS module exports, if the `css_modules` option was enabled /// during parsing. - // TODO(port): arena lifetime — CssModuleExports/References borrow the + // TODO: arena lifetime — CssModuleExports/References borrow the // parser arena. `'static` placeholder until `<'bump>` threads. pub exports: Option>, /// A map of CSS module references, if the `css_modules` config had @@ -2458,7 +2395,6 @@ impl CssRef { } pub fn to_real_ref(self, source_index: u32) -> bun_ast::Ref { - // Spec (css_parser.zig) constructs `Ref{ .tag = .symbol, ... }`. bun_ast::Ref::new(self.inner_index(), source_index, bun_ast::RefTag::Symbol) } } @@ -2487,7 +2423,7 @@ pub struct ComposesEntry { pub struct PropertyUsage { pub bitset: PropertyBitset, - pub custom_properties: Box<[&'static [u8]]>, // TODO(port): lifetime — arena slices + pub custom_properties: Box<[&'static [u8]]>, // TODO: lifetime — arena slices pub range: bun_ast::Range, } @@ -2505,14 +2441,17 @@ impl PropertyUsage { #[inline] pub fn fill(&mut self, used: &PropertyBitset, custom_properties: &[&'static [u8]]) { self.bitset.set_union(used); - // TODO(port): lifetime — Zig stored borrowed slice; box for now. + // TODO: lifetime — box for now. self.custom_properties = custom_properties.to_vec().into_boxed_slice(); } } -// TODO(port): Zig: `std.bit_set.ArrayBitSet(usize, ceilPow2(EnumFields(PropertyIdTag).len))`. -// Compute the variant count via `strum::EnumCount` instead of hardcoding 1024. -pub type PropertyBitset = ArrayBitSet<1024, { num_masks_for(1024) }>; +// `PropertyIdTag` is a dense `repr(u16)` enum with no +// explicit discriminants whose last variant is `Custom`, so the variant count +// is `Custom + 1`. +pub const PROPERTY_BITSET_BITS: usize = (PropertyIdTag::Custom as usize + 1).next_power_of_two(); +pub type PropertyBitset = + ArrayBitSet; pub fn fill_property_bit_set( bitset: &mut PropertyBitset, @@ -2563,14 +2502,11 @@ pub fn fill_property_bit_set( pub struct StyleSheet { /// A list of top-level rules within the style sheet. pub rules: CssRuleList, - // PERF(port): was arena bulk-free (sources / source_map_urls / - // license_comments were ArrayList fed input.arena()) — profile if hot. pub sources: Vec>, pub source_map_urls: Vec>>, - pub license_comments: Vec<&'static [u8]>, // TODO(port): lifetime — arena - pub options: ParserOptions<'static>, // TODO(port): lifetime - // Zig: `tailwind: if (AtRule == BundlerAtRule) ?*BundlerTailwindState else u0` - // TODO(port): conditional field; for now Option> always. + pub license_comments: Vec<&'static [u8]>, // TODO: lifetime — arena + pub options: ParserOptions<'static>, // TODO: lifetime + // Always `None` for non-bundler sheets. pub tailwind: Option>, pub layer_names: Vec, @@ -2601,16 +2537,14 @@ impl StyleSheet { } // ── StyleSheet behavior (parse/minify/to_css) ──────────────────────────────── -// Method *bodies* are ported with `// PORT NOTE:` borrowck reshapes where Zig -// aliased pointers. mod stylesheet_impl { use super::*; impl StyleSheet { /// Minify and transform the style sheet for the provided browser targets. /// - /// PORT NOTE: `arena` is the arena that owns this stylesheet's AST - /// (Zig: `arena: Allocator`). It is threaded into `MinifyContext` so + /// Note: `arena` is the arena that owns this stylesheet's AST. + /// It is threaded into `MinifyContext` so /// downstream `deep_clone` calls allocate alongside the existing tree. pub fn minify( &mut self, @@ -2698,7 +2632,7 @@ mod stylesheet_impl { local_names: Option<&'a LocalsResultsMap>, symbols: &'a bun_ast::symbol::Map, ) -> PrintResult { - // PORT NOTE: PrinterOptions has `&mut SourceMap` and so isn't Copy; capture + // Note: PrinterOptions has `&mut SourceMap` and so isn't Copy; capture // the lone field we re-read after moving `options` into Printer::new. let project_root = options.project_root; let mut printer = Printer::new( @@ -2803,9 +2737,7 @@ mod stylesheet_impl { ) -> PrintResult { // TODO: this is not necessary // Make sure we always have capacity > 0: https://github.com/napi-rs/napi-rs/issues/1124. - // TODO(port): writer adapter — Zig used std.Io.Writer.Allocating; here we - // route through bun_io::Write over Vec until 'bump dest threads. - // blocked_on: bun_io::Write impl for Vec / dest ownership reshape. + // PERF: this always heap-allocates — profile if hot. let mut dest: Vec = Vec::with_capacity(1); let result = self.to_css_with_writer( arena, @@ -2830,9 +2762,7 @@ mod stylesheet_impl { import_records: Option<&mut Vec>, source_index: SrcIndex, ) -> Maybe<(StyleSheet, StylesheetExtra), Err> { - // PORT NOTE: Zig instantiated `StyleSheet(DefaultAtRule).parse`; Rust - // cannot vary `Self`'s `AtRule` param against `DefaultAtRuleParser`, so - // this returns the concrete `StyleSheet`. Callers that + // Returns the concrete `StyleSheet`. Callers that // need a custom at-rule call `parse_with` directly. let mut default_at_rule_parser = DefaultAtRuleParser; StyleSheet::::parse_with( @@ -2846,7 +2776,7 @@ mod stylesheet_impl { } /// Parse a style sheet from a string. - // TODO(port): `ParserOptions<'static>` matches the `StyleSheet.options` + // TODO: `ParserOptions<'static>` matches the `StyleSheet.options` // field's `'static` erasure; re-threads to `<'bump>` alongside the rest of // the crate. pub fn parse_with>( @@ -2857,11 +2787,11 @@ mod stylesheet_impl { import_records: Option>>, source_index: SrcIndex, ) -> Maybe<(Self, StylesheetExtra), Err> { - // TODO(port): 'bump lifetime threading — every arena-backed slice the + // TODO: 'bump lifetime threading — every arena-backed slice the // parser hands back is currently detached to `'static` (matching the // crate-wide erasure on `DeclarationBlock<'static>`/`Token` payloads). - // The caller owns the arena (matching Zig's `arena: Allocator` - // parameter) so the storage outlives the returned `StyleSheet`. + // The caller owns the arena, so the storage outlives the + // returned `StyleSheet`. // TODO(refactor): re-thread the lifetime through `CssRuleList<'bump, R>` // and drop the `'static` bound on `arena`. let mut composes = ComposesMap::default(); @@ -2884,7 +2814,6 @@ mod stylesheet_impl { Some(&mut parser_extra), ); - // PERF(port): was arena bulk-free — profile if hot. let mut license_comments: Vec<&'static [u8]> = Vec::new(); let mut state = parser.state(); while let Ok(token) = parser.next_including_whitespace_and_comments() { @@ -2892,7 +2821,7 @@ mod stylesheet_impl { Token::Whitespace(_) => {} Token::Comment(comment) => { if comment.first() == Some(&b'!') { - // TODO(port): lifetime — arena slice; see erasure note. + // TODO: lifetime — arena slice; see erasure note. // SAFETY: `comment` borrows `parser.src`, which outlives // `license_comments` (consumed before `parser` drops). license_comments.push(unsafe { src_str(comment) }); @@ -2930,9 +2859,7 @@ mod stylesheet_impl { let source_map_urls: Vec>> = vec![parser.current_source_map_url().map(Box::<[u8]>::from)]; - // Spec: `.layer_names = if (comptime P == BundlerAtRuleParser) - // at_rule_parser.layer_names else .{}` (css_parser.zig:3324). Rust - // dispatches through the `CustomAtRuleParser::take_layer_names` hook + // Dispatch through the `CustomAtRuleParser::take_layer_names` hook // (default = empty; `BundlerAtRuleParser` overrides to move its list // out) so the accumulated layer ordering isn't silently dropped. let layer_names = P::take_layer_names(at_rule_parser); @@ -2971,24 +2898,6 @@ mod stylesheet_impl { // bun.debugAssert() } - pub fn contains_tailwind_directives(&self) -> bool { - // TODO(port): Zig `@compileError` if AtRule != BundlerAtRule. - let mut found_import = false; - for rule in self.rules.v.iter() { - match rule { - CssRule::Custom(_) => return true, - // TODO: layer - CssRule::LayerBlock(_) => {} - CssRule::Import(_) => { - found_import = true; - } - _ => return false, - } - } - let _ = found_import; - false - } - pub fn new_from_tailwind_imports( options: ParserOptions<'static>, imports_from_tailwind: CssRuleList, @@ -3016,13 +2925,10 @@ mod stylesheet_impl { out: &mut CssRuleList, new_import_records: &mut Vec, ) { - // PORT NOTE: the Zig fn takes `*const @This()` but writes - // `rule.* = .ignored;` through it (Zig has no const-transitivity). - // Writing through a `*const`-derived pointer is UB in Rust, so the - // receiver is reshaped to `&mut self`. The sole caller (Tailwind + // The receiver is `&mut self`; the sole caller (Tailwind // bundling) owns the stylesheet exclusively at this point. // - // Zig used a comptime two-pass `inline for` (count, exec). Unroll. + // Two passes: count, then exec. let mut count: u32 = 0; { let mut saw_imports = false; @@ -3048,7 +2954,6 @@ mod stylesheet_impl { } } out.v.reserve(count as usize); - // PERF(port): was ensureUnusedCapacity — profile if hot. let mut saw_imports = false; for rule in self.rules.v.iter_mut() { match rule { @@ -3077,12 +2982,9 @@ mod stylesheet_impl { original_path: b"", flags: Default::default(), }); - // PORT NOTE: reshaped for borrowck — Zig did - // `out.v.appendAssumeCapacity(rule.*)` (bitwise copy) then - // `rule.* = .ignored`. Rust moves the rule out via - // `mem::replace` (no `Clone` bound needed) and pushes that. + // Move the rule out via `mem::replace` (no `Clone` + // bound needed) and push that. let old = core::mem::replace(rule, CssRule::Ignored); - // PERF(port): was appendAssumeCapacity out.v.push(old); } CssRule::Unknown(u) => { @@ -3099,6 +3001,25 @@ mod stylesheet_impl { } } + impl StyleSheet { + pub fn contains_tailwind_directives(&self) -> bool { + let mut found_import = false; + for rule in self.rules.v.iter() { + match rule { + CssRule::Custom(_) => return true, + // TODO: layer + CssRule::LayerBlock(_) => {} + CssRule::Import(_) => { + found_import = true; + } + _ => return false, + } + } + let _ = found_import; + false + } + } + impl StyleAttribute { pub fn parse( arena: &'static Bump, @@ -3107,7 +3028,7 @@ mod stylesheet_impl { import_records: &mut Vec, source_index: SrcIndex, ) -> Maybe> { - // TODO(port): 'bump lifetime threading — `DeclarationBlock<'static>` in + // TODO: 'bump lifetime threading — `DeclarationBlock<'static>` in // `StyleAttribute` vs `Parser<'a>` here; `arena: &'static Bump` // matches the crate-wide erasure (see `parse_with`). let mut parser_extra = ParserExtra { @@ -3149,8 +3070,6 @@ mod stylesheet_impl { // ); let symbols = bun_ast::symbol::Map::init_list(Default::default()); - // TODO(port): writer adapter — Zig used std.Io.Writer.Allocating; route - // through bun_io::Write over Vec until 'bump dest threads. let mut dest: Vec = Vec::new(); let mut printer = Printer::new( arena, @@ -3184,18 +3103,17 @@ mod stylesheet_impl { import_records: &mut Vec, source_index: SrcIndex, ) -> Maybe<(Self, StylesheetExtra), Err> { - // PORT NOTE: Zig aliased `import_records` into both `BundlerAtRuleParser` - // *and* the inner `Parser` (css_parser.zig:3245), and aliased `&options` - // into the at-rule parser while also passing `options` by value (struct - // copy) to `parseWith`. Rust forbids both overlaps directly: + // `import_records` is shared by both `BundlerAtRuleParser` and the + // inner `Parser`, and `options` is both borrowed by the at-rule + // parser and passed by value: // - `import_records`: derive a single raw `NonNull` from the unique // borrow; both the at-rule parser and `Parser::new` store copies of - // that raw pointer (matching Zig's `?*Vec`). Neither holds a + // that raw pointer. Neither holds a // long-lived `&mut`, so interleaved writes from `on_import_rule` and // `add_import_record`/`state`/`reset` each create a fresh short-lived // `&mut` from the shared SharedRW provenance — sound under SB. - // - `options`: bitwise-duplicate via `ptr::read` (mirroring Zig's - // by-value struct copy) and wrap the original in `ManuallyDrop` so + // - `options`: bitwise-duplicate via `ptr::read` and wrap the + // original in `ManuallyDrop` so // only the moved copy drops — `ParserOptions` transitively owns a // `SmallList` (via `css_modules::Config::pattern`) which has a real // `Drop`, so both copies must not run their destructors. @@ -3226,7 +3144,7 @@ mod stylesheet_impl { // ───────────────────────────── StyleAttribute ───────────────────────────── pub struct StyleAttribute { - // PORT NOTE: `DeclarationBlock<'bump>` borrows the parser arena; lifetime + // Note: `DeclarationBlock<'bump>` borrows the parser arena; lifetime // erased to `'static` until 'bump threads through the rule tree (matches // `StyleRule.declarations` in rules/style.rs). pub declarations: DeclarationBlock<'static>, @@ -3344,7 +3262,7 @@ where pub struct ParserOptions<'a> { /// Filename to use in error messages. - pub filename: &'static [u8], // TODO(port): lifetime + pub filename: &'static [u8], // TODO: lifetime /// Whether to enable [CSS modules](https://github.com/css-modules/css-modules). pub css_modules: Option, /// The source index to assign to all parsed rules. Impacts the source map @@ -3354,7 +3272,7 @@ pub struct ParserOptions<'a> { pub error_recovery: bool, /// A list that will be appended to when a warning occurs. /// - /// Stored as a raw `NonNull` (mirrors Zig's `*Log`) so `warn(&self)` + /// Stored as a raw `NonNull` so `warn(&self)` /// can soundly write through it. Deriving `&mut Log` from a `&self`-reachable /// `&'a mut Log` (the previous representation) is UB under Stacked Borrows /// — see PORTING.md §Forbidden patterns. The caller that constructs @@ -3371,7 +3289,7 @@ impl<'a> ParserOptions<'a> { if let Some(lg) = self.logger { // SAFETY: `logger` was constructed from a unique `&'a mut Log` (see // `default`); the pointee outlives `'a` and no other borrow of the - // Log exists for the duration of parsing. Zig mutated through `*Log`. + // Log exists for the duration of parsing. let lg: &mut Log = unsafe { &mut *lg.as_ptr() }; lg.add_warning_fmt_line_col( self.filename, @@ -3471,7 +3389,6 @@ bitflags::bitflags! { const CSS_MODULES = 0b1; } } -// PORT NOTE: Zig packed struct had `css_modules: bool`. Expose accessor: impl ParserOpts { #[inline] pub fn css_modules(self) -> bool { @@ -3484,8 +3401,7 @@ pub struct Parser<'a> { pub at_start_of: Option, pub stop_before: Delimiters, pub flags: ParserOpts, - /// Stored as a raw `NonNull` (mirrors Zig's `?*Vec(ImportRecord)`, - /// css_parser.zig:3808) because `BundlerAtRuleParser` holds an aliasing + /// Stored as a raw `NonNull` because `BundlerAtRuleParser` holds an aliasing /// raw pointer to the same list. Keeping a long-lived `&'a mut` here would /// be invalidated under Stacked Borrows the moment `on_import_rule` /// derives its own `&mut` from the sibling raw pointer. Each access site @@ -3563,8 +3479,7 @@ impl<'a> Parser<'a> { let import_records = unsafe { &mut *ptr.as_ptr() }; let idx = u32::try_from(import_records.len()).unwrap(); // SAFETY: `url` borrows the parser source / arena which outlives - // every `ImportRecord` produced by this parse. `bun.fs.Path` in - // the Zig original stores the same borrowed slice; the lifetime + // every `ImportRecord` produced by this parse; the lifetime // is erased to 'static (see PORTING.md §Lifetimes). let url_static: &'static [u8] = unsafe { src_str(url) }; import_records.push(ImportRecord { @@ -3693,7 +3608,6 @@ impl<'a> Parser<'a> { // capacity 1, so in the somewhat common case of only one item we don't // way overallocate. Note that we always push at least one item if // parsing succeeds. - // PERF(port): was stack-fallback let mut values: Vec = Vec::with_capacity(1); loop { @@ -3730,9 +3644,6 @@ impl<'a> Parser<'a> { result } - // Zig `tryParseImpl` is the same as `tryParse` with manual args tuple; - // collapsed into `try_parse`. - #[inline] pub fn parse_nested_block( &mut self, @@ -4309,8 +4220,7 @@ struct UnclosedBlockAtEof { impl<'a> ParserInput<'a> { /// Create a `ParserInput` borrowing `code` and an arena for unescaped - /// strings. Matches Zig `ParserInput.new` (css_parser.zig:4549) which - /// takes an `Allocator` parameter — the caller owns the arena and it must + /// strings. The caller owns the arena and it must /// outlive every `Token` produced from this input. /// /// PORTING.md §Forbidden: do not fabricate `&'a Bump` from a boxed field @@ -4564,14 +4474,10 @@ pub struct Tokenizer<'a> { const FORM_FEED_BYTE: u8 = 0x0C; const REPLACEMENT_CHAR: u32 = 0xFFFD; const REPLACEMENT_CHAR_UNICODE: [u8; 3] = [0xEF, 0xBF, 0xBD]; -/// UTF-8 encoding of U+0FFD — used by `serializer` where Zig called -/// `bun.strings.encodeUTF8Comptime(0xFFD)` (css_parser.zig:6747, :6937). The -/// Zig literal is `0xFFD` (sic — likely a typo for `0xFFFD`), but the spec is -/// ground truth: encode 0x0FFD → [0xE0, 0xBF, 0xBD] to byte-match. -/// TODO(port): confirm whether the spec itself needs fixing to 0xFFFD. -// TODO(port): verify upstream — Zig wrote 0xFFD, comment says "replacement -// character" which is U+FFFD. Byte-matching the spec (0x0FFD) for now. -const REPLACEMENT_CHAR_UTF8: &[u8] = &[0xE0, 0xBF, 0xBD]; +/// UTF-8 encoding of U+FFFD REPLACEMENT CHARACTER, written by `serializer` +/// when escaping a NUL byte (css-syntax requires U+0000 → U+FFFD; upstream +/// rust-cssparser writes `"\u{FFFD}"`). +const REPLACEMENT_CHAR_UTF8: &[u8] = &REPLACEMENT_CHAR_UNICODE; const MAX_ONE_B: u32 = 0x80; const MAX_TWO_B: u32 = 0x800; const MAX_THREE_B: u32 = 0x10000; @@ -4580,12 +4486,11 @@ const MAX_THREE_B: u32 = 0x10000; /// /// PORTING.md §Forbidden flags this erasure. The proper fix is to thread a /// real `'a` lifetime through `Token<'a>` / `Dimension<'a>` / `CachedToken<'a>` -/// so `slice_from`/`to_slice` return `&'a [u8]` (matching Zig's plain -/// `[]const u8` borrows in css_parser.zig:5879/6461). That change is blocked +/// so `slice_from`/`to_slice` return `&'a [u8]`. That change is blocked /// on `crate::Token` (defined in `lib.rs`, not this file) gaining `<'a>` — /// once `lib.rs` is updated, delete this fn and every call site compiles with /// the honest lifetime. -// TODO(port): delete once `Token<'a>` lands in lib.rs; see verifier bug +// TODO: delete once `Token<'a>` lands in lib.rs; see verifier bug // "src_str / Tokenizer::slice_from / CopyOnWriteStr::to_slice". // SAFETY: every call site below feeds either (a) a sub-slice of `self.src` // (`&'a [u8]`) or (b) an arena-allocated `CopyOnWriteStr::to_slice()` whose @@ -4669,7 +4574,7 @@ impl<'a> Tokenizer<'a> { pub fn see_function(&mut self, name: &[u8]) { if self.var_or_env_functions == SeenStatus::LookingForThem { - // PORT NOTE: Zig had `and` here (always false); preserved. + // Note: this `&&` is always false; kept as-is intentionally. if strings::eql_case_insensitive_ascii_check_length(name, b"var") && strings::eql_case_insensitive_ascii_check_length(name, b"env") { @@ -4988,7 +4893,7 @@ impl<'a> Tokenizer<'a> { } let int_value: Option = if is_integer { - // Zig: bun.intFromFloat — saturating cast. + // Saturating cast. Some(value as i32) } else { None @@ -5654,15 +5559,13 @@ fn byte_to_decimal_digit(b: u8) -> Option { } pub fn split_source_map(contents: &[u8]) -> Option<&[u8]> { - // FIXME: Use bun CodepointIterator - // TODO(port): Zig used std.unicode.Utf8Iterator. Approximate with byte - // scan since the delimiters are all ASCII. + // A byte scan suffices: the delimiters are all ASCII and ASCII bytes never + // occur inside a multi-byte UTF-8 sequence. The returned slice ends *after* + // the matched byte — hence `i + 1`. for (i, &c) in contents.iter().enumerate() { match c { b' ' | b'\t' | FORM_FEED_BYTE | b'\r' | b'\n' => { return Some(&contents[0..i + 1]); - // PORT NOTE: Zig returned `[0..iter.i]` where `i` is *after* - // the codepoint — preserved. } _ => {} } @@ -5747,15 +5650,14 @@ impl TokenKind { // Data layout hoisted at crate root (lib.rs) so error.rs can name `Token` // without the parser hub. Behavior impls (kind/is_parse_error/to_css_generic) // live here. TODO: make strings be allocated in string pool. -// TODO(port): lifetime — every &[u8] payload borrows the arena/source. Uses +// TODO: lifetime — every &[u8] payload borrows the arena/source. Uses // `&'static [u8]` placeholder; thread `<'a>` once payload lifetimes settle. pub use crate::Token; impl Token { // blocked_on: generics::CssEql/CssHash blanket impls for Token payload set pub fn eql(lhs: &Token, rhs: &Token) -> bool { - // TODO(port): Zig used implementEql (comptime field-walk). - // Derive PartialEq once payload lifetimes settle. + // TODO: derive PartialEq once payload lifetimes settle. generic::implement_eql(lhs, rhs) } @@ -6005,15 +5907,11 @@ impl Token { } // `impl Display for Token` lives at crate root (lib.rs) — minimal rendering -// for error messages. The full Zig `Token.format` (CSS-serialization-correct) -// is `Token::to_css_generic` above; switch lib.rs's impl to delegate once -// dependents stop relying on the simple form. -// TODO(port): Zig `format` had subtle differences from `to_css_generic` -// (quoted_string→serialize_string, idhash→serialize_identifier). Specialize -// if it matters for diagnostics. - -/// Byte-writer trait for `serializer` and `to_css_generic` (replaces Zig -/// `anytype` writer). Aliased to the canonical `bun_io::Write`; the associated +// for error messages only. The CSS-serialization-correct form is +// `Token::to_css_generic` above. + +/// Byte-writer trait for `serializer` and `to_css_generic`. +/// Aliased to the canonical `bun_io::Write`; the associated /// `type Error` is dropped — every `Result<(), W::Error>` becomes /// `bun_io::Result<()>`. `Vec` / `ArenaVec<'_, u8>` / `Printer` all /// implement it upstream. @@ -6055,7 +5953,6 @@ impl<'a> CopyOnWriteStr<'a> { let mut list = bun_alloc::ArenaVec::with_capacity_in(b.len() + slice.len(), arena); list.extend_from_slice(b); list.extend_from_slice(slice); - // PERF(port): was appendSliceAssumeCapacity *self = CopyOnWriteStr::Owned(list); } CopyOnWriteStr::Owned(o) => { @@ -6388,7 +6285,7 @@ pub mod serializer { dest: &mut Printer, ) -> Result<(), PrintErr> { let int_value: Option = if fract(value) == 0.0 { - Some(value as i32) // saturating like Zig bun.intFromFloat + Some(value as i32) // saturating cast } else { None }; @@ -6616,9 +6513,6 @@ pub mod to_css { local_names, symbols, ); - // PORT NOTE: Zig special-cased `T == CSSString` → `CSSStringFns.toCss`; - // in Rust the `ToCss` impl on `CSSString` routes there directly, so the - // generic dispatch suffices. this.to_css(&mut printer)?; drop(printer); Ok(s) @@ -6654,7 +6548,7 @@ pub fn parse_important(input: &mut Parser) -> CssResult<()> { } pub mod signfns { - /// Spec-faithful port of `css_parser.zig:7086` — note the ±0.0 sign FLIP is + /// Note: the ±0.0 sign FLIP is /// intentional (do NOT "fix" it). Distinct from `f32::signum` and from /// `calc::std_math_sign` / `CSSNumberFns::sign`. #[inline] @@ -6884,5 +6778,3 @@ pub fn f32_length_with_5_digits(n_input: f32) -> usize { count } - -// ported from: src/css/css_parser.zig diff --git a/src/css/declaration.rs b/src/css/declaration.rs index a537474326a..41b7e5ccce5 100644 --- a/src/css/declaration.rs +++ b/src/css/declaration.rs @@ -4,10 +4,12 @@ use bun_alloc::ArenaVecExt as _; pub use css::Error; use css::{CssResult as Result, PrintErr, Printer}; -// PORT NOTE: every leaf property module is currently a `handler_stub!` ZST in -// properties/mod.rs (no-op `handle_property`/`finalize`). The real handler -// bodies un-gate per-module as the values/ calc lattice lands; this file -// composes over whichever surface is live. +// The `*Handler` types imported below are the real per-shorthand-group +// implementations from their leaf modules (BackgroundHandler, BoxShadowHandler, +// BorderRadiusHandler, …). A few leaf modules still keep individual method +// bodies internally gated until their deps land — see the per-module status +// notes at the top of properties/mod.rs; this file composes over whichever +// surface is live. use crate::css_properties::align::AlignHandler; use crate::css_properties::background::BackgroundHandler; use crate::css_properties::border::BorderHandler; @@ -45,13 +47,12 @@ pub struct DeclarationBlock<'bump> { pub struct DebugFmt<'a, 'bump>(&'a DeclarationBlock<'bump>); -// blocked_on: Printer::new signature (Zig passes arena + Managed(u8) + -// writer + options + null + null + &symbols; the Rust ctor shape is unsettled). +// blocked_on: Printer::new signature (the ctor shape is unsettled). impl<'a, 'bump> core::fmt::Display for DebugFmt<'a, 'bump> { fn fmt(&self, writer: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - // PORT NOTE: debug formatter — uses a throwaway local arena for the - // printer's scratch buffers (Zig threaded the parser arena). + // Debug formatter: uses a throwaway local arena for the printer's + // scratch buffers. let bump = Bump::new(); let mut arraylist: Vec = Vec::new(); let symbols = bun_ast::symbol::Map::init_list(Default::default()); @@ -103,14 +104,12 @@ impl<'bump> DeclarationBlock<'bump> { important_handler: &mut DeclarationHandler<'bump>, context: &mut css::PropertyHandlerContext, ) { - // PORT NOTE: Zig threaded `context.arena` through every append; the - // Rust `PropertyHandlerContext` dropped that field, so we recover the + // `PropertyHandlerContext` carries no arena field, so we recover the // arena from the handler's own bump-backed accumulator instead. let bump: &'bump Bump = handler.decls.bump(); - // PORT NOTE: Zig used a local generic `handle` fn with comptime field - // name + bool. Unrolled to two calls over a shared inner fn; reshaped - // for borrowck (iterate via &mut, move prop out and overwrite slot). + // Two calls over a shared inner fn; iterate via &mut, move prop out + // and overwrite the slot. #[inline] fn handle<'bump>( decls: &mut DeclarationList<'bump>, @@ -124,8 +123,7 @@ impl<'bump> DeclarationBlock<'bump> { let handled = hndlr.handle_property(prop, ctx); if !handled { - // Zig: `hndlr.decls.append(prop.*); prop.* = .{ .all = .@"revert-layer" }` - // — move the value out and overwrite the slot with a + // Move the value out and overwrite the slot with a // non-allocating placeholder so the source list's drop is a // no-op. hndlr @@ -145,9 +143,8 @@ impl<'bump> DeclarationBlock<'bump> { handler.finalize(context); important_handler.finalize(context); - // PORT NOTE: Zig swapped old lists out, deferred their deinit, then - // assigned the handler accumulators. In Rust the old bumpalo Vecs drop - // implicitly on overwrite (arena reclaims on reset). + // The old bumpalo Vecs drop implicitly on overwrite (arena reclaims + // on reset). self.important_declarations = core::mem::replace(&mut important_handler.decls, DeclarationList::new_in(bump)); self.declarations = core::mem::replace(&mut handler.decls, DeclarationList::new_in(bump)); @@ -155,7 +152,6 @@ impl<'bump> DeclarationBlock<'bump> { } /// Non-allocating placeholder used by `minify()` to overwrite moved-out slots. -/// Zig: `css.Property{ .all = .@"revert-layer" }`. #[inline(always)] fn placeholder_property() -> css::Property { css::Property::All(crate::css_properties::CSSWideKeyword::RevertLayer) @@ -168,7 +164,6 @@ impl<'bump> DeclarationBlock<'bump> { let length = self.len(); let mut i: usize = 0; - // PORT NOTE: Zig used `inline for` over field names with @field; unrolled to 2 arms. for decl in self.declarations.iter() { decl.to_css(dest, false)?; if i != length - 1 { @@ -198,7 +193,6 @@ impl<'bump> DeclarationBlock<'bump> { let mut i: usize = 0; let length = self.len(); - // PORT NOTE: Zig used `inline for` over field names with @field; unrolled to 2 arms. for decl in self.declarations.iter() { dest.newline()?; decl.to_css(dest, false)?; @@ -224,7 +218,7 @@ impl<'bump> DeclarationBlock<'bump> { // ─── parse ──────────────────────────────────────────────────────────────── // -// PORT NOTE: every consumer (`StyleRule`, `Keyframe`, `PageRule`, +// Every consumer (`StyleRule`, `Keyframe`, `PageRule`, // `StyleAttribute`, `NestedRuleParser`) stores `DeclarationBlock<'static>` — // the crate-wide `'bump`-erasure placeholder until `'bump` threads through // `CssRule`. `parse()` therefore lives on the `'static` instantiation and @@ -256,10 +250,8 @@ impl DeclarationBlock<'static> { options.warn(&e); continue; } - // errdefer doesn't fire on `return .{ .err = ... }` — Result(T) is a tagged - // union, not an error union. Free any declarations accumulated so far. - // PORT NOTE: in Rust, `declarations`/`important_declarations` are bumpalo - // Vec and drop on early return; deepDeinit is implicit via Drop. + // `declarations`/`important_declarations` are bumpalo Vec and + // drop on this early return; freeing them is implicit via Drop. return Err(e); } } @@ -274,8 +266,7 @@ impl DeclarationBlock<'static> { // ─── hash / eql / deep_clone (gated) ────────────────────────────────────── // blocked_on: properties_generated — `Property` lacks `DeepClone`/`CssEql` // derives and `PropertyId` lacks a `hash(&mut Wyhash)` method. The bodies -// below are the real manual unrolls of Zig's comptime-reflection helpers -// (`implementEql`/`implementDeepClone`); they un-gate the moment the +// below un-gate the moment the // per-variant trait impls land in `properties_generated.rs`. impl<'bump> DeclarationBlock<'bump> { @@ -307,9 +298,6 @@ impl<'bump> DeclarationBlock<'bump> { } pub fn deep_clone(&self, bump: &'bump Bump) -> Self { - // PORT NOTE: `css.implementDeepClone` is comptime field reflection; - // for a struct it deep-clones each field. `Property::deep_clone` is - // the inherent per-variant impl in properties_generated.rs. Self { important_declarations: bun_alloc::vec_from_iter_in( self.important_declarations @@ -333,10 +321,6 @@ pub(crate) struct PropertyDeclarationParser<'a, 'bump> { pub options: &'a css::ParserOptions<'a>, } -// PORT NOTE: Zig's nested AtRuleParser/QualifiedRuleParser/DeclarationParser/ -// RuleBodyItemParser are structural duck-typing namespaces consumed by -// RuleBodyParser(T) at comptime. In Rust these are trait impls. - impl<'a, 'bump> css::AtRuleParser for PropertyDeclarationParser<'a, 'bump> { type Prelude = (); type AtRule = (); @@ -432,10 +416,9 @@ pub fn parse_declaration<'bump>( ) } -// PORT NOTE: Zig `composes_ctx: anytype` — branches on -// `comptime @TypeOf(composes_ctx) != void`. The Rust shape is a `ComposesCtx` +// Composes handling dispatches through the `ComposesCtx` // trait (defined in `css_parser.rs`); `NoComposesCtx` returns -// `DisallowEntirely` so the `void` fast-path collapses into the match's +// `DisallowEntirely` so the no-tracking fast-path collapses into the match's // no-op arm. pub fn parse_declaration_impl<'bump, C>( name: &[u8], @@ -450,8 +433,7 @@ where { let property_id = css::PropertyId::from_string(name); let mut delimiters = css::Delimiters::BANG; - // Zig: `if (property_id != .custom or property_id.custom != .custom)` — - // i.e. NOT (tag == .custom AND payload tag == .custom). + // NOT (tag == custom AND payload tag == custom). if !matches!( property_id, css::PropertyId::Custom(CustomPropertyName::Custom(_)) @@ -459,9 +441,6 @@ where delimiters |= css::Delimiters::CURLY_BRACKET; } let source_location = input.current_source_location(); - // PORT NOTE: Zig threaded `&closure` + fn through `parseUntilBefore`; the - // Rust method takes a single `FnOnce(&mut Parser)`, so capture by move - // (`PropertyId` is `Copy`, `options` is a borrow). let mut property = input.parse_until_before(delimiters, |input2: &mut css::Parser| { css::Property::parse(property_id, input2, options) })?; @@ -481,8 +460,6 @@ where composes_ctx.record_composes(composes); } css::ComposesState::DisallowNested(info) => { - // PORT NOTE: Zig passed an empty notes slice; `warn_fmt` - // is the no-notes path. options.warn_fmt( format_args!("\"composes\" is not allowed inside nested selectors"), info.line, @@ -517,9 +494,9 @@ where /// Per-shorthand-group handler state used by `DeclarationBlock::minify`. /// -/// PORT NOTE: each `*Handler` is a `handler_stub!` ZST until its leaf module -/// un-gates; `Direction` is the data-only `properties::text` enum. The struct -/// shape is the real Zig layout — only the handler *bodies* are deferred. +/// Each `*Handler` is the real implementation from its leaf module (see the +/// per-module status notes in properties/mod.rs for any internally-gated +/// bodies); `Direction` is the data-only `properties::text` enum. pub struct DeclarationHandler<'bump> { pub background: BackgroundHandler, pub border: BorderHandler, @@ -641,5 +618,3 @@ impl<'bump> DeclarationHandler<'bump> { } } } - -// ported from: src/css/declaration.zig diff --git a/src/css/dependencies.rs b/src/css/dependencies.rs index 5ebca15c04a..dd65a35c4fd 100644 --- a/src/css/dependencies.rs +++ b/src/css/dependencies.rs @@ -1,7 +1,6 @@ //! CSS dependency tracking — `@import` and `url()` references collected during printing. use crate::SourceLocation; -// const Location = css.Location; — shadowed by the local `Location` below in Zig too. /// Options for `analyze_dependencies` in `PrinterOptions`. pub struct DependencyOptions { @@ -33,25 +32,21 @@ impl Location { column: loc.column, } } - - // PORT NOTE: Zig `hash` / `eql` methods called `css.implementHash` / `css.implementEql` - // (comptime struct-field reflection). Replaced by `#[derive(Hash, PartialEq, Eq)]` above - // per PORTING.md §Comptime reflection. } /// An `@import` dependency. pub struct ImportDependency { /// The url to import. - // TODO(port): lifetime — arena-borrowed from `rule.url` (CSS arena); consider `&'bump [u8]`. + // Lifetime: arena-borrowed from `rule.url` (CSS arena); valid until the arena is reset. pub url: *const [u8], /// The placeholder that the URL was replaced with. - // TODO(port): lifetime — arena-allocated by `css_modules::hash`. + // Lifetime: arena-allocated by `css_modules::hash`. pub placeholder: *const [u8], /// An optional `supports()` condition. - // TODO(port): lifetime — arena-allocated by `to_css::string`. + // Lifetime: arena-allocated by `to_css::string`. pub supports: Option<*const [u8]>, /// A media query. - // TODO(port): lifetime — arena-allocated by `to_css::string`. + // Lifetime: arena-allocated by `to_css::string`. pub media: Option<*const [u8]>, /// The location of the dependency in the source file. pub loc: SourceRange, @@ -109,7 +104,6 @@ impl ImportDependency { let placeholder = crate::css_modules::hash( bump, - // PORT NOTE: Zig "{s}_{s}", .{ filename, rule.url } → fmt::Arguments format_args!( "{}_{}", bstr::BStr::new(filename), @@ -119,7 +113,7 @@ impl ImportDependency { ); ImportDependency { - // TODO(zack): should we clone this? lightningcss does that + // lightningcss clones this; we borrow from the arena instead. url: std::ptr::from_ref::<[u8]>(rule.url), placeholder: std::ptr::from_ref::<[u8]>(placeholder), supports, @@ -130,9 +124,12 @@ impl ImportDependency { line: rule.loc.line + 1, column: rule.loc.column, }, + // Assumes the `@import "url"` form: 8 = len of `@import `, +2 for the + // quotes. The `@import url(...)` form yields a slightly-off range — + // a limitation inherited from lightningcss. 8, rule.url.len() + 2, - ), // TODO: what about @import url(...)? + ), } } } @@ -140,10 +137,10 @@ impl ImportDependency { /// A `url()` dependency. pub struct UrlDependency { /// The url of the dependency. - // TODO(port): lifetime — arena-borrowed from `import_records[..].path.pretty`. + // Lifetime: arena-borrowed from `import_records[..].path.pretty`. pub url: *const [u8], /// The placeholder that the URL was replaced with. - // TODO(port): lifetime — arena-allocated by `css_modules::hash`. + // Lifetime: arena-allocated by `css_modules::hash`. pub placeholder: *const [u8], /// The location of the dependency in the source file. pub loc: SourceRange, @@ -173,7 +170,7 @@ impl UrlDependency { /// Represents the range of source code where a dependency was found. pub struct SourceRange { /// The filename in which the dependency was found. - // TODO(port): lifetime — borrowed from caller (printer's filename); arena/static. + // Lifetime: borrowed from the caller (printer's filename); arena- or statically-backed. pub file_path: *const [u8], /// The starting line and column position of the dependency. pub start: Location, @@ -196,5 +193,3 @@ impl SourceRange { } } } - -// ported from: src/css/dependencies.zig diff --git a/src/css/error.rs b/src/css/error.rs index 206ca1e44dc..37a850e2ff4 100644 --- a/src/css/error.rs +++ b/src/css/error.rs @@ -5,7 +5,6 @@ use crate::{Location, SourceLocation, Token}; // Arena-owned byte slice. CSS is an AST crate (see PORTING.md §Allocators); these // slices point into the parser arena / source text and are never individually freed. -// TODO(port): arena slice lifetime — thread `<'bump>` or switch to StoreRef. use crate::Str; #[inline(always)] @@ -34,13 +33,11 @@ pub struct Err { impl fmt::Display for Err { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Zig: `if (@hasDecl(T, "format"))` → trait bound `T: Display` IS that check. self.kind.fmt(f) } } -// Zig: `pub const toErrorInstance = @import("../css_jsc/error_jsc.zig").toErrorInstance;` -// Deleted per PORTING.md — `to_error_instance` lives as an extension-trait method in `bun_css_jsc`. +// `to_error_instance` lives as an extension-trait method in `bun_css_jsc`. impl Err { pub fn from_parse_error(err: ParseError, filename: &[u8]) -> Err { @@ -194,7 +191,6 @@ impl ErrorLocation { &self, source: &bun_ast::Source, ) -> Result { - // TODO(port): narrow error set (Zig narrowed to alloc-only). // SAFETY: `'bump`-erasure — `bun_ast::Location.line_text` is `Option<&'static [u8]>` // (`Str` placeholder per src/logger/lib.rs); the slice borrows // `source.contents` which outlives the diagnostic. Re-thread once @@ -548,5 +544,3 @@ impl fmt::Display for MinifyErrorKind { } } } - -// ported from: src/css/error.zig diff --git a/src/css/generics.rs b/src/css/generics.rs index 74f7a4c2760..21aafcde835 100644 --- a/src/css/generics.rs +++ b/src/css/generics.rs @@ -1,22 +1,20 @@ //! Generic trait-based dispatch for CSS value operations. //! -//! The Zig original (`generics.zig`) uses `@typeInfo`/`@hasDecl`/`@field` comptime -//! reflection to derive `eql`, `hash`, `deepClone`, `toCss`, `parse`, etc. across -//! every CSS value type. Per PORTING.md §Comptime reflection, that has no Rust -//! equivalent — the port defines a trait per protocol, provides blanket impls for -//! the structural cases (Option/Box/slice/Vec/Vec/SmallList/primitives), and +//! `eql`, `hash`, `deepClone`, `toCss`, `parse`, etc. are dispatched across +//! every CSS value type via a trait per protocol, with blanket impls for +//! the structural cases (Option/Box/slice/Vec/Vec/SmallList/primitives); //! per-struct/-enum impls come from the `bun_css_derive` proc-macros //! (`#[derive(ToCss, DeepClone, CssEql, CssHash)]` etc.). //! //! Free functions with the original names are kept as thin trait-method wrappers -//! so call sites in sibling files port 1:1. +//! for call sites in sibling files. use core::cmp::Ordering; use bun_alloc::Arena; // bumpalo::Bump re-export use bun_collections::VecExt; -// Zig `std.hash.Wyhash` (iterative) → `bun_wyhash::Wyhash` (the final4 variant -// matching upstream `std.hash.Wyhash`; NOT `Wyhash11`, which is a legacy v0.11 +// `bun_wyhash::Wyhash` is the final4 variant +// (NOT `Wyhash11`, which is a legacy // variant kept only for on-disk lockfile compat — different digest). // Re-exported `pub` so `#[derive(CssHash)]` (in `bun_css_derive`) can name the // hasher type as `::bun_css::generics::Wyhash` without depending on `bun_wyhash` @@ -37,25 +35,24 @@ use crate::values::rect::Rect; use crate::values::size::Size2D; use crate::{PrintErr, VendorPrefix}; -// `ArrayList(T)` in the Zig is `std.ArrayListUnmanaged(T)` fed the parser arena. -// In this AST crate that maps to `bun_alloc::ArenaVec<'bump, T>`. +// In this AST crate, lists map to `bun_alloc::ArenaVec<'bump, T>` fed the +// parser arena. pub type ArrayList<'bump, T> = bun_alloc::ArenaVec<'bump, T>; // ─────────────────────────────────────────────────────────────────────────────── // DeepClone // ─────────────────────────────────────────────────────────────────────────────── -/// Arena-aware deep clone. Equivalent of Zig's `deepClone(T, *const T, Allocator) T`. +/// Arena-aware deep clone. /// -/// Per-struct/-enum impls come from `#[derive(DeepClone)]`; -/// the Zig `implementDeepClone` body is the spec for that derive (field-wise / +/// Per-struct/-enum impls come from `#[derive(DeepClone)]` (field-wise / /// variant-wise recursion). pub trait DeepClone<'bump>: Sized { fn deep_clone(&self, bump: &'bump Arena) -> Self; } -/// `#[derive(DeepClone)]` — field-wise / variant-wise port of Zig's -/// `css.implementDeepClone`. See `src/css_derive/lib.rs` for the expansion +/// `#[derive(DeepClone)]` — field-wise / variant-wise deep clone. +/// See `src/css_derive/lib.rs` for the expansion /// rules. Re-exported here so `use crate::generics::DeepClone;` brings both /// the trait and the derive into scope (same-name trait+derive is the std /// idiom, cf. `Clone`). @@ -66,15 +63,14 @@ pub fn implement_deep_clone<'bump, T: DeepClone<'bump>>(this: &T, bump: &'bump A this.deep_clone(bump) } -// Alias: in Zig `deepClone` (structural type-dispatch entry) and -// `implementDeepClone` (field-reflection body) are distinct, but in Rust both -// collapse to `T::deep_clone` because the structural dispatch lives in the -// blanket impls below and the field-reflection lives in `#[derive(DeepClone)]`. +// Alias: structural dispatch lives in the blanket impls below and the +// field-reflection lives in `#[derive(DeepClone)]`; both collapse to +// `T::deep_clone`. // Kept as a re-export so generated code (`properties_generated.rs`) and // hand-written callers can use either name. pub use implement_deep_clone as deep_clone; -// Blanket impls covering the structural cases the Zig switch handled inline. +// Blanket impls covering the structural cases. impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for Option { #[inline] @@ -86,15 +82,13 @@ impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for Option { impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for &'bump T { #[inline] fn deep_clone(&self, bump: &'bump Arena) -> Self { - // Zig: `bun.create(arena, TT, deepClone(TT, this.*, arena))` bump.alloc((**self).deep_clone(bump)) } } impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for &'bump [T] { fn deep_clone(&self, bump: &'bump Arena) -> Self { - // Zig: alloc slice, then memcpy if simple-copy else element-wise deepClone. - // PERF(port): Zig fast-paths `isSimpleCopyType` with @memcpy — profile if hot + // PERF: element-wise deep_clone — profile if hot // (specialization would let `T: Copy` use `alloc_slice_copy`). bump.alloc_slice_fill_iter(self.iter().map(|e| e.deep_clone(bump))) } @@ -103,8 +97,7 @@ impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for &'bump [T] { impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for ArrayList<'bump, T> { #[inline] fn deep_clone(&self, bump: &'bump Arena) -> Self { - // Zig: `css.deepClone(T, arena, this)` → alloc capacity, element-wise deepClone. - // PERF(port): Zig fast-paths simple-copy types with @memcpy — profile if hot. + // PERF: element-wise deep_clone — profile if hot. let mut out = ArrayList::with_capacity_in(self.len(), bump); for item in self.iter() { out.push(item.deep_clone(bump)); @@ -191,8 +184,8 @@ pub trait CssEql { fn eql(&self, other: &Self) -> bool; } -/// `#[derive(CssEql)]` — field-wise / variant-wise port of Zig's -/// `css.implementEql`. See `src/css_derive/lib.rs` for the expansion rules. +/// `#[derive(CssEql)]` — field-wise / variant-wise equality. +/// See `src/css_derive/lib.rs` for the expansion rules. /// Re-exported here so `use crate::generics::CssEql;` brings both trait and /// derive into scope (same-name idiom, cf. `Clone`). pub use bun_css_derive::CssEql; @@ -290,7 +283,7 @@ eql_simple!(f32, f64, i32, u32, i64, u64, usize, isize, u16, bool); /// /// Exported sibling of `eql_simple!` for crate-defined types whose /// inherent `pub fn eql(&self, other) { self == other }` was a pure `PartialEq` -/// forwarder (Zig `css.implementEql(@This())` leakage). +/// forwarder. /// /// Unlike `#[derive(CssEql)]` (field-wise `.eql()` walk), this does **not** /// require every field type to itself impl `CssEql`; it bridges @@ -329,7 +322,7 @@ impl CssEql for str { impl CssEql for [T; N] { #[inline] fn eql(&self, other: &Self) -> bool { - // Zig: element-wise eql (length is `N` on both sides by type). + // Element-wise eql (length is `N` on both sides by type). for (a, b) in self.iter().zip(other.iter()) { if !a.eql(b) { return false; @@ -349,7 +342,6 @@ impl CssEql for Box { impl CssEql for VendorPrefix { #[inline] fn eql(&self, other: &Self) -> bool { - // Zig: `VendorPrefix.eql` is bitwise compare of the packed struct. *self == *other } } @@ -393,8 +385,7 @@ mod ident_eql { // Bridge inherent eql/hash/deep_clone → trait impls // // Many CSS value types carry hand-rolled inherent `eql`/`hash`/`deep_clone` -// (ported verbatim from the Zig `implementEql`/`implementHash`/ -// `implementDeepClone` bodies — usually because a field is a raw `*const [u8]` +// (usually because a field is a raw `*const [u8]` // arena slice that the derive can't see through). The `#[derive(CssEql/…)]` // expansion on *containing* types dispatches via UFCS trait paths, so those // inherent methods alone don't satisfy the bound. These thin forwarding impls @@ -646,7 +637,7 @@ mod inherent_bridge { use crate::properties::border_image::{ BorderImage, BorderImageRepeat, BorderImageSideWidth, BorderImageSlice, }; - // PORT NOTE: BorderImageRepeat/SideWidth/Slice carry inherent + // BorderImageRepeat/SideWidth/Slice carry inherent // `deep_clone(&self, &Arena)` / `eql(&self, &Self)` (no `Clone`/`PartialEq` // derives — see border_image.rs), so route through bridge_deep_clone/eql. bridge_deep_clone!(BorderImageRepeat, BorderImageSideWidth, BorderImageSlice); @@ -655,8 +646,8 @@ mod inherent_bridge { bridge_eql!(BorderImage); use crate::properties::border_radius::BorderRadius; - // PORT NOTE: BorderRadius has inherent deep_clone/eql (Size2D lacks - // Clone/PartialEq derives — see border_radius.rs PORT NOTE). + // BorderRadius has inherent deep_clone/eql (Size2D lacks + // Clone/PartialEq derives — see border_radius.rs). bridge_deep_clone!(BorderRadius); bridge_eql!(BorderRadius); @@ -865,9 +856,10 @@ mod inherent_bridge { bridge_eql_partialeq!(PropertyId); } -// TODO(port): Zig also special-cases `@typeInfo(T).struct.layout == .packed` → -// bitwise `==`. In Rust those are `bitflags!` types implementing `PartialEq`; -// add `impl CssEql for T` or per-type impls. +// Packed bitfields are `bitflags!` types whose derived `PartialEq` already +// compares contents, routed through the per-type `bridge_eql_partialeq!` calls above. +// (A blanket `impl CssEql for T` would collide with the container +// blanket impls under coherence, so coverage stays per-type.) // ─────────────────────────────────────────────────────────────────────────────── // Hash @@ -880,8 +872,8 @@ pub trait CssHash { fn hash(&self, hasher: &mut Wyhash); } -/// `#[derive(CssHash)]` — field-wise / variant-wise port of Zig's -/// `css.implementHash`. See `src/css_derive/lib.rs` for the expansion rules. +/// `#[derive(CssHash)]` — field-wise / variant-wise hashing. +/// See `src/css_derive/lib.rs` for the expansion rules. /// Re-exported here so `use crate::generics::CssHash;` brings both trait and /// derive into scope. pub use bun_css_derive::CssHash; @@ -916,9 +908,8 @@ impl CssHash for () { impl CssHash for Option { #[inline] fn hash(&self, hasher: &mut Wyhash) { - // Zig `hash()` dispatcher: Some → hash inner, None → no-op (no prefix). - // The "null"/"some" prefixes are from implementHash's dead .optional arm - // (guarded by @compileError) — do NOT emit them here. + // Some → hash inner, None → no-op. Do NOT emit "null"/"some" + // prefixes here. if let Some(v) = self { v.hash(hasher); } @@ -934,7 +925,7 @@ impl CssHash for &T { impl CssHash for [T] { fn hash(&self, hasher: &mut Wyhash) { - // Zig `hash()` for `.slice` pointers iterates items only — no len prefix. + // Iterates items only — no len prefix. for item in self { item.hash(hasher); } @@ -943,8 +934,8 @@ impl CssHash for [T] { impl CssHash for [T; N] { fn hash(&self, hasher: &mut Wyhash) { - // Zig: `bun.writeAnyToHasher(hasher, list.len)` — feeds the raw bytes - // of `usize` into the hasher. `bun_core::write_any_to_hasher` exists + // Feed the raw bytes + // of the `usize` length into the hasher. `bun_core::write_any_to_hasher` exists // but is `H: Hasher`-generic and routes through `Hasher::write`, which // for `Wyhash11` calls `update` — so inlining the `usize` byte-feed // here is byte-identical and avoids the trait hop. @@ -983,7 +974,6 @@ macro_rules! hash_simple { impl CssHash for $t { #[inline] fn hash(&self, hasher: &mut Wyhash) { - // Zig: `hasher.update(std.mem.asBytes(&this))` hasher.update(&self.to_ne_bytes()); } } @@ -1023,7 +1013,6 @@ impl CssHash for Box { impl CssHash for VendorPrefix { #[inline] fn hash(&self, hasher: &mut Wyhash) { - // Zig: `hasher.update(std.mem.asBytes(&this))` on the packed-struct repr. hasher.update(&[self.as_bits()]); } } @@ -1031,8 +1020,7 @@ impl CssHash for VendorPrefix { impl CssHash for bun_ast::Loc { #[inline] fn hash(&self, hasher: &mut Wyhash) { - // Zig `implementHash` doesn't reach `Loc` (callers skip it), but - // providing a structural hash here lets `#[derive(CssHash)]` types + // Providing a structural hash here lets `#[derive(CssHash)]` types // include a `loc` field without `#[css(skip)]` if they want. hasher.update(&self.start.to_ne_bytes()); } @@ -1108,9 +1096,8 @@ impl IsCompatible for Box { impl IsCompatible for Option { #[inline] fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool { - // Zig's `isCompatible` doesn't special-case Optional, but every - // hand-written caller treats absent as compatible (no value → no - // feature gate to check). + // Every hand-written caller treats absent as compatible (no value → + // no feature gate to check). match self { Some(v) => v.is_compatible(browsers), None => true, @@ -1144,7 +1131,7 @@ impl IsCompatible for Vec { } } -// The Zig original blanket-impls over "any list container". A Rust blanket +// A blanket // `impl` conflicts with the `&T` impl above (coherence can't // prove `&T` never impls `ListContainer`), so spell out the three concrete // container types instead. @@ -1173,8 +1160,7 @@ is_compatible_container!( // ─────────────────────────────────────────────────────────────────────────────── // Parse / ParseWithOptions // ─────────────────────────────────────────────────────────────────────────────── -// Zig's `generic.parse(T, input)` / `generic.parseWithOptions(T, input, opts)` -// dispatch via `@hasDecl(T, "parse"[WithOptions])`. In Rust each leaf value +// Each leaf value // type either hand-writes an inherent `parse(&mut Parser) -> CssResult` // or derives one via `#[derive(Parse)]` / `#[derive(DefineEnumProperty)]`; the // trait below is the uniform bound that `Property::parse` and the container @@ -1182,7 +1168,7 @@ is_compatible_container!( // // `Parse` is intentionally lifetime-free: every value-type parser takes // `&mut Parser<'_>` (the borrowed source slice) and returns an owned value. -// TODO(refactor): `'bump` arena threading is a follow-up; until then the parser +// `'bump` arena threading is a follow-up; until then the parser // holds the arena and arena-backed lists go through `from_list(Vec)`. /// `T::parse(&mut Parser) -> CssResult`. @@ -1192,8 +1178,8 @@ pub trait Parse: Sized { /// `T::parse_with_options(&mut Parser, &ParserOptions) -> CssResult`. /// -/// Zig falls through to `parse` when a type has no `parseWithOptions` decl. -/// PORT NOTE: Rust can't express that as a `where Self: Parse` default method +/// Falling back to `parse` when a type has no `parse_with_options` +/// can't be expressed as a `where Self: Parse` default method /// — the bound becomes part of the *method signature*, so the free /// `parse_with_options::` below would require `T: Parse` even for impls /// that override the body. Instead the fallthrough lives in @@ -1249,7 +1235,7 @@ impl ParseWithOptions for Vec { } } -// Zig `.pointer` arm (`generics.zig:273-279`): parse the pointee then heap-allocate. +// Parse the pointee then heap-allocate. impl Parse for Box { #[inline] fn parse(input: &mut Parser) -> CssResult { @@ -1355,7 +1341,7 @@ impl ToCss for &T { } } -// Zig `.pointer` arm (`generics.zig:338-341`): recurse into `*T` pointee. +// Recurse into the pointee. impl ToCss for Box { #[inline] fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { @@ -1522,11 +1508,11 @@ impl TrySign for CSSNumber { Some(CSSNumberFns::sign(*self)) } } -// TODO(port): Zig fallback `if @hasDecl(T, "sign") T.sign else T.trySign` — -// model as a trait with default method delegating to `Sign` where available. +// Each type +// implements `TrySign` explicitly (delegating to its inherent `sign` where one exists). pub trait TryMap: Sized { - // Zig: `comptime map_fn: *const fn(f32) f32` — generic param preserves monomorphization. + // Generic param preserves monomorphization. fn try_map(&self, map_fn: impl Fn(f32) -> f32) -> Option; } @@ -1538,7 +1524,7 @@ impl TryMap for CSSNumber { } pub trait TryOpTo: Sized { - // Zig: `comptime op_fn: *const fn(...)` — generic param preserves monomorphization. + // Generic param preserves monomorphization. // `R` is method-generic (not trait-generic) so `TryOpTo` can appear as a // supertrait bound without committing to a result type. fn try_op_to(&self, rhs: &Self, ctx: C, op_fn: impl Fn(C, f32, f32) -> R) -> Option; @@ -1559,7 +1545,7 @@ impl IsCompatible for CSSNumber { } pub trait TryOp: Sized { - // Zig: `comptime op_fn: *const fn(...)` — generic param preserves monomorphization. + // Generic param preserves monomorphization. fn try_op(&self, rhs: &Self, ctx: C, op_fn: impl Fn(C, f32, f32) -> f32) -> Option; } @@ -1629,5 +1615,3 @@ impl MulF32 for CSSNumber { self * rhs } } - -// ported from: src/css/generics.zig diff --git a/src/css/lib.rs b/src/css/lib.rs index 101d7f2b906..80da3aa27ac 100644 --- a/src/css/lib.rs +++ b/src/css/lib.rs @@ -5,14 +5,12 @@ // were translated against the crate's public surface and refer to it by name. extern crate self as bun_css; -/// Case-insensitive ASCII byte-slice dispatch — the fix for Zig's -/// `css.todo_stuff.match_ignore_ascii_case` sentinel and a drop-in port of +/// Case-insensitive ASCII byte-slice dispatch — equivalent to /// rust-cssparser's `match_ignore_ascii_case!`. /// /// Expands to an `if / else if / else` chain over /// [`bun_core::strings::eql_case_insensitive_ascii_check_length`] (length-checked, -/// ASCII-fold only, byte-wise — identical to Zig's -/// `bun.strings.eqlCaseInsensitiveASCIIICheckLength`). The whole macro is an +/// ASCII-fold only, byte-wise). The whole macro is an /// expression; arms may `return`, `break 'label`, or yield a value. /// /// Supports `|`-alternation and Rust-style `if` guards on arms; the trailing @@ -26,7 +24,6 @@ extern crate self as bun_css; /// _ => Err(location.new_unexpected_token_error(token)), /// }} /// ``` -// TODO(port): swap body to phf when CI hasher lands. #[macro_export] macro_rules! match_ignore_ascii_case { ($name:expr, { $( $($lit:literal)|+ $(if $guard:expr)? => $arm:expr ,)* _ => $fallback:expr $(,)? }) => {{ @@ -78,9 +75,9 @@ pub mod declaration; pub use context::{DeclarationContext, PropertyHandlerContext, SupportsEntry}; pub use declaration::{DeclarationBlock, DeclarationHandler, DeclarationList}; -// Path aliases the ported submodules expect at crate root (Zig's `css.*` -// namespace was flat; the Rust port re-nests under `values/`/`properties/` -// but most callers still spell `bun_css::css_values::...`). +// Path aliases the submodules expect at crate root (the crate re-nests under +// `values/`/`properties/` but most callers still spell +// `bun_css::css_values::...`). pub use properties as css_properties; pub use rules as css_rules; pub use selectors::selector; @@ -94,10 +91,10 @@ pub use css_parser::{ }; // ─── selectors/ crate-root surface ──────────────────────────────────────── -// The selector grammar references these via `bun_css::*` (Zig's flat `css.*` -// namespace). `Str` is the arena-borrowed `[]const u8` slice alias; here -// it's `*const [u8]` (matches `error.rs` / `values::ident` field shape) and -// becomes `&'bump [u8]` once the arena lifetime is plumbed. +// The selector grammar references these via `bun_css::*`. `Str` is the +// arena-borrowed byte-slice alias; here it's `*const [u8]` (matches +// `error.rs` / `values::ident` field shape) and becomes `&'bump [u8]` once +// the arena lifetime is plumbed. pub(crate) type Str = *const [u8]; /// Dereference an arena-owned [`Str`] into a slice borrow. @@ -120,23 +117,22 @@ pub(crate) unsafe fn arena_str(p: Str) -> &'static [u8] { unsafe { &*p } } pub use compat::Feature; -/// `css::ParseErrorKind` — Zig spelling. Alias of `error::ParserErrorKind`. +/// Alias of `error::ParserErrorKind`. pub use error::ParserErrorKind as ParseErrorKind; pub use error::ParserErrorKind as ErrorKind; pub use properties::custom::{TokenList, TokenListFns}; pub use values::ident::{CustomIdentFns, DashedIdentFns, IdentFns}; pub use values::string::{CssString as CSSString, CssStringFns as CSSStringFns}; -// `css::generic::*` is the Zig-spelled namespace for the protocol traits + -// reflection helpers. The Rust module is `generics`; alias both spellings so -// value/property modules can use `crate::generic::partial_cmp_f32` etc. +// `css::generic::*` is an alternate spelling of the protocol traits + +// reflection helpers in `generics`; alias both spellings so value/property +// modules can use `crate::generic::partial_cmp_f32` etc. pub use generics as generic; pub use generics::{implement_deep_clone, implement_eql, implement_hash}; // Same-name trait + derive macro re-export so `#[derive(bun_css::DeepClone)]` // (and `use bun_css::DeepClone;` at leaf sites) brings both into scope. pub use generics::{CssEql, DeepClone}; -// Keyword-enum / `union(enum)` derive macros (port of Zig's `DefineEnumProperty` -// / `DeriveParse` / `DeriveToCss` comptime fns). The `EnumProperty` *trait* is +// Keyword-enum / tagged-union derive macros. The `EnumProperty` *trait* is // re-exported above from `css_parser`; the *derive* of the same name lives in // the proc-macro crate. pub use bun_css_derive::{DefineEnumProperty, Parse, ToCss}; @@ -165,8 +161,6 @@ pub mod values_stub { pub mod color { pub use crate::values::color::*; - /// `Result(CssColor)` — Zig: `pub const ParseResult = Result(CssColor);` - /// where `Result(T) = Maybe(T, ParseError(ParserError))` (css_parser.zig:278). /// `Maybe` is now un-gated as `core::result::Result`, so this is a /// straight type alias to the real `values::color::ParseResult`. pub type CssColorParseResult = crate::values::color::ParseResult; @@ -209,7 +203,7 @@ impl core::fmt::Display for PrintErr { } impl core::error::Error for PrintErr {} -/// `PrintErr!T` return shape (Zig: `PrintErr!void`) used by every `to_css` +/// Return shape used by every `to_css` /// path. Distinct from `css_parser::PrintResult = Maybe`, /// which carries the rich `Err` — this is just the bubbled /// signal (the *kind* lives in `Printer.error_kind`). @@ -224,8 +218,7 @@ pub use css_parser::{ ParserOptions, StyleAttribute, StyleSheet, StylesheetExtra, ToCssResult, }; pub use printer::{ImportInfo, Printer, PrinterOptions, PseudoClasses}; -/// Dependent crates name this `ImportRecordHandler` (Zig had a now-removed -/// union of the same name in css_parser.zig:3783); the surviving type is +/// Dependent crates name this `ImportRecordHandler`; the surviving type is /// `printer::ImportInfo`, exposed under both names. pub type ImportRecordHandler<'a> = printer::ImportInfo<'a>; pub use values::color::{CssColor, FloatColor, LABColor, LabColor, PredefinedColor, RGBA}; @@ -281,7 +274,7 @@ impl VendorPrefix { ]; // NOTE: bitflags 2.x already generates `from_name(&str) -> Option`; - // the Zig `fromName` (panicking) is exposed as `from_name_str`. + // the panicking variant is exposed as `from_name_str`. #[inline] pub fn from_name_str(name: &str) -> VendorPrefix { match name { @@ -307,7 +300,7 @@ impl VendorPrefix { } pub fn difference_(left: Self, right: Self) -> Self { - // Zig used arithmetic subtraction on bits; preserve that. + // Arithmetic subtraction on bits, not set difference; callers depend on it. Self::from_bits_retain(left.bits().wrapping_sub(right.bits())) } @@ -387,15 +380,16 @@ pub struct Num { pub struct Dimension { pub num: Num, /// e.g. "px" - // TODO(port): arena lifetime — &'static placeholder per PORTING.md §AST crates. + // Borrows the parser arena/source; `&'static` placeholder per PORTING.md §AST crates. pub unit: &'static [u8], } /// CSS lexer token. Data-only definition hoisted out of `css_parser.rs`; the /// `to_css*`/`eql`/`hash` impls stay in `css_parser.rs` (gated) since they /// depend on `serializer::*` and `generics`. -// TODO(port): every &'static [u8] payload borrows the parser arena/source; -// thread `<'a>` once the bumpalo arena lifetime is plumbed. +// Every `&'static [u8]` payload actually borrows the parser arena/source text and +// must not outlive the arena; `&'static` is the crate-wide placeholder until the +// bumpalo arena lifetime is plumbed through. #[derive(Clone, Debug)] pub enum Token { Ident(&'static [u8]), @@ -441,8 +435,8 @@ pub enum Token { impl core::fmt::Display for Token { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - // Minimal rendering for error messages. The full Zig `Token.format` - // (CSS serialization) lives in `css_parser.rs` via `serializer::*`. + // Minimal rendering for error messages. Full CSS serialization + // lives in `css_parser.rs` via `serializer::*`. use bstr::BStr; match self { Token::Ident(v) diff --git a/src/css/logical.rs b/src/css/logical.rs index 4847ed448d0..a88c79c8683 100644 --- a/src/css/logical.rs +++ b/src/css/logical.rs @@ -20,5 +20,3 @@ pub enum LogicalGroup { MinSize, MaxSize, } - -// ported from: src/css/logical.zig diff --git a/src/css/media_query.rs b/src/css/media_query.rs index 328d6423a18..28b0014fd89 100644 --- a/src/css/media_query.rs +++ b/src/css/media_query.rs @@ -1,6 +1,4 @@ //! CSS [media queries](https://drafts.csswg.org/mediaqueries/). -//! -//! Ported from `src/css/media_query.zig`. use crate as css; use crate::css_properties::custom::EnvironmentVariable; @@ -10,9 +8,8 @@ use bun_alloc::ArenaPtr; pub use crate::Error; -// TODO(port): the CSS crate borrows strings from parser input with lifetime `'i` -// (matching lightningcss). This module avoids struct lifetime params; consider -// threading `'i` through `MediaType::Custom`, `Ident`, `DashedIdent`, etc. +// Strings here borrow parser input/arena memory but the structs carry no lifetime +// params (crate-wide `&'static`/raw-slice placeholder convention); see lib.rs. // ───────────────────────── value-type imports ───────────────────────── // Real `values/` payloads — the calc lattice has un-gated, so the local @@ -30,12 +27,10 @@ type CSSInteger = i32; // Selector, Unknown} and its `needs_parens(&Self)` / `b" not "` contract are // structurally different and must stay hand-rolled. // -// `deep_clone` is intentionally NOT on this trait. The Zig precedent is ONE -// reflective `css.implementDeepClone` (generics.zig); the Rust equivalent is +// `deep_clone` is intentionally NOT on this trait. The single mechanism is // `#[derive(DeepClone)]` (generics.rs). The hand-expansions in callers exist // only because of derive blockers — fix the derive, not the trait. -/// Trait modeling Zig's `ValidQueryCondition` comptime interface check. /// Any type that can appear as a node in a query-condition tree. pub trait QueryCondition: Sized + ToCss { /// Leaf payload: `QueryFeature<_>` for media/container, `Property` for @@ -181,13 +176,12 @@ pub enum MediaType { /// `screen`. Screen, /// An unknown / deprecated / custom media type. - // TODO(port): arena lifetime — Zig borrowed parser input. + // Lifetime: arena-borrowed parser input; valid until the arena is reset. Custom(*const [u8]), } -// PORT NOTE: hand-rolled — derived PartialEq on `*const [u8]` compares -// address+len, not byte content. Spec `MediaType.eql` compares slice bytes -// (via `css.implementEql`); adjacent-@media merging (rules.zig) depends on +// Hand-rolled — derived PartialEq on `*const [u8]` compares +// address+len, not byte content. Adjacent-@media merging depends on // content equality across distinct arena offsets. impl PartialEq for MediaType { fn eq(&self, other: &Self) -> bool { @@ -203,7 +197,6 @@ impl PartialEq for MediaType { } // Flags for `parse_query_condition`. -// PORT NOTE: Zig `packed struct(u8)` with two bool fields → bitflags! bitflags::bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct QueryConditionFlags: u8 { @@ -255,8 +248,7 @@ pub enum MediaFeatureName { Unknown(Ident), } -// PORT NOTE: `eql` was hand-written byte compare on the ident slices; data-only -// PartialEq derived once `Ident`/`DashedIdent` gain `PartialEq` (values/ un-gate). +// Data-only PartialEq, derived once `Ident`/`DashedIdent` gained `PartialEq`. impl PartialEq for MediaFeatureName { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -296,9 +288,9 @@ pub enum QueryFeature { } /// Comparison operator in a range media feature. -// PORT NOTE: discriminants are power-of-two bitflags — Zig media_query.zig -// bitwise-ORs `@intFromEnum(start_operator) | @intFromEnum(end_operator)` to -// validate interval operator pairs. Do NOT use implicit 0..=4. +// Discriminants are power-of-two bitflags — interval-operator-pair +// validation bitwise-ORs the start/end operator discriminants. +// Do NOT use implicit 0..=4. #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::IntoStaticStr)] pub enum MediaFeatureComparison { @@ -315,7 +307,7 @@ pub enum MediaFeatureComparison { } /// [media feature value](https://drafts.csswg.org/mediaqueries/#typedef-mf-value). -// PORT NOTE: `Debug` hand-rolled below — `Length` (calc tree) does not derive +// `Debug` hand-rolled below — `Length` (calc tree) does not derive // `Debug`, but the `MediaCondition`/`QueryFeature` chain wants it for // diagnostics. #[derive(Clone)] @@ -355,8 +347,8 @@ impl core::fmt::Debug for MediaFeatureValue { } } -// PORT NOTE: derive(PartialEq) blocked on `Ident`/`EnvironmentVariable` lacking -// std `PartialEq`; hand-roll all arms (Zig: `css.implementEql`). +// derive(PartialEq) blocked on `Ident`/`EnvironmentVariable` lacking +// std `PartialEq`; hand-roll all arms. impl PartialEq for MediaFeatureValue { fn eq(&self, other: &Self) -> bool { use MediaFeatureValue as V; @@ -368,8 +360,7 @@ impl PartialEq for MediaFeatureValue { (V::Resolution(a), V::Resolution(b)) => a == b, (V::Ratio(a), V::Ratio(b)) => a == b, (V::Ident(a), V::Ident(b)) => a.v() == b.v(), - // Zig: `css.implementEql` recurses into `EnvironmentVariable.eql` — - // ported via the `CssEql` derive on `EnvironmentVariable` + // `CssEql` derive on `EnvironmentVariable` // (name + indices + fallback structural equality). (V::Env(a), V::Env(b)) => { use crate::generics::CssEql as _; @@ -402,7 +393,6 @@ pub enum MediaFeatureType { } impl MediaFeatureType { - /// Zig: `MediaFeatureType.allowsRanges`. pub fn allows_ranges(self) -> bool { use MediaFeatureType as T; matches!( @@ -412,8 +402,7 @@ impl MediaFeatureType { } } -/// Trait modeling Zig's `MediaFeatureId`-shape comptime interface for the -/// generic `QueryFeature`. +/// Feature-id interface for the generic `QueryFeature`. pub trait FeatureIdTrait: Copy + PartialEq + Eq { fn value_type(&self) -> MediaFeatureType; fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr>; @@ -554,7 +543,6 @@ pub enum MediaFeatureId { } impl MediaFeatureId { - // Zig: `pub const valueType = css.DeriveValueType(@This(), ValueTypeMap).valueType;` pub(crate) fn value_type(self) -> MediaFeatureType { use MediaFeatureId::*; use MediaFeatureType as T; @@ -602,8 +590,8 @@ impl FeatureIdTrait for MediaFeatureId { MediaFeatureId::value_type(*self) } fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { - // Zig: `css.DefineEnumProperty(@This()).toCss` — emits the lowercase - // tag name. `strum::IntoStaticStr` already carries those strings. + // Emits the lowercase tag name. `strum::IntoStaticStr` already + // carries those strings. dest.write_str(<&'static str>::from(*self)) } fn to_css_with_prefix( @@ -612,8 +600,8 @@ impl FeatureIdTrait for MediaFeatureId { dest: &mut Printer, ) -> core::result::Result<(), PrintErr> { match self { - // Zig: `-webkit-{s}device-pixel-ratio` — webkit places the - // min/max prefix between the vendor prefix and the feature name. + // webkit places the min/max prefix between the vendor prefix + // and the feature name. MediaFeatureId::WebkitDevicePixelRatio => { dest.write_str("-webkit-")?; dest.write_str(prefix)?; @@ -626,8 +614,8 @@ impl FeatureIdTrait for MediaFeatureId { } } fn from_str(s: &[u8]) -> Option { - // Zig: `css.DefineEnumProperty(@This()).parse` — case-insensitive - // ASCII tag-name table. No dependency on the gated `values/` lattice. + // Case-insensitive ASCII tag-name table. No dependency on the gated + // `values/` lattice. use MediaFeatureId::*; crate::match_ignore_ascii_case! { s, { b"width" => Some(Width), @@ -762,7 +750,7 @@ impl MediaQuery { } } -/// Zig: `toCssWithParensIfNeeded` — wraps `v.to_css()` in parentheses when the +/// Wraps `v.to_css()` in parentheses when the /// caller's grammar position requires it. pub(crate) fn to_css_with_parens_if_needed( v: &T, @@ -779,7 +767,7 @@ pub(crate) fn to_css_with_parens_if_needed( Ok(()) } -/// Zig: `operationToCss` — serialize `a OP b OP c ...` with per-child parens. +/// Serialize `a OP b OP c ...` with per-child parens. pub(crate) fn operation_to_css( operator: Operator, conditions: &[C], @@ -868,7 +856,6 @@ impl QueryCondition for MediaCondition { } } fn parse_style_query(input: &mut Parser) -> Result { - // Zig: `return .{ .err = input.newErrorForNextToken() }` Err(input.new_error_for_next_token()) } fn needs_parens( @@ -952,7 +939,6 @@ impl QueryFeature { } impl MediaFeatureName { - /// Zig: `MediaFeatureName.valueType`. pub(crate) fn value_type(&self) -> MediaFeatureType { match self { MediaFeatureName::Standard(standard) => standard.value_type(), @@ -963,8 +949,6 @@ impl MediaFeatureName { pub(crate) fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { match self { MediaFeatureName::Standard(v) => v.to_css(dest), - // PORT NOTE: Zig `DashedIdentFns.toCss` → `dest.writeDashedIdent` - // (handles css-module name rewriting). MediaFeatureName::Custom(d) => d.to_css(dest), MediaFeatureName::Unknown(v) => v.to_css(dest), } @@ -991,8 +975,6 @@ impl MediaFeatureName { /// Parses a media feature name. Returns `(name, legacy_comparator)` — /// `legacy_comparator` is `Some` when the ident carried a `min-`/`max-` /// prefix (lowered to `>=`/`<=`). - /// - /// Zig: `MediaFeatureName.parse`. pub(crate) fn parse(input: &mut Parser) -> Result<(Self, Option)> { use bun_core::strings; let ident = input.expect_ident_cloned()?; @@ -1021,13 +1003,9 @@ impl MediaFeatureName { None }; - // PORT NOTE: Zig `allocPrint("-webkit-{s}", .{name})` then - // `parse_utility.parseString(.., FeatureId.parse)` — the re-tokenize is - // only to feed `DefineEnumProperty.parse` an ident token. Here - // `FeatureIdTrait::from_str` does the same case-insensitive table lookup - // directly, so a stack buffer suffices and the temp string is freed - // immediately (Zig asserts `FeatureId` is an enum for the same reason). - // PERF: stack buffer here? + // `FeatureIdTrait::from_str` does a case-insensitive table lookup + // directly, so a stack buffer suffices for re-attaching the + // `-webkit-` prefix. let mut webkit_buf: [u8; 64] = [0; 64]; let final_name: &[u8] = if is_webkit { let len = 8 + name.len(); @@ -1057,8 +1035,8 @@ impl MediaFeatureName { impl MediaFeatureComparison { pub(crate) fn to_css(self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { match self { - // PORT NOTE(suspect): Zig emits '-' for `Equal` (media_query.zig:1156), - // diverging from the spec `=` and from this enum's strum tag. Ported + // Suspect but intentional: emits '-' for `Equal`, diverging from + // the spec `=` and from this enum's strum tag. Preserved // byte-for-byte; revisit if upstream fixes. MediaFeatureComparison::Equal => dest.delim(b'-', true), MediaFeatureComparison::GreaterThan => dest.delim(b'>', true), @@ -1107,11 +1085,10 @@ impl MediaFeatureValue { } } - /// Zig: `addF32` — adjust by `other` for strict-inequality → min/max + /// Adjust by `other` for strict-inequality → min/max /// boundary lowering. Consumes `self`. pub(crate) fn add_f32(self, other: f32) -> MediaFeatureValue { match self { - // Zig: `len.add(arena, Length.px(other))` — calc lattice. MediaFeatureValue::Length(len) => MediaFeatureValue::Length(len.add(Length::px(other))), MediaFeatureValue::Number(num) => MediaFeatureValue::Number(num + other), MediaFeatureValue::Integer(num) => { @@ -1121,11 +1098,14 @@ impl MediaFeatureValue { MediaFeatureValue::Resolution(res) => MediaFeatureValue::Resolution(res.add_f32(other)), MediaFeatureValue::Ratio(ratio) => MediaFeatureValue::Ratio(ratio.add_f32(other)), MediaFeatureValue::Ident(id) => MediaFeatureValue::Ident(id), - MediaFeatureValue::Env(env) => MediaFeatureValue::Env(env), // TODO: calc support + // env() values pass through unchanged — the calc-lattice add + // (wrapping the env() reference in a calc() to apply the + // strict-inequality boundary adjustment) is not implemented, so + // range bounds on env() values lose the +/-0.001 nudge. + MediaFeatureValue::Env(env) => MediaFeatureValue::Env(env), } } - /// Zig: `MediaFeatureValue.valueType`. pub(crate) fn value_type(&self) -> MediaFeatureType { use MediaFeatureValue as V; match self { @@ -1140,7 +1120,6 @@ impl MediaFeatureValue { } } - /// Zig: `MediaFeatureValue.checkType`. pub(crate) fn check_type(&self, expected_type: MediaFeatureType) -> bool { let vt = self.value_type(); if expected_type == MediaFeatureType::Unknown || vt == MediaFeatureType::Unknown { @@ -1212,10 +1191,8 @@ impl MediaFeatureValue { return Ok(MediaFeatureValue::Resolution(res)); } - // PORT NOTE: Zig `input.tryParse(EnvironmentVariable.parse, .{})` left - // `options`/`depth` undefined (tryParse builds `ArgsTuple` and only - // fills index 0) — UB. Fixed here by threading the real `ParserOptions` - // down from `QueryFeature::parse` and passing `depth = 0`. + // The real `ParserOptions` are threaded down from + // `QueryFeature::parse`; `depth = 0` since this is a fresh value. if let Ok(env) = input.try_parse(|i| EnvironmentVariable::parse(i, options, 0)) { return Ok(MediaFeatureValue::Env(env)); } @@ -1225,7 +1202,7 @@ impl MediaFeatureValue { } } -/// Zig: `writeMinMax` — lower a range/interval comparator to legacy +/// Lower a range/interval comparator to legacy /// `min-`/`max-` prefixed plain feature. fn write_min_max( operator: MediaFeatureComparison, @@ -1249,8 +1226,7 @@ fn write_min_max( dest.delim(b':', false)?; - // PORT NOTE: Zig deepCloned `value` into `dest.arena` then mutated; here - // `MediaFeatureValue: Clone` so we clone-by-value. + // `MediaFeatureValue: Clone`, so clone-by-value before adjusting. let adjusted: Option = match operator { MediaFeatureComparison::GreaterThan => Some(value.clone().add_f32(0.001)), MediaFeatureComparison::LessThan => Some(value.clone().add_f32(-0.001)), @@ -1267,19 +1243,18 @@ fn write_min_max( } // ───────────────────────── deep_clone ───────────────────────── -// Arena-aware `deep_clone` — port of Zig's per-type `deepClone(arena)` -// bodies. Un-gated this round so `rules::dc::{media_list,query_feature}` can -// route through real impls instead of `#[derive(Clone)]` passthroughs. +// Arena-aware `deep_clone`. Un-gated this round so +// `rules::dc::{media_list,query_feature}` can route through real impls +// instead of `#[derive(Clone)]` passthroughs. // -// PORT NOTE: written as **inherent** methods (not `#[derive(DeepClone)]`) to -// match the Zig hand-written bodies exactly: Zig copies `name`/`qualifier`/ -// `media_type`/`operator` fields by value (they are `Copy`/arena-slice types -// under the generics.zig "const strings" rule) and only recurses into the -// allocating payloads (`Vec`, `Box`, `MediaFeatureValue`). The derive would +// Written as **inherent** methods (not `#[derive(DeepClone)]`): +// `name`/`qualifier`/`media_type`/`operator` fields are copied by value +// (they are `Copy`/arena-slice types) and only the allocating payloads +// (`Vec`, `Box`, `MediaFeatureValue`) are recursed into. The derive would // instead add a spurious `FeatureId: DeepClone<'bump>` where-bound. impl MediaList { - /// Zig: `MediaList.deepClone` — element-wise clone of `media_queries`. + /// Element-wise clone of `media_queries`. pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { let mut media_queries = Vec::with_capacity_in(self.media_queries.len(), ArenaPtr::new(bump)); @@ -1287,13 +1262,13 @@ impl MediaList { Self { media_queries } } - /// Zig: `pub fn clone(this, arena)` — alias for `deepClone`. + /// Alias for `deep_clone`. #[inline] pub fn clone_in(&self, bump: &bun_alloc::Arena) -> Self { self.deep_clone(bump) } - /// Zig: `MediaList.cloneWithImportRecords` — `MediaList` carries no + /// `MediaList` carries no /// `ImportRecord` indices so this is just `deep_clone`. #[inline] pub fn clone_with_import_records( @@ -1304,7 +1279,7 @@ impl MediaList { self.deep_clone(bump) } - /// Zig: `pub const eql = css.implementEql(@This())` — structural eq. + /// Structural eq. #[inline] pub fn eql(&self, other: &Self) -> bool { self == other @@ -1312,7 +1287,7 @@ impl MediaList { } impl MediaQuery { - /// Zig: `MediaQuery.deepClone` — field-wise. + /// Field-wise. pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { Self { qualifier: self.qualifier, @@ -1323,8 +1298,7 @@ impl MediaQuery { } impl MediaType { - /// Zig: `css.implementDeepClone` — `Custom([]const u8)` is an arena-owned - /// slice (identity copy under the generics.zig "const strings" rule). + /// `Custom` is an arena-owned slice (identity copy). #[inline] pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { *self @@ -1332,14 +1306,13 @@ impl MediaType { } impl MediaCondition { - /// Zig: `MediaCondition.deepClone` — variant-wise recursion. + /// Variant-wise recursion. pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { let alloc = ArenaPtr::new(bump); match self { MediaCondition::Feature(f) => { MediaCondition::Feature(Box::new_in(f.deep_clone(bump), alloc)) } - // Zig: `bun.create(arena, MediaCondition, c.deepClone(arena))` MediaCondition::Not(c) => MediaCondition::Not(Box::new_in(c.deep_clone(bump), alloc)), MediaCondition::Operation { operator, @@ -1357,7 +1330,7 @@ impl MediaCondition { } impl MediaFeatureName { - /// Zig: struct-copy (`name = this.plain.name`). All payloads are `Copy` / + /// All payloads are `Copy` / /// arena-slice idents; `derive(Clone)` is the faithful deep clone. #[inline] pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { @@ -1366,7 +1339,7 @@ impl MediaFeatureName { } impl QueryFeature { - /// Zig: `QueryFeature.deepClone` — variant-wise; `name`/`operator` are + /// Variant-wise; `name`/`operator` are /// value-copied, `MediaFeatureValue` recurses. pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { match self { @@ -1404,14 +1377,13 @@ impl QueryFeature { } impl MediaFeatureValue { - /// Zig: `MediaFeatureValue.deepClone` — variant-wise. + /// Variant-wise. pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { use MediaFeatureValue as V; match self { - // Zig: `l.deepClone(arena)` — real `values::length::Length` - // owns a calc tree. The local `value_shims::Length` stand-in is a - // unit struct, so `Clone` is faithful until the calc lattice - // un-gates and the shim is replaced. + // The real `values::length::Length` owns a calc tree. The local + // `value_shims::Length` stand-in is a unit struct, so `Clone` is + // faithful until the calc lattice un-gates and the shim is replaced. V::Length(l) => V::Length(l.clone()), V::Number(n) => V::Number(*n), V::Integer(i) => V::Integer(*i), @@ -1419,7 +1391,7 @@ impl MediaFeatureValue { V::Resolution(r) => V::Resolution(*r), V::Ratio(r) => V::Ratio(*r), V::Ident(i) => V::Ident(i.deep_clone(bump)), - // Zig: `e.deepClone(arena)` — `EnvironmentVariable` carries + // `EnvironmentVariable` carries // `Vec` + `Option`; route through its // `#[derive(DeepClone)]` impl. V::Env(e) => { @@ -1477,7 +1449,6 @@ impl MediaList { match input.next() { Ok(tok) => { if !matches!(tok, css::Token::Comma) { - // Zig: bun.Output.panic(...) — see media_query.zig:54. unreachable!( "expected a comma after parsing a MediaQuery — bug in CSS parser" ); @@ -1493,7 +1464,6 @@ impl MediaList { impl MediaQuery { pub fn parse(input: &mut Parser, options: &css::ParserOptions) -> Result { - // Zig: `Fn.tryParseFn` returning `(?Qualifier, ?MediaType)`. let (qualifier, explicit_media_type) = input .try_parse(|i| -> Result<(Option, Option)> { let qualifier = i.try_parse(Qualifier::parse).ok(); @@ -1650,8 +1620,6 @@ fn parse_paren_block( flags: QueryConditionFlags, options: &css::ParserOptions, ) -> Result { - // Zig: `Closure { flags }.parseNestedBlockFn` — collapsed to a closure - // capturing `flags`/`options` by copy/reborrow. input.parse_nested_block(|i| { if let Ok(inner) = i.try_parse(|i2| parse_query_condition_with_options::(i2, flags, options)) @@ -1665,8 +1633,6 @@ fn parse_paren_block( impl QueryFeature { /// Parse a media/container feature inside `(` `)`. /// - /// Zig: `QueryFeature.parse` (media_query.zig:945). - /// /// Forwarder kept for callers that don't yet thread `ParserOptions` /// (e.g. `rules::container::ContainerCondition::parse_feature`). #[inline] @@ -1694,7 +1660,6 @@ impl QueryFeature { } } - /// Zig: `QueryFeature.parseNameFirst`. pub(crate) fn parse_name_first( input: &mut Parser, options: &css::ParserOptions, @@ -1730,18 +1695,15 @@ impl QueryFeature { } } - /// Zig: `QueryFeature.parseValueFirst`. pub(crate) fn parse_value_first( input: &mut Parser, options: &css::ParserOptions, ) -> Result { // We need to find the feature name first so we know the type. let start = input.state(); - // PORT NOTE: Zig loops `MediaFeatureName.parse` then checks - // `isExhausted()` — but `expectIdent` does not advance on error, so - // the literal Zig body would spin on a non-ident token. The intent - // (matching lightningcss) is to *skip* tokens until the name is - // found; advance one token per failed attempt. + // Skip tokens (matching lightningcss) until the name is found; + // advance one token per failed attempt so a non-ident token cannot + // cause an infinite loop. let name: MediaFeatureName = loop { if let Ok((name, legacy_op)) = input.try_parse(MediaFeatureName::::parse) { if legacy_op.is_some() { @@ -1774,8 +1736,8 @@ impl QueryFeature { let start_operator = operator.unwrap(); let end_operator = end_operator_.unwrap(); // Start and end operators must be matching. - // PORT NOTE: discriminants are bitflags (1/2/4/8/16) — see the - // comment on `MediaFeatureComparison`. Zig bitwise-ORs them. + // Discriminants are bitflags (1/2/4/8/16) — see the + // comment on `MediaFeatureComparison`. const GT: u8 = MediaFeatureComparison::GreaterThan as u8; const GTE: u8 = MediaFeatureComparison::GreaterThanEqual as u8; const LT: u8 = MediaFeatureComparison::LessThan as u8; @@ -1819,7 +1781,7 @@ impl QueryFeature { /// Consumes an operation or a colon, or returns an error. /// -/// Zig: `consumeOperationOrColon` (media_query.zig:1103). Returns `Ok(None)` +/// Returns `Ok(None)` /// when a colon was consumed (and `allow_colon`); `Ok(Some(op))` for `<`/`>`/`=`. fn consume_operation_or_colon( input: &mut Parser, @@ -1853,5 +1815,3 @@ fn consume_operation_or_colon( _ => Err(location.new_unexpected_token_error(css::Token::Delim(first_delim))), } } - -// ported from: src/css/media_query.zig diff --git a/src/css/prefixes.rs b/src/css/prefixes.rs index 2fce358a90f..9e9d39324d8 100644 --- a/src/css/prefixes.rs +++ b/src/css/prefixes.rs @@ -1,4 +1,4 @@ -// Ported from src/css/prefixes.zig (autogenerated by build-prefixes.js). +// Autogenerated by build-prefixes.js. // DO NOT EDIT BY HAND — regenerate via `build-prefixes.js --rs` when the // caniuse tables change. diff --git a/src/css/printer.rs b/src/css/printer.rs index fdca930e064..42bc1815367 100644 --- a/src/css/printer.rs +++ b/src/css/printer.rs @@ -15,8 +15,7 @@ use css_values::ident::DashedIdent; pub use css::Error; -// TODO(port): move to _sys / clarify which Write trait. Zig used *std.Io.Writer -// (byte-oriented: writeAll/writeByte/print/splatByteAll). Using a local dyn trait alias. +// Byte-oriented writer (writeAll/writeByte/print equivalents). use bun_io::Write; /// Options that control how CSS is serialized to a string. @@ -148,8 +147,9 @@ pub struct Printer<'a> { /// from JavaScript. Useful for polyfills, for example. pub pseudo_classes: Option>, pub indentation_buf: BumpVec<'a, u8>, - // TODO(port): lifetime — ctx is set to a stack-local during with_context() and restored - // after; `&'a StyleContext<'a>` will not borrow-check there. May need raw `*const StyleContext`. + // INVARIANT: `with_context()` points this at a stack-local `StyleContext` (via an + // unsafe variance cast — see the SAFETY note there) and always restores the parent + // before that frame returns; never stash `ctx` beyond the `with_context` call. pub ctx: Option<&'a css::StyleContext<'a>>, /// Number of parent-selector substitutions performed for `&` while /// serializing the current rule prelude with compiled nesting (targets @@ -183,7 +183,6 @@ pub struct Printer<'a> { pub local_names: Option<&'a css::LocalsResultsMap>, /// NOTE This should be the same mimalloc heap arena arena pub arena: &'a Bump, - // TODO: finish the fields } #[cfg(debug_assertions)] @@ -221,10 +220,8 @@ impl<'a> Printer<'a> { self.lookup_symbol(ident.as_ref().unwrap()) } - // Zig checked vtable identity against std.Io.Writer.Allocating and recovered the - // backing buffer length via `container_of`; in Rust the trait exposes `written_len()` - // directly (Vec / MutableString / counting sinks override it, others panic — same - // contract as Zig's `@panic("css: got bad writer type")` fallthrough). + // The `Write` trait exposes `written_len()` directly + // (Vec / MutableString / counting sinks override it, others panic). #[inline] fn get_written_amt(writer: &dyn Write) -> usize { writer.written_len() @@ -300,7 +297,7 @@ impl<'a> Printer<'a> { Err(PrintErr::CSSPrintError) } - // PORT NOTE: deinit() dropped — scratchbuf/indentation_buf/dependencies are arena-backed + // deinit() dropped — scratchbuf/indentation_buf/dependencies are arena-backed // BumpVec<'a, _>; freed in bulk by `arena.reset()`. No explicit Drop impl needed. /// If `import_records` is null, then the printer will error when it encounters code that relies on import records (urls()) @@ -341,7 +338,6 @@ impl<'a> Printer<'a> { column: 1, }, symbols, - // defaults for fields not set by Zig's `.{}` initializer indent_amt: 0, line: 0, col: 0, @@ -357,9 +353,8 @@ impl<'a> Printer<'a> { } /// Construct a `Printer` that writes into an in-memory `Vec` buffer - /// using default `PrinterOptions`. Mirrors the Zig pattern of pairing - /// `std.Io.Writer.Allocating` with `Printer.new(..., PrinterOptions.default(), ...)` - /// for sub-serialization (e.g. `PseudoClass::toCss`, `Selector` debug fmt). + /// using default `PrinterOptions`. Used for sub-serialization + /// (e.g. `PseudoClass::toCss`, `Selector` debug fmt). pub fn new_buffered( arena: &'a Bump, dest: &'a mut Vec, @@ -391,10 +386,10 @@ impl<'a> Printer<'a> { let import_record = &info.import_records[import_record_idx as usize]; let [a, b] = bun_core::cheap_prefix_normalizer(self.public_path, import_record.path.text); - // PORT NOTE: reshaped for borrowck — copied (a, b) out before re-borrowing &mut self + // Reshaped for borrowck — copied (a, b) out before re-borrowing &mut self let a = a.to_vec(); let b = b.to_vec(); - // PERF(port): two small heap copies above; Zig borrowed directly. Profile if it shows up on a hot path. + // PERF: two small heap copies above; profile if it shows up on a hot path. self.write_str(&a)?; self.write_str(&b)?; return Ok(()); @@ -518,13 +513,22 @@ impl<'a> Printer<'a> { Ok(()) } - /// Alias of `write_str` for callers that want to be explicit about - /// possibly-newline-containing byte content (Zig: `writeBytes`). + /// Like `write_str`, but newline-containing byte content is permitted: + /// `line`/`col` are tracked across newlines (matching `write_char` applied + /// byte-by-byte), whereas `write_str` debug-asserts that no newlines are + /// present. Used for raw token round-trip content (whitespace tokens, + /// comments, unparsed contents). #[inline] pub fn write_bytes(&mut self, s: &[u8]) -> PrintResult<()> { - // TODO(port): Zig writeBytes did not assert no-newline; tracked - // line/col separately. For now route through write_str. - self.col += u32::try_from(s.len()).expect("int cast"); + // Unlike `write_str`, newlines are allowed here; track line/col across them + // (matching `write_char` applied byte-by-byte) so source maps stay correct. + if let Some(last_newline) = s.iter().rposition(|&b| b == b'\n') { + let new_lines = s.iter().filter(|&&b| b == b'\n').count(); + self.line += u32::try_from(new_lines).expect("int cast"); + self.col = u32::try_from(s.len() - last_newline - 1).expect("int cast"); + } else { + self.col += u32::try_from(s.len()).expect("int cast"); + } if self.dest.write_all(s).is_err() { return Err(self.add_fmt_error()); } @@ -569,7 +573,7 @@ impl<'a> Printer<'a> { let Some(symbol) = self.symbols.get_const(ref_) else { return Err(self.add_fmt_error()); }; - // PORT NOTE: copy out the arena slice before re-borrowing &mut self. + // Copy out the arena slice before re-borrowing &mut self. let name = symbol.original_name.slice(); return self.serialize_identifier(name); } @@ -588,10 +592,8 @@ impl<'a> Printer<'a> { pub fn write_ident(&mut self, ident: &'a [u8], handle_css_module: bool) -> PrintResult<()> { if handle_css_module { if self.css_module.is_some() { - // PORT NOTE: borrowck reshape — Zig captured `&mut self` inside the closure - // while `css_module` (a field of self) was simultaneously borrowed. We instead - // copy the `'a`-lifetime references out of `css_module` up front so the - // closure can hold the sole `&mut self`. + // Copy the `'a`-lifetime references out of `css_module` up front so + // the closure can hold the sole `&mut self`. let source_index = self.loc.source_index as usize; let arena = self.arena; let (config, hash, source): (&'a css::css_modules::Config, &'a [u8], &'a [u8]) = { @@ -662,7 +664,7 @@ impl<'a> Printer<'a> { None => false, }; if dashed_idents { - // PORT NOTE: same borrowck reshape as `write_ident`. + // Same borrowck reshape as `write_ident`. let source_index = self.loc.source_index as usize; let arena = self.arena; let (config, hash, source): (&'a css::css_modules::Config, &'a [u8], &'a [u8]) = { @@ -836,9 +838,8 @@ impl<'a> Printer<'a> { let ctx = css::StyleContext { selectors, parent }; - // TODO(port): lifetime — `&ctx` is stack-local but field type is `&'a StyleContext<'a>`. - // Zig relied on restoring `parent` before return. Consider changing the field to raw - // `*const StyleContext` or restructuring StyleContext as an explicit stack. + // `&ctx` is stack-local but the field type is `&'a StyleContext<'a>`; + // soundness relies on restoring `parent` before this frame returns. // SAFETY: ctx outlives the call to func; self.ctx is restored to `parent` before return. // Inner-lifetime variance cast via raw pointer (`StyleContext<'x>` and // `StyleContext<'a>` share layout; only the borrow-checker tag differs). @@ -892,5 +893,3 @@ impl<'a> Printer<'a> { // bun.ast.Symbol.Map — lives in bun_logger. type SymbolMap = bun_ast::symbol::Map; - -// ported from: src/css/printer.zig diff --git a/src/css/properties/align.rs b/src/css/properties/align.rs index f812330a582..ec410269047 100644 --- a/src/css/properties/align.rs +++ b/src/css/properties/align.rs @@ -17,10 +17,7 @@ use crate::css_properties::flex::{ // ────────────────────────────────────────────────────────────────────────────── /// A value for the [align-content](https://www.w3.org/TR/css-align-3/#propdef-align-content) property. -#[derive(Clone, PartialEq, Eq)] -// Zig: `css.DeriveParse(@This()).parse` / `css.DeriveToCss(@This()).toCss` — -// comptime-reflection generators ported as proc-macro derives. -#[derive(css::Parse, css::ToCss)] +#[derive(Clone, PartialEq, Eq, css::Parse, css::ToCss)] pub enum AlignContent { /// Default alignment. Normal, @@ -32,10 +29,9 @@ pub enum AlignContent { ContentPosition(AlignContentContentPosition), } -// Zig: anonymous payload struct carrying `pub fn __generateToCss() void {}` — -// the marker telling `DeriveToCss` to auto-generate the field-sequence printer. -// In Rust the equivalent is `#[derive(css::ToCss)]` on the lifted named-field -// struct (see `css_derive::expand_derive_to_css` struct branch); the enum arm's +// `#[derive(css::ToCss)]` + `#[css(generate_to_css)]` on the lifted +// named-field struct auto-generates the field-sequence printer (see +// `css_derive::expand_derive_to_css` struct branch); the enum arm's // `__inner.to_css(dest)` then resolves to this generated inherent. #[derive(Clone, PartialEq, Eq, css::ToCss)] #[css(generate_to_css)] @@ -210,9 +206,7 @@ impl JustifyContent { // ────────────────────────────────────────────────────────────────────────────── /// A value for the [align-self](https://www.w3.org/TR/css-align-3/#align-self-property) property. -#[derive(Clone, PartialEq, Eq)] -// Zig: `css.DeriveParse` / `css.DeriveToCss` -#[derive(css::Parse, css::ToCss)] +#[derive(Clone, PartialEq, Eq, css::Parse, css::ToCss)] pub enum AlignSelf { /// Automatic alignment. Auto, @@ -226,7 +220,7 @@ pub enum AlignSelf { SelfPosition(AlignSelfSelfPosition), } -// Zig: `__generateToCss` marker — see `AlignContentContentPosition` note. +// See `AlignContentContentPosition` note. #[derive(Clone, PartialEq, Eq, css::ToCss)] #[css(generate_to_css)] pub struct AlignSelfSelfPosition { @@ -380,9 +374,7 @@ impl JustifySelf { // ────────────────────────────────────────────────────────────────────────────── /// A value for the [align-items](https://www.w3.org/TR/css-align-3/#align-items-property) property. -#[derive(Clone, PartialEq, Eq)] -// Zig: `css.DeriveParse` / `css.DeriveToCss` -#[derive(css::Parse, css::ToCss)] +#[derive(Clone, PartialEq, Eq, css::Parse, css::ToCss)] pub enum AlignItems { /// Default alignment. Normal, @@ -394,7 +386,7 @@ pub enum AlignItems { SelfPosition(AlignItemsSelfPosition), } -// Zig: `__generateToCss` marker — see `AlignContentContentPosition` note. +// See `AlignContentContentPosition` note. #[derive(Clone, PartialEq, Eq, css::ToCss)] #[css(generate_to_css)] pub struct AlignItemsSelfPosition { @@ -604,9 +596,7 @@ impl LegacyJustify { /// A [gap](https://www.w3.org/TR/css-align-3/#column-row-gap) value, as used in the /// `column-gap` and `row-gap` properties. -#[derive(Clone, PartialEq)] -// Zig: `css.DeriveParse` / `css.DeriveToCss` -#[derive(css::Parse, css::ToCss)] +#[derive(Clone, PartialEq, css::Parse, css::ToCss)] pub enum GapValue { /// Equal to `1em` for multi-column containers, and zero otherwise. Normal, @@ -624,9 +614,6 @@ pub struct Gap { } impl Gap { - // TODO(port): PropertyFieldMap was a comptime struct mapping fields → CSS property names - // (.row = "row-gap", .column = "column-gap"); could encode as derive attrs. - pub(crate) fn parse(input: &mut Parser) -> CssResult { let row = GapValue::parse(input)?; let column = input @@ -659,9 +646,6 @@ pub struct PlaceItems { } impl PlaceItems { - // TODO(port): PropertyFieldMap (.align = "align-items", .justify = "justify-items") - // TODO(port): VendorPrefixMap (.align = true) - pub(crate) fn parse(input: &mut Parser) -> CssResult { let align = AlignItems::parse(input)?; let justify = match input.try_parse(JustifyItems::parse) { @@ -724,9 +708,6 @@ pub struct PlaceSelf { } impl PlaceSelf { - // TODO(port): PropertyFieldMap (.align = "align-self", .justify = "justify-self") - // TODO(port): VendorPrefixMap (.align = true) - pub(crate) fn parse(input: &mut Parser) -> CssResult { let align = AlignSelf::parse(input)?; let justify = match input.try_parse(JustifySelf::parse) { @@ -817,9 +798,6 @@ pub struct PlaceContent { } impl PlaceContent { - // TODO(port): PropertyFieldMap (.align = PropertyIdTag::AlignContent, .justify = PropertyIdTag::JustifyContent) - // TODO(port): VendorPrefixMap (.align = true, .justify = true) - pub(crate) fn parse(input: &mut Parser) -> CssResult { let align = AlignContent::parse(input)?; let justify = match JustifyContent::parse(input) { @@ -976,12 +954,10 @@ pub struct AlignHandler { pub has_any: bool, } -// ─── helper macros (Zig used `comptime prop: []const u8` + `@field` / `@unionInit`) ─── +// ─── helper macros ─── // -// TODO(port): the Zig source threads field names as comptime strings into helper fns -// and uses @field/@unionInit for reflection. Rust cannot pass field names as values, so -// these are macro_rules! that expand at each call site; a small proc-macro could dedupe -// if maintenance burden is high. +// Field names cannot be passed as values, so these are macro_rules! that +// expand at each call site. macro_rules! handle_property_maybe_flush { ($this:expr, $dest:expr, $context:expr, $field:ident, $val:expr, $vp:expr) => {{ @@ -1057,9 +1033,6 @@ macro_rules! flush_legacy_property { prefixes_2009.insert(VendorPrefix::MOZ); } if !prefixes_2009.is_empty() { - // TODO(port): Zig branched on `T == BoxOrdinalGroup` to bypass - // from_standard. Never true at any callsite in this file; preserved - // as a note in case the macro is reused elsewhere. let s = <$ty2009>::from_standard(val); if let Some(a) = s { $dest.push(Property::$variant2009((a, prefixes_2009))); @@ -1140,9 +1113,6 @@ macro_rules! flush_shorthand_helper { align.clone(), justify_actual.clone(), ))); - // TODO(port): Zig built `prop.ty{ .align = ..., .justify = ... }` directly. - // Using a `from_align_justify` ctor here; could inline the struct init. - *$align_val = None; *$justify_val = None; } @@ -1160,7 +1130,7 @@ macro_rules! flush_shorthand_helper { ) => {{ if let Some((align, align_prefix)) = &mut *$align_val { if let Some(justify) = &mut *$justify_val { - // Zig: intersection = align_prefix & align_prefix (justify has no prefix) + // justify has no prefix, so the intersection is just align_prefix let intersection = *align_prefix; if intersection.contains(VendorPrefix::NONE) { *align_prefix = $this.flush_prefixes_helper($context, $align_feature); @@ -1174,8 +1144,6 @@ macro_rules! flush_shorthand_helper { align.clone(), justify.clone(), ))); - // TODO(port): see note above re: from_align_justify ctor. - *$align_val = None; *$justify_val = None; } @@ -1185,7 +1153,6 @@ macro_rules! flush_shorthand_helper { } // Tiny ctors used by flush_shorthand_helper! above. -// TODO(port): inline as struct literals once Property variant shapes are settled. impl PlaceContent { fn from_align_justify(align: AlignContent, justify: JustifyContent) -> Self { Self { align, justify } @@ -1332,8 +1299,8 @@ impl AlignHandler { Property::Unparsed(val) => { if is_align_property(&val.property_id) { self.flush(dest, context); - // PORT NOTE: Zig pushed `property.deepClone(context.arena)`. `Property` - // has no blanket `Clone` yet; reconstruct from the matched payload (same as flex.rs). + // `Property` has no blanket `Clone` yet; reconstruct from + // the matched payload (same as flex.rs). let bump = dest.bump(); dest.push(Property::Unparsed(val.deep_clone(bump))); } else { @@ -1471,7 +1438,6 @@ impl AlignHandler { } /// Gets prefixes for standard properties. - // PERF(port): was comptime monomorphization (`comptime feature: Feature`) — profile if hot. fn flush_prefixes_helper( &self, context: &PropertyHandlerContext<'_>, @@ -1509,5 +1475,3 @@ fn is_align_property(property_id: &PropertyId) -> bool { | PropertyId::Gap ) } - -// ported from: src/css/properties/align.zig diff --git a/src/css/properties/animation.rs b/src/css/properties/animation.rs index 6df914f0244..e3c5f3d627b 100644 --- a/src/css/properties/animation.rs +++ b/src/css/properties/animation.rs @@ -38,13 +38,9 @@ pub struct Animation { } impl Animation { - // TODO(port): PropertyFieldMap / VendorPrefixMap were comptime anonymous-struct - // metadata consumed by reflection in the shorthand codegen. Replace these with - // a derive macro (e.g. #[derive(Shorthand)]) that emits the field→PropertyIdTag - // and field→has-vendor-prefix tables. - // PORT NOTE: PropertyFieldMap dropped — `PropertyIdTag::Animation*` variants - // are not yet generated (animation longhands are unparsed-only for now), and - // the table was unread comptime metadata. Re-add when the variants land. + // PropertyFieldMap omitted: `PropertyIdTag::Animation*` variants are not yet + // generated (animation longhands are unparsed-only for now). Re-add the + // field→PropertyIdTag table once the variants land. pub const VENDOR_PREFIX_MAP: &'static [(&'static str, bool)] = &[ ("name", true), @@ -140,9 +136,8 @@ impl Animation { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // PORT NOTE: reshaped `inline .ident, .string => |name|` — Zig's inline - // switch monomorphized over two payload types; Rust extracts the inner - // string slice up front instead. + // Extract the inner string slice up front so both the ident and + // string variants share one code path. let name_str: Option<&[u8]> = match &self.name { AnimationName::None => None, AnimationName::Ident(ident) => Some(ident.v()), @@ -225,7 +220,7 @@ impl Animation { } /// A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property. -// PORT NOTE: no `#[derive(PartialEq, Eq, Hash)]` — `CustomIdent`/`CSSString` +// no `#[derive(PartialEq, Eq, Hash)]` — `CustomIdent`/`CSSString` // carry raw `*const [u8]` arena pointers; derived eq/hash would compare by // pointer. Hand-written `eql`/`hash` below compare by content. #[derive(Clone, Copy)] @@ -239,9 +234,9 @@ pub enum AnimationName { } impl AnimationName { - // PORT NOTE: hand-written (not `#[derive]`) because `CSSString` is a raw + // hand-written (not `#[derive]`) because `CSSString` is a raw // `*const [u8]` arena pointer — generics blanket impls cover `&[u8]` but - // not raw slices. Mirrors Zig `css.implementEql/Hash/DeepClone`. + // not raw slices. pub fn eql(&self, other: &Self) -> bool { match (self, other) { (AnimationName::None, AnimationName::None) => true, @@ -283,15 +278,14 @@ impl AnimationName { } pub fn parse(input: &mut Parser) -> css::Result { - // PORT NOTE: ported from src/css/properties/animation.zig — `none` keyword, - // then ``, else ``. + // `none` keyword, then ``, else ``. if input .try_parse(|i| i.expect_ident_matching(b"none")) .is_ok() { return Ok(AnimationName::None); } - // PORT NOTE: `expect_string` returns a slice borrowing `&mut self`, which + // `expect_string` returns a slice borrowing `&mut self`, which // `try_parse`'s `R` type param can't carry. Erase the lifetime through a // raw pointer inside the closure; the slice lives in the input arena and // outlives this parse (CSSString = &'static [u8]). @@ -315,7 +309,7 @@ impl AnimationName { // SAFETY: arena-owned slice valid for 'bump. let name: &[u8] = unsafe { crate::arena_str(s.v) }; if css_module_animation_enabled { - // PORT NOTE: reshaped for borrowck — capture arena/source_index + // reshaped for borrowck — capture arena/source_index // before borrowing dest.css_module mutably. let arena = dest.arena; let source_index = dest.loc.source_index; @@ -329,7 +323,7 @@ impl AnimationName { // SAFETY: arena-owned slice valid for 'bump. let name: &[u8] = unsafe { crate::arena_str(*s) }; if css_module_animation_enabled { - // PORT NOTE: reshaped for borrowck + // reshaped for borrowck let arena = dest.arena; let source_index = dest.loc.source_index; if let Some(css_module) = &mut dest.css_module { @@ -351,8 +345,6 @@ impl AnimationName { } /// A value for the [animation-iteration-count](https://drafts.csswg.org/css-animations/#animation-iteration-count) property. -// TODO(port): css.DeriveParse / css.DeriveToCss were comptime mixins generating -// parse()/to_css() from variant shape. Implement as #[derive(Parse, ToCss)]. #[derive(PartialEq)] pub enum AnimationIterationCount { /// The animation will repeat the specified number of times. @@ -479,15 +471,10 @@ pub enum AnimationTimeline { } impl AnimationTimeline { - // Port of `css.DeriveParse(@This()).parse` — void variants (`auto`, `none`) - // declared first → tried first via ident match; payloads follow in - // declaration order (`DashedIdent`, `ScrollTimeline`, `ViewTimeline`). - // Upstream `ScrollTimeline` / `ViewTimeline` carry no `parse`, so the Zig - // `DeriveParse` instantiation is dead code (`generic.parseFor` would - // `@compileError` if compiled — `Animation` is unreferenced in - // properties_generated.zig). We stop at `DashedIdent` here; if scroll()/ - // view() ever become live they need real function-syntax parsing, not the - // derived field-sequence fallback. + // Void variants (`auto`, `none`) are tried first via ident match; + // payloads follow in declaration order. We stop at `DashedIdent`; if + // scroll()/view() ever become live they need real function-syntax + // parsing, not a derived field-sequence fallback. pub fn parse(input: &mut Parser) -> css::Result { let state = input.state(); if let Ok(ident) = input.expect_ident() { @@ -504,16 +491,12 @@ impl AnimationTimeline { DashedIdent::parse(input).map(AnimationTimeline::DashedIdent) } - // Port of `css.DeriveToCss(@This()).toCss`. pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { match self { AnimationTimeline::Auto => dest.write_str(b"auto"), AnimationTimeline::None => dest.write_str(b"none"), AnimationTimeline::DashedIdent(d) => d.to_css(dest), - // Upstream Zig `ScrollTimeline` / `ViewTimeline` have no `toCss`; - // `DeriveToCss` would delegate to `generic.toCss` → `T.toCss` and - // `@compileError` if this arm were ever instantiated. Mirror that: - // these variants are currently unconstructible via `parse()`, and + // These variants are currently unconstructible via `parse()`, and // emitting bare space-separated fields here would be wrong CSS // (spec syntax is `scroll(...)` / `view(...)`). AnimationTimeline::Scroll(_) | AnimationTimeline::View(_) => { @@ -531,7 +514,7 @@ impl AnimationTimeline { } } -// PORT NOTE: hand-written `PartialEq` — `DashedIdent` carries a raw +// hand-written `PartialEq` — `DashedIdent` carries a raw // `*const [u8]` arena pointer; derive would compare by pointer, not content. impl PartialEq for AnimationTimeline { fn eq(&self, other: &Self) -> bool { @@ -659,5 +642,3 @@ pub enum TimelineRangeName { /// Represents the range during which the principal box crosses the start border edge. ExitCrossing, } - -// ported from: src/css/properties/animation.zig diff --git a/src/css/properties/background.rs b/src/css/properties/background.rs index de6369e50a1..c96ff47abdd 100644 --- a/src/css/properties/background.rs +++ b/src/css/properties/background.rs @@ -14,7 +14,7 @@ use bun_alloc::Arena as Bump; use bun_alloc::ArenaVecExt as _; /// A value for the [background](https://www.w3.org/TR/css-backgrounds-3/#background) shorthand property. -// PORT NOTE: Clone derive gated on `Image` gaining `Clone` upstream. +// Clone derive gated on `Image` gaining `Clone` upstream. #[cfg_attr(any(), derive(Clone))] pub struct Background { /// The background image. @@ -36,8 +36,6 @@ pub struct Background { } impl Background { - // Zig `deinit` was a no-op (all allocations in CSS parser are in arena) — Drop handles it. - pub(crate) fn parse(input: &mut Parser) -> css::Result { let mut color: Option = None; let mut position: Option = None; @@ -244,7 +242,6 @@ impl Background { #[inline] pub(crate) fn deep_clone(&self, arena: &Bump) -> Self { - // PORT NOTE: `css.implementDeepClone` reflection — expanded field-wise. // `Image` is the only non-`Clone` field; it provides its own `deep_clone`. Self { image: self.image.deep_clone(arena), @@ -598,18 +595,16 @@ pub struct BackgroundHandler { pub attachments: Option>, pub origins: Option>, pub clips: Option<(SmallList, VendorPrefix)>, - // TODO(perf): arena Vec — Zig is `ArrayListUnmanaged(Property)` fed `context.arena` - // (CSS arena). Should be `bun_alloc::ArenaVec<'bump, Property>`; threading `'bump` - // through BackgroundHandler would avoid the heap alloc. + // TODO(perf): should be `bun_alloc::ArenaVec<'bump, Property>`; threading + // `'bump` through BackgroundHandler would avoid the heap alloc. pub decls: Vec, pub flushed_properties: BackgroundProperty, pub has_any: bool, } -// PORT NOTE: the Zig uses comptime field-name strings + @field for `flushHelper` / -// `initSmallListHelper` / `push`. Rust cannot index struct fields by string at runtime; -// these helpers are expanded into small per-field macros below. A derive macro -// could replace them. +// Struct fields cannot be indexed by string at runtime; the `flushHelper` / +// `initSmallListHelper` / `push` helpers are expanded into small per-field +// macros below. A derive macro could replace them. macro_rules! init_small_list_helper { ($this:expr, $field:ident, $length:expr) => {{ @@ -699,8 +694,8 @@ impl BackgroundHandler { Property::BackgroundClip(x) => { let val: &SmallList = &x.0; let vendor_prefix: VendorPrefix = x.1; - // PORT NOTE: reshaped for borrowck — Zig held &mut into self.clips - // across self.flush(). Compute the predicate first, then dispatch. + // Compute the predicate first, then dispatch (avoids holding + // &mut self.clips across self.flush()). let needs_flush = if let Some((clips, vp)) = &self.clips { vendor_prefix != *vp && !SmallList::eql(val, clips) } else { @@ -737,7 +732,7 @@ impl BackgroundHandler { clips.append_assume_capacity(b.clip); } let mut clips_vp = VendorPrefix::NONE; - // PORT NOTE: reshaped for borrowck — drop borrow before calling flush(). + // Drop the `self.clips` borrow before calling flush(). let needs_flush = if let Some((existing_clips, existing_vp)) = &self.clips { clips_vp != *existing_vp && !SmallList::eql(&clips, existing_clips) } else { @@ -852,7 +847,6 @@ impl BackgroundHandler { let mut maybe_origins: Option> = self.origins.take(); let mut maybe_clips: Option<(SmallList, VendorPrefix)> = self.clips.take(); - // Zig had `defer { ... deinit }` here — Drop handles cleanup at scope exit. if let ( Some(color), @@ -905,8 +899,7 @@ impl BackgroundHandler { }; let mut backgrounds: SmallList = SmallList::init_capacity(len); - // PORT NOTE: reshaped for borrowck — Zig zipped 8 slices by value; here we - // index by `i` and clone each element to avoid 8 simultaneous borrows. + // Index by `i` and clone each element to avoid 8 simultaneous borrows. for i in 0..(len as usize) { backgrounds.append_assume_capacity(Background { color: if i == (len as usize) - 1 { @@ -930,17 +923,17 @@ impl BackgroundHandler { }, }); } - // Zig: defer { clearRetainingCapacity on each list } — values were moved - // by-value into `backgrounds` above, so clearing prevents double-free. - // In Rust we cloned, so the originals will Drop normally; no explicit clear - // needed for correctness. Leaving as-is. - // PERF(port): was arena bulk-free / move-then-clear — profile if hot. + // The elements were cloned into `backgrounds` above, so the + // originals Drop normally; no explicit clear needed. + // PERF: profile the clones if hot. if self.flushed_properties.is_empty() { let mut fallbacks = crate::small_list::get_fallbacks(&mut backgrounds, arena, &context.targets); - // PORT NOTE: Vec has no owning iterator; pop in reverse then - // re-reverse via a temp Vec to preserve order. + // Pop in reverse then re-reverse via a temp Vec to + // preserve order while consuming the list. `fallbacks` is + // a plain Vec, so a direct `for fb in fallbacks` would + // also work. let mut tmp: Vec> = Vec::with_capacity(fallbacks.len()); while let Some(fb) = fallbacks.pop() { @@ -1001,8 +994,9 @@ impl BackgroundHandler { if !self.flushed_properties.contains(BackgroundProperty::IMAGE) { let mut fallbacks = crate::small_list::get_fallbacks(&mut images, arena, &context.targets); - // PORT NOTE: Vec has no owning iterator; pop in reverse then - // re-reverse via a temp Vec to preserve order. + // Pop in reverse then re-reverse via a temp Vec to preserve + // order while consuming the list. `fallbacks` is a plain Vec, + // so a direct `for fb in fallbacks` would also work. let mut tmp: Vec> = Vec::with_capacity(fallbacks.len()); while let Some(fb) = fallbacks.pop() { tmp.push(fb); @@ -1042,7 +1036,6 @@ impl BackgroundHandler { y: y.clone(), }); } - // Zig: clearRetainingCapacity on xs/ys after moving values out — Drop handles it. push_property!( self, dest, @@ -1130,7 +1123,7 @@ impl BackgroundHandler { } fn reset(&mut self) { - // Zig deinit'd each field then set to null — Drop on assignment handles both. + // Drop on assignment frees each field's old value. self.color = None; self.images = None; self.x_positions = None; @@ -1156,8 +1149,7 @@ impl BackgroundHandler { let arena = dest.bump(); for decl in self.decls.drain(..) { - // PORT NOTE: Zig was `appendSlice` (bitwise copy of arena-backed - // values). `Property` is not `Clone` here, so move out via drain. + // `Property` is not `Clone` here, so move out via drain. let _ = arena; dest.push(decl); } @@ -1167,8 +1159,8 @@ impl BackgroundHandler { } } -// `Background` participates in `SmallList::get_fallbacks` via the duck-typed -// `ImageFallback` protocol (Zig dispatched on `@hasDecl(T, "getImage")`). +// `Background` participates in `SmallList::get_fallbacks` via the +// `ImageFallback` trait. impl crate::small_list::ImageFallback for Background { #[inline] fn get_image(&self) -> &Image { @@ -1204,5 +1196,3 @@ fn is_background_property(property_id: PropertyId) -> bool { | PropertyId::Background ) } - -// ported from: src/css/properties/background.zig diff --git a/src/css/properties/border.rs b/src/css/properties/border.rs index f6aa3be4fa1..91efc57db39 100644 --- a/src/css/properties/border.rs +++ b/src/css/properties/border.rs @@ -148,7 +148,6 @@ where { fn get_fallbacks(&mut self, arena: &Bump, targets: &Targets) -> SmallList { let fallbacks = self.color.get_fallbacks(arena, targets); - // PERF(port): was arena bulk-free (fallbacks.deinit) — profile if it shows up on a hot path let mut out: SmallList = SmallList::init_capacity(fallbacks.len()); for color in fallbacks.slice() { out.append_assume_capacity(Self { @@ -166,11 +165,9 @@ where } /// Deep-clone into a `GenericBorder` with a different const-generic - /// discriminant `Q`. The fields are identical regardless of `P`; this is - /// the Rust equivalent of Zig coercing one anonymous struct literal into - /// multiple `Border*` aliases. Needed when one logical value must be - /// emitted as two distinct physical `Property` variants (e.g. - /// inline-start → BorderLeft + BorderRight). + /// discriminant `Q`. The fields are identical regardless of `P`. Needed + /// when one logical value must be emitted as two distinct physical + /// `Property` variants (e.g. inline-start → BorderLeft + BorderRight). pub(crate) fn clone_as(&self, arena: &Bump) -> GenericBorder { let cloned = self.deep_clone(arena); GenericBorder { @@ -245,17 +242,16 @@ impl BorderSideWidth { } pub fn deep_clone(&self, _arena: &Bump) -> Self { - // PORT NOTE: css.implementDeepClone — Length is value-type; Clone suffices. + // css.implementDeepClone — Length is value-type; Clone suffices. self.clone() } } crate::css_eql_partialeq!(BorderSideWidth); // ────────────────────────────────────────────────────────────────────────── -// ImplFallbacks (Zig: `pub fn ImplFallbacks(comptime T: type) type`) +// impl_fallbacks // ────────────────────────────────────────────────────────────────────────── -// TODO(port): Zig used `inline for (std.meta.fields(T))` reflection. We expand -// the field list at macro invocation. All fields are `CssColor`. +// The field list is expanded at macro invocation. All fields are `CssColor`. // Hoisted here because `macro_rules!` is order-sensitive. macro_rules! impl_fallbacks { ($T:ty; $($field:ident),+) => { @@ -357,15 +353,13 @@ macro_rules! define_size_shorthand { } impl $name { - // TODO(port): bring this back - // (old using name space) css::DefineShorthand(@This(), PropertyIdTag::$shorthand_id); - + // The table is kept as data for a future shorthand derive to consume. pub const PROPERTY_FIELD_MAP: &[(&str, PropertyIdTag)] = &[ ("start", PropertyIdTag::$start_id), ("end", PropertyIdTag::$end_id), ]; } - // Zig `css.DefineSizeShorthand(@This(), V)` — parse/to_css via `Size2D`. + // parse/to_css via `Size2D`. // Shared impl macro lives in `properties/mod.rs`. impl_size_shorthand!($name, $inner, start, end); }; @@ -442,14 +436,12 @@ impl BorderShorthand { where S: for<'a> css::DeepClone<'a> + Into, { - // TODO(port): Zig accepted `anytype`; all callers pass GenericBorder. self.width = Some(border.width.deep_clone(arena)); self.style = Some(border.style.deep_clone(arena).into()); self.color = Some(border.color.deep_clone(arena)); } fn reset(&mut self, _arena: &Bump) { - // PERF(port): was arena bulk-free via bun.clear — profile if it shows up on a hot path self.width = None; self.style = None; self.color = None; @@ -460,8 +452,7 @@ impl BorderShorthand { } /// Generic over the `P` const param so the same `BorderShorthand` data - /// can populate any of `BorderTop`/`BorderLeft`/.../`Border` (Zig used a - /// single anonymous struct literal that coerced to each alias). + /// can populate any of `BorderTop`/`BorderLeft`/.../`Border`. fn to_border(&self, arena: &Bump) -> GenericBorder { GenericBorder { width: css::generic::deep_clone(&self.width, arena).unwrap(), @@ -472,7 +463,7 @@ impl BorderShorthand { } // ────────────────────────────────────────────────────────────────────────── -// BorderProperty bitflags (Zig: `packed struct(u32)` of all-bool fields) +// BorderProperty bitflags // ────────────────────────────────────────────────────────────────────────── bitflags::bitflags! { @@ -555,9 +546,9 @@ bitflags::bitflags! { // blocked_on: PropertyIdTag variant name verification (PascalCase mapping) impl BorderProperty { pub(crate) fn try_from_property_id(property_id: PropertyIdTag) -> Option { - // TODO(port): Zig used `inline for` over PropertyIdTag fields + @hasDecl. - // Expanded to an explicit match over every PropertyIdTag whose name - // starts with "border" and has a matching const above. + // An explicit match over every PropertyIdTag whose name starts + // with "border" and has a matching const above — keep in sync when new + // Border* PropertyIdTag variants are added. use PropertyIdTag as P; Some(match property_id { P::BorderTopColor => Self::BORDER_TOP_COLOR, @@ -635,10 +626,9 @@ mod border_handler_body { use super::*; use crate::generics::{CssEql, DeepClone}; // ────────────────────────────────────────────────────────────────────────── - // FlushContext + flush_category! (Zig: nested struct with inline fns and - // extensive comptime string-dispatch) + // FlushContext + flush_category! // ────────────────────────────────────────────────────────────────────────── - // PORT NOTE: hoisted above `impl BorderHandler` — macro_rules! is order- + // hoisted above `impl BorderHandler` — macro_rules! is order- // sensitive and the flush_category!() callsites in `flush()` need these. // Route the large `Property` enum construction through a non-inlined @@ -658,12 +648,12 @@ mod border_handler_body { } struct FlushContext<'a, 'bump, 'ctx> { - // PORT NOTE: Zig stored `self: *BorderHandler`; we only need flushed_properties - // here because the per-side BorderShorthand pointers are passed separately. + // Only flushed_properties is needed here because the per-side + // BorderShorthand pointers are passed separately. flushed_properties: &'a mut BorderProperty, dest: &'a mut DeclarationList<'bump>, ctx: &'a mut PropertyHandlerContext<'ctx>, - // PORT NOTE: `arena` field dropped from PropertyHandlerContext; the + // `arena` field dropped from PropertyHandlerContext; the // arena is recovered once via `dest.bump()` and threaded here. arena: &'bump Bump, logical_supported: bool, @@ -671,10 +661,10 @@ mod border_handler_body { } // `f.logicalProp(ltr, ltr_key, rtl, rtl_key, val)` — ltr_key/rtl_key were unused. - // PORT NOTE: `$val` is evaluated *before* reborrowing `$f` so callers may pass + // `$val` is evaluated *before* reborrowing `$f` so callers may pass // expressions that read `f.arena` without tripping E0502. macro_rules! fc_logical_prop { - // PORT NOTE: the `GenericBorder` shorthand pairs carry distinct const-generic + // the `GenericBorder` shorthand pairs carry distinct const-generic // discriminants per side (BorderLeft = P=3, BorderRight = P=1), so a single // `__val` cannot `deep_clone()` into both `Property` variants. Recast via // `clone_as::()` instead. Callers always pass a `to_border()` result here, @@ -715,8 +705,7 @@ mod border_handler_body { } // `f.push(p, val)` - // PORT NOTE: Zig's `@field(BorderProperty, p)` keyed both Property and BorderProperty - // off one kebab string. Here `$p` is the PascalCase Property/PropertyIdTag variant; + // `$p` is the PascalCase Property/PropertyIdTag variant; // the bitflags const is derived via try_from_property_id so a single ident suffices. macro_rules! fc_push { ($f:expr, $p:ident, $val:expr) => {{ @@ -747,8 +736,7 @@ mod border_handler_body { }}; } - // `f.prop(prop_name, val)` — comptime string dispatch over prop_name. - // In Rust we dispatch on the Property variant ident. + // Dispatch on the Property variant ident. macro_rules! fc_prop { // border-inline-start* ($f:expr, BorderInlineStart, $val:expr) => {{ @@ -915,9 +903,8 @@ mod border_handler_body { }; } - // `flushCategory(...)` — was a fn with comptime string params + nested `State` - // struct of inline fns. In Rust we expand it as a macro so the `comptime` prop - // names remain compile-time idents and the nested closures become local macros. + // `flush_category!` is a macro so the prop names remain + // compile-time idents and the nested closures become local macros. macro_rules! flush_category { ( $f:expr, @@ -1235,11 +1222,11 @@ mod border_handler_body { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) -> bool { - // PORT NOTE: `arena` field dropped from PropertyHandlerContext; the + // `arena` field dropped from PropertyHandlerContext; the // arena is recovered via `dest.bump()` (DeclarationList = bumpalo::Vec). let arena = dest.bump(); - // Helper macros — Zig used local comptime closures with @field string access. + // Helper macros. macro_rules! flush_helper { ($key:ident, $prop:ident, $val:expr, $category:expr) => {{ @@ -1522,9 +1509,8 @@ mod border_handler_body { let logical_shorthand_supported = !context.should_compile_logical(Feature::LogicalBorderShorthand); - // PORT NOTE: reshaped for borrowck — Zig stored `self: *BorderHandler` in - // FlushContext and accessed self.border_* through it. We instead take - // independent &mut borrows of each shorthand, plus &mut self.flushed_properties. + // Take independent &mut borrows of each shorthand, plus + // &mut self.flushed_properties, instead of one &mut self. let arena = dest.bump(); let mut flctx = FlushContext { flushed_properties: &mut self.flushed_properties, @@ -1625,16 +1611,15 @@ mod border_handler_body { } macro_rules! prop { - ($id:ident) => {{ - let _ = &dest; // autofix (matches Zig: `_ = d;`) - let mut upppppppppp = - unparsed.with_property_id(arena, PropertyId::$id); - context.add_unparsed_fallbacks(arena, &mut upppppppppp); - self.flushed_properties - .insert(BorderProperty::try_from_property_id(PropertyIdTag::$id).unwrap()); - // TODO(port): Zig did NOT push to dest here (likely a bug upstream) — preserved. - }}; - } + ($id:ident) => {{ + let _ = &dest; // autofix + let mut upppppppppp = unparsed.with_property_id(arena, PropertyId::$id); + context.add_unparsed_fallbacks(arena, &mut upppppppppp); + self.flushed_properties + .insert(BorderProperty::try_from_property_id(PropertyIdTag::$id).unwrap()); + // Intentionally not pushed to dest (preserves existing behavior; likely a bug). + }}; + } macro_rules! logical_prop { ($ltr:ident, $rtl:ident) => {{ @@ -1740,6 +1725,4 @@ mod border_handler_body { | P::Border ) } - - // ported from: src/css/properties/border.zig } // mod border_handler_body diff --git a/src/css/properties/border_image.rs b/src/css/properties/border_image.rs index d79fef7960f..a1bfe1d1a41 100644 --- a/src/css/properties/border_image.rs +++ b/src/css/properties/border_image.rs @@ -34,9 +34,7 @@ pub struct BorderImage { } impl BorderImage { - // TODO(port): PropertyFieldMap / VendorPrefixMap were comptime anonymous-struct - // tables consumed via @field reflection by the shorthand codegen. Replace with - // a trait impl (e.g. `impl ShorthandProperty for BorderImage`). + // Recorded here for a future shorthand trait/derive to consume. // PropertyFieldMap: // source -> PropertyIdTag::BorderImageSource // slice -> PropertyIdTag::BorderImageSlice @@ -46,12 +44,9 @@ impl BorderImage { // VendorPrefixMap: all fields = true pub(crate) fn parse(input: &mut css::Parser) -> Result { - // PORT NOTE: Zig passed `{}` ctx + a no-op callback struct; collapsed to a closure. Self::parse_with_callback(input, |_: &mut css::Parser| false) } - // PORT NOTE: Zig signature was (input, ctx: anytype, comptime callback: anytype) - // where callback(ctx, input) -> bool. Collapsed ctx into the closure capture. pub(crate) fn parse_with_callback( input: &mut css::Parser, mut callback: impl FnMut(&mut css::Parser) -> bool, @@ -200,22 +195,19 @@ impl BorderImage { targets: &css::targets::Targets, ) -> SmallList { let fallbacks = self.source.get_fallbacks(arena, targets); - // PORT NOTE: `defer fallbacks.deinit(arena)` dropped — SmallList drops at scope exit. + // `defer fallbacks.deinit(arena)` dropped — SmallList drops at scope exit. let mut res = SmallList::::init_capacity(fallbacks.len()); - for fallback in fallbacks.slice() { - // TODO(port): Zig moved `fallback` by value; SmallList lacks - // by-value drain in Rust port — deep_clone the source image - // until SmallList grows IntoIterator. + for fallback in fallbacks { + // `fallback` is moved into the cloned shorthand. let mut clone = self.deep_clone(arena); - clone.source = fallback.deep_clone(arena); + clone.source = fallback; res.append(clone); } res } pub(crate) fn deep_clone(&self, arena: &Arena) -> Self { - // PORT NOTE: Zig css.implementDeepClone iterated @typeInfo fields. Expanded - // explicitly here — keep in sync with the BorderImage field list. + // Keep in sync with the BorderImage field list. BorderImage { source: self.source.deep_clone(arena), slice: self.slice.deep_clone(arena), @@ -306,9 +298,8 @@ pub enum BorderImageSideWidth { } impl BorderImageSideWidth { - // PORT NOTE: `css.DeriveParse(@This()).parse` / `css.DeriveToCss(@This()).toCss` - // were comptime-reflected derives. Hand-expanded — declaration order matches - // Zig (Number → LengthPercentage → keyword `auto`). + // Hand-expanded — tried in declaration order + // (Number → LengthPercentage → keyword `auto`). pub(crate) fn parse(input: &mut css::Parser) -> Result { if let Ok(n) = input.try_parse(CSSNumberFns::parse) { return Ok(BorderImageSideWidth::Number(n)); @@ -456,11 +447,7 @@ impl BorderImageProperty { pub(crate) const BORDER_IMAGE: BorderImageProperty = BorderImageProperty::all(); - // PORT NOTE: bitflags provides `is_empty()` already; Zig `isEmpty` maps to it. - pub(crate) fn try_from_property_id(property_id: PropertyIdTag) -> Option { - // PORT NOTE: Zig used `inline for` over struct fields + @field to match - // "border-image-" ++ field.name. Unrolled explicitly here. match property_id { PropertyIdTag::BorderImageSource => Some(BorderImageProperty::SOURCE), PropertyIdTag::BorderImageSlice => Some(BorderImageProperty::SLICE), @@ -494,8 +481,7 @@ impl BorderImageHandler { ) -> bool { let arena = dest.bump(); - // PORT NOTE: Zig defined `flushHelper`/`propertyHelper` as local struct fns - // using @field for comptime field access. Ported as macro_rules! to keep the + // `flushHelper`/`propertyHelper` are macro_rules! to keep the // per-field name dispatch without reflection. macro_rules! flush_helper { ($self:expr, $d:expr, $ctx:expr, $name:ident, $val:expr) => { @@ -570,7 +556,6 @@ impl BorderImageHandler { .unwrap(), ); dest.push(Property::Unparsed(unparsed_clone)); - // PERF(port): was bun.handleOom(dest.append(arena, ...)) } else { return false; } @@ -645,7 +630,7 @@ impl BorderImageHandler { .prefixes(self.vendor_prefix, css::prefixes::Feature::BorderImage); if self.flushed_properties.is_empty() { let fallbacks = border_image.get_fallbacks(arena, &context.targets); - for fallback in fallbacks.slice() { + for fallback in fallbacks { // Match prefix of fallback. e.g. -webkit-linear-gradient // can only be used in -webkit-border-image, not -moz-border-image. // However, if border-image is unprefixed, gradients can still be. @@ -653,9 +638,7 @@ impl BorderImageHandler { if p.is_empty() { p = prefix; } - // TODO(port): Zig moved `fallback` by value; SmallList has no - // by-value drain yet — deep_clone until IntoIterator lands. - dest.push(Property::BorderImage((fallback.deep_clone(arena), p))); + dest.push(Property::BorderImage((fallback, p))); } } } @@ -669,22 +652,19 @@ impl BorderImageHandler { self.flushed_properties .insert(BorderImageProperty::BORDER_IMAGE); } - (mut source, slice, width, outset, repeat) => { - if let Some(mut_source) = &mut source { + (source, slice, width, outset, repeat) => { + if let Some(mut mut_source) = source { if !self .flushed_properties .contains(BorderImageProperty::BORDER_IMAGE_SOURCE) { let img_fallbacks = mut_source.get_fallbacks(arena, &context.targets); - for fallback in img_fallbacks.slice() { - // TODO(port): same by-value move note as above. - dest.push(Property::BorderImageSource(fallback.deep_clone(arena))); + for fallback in img_fallbacks { + dest.push(Property::BorderImageSource(fallback)); } } - dest.push(Property::BorderImageSource(mut_source.deep_clone(arena))); - // TODO(port): Zig pushed `mut_source.*` by value (move). Cloning here to - // avoid partial-move out of `source: Option`. + dest.push(Property::BorderImageSource(mut_source)); self.flushed_properties .insert(BorderImageProperty::BORDER_IMAGE_SOURCE); } @@ -732,5 +712,3 @@ pub(crate) fn is_border_image_property(property_id: PropertyIdTag) -> bool { } crate::css_eql_partialeq!(BorderImageSideWidth); - -// ported from: src/css/properties/border_image.zig diff --git a/src/css/properties/border_radius.rs b/src/css/properties/border_radius.rs index bd664d747b8..ad6187f4e0c 100644 --- a/src/css/properties/border_radius.rs +++ b/src/css/properties/border_radius.rs @@ -12,8 +12,8 @@ use crate::css_values::size::Size2D; use bun_alloc::ArenaVecExt as _; /// A value for the [border-radius](https://www.w3.org/TR/css-backgrounds-3/#border-radius) property. -// PORT NOTE: `Size2D` carries no `Clone`/`PartialEq` derives (it exposes -// inherent `deep_clone`/`eql` instead, matching the Zig protocol surface), so +// `Size2D` carries no `Clone`/`PartialEq` derives (it exposes +// inherent `deep_clone`/`eql` instead), so // `BorderRadius` can't `#[derive]` them either. The handler below uses // `Size2D::deep_clone`/`Size2D::eql` directly. pub struct BorderRadius { @@ -44,11 +44,7 @@ impl BorderRadius { && Size2D::eql(&self.bottom_left, &other.bottom_left) } - // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"border-radius", PropertyFieldMap); - - // TODO(port): PropertyFieldMap / VendorPrefixMap were Zig anonymous-struct decl literals - // consumed by comptime DefineShorthand reflection. Represent as assoc consts for now; - // wire these into the shorthand trait/derive once it exists. + // Kept as assoc consts for a future shorthand trait/derive to consume. pub const PROPERTY_FIELD_MAP: [(&'static str, &'static str); 4] = [ ("top_left", "border-top-left-radius"), ("top_right", "border-top-right-radius"), @@ -69,7 +65,7 @@ impl BorderRadius { // errdefer-style cleanup of `widths` is implicit via Drop on the `?` path. Rect::::parse_with(input, LengthPercentage::parse)? } else { - // PORT NOTE: Zig `widths.deepClone(arena)` — `LengthPercentage` is + // `LengthPercentage` is // `Clone`-via-derive (no arena indirection), so per-field `.clone()` is exact. Rect { top: widths.top.clone(), @@ -100,8 +96,7 @@ impl BorderRadius { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // PORT NOTE: Zig built `Rect(*const LengthPercentage)` and reused - // `Rect.toCss`. `Rect::<&T>::to_css` would need `&T: ToCss + PartialEq`; + // `Rect::<&T>::to_css` would need `&T: ToCss + PartialEq`; // inline the 4-side serialization to avoid that bound (logic is identical // to `values::rect::Rect::to_css`). #[inline] @@ -154,8 +149,8 @@ impl BorderRadius { Ok(()) } - // deepClone → #[derive(Clone)] (was css.implementDeepClone comptime field iteration) - // eql → #[derive(PartialEq)] (was css.implementEql comptime field iteration) + // deep_clone comes from #[derive(Clone)]. + // eql comes from #[derive(PartialEq)]. } #[derive(Default)] @@ -168,17 +163,16 @@ pub struct BorderRadiusHandler { pub start_end: Option, pub end_end: Option, pub end_start: Option, - // Zig: `= .physical`. derive(Default) is sound here because + // derive(Default) is sound here because // PropertyCategory::default() == Physical (see src/css/logical.rs). pub category: PropertyCategory, pub has_any: bool, } -// The Zig helpers take `comptime prop: []const u8` and use `@field` / `@unionInit` for -// token-level field/variant access. Rust has no field-by-name reflection, so these are +// There is no field-by-name reflection, so these helpers are // macro_rules! that paste the field ident and the corresponding Property variant ident. -// PORT NOTE: `Size2D::is_compatible` is bounded on `T: values::protocol::IsCompatible`, +// `Size2D::is_compatible` is bounded on `T: values::protocol::IsCompatible`, // which `LengthPercentage` (= `DimensionPercentage`) does not yet impl. // Hand-roll the per-component check via `LengthPercentage::is_compatible` (inherent // method) until that protocol impl lands. @@ -235,7 +229,7 @@ macro_rules! logical_property_helper { $self.flush($d, $ctx); } - // PORT NOTE: Zig stored `property.deepClone(arena)`. `Property` itself + // `Property` itself // has no blanket `Clone`; callers pass an already-deep_clone'd `Property`. $self.$prop = Some($val); $self.category = PropertyCategory::Logical; @@ -300,7 +294,7 @@ impl BorderRadiusHandler { context: &mut PropertyHandlerContext, ) -> bool { let bump = dest.bump(); - // PORT NOTE: `Property::deep_clone` is still gated; reconstruct the + // `Property::deep_clone` is still gated; reconstruct the // matched variant directly (Size2D deep-clones via inherent method). match property { Property::BorderTopLeftRadius((val, vp)) => { @@ -563,5 +557,3 @@ pub fn is_logical_border_radius_property(property_id: PropertyIdTag) -> bool { | PropertyIdTag::BorderEndStartRadius ) } - -// ported from: src/css/properties/border_radius.zig diff --git a/src/css/properties/box_shadow.rs b/src/css/properties/box_shadow.rs index 84ba72d9f34..9a4a221ad33 100644 --- a/src/css/properties/box_shadow.rs +++ b/src/css/properties/box_shadow.rs @@ -28,8 +28,8 @@ pub struct BoxShadow { pub inset: bool, } -// PORT NOTE: `SmallList::{deep_clone,eql,is_compatible}` are bounded on the -// `generics::{DeepClone,CssEql,IsCompatible}` traits. Wire BoxShadow into all +// `SmallList::{deep_clone,eql,is_compatible}` are bounded on the +// `generics::{DeepClone,CssEql,IsCompatible}` traits. BoxShadow implements all // three so the handler can use `SmallList` directly without // hand-rolling per-field loops. impl<'bump> css::generic::DeepClone<'bump> for BoxShadow { @@ -127,7 +127,6 @@ impl BoxShadow { dest.write_char(b' ')?; self.y_offset.to_css(dest)?; - // PORT NOTE: Zig `Length.eql` → Rust `PartialEq` (see values/length.rs). if self.blur != Length::zero() || self.spread != Length::zero() { dest.write_char(b' ')?; self.blur.to_css(dest)?; @@ -146,8 +145,7 @@ impl BoxShadow { } pub(crate) fn deep_clone(&self, arena: &Arena) -> Self { - // PORT NOTE: Zig css.implementDeepClone iterated @typeInfo fields. Expanded - // explicitly here — keep in sync with the BoxShadow field list. `Length` + // Expanded field-wise — keep in sync with the BoxShadow field list. `Length` // has no `DeepClone` trait impl yet but is `Clone` (Box deep-clones). BoxShadow { color: self.color.deep_clone(arena), @@ -160,8 +158,7 @@ impl BoxShadow { } pub(crate) fn eql(&self, rhs: &Self) -> bool { - // PORT NOTE: Zig css.implementEql iterated @typeInfo fields. Expanded - // explicitly. `Length` Zig `eql` → Rust `PartialEq` (values/length.rs). + // Expanded field-wise — keep in sync with the BoxShadow field list. self.color.eql(&rhs.color) && self.x_offset == rhs.x_offset && self.y_offset == rhs.y_offset @@ -204,9 +201,9 @@ impl BoxShadowHandler { self.flush(dest, context); } - // PORT NOTE: reshaped for borrowck — Zig held simultaneous &mut into - // self.box_shadows across self.flush(). Compute the predicate first, - // then either flush+replace or update in place. + // Compute the predicate first, then either flush+replace or + // update in place (avoids holding &mut self.box_shadows + // across self.flush()). let needs_flush = if let Some(bxs) = &self.box_shadows { !SmallList::eql(&bxs.0, box_shadows) && !bxs.1.contains(prefix) } else { @@ -273,10 +270,6 @@ impl BoxShadowHandler { fallbacks.insert(shadow.color.get_necessary_fallbacks(&context.targets)); } - // PORT NOTE: Zig used `initCapacity(len)` + `setLen(len)` + per-index field - // writes via `inline for std.meta.fields(BoxShadow)` skipping `color`. That - // pattern would observe partially-uninit `BoxShadow` values in Rust, so we - // build each fully-formed `BoxShadow` and `append`. Behavior is identical. macro_rules! build_color_fallback { ($conv:ident) => {{ let mut out: SmallList = @@ -327,5 +320,3 @@ impl BoxShadowHandler { self.flushed = true; } } - -// ported from: src/css/properties/box_shadow.zig diff --git a/src/css/properties/css_modules.rs b/src/css/properties/css_modules.rs index c3f02e4a881..00a451be892 100644 --- a/src/css/properties/css_modules.rs +++ b/src/css/properties/css_modules.rs @@ -79,7 +79,6 @@ impl Composes { } pub fn deep_clone(&self, bump: &Arena) -> Self { - // PORT NOTE: Zig `css.implementDeepClone` is comptime field reflection. // `CustomIdent` is `Copy` (arena-ptr payload), so an element-wise copy // into a fresh `SmallList` is the deep clone. let mut names = CustomIdentList::default(); @@ -95,7 +94,6 @@ impl Composes { } pub fn eql(lhs: &Self, rhs: &Self) -> bool { - // PORT NOTE: Zig `css.implementEql` is comptime field reflection. if lhs.names.len() != rhs.names.len() { return false; } @@ -139,7 +137,6 @@ impl crate::generics::CssEql for Specifier { impl Specifier { pub fn eql(lhs: Self, rhs: Self) -> bool { - // PORT NOTE: Zig `css.implementEql` (variant-wise reflection) → hand-match. match (lhs, rhs) { (Specifier::Global, Specifier::Global) => true, (Specifier::ImportRecordIndex(a), Specifier::ImportRecordIndex(b)) => a == b, @@ -179,12 +176,11 @@ impl Specifier { } pub fn deep_clone(self, _bump: &Arena) -> Self { - // PORT NOTE: Zig `css.implementDeepClone` — variants are `Copy`. + // Variants are `Copy`; the deep clone is the value itself. self } pub fn hash(self, hasher: &mut Wyhash) { - // PORT NOTE: Zig `css.implementHash` (variant-wise reflection) → hand-match. match self { Specifier::Global => hasher.update(&0u32.to_ne_bytes()), Specifier::ImportRecordIndex(i) => { @@ -194,5 +190,3 @@ impl Specifier { } } } - -// ported from: src/css/properties/css_modules.zig diff --git a/src/css/properties/custom.rs b/src/css/properties/custom.rs index c75e77d7b6d..0c25d607e86 100644 --- a/src/css/properties/custom.rs +++ b/src/css/properties/custom.rs @@ -1,6 +1,4 @@ //! CSS custom properties / `var()` / `env()` / unparsed token lists. -//! -//! Ported from `src/css/properties/custom.zig`. // // `TokenList::{parse, parse_into, parse_with_options, to_css, to_css_raw}`, // `UnresolvedColor::{parse, to_css}`, `Variable::{parse, to_css}`, @@ -79,8 +77,8 @@ mod ext { /// Vec`, which this round adds in css_parser.rs). pub(super) fn url_to_css(this: &Url, dest: &mut Printer) -> PrintResult<()> { let dep: Option = if dest.dependencies.is_some() { - // PORT NOTE: reshaped for borrowck — `get_import_records` borrows - // &mut *dest, so capture arena/filename first. + // `get_import_records` borrows &mut *dest, so capture + // arena/filename first. let arena = dest.arena; // SAFETY: filename borrows the printer arena/options which outlive `dest`. let filename: &[u8] = unsafe { &*std::ptr::from_ref::<[u8]>(dest.filename()) }; @@ -102,7 +100,6 @@ mod ext { dest.write_char(b')')?; if let Some(dependencies) = &mut dest.dependencies { - // PORT NOTE: bun.handleOom dropped — Vec::push aborts on OOM via global arena dependencies.push(crate::Dependency::Url(d)); } @@ -111,8 +108,8 @@ mod ext { let import_record = dest.import_record(this.import_record_idx)?; let is_internal = import_record.tag.is_internal(); - // PORT NOTE: reshaped for borrowck — `get_import_record_url` reborrows - // &mut *dest, so capture `is_internal` first. + // `get_import_record_url` reborrows &mut *dest, so capture + // `is_internal` first. let url: &'static [u8] = { let u = dest.get_import_record_url(this.import_record_idx)?; // SAFETY: import-record paths are arena/source-owned and outlive `dest`. @@ -120,7 +117,6 @@ mod ext { }; if dest.minify && !is_internal { - // PERF(port): was std.Io.Writer.Allocating with dest.arena — using Vec; profile if hot. let mut buf: Vec = Vec::new(); // PERF(alloc) we could use stack fallback here? let _ = Token::UnquotedUrl(url).to_css_generic(&mut buf); @@ -160,7 +156,7 @@ mod ext { /// Forwarder to `DashedIdentReference::to_css` (now un-gated in /// `values/ident.rs`). `CssModule::reference_dashed` is real; the - /// CSS-Modules `dashed_idents` remapping path (ident.zig:44-52) is wired. + /// CSS-Modules `dashed_idents` remapping path is wired. #[inline] pub(super) fn dashed_ident_ref_to_css( this: &DashedIdentReference, @@ -274,7 +270,6 @@ impl CssEql for Token { impl CssHash for Token { fn hash(&self, hasher: &mut Wyhash) { use Token::*; - // Zig `implementHash`: tag prefix + payload bytes. // `Token::kind() as u32` gives a stable per-variant discriminant. hasher.update(&(self.kind() as u32).to_ne_bytes()); match self { @@ -303,16 +298,14 @@ impl<'bump> DeepClone<'bump> for Token { #[inline] fn deep_clone(&self, _bump: &'bump Arena) -> Self { // All `&'static [u8]` payloads borrow the parser source/arena (`'static` - // is a placeholder) — identity copy is correct (matches generics.zig - // "const strings" fast-path). `Num`/`Dimension` are POD. + // is a placeholder) — identity copy is correct. `Num`/`Dimension` are POD. self.clone() } } -// PERF(port): the token vecs here are plain global-alloc `Vec`; -// the Zig original was arena-backed. Thread `&'bump Bump` if profiling shows it. +// PERF: the token vecs here are plain global-alloc `Vec`; +// Thread `&'bump Bump` if profiling shows it. -/// Zig: `pub fn Result(comptime T: type) type` → `Maybe(T, ParseError(ParserError))`. pub use css_parser::CssResult as Result; /// PERF: nullable optimization @@ -482,8 +475,7 @@ impl TokenList { if tokens[tokens.len() - 1].is_whitespace() { end -= 1; } - // PORT NOTE: Zig does `insertSlice(0, slice)` (shallow memcpy) then `tokens.deinit()` - // (frees only the backing array). `drain` moves the elements out without deep-cloning. + // `drain` moves the elements out without deep-cloning. let newlist: Vec = tokens.drain(start..end).collect(); return Ok(TokenList { v: newlist }); } @@ -567,7 +559,7 @@ impl TokenList { let Ok(tok) = input.next_including_whitespace() else { break; }; - // PORT NOTE: reshaped for borrowck — clone the token so we can call &mut methods on `input` below. + // Clone the token so we can call &mut methods on `input` below. let tok = tok.clone(); match &tok { Token::Whitespace(_) | Token::Comment(_) => { @@ -752,7 +744,6 @@ impl TokenList { let mut res = css::SmallList::::default(); if fallbacks.contains(ColorFallbackKind::P3) { - // PERF(port): was assume_capacity res.append(( ColorFallbackKind::P3.supports_condition(), self.get_fallback(bump, ColorFallbackKind::P3), @@ -760,7 +751,6 @@ impl TokenList { } if fallbacks.contains(ColorFallbackKind::LAB) { - // PERF(port): was assume_capacity res.append(( ColorFallbackKind::LAB.supports_condition(), self.get_fallback(bump, ColorFallbackKind::LAB), @@ -985,7 +975,6 @@ impl UnresolvedColor { }) }), b"light-dark" => return input.parse_nested_block(|input2| { - // errdefer doesn't fire on `return .{ .err = ... }` in Zig — but in Rust, // `?` drops `light` automatically on the error path. let light = input2.parse_until_before(Delimiters::COMMA, |i| { TokenListFns::parse(i, options, depth + 1) @@ -1016,9 +1005,8 @@ impl UnresolvedColor { // `ComponentParser::parse_relative` is generic over `C: LightDarkOwned` so the // `from light-dark(...)` relative-color path can rebuild a `light-dark()` of -// whatever output type the caller is producing. Zig duck-types this via -// `lightDarkOwned` on both `CssColor` and `UnresolvedColor`; in Rust the trait -// lives in `values::color` and we wire `UnresolvedColor` into it here. +// whatever output type the caller is producing. The trait lives in +// `values::color` and we wire `UnresolvedColor` into it here. impl css_values::color::LightDarkOwned for UnresolvedColor { #[inline] fn light_dark_owned(light: Self, dark: Self) -> Self { @@ -1191,9 +1179,7 @@ impl EnvironmentVariableName { } /// A UA-defined environment variable name. -// PORT NOTE: Zig `css.DefineEnumProperty(@This())` provides eql/hash/parse/ -// to_css/deep_clone via comptime reflection over @tagName. Replaced by an -// `EnumProperty` impl below (kebab-case match) — same protocol surface. +// The `EnumProperty` impl below provides parse/to_css via a kebab-case match. #[derive(Clone, Copy, PartialEq, Eq, strum::IntoStaticStr, CssHash)] pub enum UAEnvironmentVariable { /// The safe area inset from the top of the viewport. @@ -1336,8 +1322,7 @@ impl TokenOrValue { // `TokenList` payload, and `media_query::MediaFeatureValue` derives // `Debug + Clone` over an `EnvironmentVariable` payload. The leaf value types // (`Url`, `CustomIdent`, …) don't all `#[derive(Clone)]` yet, so hand-roll -// the structural clone here. PORT NOTE: Zig had no `Clone` distinction — -// shallow struct copy was implicit; arena-slice payloads (`*const [u8]`) are +// the structural clone here. Arena-slice payloads (`*const [u8]`) are // `Copy`, and the only owning fields are `Vec` / `Vec`. impl Clone for TokenList { @@ -1538,7 +1523,7 @@ pub enum CustomPropertyName { Unknown(Ident), } -// PORT NOTE: `DashedIdent`/`Ident` carry `*const [u8]` arena slices and +// `DashedIdent`/`Ident` carry `*const [u8]` arena slices and // intentionally don't derive `PartialEq` (pointer-eq would be wrong). // `PropertyId` derives `PartialEq`, so compare the underlying bytes here. impl PartialEq for CustomPropertyName { @@ -1552,7 +1537,7 @@ impl CustomPropertyName { pub fn to_css(&self, dest: &mut Printer) -> PrintResult<()> { match self { CustomPropertyName::Custom(custom) => { - // Spec custom.zig:1496-1501 → DashedIdent.toCss → dest.writeDashedIdent(ident, true), + // DashedIdent.toCss → dest.writeDashedIdent(ident, true), // which applies CSS-Modules dashed-ident renaming. dest.write_dashed_ident(custom, true) } @@ -1627,5 +1612,3 @@ pub(crate) fn try_parse_color_token( None } - -// ported from: src/css/properties/custom.zig diff --git a/src/css/properties/display.rs b/src/css/properties/display.rs index 676843912d5..5b80df0a347 100644 --- a/src/css/properties/display.rs +++ b/src/css/properties/display.rs @@ -11,8 +11,8 @@ pub enum Display { Pair(DisplayPair), } -// PORT NOTE: Zig `DeriveParse`/`DeriveToCss` for a 2-payload union(enum) tries each -// payload's `parse` in declaration order; `toCss` dispatches to the active payload. +// Tries each payload's `parse` in declaration order; `to_css` dispatches to +// the active payload. impl Display { pub(crate) fn parse(input: &mut Parser) -> css::Result { if let Ok(kw) = input.try_parse(DisplayKeyword::parse) { @@ -130,7 +130,6 @@ impl DisplayPair { let location = input.current_source_location(); let ident = input.expect_ident_cloned()?; - // PORT NOTE: Zig used `bun.ComptimeStringMap(..).getASCIIICaseInsensitive`. // 8 keys → if-chain over `eql_case_insensitive_ascii::` (phf values // would have to be const-eval, and `VendorPrefix` bitflags are not). use bun_core::eql_case_insensitive_ascii as eq; @@ -161,7 +160,6 @@ impl DisplayPair { } pub(crate) fn to_css(self, dest: &mut Printer) -> Result<(), PrintErr> { - // PORT NOTE: reshaped Zig if-else chain into match for tagged-union payload extraction. match (self.outside, &self.inside, self.is_list_item) { (DisplayOutside::Inline, DisplayInside::FlowRoot, false) => { return dest.write_str("inline-block"); @@ -243,7 +241,6 @@ impl DisplayInside { let location = input.current_source_location(); let ident = input.expect_ident_cloned()?; - // PORT NOTE: Zig used `bun.ComptimeStringMap(..).getASCIIICaseInsensitive`. // 10 keys → if-chain over `eql_case_insensitive_ascii::`. use bun_core::eql_case_insensitive_ascii as eq; Ok(if eq(ident, b"flow", true) { @@ -293,5 +290,3 @@ impl DisplayInside { } } } - -// ported from: src/css/properties/display.zig diff --git a/src/css/properties/effects.rs b/src/css/properties/effects.rs index c3d71c41cec..8b137891791 100644 --- a/src/css/properties/effects.rs +++ b/src/css/properties/effects.rs @@ -1 +1 @@ -// ported from: src/css/properties/effects.zig + diff --git a/src/css/properties/flex.rs b/src/css/properties/flex.rs index 2470c4ab847..78d42b455e0 100644 --- a/src/css/properties/flex.rs +++ b/src/css/properties/flex.rs @@ -7,9 +7,8 @@ use css::css_properties::align::{AlignContent, AlignItems, AlignSelf, JustifyCon use css::css_values::length::{LengthPercentage, LengthPercentageOrAuto}; use css::css_values::number::{CSSInteger, CSSNumber, CSSNumberFns}; use css::prefixes::Feature as PrefixFeature; -use css::{PrintErr, Printer, VendorPrefix}; -// Zig: `const isFlex2009 = css.prefixes.Feature.isFlex2009;` use css::prefixes::is_flex_2009; +use css::{PrintErr, Printer, VendorPrefix}; /// A value for the [flex-direction](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#propdef-flex-direction) property. /// A value for the [flex-direction](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#propdef-flex-direction) property. @@ -59,9 +58,7 @@ pub struct FlexFlow { pub wrap: FlexWrap, } -// (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"flex-flow", PropertyFieldMap); -// TODO(port): PropertyFieldMap / VendorPrefixMap are comptime shorthand metadata consumed by -// css::DefineShorthand reflection. Port as part of the shorthand derive. +// Shorthand metadata recorded here for a future shorthand derive: // PropertyFieldMap = { direction: PropertyIdTag::FlexDirection, wrap: PropertyIdTag::FlexWrap } // VendorPrefixMap = { direction: true, wrap: true } @@ -122,8 +119,7 @@ pub struct Flex { pub basis: LengthPercentageOrAuto, } -// (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.flex, PropertyFieldMap); -// TODO(port): PropertyFieldMap / VendorPrefixMap shorthand metadata — see FlexFlow note. +// Shorthand metadata recorded here for a future shorthand derive (see FlexFlow note): // PropertyFieldMap = { grow: PropertyIdTag::FlexGrow, shrink: PropertyIdTag::FlexShrink, basis: PropertyIdTag::FlexBasis } // VendorPrefixMap = { grow: true, shrink: true, basis: true } @@ -539,8 +535,6 @@ impl FlexHandler { dest: &mut css::DeclarationList, context: &mut css::PropertyHandlerContext, ) -> bool { - // TODO(port): Zig used local closures with `@field(self, prop)` comptime reflection. - // Ported as macro_rules! token-pasting on field idents. macro_rules! maybe_flush { ($prop:ident, $val:expr, $vp:expr) => {{ // If two vendor prefixes for the same property have different @@ -558,9 +552,8 @@ impl FlexHandler { maybe_flush!($prop, $val, $vp); // Otherwise, update the value and add the prefix - // PORT NOTE: Zig threaded `context.arena` into `css.generic.deepClone`; - // every payload here is `Clone` (Copy enums / f32 / i32 / LengthPercentageOrAuto), - // so `.clone()` is the faithful equivalent. + // Every payload here is `Clone` (Copy enums / f32 / i32 / + // LengthPercentageOrAuto), so `.clone()` suffices. if let Some(field) = &mut self.$prop { field.0 = ($val).clone(); field.1.insert(*$vp); @@ -645,8 +638,7 @@ impl FlexHandler { Property::Unparsed(val) => { if Self::is_flex_property(&val.property_id) { self.flush(dest, context); - // PORT NOTE: Zig pushed `property.deepClone(context.arena)`. `Property` - // has no blanket `deep_clone` yet; reconstruct from the matched payload. + // `Property` has no blanket `deep_clone` yet; reconstruct from the matched payload. let bump = dest.bump(); dest.push(Property::Unparsed(val.deep_clone(bump))); } else { @@ -694,8 +686,6 @@ impl FlexHandler { let mut order = self.order.take(); let mut flex_order = self.flex_order.take(); - // TODO(port): Zig `legacyProperty` / `singleProperty` use `@unionInit(Property, name, ...)` - // (comptime token-pasting). Ported as macro_rules! taking the Property variant ident. macro_rules! legacy_property { ($variant:ident, $key:expr) => {{ if let Some(value) = $key { @@ -704,8 +694,7 @@ impl FlexHandler { if !prefix.is_empty() { dest.push(Property::$variant((val, prefix))); } else { - // css.generic.eql(comptime T: type, lhs: *const T, rhs: *const T) - // css.generic.deinit(@TypeOf(val), &val, ctx.arena); + // No prefix: the value is dropped here. } } }}; @@ -744,7 +733,6 @@ impl FlexHandler { } if let (Some(dir_val), Some(wrap_val)) = (&mut direction, &mut wrap) { - // PORT NOTE: reshaped for borrowck — Zig took simultaneous &mut into both Options. let dir: &FlexDirection = &dir_val.0; let dir_prefix: &mut VendorPrefix = &mut dir_val.1; let wrapinner: &FlexWrap = &wrap_val.0; @@ -799,7 +787,6 @@ impl FlexHandler { // prop_2012 = Some, prop_2009 = BoxOrdinalGroup special case ($variant:ident, $key:expr, prop_2012 = $p2012:ident, prop_2009 = (BoxOrdinalGroup, $v2009:ident), feature = $feature:ident) => {{ single_property!(@inner $variant, $key, $feature, |val, _prefix, prefixes_2009: VendorPrefix| { - // Zig: if T == BoxOrdinalGroup -> Some(val as i32) let s: Option = Some(val); if let Some(v) = s { dest.push(Property::$v2009((v, prefixes_2009))); @@ -850,10 +837,10 @@ impl FlexHandler { } }}; } - // TODO(port): single_property! macro encodes Zig's comptime `prop_2009`/`prop_2012` branches. - // The Zig version gates the entire 2009 block on `comptime prop_2009 != null`; here the macro - // arms with `prop_2009 = None` pass a no-op closure, so the `prefix.contains(NONE)` check - // still runs but has no effect. Verify this matches behavior exactly. + // The `prop_2009 = None` arms pass a no-op closure for the 2009 block. + // That block has no side effects other than invoking the body (it only + // reads `prefix`/`targets` into locals), so running it with a no-op + // body is behaviorally identical to skipping it. single_property!( FlexDirection, @@ -890,7 +877,7 @@ impl FlexHandler { } if let (Some(g_val), Some(s_val), Some(b_val)) = (&mut grow, &mut shrink, &mut basis) { - // PORT NOTE: reshaped for borrowck + // reshaped for borrowck let g = g_val.0; let g_prefix: &mut VendorPrefix = &mut g_val.1; let s = s_val.0; @@ -970,5 +957,3 @@ impl FlexHandler { ) } } - -// ported from: src/css/properties/flex.zig diff --git a/src/css/properties/font.rs b/src/css/properties/font.rs index e3202c4d6b0..8669864f637 100644 --- a/src/css/properties/font.rs +++ b/src/css/properties/font.rs @@ -1,6 +1,4 @@ //! CSS font properties. -//! -//! Ported from `src/css/properties/font.zig`. // // The data types (FontWeight / AbsoluteFontWeight / FontSize / // AbsoluteFontSize / RelativeFontSize / @@ -39,7 +37,6 @@ use css::CssResult; /// A value for the [font-weight](https://www.w3.org/TR/css-fonts-4/#font-weight-prop) property. #[derive(Clone, PartialEq)] -// TODO(port): css.DeriveParse / css.DeriveToCss were comptime-reflection derives; provide proc-macro #[derive(Parse, ToCss)] pub enum FontWeight { /// An absolute font weight. Absolute(AbsoluteFontWeight), @@ -50,9 +47,8 @@ pub enum FontWeight { } impl FontWeight { - // PORT NOTE: Zig `css.DeriveParse(@This()).parse` for a union(enum) with one - // payload variant + 2 keyword variants tries the payload first, then matches - // the remaining keywords against `expect_ident`. + // Tries the payload variant first, then matches the remaining keywords + // against `expect_ident`. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { if let Ok(v) = input.try_parse(AbsoluteFontWeight::parse) { return Ok(FontWeight::Absolute(v)); @@ -86,8 +82,7 @@ impl FontWeight { } } - // eql → derived PartialEq - // deepClone → derived Clone; TODO(port): arena-aware deep_clone if needed + // eql → derived PartialEq; deepClone → derived Clone (no arena-owned data) } /// An [absolute font weight](https://www.w3.org/TR/css-fonts-4/#font-weight-absolute-values), @@ -105,8 +100,7 @@ pub enum AbsoluteFontWeight { } impl AbsoluteFontWeight { - // PORT NOTE: Zig `css.DeriveParse(@This()).parse` — payload (`CSSNumber`) first, - // then keyword variants. + // Payload (`CSSNumber`) first, then keyword variants. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { if let Ok(n) = input.try_parse(CSSNumberFns::parse) { return Ok(AbsoluteFontWeight::Weight(n)); @@ -237,8 +231,7 @@ pub enum FontStretch { } impl FontStretch { - // PORT NOTE: Zig `css.DeriveParse(@This()).parse` — two payload variants - // tried in declaration order. + // Two payload variants tried in declaration order. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { if let Ok(kw) = input.try_parse(FontStretchKeyword::parse) { return Ok(FontStretch::Keyword(kw)); @@ -334,9 +327,11 @@ pub enum FontFamily { /// A generic family name. Generic(GenericFontFamily), /// A custom family name. - // TODO(port): arena-backed slice — should be &'bump [u8] once 'bump lifetime is threaded through - // PORT NOTE: with *const [u8] derived PartialEq/Eq/Hash would compare by pointer; Zig's custom - // HashContext hashes/compares by content (Wyhash over bytes) — provide manual impls below. + // Arena-backed slice: the pointer targets bytes owned by the parser arena and is + // only valid while that arena is alive (becomes `&'bump [u8]` once the 'bump + // lifetime is threaded through these types). With `*const [u8]`, derived + // PartialEq/Eq/Hash would compare by pointer; the manual impls below + // hash/compare by content (Wyhash over bytes). FamilyName(*const [u8]), } @@ -433,9 +428,8 @@ impl FontFamily { // `CssEql`/`DeepClone` via `bridge_clone_partialeq!` in `generics.rs`. } -// PORT NOTE: Zig's `css.implementEql` / `css.implementHash` walked fields by -// reflection and compared/hashed `[]const u8` by *content*. With `*const [u8]` -// in Rust, derived `PartialEq`/`Hash` would compare pointers, so hand-roll. +// With `*const [u8]`, derived `PartialEq`/`Hash` would compare pointers; +// hand-roll to compare/hash the bytes by *content*. impl PartialEq for FontFamily { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -452,9 +446,9 @@ impl Eq for FontFamily {} impl core::hash::Hash for FontFamily { fn hash(&self, state: &mut H) { - // PORT NOTE: Zig `css.implementHash` hashes the active tag then the - // payload bytes. With `*const [u8]` a derived Hash would hash the - // pointer address, breaking FontFamilyHashMap dedupe semantics. + // Hash the active tag then the payload bytes. With `*const [u8]` a + // derived Hash would hash the pointer address, breaking + // FontFamilyHashMap dedupe semantics. core::mem::discriminant(self).hash(state); match self { FontFamily::Generic(g) => g.hash(state), @@ -468,8 +462,8 @@ impl core::hash::Hash for FontFamily { impl Clone for FontFamily { fn clone(&self) -> Self { - // PORT NOTE: shallow — arena slice pointers are `Copy`; matches Zig's - // implicit struct copy. `deepClone` would re-alloc the slice in 'bump. + // shallow — arena slice pointers are `Copy`. + // `deepClone` would re-alloc the slice in 'bump. match self { FontFamily::Generic(g) => FontFamily::Generic(*g), FontFamily::FamilyName(n) => FontFamily::FamilyName(*n), @@ -647,8 +641,7 @@ pub enum LineHeight { } impl LineHeight { - // PORT NOTE: Zig `css.DeriveParse(@This()).parse` — keyword variant first - // (`normal`), then payload variants in declaration order. + // Keyword variant first (`normal`), then payload variants in declaration order. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { if input .try_parse(|p| p.expect_ident_matching(b"normal")) @@ -686,10 +679,8 @@ impl LineHeight { } /// A value for the [font](https://www.w3.org/TR/css-fonts-4/#font-prop) shorthand property. -// PORT NOTE: Zig's `eql`/`deepClone` were reflection-based (`css.implementEql` -// / `css.implementDeepClone`); the field-wise `#[derive(DeepClone, CssEql)]` -// is the Rust equivalent — every field type carries the trait via the -// blankets/bridges in `generics.rs`. +// Field-wise `#[derive(DeepClone, CssEql)]` — every field type carries the +// trait via the blankets/bridges in `generics.rs`. #[derive(DeepClone, CssEql)] pub struct Font { /// The font family. @@ -746,7 +737,7 @@ impl Font { } if variant_caps.is_some() { - // PORT NOTE: Zig has `if (variant_caps != null)` here — preserved verbatim (likely upstream bug; should be `== null`) + // Intentionally checks `is_some()` to match upstream lightningcss (likely a bug there; should be `is_none()`) if let Ok(value) = input.try_parse(FontVariantCaps::parse_css2) { variant_caps = Some(value); count += 1; @@ -776,8 +767,6 @@ impl Font { None }; - // PORT NOTE: Zig `Vec(FontFamily).parse` parsed a comma-separated - // list and packed it; route through `parse_comma_separated` + move. let family = input .parse_comma_separated(FontFamily::parse) .map(Vec::::move_from_list)?; @@ -878,7 +867,7 @@ impl FontProperty { pub(crate) fn try_from_property_id( property_id: crate::properties::PropertyIdTag, ) -> Option { - // TODO(port): Zig used `inline for` over std.meta.fields + @field; expanded by hand + // Keep in sync when new Font* PropertyIdTag variants are added. use crate::properties::PropertyIdTag; match property_id { PropertyIdTag::FontFamily => Some(FontProperty::FONT_FAMILY), @@ -918,12 +907,12 @@ impl FontHandler { context: &mut crate::PropertyHandlerContext<'_>, ) -> bool { use crate::properties::Property; - // PORT NOTE: `arena` field dropped from PropertyHandlerContext; the + // `arena` field dropped from PropertyHandlerContext; the // arena is recovered via `dest.bump()` (DeclarationList = bumpalo::Vec). let arena = dest.bump(); - // TODO(port): Zig used `comptime prop: []const u8` + @field for property_helper / flush_helper / push. - // No Rust equivalent for field-name reflection — expanded as macro_rules! over (handler_field, Property variant, FontProperty flag). + // macro_rules! over (handler_field, Property variant, FontProperty flag) + // for property_helper / flush_helper / push. macro_rules! flush_helper { ($this:expr, $field:ident, $val:expr) => {{ if $this.$field.is_some() @@ -979,7 +968,6 @@ impl FontHandler { self.flush(dest, context); self.flushed_properties .insert(FontProperty::try_from_property_id(val.property_id.tag()).unwrap()); - // PERF(port): was dest.append(context.arena, property.*) on arena dest.push(property.deep_clone(arena)); } else { return false; @@ -1011,7 +999,6 @@ impl FontHandler { macro_rules! push_prop { (Font, $val:expr) => {{ - // PERF(port): was dest.append(ctx.arena, ..) on arena-backed list decls.push(Property::Font($val)); self.flushed_properties.insert(FontProperty::FONT); }}; @@ -1047,18 +1034,19 @@ impl FontHandler { if let Some(f) = family.as_mut() { if f.len() > 1 { // Dedupe - // PERF(port): was std.heap.stackFallback(664, default_allocator) — profile if it shows up on a hot path let mut seen: FontFamilyHashMap<()> = Default::default(); let mut i: usize = 0; while i < f.len() { - // TODO(port): seen.getOrPut equivalent — using entry API - let key = f.at(i).clone(); - if seen.contains_key(&key) { - let _ = f.ordered_remove(i); - } else { - seen.insert(key, ()); - i += 1; + use bun_collections::array_hash_map::MapEntry; + match seen.entry(f.at(i).clone()) { + MapEntry::Occupied(_) => { + let _ = f.ordered_remove(i); + } + MapEntry::Vacant(v) => { + v.insert(()); + i += 1; + } } } } @@ -1128,8 +1116,8 @@ impl FontHandler { } } -// TODO(port): SYSTEM_UI was `const FontFamily = .{ .generic = .system_ui }`; cannot be a `const` here -// because FontFamily contains a raw pointer. Compare against the Generic variant directly instead. +// Matched against the Generic variant directly instead of a const +// (FontFamily holds a raw pointer). fn is_system_ui(f: &FontFamily) -> bool { matches!(f, FontFamily::Generic(GenericFontFamily::SystemUi)) } @@ -1159,11 +1147,10 @@ fn compatible_font_family( } if let Some(families) = family.as_mut() { - // PORT NOTE: Zig (font.zig:1029-1035) iterates `families.sliceConst()` - // by value while inserting into `families` mid-loop, then `break`s. - // In Rust the immutable slice borrow would alias the &mut needed for - // `insert` (and `insert` may reallocate, invalidating the iterator). - // Reshape: capture the system-ui index first, drop the borrow, then + // Iterating the slice while inserting into `families` mid-loop would + // alias the &mut needed for `insert` (and `insert` may reallocate, + // invalidating the iterator). + // Capture the system-ui index first, drop the borrow, then // perform the inserts using the captured index. if let Some(i) = families.slice_const().iter().position(is_system_ui) { for (j, name) in DEFAULT_SYSTEM_FONTS.iter().enumerate() { @@ -1193,5 +1180,3 @@ fn is_font_property(property_id: &crate::properties::PropertyId) -> bool { | PropertyId::Font ) } - -// ported from: src/css/properties/font.zig diff --git a/src/css/properties/generate_properties.ts b/src/css/properties/generate_properties.ts index 5bda2f67bb5..19fc43ebfc4 100644 --- a/src/css/properties/generate_properties.ts +++ b/src/css/properties/generate_properties.ts @@ -1529,14 +1529,14 @@ generateCode({ // cursor: { // ty: "Cursor", // }, - // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.zig :) + // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.rs :) // "caret-color": { // ty: "ColorOrAuto", // }, // "caret-shape": { // ty: "CaretShape", // }, - // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.zig :) + // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.rs :) // caret: { // ty: "Caret", // shorthand: true, @@ -1573,7 +1573,7 @@ generateCode({ conditional: { css_modules: true }, parse_dont_make_unparsed: true, }, - // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.zig :) + // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.rs :) // fill: { // ty: "SVGPaint", // }, @@ -1583,7 +1583,7 @@ generateCode({ // "fill-opacity": { // ty: "AlphaValue", // }, - // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.zig :) + // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.rs :) // stroke: { // ty: "SVGPaint", // }, @@ -1752,12 +1752,12 @@ generateCode({ unprefixed: false, }, - // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.zig :) + // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.rs :) // filter: { // ty: "FilterList", // valid_prefixes: ["webkit"], // }, - // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.zig :) + // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.rs :) // "backdrop-filter": { // ty: "FilterList", // valid_prefixes: ["webkit"], diff --git a/src/css/properties/grid.rs b/src/css/properties/grid.rs index 7ea72033c05..b761b969986 100644 --- a/src/css/properties/grid.rs +++ b/src/css/properties/grid.rs @@ -9,7 +9,6 @@ use bun_core::strings; /// A [track sizing](https://drafts.csswg.org/css-grid-2/#track-sizing) value /// for the `grid-template-rows` and `grid-template-columns` properties. -// TODO(port): css.DeriveParse / css.DeriveToCss → #[derive(Parse, ToCss)] proc-macro pub enum TrackSizing { /// No explicit grid tracks. None, @@ -39,7 +38,6 @@ impl TrackList { if let Ok(track_size) = input.try_parse(TrackSize::parse) { // TODO: error handling - // TODO(port): Zig original omits arena arg here (`items.append(.{...})`); mirroring with input.arena() items.push(TrackListItem::TrackSize(track_size)); } else if let Ok(repeat) = input.try_parse(TrackRepeat::parse) { // TODO: error handling @@ -142,7 +140,6 @@ impl TrackSize { input.expect_function_matching(b"fit-content")?; - // TODO(port): css.voidWrap(LengthPercentage, LengthPercentage.parse) — wraps a parse fn for parseNestedBlock; using a closure directly let len = input.parse_nested_block(|i: &mut Parser| LengthPercentage::parse(i))?; Ok(TrackSize::FitContent(len)) @@ -289,13 +286,10 @@ impl TrackRepeat { input.expect_function_matching(b"repeat")?; input.parse_nested_block(|i: &mut Parser| -> css::Result { - // TODO(port): Zig uses `@call(.auto, @field(RepeatCount, "parse"), .{i})` — direct call here let count = RepeatCount::parse(i)?; i.expect_comma()?; - // TODO: this code will not compile if used - // TODO(port): Zig calls `bun.Vec(T).init(i.arena)` — using default + push(alloc, ..) here let mut line_names = Vec::::default(); let mut track_sizes = Vec::::default(); @@ -305,7 +299,7 @@ impl TrackRepeat { .unwrap_or_else(|_| CustomIdentList::default()); line_names.push(line_name); - // TODO(port): Zig original references outer `input` here (likely a bug); mirroring with `i` + // Use the nested parser `i`, not the outer `input`. if let Ok(track_size) = i.try_parse(TrackSize::parse) { // TODO: error handling track_sizes.push(track_size); @@ -391,7 +385,7 @@ fn parse_line_names(input: &mut Parser) -> css::Result { input.parse_nested_block(|i: &mut Parser| -> css::Result { let mut values = CustomIdentList::default(); - // TODO(port): Zig original references outer `input` here (likely a bug); mirroring with `i` + // Use the nested parser `i`, not the outer `input`. while let Ok(ident) = i.try_parse(CustomIdent::parse) { values.append(ident); } @@ -404,7 +398,6 @@ fn parse_line_names(input: &mut Parser) -> css::Result { /// used in the `repeat()` function. /// /// See [TrackRepeat](TrackRepeat). -// TODO(port): css.DeriveParse / css.DeriveToCss → #[derive(Parse, ToCss)] proc-macro #[derive(PartialEq, Eq)] pub enum RepeatCount { /// The number of times to repeat. @@ -416,8 +409,8 @@ pub enum RepeatCount { } impl RepeatCount { - // PORT NOTE: `css.DeriveParse(@This()).parse` — hand-expanded in declaration - // order (Number → keyword `auto-fill` → keyword `auto-fit`). + // Variants tried in declaration order (Number → keyword `auto-fill` → + // keyword `auto-fit`). pub fn parse(input: &mut Parser) -> css::Result { if let Ok(n) = input.try_parse(CSSIntegerFns::parse) { return Ok(RepeatCount::Number(n)); @@ -433,7 +426,6 @@ impl RepeatCount { Err(location.new_unexpected_token_error(css::Token::Ident(ident))) } - // PORT NOTE: `css.DeriveToCss(@This()).toCss` — hand-expanded. pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { match self { RepeatCount::Number(n) => CSSIntegerFns::to_css(*n, dest), @@ -454,7 +446,7 @@ pub enum GridTemplateAreas { columns: u32, /// A flattened list of grid area names. /// Unnamed areas specified by the `.` token are represented as null. - // TODO(port): arena-owned slice lifetime — Zig `?[]const u8` in CSS arena + // TODO: arena-owned slice lifetime — should be `Option<&'bump [u8]>` areas: SmallList, 1>, }, } @@ -472,7 +464,7 @@ impl GridTemplateAreas { let mut row: u32 = 0; let mut columns: u32 = 0; - // PORT NOTE: `expect_string` returns a slice borrowing `&mut self`, which + // `expect_string` returns a slice borrowing `&mut self`, which // `try_parse`'s `R` type param can't carry. Erase the lifetime through a // raw pointer inside the closure; the slice lives in the input arena and // outlives this parse. @@ -483,7 +475,6 @@ impl GridTemplateAreas { let parsed_columns = match Self::parse_string(input.arena(), s, &mut tokens) { Ok(v) => v, Err(()) => { - // TODO(port): Zig uses `.{input.newError(.qualified_rule_invalid)}` — anonymous struct shorthand; mapping to Err(..) return Err(input.new_error(css::BasicParseErrorKind::qualified_rule_invalid)); } }; @@ -529,8 +520,11 @@ impl GridTemplateAreas { column += 1; if strings::starts_with_char(rest, b'.') { - // TODO(port): Zig original falls through here without `continue` — likely a bug (the `.` token - // is supposed to push None and continue). Mirroring Zig control flow exactly. + // TODO: this intentionally falls through without `continue`, + // which is likely a bug (per upstream lightningcss, a `.` + // null-cell token should push None and continue; as written, + // any string containing `.` fails the name-codepoint check + // below and the whole value fails to parse). } let starts_with_name_codepoint = 'brk: { @@ -553,7 +547,7 @@ impl GridTemplateAreas { rest.len() }; let token = &rest[..token_len]; - // TODO(port): arena-owned slice — Zig stores borrowed slice into SmallList; using raw ptr here + // TODO: arena-owned slice — should store a borrowed slice into SmallList; using raw ptr here let _ = bump; tokens.append(Some(std::ptr::from_ref::<[u8]>(token))); string = &rest[token_len..]; @@ -574,5 +568,3 @@ fn is_name_codepoint(c: u8) -> bool { } crate::css_eql_partialeq!(TrackSize, RepeatCount); - -// ported from: src/css/properties/grid.zig diff --git a/src/css/properties/list.rs b/src/css/properties/list.rs index bbc9f7cc9f0..8b137891791 100644 --- a/src/css/properties/list.rs +++ b/src/css/properties/list.rs @@ -1 +1 @@ -// ported from: src/css/properties/list.zig + diff --git a/src/css/properties/margin_padding.rs b/src/css/properties/margin_padding.rs index d335107cb74..44369ee5edd 100644 --- a/src/css/properties/margin_padding.rs +++ b/src/css/properties/margin_padding.rs @@ -6,8 +6,7 @@ use crate::properties::{Property, PropertyId, PropertyIdTag}; use crate::{DeclarationList, PropertyHandlerContext}; use bun_alloc::ArenaVecExt as _; -// `RectShorthand`/`SizeShorthand` mirror Zig's `css.DefineRectShorthand` / -// `css.DefineSizeShorthand` comptime mixins. The marker traits stay (some +// The `RectShorthand`/`SizeShorthand` marker traits stay (some // callers name `::Value`). The rect-shorthand structs // below are stamped out by `define_rect_shorthand!` (struct + PROPERTY_FIELD_MAP // + deep_clone/eql + parse/to_css + RectShorthand impl); the size-shorthand @@ -71,11 +70,8 @@ impl_size_shorthand!( // Shorthand value types // ────────────────────────────────────────────────────────────────────────── // -// Zig used `css.DefineRectShorthand(@This(), V)` / `css.DefineSizeShorthand(@This(), V)` -// as comptime mixins that inject `parse` + `toCss`. In Rust those become trait -// impls (`RectShorthand` / `SizeShorthand`) that provide default `parse`/`to_css`. -// The trait comes first (PORTING.md §Comptime reflection); a `#[derive]` could -// replace the manual impls. +// Trait impls (`RectShorthand` / `SizeShorthand`) provide default +// `parse`/`to_css`. A `#[derive]` could replace the manual impls. // // `implementDeepClone` / `implementEql` are field-wise reflection helpers → // `#[derive(Clone, PartialEq)]`; the `DeepClone`/`CssEql` trait impls are @@ -83,8 +79,8 @@ impl_size_shorthand!( // // `PropertyFieldMap` (an anonymous struct mapping field-name → PropertyIdTag) // becomes an associated const slice; consumers that did `@field(map, name)` -// will look up by name. // TODO(port): if consumers need O(1) by-field access, -// switch to per-type associated consts. +// will look up by name. (If consumers ever need O(1) by-field access, this +// could switch to per-type associated consts.) define_rect_shorthand! { /// A value for the [inset](https://drafts.csswg.org/css-logical/#propdef-inset) shorthand property. @@ -361,27 +357,19 @@ pub type ScrollMarginHandler = SizeHandler; pub type InsetHandler = SizeHandler; // ────────────────────────────────────────────────────────────────────────── -// NewSizeHandler — Zig `fn(comptime ...) type { return struct { ... } }` +// SizeHandler // ────────────────────────────────────────────────────────────────────────── // -// The Zig generator took 11 `comptime PropertyIdTag` parameters, a -// `comptime PropertyCategory`, and an optional `{feature, shorthand_feature}` -// pair, and used `@field` / `@tagName` / `@unionInit` to project in/out of -// the `Property` tagged union by tag name at compile time. +// The per-variant projection in/out of the `Property` tagged union lives in +// a `SizeHandlerSpec` trait. The generic body (`handle_property` / `flush` / +// helpers) calls through `S::*`. Each concrete handler is a zero-sized +// marker type implementing the spec. // -// Rust cannot reflect on enum variants by `PropertyIdTag` value, so the -// per-variant projection is moved into a `SizeHandlerSpec` trait. The -// generic body (`handle_property` / `flush` / helpers) is preserved 1:1 and -// calls through `S::*`. Each concrete handler is a zero-sized marker type -// implementing the spec. -// -// TODO(port): a `macro_rules! size_handler_spec!` could generate the four -// `SizeHandlerSpec` impls from the same 13-argument table the Zig used, -// eliminating the per-spec extract/construct boilerplate. Left explicit for -// reviewability. +// A macro could generate the four `SizeHandlerSpec` impls from a single +// argument table, eliminating the per-spec extract/construct boilerplate. +// Left explicit for reviewability. -/// Selector for the four physical slots on `SizeHandler` (Zig used a -/// `comptime field: []const u8` and `@field(this, field)`). +/// Selector for the four physical slots on `SizeHandler`. #[derive(Copy, Clone)] enum PhysicalSlot { Top, @@ -400,12 +388,8 @@ enum LogicalSlot { } /// Compile-time configuration for one `SizeHandler` instantiation. -/// -/// Replaces the 13 `comptime` parameters of Zig's `NewSizeHandler` and the -/// `@field(property, @tagName(X_prop))` / `@unionInit(Property, @tagName(X_prop), v)` -/// reflection it performed. pub trait SizeHandlerSpec { - // ---- comptime tag parameters ---- + // ---- tag parameters ---- const TOP: PropertyIdTag; const BOTTOM: PropertyIdTag; const LEFT: PropertyIdTag; @@ -417,7 +401,7 @@ pub trait SizeHandlerSpec { const SHORTHAND: PropertyIdTag; const BLOCK_SHORTHAND: PropertyIdTag; const INLINE_SHORTHAND: PropertyIdTag; - // PORT NOTE: `PropertyId` mirrors of TOP/BOTTOM/LEFT/RIGHT for + // `PropertyId` mirrors of TOP/BOTTOM/LEFT/RIGHT for // `UnparsedProperty::with_property_id`. All margin/padding/inset/scroll-* // `PropertyId` variants are payload-free, so these are well-formed consts. const TOP_ID: PropertyId; @@ -425,30 +409,30 @@ pub trait SizeHandlerSpec { const LEFT_ID: PropertyId; const RIGHT_ID: PropertyId; const SHORTHAND_CATEGORY: PropertyCategory; - /// `shorthand_extra.?.feature` — `None` ⇔ Zig passed `null`. + /// Optional prefix feature for the shorthand. const FEATURE: Option; /// `shorthand_extra.?.shorthand_feature`. const SHORTHAND_FEATURE: Option; - // ---- value-type bindings (Zig: `X_prop.valueType()`) ---- + // ---- value-type bindings ---- // In every instantiation in this file the longhand value type is // `LengthPercentageOrAuto`, so the generic body below uses that // concretely. If a future spec needs a different `valueType()`, lift it // to an associated type here. - /// Zig: `shorthand_prop.valueType()` (the 4-field rect struct). + /// The 4-field rect struct. type Shorthand; - /// Zig: `block_shorthand.valueType()` (the 2-field block struct). + /// The 2-field block struct. type BlockShorthand; - /// Zig: `inline_shorthand.valueType()` (the 2-field inline struct). + /// The 2-field inline struct. type InlineShorthand; // ---- @field / @unionInit replacements ---- // Each pair is the Rust spelling of: // `@field(property, @tagName(X_prop))` → extract_x // `@unionInit(Property, @tagName(X_prop), v)` → make_x - // TODO(port): these are pure mechanical pattern-matches over `Property`; - // could generate via macro. + // These are pure mechanical pattern-matches over `Property`; they could + // be generated via macro. fn extract_top(p: &Property) -> &LengthPercentageOrAuto; fn extract_bottom(p: &Property) -> &LengthPercentageOrAuto; @@ -485,7 +469,7 @@ pub trait SizeHandlerSpec { inline_end: LengthPercentageOrAuto, ) -> Property; - // Field accessors on the shorthand value structs (Zig: `val.block_start` etc.). + // Field accessors on the shorthand value structs. fn shorthand_top(v: &Self::Shorthand) -> &LengthPercentageOrAuto; fn shorthand_right(v: &Self::Shorthand) -> &LengthPercentageOrAuto; fn shorthand_bottom(v: &Self::Shorthand) -> &LengthPercentageOrAuto; @@ -497,8 +481,6 @@ pub trait SizeHandlerSpec { } /// Generic margin/padding/inset/scroll-* handler. -/// -/// Zig: the anonymous `return struct { ... }` inside `NewSizeHandler`. pub struct SizeHandler { pub top: Option, pub bottom: Option, @@ -531,7 +513,7 @@ impl Default for SizeHandler { } } -// PORT NOTE: `context.arena` was dropped from PropertyHandlerContext; the +// `context.arena` was dropped from PropertyHandlerContext; the // arena is recovered via `dest.bump()` (DeclarationList = bumpalo::Vec). impl SizeHandler { // ---- @field(this, field) replacements ---- @@ -574,10 +556,10 @@ impl SizeHandler { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) -> bool { - // Zig: `switch (@as(PropertyIdTag, property.*))` — the *raw* union - // discriminant, ported as `Property::variant_tag()`. The `.unparsed` - // arm needs the inner `property_id` to decide whether the unparsed - // value belongs to this handler, so it stays a structural match. + // Match on the *raw* union discriminant (`Property::variant_tag()`). + // The `Unparsed` arm needs the inner `property_id` to decide whether + // the unparsed value belongs to this handler, so it stays a + // structural match. if let Property::Unparsed(unparsed) = property { let id = unparsed.property_id.tag(); if id == S::TOP @@ -674,8 +656,7 @@ impl SizeHandler { dest, context, ); - // PORT NOTE: Zig stored `property.deepClone(arena)`; reconstruct - // via the spec's `make_X(extract_X)` pair (same observable shape). + // Reconstruct via the spec's `make_X(extract_X)` pair. self.logical_property_helper( LogicalSlot::BlockStart, S::make_block_start(S::extract_block_start(property).clone()), @@ -830,12 +811,11 @@ impl SizeHandler { self.flush(dest, context); } - // PORT NOTE: reshaped — Zig's single `flushHelper` (generic over `comptime field: []const u8` - // via `@field(this, field)`) is split into `flush_helper_physical` + `flush_helper_logical` - // because the physical slots hold `Option` and the logical slots hold - // `Option`; Rust cannot express `@field` over heterogeneous Option payloads generically. + // The flush helper is split into `flush_helper_physical` + `flush_helper_logical` + // because the physical slots hold `Option` and the + // logical slots hold `Option`. - /// Zig `flushHelper` for the four physical slots (`top`/`bottom`/`left`/`right`). + /// Flush helper for the four physical slots (`top`/`bottom`/`left`/`right`). fn flush_helper_physical( &mut self, field: PhysicalSlot, @@ -844,7 +824,6 @@ impl SizeHandler { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) { - // PERF(port): `category` was comptime monomorphization — profile if hot. // If the category changes betweet logical and physical, // or if the value contains syntax that isn't supported across all targets, // preserve the previous value as a fallback. @@ -857,7 +836,7 @@ impl SizeHandler { } } - /// Zig `flushHelper` for the four logical slots (`block_start`/.../`inline_end`). + /// Flush helper for the four logical slots (`block_start`/.../`inline_end`). fn flush_helper_logical( &mut self, field: LogicalSlot, @@ -866,7 +845,6 @@ impl SizeHandler { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) { - // PERF(port): `category` was comptime monomorphization — profile if hot. // If the category changes betweet logical and physical, // or if the value contains syntax that isn't supported across all targets, // preserve the previous value as a fallback. @@ -887,7 +865,6 @@ impl SizeHandler { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) { - // PERF(port): `category` was comptime monomorphization — profile if hot. self.flush_helper_physical(field, val, category, dest, context); *self.physical_slot(field) = Some(val.clone()); self.category = category; @@ -908,8 +885,7 @@ impl SizeHandler { self.flush(dest, context); } - // Zig: `if (@field(this, field)) |*p| p.deinit(context.arena);` - // Drop handles deinit; assigning over the Option drops the old value. + // Assigning over the Option drops the old value. *self.logical_slot(field) = Some(val); self.category = PropertyCategory::Logical; self.has_any = true; @@ -998,8 +974,7 @@ impl SizeHandler { context, ); } else if inline_start.is_some() || inline_end.is_some() { - // Zig: `inline_start.? == @field(Property, @tagName(inline_start_prop))` - // — raw union-tag equality, which is `false` for `.unparsed`. + // Raw union-tag equality, which is `false` for `Unparsed`. let start_matches = inline_start .as_ref() .map(|p| p.variant_tag() == S::INLINE_START) @@ -1077,7 +1052,7 @@ impl SizeHandler { // _ = this; // autofix let bump = dest.bump(); if let Some(v_) = val.as_ref() { - // Zig: `@as(css.PropertyIdTag, _v.*) == logical` — raw discriminant. + // Raw discriminant comparison. if v_.variant_tag() == logical { let v = extract_logical(v_); context.add_logical_rule(make_ltr(v.clone()), make_rtl(v.clone())); @@ -1111,8 +1086,7 @@ impl SizeHandler { LogicalSidePair::Inline => (S::INLINE_START, S::INLINE_END), }; - // Zig: `@as(PropertyIdTag, start.*.?) == start_prop` — raw - // discriminant. `variant_tag()` keeps `Unparsed` distinct so an + // Raw discriminant comparison. `variant_tag()` keeps `Unparsed` distinct so an // unparsed longhand falls through to the else branch and is appended // as-is, instead of hitting `unreachable!()` in `extract_*`. if start @@ -1125,9 +1099,7 @@ impl SizeHandler { .unwrap_or(false) && shorthand_supported { - // Zig built `value: ValueType` field-by-field then `@unionInit`. - // The Zig also `@compileError`ed if the value type had >2 fields; - // that invariant is upheld structurally by `make_*_shorthand`. + // The ≤2-field invariant is upheld structurally by `make_*_shorthand`. let start_v = match pair { LogicalSidePair::Block => S::extract_block_start(start.as_ref().unwrap()).clone(), LogicalSidePair::Inline => S::extract_inline_start(start.as_ref().unwrap()).clone(), @@ -1165,11 +1137,10 @@ impl SizeHandler { let _ = context; let bump = dest.bump(); if let Some(v) = val.as_ref() { - // Zig: `@as(css.PropertyIdTag, v.*) == logical` — raw discriminant. + // Raw discriminant comparison. if v.variant_tag() == logical { - // Zig moved the payload (`@field(v, @tagName(logical))`) by value. - // PORT NOTE: reshaped for borrowck — clone instead of moving out - // of `&Property`; `LengthPercentageOrAuto` is small. + // Clone instead of moving out of `&Property`; + // `LengthPercentageOrAuto` is small. dest.push(make_physical(extract_logical(v).clone())); } else if let Property::Unparsed(u) = v { dest.push(Property::Unparsed(u.with_property_id(bump, physical))); @@ -1188,11 +1159,9 @@ enum LogicalSidePair { // Spec instantiations // ────────────────────────────────────────────────────────────────────────── // -// PORT NOTE: the `extract_*` / `make_*` / `shorthand_*` bodies are pure -// `@field` / `@unionInit` token-pasting in Zig (`NewSizeHandler`). -// `size_handler_spec_projections!` expands them from the 11 `Property` -// variant idents + 3 shorthand value-type idents that the Zig -// `NewSizeHandler(...)` call sites passed positionally. +// `size_handler_spec_projections!` expands the `extract_*` / `make_*` / +// `shorthand_*` bodies from the 11 `Property` variant idents + 3 shorthand +// value-type idents. macro_rules! size_handler_spec_projections { ( @@ -1496,7 +1465,5 @@ impl SizeHandlerSpec for InsetSpec { ); } -// NOTE: Zig also defined `ScrollPadding{,Block,Inline}` value types above but -// did NOT instantiate a `ScrollPaddingHandler` — matching that here. - -// ported from: src/css/properties/margin_padding.zig +// NOTE: `ScrollPadding{,Block,Inline}` value types are defined above but no +// `ScrollPaddingHandler` is instantiated. diff --git a/src/css/properties/masking.rs b/src/css/properties/masking.rs index 964188074ab..47b56d9bd74 100644 --- a/src/css/properties/masking.rs +++ b/src/css/properties/masking.rs @@ -28,8 +28,6 @@ use crate::properties::PropertyIdTag; /// A [``](https://www.w3.org/TR/css-masking-1/#typedef-geometry-box) value /// as used in the `mask-clip` and `clip-path` properties. -// TODO(port): css.DefineEnumProperty(@This()) — comptime-generated eql/hash/parse/toCss/deepClone. -// In Rust this becomes #[derive] of the css enum-property protocol (kebab-case serialization). #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum GeometryBox { /// The painted content is clipped to the content box. @@ -75,8 +73,7 @@ pub enum BasicShape { } /// An [`inset()`](https://www.w3.org/TR/css-shapes-1/#funcdef-inset) rectangle shape. -// Zig declares this `const` (file-private) but it's reachable via `pub enum BasicShape::Inset`, -// so Rust requires `pub` here — Zig has no private-in-public lint. +// Reachable via `pub enum BasicShape::Inset`, so it must be `pub`. pub struct InsetRect { /// The rectangle. pub rect: Rect, @@ -107,10 +104,9 @@ pub struct Polygon { /// The fill rule used to determine the interior of the polygon. pub fill_rule: FillRule, /// The points of each vertex of the polygon. - // TODO(port): css is an AST crate (§Allocators) — if Polygon is arena-fed this must become + // If Polygon ever becomes arena-fed this must become (§Allocators: AST crates are arena-fed) // `bun_alloc::ArenaVec<'bump, Point>` and Polygon/BasicShape/ClipPath gain `<'bump>`. - // No construction site exists in src/css/*.zig today, so provenance is unconfirmed; keeping - // plain Vec until the arena story is verified. + // Keeping plain Vec until the arena story is verified. pub points: Vec, } @@ -136,7 +132,6 @@ pub struct Point { } /// A value for the [mask-mode](https://www.w3.org/TR/css-masking-1/#the-mask-mode) property. -// TODO(port): css.DefineEnumProperty(@This()) → derive css enum-property protocol #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum MaskMode { /// The luminance values of the mask image is used. @@ -152,11 +147,9 @@ pub enum MaskMode { } /// A value for the [mask-clip](https://www.w3.org/TR/css-masking-1/#the-mask-clip) property. -// TODO(port): css.DeriveParse / css.DeriveToCss → derive css union-property protocol #[derive(Debug, Clone, Copy, PartialEq, Eq, css::Parse, css::ToCss)] pub enum MaskClip { /// A geometry box. - // Zig: @"geometry-box" GeometryBox(GeometryBox), /// The painted content is not clipped. #[css(name = "no-clip")] @@ -164,7 +157,6 @@ pub enum MaskClip { } /// A value for the [mask-composite](https://www.w3.org/TR/css-masking-1/#the-mask-composite) property. -// TODO(port): css.DefineEnumProperty(@This()) → derive css enum-property protocol #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum MaskComposite { /// The source is placed over the destination. @@ -183,7 +175,6 @@ pub enum MaskComposite { } /// A value for the [mask-type](https://www.w3.org/TR/css-masking-1/#the-mask-type) property. -// TODO(port): css.DefineEnumProperty(@This()) → derive css enum-property protocol #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum MaskType { /// The luminance values of the mask is used. @@ -195,7 +186,7 @@ pub enum MaskType { } /// A value for the [mask](https://www.w3.org/TR/css-masking-1/#the-mask) shorthand property. -// PORT NOTE: Debug/Clone/PartialEq derives gated on `Image`/`Position`/ +// Debug/Clone/PartialEq derives gated on `Image`/`Position`/ // `BackgroundSize`/`BackgroundRepeat` gaining those derives upstream. #[cfg_attr(any(), derive(Debug, Clone, PartialEq))] #[derive(DeepClone, CssEql)] @@ -219,9 +210,7 @@ pub struct Mask { } impl Mask { - // TODO(port): PropertyFieldMap was a Zig anon-struct const consumed by comptime - // reflection in shorthand handlers. Represented as an assoc const slice; could - // be replaced with a trait/derive. + // Shorthand field→property map, consumed by shorthand handlers. pub const PROPERTY_FIELD_MAP: &'static [(&'static str, PropertyIdTag)] = &[ ("image", PropertyIdTag::MaskImage), ("position", PropertyIdTag::MaskPosition), @@ -233,8 +222,7 @@ impl Mask { ("mode", PropertyIdTag::MaskMode), ]; - // TODO(port): VendorPrefixMap was a Zig anon-struct const of bools consumed by - // comptime reflection. Represented as a field-name slice; could be replaced with trait/derive. + // Field names that carry a vendor prefix. pub const VENDOR_PREFIX_MAP: &'static [&'static str] = &["image", "position", "size", "repeat", "clip", "origin"]; @@ -373,7 +361,6 @@ impl Mask { } /// A value for the [mask-border-mode](https://www.w3.org/TR/css-masking-1/#the-mask-border-mode) property. -// TODO(port): css.DefineEnumProperty(@This()) → derive css enum-property protocol #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum MaskBorderMode { /// The luminance values of the mask image is used. @@ -386,7 +373,7 @@ pub enum MaskBorderMode { } /// A value for the [mask-border](https://www.w3.org/TR/css-masking-1/#the-mask-border) shorthand property. -// PORT NOTE: Debug/Clone/PartialEq derives gated on `Image`/`Rect<_>` gaining +// Debug/Clone/PartialEq derives gated on `Image`/`Rect<_>` gaining // those derives upstream. #[cfg_attr(any(), derive(Debug, Clone, PartialEq))] #[derive(DeepClone, CssEql)] @@ -408,7 +395,7 @@ pub struct MaskBorder { impl MaskBorder { // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"mask-border", PropertyFieldMap); - // TODO(port): PropertyFieldMap — see note on Mask::PROPERTY_FIELD_MAP + // See the PropertyFieldMap note on Mask::PROPERTY_FIELD_MAP. pub const PROPERTY_FIELD_MAP: &'static [(&'static str, PropertyIdTag)] = &[ ("source", PropertyIdTag::MaskBorderSource), ("slice", PropertyIdTag::MaskBorderSlice), @@ -431,7 +418,7 @@ impl MaskBorder { }); if border_image.is_ok() || mode.is_some() { - // PERF(port): Zig used `comptime BorderImage.default()` — could const-eval the default + // PERF: could const-eval the default let bi = border_image.unwrap_or_else(|_| BorderImage::default()); Ok(MaskBorder { source: bi.source, @@ -474,7 +461,6 @@ impl MaskBorder { /// property. /// /// See also [MaskComposite](MaskComposite). -// TODO(port): css.DefineEnumProperty(@This()) → derive css enum-property protocol #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum WebKitMaskComposite { #[css(name = "clear")] @@ -513,7 +499,6 @@ pub enum WebKitMaskComposite { /// property. /// /// See also [MaskMode](MaskMode). -// TODO(port): css.DefineEnumProperty(@This()) → derive css enum-property protocol #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum WebKitMaskSourceType { /// Equivalent to `match-source` in the standard `mask-mode` syntax. @@ -529,8 +514,6 @@ pub enum WebKitMaskSourceType { // blocked_on: PropertyId::WebKitMaskComposite variant name (codegen spelling is `WebKitMaskComposite`) pub fn get_webkit_mask_property(property_id: &PropertyId) -> Option { - // TODO(port): PropertyId variant naming — Zig uses kebab-case @"mask-border-source" etc. - // Mapping to PascalCase variants here; verify exact PropertyId enum shape. match property_id { PropertyId::MaskBorderSource => Some(PropertyId::MaskBoxImageSource(VendorPrefix::WEBKIT)), PropertyId::MaskBorderSlice => Some(PropertyId::MaskBoxImageSlice(VendorPrefix::WEBKIT)), @@ -543,5 +526,3 @@ pub fn get_webkit_mask_property(property_id: &PropertyId) -> Option _ => None, } } - -// ported from: src/css/properties/masking.zig diff --git a/src/css/properties/mod.rs b/src/css/properties/mod.rs index 85ee46adb5f..b0013bfafa1 100644 --- a/src/css/properties/mod.rs +++ b/src/css/properties/mod.rs @@ -1,6 +1,4 @@ //! CSS property definitions. -//! -//! Ported from `src/css/properties/properties.zig`. #![warn(unused_must_use)] @@ -14,22 +12,8 @@ // `PropertyId::set_prefixes_for_targets` / `from_name_and_prefix` and the // `Property` payloads that name `css_values::*` resolve directly. -/// Declares a property-handler ZST with the `handle_property` / `finalize` -/// surface that `DeclarationHandler` (declaration.rs) composes over. The -/// real handler bodies live in the gated leaf .rs files; until those -/// un-gate, these no-op stubs keep `DeclarationHandler` compiling against -/// the now-real `Property` enum. -/// -/// PORT NOTE: Zig handlers are plain structs with `handleProperty(*Self, -/// *const Property, *DeclarationList, *PropertyHandlerContext) bool` + -/// `finalize(*Self, *DeclarationList, *PropertyHandlerContext) void`. Same -/// shape here; lifetimes on `DeclarationList<'bump>` / context are erased -/// behind anonymous lifetimes since the stub bodies touch neither. - // ─── Rect / Size shorthand impl + define macros ──────────────────────────── -// Shared by `border.rs` and `margin_padding.rs`. These are the Rust port of -// Zig's `css.DefineRectShorthand` / `css.DefineSizeShorthand` comptime mixins -// (src/css/css_parser.zig:502 / :532). +// Shared by `border.rs` and `margin_padding.rs`. // // `impl_rect_shorthand!` / `impl_size_shorthand!` stamp out the inherent // `parse`/`to_css` pair (and the `generic::{Parse,ToCss}` forwarders) for a @@ -95,8 +79,7 @@ macro_rules! define_rect_shorthand { } impl $name { - // TODO(port): bring this back - // (old using name space) css::DefineShorthand(@This(), PropertyIdTag::$shorthand_id); + // `PROPERTY_FIELD_MAP` records the shorthand field→property map. pub const PROPERTY_FIELD_MAP: &[(&str, $crate::properties::PropertyIdTag)] = &[ ("top", $crate::properties::PropertyIdTag::$top_id), @@ -108,7 +91,7 @@ macro_rules! define_rect_shorthand { impl $crate::properties::margin_padding::RectShorthand for $name { type Value = $inner; } - // Zig `css.DefineRectShorthand(@This(), V)` — parse/to_css via `Rect`. + // parse/to_css via `Rect`. impl_rect_shorthand!($name, $inner); }; } @@ -140,7 +123,6 @@ macro_rules! impl_size_shorthand { } // ─── Submodule declarations ──────────────────────────────────────────────── -// (Zig: `pub const X = @import("./X.zig");`) // pub mod align; // `animation`: un-gated — real AnimationName / Animation / AnimationIterationCount / @@ -174,7 +156,7 @@ pub mod font; pub mod grid; // `list`: un-gated — real ListStyleType / CounterStyle / Symbols / Symbol // live in `list.rs`. PredefinedCounterStyle / SymbolsType / ListStylePosition / -// ListStyle / MarkerSide are uninhabited (Zig source is `@compileError`). +// ListStyle / MarkerSide are uninhabited. pub mod list; pub mod margin_padding; pub mod masking; @@ -215,8 +197,7 @@ pub use self::custom::CustomPropertyName; pub use self::properties_generated::{Property, PropertyId, PropertyIdTag}; /// A [CSS-wide keyword](https://drafts.csswg.org/css-cascade-5/#defaulting-keywords). -// Zig: `css.DefineEnumProperty(@This())` provides eql/hash/parse/toCss/deepClone via -// comptime reflection over @tagName. The Rust derive emits `EnumProperty` + +// The `DefineEnumProperty` derive emits `EnumProperty` + // `From for &'static str` + inherent `parse`/`to_css`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, crate::DefineEnumProperty)] pub enum CSSWideKeyword { @@ -413,12 +394,3 @@ mod generic_registrations { } } pub(crate) use generic_registrations::GenericBorderImpl; - -// ─── Dead code (not ported) ──────────────────────────────────────────────── -// The original Zig file contains ~1800 lines of commented-out code (lines 60–1876) -// implementing the old `DefineProperties(...)` comptime-reflection approach that -// predates `properties_generated.zig`. It is dead reference material and is -// intentionally omitted here. See `src/css/properties/properties.zig` for the -// historical block; the live definitions come from `properties_generated`. - -// ported from: src/css/properties/properties.zig diff --git a/src/css/properties/outline.rs b/src/css/properties/outline.rs index 9f42cdc663a..746cb7af0c1 100644 --- a/src/css/properties/outline.rs +++ b/src/css/properties/outline.rs @@ -4,9 +4,6 @@ use super::border::{GenericBorder, LineStyle}; pub(crate) type Outline = GenericBorder; /// A value for the [outline-style](https://drafts.csswg.org/css-ui/#outline-style) property. -// `DeriveParse`/`DeriveToCss` in Zig are comptime-reflection helpers that iterate variants -// to implement the domain protocol — in Rust the protocol is a trait and we derive it. -// `implementEql`/`implementDeepClone` are field-iteration eq/clone → `#[derive(PartialEq, Clone)]`. #[derive(Clone, PartialEq, Eq, crate::Parse, crate::ToCss)] pub enum OutlineStyle { /// The `auto` keyword. @@ -20,5 +17,3 @@ impl Default for OutlineStyle { OutlineStyle::LineStyle(LineStyle::None) } } - -// ported from: src/css/properties/outline.zig diff --git a/src/css/properties/overflow.rs b/src/css/properties/overflow.rs index f6613e7c245..5fa4e16ced0 100644 --- a/src/css/properties/overflow.rs +++ b/src/css/properties/overflow.rs @@ -29,8 +29,6 @@ impl Overflow { /// An [overflow](https://www.w3.org/TR/css-overflow-3/#overflow-properties) keyword /// as used in the `overflow-x`, `overflow-y`, and `overflow` properties. -// PORT NOTE: css.DefineEnumProperty(@This()) — comptime mixin providing -// eql/hash/parse/to_css/deep_clone from @tagName. #[derive(Clone, Copy, PartialEq, Eq, Hash, crate::DefineEnumProperty)] pub enum OverflowKeyword { /// Overflowing content is visible. @@ -53,5 +51,3 @@ pub enum TextOverflow { /// Overflowing text is truncated with an ellipsis. Ellipsis, } - -// ported from: src/css/properties/overflow.zig diff --git a/src/css/properties/position.rs b/src/css/properties/position.rs index 944b2bc6d9e..6fb463a7517 100644 --- a/src/css/properties/position.rs +++ b/src/css/properties/position.rs @@ -28,8 +28,7 @@ enum PositionKeyword { } fn lookup_keyword(ident: &[u8]) -> Option { - // ≤8 entries → plain match per PORTING.md (Zig: `bun.ComptimeEnumMap` + - // `getASCIIICaseInsensitive`). + // ≤8 entries → plain match. use bun_core::eql_case_insensitive_ascii_check_length as eq; Some(if eq(ident, b"static") { PositionKeyword::Static @@ -80,5 +79,3 @@ impl Position { } } } - -// ported from: src/css/properties/position.zig diff --git a/src/css/properties/prefix_handler.rs b/src/css/properties/prefix_handler.rs index e3bebada314..77b8be3291b 100644 --- a/src/css/properties/prefix_handler.rs +++ b/src/css/properties/prefix_handler.rs @@ -9,7 +9,7 @@ use bun_alloc::ArenaVecExt as _; pub struct FallbackHandler { pub color: Option, pub text_shadow: Option, - // TODO: add these back plz + // The remaining fallback fields are not implemented yet. // filter: Option, // backdrop_filter: Option, // fill: Option, @@ -25,22 +25,15 @@ impl FallbackHandler { dest: &mut css::DeclarationList, context: &mut css::PropertyHandlerContext, ) -> bool { - // The Zig source does `inline for (std.meta.fields(FallbackHandler))` and uses - // `@field` / `@unionInit` keyed on the field name. Rust has no field reflection, - // so we expand each (field, Property variant, has_vendor_prefix) pair via macro. - // TODO(port): proc-macro — if the field list grows, generate these arms from a - // single source of truth shared with `Property`/`PropertyIdTag`. + // Each (field, Property variant, has_vendor_prefix) pair is expanded via macro. let arena = dest.bump(); - // PORT NOTE: Zig's `inline for` over `std.meta.fields(FallbackHandler)` dispatched - // each (field, Property variant) pair via a single generic body using `@field` / - // `@unionInit` + `css.generic.{deepClone,isCompatible,hasGetFallbacks}`. Rust has - // no field reflection and the generic-trait surface (`DeepClone`/`IsCompatible`/ - // `get_fallbacks` on `SmallList`) is still partially gated, so we - // expand each pair via a macro that takes per-type closures for those three ops. - // This keeps the *control flow* identical while letting each payload type use its - // own inherent methods until the trait lattice un-gates. + // The generic-trait surface (`DeepClone`/`IsCompatible`/`get_fallbacks` + // on `SmallList`) is still partially gated, so each + // (field, Property variant) pair is expanded via a macro that takes + // per-type closures for those three ops. This lets each payload type + // use its own inherent methods until the trait lattice un-gates. macro_rules! handle_unprefixed { ( $self_field:ident, @@ -53,7 +46,7 @@ impl FallbackHandler { let mut val = ($dc)(payload, arena); if $self_field.is_none() { - // PORT NOTE: `has_fallbacks` only used in the vendor-prefixed branch in Zig. + // `has_fallbacks` is only consulted in the vendor-prefixed branch. ($fb)(&mut val, arena, &context.targets, dest); } @@ -66,7 +59,7 @@ impl FallbackHandler { } else if let Some(index) = *$self_field { dest[index] = Property::$Variant(val); } else { - // val dropped — Rust Drop handles cleanup (Zig: val.deinit(context.arena)) + // val dropped — Drop handles cleanup drop(val); } @@ -75,7 +68,7 @@ impl FallbackHandler { }; } - // PORT NOTE: reshaped for borrowck — pre-borrow each self. as &mut so the + // Reshaped for borrowck — pre-borrow each self. as &mut so the // macro body can both read and assign it without re-borrowing `self`. let this = &mut *self; let color = &mut this.color; @@ -138,11 +131,7 @@ impl FallbackHandler { return false; }; - // TODO(port): re-enable once `PropertyHandlerContext::add_unparsed_fallbacks` - // un-gates (blocked on `SupportsCondition::eql` in context.rs). - context.add_unparsed_fallbacks(arena, &mut unparsed); - let _ = &mut unparsed; if let Some(i) = *index { dest[i] = Property::Unparsed(unparsed); } else { @@ -161,10 +150,7 @@ impl FallbackHandler { _dest: &mut css::DeclarationList, _context: &mut css::PropertyHandlerContext, ) { - // Zig: inline for (std.meta.fields(FallbackHandler)) |f| @field(this, f.name) = null; self.color = None; self.text_shadow = None; } } - -// ported from: src/css/properties/prefix_handler.zig diff --git a/src/css/properties/properties_generated.rs b/src/css/properties/properties_generated.rs index 9777c271783..11121aae21f 100644 --- a/src/css/properties/properties_generated.rs +++ b/src/css/properties/properties_generated.rs @@ -1,9 +1,9 @@ -// This file is autogenerated by generate_properties.ts. DO NOT EDIT! -// (Rust emitter: generated from src/css/properties/properties_generated.zig) +// Hand-maintained. Note: generate_properties.ts (`bun run css-properties`) +// does not write this file — edits here are safe and will not be clobbered. // -// PORT NOTE: type paths below resolve against `super::*` (the leaf property +// Type paths below resolve against `super::*` (the leaf property // modules — currently data-only stub bodies in `mod.rs`) and -// `crate::css_values`, matching the Zig prelude in properties_generated.zig. +// `crate::css_values`. #![allow(non_camel_case_types)] @@ -41,7 +41,7 @@ use super::transform; use super::transition; use super::ui; -/// Discriminant-only tag for [`Property`] / [`PropertyId`] (Zig: `enum(u16)`). +/// Discriminant-only tag for [`Property`] / [`PropertyId`]. #[repr(u16)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum PropertyIdTag { @@ -298,7 +298,7 @@ pub enum PropertyIdTag { impl PropertyIdTag { /// Whether the corresponding `Property` payload carries a `VendorPrefix` - /// (i.e. is a `(T, VendorPrefix)` tuple in the Zig union). + /// (i.e. is a `(T, VendorPrefix)` tuple). pub const fn has_vendor_prefix(self) -> bool { matches!( self, @@ -423,7 +423,7 @@ impl PropertyIdTag { }) } - /// Kebab-case CSS property name (Zig: `@tagName(PropertyIdTag)`). + /// Kebab-case CSS property name. pub const fn name(self) -> &'static [u8] { match self { PropertyIdTag::BackgroundColor => b"background-color", @@ -682,8 +682,8 @@ impl PropertyIdTag { /// A known CSS property name + (for prefixable properties) the vendor /// prefix it was parsed with. Variants without payload are unprefixed. // -// PORT NOTE: do NOT `#[derive(PartialEq, Eq)]` here — the spec-correct -// equality (Zig `PropertyId.eql`, properties_generated.zig:9195) ignores the +// Do NOT `#[derive(PartialEq, Eq)]` here — the spec-correct +// equality ignores the // `Custom(CustomPropertyName)` payload and is hand-written below. A derived // impl would (a) conflict (E0119) and (b) diverge by comparing custom-name // bytes. @@ -940,15 +940,14 @@ pub enum PropertyId { Custom(CustomPropertyName), } -// PORT NOTE: Zig `PropertyId.eql()` (properties_generated.zig:9195) compares the -// tag, then *only* compares the payload when its type is `VendorPrefix` — for -// `.custom` (whose payload is `CustomPropertyName`) and all unit/void variants -// it returns `true` on tag match alone. A derived `PartialEq` would compare the -// `CustomPropertyName` bytes, diverging from the spec (observable in -// `rules/style.zig:isDuplicate`). `prefix()` already returns the `VendorPrefix` -// payload for the 65 prefixed variants and `VendorPrefix::empty()` for every -// other variant (including `Custom`/`All`/`Unparsed`), so `tag` + `prefix` -// equality is exactly the Zig semantics. +// `PropertyId` equality compares the tag, then *only* compares the payload +// when its type is `VendorPrefix` — for `Custom` (whose payload is +// `CustomPropertyName`) and all unit variants it returns `true` on tag match +// alone. A derived `PartialEq` would compare the `CustomPropertyName` bytes, +// diverging from the duplicate-detection semantics in `rules/style.rs`. +// `prefix()` already returns the `VendorPrefix` payload for the 65 prefixed +// variants and `VendorPrefix::empty()` for every other variant (including +// `Custom`/`All`/`Unparsed`), so `tag` + `prefix` equality is exactly that. impl PartialEq for PropertyId { #[inline] fn eq(&self, other: &Self) -> bool { @@ -958,8 +957,8 @@ impl PartialEq for PropertyId { impl Eq for PropertyId {} -// PORT NOTE: Zig `PropertyId.hash()` (properties_generated.zig:9209) hashes only -// the tag discriminant so PropertyId can key a hash map consistent with `eql`. +// Hash only the tag discriminant so PropertyId can key a hash map +// consistent with `eq`. impl core::hash::Hash for PropertyId { #[inline] fn hash(&self, h: &mut H) { @@ -1223,9 +1222,6 @@ impl PropertyId { } /// Returns the property name, without any vendor prefixes. - /// - /// Mirrors Zig `PropertyId.name()` (properties_generated.zig:7674), which is - /// literally `if (.custom) return custom.asStr(); return @tagName(this.*);`. pub fn name(&self) -> &[u8] { match self { PropertyId::Custom(c) => c.as_str(), @@ -1347,8 +1343,8 @@ impl PropertyId { /// isn't allowed for that property. pub fn from_name_and_prefix(name: &[u8], pre: VendorPrefix) -> Option { use bun_core::strings; - // PORT NOTE: Zig used a comptime perfect-hash map; linear scan here is - // a placeholder — replace with a phf / match-on-len when un-gated. + // PERF: the linear scan here is correct but slow — a phf / + // match-on-len would be an optimization. if strings::eql_case_insensitive_ascii_check_length(name, b"background-color") { let allowed: VendorPrefix = VendorPrefix::NONE; if allowed.intersects(pre) { @@ -3133,7 +3129,7 @@ impl PropertyId { /// A parsed CSS declaration value, tagged by [`PropertyIdTag`]. Prefixed /// properties carry `(value, VendorPrefix)`. -// PORT NOTE: no `#[derive(Clone)]` — several `css_values::*` payloads +// No `#[derive(Clone)]` — several `css_values::*` payloads // (Image, Size2D, Rect, SmallList) intentionally lack `Clone` and use // `deep_clone(&Arena)` instead. `Property::deep_clone` is the public API. pub enum Property { @@ -6194,21 +6190,13 @@ impl Property { /// Returns the given longhand property for a shorthand. /// - /// PORT NOTE: in Zig (`properties_generated.zig:7087-7160`) each arm - /// dispatches to `v.longhand(property_id)` where the per-type `longhand` - /// is provided by `DefineShorthand`, whose body is - /// `@compileError(todo_stuff.depth)`. Zig only instantiates - /// `Property.longhand` when referenced (it isn't), so the @compileError - /// never fires. Rust type-checks eagerly, so the per-arm dispatch is - /// routed through a no-op `lh!` that mirrors the Zig fallthrough - /// (`return null`) until the `DefineShorthand` derive is ported. There - /// are no callers. - // blocked_on: shorthand_handler_port — leaf shorthand types lack `.longhand()` (Zig body is `@compileError(todo_stuff.depth)`, .zig:7087-7160) + /// Per-type `longhand` is not implemented yet, so the per-arm dispatch is + /// routed through a no-op `lh!` (`return None`) until the + /// `DefineShorthand` derive exists. There are no callers. + // blocked_on: shorthand_handler_port — leaf shorthand types lack `.longhand()` pub fn longhand(&self, property_id: &PropertyId) -> Option { #[inline(always)] fn lh(_v: &T, _id: &PropertyId) -> Option { - // PORT NOTE: per-type `v.longhand(property_id)` is - // `@compileError(todo_stuff.depth)` in Zig and never instantiated. // Trip in debug so callers can't accidentally rely on the // always-`None` placeholder before `DefineShorthand::longhand` // is ported. @@ -6303,7 +6291,7 @@ impl Property { } } - // blocked_on: leaf_value_traits — un-gate once every payload type impls `generics::DeepClone` (.zig:6307-6558) + // blocked_on: leaf_value_traits — un-gate once every payload type impls `generics::DeepClone` pub fn deep_clone(&self, arena: &bun_alloc::Arena) -> Property { match self { Property::BackgroundColor(v) => { @@ -6880,7 +6868,7 @@ impl Property { } } - // blocked_on: leaf_value_traits — un-gate once every payload type impls `generics::CssEql` (.zig:7162-7415) + // blocked_on: leaf_value_traits — un-gate once every payload type impls `generics::CssEql` pub fn eql(&self, other: &Property) -> bool { match (self, other) { (Property::BackgroundColor(a), Property::BackgroundColor(b)) => css::generic::eql(a, b), @@ -7411,15 +7399,14 @@ impl Property { } } -// PORT NOTE: `declaration::placeholder_property()` (the moved-out slot -// sentinel in `DeclarationBlock::minify`) is `Property{ .all = .revert-layer }` -// in Zig. Expose it via `Default` so the un-gated stub branch in -// `declaration.rs` keeps compiling against the real enum. +// `declaration::placeholder_property()` (the moved-out slot +// sentinel in `DeclarationBlock::minify`) is +// `Property::All(CSSWideKeyword::RevertLayer)`. Expose it via `Default` so +// the un-gated stub branch in `declaration.rs` keeps compiling against the +// real enum. impl Default for Property { #[inline] fn default() -> Self { Property::All(CSSWideKeyword::RevertLayer) } } - -// ported from: src/css/properties/properties_generated.zig diff --git a/src/css/properties/properties_impl.rs b/src/css/properties/properties_impl.rs index aa1295dd1a5..f3c05519931 100644 --- a/src/css/properties/properties_impl.rs +++ b/src/css/properties/properties_impl.rs @@ -15,8 +15,8 @@ impl Property { /// an `Unparsed` declaration always returns `PropertyIdTag::Unparsed`, and /// a `Custom` declaration always returns `PropertyIdTag::Custom`. /// - /// This mirrors Zig's `@as(PropertyIdTag, property.*)` (a raw union-tag - /// coercion). Handlers that switch on the discriminant to project a parsed + /// This is a raw discriminant lookup. + /// Handlers that switch on the discriminant to project a parsed /// payload — e.g. `SizeHandler` in `margin_padding.rs` — must use this so /// an unparsed `margin-top: var(--x)` does not route into the parsed /// `MarginTop` arm and panic in `extract_top`. @@ -32,10 +32,10 @@ impl Property { } } -/// Ordered single-bit prefix flags for the `inline for (VendorPrefix.FIELDS)` -/// Zig idiom. The crate-root `VendorPrefix::FIELDS` is a `&[&str]` name list; -/// the to_css loops here need the bitflag values directly, in Zig declaration -/// order (webkit, moz, ms, o, none). +/// Ordered single-bit prefix flags. The crate-root `VendorPrefix::FIELDS` is +/// a `&'static [VendorPrefix]` with the same values in the same declaration +/// order (webkit, moz, ms, o, none); kept duplicated here as a fixed-size +/// array for the to_css loops. pub(super) const PREFIX_FLAGS: [VendorPrefix; 5] = [ VendorPrefix::WEBKIT, VendorPrefix::MOZ, @@ -51,9 +51,8 @@ pub(super) mod property_id_mixin { let name = this.name(); let prefix_value = this.prefix().or_none(); - // PORT NOTE: Zig `inline for (VendorPrefix.FIELDS) |field|` + `@field` iterates each - // bitflag field and tests it. `PREFIX_FLAGS` is the same set in the same order; - // `contains` replaces the `@field` test. + // `PREFIX_FLAGS` matches `VendorPrefix::FIELDS` — same set, same + // order; serialization order of prefixed names depends on it. dest.write_comma_separated( PREFIX_FLAGS .iter() @@ -67,8 +66,6 @@ pub(super) mod property_id_mixin { } pub(crate) fn parse(input: &mut css::Parser) -> css::Result { - // PORT NOTE: `css::Result` is assumed to alias `Result`; - // the Zig `.result`/`.err` switch collapses to `?`. let name = input.expect_ident()?; Ok(from_string(name)) } @@ -101,7 +98,6 @@ pub(super) mod property_mixin { } let (name, prefix) = this.__to_css_helper(); - // PORT NOTE: see property_id_mixin::to_css for the `inline for` + `@field` mapping. dest.write_separated( PREFIX_FLAGS.iter().copied().filter(|p| prefix.contains(*p)), |d| { @@ -122,5 +118,3 @@ pub(super) mod property_mixin { ) } } - -// ported from: src/css/properties/properties_impl.zig diff --git a/src/css/properties/shape.rs b/src/css/properties/shape.rs index 17615dce199..e184b566492 100644 --- a/src/css/properties/shape.rs +++ b/src/css/properties/shape.rs @@ -4,9 +4,7 @@ pub use crate::css_parser as css; /// determine the interior of a `polygon()` shape. /// /// See [Polygon](Polygon). -// TODO(port): Zig source is `css.DefineEnumProperty(@compileError(css.todo_stuff.depth))` — -// a placeholder that compile-errors on use. Left as an uninhabited stub until the -// CSS shapes module is actually implemented. +// Uninhabited placeholder until the CSS shapes module is actually implemented. pub enum FillRule {} /// A CSS [``](https://www.w3.org/TR/css-color-4/#typedef-alpha-value), @@ -16,5 +14,3 @@ pub enum FillRule {} pub struct AlphaValue { pub v: f32, } - -// ported from: src/css/properties/shape.zig diff --git a/src/css/properties/size.rs b/src/css/properties/size.rs index 70fa24529e1..828c5f73c90 100644 --- a/src/css/properties/size.rs +++ b/src/css/properties/size.rs @@ -25,9 +25,8 @@ pub enum BoxSizing { /// Include the padding and border (but not the margin) in the width and height. BorderBox, } -// PORT NOTE: css::DefineEnumProperty(@This()) — provided eql/hash/parse/toCss/deepClone via -// comptime reflection over @tagName. Hand-written here (only two variants) so the inherent -// `parse`/`to_css` participate in `impl_parse_tocss_via_inherent!` without a derive-coherence clash. +// Hand-written (only two variants) so the inherent `parse`/`to_css` participate in +// `impl_parse_tocss_via_inherent!` without a derive-coherence clash. impl BoxSizing { pub(crate) fn parse(input: &mut css::Parser) -> css::Result { let location = input.current_source_location(); @@ -70,9 +69,8 @@ pub enum Size { } /// Case-insensitive keyword dispatch for `Size`/`MaxSize` parse bodies. -/// PORT NOTE: Zig used `bun.ComptimeStringMap(..).getASCIIICaseInsensitive` — -/// expanded as an `if`-chain over `eql_case_insensitive_ascii::` (≤14 keys; -/// per PORTING.md a phf table is overkill at this size). +/// An `if`-chain over `eql_case_insensitive_ascii::` (≤14 keys; +/// a phf table is overkill at this size). macro_rules! size_ident_match { ($ident:expr, { $($lit:literal => $val:expr,)+ } else $err:expr) => {{ let __ident: &[u8] = $ident; @@ -153,7 +151,7 @@ impl Size { } } -// PORT NOTE: split out of `impl Size` above — these don't depend on +// Split out of `impl Size` above — these don't depend on // `parse`/`to_css` surface and are needed by `SizeHandler`. impl Size { pub(crate) fn is_compatible(&self, browsers: &css::targets::Browsers) -> bool { @@ -184,13 +182,11 @@ impl Size { } pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // TODO(port): css.implementDeepClone — comptime field-walk; `Size` carries - // only `LengthPercentage`/`VendorPrefix` payloads, both `Clone`-via-derive. + // `Size` carries only `LengthPercentage`/`VendorPrefix` payloads, both `Clone`-via-derive. self.clone() } pub(crate) fn eql(lhs: &Self, rhs: &Self) -> bool { - // TODO(port): css.implementEql — comptime field-walk; #[derive(PartialEq)] above covers it. lhs == rhs } } @@ -291,7 +287,7 @@ impl MaxSize { } } -// PORT NOTE: split out of `impl MaxSize` above — these don't depend on +// Split out of `impl MaxSize` above — these don't depend on // `parse`/`to_css` surface and are needed by `SizeHandler`. impl MaxSize { pub(crate) fn is_compatible(&self, browsers: &css::targets::Browsers) -> bool { @@ -322,8 +318,7 @@ impl MaxSize { } pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // TODO(port): css.implementDeepClone — comptime field-walk; `MaxSize` carries - // only `LengthPercentage`/`VendorPrefix` payloads, both `Clone`-via-derive. + // `MaxSize` carries only `LengthPercentage`/`VendorPrefix` payloads, both `Clone`-via-derive. self.clone() } @@ -375,7 +370,7 @@ impl AspectRatio { } pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // PORT NOTE: css.implementDeepClone — `Ratio` is two `f32`s; #[derive(Clone)] is exact. + // `Ratio` is two `f32`s; #[derive(Clone)] is exact. *self } @@ -410,8 +405,6 @@ bitflags::bitflags! { impl SizeProperty { pub(crate) fn try_from_property_id_tag(property_id: PropertyIdTag) -> Option { - // TODO(port): Zig used `inline for (std.meta.fields(@This()))` to compare each - // bitfield name against PropertyIdTag's @tagName. Expanded explicitly here. match property_id { PropertyIdTag::Width => Some(SizeProperty::WIDTH), PropertyIdTag::Height => Some(SizeProperty::HEIGHT), @@ -449,17 +442,11 @@ pub struct SizeHandler { pub category: PropertyCategory, } -// PORT NOTE: `context.arena` was dropped from PropertyHandlerContext; the +// `context.arena` was dropped from PropertyHandlerContext; the // arena is recovered via `dest.bump()`. use css::compat::Feature; -// ─── helper macros (Zig used `inline fn` + `comptime []const u8` field names + @field/@unionInit) ─── -// -// TODO(port): the following four macros replace Zig's `propertyHelper`, `logicalUnparsedHelper`, -// `flushPrefixHelper`, `flushPropertyHelper`, `flushLogicalHelper`. The Zig code passes field -// names as comptime strings and uses @field/@unionInit/@tagName to splice them into struct/enum -// accesses. Rust has no equivalent reflection — macro_rules! is the closest 1:1 mapping. -// PERF(port): was comptime monomorphization. +// ─── helper macros ─── macro_rules! property_helper { ($this:expr, $field:ident, $ty:ty, $value:expr, $category:expr, $dest:expr, $context:expr) => {{ @@ -487,8 +474,7 @@ macro_rules! logical_unparsed_helper { $this.flushed_properties.insert( SizeProperty::try_from_property_id_tag($unparsed.property_id.tag()).unwrap(), ); - // PORT NOTE: Zig pushed `property.deepClone(arena)`; the matched - // payload is `Unparsed`, so reconstruct directly. + // The matched payload is `Unparsed`, so reconstruct directly. $dest.push(Property::Unparsed($unparsed.deep_clone(bump))); } else { $dest.push(Property::Unparsed( @@ -506,7 +492,7 @@ macro_rules! flush_prefix_helper { .targets .prefixes(VendorPrefix::NONE, css::prefixes::Feature::$feature) .difference(VendorPrefix::NONE); - // TODO(port): `inline for (css.VendorPrefix.FIELDS)` — iterate set bits. + // Iterate set bits. for prefix in prefixes.iter() { $dest.push(Property::$prop_variant($size_ty::$size_variant(prefix))); } @@ -966,5 +952,3 @@ impl SizeHandler { self.flushed_properties = SizeProperty::empty(); } } - -// ported from: src/css/properties/size.zig diff --git a/src/css/properties/svg.rs b/src/css/properties/svg.rs index ad3c0402af0..8b137891791 100644 --- a/src/css/properties/svg.rs +++ b/src/css/properties/svg.rs @@ -1 +1 @@ -// ported from: src/css/properties/svg.zig + diff --git a/src/css/properties/text.rs b/src/css/properties/text.rs index 0e737247e74..2e008a39479 100644 --- a/src/css/properties/text.rs +++ b/src/css/properties/text.rs @@ -126,13 +126,11 @@ impl TextShadow { && self.spread.is_compatible(browsers) } - // Zig: `pub fn eql` via `css.implementEql(@This(), ...)` — field-wise equality. - // Ported as `#[derive(PartialEq)]` above; callers use `==`. - pub(crate) fn deep_clone(&self, alloc: &bun_alloc::Arena) -> Self { - // TODO(port): Zig used reflection-based `css.implementDeepClone`. Fields here - // are value types, so a plain Clone is equivalent; arena param retained for - // signature compatibility with the CSS deep_clone protocol. + // Fields deep-clone via `#[derive(Clone)]` (`CssColor`/`Length` + // `Box`-carrying variants clone deeply onto the global heap), so a + // plain Clone is equivalent; arena param retained for signature + // compatibility with the CSS deep_clone protocol. let _ = alloc; self.clone() } @@ -149,7 +147,6 @@ impl css::generics::IsCompatible for TextShadow { } /// A value for the [direction](https://drafts.csswg.org/css-writing-modes-3/#direction) property. -// Zig wires eql/hash/parse/toCss/deepClone via `css.DefineEnumProperty(@This())`. #[derive( Debug, Clone, Copy, PartialEq, Eq, Hash, crate::DefineEnumProperty, crate::generics::CssHash, )] @@ -159,5 +156,3 @@ pub enum Direction { /// This value sets inline base direction (bidi directionality) to line-right-to-line-left. Rtl, } - -// ported from: src/css/properties/text.zig diff --git a/src/css/properties/transform.rs b/src/css/properties/transform.rs index 0ddb169af13..61852727f7a 100644 --- a/src/css/properties/transform.rs +++ b/src/css/properties/transform.rs @@ -13,21 +13,22 @@ use crate::{ }; /// A value for the [transform](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#propdef-transform) property. -// PORT NOTE: was `BumpVec<'bump, Transform>`; downgraded to `Vec` so the -// `Property` enum (properties_generated.rs) stays lifetime-free. Re-thread -// `'bump` through `Property<'a>` crate-wide in a later pass (see line :1096). +// Was `BumpVec<'bump, Transform>` in an earlier port iteration; uses `Vec` so +// the `Property` enum (properties_generated.rs) stays lifetime-free. +// Re-threading `'bump` through `Property<'a>` crate-wide is deferred work +// (see /tmp/todo-fix/complex/mk04-repair.md; also TransformHandler below). #[derive(Clone, PartialEq, Default)] pub struct TransformList { pub v: Vec, } -// PORT NOTE: split out of the parse/to_css `impl TransformList` below — +// Split out of the parse/to_css `impl TransformList` below — // `TransformHandler` only needs deep_clone/eql. impl TransformList { pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // TODO(port): css.implementDeepClone reflection — `Transform`/`TransformList` - // are `Clone`-via-derive (Vec + POD payloads); an arena-aware DeepClone - // trait should land crate-wide. + // `Transform`/`TransformList` deep-clone via `#[derive(Clone)]`: payloads + // with heap variants (`LengthPercentage`/`Length` `Calc(Box<..>)`) clone + // deeply onto the global heap. self.clone() } } @@ -63,10 +64,7 @@ impl TransformList { // TODO: Re-enable with a better solution // See: https://github.com/parcel-bundler/lightningcss/issues/288 - // PORT NOTE: Zig's minify branch built a sub-`Printer` writing into a temp - // buffer then `dest.writeStr(base)` — observably identical to writing - // directly into `dest` while `dest.minify` is set (the original - // lightningcss size-comparison was already disabled upstream). Collapsed. + // (The original lightningcss size-comparison was already disabled upstream.) self.to_css_base(dest) } @@ -148,8 +146,6 @@ impl Transform { pub(crate) fn parse(input: &mut Parser) -> Result { let function = input.expect_function_cloned()?; - // PORT NOTE: Zig used a Closure struct + nested anon-struct fn passed to - // parseNestedBlock; Rust closures capture `function` directly. input.parse_nested_block(|i| -> Result { let location = i.current_source_location(); crate::match_ignore_ascii_case! { function, { @@ -241,8 +237,7 @@ impl Transform { let y = NumberOrPercentage::parse(i)?; Ok(Transform::Scale { x, y }) } else { - // PORT NOTE: Zig `x.deepClone(arena)` — `NumberOrPercentage` - // is POD; `clone()` is exact. + // `NumberOrPercentage` is POD. let y = x.clone(); Ok(Transform::Scale { x, y }) } @@ -574,7 +569,9 @@ impl Transform { } pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // TODO(port): css.implementDeepClone reflection — payload types may need bump-aware clone + // All payload types deep-clone via `#[derive(Clone)]` — `Calc` variants + // of `LengthPercentage`/`Length` are `Box`ed and clone deeply onto the + // global heap. self.clone() } } @@ -612,8 +609,6 @@ pub struct Matrix3d { } /// A value for the [transform-style](https://drafts.csswg.org/css-transforms-2/#transform-style-property) property. -// TODO(port): css.DefineEnumProperty reflection → crate-wide #[derive(EnumProperty)] providing -// parse/to_css/eql/hash/deep_clone from kebab-case variant names. #[derive(Clone, Copy, PartialEq, Eq, Hash, crate::DefineEnumProperty)] pub enum TransformStyle { #[css("flat")] @@ -726,8 +721,8 @@ impl Translate { } } -// PORT NOTE: split out of the parse/to_css `impl Translate` above — these -// don't depend on Parser/Printer surface and are needed by `TransformHandler`. +// Split out of the parse/to_css `impl Translate` above — these don't depend +// on Parser/Printer surface and are needed by `TransformHandler`. impl Translate { pub(crate) fn to_transform(&self, _bump: &Bump) -> Transform { match self { @@ -745,7 +740,7 @@ impl Translate { } pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // TODO(port): css.implementDeepClone — arena-aware clone for LengthPercentage + // `LengthPercentage` is `Clone`-via-derive; a plain clone suffices. self.clone() } } @@ -846,7 +841,7 @@ impl Rotate { } } -// PORT NOTE: split out of the parse/to_css `impl Rotate` above — needed by +// Split out of the parse/to_css `impl Rotate` above — needed by // `TransformHandler`. impl Rotate { /// Converts the rotation to a transform function. @@ -930,7 +925,7 @@ impl Scale { } } -// PORT NOTE: split out of the parse/to_css `impl Scale` above — needed by +// Split out of the parse/to_css `impl Scale` above — needed by // `TransformHandler`. impl Scale { pub(crate) fn to_transform(&self, _bump: &Bump) -> Transform { @@ -962,9 +957,10 @@ crate::css_eql_partialeq!( Scale ); -// PORT NOTE: was `TransformHandler<'bump>` holding `TransformList<'bump>`; the -// `Property` enum is lifetime-free (see TransformList above), so the handler -// is too. Re-thread `'bump` crate-wide in a later pass. +// Was `TransformHandler<'bump>` holding `TransformList<'bump>` in an earlier +// port iteration; the `Property` enum is lifetime-free (see TransformList +// above), so the handler is too. Re-threading `'bump` crate-wide is deferred +// work (see /tmp/todo-fix/complex/mk04-repair.md). #[derive(Default)] pub struct TransformHandler { pub transform: Option<(TransformList, VendorPrefix)>, @@ -974,8 +970,8 @@ pub struct TransformHandler { pub has_any: bool, } -// PORT NOTE: `context.arena` was dropped from PropertyHandlerContext, so the -// arena is recovered via `dest.bump()` (DeclarationList = bumpalo::Vec). +// `context.arena` does not exist on PropertyHandlerContext; the arena is +// recovered via `dest.bump()` (DeclarationList = bumpalo::Vec). impl TransformHandler { pub(crate) fn handle_property( &mut self, @@ -984,7 +980,6 @@ impl TransformHandler { context: &mut PropertyHandlerContext, ) -> bool { let bump = dest.bump(); - // PORT NOTE: Zig used a local fn with `comptime field: []const u8` + `@field(self, field)`. // Rust cannot index struct fields by string at runtime; use a macro to paste the ident. macro_rules! individual_property { ($field:ident, $val:expr) => {{ @@ -1004,8 +999,8 @@ impl TransformHandler { // If two vendor prefixes for the same property have different // values, we need to flush what we have immediately to preserve order. - // PORT NOTE: reshaped for borrowck — Zig held &self.transform across - // self.flush(); compute the predicate first, then act. + // Compute the predicate first, then act, so no borrow of + // self.transform is held across self.flush(). let needs_flush = if let Some(current) = &self.transform { current.0 != *transform_val && !current.1.contains(vp) } else { @@ -1046,8 +1041,7 @@ impl TransformHandler { prefixes::Feature::Transform, )) } else { - // PORT NOTE: Zig pushed `property.deepClone(arena)`; the - // matched payload is `Unparsed`, so reconstruct directly. + // The matched payload is `Unparsed`, so reconstruct directly. Property::Unparsed(unparsed.deep_clone(bump)) }; dest.push(prop); @@ -1099,5 +1093,3 @@ impl TransformHandler { } } } - -// ported from: src/css/properties/transform.zig diff --git a/src/css/properties/transition.rs b/src/css/properties/transition.rs index 10e61d8d012..0b38853346a 100644 --- a/src/css/properties/transition.rs +++ b/src/css/properties/transition.rs @@ -33,14 +33,13 @@ pub struct Transition { impl Transition { pub(crate) fn eql(&self, rhs: &Self) -> bool { - // Zig: css.implementEql(@This(), lhs, rhs) — field-by-field reflection. self == rhs } pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // Zig: css.implementDeepClone(@This(), this, arena) — field-by-field - // reflection. All four fields are POD/Copy or `Clone`-via-derive with no - // arena indirections; #[derive(Clone)] is exact. + // All four fields deep-clone via `#[derive(Clone)]` with no + // arena indirections (any heap-carrying variants of `PropertyId`/ + // `EasingFunction` clone deeply onto the global heap), so Clone is exact. self.clone() } @@ -119,11 +118,9 @@ pub struct TransitionHandler { pub has_any: bool, } -// PORT NOTE: Zig's `property`/`maybeFlush` took `comptime prop: []const u8` and used -// `@field(this, prop)` + `val: anytype` for comptime field dispatch. Rust has no -// `@field`, and passing both `&mut self` and `&mut self.` to a generic fn -// trips borrowck (because `flush` needs `&mut self`). Macros expand at the call -// site exactly like the Zig comptime dispatch did. +// Passing both `&mut self` and `&mut self.` to a generic fn trips +// borrowck (because `flush` needs `&mut self`), so macros expand the field +// dispatch at the call site instead. macro_rules! handler_maybe_flush { ($this:expr, $dest:expr, $context:expr, $field:ident, $val:expr, $vp:expr) => {{ // If two vendor prefixes for the same property have different @@ -166,8 +163,8 @@ mod transition_handler_body { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) -> bool { - // PORT NOTE: `arena` field dropped from PropertyHandlerContext; the - // arena is recovered via `dest.bump()` (DeclarationList = bumpalo::Vec). + // PropertyHandlerContext carries no arena; it is recovered via + // `dest.bump()` (DeclarationList = bumpalo::Vec). let arena = dest.bump(); match prop { Property::TransitionProperty(x) => { @@ -480,29 +477,17 @@ mod transition_handler_body { delay, timing_function, }; - let mut cloned = false; - let prefix_to_iter = property_id.prefix().or_none(); // Expand vendor prefixes into multiple transitions. - // PORT NOTE: Zig used `inline for (VendorPrefix.FIELDS)` over packed-struct - // bool fields. With bitflags, iterate the individual flag bits. - // PERF(port): was comptime-unrolled inline-for — profile if it shows up on a hot path. + // Cloning for every iteration costs at most one extra clone of a + // small value. for &prefix_flag in VendorPrefix::FIELDS { if prefix_to_iter.contains(prefix_flag) { - let mut t = if cloned { - transition.deep_clone(arena) - } else { - // TODO(port): Zig moved `transition` here on first iteration; Rust - // can't move out of a value that may be reused next iteration. - // Clone unconditionally for now. - transition.deep_clone(arena) - }; - cloned = true; + let mut t = transition.deep_clone(arena); t.property = property_id.with_prefix(prefix_flag); transitions.append(t); } } - let _ = cloned; } transitions } @@ -830,6 +815,4 @@ mod transition_handler_body { | PropertyId::Transition(..) ) } - - // ported from: src/css/properties/transition.zig } // mod transition_handler_body diff --git a/src/css/properties/ui.rs b/src/css/properties/ui.rs index 900222e9617..5272f703740 100644 --- a/src/css/properties/ui.rs +++ b/src/css/properties/ui.rs @@ -18,7 +18,6 @@ bitflags::bitflags! { const DARK = 1 << 1; /// Forbids the user agent from overriding the color scheme for the element. const ONLY = 1 << 2; - // Zig: __unused: u5 = 0 (padding — bitflags handles this implicitly) } } @@ -83,8 +82,7 @@ impl ColorScheme { } } -// Zig: `const Map = bun.ComptimeEnumMap(enum { normal, only, light, dark });` -// ≤8 entries → plain match on bytes (per PORTING.md). +// ≤8 entries → plain match on bytes. #[derive(Clone, Copy)] enum ColorSchemeKeyword { Normal, @@ -104,15 +102,13 @@ fn color_scheme_map_get(ident: &[u8]) -> Option { } /// A value for the [resize](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#resize) property. -// TODO(port): Zig source is `css.DefineEnumProperty(@compileError(css.todo_stuff.depth))` — intentionally unimplemented upstream. +// Intentionally unimplemented upstream, so this stays a unit placeholder. pub struct Resize; #[derive(Default)] pub struct ColorSchemeHandler; -// PORT NOTE: `context.arena` was dropped from PropertyHandlerContext; -// `define_var` no longer needs an arena because `TokenList.v` is a std -// `Vec` (LIFETIMES.tsv classification). +// `define_var` needs no arena because `TokenList.v` is a std `Vec`. impl ColorSchemeHandler { pub(crate) fn handle_property( &mut self, @@ -146,8 +142,7 @@ impl ColorSchemeHandler { dest.push(define_var(b"--buncss-dark", css::Token::Ident(b"initial"))); } } - // PORT NOTE: Zig pushed `property.deepClone(arena)`; ColorScheme is - // `Copy` (bitflags u8), so reconstruct the variant directly. + // ColorScheme is `Copy` (bitflags u8), so reconstruct the variant directly. dest.push(Property::ColorScheme(color_scheme)); true } @@ -164,9 +159,9 @@ impl ColorSchemeHandler { } fn define_var(name: &'static [u8], value: css::Token) -> Property { - // PORT NOTE: `name` is `&'static [u8]` because all call sites pass byte-string literals. + // `name` is `&'static [u8]` because all call sites pass byte-string literals. // `TokenList.v` is `Vec` (std Vec — see custom.rs:320), so no arena - // threading is needed here despite Zig's `ArrayList(TokenOrValue)`. + // threading is needed here. Property::Custom(css::css_properties::custom::CustomProperty { name: css::css_properties::custom::CustomPropertyName::Custom(DashedIdent { v: name }), value: css::TokenList { @@ -174,5 +169,3 @@ fn define_var(name: &'static [u8], value: css::Token) -> Property { }, }) } - -// ported from: src/css/properties/ui.zig diff --git a/src/css/rules/container.rs b/src/css/rules/container.rs index f97c5e1d3f4..17de7df9b96 100644 --- a/src/css/rules/container.rs +++ b/src/css/rules/container.rs @@ -19,8 +19,6 @@ impl ContainerName { impl ContainerName { #[inline] pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk — `CustomIdent` - // identity-copy (arena-owned slice pointer). Self { v: self.v.deep_clone(bump), } @@ -67,11 +65,9 @@ pub enum ContainerSizeFeatureId { // `QueryFeature` requires `FeatureId: FeatureIdTrait` at the type // level, so this impl must be present for `ContainerSizeFeature` to resolve. -// `value_type` inlines the Zig `DeriveValueType` reflection; `to_css`/`from_str` -// delegate to `enum_property_util` (driven by the `EnumProperty` derive). +// `to_css`/`from_str` delegate to `enum_property_util` (driven by the +// `EnumProperty` derive). impl crate::media_query::FeatureIdTrait for ContainerSizeFeatureId { - // Zig: pub const valueType = css.DeriveValueType(@This(), ValueTypeMap).valueType; - // PORT NOTE: DeriveValueType is comptime reflection over ValueTypeMap; expanded inline. fn value_type(&self) -> MediaFeatureType { match self { Self::Width => MediaFeatureType::Length, @@ -105,8 +101,7 @@ pub enum StyleQuery { /// The operator for the conditions. operator: Operator, /// The conditions for the operator. - // PERF(port): was ArrayListUnmanaged fed input.arena() (parser arena); - // could use bun_alloc::ArenaVec<'bump, _> instead of global Vec — profile if hot. + // PERF: could use bun_alloc::ArenaVec<'bump, _> instead of global Vec — profile if hot. conditions: Vec, }, } @@ -153,8 +148,8 @@ impl QueryCondition for StyleQuery { let property_id = crate::properties::PropertyId::parse(input)?; input.expect_colon()?; input.skip_whitespace(); - // PORT NOTE: Zig threaded `(input.arena(), null)` here; re-thread - // `&Bump` once `ParserOptions` carries the arena. + // The arena gets re-threaded as part of the crate-wide `'bump` + // lifetime work (see css_parser.rs). let opts = css::ParserOptions::default(None); let feature = StyleQuery::Feature(Box::new(Property::parse(property_id, input, &opts)?)); let _ = input.try_parse(css::css_parser::parse_important); @@ -170,7 +165,6 @@ impl QueryCondition for StyleQuery { } } fn parse_style_query(input: &mut css::Parser) -> css::Result { - // Zig: `return .{ .err = input.newErrorForNextToken() }` Err(input.new_error_for_next_token()) } fn needs_parens(&self, parent_operator: Option, _targets: &css::Targets) -> bool { @@ -184,9 +178,8 @@ impl QueryCondition for StyleQuery { impl StyleQuery { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` variant-walk. `Operator` is `Copy`; - // `Property` routes through `dc::property` until the per-variant - // `DeepClone` derives land in `properties_generated.rs`. + // `Operator` is `Copy`; `Property` routes through `dc::property` until + // the per-variant `DeepClone` derives land in `properties_generated.rs`. match self { Self::Feature(p) => Self::Feature(Box::new(super::dc::property(p, bump))), Self::Not(c) => Self::Not(Box::new(c.deep_clone(bump))), @@ -211,8 +204,7 @@ pub enum ContainerCondition { /// The operator for the conditions. operator: Operator, /// The conditions for the operator. - // PERF(port): was ArrayListUnmanaged fed input.arena() (parser arena); - // could use bun_alloc::ArenaVec<'bump, _> instead of global Vec — profile if hot. + // PERF: could use bun_alloc::ArenaVec<'bump, _> instead of global Vec — profile if hot. conditions: Vec, }, /// A style query. @@ -283,7 +275,6 @@ impl QueryCondition for ContainerCondition { } fn parse_style_query(input: &mut css::Parser) -> css::Result { use crate::media_query::QueryConditionFlags; - // Zig defined a local `Fns` struct with two callbacks; in Rust pass closures. input.parse_nested_block(|i| { if let Ok(res) = i.try_parse(|i2| { media_query::parse_query_condition::(i2, QueryConditionFlags::ALLOW_OR) @@ -305,9 +296,8 @@ impl QueryCondition for ContainerCondition { impl ContainerCondition { pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` variant-walk. `QueryFeature` - // routes through `dc::query_feature` (Clone is faithful — see note - // there); `Operator` is `Copy`. + // `QueryFeature` routes through `dc::query_feature` (Clone is + // faithful — see note there); `Operator` is `Copy`. match self { Self::Feature(f) => Self::Feature(Box::new(super::dc::query_feature(f, bump))), Self::Not(c) => Self::Not(Box::new(c.deep_clone(bump))), @@ -359,7 +349,6 @@ impl ContainerRule { // Don't downlevel range syntax in container queries. let exclude = dest.targets.exclude; - // Zig: bun.bits.insert(css.targets.Features, &dest.targets.exclude, .media_queries); dest.targets.exclude.insert(css::Features::MEDIA_QUERIES); self.condition.to_css(dest)?; dest.targets.exclude = exclude; @@ -376,7 +365,6 @@ impl ContainerRule { where R: css::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { name: self.name.as_ref().map(|n| n.deep_clone(bump)), condition: self.condition.deep_clone(bump), @@ -385,5 +373,3 @@ impl ContainerRule { } } } - -// ported from: src/css/rules/container.zig diff --git a/src/css/rules/counter_style.rs b/src/css/rules/counter_style.rs index 9f4ea2e2a77..789a3555aa9 100644 --- a/src/css/rules/counter_style.rs +++ b/src/css/rules/counter_style.rs @@ -7,8 +7,8 @@ pub struct CounterStyleRule { /// The name of the counter style to declare. pub name: CustomIdent, /// Declarations in the `@counter-style` rule. - // PORT NOTE: `DeclarationBlock<'bump>` borrows the parser arena; lifetime - // erased to `'static` here per the rules/mod.rs `CssRule` PORT NOTE. + // `DeclarationBlock<'bump>` borrows the parser arena; lifetime erased to + // `'static` here per the rules/mod.rs `CssRule` lifetime-erasure note. pub declarations: DeclarationBlock<'static>, /// The location of the rule in the source file. pub loc: Location, @@ -27,7 +27,6 @@ impl CounterStyleRule { impl CounterStyleRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { name: self.name.deep_clone(bump), declarations: super::dc::decl_block_static(&self.declarations, bump), @@ -35,5 +34,3 @@ impl CounterStyleRule { } } } - -// ported from: src/css/rules/counter_style.zig diff --git a/src/css/rules/custom_media.rs b/src/css/rules/custom_media.rs index 07b63c6902c..73393ec924a 100644 --- a/src/css/rules/custom_media.rs +++ b/src/css/rules/custom_media.rs @@ -34,5 +34,3 @@ impl CustomMediaRule { Ok(()) } } - -// ported from: src/css/rules/custom_media.zig diff --git a/src/css/rules/document.rs b/src/css/rules/document.rs index b2949793101..8d766340523 100644 --- a/src/css/rules/document.rs +++ b/src/css/rules/document.rs @@ -29,12 +29,9 @@ impl MozDocumentRule { where R: crate::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { rules: self.rules.deep_clone(bump), loc: self.loc, } } } - -// ported from: src/css/rules/document.zig diff --git a/src/css/rules/font_face.rs b/src/css/rules/font_face.rs index f0200635845..b26d7f28e05 100644 --- a/src/css/rules/font_face.rs +++ b/src/css/rules/font_face.rs @@ -40,7 +40,6 @@ pub enum FontFaceProperty { impl FontFaceProperty { pub(crate) fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // Local helpers mirroring the Zig `Helpers.writeProperty` with `comptime multi: bool`. macro_rules! write_property_single { ($d:expr, $prop:expr, $value:expr) => {{ $d.write_str($prop)?; @@ -89,7 +88,6 @@ impl FontFaceProperty { } pub(crate) fn deep_clone(&self, arena: &bun_alloc::Arena) -> Self { - // PORT NOTE: Zig `css.implementDeepClone` field-walk, hand-expanded. match self { FontFaceProperty::Source(v) => { FontFaceProperty::Source(v.iter().map(|s| s.deep_clone(arena)).collect()) @@ -228,7 +226,7 @@ impl UnicodeRange { fn parse_tokens(input: &mut css::Parser) -> css::Result<()> { let tok = input.next_including_whitespace()?.clone(); - // TODO(port): verify exact `Token` variant shapes (Dimension/Number payloads). + // Tag-only matches on `Dimension`/`Number` — payloads are never inspected. match tok { css::Token::Dimension { .. } => return Self::parse_question_marks(input), css::Token::Number { .. } => { @@ -279,7 +277,6 @@ impl UnicodeRange { } } - // PORT NOTE: Zig `css.Maybe(UnicodeRange, void)` carries no error payload → `Option`. fn parse_concatenated(text_: &[u8]) -> Option { let mut text = if !text_.is_empty() && text_[0] == b'+' { &text_[1..] @@ -334,8 +331,8 @@ impl UnicodeRange { } fn consume_hex(text: &mut &[u8]) -> (u32, usize) { - // Cap at 8: caller validates `<= 6` post-hoc; the unbounded Zig original - // panic-overflows u32 in debug on >8 hex chars (malformed input). + // Cap at 8: caller validates `<= 6` post-hoc; an unbounded parse + // would overflow u32 on >8 hex chars (malformed input). let (value, n) = bun_core::fmt::parse_hex_prefix(text, 8); *text = &text[n..]; (value, n) @@ -426,7 +423,7 @@ pub enum FontFormat { /// An SVG font. Svg, /// An unknown format. - // PORT NOTE: arena-owned slice from parser input; TODO(refactor): thread `'i`. + // Arena-owned slice from parser input; TODO(refactor): thread `'i`. String(&'static [u8]), } @@ -464,7 +461,7 @@ impl FontFormat { } pub(crate) fn deep_clone(&self, _arena: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` variant-walk. All payloads are + // `css.implementDeepClone` variant-walk. All payloads are // `Copy` / arena-slice idents → identity copy. match self { FontFormat::Woff => FontFormat::Woff, @@ -501,7 +498,6 @@ impl Source { match input.try_parse(UrlSource::parse) { Ok(url) => return Ok(Source::Url(url)), Err(e) => { - // Zig: `e.kind == .basic and e.kind.basic == .at_rule_body_invalid` if matches!( e.kind, css::ParseErrorKind::basic(css::BasicParseErrorKind::at_rule_body_invalid) @@ -529,7 +525,7 @@ impl Source { } pub fn deep_clone(&self, arena: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` variant-walk, hand-expanded. + // `css.implementDeepClone` variant-walk, hand-expanded. match self { Source::Url(u) => Source::Url(u.deep_clone(arena)), Source::Local(l) => Source::Local(l.deep_clone(arena)), @@ -638,7 +634,7 @@ impl UrlSource { } pub(crate) fn deep_clone(&self, arena: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk, hand-expanded. + // `css.implementDeepClone` field-walk, hand-expanded. Self { url: self.url.deep_clone(arena), format: self.format.as_ref().map(|f| f.deep_clone(arena)), @@ -684,7 +680,7 @@ impl FontFaceRule { impl FontFaceRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `FontFaceProperty`'s + // `css.implementDeepClone` field-walk. `FontFaceProperty`'s // variant-walk lands when its enum body un-gates (properties::{font, // custom}); the gated stub above panics with the blocker named. Self { @@ -700,11 +696,6 @@ impl FontFaceRule { pub(crate) struct FontFaceDeclarationParser; -// PORT NOTE: Zig modeled `AtRuleParser` / `QualifiedRuleParser` / -// `DeclarationParser` / `RuleBodyItemParser` as nested namespaces with -// associated consts + fns. In Rust these are trait impls on -// `FontFaceDeclarationParser`. -// // blocked_on: css::{AtRuleParser,QualifiedRuleParser,DeclarationParser, // RuleBodyItemParser} trait signatures, properties::font::* + // properties::custom::CustomProperty, Size2D::parse, Parser surface, @@ -831,5 +822,3 @@ const _: () = { } } }; - -// ported from: src/css/rules/font_face.zig diff --git a/src/css/rules/font_palette_values.rs b/src/css/rules/font_palette_values.rs index 47093fb15f5..45258ff8c98 100644 --- a/src/css/rules/font_palette_values.rs +++ b/src/css/rules/font_palette_values.rs @@ -53,7 +53,6 @@ impl FontPaletteValuesRule { while let Some(result) = parser.next() { if let Ok(decl) = result { properties.push(decl); - // PERF(port): was `append(input.arena(), decl) catch unreachable` } } @@ -81,9 +80,9 @@ pub enum FontPaletteValuesProperty { impl FontPaletteValuesRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `FontPaletteValuesProperty`'s - // variant-walk lands when its enum body un-gates (properties::{font, - // custom}); the gated stub above panics with the blocker named. + // `FontPaletteValuesProperty`'s variant-walk lands when its enum body + // un-gates (properties::{font, custom}); the gated stub above panics + // with the blocker named. Self { name: self.name.deep_clone(bump), properties: self.properties.iter().map(|p| p.deep_clone(bump)).collect(), @@ -119,7 +118,6 @@ impl FontPaletteValuesProperty { } pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` variant-walk. match self { Self::FontFamily(f) => Self::FontFamily(f.deep_clone(bump)), Self::BasePalette(b) => Self::BasePalette(b.deep_clone(bump)), @@ -222,7 +220,6 @@ impl BasePalette { } pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` — `Copy` payload (u16). match self { Self::Light => Self::Light, Self::Dark => Self::Dark, @@ -233,9 +230,6 @@ impl BasePalette { pub(crate) struct FontPaletteValuesDeclarationParser {} -// PORT NOTE: Zig models these as nested namespace structs (`DeclarationParser`, -// `RuleBodyItemParser`, `AtRuleParser`, `QualifiedRuleParser`) duck-typed by -// `RuleBodyParser`. In Rust these are trait impls. const _: () = { use crate::css_properties::custom::{CustomProperty, CustomPropertyName}; use crate::css_properties::font::FontFamily; @@ -281,7 +275,6 @@ const _: () = { }} input.reset(&state); - // PERF(port): Zig passed `input.arena()` + `null` here. let opts = ParserOptions::default(None); let custom = CustomProperty::parse(CustomPropertyName::from_str(name), input, &opts)?; Ok(FontPaletteValuesProperty::Custom(custom)) @@ -350,5 +343,3 @@ const _: () = { } } }; - -// ported from: src/css/rules/font_palette_values.zig diff --git a/src/css/rules/import.rs b/src/css/rules/import.rs index d25a5e8b81a..9f0cf3f7180 100644 --- a/src/css/rules/import.rs +++ b/src/css/rules/import.rs @@ -8,10 +8,8 @@ use crate::{PrintErr, Printer}; use bun_alloc::Arena; use bun_ast::ImportRecord; -/// Named replacement for the Zig anonymous `struct { v: ?LayerName }` used in -/// both `ImportConditions.layer` and `ImportRule.layer`. The two Zig anonymous -/// structs are layout-identical (the code `@ptrCast`s between the parents), so -/// we use a single Rust type for both. +/// Layer slot used in both `ImportConditions.layer` and `ImportRule.layer`. +/// The two uses are layout-identical, so a single type serves both. #[repr(C)] #[derive(Default)] pub struct Layer { @@ -164,7 +162,7 @@ impl ImportConditions { #[repr(C)] pub struct ImportRule { /// The url to import. - // TODO(port): arena lifetime — `&'bump [u8]` once crate-wide thread lands. + // TODO: arena lifetime — `&'bump [u8]` once crate-wide thread lands. pub url: &'static [u8], /// An optional cascade layer name, or `None` for an anonymous layer. @@ -227,11 +225,15 @@ impl ImportRule { pub fn conditions(&self) -> &ImportConditions { // SAFETY: ImportConditions is #[repr(C)] with fields {layer, supports, media} // laid out identically to the {layer, supports, media} field run of ImportRule - // (also #[repr(C)]). The Zig code relies on this same layout pun via @ptrCast. + // (also #[repr(C)]). // The pointer is derived from `self` (not `&self.layer`) so its provenance // covers all three fields — going through a field reference would narrow // provenance to just `layer` and make sibling-field reads UB under SB. - // TODO(port): replace with an actual `conditions: ImportConditions` field on ImportRule + // + // Long-term, the pun should be replaced with an actual + // `conditions: ImportConditions` field on `ImportRule` (see the struct + // doc on `ImportConditions`); that touches every `.layer`/`.supports`/ + // `.media` access crate-wide, so it is deferred. unsafe { &*std::ptr::from_ref(self) .byte_add(core::mem::offset_of!(Self, layer)) @@ -278,9 +280,8 @@ impl ImportRule { } pub fn deep_clone(&self, bump: &Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `url: &'static [u8]` - // is an arena-owned slice → identity copy (generics.zig "const - // strings" rule); `media` routes through `dc::media_list` until + // `url: &'static [u8]` is an arena-owned slice → identity copy; + // `media` routes through `dc::media_list` until // `MediaList` gains an arena-aware `deep_clone`. Self { url: self.url, @@ -316,7 +317,6 @@ impl ImportRule { dest.serialize_string(placeholder)?; if let Some(deps) = &mut dest.dependencies { - // PERF(port): was `catch unreachable` (alloc cannot fail under arena) deps.push(css::Dependency::Import(d)); } } else { @@ -368,5 +368,3 @@ const _: () = { }; // silence unused-import warnings on the gated bodies' deps - -// ported from: src/css/rules/import.zig diff --git a/src/css/rules/keyframes.rs b/src/css/rules/keyframes.rs index 0f75617789d..06751a4ee89 100644 --- a/src/css/rules/keyframes.rs +++ b/src/css/rules/keyframes.rs @@ -13,9 +13,8 @@ use super::ArrayList; // ────────────────────────────────────────────────────────────────────────── /// ` = | ` -// PORT NOTE: Zig threaded the parser-input lifetime; this stores -// `&'static [u8]` per PORTING.md §AST crates and the rules/mod.rs -// `CssRule` lifetime-erasure note. +// Stores `&'static [u8]` per the rules/mod.rs `CssRule` lifetime-erasure +// note (mod.rs:37-41). // TODO(refactor): re-thread `'bump` here. pub enum KeyframesName { /// `` of a `@keyframes` name. @@ -24,14 +23,12 @@ pub enum KeyframesName { Custom(&'static [u8]), } -// Zig: `pub fn HashMap(comptime V: type) type { return std.ArrayHashMapUnmanaged(...) }` -// → a generic type alias keyed by `KeyframesName` with the custom hash/eq below. +// A generic type alias keyed by `KeyframesName` with the custom hash/eq below. pub type KeyframesNameHashMap = bun_collections::ArrayHashMap; impl Hash for KeyframesName { fn hash(&self, state: &mut H) { - // Matches Zig: hash only the underlying string bytes; variant tag does NOT - // participate (Zig's `hash` switches and calls `hashString` on the slice). + // Hash only the underlying string bytes; the variant tag does NOT participate. match self { KeyframesName::Ident(ident) => state.write(ident.v()), KeyframesName::Custom(s) => state.write(s), @@ -94,8 +91,7 @@ impl KeyframesName { impl KeyframesName { pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` variant-walk. `Custom(&'static [u8])` - // is an arena-owned slice → identity copy (generics.zig "const strings"). + // `Custom(&'static [u8])` is an arena-owned slice → identity copy. match self { Self::Ident(i) => Self::Ident(i.deep_clone(bump)), Self::Custom(s) => Self::Custom(s), @@ -167,8 +163,6 @@ impl KeyframeSelector { impl KeyframeSelector { pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` variant-walk. `Percentage` is - // `Copy` (`{ v: f32 }`) → identity. match self { Self::Percentage(p) => Self::Percentage(*p), Self::From => Self::From, @@ -178,13 +172,11 @@ impl KeyframeSelector { } // ─── KeyframeSelector parse ─────────────────────────────────────────────── -// blocked_on: css::derive_parse (DeriveParse comptime macro replacement). +// blocked_on: css::derive_parse (DeriveParse). impl KeyframeSelector { - // Zig: `pub const parse = css.DeriveParse(@This()).parse;` - // PORT NOTE: `DeriveParse` is a comptime type-generator producing `parse` from - // variant introspection. Expanded by hand here: try the tuple variant - // (`Percentage`) first, then fall back to keyword idents (`from`/`to`). + // Try the tuple variant (`Percentage`) first, then fall back to keyword + // idents (`from`/`to`). pub(crate) fn parse(input: &mut css::Parser) -> css::Result { if let Ok(p) = input.try_parse(Percentage::parse) { return Ok(KeyframeSelector::Percentage(p)); @@ -212,7 +204,7 @@ pub struct Keyframe { /// A list of keyframe selectors to associate with the declarations in this keyframe. pub selectors: ArrayList, /// The declarations for this keyframe. - // PORT NOTE: lifetime erased to `'static` per rules/mod.rs `CssRule` note. + // Lifetime erased to `'static` per rules/mod.rs `CssRule` note. pub declarations: DeclarationBlock<'static>, } @@ -225,7 +217,6 @@ impl Keyframe { impl Keyframe { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { selectors: self.selectors.iter().map(|s| s.deep_clone(bump)).collect(), declarations: super::dc::decl_block_static(&self.declarations, bump), @@ -256,9 +247,8 @@ impl KeyframesRule { let mut first_rule = true; - // Zig: `inline for (.{ "webkit", "moz", "ms", "o", "none" }) |prefix_name|` with - // `@field(this.vendor_prefix, prefix_name)`. VendorPrefix is a packed-bool struct - // (→ `bitflags!`), so iterate the flag constants directly and use `.contains()`. + // VendorPrefix is `bitflags!`, so iterate the flag constants directly + // and use `.contains()`. const PREFIXES: [VendorPrefix; 5] = [ VendorPrefix::WEBKIT, VendorPrefix::MOZ, @@ -311,8 +301,6 @@ impl KeyframesRule { impl KeyframesRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `VendorPrefix` is a - // `Copy` bitflag (generics.zig "simple copy types" → identity). Self { name: self.name.deep_clone(bump), keyframes: self.keyframes.iter().map(|k| k.deep_clone(bump)).collect(), @@ -328,10 +316,6 @@ impl KeyframesRule { pub(crate) struct KeyframesListParser; -// PORT NOTE: in Zig these are nested `pub const DeclarationParser = struct { ... }` -// namespaces that the css parser duck-types via `@hasDecl`. In Rust they become -// trait impls on `KeyframesListParser`. -// // blocked_on: css::{DeclarationParser, AtRuleParser, QualifiedRuleParser, // RuleBodyItemParser} trait signatures (css_parser.rs round-5 surface), // Parser::parse_comma_separated, DeclarationBlock::parse, ParserOptions::default @@ -428,5 +412,3 @@ const _: () = { } } }; - -// ported from: src/css/rules/keyframes.zig diff --git a/src/css/rules/layer.rs b/src/css/rules/layer.rs index 023179aa803..40894b2a3e6 100644 --- a/src/css/rules/layer.rs +++ b/src/css/rules/layer.rs @@ -12,20 +12,21 @@ use crate::{PrintErr, Printer, SmallList}; /// notation (`a.b.c`) creates sublayers. #[derive(Default)] pub struct LayerName { - // TODO(port): arena lifetime — Zig `[]const u8` segments borrow the parser - // arena. Thread `'bump` once `CssRuleList` re-gains its arena lifetime; - // until then segments are laundered through `&'static [u8]` like every - // other CSS slice in this crate. + // TODO: arena lifetime — segments borrow the parser arena. Thread + // `'bump` once `CssRuleList` re-gains its arena lifetime; until then + // segments are laundered through `&'static [u8]` like every other CSS + // slice in this crate. pub v: SmallList<&'static [u8], 1>, } // The inline hash/eql context is replaced by `Hash`/`PartialEq` impls on `LayerName` below. -// TODO(port): ArrayHashMap must use wyhash (u32-truncated) to match Zig iteration order. +// Iteration order is insertion order (collections/array_hash_map.rs) +// regardless of hash function. pub type LayerNameHashMap = ArrayHashMap; impl core::hash::Hash for LayerName { fn hash(&self, state: &mut H) { - // Mirrors the Zig ArrayHashMap context: Wyhash(seed=0) over each part's bytes. + // Hash each part's bytes. for part in self.v.slice() { state.write(part); } @@ -39,7 +40,7 @@ impl PartialEq for LayerName { } impl Eq for LayerName {} -// PORT NOTE: trait `Clone` (not just inherent `deep_clone`) so the bundler's +// Trait `Clone` (not just inherent `deep_clone`) so the bundler's // `Chunk::Layers::to_owned` can `deep_clone_with(|l| l.clone())`. Segments are // arena-borrowed `&'static [u8]` (Copy), so this is the same shallow // `SmallList` copy as `deep_clone` / `clone_with_import_records`. @@ -51,9 +52,9 @@ impl Clone for LayerName { impl LayerName { pub fn clone_with_import_records(&self, bump: &Arena, _: &mut Vec) -> Self { - // `[]const u8` segments are arena-borrowed, not owned, so the Zig - // `deepClone` here was a shallow `SmallList` copy. No import records to - // rewrite — layer names contain no URLs. + // Segments are arena-borrowed, not owned, so this is a shallow + // `SmallList` copy. No import records to rewrite — layer names + // contain no URLs. // // Allocate the segment-pointer slab from `bump` (not the global // allocator): callers store the result in arena-owned `ImportConditions` @@ -83,7 +84,6 @@ impl LayerName { parts.append(ident); loop { - // Zig: `const Fn = struct { pub fn tryParseFn(...) ... }` let try_parse_fn = |i: &mut css::css_parser::Parser<'_>| -> css::css_parser::CssResult<&'static [u8]> { let start_location = i.current_source_location(); @@ -116,9 +116,8 @@ impl LayerName { } pub fn deep_clone(&self, _bump: &Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` — `[]const u8` segments are - // arena-owned (identity copy per generics.zig "const strings"). Same - // body as `clone_with_import_records` above. + // Segments are arena-owned (identity copy). Same body as + // `clone_with_import_records` above. LayerName { v: self.v.clone() } } } @@ -130,7 +129,6 @@ impl css::generics::ToCss for LayerName { } } -// Zig: `pub fn format(self, writer: *std.Io.Writer) !void` → `impl Display` impl fmt::Display for LayerName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut first = true; @@ -163,7 +161,7 @@ impl LayerBlockRule { where R: css::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. + // `css.implementDeepClone` field-walk. Self { name: self.name.as_ref().map(|n| n.deep_clone(bump)), rules: self.rules.deep_clone(bump), @@ -200,7 +198,7 @@ pub struct LayerStatementRule { impl LayerStatementRule { pub fn deep_clone(&self, bump: &Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. + // `css.implementDeepClone` field-walk. let mut names = SmallList::::default(); for n in self.names.slice() { names.append(n.deep_clone(bump)); @@ -223,5 +221,3 @@ impl LayerStatementRule { } } } - -// ported from: src/css/rules/layer.zig diff --git a/src/css/rules/media.rs b/src/css/rules/media.rs index 29ec17f97ae..e5200de2dcf 100644 --- a/src/css/rules/media.rs +++ b/src/css/rules/media.rs @@ -13,10 +13,8 @@ pub struct MediaRule { } // ─── behavior bodies ────────────────────────────────────────────────────── -// PORT NOTE: `minify` lives in `rules/mod.rs` (hoisted next to `CssRuleList:: -// minify` so the dispatch can call it without re-exporting `MinifyContext` -// here). `to_css` un-gated this round — `MediaList::{always_matches,to_css}` -// and `CssRuleList::to_css` are both real now. +// `minify` lives in `rules/mod.rs` (hoisted next to `CssRuleList::minify` so +// the dispatch can call it without re-exporting `MinifyContext` here). impl MediaRule { pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { if dest.minify && self.query.always_matches() { @@ -40,7 +38,6 @@ impl MediaRule { where R: crate::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { query: super::dc::media_list(&self.query, bump), rules: self.rules.deep_clone(bump), @@ -48,5 +45,3 @@ impl MediaRule { } } } - -// ported from: src/css/rules/media.zig diff --git a/src/css/rules/mod.rs b/src/css/rules/mod.rs index 5780f930222..d5040fbdb9d 100644 --- a/src/css/rules/mod.rs +++ b/src/css/rules/mod.rs @@ -4,7 +4,7 @@ use css::PrintErr; use css::Printer; use css::error::MinifyErr; -// PERF(port): heap-backed shim — Zig used arena-backed `std.ArrayListUnmanaged`. +// PERF: heap-backed shim. // TODO(refactor): thread `'bump` and replace this with `crate::generics::ArrayList<'bump, T>` // (= `bun_alloc::ArenaVec`) crate-wide in one pass. pub(super) type ArrayList = Vec; @@ -32,10 +32,8 @@ pub mod unknown; pub mod viewport; // ─── CssRule / CssRuleList ───────────────────────────────────────────────── -// Zig: pub fn CssRule(comptime Rule: type) type { return union(enum) { ... } } -// -// PORT NOTE: the original port threaded a `'bump` arena lifetime through every -// rule (matching Zig's `ArrayListUnmanaged`-backed AST). That cascades into +// An earlier iteration threaded a `'bump` arena lifetime through every +// rule. That cascades into // every leaf module signature; while those leaves are gated, `CssRule` is // kept lifetime-free here (the gated bodies re-introduce `'bump` when they // un-gate alongside `bumpalo::collections::Vec` storage). @@ -44,7 +42,7 @@ pub mod viewport; // Single source of truth for the 20 typed at-rule payloads. Adding a new // at-rule = one line here; the enum variant + `to_css` arm + `deep_clone` // arm are generated. `Unknown`/`Custom`/`Ignored` stay a fixed tail because -// their `to_css` arms are special-cased (see PORT NOTE on `Custom`). +// their `to_css` arms are special-cased (see the note on `Custom`). macro_rules! css_rule_variants { ( $( $(#[$doc:meta])* $Variant:ident($Payload:ty) ),+ $(,)? ) => { /// A single CSS rule (at-rule or style rule). @@ -63,33 +61,25 @@ macro_rules! css_rule_variants { match self { $( CssRule::$Variant(x) => x.to_css(dest), )+ CssRule::Unknown(x) => x.to_css(dest), - // Zig: `.custom => |x| x.toCss(dest) catch return dest.addFmtError()`. - // - // PORT NOTE (incomplete): the spec has TWO concrete `R` types — - // `DefaultAtRule` (whose `toCss` errors unconditionally) and - // `TailwindAtRule` (src/css/rules/tailwind.zig:14-19, used via - // `BundlerAtRule` when `ENABLE_TAILWIND_PARSING`), whose `toCss` - // SUCCEEDS and writes `@tailwind ;`. This arm therefore - // diverges from the spec for `R = TailwindAtRule`: it fails - // serialization where the spec round-trips. - // - // The correct port threads a `ToCss`-style bound (or per-`R` - // vtable) so `Custom(x)` dispatches to `x.to_css(dest)` and only - // maps the error path via `add_fmt_error()`. That bound cascades - // through every nested `CssRuleList` printer (media, supports, - // layer, document, nesting, starting_style, style, scope, - // container) — deferred to the patch that un-gates - // `BundlerAtRule = TailwindAtRule`. - // TODO(port): dispatch to `x.to_css(dest)` once `R: ToCss` (or - // equivalent) is threaded; current behavior is only spec-correct - // for `R = DefaultAtRule`. + // There are TWO concrete `R` types — `DefaultAtRule` (whose + // `to_css` errors unconditionally) and `TailwindAtRule`, + // whose `to_css` succeeds and writes `@tailwind ;`. + // Tailwind parsing is disabled (`ENABLE_TAILWIND_PARSING = + // false`, `BundlerAtRule = DefaultAtRule` in css_parser.rs), + // so erroring here is correct for every `R` that is + // actually instantiated. If + // `TailwindAtRule` is ever un-gated, thread a `ToCss`-style bound + // (or per-`R` vtable) so `Custom(x)` dispatches to + // `x.to_css(dest)` and only the error path maps through + // `add_fmt_error()`; that bound cascades through every nested + // `CssRuleList` printer (media, supports, layer, document, + // nesting, starting_style, style, scope, container). CssRule::Custom(_x) => Err(dest.add_fmt_error()), CssRule::Ignored => Ok(()), } } - /// Zig: `css.implementDeepClone(@This(), this, arena)` — variant-wise - /// dispatch to each leaf rule's `deep_clone`. Hand-written (not + /// Variant-wise dispatch to each leaf rule's `deep_clone`. Hand-written (not /// `#[derive(DeepClone)]`) because the leaf payloads expose `deep_clone` /// as **inherent** methods rather than `DeepClone` trait impls; /// method-syntax dispatch here picks up either. @@ -157,17 +147,16 @@ css_rule_variants! { // `bun_alloc::ArenaVec<'bump, T>` (raw `NonNull` + `&Bump`) deep in // leaf rule payloads, both of which suppress the auto-traits. Those containers // uniquely own their storage exactly like `Vec`, and post-parse the tree is -// shared read-only across the bundler thread pool (mirrors Zig, which freely -// hands the arena-backed AST between threads). Thread-safety therefore follows -// `R`'s auto-traits. +// shared read-only across the bundler thread pool. Thread-safety therefore +// follows `R`'s auto-traits. unsafe impl Send for CssRule {} // SAFETY: see the `Send` impl above — uniquely-owned storage; `Sync` follows `R: Sync`. unsafe impl Sync for CssRule {} -/// Zig: pub fn CssRuleList(comptime AtRule: type) type { return struct { ... } } +/// Ordered list of CSS rules, generic over the custom at-rule type `R`. pub struct CssRuleList { - // PERF(port): was `bun_alloc::ArenaVec<'bump, CssRule<'bump, R>>`; - // arena threading restored when leaf rules un-gate. + // PERF: re-thread to `bun_alloc::ArenaVec<'bump, CssRule<'bump, R>>` + // when leaf rules un-gate. pub v: Vec>, } @@ -191,7 +180,7 @@ impl Default for CssRuleList { // wise / variant-wise port of `css.implementDeepClone`. `CssRule::deep_clone` // (below) dispatches via method-syntax so it picks up the inherent impl. // -// PORT NOTE: most leaf rules can't use `#[derive(DeepClone)]` directly yet +// Most leaf rules can't use `#[derive(DeepClone)]` directly yet // because two field types still lack an arena-aware `deep_clone(&self, // &Arena) -> Self`: `SelectorList` (selectors/parser.rs uses no-arg // `deep_clone()`) and `Property` (properties_generated.rs — per-variant body @@ -210,7 +199,7 @@ pub(super) mod dc { /// through `dc::property` so the only remaining bottleneck is the /// per-variant `Property::deep_clone` body. /// - /// PORT NOTE: threads the real `'bump` lifetime instead of fabricating + /// Threads the real `'bump` lifetime instead of fabricating /// `'static` (PORTING.md §Forbidden: `unsafe { &*(p as *const _) }` to /// extend a lifetime). Callers whose storage is still pinned to /// `DeclarationBlock<'static>` must fix that storage type — the lie @@ -239,7 +228,7 @@ pub(super) mod dc { /// /// SAFETY: `DeclarationBlock<'static>` is the crate-wide `'bump`-erasure /// placeholder until `CssRule<'bump, R>` re-threads the arena lifetime - /// (see `style.rs` struct PORT NOTE). `bumpalo::Vec` is invariant in + /// (see the note on `StyleRule.declarations` in `style.rs`). `bumpalo::Vec` is invariant in /// `'bump`, so any `DeclarationBlock<'static>` constructor must observe a /// `&'static Arena`. The arena outlives every rule that borrows it (it /// owns them); lifetimes re-thread together when the rule structs grow a @@ -261,11 +250,10 @@ pub(super) mod dc { decl_block(this, unsafe { arena_static(bump) }) } - /// Empty `DeclarationBlock<'static>` — Zig spec writes `css.DeclarationBlock{}`. + /// Empty `DeclarationBlock<'static>`. /// - /// Exists so call-sites that need an empty block (rules.zig:363 - /// `nested_rule.declarations = .{}`) route through ONE centralized - /// erasure helper. Delete with `decl_block_static` once + /// Exists so call-sites that need an empty block route through ONE + /// centralized erasure helper. Delete with `decl_block_static` once /// `CssRule<'bump, R>` re-threads the arena lifetime. #[inline] pub(crate) fn decl_block_empty_static(bump: &Arena) -> crate::DeclarationBlock<'static> { @@ -275,8 +263,8 @@ pub(super) mod dc { /// `'bump`-erasure adaptor for `&mut DeclarationHandler<'_>`. /// - /// SAFETY: `DeclarationBlock<'static>` on `StyleRule` (see style.rs struct - /// PORT NOTE) forces `DeclarationBlock::minify` to expect + /// SAFETY: `DeclarationBlock<'static>` on `StyleRule` (see the note on + /// `StyleRule.declarations` in style.rs) forces `DeclarationBlock::minify` to expect /// `DeclarationHandler<'static>`; the handlers in `MinifyContext` carry the /// real `'bump`. Both reference the same arena. Centralized here so the /// erasure lives in ONE place; collapses together with `decl_block_static` @@ -325,8 +313,7 @@ pub(super) mod dc { } /// `Property::deep_clone` — routes to the real inherent - /// `Property::deep_clone` in properties_generated.rs (faithful per-variant - /// port of .zig:6307-6558). + /// `Property::deep_clone` in properties_generated.rs. #[inline] pub(crate) fn property( this: &crate::properties::Property, @@ -339,7 +326,6 @@ pub(super) mod dc { // `Location` is plain `Copy` data; the derive expands to field-wise // `u32::deep_clone` (identity). Doubles as the in-tree smoke test that the // `#[derive(DeepClone)]` proc-macro round-trips through a real CSS type. -// (The Zig `implementDeepClone` returns `this.*` for simple-copy types.) // ─── shared serialization helpers for leaf rules ────────────────────────── // Several leaf-rule `to_css` bodies bottom out on helpers whose canonical @@ -349,8 +335,8 @@ pub(super) mod dc { // 12 leaf rules can serialize for real. Once the upstream gates drop, callers // switch back and these are deleted. -/// Port of `DeclarationBlock.toCssBlock` (declaration.zig). The real impl is -/// gated in `declaration.rs`; `Property::to_css` is un-gated so the body is +/// `DeclarationBlock` block serialization. The real impl is gated in +/// `declaration.rs`; `Property::to_css` is un-gated so the body is /// trivially inlinable here. pub(super) fn decl_block_to_css( decls: &css::DeclarationBlock<'_>, @@ -362,7 +348,6 @@ pub(super) fn decl_block_to_css( let length = decls.len(); let mut i: usize = 0; - // Zig: `inline for (.{"declarations","important_declarations"}) |field|` — unrolled. for decl in decls.declarations.iter() { dest.newline()?; decl.to_css(dest, false)?; @@ -385,7 +370,7 @@ pub(super) fn decl_block_to_css( dest.write_char(b'}') } -/// Port of `VendorPrefix.toCss` (css_parser.zig:182). Lives here because the +/// `VendorPrefix` serialization. Lives here because the /// canonical `impl VendorPrefix` block in lib.rs hasn't grown a `to_css` yet /// and `rules/` is the only un-gated caller. #[inline] @@ -445,7 +430,7 @@ pub(super) fn dashed_ident_to_css( /// rule should be dropped. NOTE: `never_matches()` is a *drop condition*, not /// merely an optimization — omitting it diverges output (e.g. `@media not all /// { a{color:red} }` must be removed). `MediaList::never_matches` is un-gated, -/// so call it here to match the spec (`media.zig:19-23`). +/// so call it here. impl media::MediaRule { pub fn minify( &mut self, @@ -563,16 +548,15 @@ impl CssRuleList { for rule in self.v.iter_mut() { // NOTE Anytime you push `rule` into `rules`, set `moved_rule = true` - // so the source slot is replaced with `Ignored` (mirrors Zig's - // `defer if (moved_rule) rule.* = .ignored`). + // so the source slot is replaced with `Ignored`. let mut moved_rule = false; 'arm: { match rule { CssRule::Keyframes(_keyframez) => { - // TODO(port): KeyframesRule minify (unused-symbol drop + - // same-name merge + vendor-prefix downlevel + fallbacks). - // Zig leaves this as a debug-TODO fallthrough today. + // KeyframesRule minify (unused-symbol drop + same-name + // merge + vendor-prefix downlevel + fallbacks) is + // not implemented; fall through. } CssRule::CustomMedia(_) => { if context.custom_media.is_some() { @@ -599,7 +583,7 @@ impl CssRuleList { if let Some(CssRule::Supports(last_rule)) = rules.last_mut() && last_rule.condition.eql(&supp.condition) { - // Zig drops the duplicate-condition rule outright. + // Drop the duplicate-condition rule outright. break 'arm; } supp.minify(context, parent_is_unused)?; @@ -623,7 +607,7 @@ impl CssRuleList { } } CssRule::LayerStatement(_lay) => { - // TODO(port): LayerStatementRule minify — Zig fallthrough. + // LayerStatementRule minify is not implemented; fall through. } CssRule::MozDocument(doc) => { // See `Container` above: recurse so nested style rules @@ -648,7 +632,7 @@ impl CssRuleList { break 'arm; } } - CssRule::CounterStyle(_) => { /* TODO(port): Zig fallthrough */ } + CssRule::CounterStyle(_) => {} CssRule::Scope(scpe) => { // See `Container` above: recurse so nested style rules // count against the selector-expansion cap. @@ -676,8 +660,8 @@ impl CssRuleList { // count against the selector-expansion cap. rl.rules.minify(context, parent_is_unused)?; } - CssRule::FontPaletteValues(_) => { /* TODO(port): Zig fallthrough */ } - CssRule::Property(_) => { /* TODO(port): Zig fallthrough */ } + CssRule::FontPaletteValues(_) => {} + CssRule::Property(_) => {} _ => {} } @@ -699,13 +683,11 @@ impl CssRuleList { } } - // Zig: css.deepDeinit(CssRule(AtRule), context.arena, &this.v); - // Rust drops the old Vec on assignment. + // The old Vec is dropped on assignment. self.v = rules; Ok(()) } - /// Zig: `css.implementDeepClone(@This(), this, arena)`. pub fn deep_clone<'bump>(&self, bump: &'bump bun_alloc::Arena) -> Self where R: css::generics::DeepClone<'bump>, @@ -846,8 +828,8 @@ fn minify_style_arm css::generics::DeepClone<'b>>( { let mut rulesss = CssRuleList::::default(); core::mem::swap(&mut sty.rules, &mut rulesss); - // Zig: `.declarations = css.DeclarationBlock{}` — empty block. Route - // through the centralized `'bump`-erasure helper instead of fabricating + // Empty block: route through the centralized `'bump`-erasure helper + // instead of fabricating // `&'static Arena` here (PORTING.md §Forbidden). Some(style::StyleRule { selectors: sty.selectors.deep_clone(), @@ -914,9 +896,8 @@ fn minify_style_arm css::generics::DeepClone<'b>>( /// duplicates. It stores an index into the live `rules` Vec plus a /// pre-computed hash for fast lookups. /// -/// PORT NOTE: the Zig spec (`rules.zig:StyleRuleKey`) additionally stores -/// `list: *const ArrayList(CssRule(R))` and dereferences it inside `eql()`. -/// That pattern is unsound in Rust under Stacked/Tree Borrows — keys persist +/// NOTE: storing a `*const Vec>` in the key and dereferencing it +/// during equality would be unsound under Stacked/Tree Borrows — keys persist /// in the dedup map across iterations of `minify_style_arm`, and between /// iterations the same `Vec` is written through fresh `&mut` reborrows /// (`rules.push`, `rules[i] = Ignored`), invalidating any previously-derived @@ -939,21 +920,20 @@ impl StyleRuleKey { } } -/// Dedup table for [`StyleRuleKey`]s — the Rust-side equivalent of Zig's -/// `StyleRuleKey(R).HashMap(usize)`. +/// Dedup table for [`StyleRuleKey`]s. /// /// Buckets keyed by the pre-computed `StyleRule::hash_key()` hold indices into /// the caller's `rules` Vec; equality (`StyleRule::is_duplicate`) is evaluated /// against an explicitly-passed `&[CssRule]` so we never smuggle a stale -/// raw pointer across `&mut rules` writes (see PORT NOTE on `StyleRuleKey`). +/// raw pointer across `&mut rules` writes (see the note on `StyleRuleKey`). #[derive(Default)] pub(crate) struct StyleRuleKeyMap { buckets: bun_collections::HashMap>, } impl StyleRuleKeyMap { - /// Zig `style_rules.fetchSwapRemove(key)` — find and remove an earlier - /// index whose rule `is_duplicate` of `rules[key.index]`. + /// Find and remove an earlier index whose rule `is_duplicate` of + /// `rules[key.index]`. pub(crate) fn remove_duplicate( &mut self, rules: &[CssRule], @@ -964,8 +944,7 @@ impl StyleRuleKeyMap { return None; }; let pos = bucket.iter().position(|&other_idx| { - // Mirrors `StyleRuleKey.eql` from rules.zig: bounds-check + .style - // tag-check + `isDuplicate`. + // Bounds-check + Style tag-check + `is_duplicate`. match rules.get(other_idx) { Some(CssRule::Style(other_rule)) => rule.is_duplicate(other_rule), _ => false, @@ -974,7 +953,7 @@ impl StyleRuleKeyMap { Some(bucket.swap_remove(pos)) } - /// Zig `style_rules.put(ctx.arena, key, idx)`. + /// Record the rule's index under its style-rule key for later dedup lookups. pub(crate) fn insert(&mut self, key: StyleRuleKey) { self.buckets.entry(key.hash).or_default().push(key.index); } @@ -1044,8 +1023,8 @@ pub(crate) fn merge_style_rules( if sty.is_compatible(context.targets) && last_style_rule.is_compatible(context.targets) { let moved = core::mem::take(&mut sty.selectors.v); // `reserve` (not `ensure_total_capacity`) so capacity grows - // super-linearly across repeated merges — matches .zig - // `appendSlice` and keeps the N-way merge amortized O(N). + // super-linearly across repeated merges, keeping the N-way merge + // amortized O(N). last_style_rule.selectors.v.reserve(moved.len()); for sel in moved { last_style_rule.selectors.v.append_assume_capacity(sel); @@ -1065,9 +1044,8 @@ pub(crate) fn merge_style_rules( // ─── Location / StyleContext / MinifyContext ────────────────────────────── -// Zig spec: `css.Location = css_rules.Location` is a TYPE ALIAS (one nominal -// type). Re-export the crate-root struct so `css_rules::Location` and -// `crate::Location` are interchangeable. +// Re-export the crate-root struct so `css_rules::Location` and +// `crate::Location` are interchangeable (one nominal type). pub use crate::Location; /// Printer's nesting cursor — linked list of parent selector lists used to @@ -1094,10 +1072,9 @@ pub const MAX_SELECTOR_EXPANSION: u32 = 65_536; /// Per-stylesheet minification state threaded through `CssRuleList::minify` /// and every leaf rule's `minify`. /// -/// PORT NOTE: Zig carried `arena: std.mem.Allocator` for the AST arena; -/// here that is `&'a Arena` (bumpalo). All sub-allocations during minify go +/// All sub-allocations during minify go /// through it so the whole transformed tree is bulk-freed with the arena. -// PORT NOTE: split lifetimes — `'bump` is the parser arena (long), `'a` is the +// Split lifetimes — `'bump` is the parser arena (long), `'a` is the // per-minify borrow scope (short). `&'a mut DeclarationHandler<'a>` would force // the handler borrow to outlive the arena (invariance via `bumpalo::Vec`), // making `Stylesheet::minify`'s stack-local handlers unusable. @@ -1109,7 +1086,6 @@ pub struct MinifyContext<'a, 'bump> { pub important_handler: &'a mut css::DeclarationHandler<'bump>, pub handler_context: css::PropertyHandlerContext<'bump>, /// Class/id names known to be unused (tree-shaking input). - // PORT NOTE: Zig `*const std.StringArrayHashMapUnmanaged(void)`. // `selector::is_unused` currently borrows `&ArrayHashMap<&[u8], ()>`; the // owning `MinifyOptions` stores `Box<[u8]>` keys — reconcile when // `style.rs::minify` un-gates (single key type, `Borrow<[u8]>` lookup). @@ -1119,7 +1095,7 @@ pub struct MinifyContext<'a, 'bump> { Option, custom_media::CustomMediaRule>>, pub extra: &'a css::StylesheetExtra, pub css_modules: bool, - /// First minification error encountered (Zig surfaced this out-of-band). + /// First minification error encountered (surfaced out-of-band). pub err: Option, /// How many copies of the current rule's selectors compiling the enclosing /// nesting for the targets will produce — the product of the enclosing @@ -1129,5 +1105,3 @@ pub struct MinifyContext<'a, 'bump> { /// will expand to, checked against [`MAX_SELECTOR_EXPANSION`]. pub selector_expansion_total: u32, } - -// ported from: src/css/rules/rules.zig diff --git a/src/css/rules/namespace.rs b/src/css/rules/namespace.rs index babca2d32a8..45f38e6ab1a 100644 --- a/src/css/rules/namespace.rs +++ b/src/css/rules/namespace.rs @@ -29,9 +29,8 @@ impl NamespaceRule { } pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `CssString` is - // `*const [u8]` (arena-owned slice → identity copy per generics.zig - // "const strings" rule); `Ident::deep_clone` is the same identity copy. + // `CssString` is `*const [u8]` (arena-owned slice → identity copy); + // `Ident::deep_clone` is the same identity copy. Self { prefix: self.prefix.as_ref().map(|p| p.deep_clone(bump)), url: self.url, @@ -39,5 +38,3 @@ impl NamespaceRule { } } } - -// ported from: src/css/rules/namespace.zig diff --git a/src/css/rules/nesting.rs b/src/css/rules/nesting.rs index 2617e20dfa6..db99c278929 100644 --- a/src/css/rules/nesting.rs +++ b/src/css/rules/nesting.rs @@ -28,12 +28,9 @@ impl NestingRule { where R: crate::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { style: self.style.deep_clone(bump), loc: self.loc, } } } - -// ported from: src/css/rules/nesting.zig diff --git a/src/css/rules/page.rs b/src/css/rules/page.rs index 275c7e3b802..1e1e059661a 100644 --- a/src/css/rules/page.rs +++ b/src/css/rules/page.rs @@ -10,9 +10,9 @@ use super::ArrayList; /// Either a name or at least one pseudo class is required. pub struct PageSelector { /// An optional named page type. - // PORT NOTE: arena-owned slice borrowed from parser input; `&'static` per - // PORTING.md §AST crates / rules/mod.rs lifetime-erasure note. - // TODO(port): re-thread `'bump`. + // Arena-owned slice borrowed from parser input; `&'static` per the + // rules/mod.rs lifetime-erasure note. + // TODO: re-thread `'bump`. pub name: Option<&'static [u8]>, /// A list of page pseudo classes. pub pseudo_classes: ArrayList, @@ -34,9 +34,7 @@ impl PageSelector { impl PageSelector { pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `name: Option<&'static - // [u8]>` is an arena-owned slice → identity copy; `PagePseudoClass` is - // `Copy`. + // `name` is an arena-owned slice → identity copy; `PagePseudoClass` is `Copy`. Self { name: self.name, pseudo_classes: self @@ -82,7 +80,7 @@ pub struct PageMarginRule { /// The margin box identifier for this rule. pub margin_box: PageMarginBox, /// The declarations within the rule. - // PORT NOTE: lifetime erased to `'static` per rules/mod.rs `CssRule` note. + // Lifetime erased to `'static` per the rules/mod.rs `CssRule` note. pub declarations: DeclarationBlock<'static>, /// The location of the rule in the source file. pub loc: Location, @@ -101,7 +99,6 @@ impl PageMarginRule { impl PageMarginRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `PageMarginBox` is `Copy`. Self { margin_box: self.margin_box, declarations: super::dc::decl_block_static(&self.declarations, bump), @@ -115,7 +112,7 @@ pub struct PageRule { /// A list of page selectors. pub selectors: ArrayList, /// The declarations within the `@page` rule. - // PORT NOTE: lifetime erased to `'static` per rules/mod.rs `CssRule` note. + // Lifetime erased to `'static` per the rules/mod.rs `CssRule` note. pub declarations: DeclarationBlock<'static>, /// The nested margin rules. pub rules: ArrayList, @@ -144,8 +141,7 @@ impl PageRule { let mut i: usize = 0; let len = self.declarations.len() + self.rules.len(); - // PORT NOTE: Zig used `inline for` over field-name tuple + @field reflection. - // Unrolled to a 2-tuple of (slice, important) since both fields are property lists. + // Both declaration fields are property lists; iterate as (slice, important) pairs. let decls_groups: [(&[crate::css_parser::Property], bool); 2] = [ (self.declarations.declarations.as_slice(), false), (self.declarations.important_declarations.as_slice(), true), @@ -187,7 +183,6 @@ impl PageRule { impl PageRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { selectors: self.selectors.iter().map(|s| s.deep_clone(bump)).collect(), declarations: super::dc::decl_block_static(&self.declarations, bump), @@ -258,7 +253,7 @@ pub enum PagePseudoClass { impl PagePseudoClass { #[inline] pub(crate) fn deep_clone(self, _bump: &bun_alloc::Arena) -> Self { - // `Copy` enum (generics.zig "simple copy types" → identity). + // `Copy` enum → identity. self } } @@ -310,10 +305,7 @@ pub(crate) struct PageRuleParser<'a> { pub options: &'a css::ParserOptions<'a>, } -// PORT NOTE: Zig modeled DeclarationParser/AtRuleParser/QualifiedRuleParser/ -// RuleBodyItemParser as nested `pub const Foo = struct { ... }` namespaces with -// methods taking `*This`. In Rust these become trait impls on PageRuleParser; -// associated `pub const X = T` → `type X = T`. +// Parser trait impls for `@page` rule bodies. const _: () = { use css::css_parser::{ AtRuleParser, DeclarationParser, QualifiedRuleParser, RuleBodyItemParser, @@ -413,5 +405,3 @@ const _: () = { } } }; - -// ported from: src/css/rules/page.zig diff --git a/src/css/rules/property.rs b/src/css/rules/property.rs index 8388e17eb41..4c4f105d40b 100644 --- a/src/css/rules/property.rs +++ b/src/css/rules/property.rs @@ -61,12 +61,8 @@ impl PropertyRule { impl PropertyRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `SyntaxString` has an - // inherent `deep_clone(&self, &Arena)`. While `ParsedComponent` is - // ``-gated to `()`, `Option<()>` is `Copy` → identity; - // once it un-gates, swap to `self.initial_value.as_ref().map(|v| - // v.deep_clone(bump))` (values/syntax.rs already provides the - // inherent impl). + // `SyntaxString` and `ParsedComponent` provide inherent + // `deep_clone(&self, &Arena)` impls. Self { name: self.name.deep_clone(bump), syntax: self.syntax.deep_clone(bump), @@ -92,8 +88,8 @@ impl PropertyRule { initial_value: None, }; - // PORT NOTE: split the borrows — `RuleBodyParser` borrows `input`+`p`; - // we re-borrow `input` after dropping `decl_parser`. + // `RuleBodyParser` borrows `input`+`p`; re-borrow `input` after + // dropping `decl_parser`. { let mut decl_parser = RuleBodyParser::new(input, &mut p); while let Some(decl) = decl_parser.next() { @@ -156,18 +152,16 @@ impl PropertyRule { } } -// PORT NOTE: borrows the parser input buffer for `initial_value` (arena-backed -// in CSS crate). Kept as `&'static [u8]` per PORTING.md §AST crates; -// TODO(refactor): re-thread `'i`. +// Borrows the parser input buffer for `initial_value` (arena-backed in the +// CSS crate). Kept as `&'static [u8]` per the rules/mod.rs lifetime-erasure +// note; TODO(refactor): re-thread `'i`. pub(crate) struct PropertyRuleDeclarationParser { pub syntax: Option, pub inherits: Option, pub initial_value: Option<&'static [u8]>, } -// PORT NOTE: Zig's nested `pub const DeclarationParser = struct { ... }` -// namespaces are structural duck-typing for RuleBodyParser; in Rust these -// become trait impls. +// Parser trait impls for `@property` rule bodies. const _: () = { use css::css_parser::{ AtRuleParser, DeclarationParser, QualifiedRuleParser, RuleBodyItemParser, @@ -177,8 +171,6 @@ const _: () = { impl DeclarationParser for PropertyRuleDeclarationParser { type Declaration = (); - // TODO(port): the Zig defines a ComptimeStringMap over FieldEnum but never uses it - // (usage is commented out). Preserved the active if/else-if chain instead. fn parse_value( this: &mut Self, name: &[u8], @@ -273,5 +265,3 @@ const _: () = { } } }; - -// ported from: src/css/rules/property.zig diff --git a/src/css/rules/scope.rs b/src/css/rules/scope.rs index 63f9fc7d35b..cf880646bf4 100644 --- a/src/css/rules/scope.rs +++ b/src/css/rules/scope.rs @@ -33,7 +33,7 @@ impl ScopeRule { if let Some(scope_start) = &self.scope_start { dest.write_char(b'(')?; // scope_start.to_css(dest)?; - // PORT NOTE: read `dest.ctx` directly (Copy) — `Printer::context()` + // Read `dest.ctx` directly (Copy) — `Printer::context()` // ties the borrow to `&self`, which conflicts with `&mut dest`. let ctx = dest.ctx; serialize_selector_list(scope_start.v.slice(), dest, ctx, false)?; @@ -48,7 +48,6 @@ impl ScopeRule { // is treated as an ancestor of scope end. // https://drafts.csswg.org/css-nesting/#nesting-at-scope if let Some(scope_start) = &self.scope_start { - // PORT NOTE: Zig passed an anon-struct fn pointer; the Rust // `Printer::with_context` carries the captured state as the // first closure arg (no `&self` capture across `&mut dest`). dest.with_context( @@ -85,9 +84,9 @@ impl ScopeRule { where R: crate::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. `SelectorList:: - // deep_clone()` intentionally drops the `&Arena` (selectors/parser.rs - // — every payload is arena-static); routed via `dc::selector_list`. + // `SelectorList::deep_clone()` intentionally drops the `&Arena` + // (selectors/parser.rs — every payload is arena-static); routed via + // `dc::selector_list`. Self { scope_start: self .scope_start @@ -102,5 +101,3 @@ impl ScopeRule { } } } - -// ported from: src/css/rules/scope.zig diff --git a/src/css/rules/starting_style.rs b/src/css/rules/starting_style.rs index ef368fd10db..f7a2ee60916 100644 --- a/src/css/rules/starting_style.rs +++ b/src/css/rules/starting_style.rs @@ -27,12 +27,9 @@ impl StartingStyleRule { where R: crate::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { rules: self.rules.deep_clone(bump), loc: self.loc, } } } - -// ported from: src/css/rules/starting_style.zig diff --git a/src/css/rules/style.rs b/src/css/rules/style.rs index f507372492e..8474c12b251 100644 --- a/src/css/rules/style.rs +++ b/src/css/rules/style.rs @@ -5,12 +5,12 @@ use crate::error::MinifyErr; use crate::selectors::selector; use crate::{PrintErr, Printer, VendorPrefix}; -// `fn StyleRule(comptime R: type) type { return struct {...} }` → generic struct. +// `StyleRule` is generic over the custom at-rule type `R`. // -// PORT NOTE: `DeclarationBlock<'bump>` borrows the parser arena (bumpalo Vecs). +// `DeclarationBlock<'bump>` borrows the parser arena (bumpalo Vecs). // Threading `'bump` here cascades into `CssRule<'bump, R>` / `CssRuleList<'bump, R>` -// (rules/mod.rs PORT NOTE) which is deferred until the leaf rules un-gate -// together; for now the lifetime is erased to `'static`. +// (see the rules/mod.rs lifetime-erasure note); for now the lifetime is erased +// to `'static`. pub struct StyleRule { /// The selectors for the style rule. pub selectors: selector::parser::SelectorList, @@ -36,13 +36,11 @@ impl StyleRule { /// Returns a hash of this rule for use when deduplicating. /// Includes the selectors and properties. pub fn hash_key(&self) -> u64 { - // std.hash.Wyhash.init(0) — same algorithm as bun.hash + // Wyhash seeded with 0 — same algorithm as bun.hash let mut hasher = bun_wyhash::Wyhash::init(0); self.selectors.hash(&mut hasher); - // PORT NOTE: `DeclarationBlock::hash_property_ids` is still - // ``-gated in declaration.rs; inline its body here. The - // Zig `PropertyId.hash` is `hasher.update(asBytes(&@intFromEnum(self)))` - // — i.e. just the u16 tag bytes. + // Inlined `DeclarationBlock::hash_property_ids`: hash just the u16 + // property-id tag bytes. for decl in self.declarations.declarations.iter() { let tag = decl.property_id().tag() as u16; hasher.update(&tag.to_ne_bytes()); @@ -175,7 +173,7 @@ impl StyleRule { // #[cfg(feature = "sourcemap")] // dest.add_mapping(self.loc); - // PORT NOTE: `dest.context()` borrows `dest`; copy the (Copy) raw + // `dest.context()` borrows `dest`; copy the (Copy) raw // ctx field out so it doesn't conflict with the `&mut *dest` below. let ctx = dest.ctx; // Each rule prelude gets its own budget for `&` substitutions when @@ -192,8 +190,8 @@ impl StyleRule { dest.indent(); let mut i: usize = 0; - // Zig: inline for (.{"declarations", "important_declarations"}) — @field reflection. - // Unrolled into a pair of (slice, important) tuples; same iteration order. + // A pair of (slice, important) tuples; declarations first, then + // important declarations. let decls_groups: [(&[Property], bool); 2] = [ (self.declarations.declarations.as_slice(), false), (self.declarations.important_declarations.as_slice(), true), @@ -211,10 +209,8 @@ impl StyleRule { } if dest.css_module.is_some() { - // PORT NOTE: reshaped for borrowck — Zig - // `if (dest.css_module) |*css_module| - // css_module.handleComposes(dest, ...)` overlaps - // `&mut dest.css_module` with `&mut *dest`. Move the + // `handle_composes` needs `&mut dest` while the + // module also lives in `dest.css_module`. Move the // module out for the duration of the call, then put // it back before any `dest.new_error` early return. let mut cm = dest.css_module.take(); @@ -250,7 +246,6 @@ impl StyleRule { } } - // Zig: local `Helpers` struct with two fns. Rust: nested fn items (no capture needed). fn helpers_newline( self_: &StyleRule, d: &mut Printer, @@ -303,8 +298,7 @@ impl StyleRule { helpers_newline(self, dest, supports_nesting, len)?; } dest.skip_prefixed_nested_rules = skip_prefixed_nested; - // Zig: dest.withContext(&this.selectors, this, struct { fn toCss(...) }.toCss) - // Rust `with_context` keeps the (closure-data, fn) split so the + // `with_context` keeps the (closure-data, fn) split so the // `Printer` reborrow lives only inside `func`. let result = dest.with_context(&self.selectors, &self.rules, |rules, d| rules.to_css(d)); @@ -327,13 +321,6 @@ impl StyleRule { use css::context::DeclarationContext; let mut unused = false; - // TODO(port): blocked_on key-type mismatch — `selector::is_unused` takes - // `&ArrayHashMap<&[u8], ()>` but `MinifyContext.unused_symbols` is - // `&ArrayHashMap, ()>` (rules/mod.rs PORT NOTE: "reconcile when - // style.rs::minify un-gates — single key type, Borrow<[u8]> lookup"). - // The reconciliation lives in rules/mod.rs + selectors/selector.rs, not - // here; gate the body until those agree. - if context.unused_symbols.count() > 0 { if selector::is_unused( self.selectors.v.slice(), @@ -368,7 +355,7 @@ impl StyleRule { // } context.handler_context.context = DeclarationContext::StyleRule; - // PORT NOTE: `DeclarationBlock<'static>` (struct PORT NOTE above) forces + // `DeclarationBlock<'static>` (see the struct-level note above) forces // `minify` to want `DeclarationHandler<'static>`; route through the // single centralized `'bump`-erasure helper instead of open-coding the // lifetime cast. Collapses when `CssRule<'bump, R>` @@ -484,13 +471,12 @@ impl StyleRule { .declarations .len() .min(other.declarations.declarations.len()); - // for (a, b) |*a, *b| → zip; Zig asserts equal length but here len is @min so truncation is intended. + // len is the min of the two lengths, so truncation is intended. for (a, b) in self.declarations.declarations[..len] .iter() .zip(&other.declarations.declarations[..len]) { - // PORT NOTE: Zig `PropertyId.eql` == tag+prefix compare; - // that's exactly the `PartialEq` impl on `PropertyId`. + // `PropertyId`'s `PartialEq` is a tag+prefix compare. if a.property_id() != b.property_id() { break 'brk false; } @@ -519,10 +505,10 @@ impl StyleRule { where R: crate::generics::DeepClone<'bump>, { - // css is an AST crate (PORTING.md §Allocators): std.mem.Allocator → &'bump Bump, threaded. - // PORT NOTE: `css.implementDeepClone` field-walk. `declarations` routes - // through `dc::decl_block` until `DeclarationBlock::deep_clone` un-gates - // (declaration.rs — bottoms out on `Property: DeepClone`). + // css is an AST crate (PORTING.md §Allocators): the allocator is &'bump Bump, threaded. + // `declarations` routes through `dc::decl_block` until + // `DeclarationBlock::deep_clone` un-gates (declaration.rs — bottoms out + // on `Property: DeepClone`). Self { selectors: self.selectors.deep_clone(), vendor_prefix: self.vendor_prefix, @@ -532,5 +518,3 @@ impl StyleRule { } } } - -// ported from: src/css/rules/style.zig diff --git a/src/css/rules/supports.rs b/src/css/rules/supports.rs index c4c5c7cf50f..ff8920f725f 100644 --- a/src/css/rules/supports.rs +++ b/src/css/rules/supports.rs @@ -7,8 +7,8 @@ use bun_alloc::ArenaPtr; /// A [``](https://drafts.csswg.org/css-conditional-3/#typedef-supports-condition), /// as used in the `@supports` and `@import` rules. -// PORT NOTE: Zig threaded the parser-input lifetime (`[]const u8` slices borrow -// the source). Currently uses `&'static [u8]` per PORTING.md §AST crates; +// String payloads borrow the parser input/arena; currently `&'static [u8]` +// per the rules/mod.rs lifetime-erasure note. // TODO(refactor): re-thread `'i` once `PropertyId<'i>` and the parser arena are real. pub enum SupportsCondition { /// A `not` expression. @@ -30,8 +30,8 @@ pub enum SupportsCondition { Unknown(&'static [u8]), } -// PORT NOTE: Zig used an anonymous inline struct for the `.declaration` payload; -// hoisted to a named type because Rust enum variants cannot carry inherent methods. +// Named payload type for `SupportsCondition::Declaration` (enum variants +// cannot carry inherent methods). pub struct Declaration { /// The property id for the declaration. pub property_id: PropertyId, @@ -44,9 +44,7 @@ pub struct Declaration { impl Declaration { pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `PropertyId` is `Copy`; - // `value: &'static [u8]` is an arena-owned slice → identity copy - // (generics.zig "const strings" rule). + // `PropertyId` is `Copy`; `value` is an arena-owned slice → identity copy. Self { property_id: self.property_id, value: self.value, @@ -56,7 +54,6 @@ impl Declaration { impl Declaration { pub(crate) fn eql(&self, other: &Self) -> bool { - // PORT NOTE: Zig `css.implementEql` field-walk, hand-expanded. // `PropertyId` carries its own tag+prefix `PartialEq` (see // properties_generated.rs `impl PartialEq for PropertyId`); `value` is // byte-slice equality. @@ -74,7 +71,7 @@ impl SupportsCondition { } pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> SupportsCondition { - // PORT NOTE: `css.implementDeepClone` variant-walk (hand-rolled — + // Hand-rolled variant-walk — // `#[derive(DeepClone)]` can't be used while `Selector`/`Unknown` // carry `&'static [u8]`; the blanket `&'bump [u8]` impl doesn't unify // with a fresh `'__bump`). @@ -107,10 +104,9 @@ impl SupportsCondition { // `impl CssHash for PropertyId` then swap to `#[derive(CssHash)]`. pub fn hash(&self, hasher: &mut bun_wyhash::Wyhash) { - // PORT NOTE: Zig `css.implementHash` variant-walk, hand-expanded because - // `#[derive(CssHash)]` would require `PropertyId: CssHash` (it only - // provides `core::hash::Hash`). Semantics match the Zig reflection: - // hash the discriminant, then field-wise structural hash. + // Hand-expanded because `#[derive(CssHash)]` would require + // `PropertyId: CssHash` (it only provides `core::hash::Hash`). + // Hash the discriminant, then field-wise structural hash. use core::hash::{Hash, Hasher}; core::mem::discriminant(self).hash(hasher); match self { @@ -130,10 +126,9 @@ impl SupportsCondition { } pub fn eql(&self, other: &SupportsCondition) -> bool { - // PORT NOTE: Zig `css.implementEql` variant-walk, hand-expanded because - // `#[derive(CssEql)]` would require `PropertyId: CssEql` (it only - // provides the custom tag+prefix `PartialEq`). Semantics match the Zig - // reflection: tag mismatch → false, then field-wise structural eq. + // Hand-expanded because `#[derive(CssEql)]` would require + // `PropertyId: CssEql` (it only provides the custom tag+prefix + // `PartialEq`). Tag mismatch → false, then field-wise structural eq. match (self, other) { (Self::Not(a), Self::Not(b)) => a.eql(b), (Self::And(a), Self::And(b)) | (Self::Or(a), Self::Or(b)) => { @@ -155,10 +150,6 @@ impl crate::generics::CssEql for SupportsCondition { } impl SupportsCondition { - // PORT NOTE: `pub fn deinit` dropped — body only freed Box/Vec payloads which Rust - // drops automatically. Input-slice variants (`Declaration`/`Selector`/`Unknown`) - // were no-ops in Zig as well (arena/input-owned). - fn needs_parens(&self, parent: &SupportsCondition) -> bool { match self { SupportsCondition::Not(_) => true, @@ -233,11 +224,8 @@ impl SupportsCondition { } let name = property_id.name(); - // PORT NOTE: `inline for (css.VendorPrefix.FIELDS) |field| { if @field(prefix, field) ... }` - // iterates the packed-struct bool fields at comptime. VendorPrefix ports to - // bitflags!; iterate the ordered single-bit table directly (same pattern as - // rules/style.rs). The Zig also builds `var p = VendorPrefix{}; @field(p, field) = true;` - // but never reads it — dead store dropped. + // Iterate the ordered single-bit VendorPrefix table directly (same + // pattern as rules/style.rs). dest.write_separated( css::VendorPrefix::FIELDS .iter() @@ -283,14 +271,12 @@ impl SupportsCondition { let mut expected_type: Option = None; let mut conditions: Vec = Vec::new_in(ArenaPtr::new(input.arena())); - // PORT NOTE: Zig used std.ArrayHashMap with an inline custom hash/eql context; - // SeenDeclKey below carries equivalent Hash/Eq impls. + // `SeenDeclKey` below carries the custom Hash/Eq impls for this map. let mut seen_declarations: ArrayHashMap = ArrayHashMap::new(); loop { - // PORT NOTE: reshaped for borrowck — Zig threaded `*?i32` through a - // local `Closure` struct (LIFETIMES.tsv: BORROW_PARAM); a Rust closure - // capturing `&mut expected_type` is the direct equivalent. + // A closure capturing `&mut expected_type` threads the expected + // type through the parse attempt. let _condition = input.try_parse(|i: &mut css::Parser| -> css::Result { let location = i.current_source_location(); @@ -421,16 +407,14 @@ impl SupportsCondition { } } -// PORT NOTE: Zig `SeenDeclKey` was a tuple struct with an inline hash-map context -// providing custom hash/eql. Ported as a tuple struct with manual Hash/PartialEq -// matching the Zig context exactly (wrapping_add of string hash and enum int). +// Dedup key for `@supports` declaration conditions; manual Hash/PartialEq +// (wrapping_add of string hash and enum int). struct SeenDeclKey(PropertyId, &'static [u8]); impl core::hash::Hash for SeenDeclKey { fn hash(&self, state: &mut H) { - // TODO(port): Zig used std.array_hash_map.hashString (wyhash, 32-bit) +% @intFromEnum. - // bun_collections::ArrayHashMap is wyhash-backed; confirm hasher parity. - // PORT NOTE: hash_string returns u32 directly (mirrors Zig hashString) — no narrowing cast. + // wyhash of the value bytes +% the property-id tag. + // `hash_string` returns u32 directly — no narrowing cast. let h: u32 = bun_collections::array_hash_map::hash_string(self.1); state.write_u32(h.wrapping_add(self.0.tag() as u32)); } @@ -438,7 +422,7 @@ impl core::hash::Hash for SeenDeclKey { impl PartialEq for SeenDeclKey { fn eq(&self, other: &Self) -> bool { - // Zig: tag-only equality + slice byte equality. + // Tag-only equality + slice byte equality. self.0.tag() as u16 == other.0.tag() as u16 && self.1 == other.1 } } @@ -494,7 +478,6 @@ impl SupportsRule { where R: css::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { condition: self.condition.deep_clone(bump), rules: self.rules.deep_clone(bump), @@ -502,5 +485,3 @@ impl SupportsRule { } } } - -// ported from: src/css/rules/supports.zig diff --git a/src/css/rules/tailwind.rs b/src/css/rules/tailwind.rs index 78686113eb4..b7427b0608a 100644 --- a/src/css/rules/tailwind.rs +++ b/src/css/rules/tailwind.rs @@ -46,5 +46,3 @@ pub enum TailwindStyleName { /// your stylesheet by default. Variants, } - -// ported from: src/css/rules/tailwind.zig diff --git a/src/css/rules/unknown.rs b/src/css/rules/unknown.rs index a8a934d5414..bac6abcaaf7 100644 --- a/src/css/rules/unknown.rs +++ b/src/css/rules/unknown.rs @@ -5,7 +5,7 @@ use crate::{PrintErr, Printer}; /// An unknown at-rule, stored as raw tokens. pub struct UnknownAtRule { /// The name of the at-rule (without the @). - // TODO(port): arena lifetime — Zig `[]const u8` backed by parser arena. + // TODO: arena lifetime — slice backed by parser arena. pub name: &'static [u8], /// The prelude of the rule. pub prelude: TokenList, @@ -40,9 +40,8 @@ impl UnknownAtRule { pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { use crate::generics::DeepClone as _; - // PORT NOTE: `css.implementDeepClone` field-walk. `name: &'static [u8]` - // is an arena-owned slice → identity copy (generics.zig "const strings" - // rule); `TokenList` carries `#[derive(DeepClone)]`. + // `name` is an arena-owned slice → identity copy; `TokenList` + // carries `#[derive(DeepClone)]`. Self { name: self.name, prelude: self.prelude.deep_clone(bump), @@ -51,5 +50,3 @@ impl UnknownAtRule { } } } - -// ported from: src/css/rules/unknown.zig diff --git a/src/css/rules/viewport.rs b/src/css/rules/viewport.rs index 445fe43d4dc..8e1514c964b 100644 --- a/src/css/rules/viewport.rs +++ b/src/css/rules/viewport.rs @@ -6,8 +6,8 @@ pub struct ViewportRule { /// The vendor prefix for this rule, e.g. `@-ms-viewport`. pub vendor_prefix: VendorPrefix, /// The declarations within the `@viewport` rule. - // PORT NOTE: `DeclarationBlock<'bump>` borrows the parser arena; lifetime - // erased to `'static` here per the rules/mod.rs `CssRule` PORT NOTE + // `DeclarationBlock<'bump>` borrows the parser arena; the lifetime is + // erased to `'static` here, matching `CssRule` in rules/mod.rs // (the `'bump` arena lifetime is re-threaded crate-wide in one pass). pub declarations: DeclarationBlock<'static>, /// The location of the rule in the source file. @@ -27,8 +27,6 @@ impl ViewportRule { impl ViewportRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `VendorPrefix` is a - // `Copy` bitflag (generics.zig "simple copy types" → identity). Self { vendor_prefix: self.vendor_prefix, declarations: super::dc::decl_block_static(&self.declarations, bump), @@ -36,5 +34,3 @@ impl ViewportRule { } } } - -// ported from: src/css/rules/viewport.zig diff --git a/src/css/selectors/builder.rs b/src/css/selectors/builder.rs index 35969127a18..2960324c722 100644 --- a/src/css/selectors/builder.rs +++ b/src/css/selectors/builder.rs @@ -86,9 +86,8 @@ impl SelectorBuilder { /// by the given combinator. #[inline] pub(crate) fn push_combinator(&mut self, combinator: Combinator) { - // PORT NOTE: `SmallList::append/insert` no longer take an arena — - // it owns its spill buffer (global arena). The `bump` field is - // retained for `BuildResult.components` (BumpVec) only. + // `SmallList` owns its spill buffer (global arena); the `bump` field + // is retained for `BuildResult.components` (BumpVec) only. self.combinators.append((combinator, self.current_len)); self.current_len = 0; } @@ -105,9 +104,8 @@ impl SelectorBuilder { self.simple_selectors.insert(0, GenericComponent::Nesting); } - // PORT NOTE: Zig `deinit` only freed `simple_selectors` and `combinators`. - // In Rust, `SmallList` owns its spill buffer and frees on `Drop`, so no - // explicit `Drop` impl is needed here. + // `SmallList` owns its spill buffer and frees on `Drop`, so no explicit + // `Drop` impl is needed here. /// Consumes the builder, producing a Selector. /// @@ -129,10 +127,9 @@ impl SelectorBuilder { if parsed_part { flags |= SelectorFlags::HAS_PART; } - // `build_with_specificity_and_flags()` will - // PORT NOTE: Zig had `defer this.deinit()` here to free SmallList capacity - // after building. In Rust, `Drop` on `SelectorBuilder` handles this when the - // builder goes out of scope; the call below already drains the contents. + // `build_with_specificity_and_flags()` drains the contents; `Drop` on + // `SelectorBuilder` frees the SmallList capacity when the builder goes + // out of scope. self.build_with_specificity_and_flags(SpecificityAndFlags { specificity, flags }) } @@ -151,8 +148,7 @@ impl SelectorBuilder { &mut self, spec: SpecificityAndFlags, ) -> BuildResult { - // PORT NOTE: reshaped for borrowck — capture combinators.len() - // before borrowing simple_selectors.slice(). + // Capture combinators.len() before borrowing simple_selectors.slice(). let combinators_len = self.combinators.len(); let (rest, current) = split_from_end::>( @@ -170,7 +166,6 @@ impl SelectorBuilder { loop { if current_simple_selectors_i < current_simple_selectors.len() { - // PORT NOTE: Zig copies the component by value here (struct copy). // `GenericComponent` is not `Copy`; we bitwise-move it out // via `ptr::read` — sound because every element of // `simple_selectors` is consumed exactly once across the loop, @@ -222,5 +217,3 @@ pub(crate) fn split_from_end(s: &[T], at: usize) -> (&[T], &[T]) { let midpoint = s.len() - at; (&s[0..midpoint], &s[midpoint..]) } - -// ported from: src/css/selectors/builder.zig diff --git a/src/css/selectors/mod.rs b/src/css/selectors/mod.rs index 4f5433deff0..6e80b609962 100644 --- a/src/css/selectors/mod.rs +++ b/src/css/selectors/mod.rs @@ -13,8 +13,7 @@ //! the two serializer namespaces (`serialize::*`, `tocss_servo::*`). //! `builder.rs` carries `SelectorBuilder`. //! -//! The `impl_::Selectors` marker (Rust trait-based reshaping of Zig's -//! `selector.impl.Selectors.SelectorImpl` type-alias namespace) lives here in +//! The `impl_::Selectors` marker lives here in //! the hub so the parser↔selector cycle has a single anchor; both files reach //! it via `bun_css::selector::impl_` / `super::impl_`. @@ -25,9 +24,9 @@ pub mod selector; pub use parser::{Component, PseudoClass, PseudoElement, Selector, SelectorList}; -/// Our implementation of the `SelectorImpl` interface — the Rust-shaped -/// equivalent of Zig's `selector.impl.Selectors`. Defined in the hub (not in -/// `selector.rs`) to break the parser↔selector dependency cycle: `parser.rs` +/// Our implementation of the `SelectorImpl` interface. Defined in the hub +/// (not in `selector.rs`) to break the parser↔selector dependency cycle: +/// `parser.rs` /// needs `impl_::Selectors` to instantiate `Component`/`Selector`/ /// `SelectorList`, and `selector.rs` needs those instantiations. pub mod impl_ { @@ -48,7 +47,7 @@ pub mod impl_ { type LocalIdentifier = IdentOrRef; type LocalName = Ident; type NamespacePrefix = Ident; - // TODO(port): lifetime — Zig `[]const u8` type alias borrowing input. + // TODO: lifetime — should borrow the parser input. type NamespaceUrl = &'static [u8]; type BorrowedNamespaceUrl = &'static [u8]; type BorrowedLocalName = Ident; diff --git a/src/css/selectors/parser.rs b/src/css/selectors/parser.rs index 49bffbe820d..f82080fb625 100644 --- a/src/css/selectors/parser.rs +++ b/src/css/selectors/parser.rs @@ -1,4 +1,4 @@ -//! CSS selector parser — ported from `src/css/selectors/parser.zig`. +//! CSS selector parser. //! Originally derived from servo/lightningcss selector parsing. use core::fmt; @@ -23,11 +23,10 @@ pub use bun_css::Printer as PrinterRe; // re-export parity (Printer/PrintErr wer /// `css::Result` — the CSS parser result type (`Ok(T)` / `Err(css::ParseError)`). type CResult = css::Result; -// TODO(port): arena lifetimes. The Zig code threads `parser.arena` / `input.arena()` -// (a bump arena) through every allocation. The Rust port uses `Vec`/`Box` and a `Str` alias for -// source-borrowed byte slices; re-thread `'bump` and switch to -// `bun_alloc::ArenaVec<'bump, T>` / `&'bump [u8]` per PORTING.md §Allocators (AST crates). -// PERF(port): was arena bulk-free — profile if it shows up on a hot path. +// Arena lifetimes: this module uses `Vec`/`Box` and a `Str` +// alias for source-borrowed byte slices until the crate-wide `'bump` re-threading +// (`bun_alloc::ArenaVec<'bump, T>` / `&'bump [u8]` per PORTING.md §Allocators +// (AST crates)) lands. // // NOTE: `Str` is `&'static [u8]` here (not `crate::Str = *const [u8]`) to match // `crate::Token`'s payload shape (`Token::Ident(&'static [u8])` etc.) — every @@ -37,8 +36,7 @@ type CResult = css::Result; type Str = &'static [u8]; // arena-backed `[]const u8` source slice // ─── Protocol traits ───────────────────────────────────────────────────────── -// Zig's `implementEql` / `implementHash` / `implementDeepClone` are comptime -// field/variant reflection over `@typeInfo(T)` — in Rust this is the body of +// `eql` / `hash` / `deep_clone` are field/variant reflection, provided by // `#[derive(CssEql, CssHash, DeepClone)]` (`bun_css_derive`). Non-generic // grammar types below carry the derive directly; the ``- // generic types hand-write bodies (the derive's `where Impl: CssEql` bound is @@ -50,7 +48,7 @@ use css::generics::{CssEql, CssHash}; /// Drain a `SmallList` into a `Box<[T]>`. `SmallList` has no `into_vec`; /// this bitwise-moves each element out and `set_len(0)`s the source so its -/// `Drop` doesn't double-free. Mirrors Zig `toOwnedSlice`. +/// `Drop` doesn't double-free. fn small_list_into_box(mut sl: SmallList) -> Box<[T]> { let len = sl.len() as usize; let mut v: Vec = Vec::with_capacity(len); @@ -67,13 +65,11 @@ fn small_list_into_box(mut sl: SmallList) -> Box<[T]> { } /// Allocate an ASCII-lowercased copy of `name` in the parse-session bump arena. -/// Zig used `parser.arena().alloc(u8, n)` (the bump arena owns the buffer -/// for the parse session and frees it on arena reset). Returns a raw arena +/// Returns a raw arena /// pointer (`*const [u8]`) — `Ident.v`'s field type — so we don't fabricate a /// `'static` lifetime (PORTING.md §Forbidden: never `Box::leak` to satisfy /// `&'static`). Re-threading `&'bump Bump` would widen `Ident.v` to /// `&'bump [u8]`. -// PERF(port): was arena alloc — profile if it shows up on a hot path. #[inline] fn arena_lowercase(bump: &Bump, name: &[u8]) -> *const [u8] { let buf = bump.alloc_slice_fill_copy(name.len(), 0u8); @@ -123,13 +119,12 @@ pub const SELECTOR_WHITESPACE: &[u8] = &[b' ', b'\t', b'\n', b'\r', 0x0C]; /// Compile-time check that `T` satisfies the `SelectorImpl` trait shape. /// In Rust this is expressed as a trait bound; this fn is kept for diff parity. pub fn valid_selector_impl() { - // Zig used `_ = T.SelectorImpl.X;` to force decl resolution; in Rust the trait - // bound `T: SelectorImpl` is the check. + // The trait bound `T: SelectorImpl` is the check. } -/// The `SelectorImpl` shape (Zig validated via `ValidSelectorImpl`). Implemented +/// The `SelectorImpl` shape. Implemented /// by `impl_::Selectors` in `bun_css::selector::impl_`. -// PORT NOTE: `PartialEq + Clone` bounds dropped — the concrete assoc types +// `PartialEq + Clone` bounds dropped — the concrete assoc types // (`values::ident::{Ident,IdentOrRef}`, `*const [u8]`) implement structural // equality via the `CssEql` protocol (`generics::implement_eql`), not // `core::cmp::PartialEq`. Every `eql`/`deep_clone`/`hash` callsite in this @@ -152,9 +147,8 @@ pub trait SelectorImpl: Sized { /// Constrained `SelectorImpl` with the concrete assoc-type bundle Bun uses. /// -/// PORT NOTE: in Zig the `parse_*` functions were `comptime Impl: type` generics -/// but every body assumed the concrete `selector.impl.Selectors` shapes (it was -/// the only instantiation). Rust can't see through the open `Impl::LocalName` +/// Every `parse_*` body assumes the concrete `selector.impl.Selectors` shapes +/// (the only instantiation). Rust can't see through the open `Impl::LocalName` /// to `Ident`, so the parse functions bound on this sub-trait instead — the /// associated-type equality clauses make `Impl::LocalName == Ident` etc. /// visible to the body without monomorphizing the signature. @@ -328,7 +322,7 @@ pub mod attrs { }, } - // PORT NOTE: implemented for the concrete `AttrValue = css::CSSString` + // Implemented for the concrete `AttrValue = css::CSSString` // (= `*const [u8]`) only — the sole `BunSelectorImpl` instantiation. impl ParsedAttrSelectorOperation { pub fn deep_clone(&self) -> Self { @@ -559,7 +553,10 @@ fn compute_simple_selector_specificity( // Does not affect specificity } C::Nesting => { - // TODO + // No specificity contribution here. Whether `&` should + // contribute specificity at this point is unresolved; + // nesting substitution happens at print time, after + // specificity is computed. } } } @@ -623,7 +620,7 @@ fn parse_selector( any_whitespace = true; continue; } - // PORT NOTE: `Token::Delim` carries `u32` codepoint; cast to + // `Token::Delim` carries `u32` codepoint; cast to // `u8` for ASCII match (all CSS combinator delims are ASCII). Token::Delim(d) => match u8::try_from(*d).ok() { Some(b'>') => { @@ -736,7 +733,6 @@ fn parse_compound_selector( } if parse_type_selector::(parser, input, *state, builder).is_ok() { - // Note: Zig `.asValue()` here means "if Ok"; the bool result is unused. empty = false; } @@ -874,7 +870,6 @@ fn parse_relative_selector( selector .components .push(GenericComponent::Combinator(wombo_combo)); - // PERF(port): was assume_capacity (catch unreachable on arena) selector.components.push(scope); } @@ -884,20 +879,18 @@ fn parse_relative_selector( /// Compile-time validation of the `SelectorParser` shape. In Rust the methods are /// inherent on `SelectorParser`; this is a no-op kept for diff parity. pub fn valid_selector_parser() { - // Zig: `_ = T.SelectorParser.parseSlotted;` etc. — structural duck-typing check. - // In Rust these are inherent methods on `SelectorParser`; nothing to validate at runtime. + // These are inherent methods on `SelectorParser`; nothing to validate at runtime. } /// The [:dir()](https://drafts.csswg.org/selectors-4/#the-dir-pseudo) pseudo class. -// Re-export of the canonical `{ltr, rtl}` enum from `properties::text` — both Zig -// specs (selectors/parser.zig:700, properties/text.zig:251) define the same -// `DefineEnumProperty` shape, so the Rust port shares one definition. The +// Re-export of the canonical `{ltr, rtl}` enum from `properties::text` — the +// selector and property grammars share one definition. The // `#[derive(DefineEnumProperty)]` on the canonical provides `parse`/`to_css`/ // `as_str`; `CssEql`/`CssHash`/`DeepClone` come from `generics::inherent_bridge`. pub use css::css_properties::text::Direction; /// A pseudo class. -// PORT NOTE: `PartialEq` derive dropped — `Local`/`Global` carry +// `PartialEq` derive dropped — `Local`/`Global` carry // `Box` and `CustomFunction` carries `TokenList`, neither of which // implements `PartialEq`. Equality goes through `eql()` (CssEql protocol). #[derive(Clone, CssEql, CssHash)] @@ -907,7 +900,6 @@ pub enum PseudoClass { Lang { /// A list of language codes. languages: Vec, - // PERF(port): was arena ArrayList — profile if it shows up on a hot path. }, /// The [:dir()](https://drafts.csswg.org/selectors-4/#the-dir-pseudo) pseudo class. Dir { @@ -1078,12 +1070,9 @@ impl PseudoClass { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // PERF(alloc): I don't like making these little allocations - // PORT NOTE: Zig builds a fresh `Printer` over an allocating writer, - // calls `serialize::serializePseudoClass`, then writes the buffer to - // `dest`. The buffered indirection only matters for length-dependent - // minification decisions made by callers (none here), so write - // directly to `dest` until `Printer::new_buffered` lands. + // A buffered intermediate `Printer` would only matter for + // length-dependent minification decisions made by callers (none + // here), so write directly to `dest`. serialize::serialize_pseudo_class(self, dest, None) } @@ -1206,14 +1195,12 @@ impl WebKitScrollbarPseudoElement { pub struct SelectorParser<'a> { pub is_nesting_allowed: bool, pub options: &'a ParserOptions<'a>, - // `arena: Allocator` dropped — arena threaded via `input.arena()` in Zig. - // PERF(port): was arena bulk-free — re-thread `&'bump Bump` to restore. + // PERF: re-thread `&'bump Bump` here to restore arena allocation. } -// Zig: `pub const Impl = impl_.Selectors;` lived inside the struct for -// `ValidSelectorParser`'s comptime decl-probe. Rust inherent associated types -// are unstable (rust#8995); the equivalent contract is the `BunSelectorImpl` -// blanket impl above, so expose the alias at module scope instead. +// Rust inherent associated types are unstable (rust#8995); the equivalent +// contract is the `BunSelectorImpl` blanket impl above, so expose the alias +// at module scope instead. pub type SelectorParserImpl = impl_::Selectors; impl<'a> SelectorParser<'a> { @@ -1259,13 +1246,11 @@ impl<'a> SelectorParser<'a> { name: Str, input: &mut CssParser, ) -> CResult { - // Spec parity: parser.zig:1054 uses `ComptimeEnumMap.get(name)` which is - // CASE-SENSITIVE (`ComptimeStringMap.get`, not `getAnyCase`/`getASCIIICaseInsensitive`). - // `::CUE(..)` / `::View-Transition-Group(..)` therefore fall through to - // `CustomFunction` in the spec — match that here by looking up `name` - // verbatim with no case folding. + // This lookup is intentionally CASE-SENSITIVE: `::CUE(..)` / + // `::View-Transition-Group(..)` fall through to `CustomFunction`, + // so look up `name` verbatim with no case folding. // - // PERF(port): 6 entries with near-unique lengths (3/10/19/19/21/26) — + // PERF: 6 entries with near-unique lengths (3/10/19/19/21/26) — // a length-gated `match` rejects the overwhelmingly-common miss path // (unknown `::-webkit-foo(...)` etc.) on a single `usize` compare, // versus phf's hash + 2 table loads + slice compare. Only len==19 has @@ -1351,9 +1336,6 @@ impl<'a> SelectorParser<'a> { ) -> CResult { // @compileError(css.todo_stuff.match_ignore_ascii_case); let pseudo_class: PseudoClass = 'pseudo_class: { - // TODO(port): phf custom hasher — Zig used `ComptimeStringMap.getAnyCase` - // (ASCII case-insensitive). Generate a case-folded phf or use a - // `match` over the lowercased name. if let Some(pseudo) = lookup_non_ts_pseudo_class(name) { break 'pseudo_class pseudo; } @@ -1386,7 +1368,7 @@ impl<'a> SelectorParser<'a> { ) -> CResult { let pseudo_class = crate::match_ignore_ascii_case! { name, { b"lang" => { - // PORT NOTE: `expect_ident_or_string` returns `&'_ [u8]` + // `expect_ident_or_string` returns `&'_ [u8]` // (lifetime-tied to `&mut self`), which can't satisfy // `parse_comma_separated`'s HRTB. Clone the token to extract // the underlying `&'static [u8]` payload directly. @@ -1466,7 +1448,6 @@ impl<'a> SelectorParser<'a> { loc: css::SourceLocation, name: Str, ) -> CResult { - // TODO(port): phf custom hasher — Zig used `ComptimeStringMap.getCaseInsensitiveWithEql`. let pseudo_element = lookup_pseudo_element(name).unwrap_or_else(|| { if !strings::starts_with_char(name, b'-') { self.options.warn(&loc.new_custom_error( @@ -1481,7 +1462,6 @@ impl<'a> SelectorParser<'a> { } /// Case-insensitive lookup table for `parse_non_ts_pseudo_class`. -/// Mirrors the `ComptimeStringMap` at parser.zig:1120. fn lookup_non_ts_pseudo_class(name: &[u8]) -> Option { use PseudoClass as P; use WebKitScrollbarPseudoClass as WS; @@ -1571,7 +1551,6 @@ fn lookup_non_ts_pseudo_class(name: &[u8]) -> Option { } /// Case-insensitive lookup table for `parse_pseudo_element`. -/// Mirrors the `ComptimeStringMap` at parser.zig:1333. fn lookup_pseudo_element(name: &[u8]) -> Option { use PseudoElement as PE; use WebKitScrollbarPseudoElement as WS; @@ -1649,7 +1628,9 @@ impl GenericSelectorList { /// `DebugFmt` wrapper — implements `Display` over a borrowed list (debug builds only). pub struct SelectorListDebugFmt<'a, Impl: SelectorImpl>(pub &'a GenericSelectorList); -impl<'a, Impl: BunSelectorImpl> fmt::Display for SelectorListDebugFmt<'a, Impl> { +// Concrete `impl_::Selectors` only — `sel.debug()` formatting requires the +// concrete `Display` impl on `SelectorDebugFmt` (see the comment there). +impl<'a> fmt::Display for SelectorListDebugFmt<'a, impl_::Selectors> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if !cfg!(debug_assertions) { return Ok(()); @@ -1757,13 +1738,11 @@ impl GenericSelectorList { nesting_requirement: NestingRequirement, ) -> CResult { let original_state = *state; - // TODO: Think about deinitialization in error cases let mut values: SmallList, 1> = SmallList::default(); loop { - // PORT NOTE: reshaped for borrowck — Zig used a `Closure` struct capturing - // `&mut state` and `&mut parser`; Rust captures a local `saw_nesting` flag - // and applies it to `state` after the closure returns (no raw `*mut`). + // For borrowck, the closure captures a local `saw_nesting` flag + // and applies it to `state` after it returns (no raw `*mut`). let mut saw_nesting = false; let selector = input.parse_until_before(css::Delimiters::COMMA, |input2: &mut CssParser| { @@ -1787,7 +1766,6 @@ impl GenericSelectorList { match selector { Ok(sel) => { values.append(sel); - // PERF(port): was arena append — profile if it shows up on a hot path. } Err(e) => match recovery { ParseErrorRecovery::DiscardList => return Err(e), @@ -1806,8 +1784,8 @@ impl GenericSelectorList { } } - // TODO: this looks exactly the same as `parse_with_state()` except it uses - // `parse_relative_selector()` instead of `parse_selector()` + // Same shape as `parse_with_state()` but parses each item with + // `parse_relative_selector()` instead of `parse_selector()`. pub fn parse_relative_with_state( parser: &mut SelectorParser, input: &mut CssParser, @@ -1816,11 +1794,10 @@ impl GenericSelectorList { nesting_requirement: NestingRequirement, ) -> CResult { let original_state = *state; - // TODO: Think about deinitialization in error cases let mut values: SmallList, 1> = SmallList::default(); loop { - // PORT NOTE: reshaped for borrowck — capture a local flag instead of a + // Reshaped for borrowck — capture a local flag instead of a // raw `*mut SelectorParsingState`, then fold into `state` after return. let mut saw_nesting = false; let selector = @@ -1926,16 +1903,47 @@ pub struct GenericSelector { pub struct SelectorDebugFmt<'a, Impl: SelectorImpl>(pub &'a GenericSelector); -impl<'a, Impl: SelectorImpl> fmt::Display for SelectorDebugFmt<'a, Impl> { +// Implemented for the concrete `impl_::Selectors` only (the crate's sole +// `SelectorImpl`): `tocss_servo::to_css_selector` serializes the concrete +// `Selector`, not a generic `GenericSelector`. +impl<'a> fmt::Display for SelectorDebugFmt<'a, impl_::Selectors> { + // `IN_DEBUG_FMT` only exists under `#[cfg(debug_assertions)]` + // (printer.rs), so the whole serialization body is compiled out of + // release builds rather than gated at runtime. + #[cfg(not(debug_assertions))] + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + Ok(()) + } + + #[cfg(debug_assertions)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if !cfg!(debug_assertions) { - return Ok(()); + // Serialize through a buffered + // `Printer` over an empty symbol map. `IN_DEBUG_FMT` makes + // `lookup_ident_or_ref` fall back to `debug_ident` instead of + // consulting the (empty) symbol table. + let arena = Bump::new(); + let mut buf: Vec = Vec::new(); + let symbols = bun_ast::symbol::Map::default(); + // Unwind-safe reset of the flag: + // Debug formatting commonly runs while building panic messages, so a + // panic inside `to_css_selector` must not leak the flag thread-wide. + struct InDebugFmtGuard; + impl Drop for InDebugFmtGuard { + fn drop(&mut self) { + crate::printer::IN_DEBUG_FMT.with(|flag| flag.set(false)); + } + } + crate::printer::IN_DEBUG_FMT.with(|flag| flag.set(true)); + let result = { + let _guard = InDebugFmtGuard; + let mut printer = Printer::new_buffered(&arena, &mut buf, None, None, &symbols); + css::selector::tocss_servo::to_css_selector(self.0, &mut printer) + }; + write!(f, "Selector(")?; + match result { + Ok(()) => write!(f, "{}", bstr::BStr::new(&buf)), + Err(e) => writeln!(f, "", e.name()), } - // TODO(port): the Zig builds a fresh `Printer` and calls - // `tocss_servo::to_css_selector` into a buffer, then writes the buffer. - // blocked_on: `Printer::new_buffered` + `SymbolMap::default` (debug- - // only path; serialization body lives in `selector::tocss_servo`). - write!(f, "Selector(<{} components>)", self.0.components.len()) } } @@ -2233,7 +2241,7 @@ impl GenericComponent { } pub fn deep_clone(&self) -> Self { - // PORT NOTE: hand-written variant-walk (Zig `implementDeepClone`). + // Hand-written variant-walk. // Every borrowed payload (`Str`, `Ident.v`, `IdentOrRef`) is an // arena-static identity copy; owning containers (`Vec`/`Box`) recurse. use GenericComponent as C; @@ -2422,11 +2430,11 @@ impl GenericComponent { pub fn hash(&self, hasher: &mut Wyhash) { use GenericComponent as C; - // Zig `implementHash`: `bun.writeAnyToHasher(@intFromEnum(this))` then payload. + // Hash a variant tag, then the payload. // SAFETY: `GenericComponent` is `#[repr(Rust)]`; reading the discriminant // via `core::mem::discriminant` is stable but not byte-hashable. Use a - // per-arm tag instead (CSS hashing is in-process dedup only — self- - // consistency, not Zig-byte-identity, is the contract). + // per-arm tag instead (CSS hashing is in-process dedup only — + // self-consistency is the contract). macro_rules! tag { ($n:expr) => { hasher.update(&($n as u32).to_ne_bytes()) @@ -2567,14 +2575,41 @@ impl CssHash for GenericComponent { impl fmt::Display for GenericComponent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO(port): Zig matches on a few variants and falls through to `@tagName`. - // Rust enums need `strum::IntoStaticStr` for the tag name. + // A few variants get detail, the rest print + // their tag name. `BunSelectorImpl` + // pins `PseudoElement`/`LocalIdentifier` to the concrete types, whose + // `Display` impls handle the payloads. match self { Self::LocalName(ln) => write!(f, "local_name={}", bstr::BStr::new(ln.name.v())), Self::Combinator(c) => write!(f, "combinator='{}'", c), - Self::PseudoElement(_) => write!(f, "pseudo_element=<..>"), - Self::Class(_) => write!(f, "class=<..>"), - _ => write!(f, ""), + Self::PseudoElement(pe) => write!(f, "pseudo_element={}", pe), + Self::Class(c) => write!(f, "class={}", c), + Self::ExplicitAnyNamespace => f.write_str("explicit_any_namespace"), + Self::ExplicitNoNamespace => f.write_str("explicit_no_namespace"), + Self::DefaultNamespace(_) => f.write_str("default_namespace"), + Self::Namespace { .. } => f.write_str("namespace"), + Self::ExplicitUniversalType => f.write_str("explicit_universal_type"), + Self::Id(_) => f.write_str("id"), + Self::AttributeInNoNamespaceExists { .. } => { + f.write_str("attribute_in_no_namespace_exists") + } + Self::AttributeInNoNamespace { .. } => f.write_str("attribute_in_no_namespace"), + Self::AttributeOther(_) => f.write_str("attribute_other"), + Self::Negation(_) => f.write_str("negation"), + Self::Root => f.write_str("root"), + Self::Empty => f.write_str("empty"), + Self::Scope => f.write_str("scope"), + Self::Nth(_) => f.write_str("nth"), + Self::NthOf(_) => f.write_str("nth_of"), + Self::NonTsPseudoClass(_) => f.write_str("non_ts_pseudo_class"), + Self::Slotted(_) => f.write_str("slotted"), + Self::Part(_) => f.write_str("part"), + Self::Host(_) => f.write_str("host"), + Self::Where(_) => f.write_str("where"), + Self::Is(_) => f.write_str("is"), + Self::Any { .. } => f.write_str("any"), + Self::Has(_) => f.write_str("has"), + Self::Nesting => f.write_str("nesting"), } } } @@ -2984,8 +3019,8 @@ impl SelectorParseErrorKind { } pub fn into_selector_error(self) -> css::SelectorError { - // PORT NOTE: `error.rs::SelectorError` variants are snake_case - // (`#[allow(non_camel_case_types)]` Zig-tagName parity). + // `error.rs::SelectorError` variants are snake_case + // (`#[allow(non_camel_case_types)]`). use SelectorParseErrorKind as K; use css::SelectorError as S; match self { @@ -3044,7 +3079,7 @@ pub enum SimpleSelectorParseResult { } /// A pseudo element. -// PORT NOTE: see PseudoClass — `PartialEq` derive dropped (Box/TokenList). +// See PseudoClass — `PartialEq` derive dropped (Box/TokenList). #[derive(Clone, CssEql, CssHash)] pub enum PseudoElement { /// The [::after](https://drafts.csswg.org/css-pseudo-4/#selectordef-after) pseudo element. @@ -3201,17 +3236,37 @@ impl PseudoElement { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // PERF(alloc): I don't like making small allocations here for the string. - // PORT NOTE: see PseudoClass::to_css — write directly until - // `Printer::new_buffered` lands. + // See PseudoClass::to_css — write directly to `dest`; no caller makes + // length-dependent minification decisions here. serialize::serialize_pseudo_element(self, dest, None) } } impl fmt::Display for PseudoElement { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO(port): @tagName — needs strum::IntoStaticStr. - write!(f, "") + f.write_str(match self { + Self::After => "after", + Self::Before => "before", + Self::FirstLine => "first_line", + Self::FirstLetter => "first_letter", + Self::Selection(_) => "selection", + Self::Placeholder(_) => "placeholder", + Self::Marker => "marker", + Self::Backdrop(_) => "backdrop", + Self::FileSelectorButton(_) => "file_selector_button", + Self::WebkitScrollbar(_) => "webkit_scrollbar", + Self::Cue => "cue", + Self::CueRegion => "cue_region", + Self::CueFunction { .. } => "cue_function", + Self::CueRegionFunction { .. } => "cue_region_function", + Self::ViewTransition => "view_transition", + Self::ViewTransitionGroup { .. } => "view_transition_group", + Self::ViewTransitionImagePair { .. } => "view_transition_image_pair", + Self::ViewTransitionOld { .. } => "view_transition_old", + Self::ViewTransitionNew { .. } => "view_transition_new", + Self::Custom { .. } => "custom", + Self::CustomFunction { .. } => "custom_function", + }) } } @@ -3325,7 +3380,6 @@ pub fn parse_type_selector( sink.push_simple_selector(GenericComponent::LocalName(LocalName { lower_name: { // PERF: check if it's already lowercase - // PERF(port): was arena alloc — profile if hot (see `arena_lowercase`). Ident { v: arena_lowercase(input.arena(), name), } @@ -3427,7 +3481,6 @@ pub fn parse_one_simple_selector( let names = input.parse_nested_block( |input2: &mut CssParser| -> CResult> { // todo_stuff.think_about_mem_mgmt - // PERF(port): was arena ArrayList with capacity 1 — profile if hot. let mut result: Vec = Vec::with_capacity(1); result.push(Ident { @@ -3464,8 +3517,6 @@ pub fn parse_one_simple_selector( input.parse_nested_block(|i: &mut CssParser| { parser.parse_functional_pseudo_element(name, i) })? - // TODO(port): `Impl::PseudoElement` is `PseudoElement` for the concrete - // `impl_::Selectors`; the generic path would need a `From`/trait bound. } else { parser.parse_pseudo_element(location, name)? }; @@ -3571,7 +3622,6 @@ pub fn parse_attribute_selector( Ok(v) => v.clone(), Err(_) => { // [foo] - // PERF(port): was arena alloc — profile if hot (see `arena_lowercase`). let local_name_lower: *const [u8] = arena_lowercase(input.arena(), local_name); if let Some(ns) = namespace { let x = attrs::AttrSelectorWithOptionalNamespace:: { @@ -3618,7 +3668,7 @@ pub fn parse_attribute_selector( )); }; - // PORT NOTE: `expect_ident_or_string` returns `&'_ [u8]` (lifetime-tied to + // `expect_ident_or_string` returns `&'_ [u8]` (lifetime-tied to // `&mut *input`); `parse_attribute_flags(input)` below needs `input` again. // Clone the token so the borrow is released before we re-borrow. let value_str: Str = { @@ -3657,7 +3707,6 @@ pub fn parse_attribute_selector( }; if let Some(first_uppercase) = first_uppercase { let str_ = &local_name[first_uppercase..]; - // PERF(port): was arena alloc — profile if hot (see `arena_lowercase`). let lowered: *const [u8] = arena_lowercase(input.arena(), str_); break 'brk (Ident { v: lowered }, false); } else { @@ -3776,8 +3825,6 @@ pub fn parse_functional_pseudo_class( let result = parser.parse_non_ts_functional_pseudo_class(name, input)?; Ok(GenericComponent::NonTsPseudoClass(result)) - // TODO(port): `Impl::NonTSPseudoClass` is `PseudoClass` for the concrete impl; - // generic path would need a `From` bound. } pub fn parse_simple_pseudo_class( @@ -3930,8 +3977,7 @@ where let selector_slice = inner.into_boxed_selectors(); - // PORT NOTE: Zig threaded extra `args_` through an ArgsTuple to `func`; in Rust - // the closure captures extras directly (e.g. `prefix` for `:any()`). + // The closure captures extra args directly (e.g. `prefix` for `:any()`). let result = func(selector_slice); Ok(result) @@ -4069,7 +4115,7 @@ pub fn parse_qualified_name( ); } } - // PORT NOTE: reshaped for borrowck — clone token before reset. + // Reshaped for borrowck — clone token before reset. let result_cloned = result.cloned(); input.reset(&after_star); if in_attr_selector { @@ -4195,8 +4241,7 @@ impl AttributeFlags { /// explicit `s`/`i` flag is given on the attribute selector. /// /// -/// PERF(port): Zig used `ComptimeEnumMap.has` (zero-cost membership at -/// comptime). An earlier `phf::Set` port paid, on every +/// PERF: an earlier `phf::Set` implementation paid, on every /// `[attr=val]` selector, a 32-bit FNV-ish hash over the name plus a /// bounds check, indirect load, and full key compare — measurable in CSS /// bundling profiles where the dominant inputs (`class`, `href`, `data-*`, @@ -4286,7 +4331,7 @@ pub enum ViewTransitionPartName { impl ViewTransitionPartName { pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // PORT NOTE: `CustomIdentFns::to_css` is ``-gated on + // `CustomIdentFns::to_css` is CSS-modules-gated via // `Printer::{css_module,write_ident}`; inline the // `write_ident(v, false)` body (CSS-modules custom-ident scoping is a // serializer concern, not a grammar concern — the gated impl just @@ -4371,5 +4416,3 @@ pub fn parse_attribute_flags(input: &mut CssParser) -> CResult { } crate::css_eql_partialeq!(NthSelectorData, SpecificityAndFlags, Combinator); - -// ported from: src/css/selectors/parser.zig diff --git a/src/css/selectors/selector.rs b/src/css/selectors/selector.rs index 1f5068221a9..f86a31e12e4 100644 --- a/src/css/selectors/selector.rs +++ b/src/css/selectors/selector.rs @@ -21,9 +21,8 @@ pub use parser::SelectorList; /// Our implementation of the `SelectorImpl` interface — the trait-based /// `impl_::Selectors` marker lives in the hub (`super::impl_`) so the -/// parser↔selector cycle has a single anchor. This module is the literal -/// Zig-shaped namespace (`selector.impl.Selectors.SelectorImpl.*` type -/// aliases) kept for diff parity with `selector.zig`. +/// parser↔selector cycle has a single anchor. This module holds the +/// `SelectorImpl` type aliases. pub use super::impl_; pub mod r#impl { use super::*; @@ -175,7 +174,6 @@ pub(crate) fn downlevel_component<'bump>( // https://drafts.csswg.org/selectors/#specificity-rules if selectors.len() > 1 && targets.should_compile_same(Feature::NotSelectorList) { let is: Selector = Selector::from_component(Component::Is({ - // PERF(port): was arena bulk-alloc — profile if hot. // `Component::Is` carries `Box<[Selector]>` (heap, not arena); // could re-thread `&'bump [Selector]` once the arena lifetime is plumbed. let mut new_selectors: Vec = Vec::with_capacity(selectors.len()); @@ -184,7 +182,6 @@ pub(crate) fn downlevel_component<'bump>( } new_selectors.into_boxed_slice() })); - // PERF(port): was appendAssumeCapacity *component = Component::Negation(vec![is].into_boxed_slice()); if targets.should_compile_same(Feature::IsSelector) { @@ -214,7 +211,6 @@ fn downlevel_dir<'bump>(bump: &'bump Bump, dir: parser::Direction, targets: &Tar // otherwise, use :is/:not, which may be further downleveled to e.g. :-webkit-any. if !targets.should_compile_same(Feature::LangSelectorList) { let c = Component::NonTsPseudoClass(PseudoClass::Lang { - // PERF(port): was appendSliceAssumeCapacity (arena) — could re-thread bump. languages: RTL_LANGS.to_vec(), }); if dir == parser::Direction::Ltr { @@ -230,14 +226,12 @@ fn downlevel_dir<'bump>(bump: &'bump Bump, dir: parser::Direction, targets: &Tar } fn lang_list_to_selectors<'bump>(_bump: &'bump Bump, langs: &[&'static [u8]]) -> Box<[Selector]> { - // PORT NOTE: Zig returned `[]Selector` (mutable arena slice). Here - // `Component::Is`/`Negation` carry `Box<[Selector]>`; could re-thread - // `&'bump [Selector]` once the arena lifetime is plumbed. + // `Component::Is`/`Negation` carry `Box<[Selector]>`; this could become + // `&'bump [Selector]` once the arena lifetime is plumbed through. let mut selectors: Vec = Vec::with_capacity(langs.len()); for lang in langs { selectors.push(Selector::from_component(Component::NonTsPseudoClass( PseudoClass::Lang { - // PERF(port): was appendAssumeCapacity (arena) languages: vec![*lang], }, ))); @@ -519,7 +513,7 @@ pub fn is_compatible(selectors: &[parser::Selector], targets: &Targets) -> bool /// A selector is considered unused if it contains a class or id component that exists in the set of unused symbols. pub fn is_unused( selectors: &[parser::Selector], - unused_symbols: &ArrayHashMap, ()>, // Zig `std.StringArrayHashMapUnmanaged(void)` + unused_symbols: &ArrayHashMap, ()>, symbols: &SymbolList, parent_is_unused: bool, ) -> bool { @@ -545,8 +539,8 @@ fn is_selector_unused( for component in selector.components.iter() { match component { Component::Class(ident) | Component::Id(ident) => { - // PORT NOTE: `IdentOrRef::as_original_string` is - // ``-gated (blocked_on bun_ast::symbol::List::at + // `IdentOrRef::as_original_string` is + // gated (blocked_on bun_ast::symbol::List::at // + Symbol.original_name). Inline the ident arm; the ref arm // (CSS-modules symbol-table lookup) is unreachable until // `Parser::add_symbol_for_name` un-gates (see @@ -559,9 +553,8 @@ fn is_selector_unused( continue; // blocked_on: as_original_string ref arm } }; - // PORT NOTE: Zig `unused_symbols.contains(actual_ident)` — - // adapted lookup to compare the borrowed `&[u8]` against - // owned `Box<[u8]>` keys without allocating. + // Look up the borrowed `&[u8]` against the map's owned + // `Box<[u8]>` keys without allocating. struct SliceAdapter; impl bun_collections::array_hash_map::ArrayHashAdapter<[u8], Box<[u8]>> for SliceAdapter { #[inline] @@ -865,9 +858,8 @@ pub mod serialize { let mut id: Vec = Vec::new(); let _ = css::serializer::serialize_identifier(value_bytes, &mut id); - // PORT NOTE: Zig routed through `css.to_css.string(CSSString, ...)`, which - // dispatches to `CSSStringFns.toCss` → `serialize_string`. Inline that here - // since `CssString` (`*const [u8]`) does not implement `generic::ToCss`. + // `serialize_string` is called directly here since `CssString` + // (`*const [u8]`) does not implement `generic::ToCss`. let mut s: Vec = Vec::new(); let _ = css::serializer::serialize_string(value_bytes, &mut s); @@ -1822,15 +1814,10 @@ impl<'a> CompoundSelectorIter<'a> { /// The iterator would return: /// ``` /// First slice: - /// .{ - /// .{ .local_name = "div" } - /// } + /// [ LocalName("div") ] /// /// Second slice: - /// .{ - /// .{ .local_name = "p" }, - /// .{ .class = "class" } - /// } + /// [ LocalName("p"), Class("class") ] /// ``` /// /// BUT, the selectors are stored in reverse order, so this code needs to split the components backwards. @@ -1874,5 +1861,3 @@ impl<'a> CompoundSelectorIter<'a> { None } } - -// ported from: src/css/selectors/selector.zig diff --git a/src/css/small_list.rs b/src/css/small_list.rs index f1b3c57201d..6749f308823 100644 --- a/src/css/small_list.rs +++ b/src/css/small_list.rs @@ -2,7 +2,7 @@ // The container itself now lives in `bun_collections::SmallList` (a thin // `#[repr(transparent)]` newtype over `smallvec::SmallVec<[T; N]>`). This file // keeps only the CSS-domain pieces that depend on `bun_css` types — the -// `ImageFallback` protocol and the two `getFallbacks` comptime branches — +// `ImageFallback` protocol and the two `get_fallbacks` flavors — // which the orphan rule prevents from living on the foreign `SmallList` type // as inherent methods. // @@ -17,7 +17,6 @@ // `grow_capacity`, manual `Drop`, and `SmallListIntoIter` (~800 lines of // `unsafe`) were a direct port of servo/rust-smallvec; that loop is now closed // back onto the upstream crate. -// ported from: src/css/small_list.zig pub use bun_collections::SmallList; @@ -28,11 +27,8 @@ pub use bun_collections::SmallList; // trait at the call site instead.) // ─── getFallbacks ────────────────────────────────────────────────────────── -// The Zig version uses `@hasDecl(T, "getImage")` and `T == TextShadow` comptime -// dispatch with a comptime-computed return type. In Rust this becomes a trait -// with associated type for the return. -/// Duck-typed protocol from the Zig source (`@hasDecl(T, "getImage")`): any +/// Protocol for any /// value type that carries an `Image` and can produce color/prefix fallbacks /// of itself. Implemented by `values::image::Image` and /// `properties::background::Background`. @@ -53,8 +49,8 @@ pub trait ImageFallback: Sized { // `ImageFallback for Image` is implemented alongside the type in // `crate::values::image` to avoid a duplicate impl here. -/// Port of Zig `SmallList(T, N).getFallbacks` for the `@hasDecl(T, "getImage")` -/// branch. The TextShadow branch is `get_fallbacks_text_shadow`. +/// `getFallbacks` for `ImageFallback` element types. +/// The TextShadow variant is `get_fallbacks_text_shadow`. /// /// Free-standing (was an inherent on `SmallList`) so it can live in this /// crate now that `SmallList` is foreign. The lone caller threads `self` @@ -75,7 +71,6 @@ pub mod fallbacks_gated { use crate::css_parser as css; use crate::properties::text::TextShadow; - // TODO(port): trait bound placeholder — any T with getImage()/withImage()/getFallback()/getNecessaryFallbacks() pub fn get_fallbacks_image( this: &mut SmallList, arena: &bun_alloc::Arena, diff --git a/src/css/sourcemap.rs b/src/css/sourcemap.rs index 1f217c1c858..154ab560710 100644 --- a/src/css/sourcemap.rs +++ b/src/css/sourcemap.rs @@ -1,11 +1,17 @@ pub struct SourceMap { - // TODO(port): []const u8 struct field in CSS (arena crate) — using raw slice ptr per PORTING.md; revisit ownership + // Arena-owned `[]const u8` erased to a raw slice pointer per PORTING.md + // §Allocators (AST crates); becomes `&'bump [u8]` once the CSS crate + // threads the arena lifetime. pub project_root: *const [u8], pub inner: SourceMapInner, } pub struct SourceMapInner { - // PERF(port): ArrayListUnmanaged in CSS arena crate — using Vec; may want bun_alloc::ArenaVec<'bump, T> if hot + // PERF: using Vec; may want bun_alloc::ArenaVec<'bump, T> if hot + // The `*const [u8]` elements of `sources`/`sources_content`/`names` are + // arena-owned slices erased to raw pointers per PORTING.md §Allocators + // (AST crates) — never dereferenced after the arena resets; they become + // `&'bump [u8]` once the CSS crate threads the arena lifetime. pub sources: Vec<*const [u8]>, pub sources_content: Vec<*const [u8]>, pub names: Vec<*const [u8]>, @@ -29,5 +35,3 @@ pub struct OriginalLocation { pub source: u32, pub name: Option, } - -// ported from: src/css/sourcemap.zig diff --git a/src/css/targets.rs b/src/css/targets.rs index 15a73a4404f..3d0463d1853 100644 --- a/src/css/targets.rs +++ b/src/css/targets.rs @@ -77,8 +77,6 @@ impl Targets { { let mut browsers = Browsers::default(); let mut has_any = false; - // PORT NOTE: Zig used `inline for (std.meta.fields(Browsers))` reflection. - // Expanded manually per field. macro_rules! check_field { ($field:ident, $env:literal) => { if let Some(val) = bun_core::getenv_z_any_case(bun_core::zstr!($env)) { @@ -137,9 +135,8 @@ impl Targets { } pub fn should_compile_same(&self, compat_feature: css::compat::Feature) -> bool { - // PERF(port): was comptime enum param — demoted to runtime (const-generic + // PERF: runtime dispatch (a const-generic param // would need #[derive(ConstParamTy)] on compat::Feature). - // Zig: comptime construct a Features with @field(feature, @tagName(compat_feature)) = true. let target_feature: Features = Features::from_compat(compat_feature); self.should_compile(compat_feature, target_feature) } @@ -185,8 +182,7 @@ pub struct Browsers { pub samsung: Option, } -// Zig: `pub const browserDefault = convertFromString(&.{...}) catch unreachable;` -// convert_from_string is not const-evaluable in Rust; compute once lazily. +// convert_from_string is not const-evaluable; compute once lazily. static BROWSER_DEFAULT: std::sync::LazyLock = std::sync::LazyLock::new(|| { Browsers::convert_from_string(&[ b"es2020", // support import.meta.url @@ -213,13 +209,14 @@ impl Browsers { } let number_part = &str[2..]; - // Zig: `try std.fmt.parseInt(u16, number_part, 10)` — propagates - // error.InvalidCharacter / error.Overflow. Preserve the tag for - // @errorName snapshot compat (do NOT collapse to UnsupportedCSSTarget). - // TODO(port): narrow error set (InvalidCharacter | Overflow) - let year = strings::parse_int::(number_part, 10) - .ok() - .ok_or_else(|| bun_core::err!("InvalidCharacter"))?; + // Propagates InvalidCharacter / Overflow. Preserve the tag for + // error-name snapshot compat (do NOT collapse to UnsupportedCSSTarget). + let year = strings::parse_int::(number_part, 10).map_err(|e| match e { + strings::ParseIntError::Overflow => bun_core::err!("Overflow"), + strings::ParseIntError::InvalidCharacter => { + bun_core::err!("InvalidCharacter") + } + })?; match year { // https://caniuse.com/?search=es2015 2015 => { @@ -320,7 +317,6 @@ impl Browsers { break 'entries_without_es &entries_buf[0..4]; } _ => { - // Zig had `if (@inComptime()) @compileLog(...)` here — no equivalent. return Err(bun_core::err!("UnsupportedCSSTarget")); } } @@ -390,8 +386,6 @@ impl Browsers { }; let version: u32 = ((major as u32) << 16) | ((minor as u32) << 8); - // Zig: `switch (browser.?) { inline else => |b| @field(browsers, @tagName(b)) ... }` - // PORT NOTE: reflection expanded into a direct match yielding a field ref. let slot: &mut Option = match browser { Browser::Chrome => &mut browsers.chrome, Browser::Edge => &mut browsers.edge, @@ -439,7 +433,6 @@ bitflags::bitflags! { const DOUBLE_POSITION_GRADIENTS = 1 << 17; const VENDOR_PREFIXES = 1 << 18; const LOGICAL_PROPERTIES = 1 << 19; - // __unused: u12 padding in Zig const SELECTORS = Self::NESTING.bits() | Self::NOT_SELECTOR_LIST.bits() @@ -469,9 +462,7 @@ impl Default for Features { impl Features { /// Map a `compat::Feature` enum variant to the same-named `Features` bitflag. /// - /// Zig did this via `@field(feature, @tagName(compat_feature)) = true` reflection - /// inside `shouldCompileSame` (a `comptime` parameter, so a non-matching variant - /// was a compile error). Rust takes the variant at runtime, so the table is + /// The variant is taken at runtime, so the table is /// hand-written: every `compat::Feature` whose snake_case tag matches a /// `Features` field gets an arm; any other variant is a programmer error. pub fn from_compat(compat_feature: css::compat::Feature) -> Features { @@ -495,9 +486,8 @@ impl Features { Feature::SpaceSeparatedColorNotation => Features::SPACE_SEPARATED_COLOR_NOTATION, Feature::FontFamilySystemUi => Features::FONT_FAMILY_SYSTEM_UI, Feature::DoublePositionGradients => Features::DOUBLE_POSITION_GRADIENTS, - // Zig: `@field` on a tag with no matching `Features` field is a - // *compile* error; in Rust the equivalent guard is a runtime - // unreachable since `compat_feature` is no longer `comptime`. + // A tag with no matching `Features` field is a programmer error, + // guarded by a runtime unreachable. _ => unreachable!( "compat::Feature::{:?} has no same-named targets::Features flag", compat_feature @@ -505,5 +495,3 @@ impl Features { } } } - -// ported from: src/css/targets.zig diff --git a/src/css/values/alpha.rs b/src/css/values/alpha.rs index 2113b195d0f..ad1b35ed634 100644 --- a/src/css/values/alpha.rs +++ b/src/css/values/alpha.rs @@ -13,8 +13,6 @@ pub struct AlphaValue { impl AlphaValue { pub(crate) fn parse(input: &mut Parser) -> Result { - // For some reason NumberOrPercentage.parse makes zls crash, using this instead. - // PORT NOTE: the Zig used `@call(.auto, @field(...))` as a zls workaround; direct call in Rust. let val: NumberOrPercentage = NumberOrPercentage::parse(input)?; let final_ = match val { NumberOrPercentage::Percentage(percent) => AlphaValue { v: percent.v }, @@ -26,9 +24,12 @@ impl AlphaValue { pub(crate) fn to_css(self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { CSSNumberFns::to_css(self.v, dest) } - - // TODO(port): css.implementHash (comptime field reflection) — wires once - // generics::CssHash blanket impl covers f32-payload structs. } -// ported from: src/css/values/alpha.zig +impl crate::generics::CssHash for AlphaValue { + /// Field-wise: hash the single `f32` payload. + #[inline] + fn hash(&self, hasher: &mut crate::generics::Wyhash) { + crate::generics::CssHash::hash(&self.v, hasher); + } +} diff --git a/src/css/values/angle.rs b/src/css/values/angle.rs index d200a55940f..f62cb9c17b3 100644 --- a/src/css/values/angle.rs +++ b/src/css/values/angle.rs @@ -198,7 +198,6 @@ impl Angle { pub(crate) fn op(self, other: Angle, ctx: C, op_fn: fn(C, f32, f32) -> f32) -> Angle { // PERF: not sure if this is faster - // PORT NOTE: reshaped for borrowck — Zig used packed-tag bit-twiddling switch; Rust match on (tag, tag) is equivalent. match (self, other) { (Angle::Deg(a), Angle::Deg(b)) => Angle::Deg(op_fn(ctx, a, b)), (Angle::Rad(a), Angle::Rad(b)) => Angle::Rad(op_fn(ctx, a, b)), @@ -206,7 +205,6 @@ impl Angle { (Angle::Turn(a), Angle::Turn(b)) => Angle::Turn(op_fn(ctx, a, b)), _ => Angle::Deg(op_fn(ctx, self.to_degrees(), other.to_degrees())), } - // PERF(port): was comptime monomorphization (fn-ptr arg) — profile if it shows up on a hot path. } pub(crate) fn sign(self) -> f32 { @@ -224,7 +222,6 @@ impl Angle { impl crate::generics::CssEql for Angle { #[inline] fn eql(&self, other: &Self) -> bool { - // Spec angle.zig:200-202 — `lhs.toDegrees() == rhs.toDegrees()`. // NOT structural variant comparison: Deg(180) eql Rad(PI) eql Turn(0.5). self.to_degrees() == other.to_degrees() } @@ -233,5 +230,3 @@ impl crate::generics::CssEql for Angle { /// A CSS [``](https://www.w3.org/TR/css-values-4/#typedef-angle-percentage) value. /// May be specified as either an angle or a percentage that resolves to an angle. pub(crate) type AnglePercentage = DimensionPercentage; - -// ported from: src/css/values/angle.zig diff --git a/src/css/values/calc.rs b/src/css/values/calc.rs index 3c15de0172e..1c6fec6b392 100644 --- a/src/css/values/calc.rs +++ b/src/css/values/calc.rs @@ -39,11 +39,10 @@ pub enum CalcUnit { } impl CalcUnit { - /// Zig: `bun.ComptimeEnumMap(CalcUnit).getAnyCase(f)` - // TODO(port): phf custom hasher — case-insensitive lookup over &[u8]. + /// Case-insensitive lookup of a `CalcUnit` by its function name. pub fn get_any_case(f: &[u8]) -> Option { - // PERF(port): Zig used a comptime perfect hash; this is a linear match on a - // stack-lowercased byte slice. TODO(perf): use a phf_map! over &[u8]. + // PERF: linear match on a + // stack-lowercased byte slice. Profile before swapping in a phf. // §Strings: source bytes are &[u8], never &str/String — no from_utf8/to_ascii_lowercase(). let (buf, len) = bun_core::strings::ascii_lowercase_buf::<5>(f)?; match &buf[..len] { @@ -115,7 +114,6 @@ pub enum Calc { } // ───────────────────────────── CalcValue trait ───────────────────────────── -// Replaces the Zig `switch (V)` / `@hasDecl(V, ...)` comptime-type dispatch. // Every type that can appear inside `Calc` implements this. // // The numeric protocol (`mul_f32`/`try_sign`/`try_map`/`try_op`/`try_op_to`/ @@ -139,9 +137,9 @@ pub trait CalcValue: + protocol::IsCompatible { fn add_internal(self, rhs: Self) -> Self; - /// Wrap a value as a `Calc` (Zig: `intoCalc`). + /// Wrap a value as a `Calc`. fn into_calc(self) -> Calc; - /// Convert a `Calc` into `Self` if representable (Zig: `intoValue`). + /// Convert a `Calc` into `Self` if representable. fn from_calc(c: Calc, input: &mut css::Parser) -> CssResult; fn eql(&self, other: &Self) -> bool; } @@ -194,8 +192,7 @@ impl Calc { { match self { Calc::Value(v) => { - // Zig: if (needs_deepclone) v.deepClone(arena) else v.* - // Rust: V: Clone covers both — V's Clone impl is the deep clone. + // V's Clone impl is the deep clone. Calc::Value(Box::new((**v).clone())) } Calc::Number(n) => Calc::Number(*n), @@ -218,7 +215,7 @@ impl Calc { Box::new(self.deep_clone()) } - // Zig `deinit` only freed owned Box fields → handled by Drop on Box/Box>/ + // Cleanup is handled by Drop on Box/Box>/ // Box>. No explicit Drop impl needed. pub fn eql(&self, other: &Self) -> bool @@ -259,32 +256,25 @@ impl Calc { impl Calc { fn mul_value_f32(lhs: V, rhs: f32) -> V { - // Zig: `f32 => lhs * rhs, else => lhs.mulF32(...)` — folded into trait. lhs.mul_f32(rhs) } - // TODO: addValueOwned pub fn add_value(lhs: V, rhs: V) -> V { - // Zig: `f32 => lhs + rhs, else => lhs.addInternal(...)` — folded into trait. lhs.add_internal(rhs) } - // TODO: intoValueOwned pub fn into_value(self, input: &mut css::Parser) -> CssResult { - // Zig comptime type switch on V → trait method `V::from_calc`. V::from_calc(self, input) } pub fn into_calc(val: V) -> Self { - // Zig: `f32 => .{ .value = box(val) }, else => val.intoCalc()` — folded into trait. val.into_calc() } - // TODO: change to addOwned() pub fn add(self, rhs: Self, input: &mut css::Parser) -> CssResult { if let (Calc::Value(_), Calc::Value(_)) = (&self, &rhs) { // PERF: we can reuse the allocation here - // PORT NOTE: reshaped for borrowck — clone out of boxes then drop originals. + // Reshaped for borrowck — clone out of boxes then drop originals. let (a, b) = match (self, rhs) { (Calc::Value(a), Calc::Value(b)) => (*a, *b), _ => unreachable!(), @@ -323,8 +313,6 @@ impl Calc { Ok(Self::into_calc(Self::add_value(this_value, rhs_value))) } - // TODO: users of this and `parseWith` don't need the pointer and often throwaway heap allocated values immediately - // use temp arena or something? pub fn parse(input: &mut css::Parser) -> CssResult { fn parse_with_fn(_: (), _: &[u8]) -> Option> { None @@ -338,7 +326,7 @@ impl Calc { parse_ident: F, ) -> CssResult { let location = input.current_source_location(); - // PORT NOTE: clone the token before reborrowing `input` so the function + // Clone the token before reborrowing `input` so the function // name slice is owned by the cloned `Token` (whose payload already // carries the parser's arena lifetime) instead of being laundered to // `'static` here. @@ -353,8 +341,7 @@ impl Calc { other => return Err(location.new_unexpected_token_error(other)), }; - // PORT NOTE: Zig used explicit `Closure` structs because Zig lacks closures. - // Rust closures capture `ctx` + `parse_ident` directly. + // The closures capture `ctx` + `parse_ident` directly. match unit { CalcUnit::Calc => { let calc = input.parse_nested_block(|i| Self::parse_sum(i, ctx, parse_ident))?; @@ -457,7 +444,7 @@ impl Calc { i, (), |_, a, b| { - // Zig `@mod(a, b)`: floored modulo, result takes sign of divisor. + // Floored modulo: result takes the sign of the divisor. // Equivalent to `a - b * floor(a / b)`. Rust `%` is truncated (sign of // dividend) and `rem_euclid` is non-negative, so neither matches for // negative `b` — use the explicit floor formula. @@ -476,9 +463,13 @@ impl Calc { i, (), |_, a, b| { - // return ((a % b) + b) % b; - // TODO(port): Zig used nested `@mod`; using Rust `%` per the commented - // formula. Verify edge cases (negative b, NaN). + // Floored modulo (result takes the sign of the divisor): + // `((a % b) + b) % b` over Rust's truncated `%` gets the + // sign right for NaN/inf and ordinary finite inputs. + // Note: this double-wrap formula applies a different rounding + // sequence than the explicit `a - b * floor(a / b)`, so results + // can differ by ~|b| near exact multiples of the divisor + // (e.g. f32 `mod(-3e-8, 1)` -> 0.99999994 vs 0.0). ((a % b) + b) % b }, |_, a, b| MathFunction::Mod { @@ -561,7 +552,6 @@ impl Calc { ctx: C, parse_ident: impl Fn(C, &[u8]) -> Option + Copy, ) -> CssResult { - // PERF(port): was comptime monomorphization on `op` — profile if hot. input.parse_nested_block(|i| { let v = Self::parse_numeric(i, ctx, parse_ident)?; Ok(Calc::Number(match op { @@ -724,7 +714,7 @@ impl Calc { } let location = input.current_source_location(); - // PORT NOTE: reshaped for borrowck — clone the next token inside the + // Reshaped for borrowck — clone the next token inside the // try-parse so the ident slice is owned by the cloned `Token` rather // than laundered to `'static` from the `&mut Parser` borrow. if let Ok(ident) = input.try_parse(|p| { @@ -751,7 +741,6 @@ impl Calc { ctx: C, parse_ident: impl Fn(C, &[u8]) -> Option + Copy, ) -> CssResult { - // PERF(port): was comptime monomorphization on `trig_fn_kind` — profile if hot. let trig_fn = move |x: f32| -> f32 { match trig_fn_kind { TrigFnKind::Sin => x.sin(), @@ -764,8 +753,8 @@ impl Calc { }; input.parse_nested_block(|i| { - // PORT NOTE: Zig wrapped `parse_ident` to project `Calc::Number` into - // `Calc::Number`. Rust closure does the same. + // Wrap `parse_ident` to project `Calc::Number` into + // `Calc::Number`. let parse_ident_fn = |_self: (), ident: &[u8]| -> Option> { let v = parse_ident(ctx, ident)?; if let Calc::Number(n) = v { @@ -774,8 +763,9 @@ impl Calc { None } }; - // TODO(port): Zig passed `&closure` (a *@This()) as ctx; here we use `()` and - // capture `ctx` via the outer closure. Verify `Calc::parse_sum` signature. + // ctx is `()` and the + // outer closure captures `ctx`/`parse_ident` (both `Copy`, satisfying + // `parse_sum`'s `C: Copy` / `F: Fn(C, &[u8]) -> Option + Copy`). let v = Calc::::parse_sum(i, (), parse_ident_fn)?; let rad: f32 = 'rad: { @@ -863,8 +853,8 @@ impl Calc { // blocked_on: values/length.rs un-gate — until Length is real, // `atan2(10px, 5px)` (and any other length-dimension pair) falls - // through to the CSSNumber path below and errors with `invalid_value`, - // diverging from Zig (`Angle::Rad(atan2(10,5))`). Tracked as a known + // through to the CSSNumber path below and errors with `invalid_value` + // instead of producing `Angle::Rad(atan2(10,5))`. Tracked as a known // incompleteness; no behaviour stub is added because a partial // dimension matcher would mis-reduce mixed-unit lengths. if let Ok(v) = try_parse_atan2_args::(input, ctx) { @@ -934,7 +924,6 @@ impl Calc { Ok(val) } - // PERF(port): `args` was arena bulk-free (ArrayList fed input.arena()) — profile if hot pub fn parse_hypot(args: &mut [Self]) -> CssResult> { if args.len() == 1 { let v = core::mem::replace(&mut args[0], Calc::Number(0.0)); @@ -1104,7 +1093,6 @@ impl Calc { /// I don't like how this function requires allocating a second ArrayList /// I am pretty sure we could do this reduction in place, or do it as the /// arguments are being parsed. - // PERF(port): `args`/`reduced` were arena bulk-free (ArrayList fed input.arena()) — profile if hot fn reduce_args(args: &mut Vec, order: Ordering) { // Reduces the arguments of a min() or max() expression, combining compatible values. // e.g. min(1px, 1em, 2px, 3in) => min(1px, 1em) @@ -1129,7 +1117,7 @@ impl Calc { } } - // PORT NOTE: reshaped for borrowck — Zig stored `?*This`; Rust stores index. + // For borrowck, `found` stores an index rather than a pointer. if let Some(maybe_idx) = found { if let Some(idx) = maybe_idx { reduced[idx] = core::mem::replace(arg, Calc::Number(420.0)); @@ -1137,14 +1125,13 @@ impl Calc { } } else { reduced.push(core::mem::replace(arg, Calc::Number(420.0))); - // PERF(port): was assume_capacity-free append continue; } - // arg dropped here (Zig: arg.deinit + dummy) + // arg dropped here *arg = Calc::Number(420.0); } - // Zig: css.deepDeinit(This, arena, args) — Rust: Drop on replace handles it. + // Drop on replace frees the old args. *args = reduced; } @@ -1194,10 +1181,8 @@ pub enum MathFunction { /// The `calc()` function. Calc(Calc), /// The `min()` function. - // PERF(port): was arena bulk-free (ArrayList fed input.arena()) — profile if hot Min(Vec>), /// The `max()` function. - // PERF(port): was arena bulk-free (ArrayList fed input.arena()) — profile if hot Max(Vec>), /// The `clamp()` function. Clamp { @@ -1220,7 +1205,6 @@ pub enum MathFunction { /// The `sign()` function. Sign(Calc), /// The `hypot()` function. - // PERF(port): was arena bulk-free (ArrayList fed input.arena()) — profile if hot Hypot(Vec>), } @@ -1392,7 +1376,7 @@ impl MathFunction { } } - // Zig `deinit` only freed owned Vec/Calc fields → handled by Drop. No explicit impl. + // Owned Vec/Calc fields are freed by Drop. No explicit impl. pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> where @@ -1540,7 +1524,6 @@ pub enum RoundingStrategy { } fn arr2(a: T, b: T) -> Vec { - // PERF(port): was arena bulk-free (ArrayList fed input.arena()) — profile if hot vec![a, b] } @@ -1566,7 +1549,7 @@ fn sqrtf32(v: f32) -> f32 { v.sqrt() } -/// Zig `std.math.sign` — returns -1.0, 0.0, or 1.0 (NOT Rust's `f32::signum`, which +/// Returns -1.0, 0.0, or 1.0 (NOT Rust's `f32::signum`, which /// returns ±1.0 for ±0.0). fn std_math_sign(v: f32) -> f32 { if v > 0.0 { @@ -1615,7 +1598,7 @@ fn absf(a: f32) -> f32 { // numeric-protocol surface (`mul_f32`/`try_sign`/`try_map`/`try_op{,_to}`/ // `partial_cmp`/`try_from_angle`/`parse`/`to_css`/`is_compatible`) is // satisfied via `crate::values::protocol::*` supertraits; only the -// calc-specific Zig `switch (V)` arms (`intoValue` / `addValue` / `intoCalc` +// calc-specific methods (`intoValue` / `addValue` / `intoCalc` // / `eql`) live here. // // Any type whose protocol impls don't already exist elsewhere gets them @@ -1668,9 +1651,7 @@ impl CalcValue for Angle { // ───────────────────────────────────────────────────────────────────────────── // `protocol::*` forwarder stamper for `CalcValue` leaf types. // -// Rust analogue of Zig's single comptime dispatcher in `src/css/generics.zig` -// (`tryFromAngle`/`trySign`/`tryMap`/`tryOp`/`tryOpTo`/`partialCmp`, lines -// ~489-570), which duck-types via `@hasDecl(T, "sign")` etc. Here each leaf +// Each leaf // opts in per-trait; only the listed arms are stamped — `Parse`/`ToCss`/ // `IsCompatible` already supplied by `impl_parse_tocss_via_inherent!` / // `bridge_is_compatible!` stay out of the invocation. @@ -1799,7 +1780,6 @@ impl CalcValue for Percentage { fn from_calc(c: Calc, _input: &mut css::Parser) -> CssResult { match c { Calc::Value(v) => Ok(*v), - // Zig: else → Percentage { v: NaN } _ => Ok(Percentage { v: f32::NAN }), } } @@ -1888,7 +1868,6 @@ impl CalcValue for Length { Length::into_calc(self) } fn from_calc(c: Calc, _input: &mut css::Parser) -> CssResult { - // Zig: Length { .calc = Box::new(self) } Ok(Length::Calc(Box::new(c))) } #[inline] @@ -1949,5 +1928,3 @@ macro_rules! dim_pct_protocol { } dim_pct_protocol!(LengthValue); dim_pct_protocol!(Angle, parse_to_css: forward,); - -// ported from: src/css/values/calc.zig diff --git a/src/css/values/color.rs b/src/css/values/color.rs index ba92a1ae9ec..fd3f599a467 100644 --- a/src/css/values/color.rs +++ b/src/css/values/color.rs @@ -17,7 +17,7 @@ use bun_alloc::Arena; use bun_core::strings; // ───────────────────────── colorspace structs ──────────────────────────── -// Field layout matches `color.zig`; every space is 3 channels + alpha. +// Every space is 3 channels + alpha. /// A color with red, green, blue, and alpha components, in a byte each. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -86,20 +86,19 @@ impl RGBA { } } - /// Zig: `rgba.into(.HSL)` — routes RGBA → SRGB → HSL. + /// Routes RGBA → SRGB → HSL. #[inline] pub fn into_hsl(self) -> HSL { HSL::from_rgba(self) } - /// Zig: `rgba.into(.LAB)` — routes RGBA → SRGB → LAB. + /// Routes RGBA → SRGB → LAB. #[inline] pub fn into_lab(self) -> LAB { LAB::from_rgba(self) } /// Convert any `CssColor` into `RGBA` by routing through `SRGB`. - /// Zig: `ColorspaceConversions(@This()).tryFromCssColor`. #[inline] pub fn try_from_css_color(color: &CssColor) -> Option { Some(SRGB::try_from_css_color(color)?.into_rgba()) @@ -122,8 +121,7 @@ impl RGBA { /// /// NaN → 0 (clamp passes NaN through; `NaN as u8` saturates to 0). /// -/// NOTE: this *rounds*. Do **not** use for thumbhash, whose spec truncates -/// (`thumbhash.zig:256` `@intFromFloat`). +/// NOTE: this *rounds*. Do **not** use for thumbhash, whose spec truncates. #[inline] pub(crate) fn clamp_unit_f32(val: f32) -> u8 { (val * 255.0).round().clamp(0.0, 255.0) as u8 @@ -165,11 +163,8 @@ pub enum FloatColor { // Variant dispatch — single source of truth for (Variant ↔ Payload type ↔ // css-name ↔ hash-ordinal). Every match-over-all-variants in this file is // driven from the three `impl_variant_dispatch!` invocations below; do NOT -// hand-roll a new copy. -// -// Mirrors Zig's `switch (color.*) { inline else => |*v| v.into(T) }` shape -// (color.zig:3213 `ColorspaceConversions`) which the original Rust port -// regressed into 14 textual copies inside `define_colorspace!`. +// hand-roll a new copy (an earlier version +// regressed into 14 textual copies inside `define_colorspace!`). // // ROW ORDER IS LOAD-BEARING: ordinals feed `CssColor::hash` (Wyhash). // css-name is NOT derivable from the ident: `XyzD65` serializes as `"xyz"` @@ -392,7 +387,7 @@ pub enum CssColor { System(SystemColor), } -/// `Result(CssColor)` — Zig: `pub const ParseResult = Result(CssColor);` +/// `Result(CssColor)`. pub type ParseResult = css::CssResult; impl Default for CssColor { @@ -403,8 +398,6 @@ impl Default for CssColor { } impl CssColor { - // TODO(port): move to *_jsc — `pub const jsFunctionColor = @import("../../css_jsc/color_js.zig").jsFunctionColor;` - /// Parse a CSS `` from the parser cursor. pub fn parse(input: &mut css::Parser) -> CssResult { let location = input.current_source_location(); @@ -583,14 +576,12 @@ impl CssColor { let mut res = crate::SmallList::::default(); if fallbacks.contains(ColorFallbackKind::RGB) { - // PERF(port): was assume_capacity if let Some(rgb) = self.to_rgb() { res.append(rgb); } } if fallbacks.contains(ColorFallbackKind::P3) { - // PERF(port): was assume_capacity if let Some(p3) = self.to_p3() { res.append(p3); } @@ -881,7 +872,6 @@ impl CssColor { } pub fn hash(&self, hasher: &mut bun_wyhash::Wyhash) { - // PORT NOTE: Zig `css.implementHash` — variant-tag prefix + payload fields. // Hash the discriminant + the active variant's f32 components explicitly; // never reinterpret a `repr(Rust)` enum as raw bytes (unspecified layout / // padding → UB and non-deterministic hashes). @@ -935,8 +925,8 @@ impl crate::generics::ToCss for CssColor { } } -// `light_dark` payload helpers (Zig anonymous struct methods) -// `takeLightFreeDark` / `takeDarkFreeLight` — in Rust, taking ownership of one +// `light_dark` payload helpers +// `takeLightFreeDark` / `takeDarkFreeLight` — taking ownership of one // `Box` and dropping the other is just destructuring; provided as free fns. #[inline] pub(crate) fn take_light_free_dark(light: Box, dark: Box) -> Box { @@ -997,15 +987,11 @@ impl ColorFallbackKind { } // ────────────────────────────────────────────────────────────────────────── -// Colorspace traits (replaces Zig comptime mixins: DefineColorspace, -// BoundedColorGamut, UnboundedColorGamut, HslHwbColorGamut, DeriveInterpolate, -// RecangularPremultiply, PolarPremultiply, AdjustPowerlessLAB/LCH, -// ColorspaceConversions, ColorIntoMixin, ImplementIntoCssColor) +// Colorspace traits // ────────────────────────────────────────────────────────────────────────── -/// Trait every colorspace implements. The Zig used `@field(this, "x")` over the -/// first three struct fields plus `alpha`; here we expose them by index. -/// `// TODO(port): could be derived with a proc-macro.` +/// Trait every colorspace implements. The +/// first three struct fields plus `alpha` are exposed by index. pub trait Colorspace: Copy + Sized + FromAnyColorspace { const CHANNEL_NAMES: (&'static [u8], &'static [u8], &'static [u8]); const CHANNEL_TYPES: (ChannelType, ChannelType, ChannelType); @@ -1081,8 +1067,7 @@ pub trait Colorspace: Copy + Sized + FromAnyColorspace { fn into_css_color(self) -> CssColor; } -/// Gamut behavior — replaces UnboundedColorGamut / BoundedColorGamut / -/// HslHwbColorGamut comptime mixins. +/// Gamut behavior — in-gamut check and clipping per color space. pub trait ColorGamut: Sized + Copy { fn in_gamut(&self) -> bool; fn clip(&self) -> Self; @@ -1251,7 +1236,6 @@ pub fn parse_hslhwb_components( parser: &mut ComponentParser, allows_legacy: bool, ) -> CssResult<(f32, f32, f32, bool)> { - // Zig name: parseHSLHWBComponents — acronym run collapses to one segment let _ = core::marker::PhantomData::; // autofix let h = parse_angle_or_number(input, parser)?; let is_legacy_syntax = allows_legacy @@ -1508,18 +1492,18 @@ impl LABColor { } pub fn new_oklab(l: f32, a: f32, b: f32, alpha: f32) -> LABColor { - // PORT NOTE: Zig had `LABColor{ .lab = OKLAB.new(...) }` which looks like a bug; - // mirrored as Lab variant for behavioral parity. + // Intentionally the `Lab` variant (sic) — kept for behavioral + // compatibility. LABColor::Lab(LAB { l, a, b, alpha }) } pub fn new_lch(l: f32, a: f32, b: f32, alpha: f32) -> LABColor { - // PORT NOTE: Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored. + // Intentionally the `Lab` variant (sic) — kept for behavioral compatibility. LABColor::Lab(LAB { l, a, b, alpha }) } pub fn new_oklch(l: f32, a: f32, b: f32, alpha: f32) -> LABColor { - // PORT NOTE: Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored. + // Intentionally the `Lab` variant (sic) — kept for behavioral compatibility. LABColor::Lab(LAB { l, a, b, alpha }) } @@ -1559,8 +1543,7 @@ impl FloatColor { // Colorspace structs (LAB, SRGB, HSL, HWB, SRGBLinear, P3, A98, ProPhoto, // Rec2020, XYZd50, XYZd65, LCH, OKLAB, OKLCH) // -// In Zig each struct manually wires `pub const X = mixin.X` for ~12 mixin -// items. In Rust the trait impls below cover that surface; the per-type +// The trait impls below cover the shared surface; the per-type // declarations collapse into a `define_colorspace!` macro invocation. // ────────────────────────────────────────────────────────────────────────── @@ -1777,8 +1760,8 @@ define_colorspace! { premultiply = rectangular; powerless = none; into_css = |srgb: &SRGB| { - // TODO: should we serialize as color(srgb, ...)? - // would be more precise than 8-bit color. + // Serializes through 8-bit RGBA, matching upstream lightningcss + // (`color(srgb, ...)` would be more precise but would change output). CssColor::Rgba(RGBA::from(*srgb)) }; } @@ -1813,8 +1796,8 @@ define_colorspace! { define_colorspace! { /// A color in the [`sRGB-linear`](https://www.w3.org/TR/css-color-4/#predefined-sRGB-linear) color space. SRGBLinear { r, g, b } - // PORT NOTE: Zig had `.r = ChannelType{ .angle = true }` for SRGBLinear which looks like a bug; - // mirrored for parity. + // `r` intentionally uses the angle channel type (sic) — kept for + // behavioral compatibility. types = (CT_ANG, CT_PCT, CT_PCT); gamut = bounded; premultiply = rectangular; @@ -2069,7 +2052,7 @@ impl ComponentParser { } /// Helper trait so `parse_from` can build a light-dark wrapper for the result -/// type `C`. (Zig used `C.lightDarkOwned`.) +/// type `C`. pub(crate) trait LightDarkOwned: Sized { fn light_dark_owned(light: Self, dark: Self) -> Self; } @@ -2141,9 +2124,7 @@ impl RelativeComponentParser { return Ok(css::color::AngleOrNumber::Number { value }); } - // PORT NOTE: Zig threads a stack `Angle` through `Calc(Angle).parseWith` - // via a closure that returns `Calc{ .value = &t.angle }` (raw stack - // pointer). Rust `Calc::Value` is `Box`, so box the temporary. + // `Calc::Value` is `Box`, so box the temporary `Angle`. if let Ok(value) = input.try_parse(|i| { match Calc::::parse_with(i, this, |ctx, ident| { let value = ctx.get_ident(ident, allowed)?; @@ -2309,7 +2290,6 @@ impl RelativeComponentParser { bitflags::bitflags! { /// A channel type for a color space. - /// TODO(zack): why tf is this bitflags? #[derive(Clone, Copy, PartialEq, Eq)] pub struct ChannelType: u8 { /// Channel represents a percentage. @@ -2337,7 +2317,7 @@ pub fn parse_predefined( None }; - // PORT NOTE: reshaped for borrowck — detach the slice from the + // Reshaped for borrowck — detach the slice from the // `&mut self` borrow so `i` is reusable below. let colorspace = i.expect_ident_cloned()?; @@ -2402,7 +2382,7 @@ pub fn parse_predefined_relative( b"srgb" => PredefinedColor::Srgb(SRGB { r: a, g: b, b: c, alpha }), b"srgb-linear" => PredefinedColor::SrgbLinear(SRGBLinear { r: a, g: b, b: c, alpha }), b"display-p3" => PredefinedColor::DisplayP3(P3 { r: a, g: b, b: c, alpha }), - // PORT NOTE: Zig has "a99-rgb" here (typo?); mirrored for behavioral parity. + // "a99-rgb" (sic) — kept for behavioral compatibility. b"a99-rgb" => PredefinedColor::A98(A98 { r: a, g: b, b: c, alpha }), b"prophoto-rgb" => PredefinedColor::Prophoto(ProPhoto { r: a, g: b, b: c, alpha }), b"rec2020" => PredefinedColor::Rec2020(Rec2020 { r: a, g: b, b: c, alpha }), @@ -2501,7 +2481,7 @@ pub fn parse_color_mix(input: &mut css::Parser) -> CssResult { ColorSpaceName::Xyz | ColorSpaceName::XyzD65 => { first_color.interpolate::(p1, &second_color, p2, hue_method) } - // PORT NOTE: Zig used XYZd65 for xyz-d50 too (likely bug); mirrored for parity. + // Intentionally XYZd65 for xyz-d50 too (sic) — kept for behavioral compatibility. ColorSpaceName::XyzD50 => { first_color.interpolate::(p1, &second_color, p2, hue_method) } @@ -2583,7 +2563,6 @@ fn rectangular_to_polar(l: f32, a: f32, b: f32) -> (f32, f32, f32) { h += 360.0; } - // PERF: Zig does not have Rust's f32::powi let c = (a.powi(2) + b.powi(2)).sqrt(); h = h.rem_euclid(360.0); @@ -2782,14 +2761,9 @@ const D50: [f32; 3] = [ ]; // ────────────────────────────────────────────────────────────────────────── -// Handwritten conversions (Zig `color_conversions` namespace). +// Handwritten conversions. // -// In Zig, `ColorIntoMixin(T, .Space).into(target)` looked up `intoXXX` in -// (a) handwritten `color_conversions.convert_`, then -// (b) generated `generated_color_conversions.convert_`, then -// (c) the type itself. -// -// In Rust we express each conversion as `impl From for Dst`. The +// Each conversion is an `impl From for Dst`. The // handwritten ones are below; generated ones live in `color_generated.rs`. // ────────────────────────────────────────────────────────────────────────── @@ -3332,7 +3306,8 @@ impl From for ProPhoto { // convert linear-light prophoto-rgb in the range 0.0-1.0 // to gamma corrected form // Transfer curve is gamma 1.8 with a small linear portion - // TODO for negative values, extend linear portion on reflection of axis, then add pow below that + // For negative values, the linear portion is extended on + // reflection of axis, with pow applied beyond it (csswg conversions.js). const ET: f32 = 1.0 / 512.0; let abs = c.abs(); if abs >= ET { @@ -3637,15 +3612,10 @@ impl From for OKLAB { } // ────────────────────────────────────────────────────────────────────────── -// ConvertTo (kept for parity; in Rust the `.into()` dispatch above replaces -// `ColorIntoMixin(T, .Space).into(target)`). +// ConvertTo (kept for parity with the `.into()` dispatch above). // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: `ColorIntoMixin` resolved conversions at comptime via @hasDecl -// across handwritten + generated tables. In Rust this is the union of the -// `impl From for Dst` blocks above plus `color_generated.rs`; the -// generated file fills the transitive gaps the macro requires. +// Conversions are the union of the `impl From for Dst` blocks above +// plus `color_generated.rs`; the generated file fills the transitive gaps. crate::css_eql_partialeq!(CssColor); - -// ported from: src/css/values/color.zig diff --git a/src/css/values/color_generated.rs b/src/css/values/color_generated.rs index 43823af928d..b2d7629fd8b 100644 --- a/src/css/values/color_generated.rs +++ b/src/css/values/color_generated.rs @@ -1,17 +1,14 @@ //! This file is generated by `color_via.ts`. Do not edit it directly! //! -//! Ported from `src/css/values/color_generated.zig` (946 lines). -//! -//! Each `convert_.into` in the Zig becomes an `impl From for Dst` -//! here. The Zig comptime mixin `ColorIntoMixin(T, .Space).into(target)` searched -//! handwritten conversions first, then this generated table; in Rust both are +//! Generated colorspace conversions: each is an `impl From for Dst`. +//! Handwritten conversions and this generated table are both //! flattened into the global `From`/`Into` trait graph, so any pair already //! defined by a handwritten impl in `color.rs` is *omitted* below to avoid the //! overlapping-impl error (Rust forbids duplicate `From` impls). //! //! Omitted (handwritten in `color.rs::gated_full_impl` takes precedence): -//! - `From for RGBA` (Zig: `convert_HSL.intoRGBA`) -//! - `From for RGBA` (Zig: `convert_HWB.intoRGBA`) +//! - `From for RGBA` +//! - `From for RGBA` use super::color::{ A98, HSL, HWB, LAB, LCH, OKLAB, OKLCH, P3, ProPhoto, RGBA, Rec2020, SRGB, SRGBLinear, XYZd50, @@ -248,5 +245,3 @@ convert_via!(OKLCH => XYZd65 => Rec2020); convert_via!(OKLCH => XYZd65 => HSL); convert_via!(OKLCH => XYZd65 => HWB); convert_via!(OKLCH => SRGB => RGBA); - -// ported from: src/css/values/color_generated.zig diff --git a/src/css/values/css_string.rs b/src/css/values/css_string.rs index cbb4bed93bb..960cd50e69c 100644 --- a/src/css/values/css_string.rs +++ b/src/css/values/css_string.rs @@ -27,5 +27,3 @@ impl CssStringFns { dest.serialize_string(s) } } - -// ported from: src/css/values/css_string.zig diff --git a/src/css/values/easing.rs b/src/css/values/easing.rs index 30981e81a0d..df2154dba9c 100644 --- a/src/css/values/easing.rs +++ b/src/css/values/easing.rs @@ -52,9 +52,9 @@ enum EasingKeyword { StepEnd, } -/// Zig: `Map.getASCIIICaseInsensitive(ident)`. +/// ASCII-case-insensitive keyword lookup. /// -/// PERF(port): was `phf::Map<&[u8], _>` + lowercase-into-stack-buf + `get`. +/// PERF: chosen over `phf::Map<&[u8], _>` + lowercase-into-stack-buf + `get`. /// 7 keys with near-unique lengths (only len 8 collides: `ease-out` / /// `step-end`), so a length-gated byte match is cheaper than phf's /// hash+displace+verify — one `usize` compare rejects almost every miss @@ -85,7 +85,7 @@ fn easing_map_get_any_case(ident: &[u8]) -> Option { impl EasingFunction { pub fn parse(input: &mut css::Parser) -> Result { - // PORT NOTE: reshaped for borrowck — `try_parse(|i| i.expect_ident())` + // Reshaped for borrowck — `try_parse(|i| i.expect_ident())` // ties the returned slice to the closure's `&mut Parser` borrow, so the // ident can't escape. Read the next token by value (Token slices are // `'static` placeholders for the not-yet-threaded `'bump`) and dispatch @@ -258,10 +258,10 @@ enum StepPositionKeyword { JumpEnd, } -/// Zig: `Map.getASCIIICaseInsensitive(ident)` — lowercase into a stack buffer, +/// ASCII-case-insensitive keyword lookup — lowercase into a stack buffer, /// then a length-gated byte match. /// -/// PERF(port): was `phf::Map<&[u8], _>` (6 keys). phf hashes the whole slice +/// PERF: chosen over `phf::Map<&[u8], _>` (6 keys). phf hashes the whole slice /// before a single bucket compare; with 6 keys spread across 5 distinct /// lengths (3/5/8/9/9/10), gating on `len()` rejects every miss with one /// `usize` compare and resolves every hit with at most one slice compare @@ -285,8 +285,7 @@ fn step_position_map_get_any_case(ident: &[u8]) -> Option { } impl StepPosition { - // TODO(port): Zig used `css.DeriveToCss(@This()).toCss` — reflection-derived serializer. - // Replace with `#[derive(ToCss)]` once the trait/derive exists. + /// Hand-written keyword serializer. pub fn to_css(self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { dest.write_str(<&'static str>::from(self)) } @@ -313,5 +312,3 @@ impl StepPosition { Ok(keyword) } } - -// ported from: src/css/values/easing.zig diff --git a/src/css/values/gradient.rs b/src/css/values/gradient.rs index 17631e82381..dbc501a9a2f 100644 --- a/src/css/values/gradient.rs +++ b/src/css/values/gradient.rs @@ -35,8 +35,7 @@ pub trait GradientPosition: Sized + Clone + PartialEq { // Only two `D` instantiations exist (`LengthValue` / `Angle`); both already // satisfy `DimensionPercentage: CalcValue` in `calc.rs`. A blanket impl -// would need to re-state that bound; concrete impls are simpler and match -// the Zig monomorphization sites exactly. +// would need to re-state that bound; concrete impls are simpler. macro_rules! impl_gradient_position { ($ty:ty) => { impl GradientPosition for $ty { @@ -1455,8 +1454,6 @@ pub fn parse_items(input: &mut css::Parser) -> Result Result<()> { @@ -1577,5 +1574,3 @@ pub(crate) fn convert_stops_to_webkit( Some(stops) } - -// ported from: src/css/values/gradient.zig diff --git a/src/css/values/ident.rs b/src/css/values/ident.rs index 21108d8cf37..84a44d90e77 100644 --- a/src/css/values/ident.rs +++ b/src/css/values/ident.rs @@ -20,8 +20,9 @@ macro_rules! arena_slice_newtype { $(#[$meta])* #[derive(Debug, Clone, Copy)] pub struct $name { - // TODO(port): arena lifetime — CSS parser slices are arena-owned; thread a `'bump` - // lifetime through instead of erasing to a raw pointer. + // CSS parser slices are arena-owned; the borrow is erased to a raw + // pointer until the crate threads a `'bump` lifetime (see + // PORTING.md §Allocators). Never dereferenced after arena reset. pub v: *const [u8], } @@ -41,20 +42,18 @@ macro_rules! arena_slice_newtype { #[inline] pub fn v(&self) -> &[u8] { // SAFETY: arena-owned, never null, immutable for the parse session - // (see field-level TODO(port) on `'bump` threading). + // (see the field-level comment on `'bump` threading). unsafe { crate::arena_str(self.v) } } pub fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: Zig `css.implementDeepClone` — field-wise. The - // `*const [u8]` slice is arena-owned (never mutated, freed on - // arena reset), so identity copy is correct (matches generics.zig - // "const strings" fast-path). + // The `*const [u8]` slice is arena-owned (never mutated, freed + // on arena reset), so identity copy is correct. *self } pub fn hash(&self, hasher: &mut Wyhash) { - // PORT NOTE: Zig `css.implementHash` (comptime field-walk) → arena slice bytes. + // Hash the arena slice bytes. hasher.update(self.v()); } @@ -73,7 +72,7 @@ macro_rules! arena_slice_newtype { // `from` field below uses it directly. `parse_with_options` honors // `ParserOptions.css_modules.dashed_idents`. `to_css` resolves the // import-record path up front and hands it to `CssModule::reference_dashed` -// (borrowck — see PORT NOTE on that method). +// (borrowck — see the comment on that method). /// A CSS [``](https://www.w3.org/TR/css-values-4/#dashed-idents) reference. /// @@ -93,9 +92,7 @@ pub struct DashedIdentReference { impl DashedIdentReference { pub(crate) fn eql(&self, rhs: &Self) -> bool { - // PORT NOTE: Zig `css.implementEql` — field-wise. `from` is a CSS-modules - // resolution hint, not part of value identity, so compare on `ident` only - // (matches Zig `Specifier`-less comparison in the dashed-ident dedup path). + // Field-wise over `ident` and `from`. use crate::generics::CssEql; self.ident.eql(&rhs.ident) && self.from.eql(&rhs.from) } @@ -149,11 +146,10 @@ impl DashedIdentReference { let ident_v = unsafe { crate::arena_str(self.ident.v) }; let source_index = dest.loc.source_index; let bump = dest.arena; - // PORT NOTE: Zig `referenceDashed` took `*Printer` and called - // `dest.importRecord()` internally. Rust borrowck forbids handing + // Borrowck forbids handing // `dest` to a method on `dest.css_module`, so resolve the path - // here and pass the slice down. The `?` preserves the Zig - // `try dest.importRecord(...)` error path. + // here and pass the slice down. The `?` propagates the + // `import_record` error path. use crate::properties::css_modules::Specifier; let specifier_path: Option<&[u8]> = match &self.from { Some(Specifier::ImportRecordIndex(idx)) => { @@ -187,13 +183,26 @@ arena_slice_newtype! { DashedIdent } -// TODO(port): Zig `pub fn HashMap(comptime V: type) type` returned an -// ArrayHashMapUnmanaged with a custom string-hash context. Inherent assoc -// type aliases are unstable in Rust; expose as a free type alias instead. -// bun_collections::ArrayHashMap is wyhash-keyed; verify the hasher matches -// std.array_hash_map.hashString or supply a custom Hash impl. -// blocked_on: bun_collections::ArrayHashMap surface -pub type DashedIdentHashMap = bun_collections::ArrayHashMap; +/// Hash/eql context for [`DashedIdentHashMap`]: keys hash their string bytes +/// (wyhash seed 0, truncated to u32) and compare by byte equality. +#[derive(Default, Clone, Copy)] +pub struct DashedIdentContext; + +impl bun_collections::array_hash_map::ArrayHashContext for DashedIdentContext { + #[inline] + fn hash(&self, key: &DashedIdent) -> u32 { + bun_collections::array_hash_map::hash_string(key.v()) + } + + #[inline] + fn eql(&self, a: &DashedIdent, b: &DashedIdent, _b_index: usize) -> bool { + a.v() == b.v() + } +} + +/// Inherent assoc type aliases +/// are unstable in Rust, so this is a free type alias instead. +pub type DashedIdentHashMap = bun_collections::ArrayHashMap; impl DashedIdent { pub fn parse(input: &mut Parser) -> CssResult { @@ -248,7 +257,7 @@ impl Ident { #[derive(Clone, Copy, Default)] pub struct IdentOrRef(u128); -// Zig packed struct(u128) field layout, LSB-first: +// Packed u128 field layout, LSB-first: // __ptrbits: u63 -> bits 0..63 // __ref_bit: bool -> bit 63 // __len: u64 -> bits 64..128 @@ -303,7 +312,8 @@ impl IdentOrRef { #[cfg(debug_assertions)] pub fn debug_ident(self) -> &'static [u8] { - // TODO(port): lifetime — returns arena-borrowed slice; `'static` is a placeholder. + // Returns an arena-borrowed slice; `'static` is a placeholder for the + // not-yet-threaded `'bump` lifetime. if self.ref_bit() { let ptr = self.ptrbits() as usize as *const *const [u8]; // SAFETY: in debug builds, `ptrbits` stores a valid arena-allocated `*const *const [u8]` @@ -315,15 +325,14 @@ impl IdentOrRef { } } - // NOTE: no `#[cfg(not(debug_assertions))]` variant. Zig's `@compileError` is lazy (fires only - // if the body is analyzed); Rust's `compile_error!` fires at expansion and would break every - // release build. Omitting the fn in release yields a name-resolution error at the call site, - // which is the closest Rust equivalent. + // NOTE: no `#[cfg(not(debug_assertions))]` variant. `compile_error!` fires at expansion and + // would break every release build. Omitting the fn in release yields a name-resolution error + // at the call site, which is the intent: this is debug-only. pub fn from_ident(ident: Ident) -> Self { let s = ident.v(); let (ptr, len) = (s.as_ptr() as usize as u64, s.len() as u64); - // @intCast(@intFromPtr(...)) — narrowing usize→u63 is checked in debug + // narrowing usize→u63 is checked in debug debug_assert!(ptr & (1u64 << 63) == 0); Self::pack(ptr, false, len) } @@ -387,7 +396,8 @@ impl IdentOrRef { map: &bun_ast::symbol::Map, local_names: Option<&css::LocalsResultsMap>, ) -> Option<&'static [u8]> { - // TODO(port): lifetime — returns arena/symbol-table borrow; `'static` is a placeholder. + // Returns an arena/symbol-table borrow; `'static` is a placeholder for + // the not-yet-threaded `'bump` lifetime. if self.is_ident() { // SAFETY: arena slice reconstructed from packed ptr/len return Some(unsafe { crate::arena_str(self.as_ident().unwrap().v) }); @@ -416,9 +426,9 @@ impl IdentOrRef { if let Some(ident) = self.as_ident() { hasher.update(ident.v()); } else { - // SAFETY: self is #[repr(transparent)] u128; reading first 2 bytes matches Zig's - // `slice_u8[0..2]` (which is almost certainly a Zig bug — hashes 2 bytes, not 16). - // TODO(port): verify upstream intent; preserving behavior verbatim. + // SAFETY: self is #[repr(transparent)] u128 (16 bytes), so reading the first 2 + // bytes is in-bounds. Hashing only 2 of the 16 bytes (sic) is preserved for + // behavioral compatibility; PR #30784 hashes the full identity. let bytes = unsafe { core::slice::from_raw_parts(std::ptr::from_ref::(self).cast::(), 2) }; @@ -521,5 +531,3 @@ impl CustomIdent { /// A list of CSS [``](https://www.w3.org/TR/css-values-4/#custom-idents) values. pub type CustomIdentList = SmallList; - -// ported from: src/css/values/ident.zig diff --git a/src/css/values/image.rs b/src/css/values/image.rs index 719c062717e..ae5f54c5f16 100644 --- a/src/css/values/image.rs +++ b/src/css/values/image.rs @@ -11,8 +11,6 @@ use bun_alloc::Arena; use bun_ast::ImportKind; /// A CSS [``](https://www.w3.org/TR/css-images-3/#image-values) value. -// TODO(port): `parse`/`to_css` were `css.DeriveParse(@This()).parse` / `css.DeriveToCss(@This()).toCss` -// — comptime-reflection derives. Hand-expanded below until the proc-macro lands. #[derive(Default)] pub enum Image { /// The `none` keyword. @@ -21,14 +19,12 @@ pub enum Image { /// A `url()`. Url(Url), /// A gradient. - // PERF(port): arena-allocated in Zig (bun.create); LIFETIMES.tsv → Box Gradient(Box), /// An `image-set()`. ImageSet(ImageSet), } impl Image { - // NOTE: `pub fn deinit` was a no-op in Zig (all CSS parser memory is arena-owned). // No `Drop` impl needed — Box/Vec fields drop automatically. pub fn is_compatible(&self, browsers: &css::targets::Browsers) -> bool { @@ -67,7 +63,6 @@ impl Image { pub fn get_prefixed(&self, arena: &Arena, prefix: css::VendorPrefix) -> Image { match self { - // PERF(port): was arena bulk-free — profile if hot Image::Gradient(grad) => Image::Gradient(Box::new(grad.get_prefixed(arena, prefix))), Image::ImageSet(image_set) => Image::ImageSet(image_set.get_prefixed(arena, prefix)), _ => self.deep_clone(arena), @@ -109,8 +104,6 @@ impl Image { #[inline] pub fn eql(&self, other: &Image) -> bool { - // TODO(port): was `css.implementEql(@This(), this, other)` (comptime field-walk). - // Hand-expanded; replace with `#[derive(PartialEq)]` once `Url: PartialEq`. match (self, other) { (Image::None, Image::None) => true, (Image::Url(a), Image::Url(b)) => a.import_record_idx == b.import_record_idx, @@ -121,7 +114,6 @@ impl Image { } pub fn deep_clone(&self, arena: &Arena) -> Self { - // TODO(port): was `css.implementDeepClone(@This(), this, arena)` (comptime field-walk). match self { Image::None => Image::None, Image::Url(u) => Image::Url(Url { @@ -138,12 +130,9 @@ impl Image { /// May return an error in case the gradient cannot be converted. pub fn get_legacy_webkit(&self, arena: &Arena) -> Option { match self { - Image::Gradient(gradient) => { - // PERF(port): was arena bulk-free — profile if hot - Some(Image::Gradient(Box::new( - gradient.get_legacy_webkit(arena)?, - ))) - } + Image::Gradient(gradient) => Some(Image::Gradient(Box::new( + gradient.get_legacy_webkit(arena)?, + ))), _ => Some(self.deep_clone(arena)), } } @@ -169,8 +158,8 @@ impl Image { let prefix_image: &Image = if let Some(r) = &rgb { r } else { &*self }; // Legacy -webkit-gradient() - // PORT NOTE: Zig's `and`/`if-else` precedence here is preserved verbatim: - // `if (targets.browsers) |b| isWebkitGradient(b) else (false and prefix_image.* == .gradient)` + // The `false && ...` else branch is intentional (sic) — kept for + // behavioral compatibility. if prefixes.contains(VendorPrefix::WEBKIT) && if let Some(browsers) = targets.browsers { css::prefixes::Feature::is_webkit_gradient(&browsers) @@ -223,7 +212,6 @@ impl Image { pub fn get_fallback(&self, arena: &Arena, kind: ColorFallbackKind) -> Image { match self { - // PERF(port): was arena bulk-free — profile if hot Image::Gradient(grad) => Image::Gradient(Box::new(grad.get_fallback(arena, kind))), _ => self.deep_clone(arena), } @@ -236,11 +224,7 @@ impl Image { } } - // TODO(port): `css.DeriveParse(@This()).parse` — hand-expanded: try each - // variant in Zig field order (none/url/gradient/image-set). - // blocked_on: `Url::parse` (gated on `Parser::add_import_record`). The - // gradient/image-set arms are real; the url arm un-gates with url.rs. - + // Variants are tried in declaration order: none, url, gradient, image-set. pub fn parse(input: &mut css::Parser) -> Result { if input .try_parse(|i| i.expect_ident_matching(b"none")) @@ -257,7 +241,6 @@ impl Image { ImageSet::parse(input).map(Image::ImageSet) } - // PORT: `css.DeriveToCss(@This()).toCss` — hand-expanded over enum variants. pub fn to_css(&self, dest: &mut css::Printer) -> core::result::Result<(), css::PrintErr> { match self { Image::None => dest.write_str(b"none"), @@ -293,7 +276,6 @@ impl crate::small_list::ImageFallback for Image { /// display the most appropriate resolution or file type that it supports. pub struct ImageSet { /// The image options to choose from. - // PERF(port): was ArrayListUnmanaged fed arena — profile if hot pub options: Vec, /// The vendor prefix for the `image-set()` function. @@ -345,14 +327,12 @@ impl ImageSet { /// Returns the `image-set()` value with the given vendor prefix. pub(crate) fn get_prefixed(&self, arena: &Arena, prefix: css::VendorPrefix) -> ImageSet { ImageSet { - // TODO(port): was `css.deepClone(ImageSetOption, arena, &this.options)` (comptime helper) options: self.options.iter().map(|o| o.deep_clone(arena)).collect(), vendor_prefix: prefix, } } pub(crate) fn eql(&self, other: &ImageSet) -> bool { - // TODO(port): was `css.implementEql(@This(), this, other)` — derive PartialEq instead. self.vendor_prefix == other.vendor_prefix && self.options.len() == other.options.len() && self @@ -363,7 +343,6 @@ impl ImageSet { } pub(crate) fn deep_clone(&self, arena: &Arena) -> Self { - // TODO(port): was `css.implementDeepClone(@This(), this, arena)` — derive Clone instead. ImageSet { options: self.options.iter().map(|o| o.deep_clone(arena)).collect(), vendor_prefix: self.vendor_prefix, @@ -390,7 +369,8 @@ pub struct ImageSetOption { /// The resolution of the image. pub resolution: Resolution, /// The mime type of the image. - // TODO(port): arena-borrowed slice from tokenizer input; revisit ownership. + // Arena-borrowed slice from the tokenizer input; the parser arena outlives + // this value (see SAFETY notes at the use sites). pub file_type: Option<*const [u8]>, } @@ -398,7 +378,7 @@ impl ImageSetOption { pub(crate) fn parse(input: &mut css::Parser) -> Result { let start_position = input.input.tokenizer.get_position(); let loc = input.current_source_location(); - // PORT NOTE: `expect_url_or_string` returns a borrow of the parser, so + // `expect_url_or_string` returns a borrow of the parser, so // it can't be used as a `try_parse` callback directly (the result type // `R` may not borrow the closure arg). Erase the borrow via `*const` // — token slices are arena-static (see `css_parser::src_str`). @@ -413,7 +393,6 @@ impl ImageSetOption { loc: css::dependencies::Location::from_source_location(loc), }) } else { - // For some reason, `Image.parse` made zls crash; the Zig used `@call(.auto, ...)`. Image::parse(input)? }; @@ -446,7 +425,7 @@ impl ImageSetOption { unreachable!() }; let dep_: Option = if dest.dependencies.is_some() { - // PORT NOTE: hoist `get_import_records` (mut borrow) out of the + // Hoist `get_import_records` (mut borrow) out of the // arg list so `filename()` (shared borrow) can run; result is `&'a _`. let import_records = dest.get_import_records()?; Some(UrlDependency::new( @@ -464,7 +443,7 @@ impl ImageSetOption { let placeholder = unsafe { crate::arena_str(dep.placeholder) }; dest.serialize_string(placeholder)?; if let Some(dependencies) = &mut dest.dependencies { - // PERF(port): was `catch |err| bun.handleOom(err)` — Vec::push aborts on OOM by default + // Vec::push aborts on OOM by default. dependencies.push(css::Dependency::Url(dep)); } } else { @@ -497,7 +476,6 @@ impl ImageSetOption { if let Some(file_type) = self.file_type { dest.write_str(" type(")?; // SAFETY: file_type points into the arena-owned parser input which outlives printing. - // TODO(port): replace raw slice with proper arena-lifetime borrow. let file_type_slice = unsafe { crate::arena_str(file_type) }; dest.serialize_string(file_type_slice)?; dest.write_char(b')')?; @@ -507,7 +485,6 @@ impl ImageSetOption { } pub(crate) fn deep_clone(&self, arena: &Arena) -> Self { - // TODO(port): was `css.implementDeepClone(@This(), this, arena)` — derive Clone instead. ImageSetOption { image: self.image.deep_clone(arena), resolution: self.resolution, @@ -516,7 +493,6 @@ impl ImageSetOption { } pub(crate) fn eql(&self, rhs: &ImageSetOption) -> bool { - // TODO(port): was `css.implementEql(@This(), lhs, rhs)` — derive PartialEq instead. self.image.eql(&rhs.image) && self.resolution == rhs.resolution && match (self.file_type, rhs.file_type) { @@ -531,9 +507,8 @@ impl ImageSetOption { fn parse_file_type(input: &mut css::Parser) -> Result<*const [u8]> { input.expect_function_matching(b"type")?; input.parse_nested_block(|i: &mut css::Parser| { - // TODO(port): expect_string returns arena-borrowed &[u8]; coerced to raw ptr to avoid struct lifetime + // expect_string returns an arena-borrowed &[u8]; coerced to a raw ptr to + // avoid a struct lifetime (token slices outlive the parse session). i.expect_string().map(std::ptr::from_ref::<[u8]>) }) } - -// ported from: src/css/values/image.zig diff --git a/src/css/values/length.rs b/src/css/values/length.rs index 504bf05ed39..3ebd9bd285b 100644 --- a/src/css/values/length.rs +++ b/src/css/values/length.rs @@ -37,9 +37,6 @@ impl Default for LengthOrNumber { } } -// Zig `deinit` only freed the owned `calc` Box inside Length — handled by Drop now. -// Zig `eql` → derive(PartialEq); Zig `deepClone` → derive(Clone). - pub(crate) type LengthPercentage = DimensionPercentage; /// Either a [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage), or the `auto` keyword. @@ -62,8 +59,6 @@ impl LengthPercentageOrAuto { } } -// Zig `eql` → derive(PartialEq); Zig `deepClone` → derive(Clone). - const PX_PER_IN: f32 = 96.0; const PX_PER_CM: f32 = PX_PER_IN / 2.54; const PX_PER_MM: f32 = PX_PER_CM / 10.0; @@ -74,15 +69,13 @@ const PX_PER_PC: f32 = PX_PER_IN / 6.0; // ────────────────────────────────────────────────────────────────────────── // LengthValue // -// The Zig original is a `union(enum)` with ~50 variants, each carrying a single -// `CSSNumber` (f32). Nearly every method iterates `std.meta.fields(@This())` / -// `bun.meta.EnumFields(@This())` to dispatch by tag — Zig comptime reflection. +// An enum with ~50 variants, each carrying a single `CSSNumber` (f32). // // Per PORTING.md §"Comptime reflection": >8 variants → small macro generator. // `define_length_units!` generates the enum plus the handful of per-variant // dispatch helpers (`value()`, `unit()`, `from_unit_ci()`, `map_value()`, // `try_same_unit_op()`, `feature()`); all higher-level methods are then written -// in terms of those, keeping the logic 1:1 with the Zig. +// in terms of those. // ────────────────────────────────────────────────────────────────────────── macro_rules! define_length_units { @@ -138,7 +131,7 @@ macro_rules! define_length_units { } } - /// Compat-feature gate for this unit (the Zig `FeatureMap`). + /// Compat-feature gate for this unit. #[inline] fn feature(&self) -> Option { match self { $( Self::$variant(_) => $feature, )* } @@ -268,8 +261,7 @@ define_length_units! { Cqmax: b"cqmax" => Some(Feature::ContainerQueryLengthUnits), } -// The Zig `comptime { ... }` block at :253-262 statically asserts that -// `FeatureMap` covers every variant. The macro above guarantees this by +// The macro above guarantees feature coverage of every variant by // construction (one `=> $feature` per variant), so no separate assert needed. impl LengthValue { @@ -351,7 +343,6 @@ impl LengthValue { } pub(crate) fn map(self, map_fn: impl FnOnce(f32) -> f32) -> LengthValue { - // PERF(port): was comptime monomorphization (`comptime map_fn: *const fn`). self.map_value(map_fn) } @@ -397,7 +388,7 @@ impl LengthValue { impl PartialEq for LengthValue { #[inline] fn eq(&self, other: &Self) -> bool { - // Zig `eql`: same tag AND equal payload (f32 `==`). + // Same tag AND equal payload (f32 `==`). core::mem::discriminant(self) == core::mem::discriminant(other) && self.value() == other.value() } @@ -422,8 +413,6 @@ impl Length { self.clone() } - // Zig `deinit` → Drop on Box> handles this. - pub(crate) fn parse(input: &mut Parser) -> CssResult { if let Ok(calc_value) = input.try_parse(Calc::::parse) { // PERF: I don't like this redundant allocation @@ -440,8 +429,6 @@ impl Length { // to_css — provided by #[derive(css::ToCss)] (Box> auto-derefs // to Calc::::to_css inherent). parse KEPT (custom Calc::Value unwrap). - // Zig `eql` → derive(PartialEq). - pub(crate) fn px(p: CSSNumber) -> Length { Self::Value(LengthValue::Px(p)) } @@ -526,8 +513,7 @@ impl Length { right: Box::new(b.into_calc()), })), } - // PORT NOTE: reshaped for borrowck — Zig matched on tags then accessed - // `a.calc.value` / `b.calc.value` while both were still bound; Rust needs + // For borrowck this needs // to move out of the Box, so the conditions are folded into match guards. } @@ -557,9 +543,8 @@ impl Length { } _ => return None, } - // TODO(port): the Zig builds `Length{ .calc = s.left }` without - // cloning (alias of the same heap node). With `Box` ownership we - // must clone here; revisit if Calc nodes become arena-backed refs. + // `Box` ownership requires cloning the sub-nodes here (no aliasing + // of the same heap node). } if let Self::Calc(c) = other { @@ -596,8 +581,7 @@ impl Length { }, _ => length, } - // PORT NOTE: reshaped for borrowck — Zig rebinds `c` while reading `c.*`; - // Rust moves out of the Box once and rebuilds. + // For borrowck, this moves out of the Box once and rebuilds. } pub(crate) fn try_sign(&self) -> Option { @@ -699,7 +683,7 @@ impl protocol::TryMap for LengthValue { impl protocol::TryOp for LengthValue { #[inline] fn try_op(&self, rhs: &Self, ctx: C, f: impl Fn(C, f32, f32) -> f32) -> Option { - // PORT NOTE: `LengthValue::try_op` takes a 2-arg closure (ctx folded + // `LengthValue::try_op` takes a 2-arg closure (ctx folded // in by caller); the `protocol::TryOp` shape passes `C` by-value with // no `Copy` bound, so we can't capture `ctx` in an `Fn` closure that // might be called more than once. Inline the same-unit + px-convert @@ -708,8 +692,8 @@ impl protocol::TryOp for LengthValue { let v = f(ctx, self.value(), rhs.value()); return Some(self.map_value(|_| v)); } - // PORT NOTE: Zig calls `this.toPx()` for BOTH operands here (length.zig:447) — - // preserving that behavior verbatim; likely an upstream bug. + // Intentionally calls `self.to_px()` for BOTH operands (sic) — kept + // for behavioral compatibility. if let (Some(a), Some(b)) = (self.to_px(), self.to_px()) { return Some(LengthValue::Px(f(ctx, a, b))); } @@ -722,8 +706,8 @@ impl protocol::TryOpTo for LengthValue { if core::mem::discriminant(self) == core::mem::discriminant(rhs) { return Some(f(ctx, self.value(), rhs.value())); } - // PORT NOTE: Zig calls `this.toPx()` for BOTH operands here (length.zig:473) — - // preserving that behavior verbatim; likely an upstream bug. + // Intentionally calls `self.to_px()` for BOTH operands (sic) — kept + // for behavioral compatibility. if let (Some(a), Some(b)) = (self.to_px(), self.to_px()) { return Some(f(ctx, a, b)); } @@ -799,9 +783,8 @@ impl protocol::TryMap for Angle { impl protocol::TryOp for Angle { #[inline] fn try_op(&self, rhs: &Self, ctx: C, f: impl Fn(C, f32, f32) -> f32) -> Option { - // PORT NOTE: `Angle::op` takes a `fn(C, f32, f32)` pointer (Zig - // `comptime`-monomorphized fn arg), so we can't pass the generic - // closure through it. Inline the per-variant dispatch here instead. + // `Angle::op` takes a `fn(C, f32, f32)` pointer, so we can't pass the + // generic closure through it. Inline the per-variant dispatch here instead. Some(match (self, rhs) { (Angle::Deg(a), Angle::Deg(b)) => Angle::Deg(f(ctx, *a, *b)), (Angle::Rad(a), Angle::Rad(b)) => Angle::Rad(f(ctx, *a, *b)), @@ -847,5 +830,3 @@ impl protocol::IsCompatible for Angle { true } } - -// ported from: src/css/values/length.zig diff --git a/src/css/values/mod.rs b/src/css/values/mod.rs index eee401eb419..00652cfa40b 100644 --- a/src/css/values/mod.rs +++ b/src/css/values/mod.rs @@ -34,10 +34,9 @@ pub mod position; pub mod rect; pub mod size; pub mod syntax; -// `color_generated.rs` is the codegen'd named-color tables (47KB). Its parent -// in Zig was `color.zig`'s `pub usingnamespace`; here it's a sibling module -// re-exported through `color::*` so the stub-set re-export at crate root -// (`pub use values::color::{CssColor, RGBA, ...}`) keeps resolving. +// `color_generated.rs` is the codegen'd named-color tables (47KB). It's a +// sibling module re-exported through `color::*` so the stub-set re-export at +// crate root (`pub use values::color::{CssColor, RGBA, ...}`) keeps resolving. #[path = "color_generated.rs"] pub mod color_generated; pub mod ident; @@ -52,5 +51,3 @@ pub mod protocol { TryMap, TryOp, TryOpTo, TrySign, Zero, }; } - -// ported from: src/css/values/values.zig diff --git a/src/css/values/number.rs b/src/css/values/number.rs index ba32d31d773..e1e141b3591 100644 --- a/src/css/values/number.rs +++ b/src/css/values/number.rs @@ -25,7 +25,6 @@ impl CSSNumberFns { let number: f32 = this; if number != 0.0 && number.abs() < 1.0 { let mut dtoa_buf: [u8; 129] = [0; 129]; - // PERF(port): Zig left dtoa_buf uninitialized — profile if hot. let (str, _) = css::dtoa_short(&mut dtoa_buf, number, 6); if number < 0.0 { dest.write_char(b'-')?; @@ -44,7 +43,7 @@ impl CSSNumberFns { pub fn sign(this: CSSNumber) -> f32 { if this == 0.0 { - // Spec-faithful (number.zig:45): ±0.0 both map to +0.0 — do NOT + // Spec-faithful: ±0.0 both map to +0.0 — do NOT // collapse with `signfns::sign_f32` / `calc::std_math_sign`. return 0.0; } @@ -68,5 +67,3 @@ impl CSSIntegerFns { css::to_css::integer(this, dest) } } - -// ported from: src/css/values/number.zig diff --git a/src/css/values/percentage.rs b/src/css/values/percentage.rs index 9bcaa8eac5d..ecc68059285 100644 --- a/src/css/values/percentage.rs +++ b/src/css/values/percentage.rs @@ -30,7 +30,6 @@ impl Percentage { pub(crate) fn to_css(self, dest: &mut Printer) -> Result<(), PrintErr> { let x = self.v * 100.0; let int_value: Option = if (x - x.trunc()) == 0.0 { - // PORT NOTE: Rust `as` saturates on overflow/NaN where Zig is UB. Some(self.v as i32) } else { None @@ -152,15 +151,10 @@ impl PartialEq for DimensionPercentage { } // `Zero`/`MulF32`/`TryAdd`/`Parse` protocol traits live in -// `crate::values::protocol` until `generics::parse_tocss_numeric_gated` -// un-gates. The bound set below mirrors the full Zig comptime-method surface -// on `D`; per-method `where` clauses narrow further so plain -// `DimensionPercentage` (no behavior) needs only `D: Clone`. -impl DimensionPercentage -where - // TODO(port): narrow these bounds; mirroring methods called on D below. - D: Clone, -{ +// `crate::values::protocol`. Bounds on `D` are expressed via per-method +// `where` clauses, so plain `DimensionPercentage` (no behavior) needs no +// bounds at all. +impl DimensionPercentage { pub(crate) fn parse(input: &mut css::Parser) -> CssResult where Self: crate::values::calc::CalcValue, @@ -170,7 +164,6 @@ where if let Calc::Value(v) = calc_value { return Ok(*v); } - // PERF(port): was arena alloc (bun.create with input.arena()) — profile if hot. return Ok(Self::Calc(Box::new(calc_value))); } @@ -209,19 +202,20 @@ where } } - pub(crate) fn deep_clone(&self) -> Self { + pub(crate) fn deep_clone(&self) -> Self + where + D: Clone, + { match self { - // PORT NOTE: Zig branched on `comptime needs_deepclone` to avoid cloning POD types. - // In Rust, D: Clone covers both — Copy types' clone is a bitwise copy. + // D: Clone covers POD types too — Copy types' clone is a bitwise copy. Self::Dimension(d) => Self::Dimension(d.clone()), Self::Percentage(p) => Self::Percentage(*p), - // PERF(port): was arena alloc (bun.create) — profile if hot. Self::Calc(calc) => Self::Calc(Box::new(calc.deep_clone())), } } - // PORT NOTE: `deinit` dropped — Box> frees via Drop; D's Drop (if any) runs - // automatically. Zig body only freed owned fields, so no explicit `impl Drop` needed. + // No explicit `impl Drop` needed — Box> frees via Drop; D's Drop + // (if any) runs automatically. pub(crate) fn zero() -> Self where @@ -256,7 +250,6 @@ where match self { Self::Dimension(d) => Self::Dimension(Self::mul_value_f32(d, other)), Self::Percentage(p) => Self::Percentage(p.mul_f32(other)), - // PERF(port): was arena alloc (bun.create) — profile if hot. Self::Calc(c) => Self::Calc(Box::new(c.mul_f32(other))), } } @@ -289,9 +282,9 @@ where (Self::Calc(this_calc), _) => match this_calc.as_ref() { Calc::Value(v) => return v.add_recursive(other), Calc::Sum { left, right } => { - // PORT NOTE: reshaped for borrowck — Zig wrapped sum.left/right (raw ptrs) - // directly in This{.calc = ...}. Here we deep_clone since Box is owning. - // TODO(port): lifetime — sum.left/right ownership semantics need review. + // With owning Boxes we deep_clone the sum operands. The values + // are only read during this computation, so the clone is + // semantically equivalent (just extra allocation). let left_calc = Self::Calc(left.deep_clone_boxed()); if let Some(res) = left_calc.add_recursive(other) { return Some(res.add_impl(Self::Calc(right.deep_clone_boxed()))); @@ -361,13 +354,10 @@ where }; a.add_impl(*v) } - (a, b) => { - // PERF(port): was arena alloc (bun.create) — profile if hot. - Self::Calc(Box::new(Calc::Sum { - left: Box::new(a.into_calc()), - right: Box::new(b.into_calc()), - })) - } + (a, b) => Self::Calc(Box::new(Calc::Sum { + left: Box::new(a.into_calc()), + right: Box::new(b.into_calc()), + })), } } @@ -438,7 +428,6 @@ where pub(crate) fn into_calc(self) -> Calc> { match self { Self::Calc(calc) => *calc, - // PERF(port): was arena alloc (bun.create) — profile if hot. other => Calc::Value(Box::new(other)), } } @@ -454,9 +443,8 @@ pub enum NumberOrPercentage { } impl NumberOrPercentage { - // PORT NOTE: Zig used `css.DeriveParse(@This()).parse` / `css.DeriveToCss(@This()).toCss` - // (comptime reflection derives). Hand-rolled here as the trivial two-variant - // try-parse cascade so `AlphaValue::parse` doesn't panic at runtime. + // Hand-rolled as the trivial two-variant try-parse cascade so + // `AlphaValue::parse` doesn't panic at runtime. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { if let Ok(n) = input.try_parse(crate::values::number::CSSNumberFns::parse) { return Ok(NumberOrPercentage::Number(n)); @@ -497,5 +485,3 @@ impl PartialEq for Percentage { } crate::css_eql_partialeq!(NumberOrPercentage); - -// ported from: src/css/values/percentage.zig diff --git a/src/css/values/position.rs b/src/css/values/position.rs index c455bc3e579..e3fccdd9988 100644 --- a/src/css/values/position.rs +++ b/src/css/values/position.rs @@ -148,8 +148,6 @@ impl Position { } pub(crate) fn to_css(&self, dest: &mut css::Printer) -> Result<(), css::PrintErr> { - // PORT NOTE: reshaped for borrowck — Zig used tag-then-payload-access (`this.x == .side and this.x.side.side != .left`); - // Rust uses if-let pattern matching to bind payloads. if let (PositionComponent::Side(xs), PositionComponent::Length(yl)) = (&self.x, &self.y) { if xs.side != HorizontalPositionKeyword::Left { self.x.to_css(dest)?; @@ -312,9 +310,8 @@ pub enum PositionComponent { Side(PositionComponentSide), } -// PORT NOTE: Zig used duck-typed `S.parse`/`S.toCss`; Rust bounds on the -// values-local `protocol::{Parse,ToCss}` shapes until `generics::Parse` -// un-gates. +// `S` is bounded on the values-local `protocol::{Parse,ToCss}` shapes until +// `generics::Parse` un-gates. impl PositionComponent { pub(crate) fn is_zero(&self) -> bool { if let PositionComponent::Length(l) = self { @@ -420,5 +417,3 @@ impl VerticalPositionKeyword { pub(crate) type HorizontalPosition = PositionComponent; pub(crate) type VerticalPosition = PositionComponent; - -// ported from: src/css/values/position.zig diff --git a/src/css/values/ratio.rs b/src/css/values/ratio.rs index 5d15f69c423..c78f40063ba 100644 --- a/src/css/values/ratio.rs +++ b/src/css/values/ratio.rs @@ -44,7 +44,6 @@ impl Ratio { Ok(()) } - // PORT NOTE: dropped unused `std.mem.Allocator` param (was `_` in Zig). pub(crate) fn add_f32(self, other: f32) -> Ratio { Ratio { numerator: self.numerator + other, @@ -52,5 +51,3 @@ impl Ratio { } } } - -// ported from: src/css/values/ratio.zig diff --git a/src/css/values/rect.rs b/src/css/values/rect.rs index 7a8c585a8ee..ccaf32c9d33 100644 --- a/src/css/values/rect.rs +++ b/src/css/values/rect.rs @@ -3,11 +3,9 @@ use crate::css_parser::{CssResult as Result, PrintErr, Printer}; use crate::targets::Browsers; use crate::values::protocol::{IsCompatible, Parse, ToCss}; -// PORT NOTE: the Zig `needsDeinit(comptime T: type) bool` switch and the -// `deinit(this, arena)` method are dropped entirely. They existed to -// thread per-field `arena.free` through a comptime type table; in Rust, -// `T: Drop` on the four fields handles this automatically (and arena-owned -// payloads in `bun_css` are bulk-freed by the bump, never per-value). +// No `deinit` is needed: `T: Drop` on the four fields handles cleanup +// automatically (and arena-owned payloads in `bun_css` are bulk-freed by the +// bump, never per-value). /// A generic value that represents a value for four sides of a box, /// e.g. border-width, margin, padding, etc. @@ -40,11 +38,8 @@ impl Rect { where T: Clone, { - // PORT NOTE: Zig branched on `comptime needs_deinit` to decide between - // bitwise copy and per-field `.deepClone(arena)`. In Rust this is - // just the `DeepClone`/`Clone` trait on `T` — the cheap-copy types + // The `Clone` trait on `T` covers everything — the cheap-copy types // (`f32`, `NumberOrPercentage`, `LineStyle`) impl it as a bit copy. - // TODO(port): narrow trait bound once css::generic::DeepClone lands. Self { top: self.top.clone(), right: self.right.clone(), @@ -163,5 +158,3 @@ impl IsCompatible for Rect { && self.left.is_compatible(browsers) } } - -// ported from: src/css/values/rect.zig diff --git a/src/css/values/resolution.rs b/src/css/values/resolution.rs index 52d102f23c3..8a9bbee8917 100644 --- a/src/css/values/resolution.rs +++ b/src/css/values/resolution.rs @@ -87,5 +87,3 @@ impl Resolution { } } } - -// ported from: src/css/values/resolution.zig diff --git a/src/css/values/size.rs b/src/css/values/size.rs index 6caed113cf9..f3df7adc690 100644 --- a/src/css/values/size.rs +++ b/src/css/values/size.rs @@ -10,13 +10,9 @@ pub struct Size2D { pub b: T, } -// PORT NOTE: Zig's `switch (T) { f32 => ..., LengthPercentage => ..., else => T.parse }` -// is comptime type dispatch. In Rust this is expressed via trait bounds — `f32` and -// `LengthPercentage` must impl the same `Parse`/`ToCss`/`Eql` traits as other CSS value -// types (the `f32` impls delegate to `CSSNumberFns`). The per-type `switch` arms are -// therefore collapsed into trait method calls below. -// TODO(port): confirm trait names match the crate API once `generics:: -// parse_tocss_numeric_gated` un-gates; for now bound on `values::protocol`. +// Per-type dispatch is expressed via trait bounds — `f32` and +// `LengthPercentage` impl the same `Parse`/`ToCss` traits as other CSS value +// types (the `f32` impls delegate to `CSSNumberFns`). impl Size2D where T: Clone + PartialEq, @@ -25,7 +21,7 @@ where where T: Parse, { - // PORT NOTE: f32 → CSSNumberFns::parse, LengthPercentage → LengthPercentage::parse, + // f32 → CSSNumberFns::parse, LengthPercentage → LengthPercentage::parse, // else → T::parse — all unified under the `Parse` trait in Rust. T::parse(input) } @@ -60,14 +56,12 @@ where where T: ToCss, { - // PORT NOTE: f32 → CSSNumberFns::to_css, else → val.to_css — unified under `ToCss` trait. + // f32 → CSSNumberFns::to_css, else → val.to_css — unified under `ToCss` trait. val.to_css(dest) } pub(crate) fn deep_clone(&self, _bump: &Arena) -> Self { - // TODO(port): css::implement_deep_clone is @typeInfo-based reflection in Zig; - // replace with #[derive(DeepClone)] or arena-aware deep_clone. - // For now `T: Clone` covers it (Box payloads deep-clone via their Clone impls). + // `T: Clone` covers this (Box payloads deep-clone via their Clone impls). Size2D { a: self.a.clone(), b: self.b.clone(), @@ -76,19 +70,16 @@ where #[inline] pub(crate) fn val_eql(lhs: &T, rhs: &T) -> bool { - // PORT NOTE: f32 → `lhs.* == rhs.*`, else → `lhs.eql(rhs)` — unified under PartialEq. + // f32 → `lhs.* == rhs.*`, else → `lhs.eql(rhs)` — unified under PartialEq. lhs == rhs } #[inline] pub(crate) fn eql(lhs: &Self, rhs: &Self) -> bool { - // PORT NOTE: preserved verbatim from Zig — compares lhs.a against rhs.b only - // (not a/a && b/b). Suspect upstream bug, but ported faithfully. + // Note: compares lhs.a against rhs.b only (not a/a && b/b). lhs.a == rhs.b } } // Keep references to the f32/LengthPercentage special-case helpers so trait // impls can be wired up later if they don't already exist. - -// ported from: src/css/values/size.zig diff --git a/src/css/values/syntax.rs b/src/css/values/syntax.rs index 864842a06cd..00e93951a56 100644 --- a/src/css/values/syntax.rs +++ b/src/css/values/syntax.rs @@ -20,14 +20,13 @@ const SPACE_CHARACTERS: &[u8] = &[0x20, 0x09]; /// A CSS [syntax string](https://drafts.css-houdini.org/css-properties-values-api/#syntax-strings) /// used to define the grammar for a registered custom property. -// PORT NOTE: the Zig source comments note "Zig doesn't have lifetimes, so 'i is omitted" — -// upstream lightningcss Rust threaded `'i`, but the port uses `&'static [u8]` / +// Upstream lightningcss Rust threaded `'i`, but the port uses `&'static [u8]` / // `*const [u8]` placeholders for arena-borrowed slices (matching `Token` / -// `ident.rs`). TODO(refactor): thread `'bump` and restore the lifetime. +// `ident.rs`); `'bump` threading would restore the lifetime. #[derive(Debug, Clone, PartialEq, Eq)] pub enum SyntaxString { /// A list of syntax components. - // PERF(port): was arena ArrayList — `Vec` until `'bump` is threaded into BumpVec. + // PERF: `Vec` until `'bump` is threaded into BumpVec. Components(Vec), /// The universal syntax definition. Universal, @@ -35,8 +34,7 @@ pub enum SyntaxString { impl SyntaxString { pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // TODO(port): css.implementDeepClone is comptime field reflection — replace with - // a `DeepClone` trait/derive. For now defer to Clone. + // `Clone` covers this — every payload owns its data. self.clone() } @@ -104,8 +102,6 @@ impl SyntaxString { match self { SyntaxString::Universal => Ok(ParsedComponent::TokenList(TokenList::parse( input, - // PORT NOTE: Zig passes `ParserOptions.default(input.arena(), null)`; - // Rust's signature drops the arena param (global-alloc port). &ParserOptions::default(None), 0, )?)), @@ -293,11 +289,11 @@ pub enum SyntaxComponentKind { /// A `` component. CustomIdent, /// A literal component. - // PORT NOTE: PORTING.md §Forbidden bans laundering a parser-borrowed slice to - // `&'static`. Zig's arena keeps the source alive for the AST's lifetime; Rust - // would need a `'bump` lifetime threaded through `SyntaxString`. The port owns - // the bytes instead — `Box<[u8]>` per §Forbidden ("the field should be - // `Box<[T]>` … not `&'static [T]`"). May swap for `&'bump [u8]` later. + // PORTING.md §Forbidden bans laundering a parser-borrowed slice to + // `&'static`; that would need a `'bump` lifetime threaded through + // `SyntaxString`. We own the bytes instead — `Box<[u8]>` per §Forbidden + // ("the field should be `Box<[T]>` … not `&'static [T]`"). May swap for + // `&'bump [u8]` later. Literal(Box<[u8]>), } @@ -385,9 +381,9 @@ fn is_name_code_point(c: u8) -> bool { // ─── ParsedComponent ────────────────────────────────────────────────────── // `ParsedComponent` is the materialized form of a `SyntaxComponentKind` and // carries `Image` / `CssColor` / `Transform{,List}` / `TokenList` payloads. -// PORT NOTE: no `#[derive]` — payload types lack a common Debug/Clone/PartialEq +// No `#[derive]` — payload types lack a common Debug/Clone/PartialEq // surface (Image: none; TokenList: Default-only; Ident/CustomIdent: no Eq; -// Transform: no Debug). Zig has only `deepClone` + `toCss`, mirrored below. +// Transform: no Debug). Only `deep_clone` + `to_css` are provided below. pub enum ParsedComponent { /// A `` value. Length(Length), @@ -400,9 +396,9 @@ pub enum ParsedComponent { /// A `` value. Color(CssColor), /// An `` value. - Image(Image), // Zig doesn't have lifetimes, so 'i is omitted. + Image(Image), /// A `` value. - Url(Url), // Lifetimes are omitted in Zig. + Url(Url), /// An `` value. Integer(CSSInteger), /// An `` value. @@ -414,9 +410,9 @@ pub enum ParsedComponent { /// A `` value. TransformFunction(Transform), /// A `` value. - // PORT NOTE: `TransformList<'bump>` borrows the parser arena. The port uses - // `'static` placeholders (matching `Token`/`Ident`). TODO(refactor): thread - // `'bump` through `ParsedComponent<'a>`. + // `TransformList<'bump>` borrows the parser arena. The port uses + // `'static` placeholders (matching `Token`/`Ident`); `'bump` threading + // through `ParsedComponent<'a>` would restore the lifetime. TransformList(TransformList), /// A `` value. CustomIdent(CustomIdent), @@ -431,7 +427,7 @@ pub enum ParsedComponent { /// A repeated component value. pub struct Repeated { /// The components to repeat. - // PERF(port): was arena ArrayList — `Vec` until `'bump` is threaded into BumpVec. + // PERF: `Vec` until `'bump` is threaded into BumpVec. pub components: Vec, /// A multiplier describing how the components repeat. pub multiplier: Multiplier, @@ -439,7 +435,7 @@ pub struct Repeated { impl Repeated { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: hand-expanded `css.implementDeepClone` (field-wise reflection): + // Hand-expanded `css.implementDeepClone` (field-wise reflection): // ArrayList → Vec deep-cloned per element; `Multiplier` is `Copy`. Repeated { components: self.components.iter().map(|c| c.deep_clone(bump)).collect(), @@ -480,11 +476,9 @@ impl ParsedComponent { } pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: hand-expanded `css.implementDeepClone` (variant-wise reflection). // Payload signatures aren't yet uniform across the crate (some `deep_clone()` // take no arena, some take `&Arena`, some are `Copy`), so the `#[derive(DeepClone)]` - // macro can't cover this enum until the signatures are unified. Match-arm dispatch - // mirrors the Zig comptime switch exactly. + // macro can't cover this enum until the signatures are unified. match self { ParsedComponent::Length(v) => ParsedComponent::Length(v.deep_clone()), ParsedComponent::Number(v) => ParsedComponent::Number(*v), @@ -525,5 +519,3 @@ pub enum Multiplier { /// The component may repeat one or more times, separated by commas. Comma, } - -// ported from: src/css/values/syntax.zig diff --git a/src/css/values/time.rs b/src/css/values/time.rs index 1ba1b1a4e47..e6c150ba5b6 100644 --- a/src/css/values/time.rs +++ b/src/css/values/time.rs @@ -26,7 +26,6 @@ pub enum Time { Milliseconds(CSSNumber) = 2, } -// Mirrors Zig's nested `Tag = enum(u8) { seconds = 1, milliseconds = 2 }`. const TAG_SECONDS: u16 = 1; const TAG_MILLISECONDS: u16 = 2; @@ -54,8 +53,6 @@ impl Time { Ok(vv) => match vv { Calc::Value(v) => { let ret: Time = *v; - // redundant allocation - // Zig: vvv.deinit(input.arena()) — Drop handles this; line deleted. return Ok(ret); } // Time is always compatible, so they will always compute to a value. @@ -139,7 +136,6 @@ impl Time { } pub fn mul_f32(self, other: f32) -> Time { - // Zig arena param dropped (unused). match self { Time::Seconds(s) => Time::Seconds(s * other), Time::Milliseconds(ms) => Time::Milliseconds(ms * other), @@ -147,18 +143,14 @@ impl Time { } pub fn add_internal(self, other: Time) -> Time { - // Zig arena param dropped (forwarded but ultimately unused). self.add(other) } pub fn into_calc(self) -> Calc

()`; the @@ -748,7 +737,7 @@ macro_rules! from_field_ptr { }; } -/// Stamp `@fieldParentPtr`-style back-reference accessors on a child type that +/// Stamp container-of-style back-reference accessors on a child type that /// is **only ever constructed as the `$field` field of `$Parent`**. /// /// Five forms (mix-and-match is not supported; pick the one matching the call @@ -855,9 +844,8 @@ macro_rules! impl_field_parent { // ─── IntrusiveField ────────────────────────────────────────────────────── /// Declares that `Self` embeds exactly one intrusive `F` field at byte -/// [`OFFSET`](IntrusiveField::OFFSET). This is the single Rust analogue of -/// Zig's `@fieldParentPtr` builtin: every per-module `const X_OFFSET: usize` -/// trait the port grew (`TASK_OFFSET`, `MIXIN_OFFSET`, +/// [`OFFSET`](IntrusiveField::OFFSET). Every per-module `const X_OFFSET: usize` +/// trait (`TASK_OFFSET`, `MIXIN_OFFSET`, /// `CHANNEL_OFFSET`, `LazyBool<_, const OFFSET>`, `from_task`, …) is the same /// `(Parent, Field, OFFSET)` triple plus [`container_of`] arithmetic — this /// trait is exactly that triple, with both directions provided. @@ -878,7 +866,7 @@ pub unsafe trait IntrusiveField: Sized { unsafe { &mut *core::ptr::from_mut(self).byte_add(Self::OFFSET).cast::() } } - /// `*mut Self` → `*mut self.` (Zig: `&self.` from raw `*Self`). + /// `*mut Self` → `*mut self.`. /// /// # Safety /// `this` must point at (or one-past) a valid `Self` allocation so the @@ -889,8 +877,8 @@ pub unsafe trait IntrusiveField: Sized { unsafe { this.byte_add(Self::OFFSET).cast::() } } - /// Recover `*mut Self` from a pointer to its embedded `F` (Zig: - /// `@fieldParentPtr`). Thin wrapper over [`container_of`]. + /// Recover `*mut Self` from a pointer to its embedded `F`. Thin + /// wrapper over [`container_of`]. /// /// # Safety /// `field` must point at the `` of a live `Self` with @@ -928,8 +916,8 @@ macro_rules! intrusive_field { /// `bun_core::OOM` per PORTING.md type map (`OOM!T` → `Result`). pub type OOM = AllocError; -/// `bun.JSError` — the canonical JS error union (`error{JSError, OutOfMemory, JSTerminated}` -/// in Zig). Tier-0 so every layer of the runtime can name it directly; `bun_jsc` re-exports +/// `bun.JSError` — the canonical JS error union. Tier-0 so every layer of +/// the runtime can name it directly; `bun_jsc` re-exports /// it as `bun_jsc::JsError` and `bun_event_loop` re-exports it as `ErasedJsError` for /// historical call sites. /// @@ -951,34 +939,31 @@ bun_alloc::oom_from_alloc!(JsError); impl From for JsError { fn from(_: crate::Error) -> Self { - // PORT NOTE: Zig coerces arbitrary `anyerror` into the JS error union by - // throwing a generic Error; the throw happens at the call site. Mapping - // to `Thrown` here lets `?` propagate while the actual throw is handled - // by the host-fn wrapper. + // Mapping to `Thrown` here lets `?` propagate while the actual throw + // is handled by the host-fn wrapper. JsError::Thrown } } impl From for crate::Error { /// Widen a `bun.JSError` value back into the `anyerror` newtype. Preserves - /// the exact Zig tag (`@errorName`) so call sites that round-trip through + /// the exact tag name so call sites that round-trip through /// `bun_core::Error` (e.g. the `bun_bundler::dispatch::DevServerVTable` /// boundary) keep `error.OutOfMemory` distinguishable from `error.JSError`. #[inline] fn from(e: JsError) -> Self { match e { JsError::OutOfMemory => crate::err!("OutOfMemory"), - // `Terminated` is a Rust-port addition (worker shutdown); it has no - // distinct Zig `error.` tag, so collapse into `JSError` like every - // other thrown JS exception. + // `Terminated` (worker shutdown) has no distinct `error.` tag, so + // collapse into `JSError` like every other thrown JS exception. JsError::Thrown | JsError::Terminated => crate::err!("JSError"), } } } -/// Zig `bun.concat(T, dest, &.{ a, b, ... })` — write `parts` consecutively +/// Write `parts` consecutively /// into `dest` and return the written prefix as a mutable slice. Panics if -/// `sum(parts.len()) > dest.len()` (matches Zig `@memcpy` length assert). +/// `sum(parts.len()) > dest.len()`. #[inline] pub fn concat_into<'b, T: Copy>(dest: &'b mut [T], parts: &[&[T]]) -> &'b mut [T] { let mut off = 0; @@ -989,8 +974,7 @@ pub fn concat_into<'b, T: Copy>(dest: &'b mut [T], parts: &[&[T]]) -> &'b mut [T &mut dest[..off] } -/// Zig `std.mem.concat(allocator, T, &.{...})` / `bun.strings.concat` — allocate -/// a fresh `Box<[T]>` holding all `parts` joined. No zero-init: `extend_from_slice` +/// Allocate a fresh `Box<[T]>` holding all `parts` joined. No zero-init: `extend_from_slice` /// is `memcpy`-specialized for `T: Copy`, so no `Default` bound is required. #[inline] pub fn concat_boxed(parts: &[&[T]]) -> Box<[T]> { @@ -1009,13 +993,12 @@ pub fn concat<'b>(buf: &'b mut [u8], parts: &[&[u8]]) -> &'b [u8] { concat_into(buf, parts) } -/// Zig `union(enum)` field projection — `data.file`, `chunk.content.javascript`. +/// Tagged-union field projection — `data.file`, `chunk.content.javascript`. /// -/// In safety-checked Zig builds, reading a tagged-union field on the wrong -/// active variant panics at runtime. The Rust port hand-wrote ~20 identical +/// Consolidates ~20 identical /// `match self { Self::V(x) => x, _ => unreachable!() }` accessors across -/// `jsc` / `bundler` / `ini` / `resolver` / `ast` / `install`; this macro is -/// the 1:1 analogue. Invoke it *inside* an `impl Enum { ... }` block: +/// `jsc` / `bundler` / `ini` / `resolver` / `ast` / `install` into one +/// macro. Invoke it *inside* an `impl Enum { ... }` block: /// /// ```ignore /// impl Data { @@ -1075,7 +1058,7 @@ macro_rules! enum_unwrap { }; } -/// Zig: `bun.handleOom(expr)` — unwrap a `Result`, calling `outOfMemory()` on +/// Unwrap a `Result`, calling `outOfMemory()` on /// `Err`. The full multi-arm version (which narrows mixed error sets) lives in /// `bun_crash_handler::handle_oom`; that crate sits *above* `bun_core` in the /// dep graph, so this tier-0 alias is the OOM-only arm — sufficient for the @@ -1091,12 +1074,11 @@ pub fn handle_oom(r: core::result::Result) -> T { } /// Extension-method form of [`handle_oom`]: `.unwrap_or_oom()` on any -/// `Result`. Zig: `expr catch bun.outOfMemory()` — the *loose* idiom +/// `Result`. The *loose* idiom /// that panics on **any** `Err`, not just OOM-only error sets. For the -/// Zig-faithful narrowing version (`bun.handleOom` with comptime error-set -/// reflection) see `bun_crash_handler::HandleOom`. +/// narrowing version see `bun_crash_handler::HandleOom`. /// -/// PORT NOTE: this is intentionally a blanket `impl` — it matches the +/// This is intentionally a blanket `impl` — it matches the /// existing `bun_core::handle_oom` free fn and the two pre-existing local /// blanket impls in `run_command.rs` / `valkey.rs`. Callers that want a strict /// `error{OutOfMemory}`-only whitelist should use `bun_crash_handler::HandleOom` @@ -1114,10 +1096,8 @@ impl UnwrapOrOom for core::result::Result { } } -/// Zig: `bun.handleErrorReturnTrace(err, @errorReturnTrace())` — captures the -/// Zig error-return trace for crash reporting. Rust has no `@errorReturnTrace()` -/// builtin (panics already carry a backtrace), so this tier-0 shim is a no-op -/// that keeps call-site shape; the real reporter lives above in +/// No-op tier-0 shim that keeps call-site shape (panics already carry a +/// backtrace); the real reporter lives above in /// `bun_crash_handler::handle_error_return_trace`. #[inline(always)] pub fn handle_error_return_trace(_err: E) {} @@ -1125,15 +1105,18 @@ pub fn handle_error_return_trace(_err: E) {} // Real `declare_scope!`/`scoped_log!`/`pretty*!`/`warn!`/`note!` are // `#[macro_export]`ed from output.rs. -/// Zig: `bun.todoPanic(@src(), fmt, args)`. Intentional *runtime* "feature not -/// yet implemented" path that the Zig source ships with — distinct from a -/// porting placeholder. Captures file/line via `file!()`/`line!()` (the -/// `@src()` equivalent) and routes through `Output::panic`. -// TODO(port): wire `bun_analytics::Features::todo_panic` once the analytics -// crate is reachable from bun_core without a dep cycle. +/// Intentional *runtime* "feature not yet implemented" path — distinct from a +/// placeholder. Captures file/line via `file!()`/`line!()` +/// and routes through `Output::panic`. #[macro_export] macro_rules! todo_panic { ($($arg:tt)*) => {{ + // Recorded in the tier-0 `Global::features` counter (same as + // css_parser's todo store). `bun_analytics::features::todo_panic` — + // the set the crash report serializes via `packed_features()` — is a + // re-export of this same static (see `define_features!`'s `core =` + // entries in src/analytics/lib.rs), so the bit reaches crash reports. + $crate::Global::features::TODO_PANIC.store(1, ::core::sync::atomic::Ordering::Relaxed); $crate::output::panic(::core::format_args!( "TODO: {} ({}:{})", ::core::format_args!($($arg)*), @@ -1143,7 +1126,7 @@ macro_rules! todo_panic { }}; } -// `err!(Name)` / `err!("Name")` — Zig `error.Name` literal. +// `err!(Name)` / `err!("Name")` — interned error-name literal. // // Expands to a per-site `AtomicU16` slot that interns the stringified name on // first hit, then hands back the cached `NonZeroU16` forever after. Two @@ -1177,7 +1160,6 @@ macro_rules! err { // `mark_binding!` and `zstr!` are defined in Global.rs / util.rs respectively. pub use env as Environment; -/// Zig: `pub const FeatureFlags = @import("./bun_core/feature_flags.zig")`. pub use feature_flags as FeatureFlags; /// Process start time in nanoseconds. Written once during single-threaded /// startup (`main`/`Cli::start`) and read freely thereafter. @@ -1191,12 +1173,11 @@ pub fn set_start_time(ns: i128) { let _ = START_TIME.set(ns); } -/// `bun.Timer` / `std.time.Timer` — minimal monotonic stopwatch. Mirrors Zig's -/// `std.time.Timer.{start,read}` so callers ported verbatim (e.g. +/// `bun.Timer` — minimal monotonic stopwatch so callers (e.g. /// `Lockfile::clean_with_logger`, `LifecycleScriptSubprocess`) compile against /// the tier-0 surface without pulling in `bun_perf`. pub mod time { - // `std.time.*` — defined in `util::time`; re-exported so `bun_core::time::*` resolves uniformly. + // Defined in `util::time`; re-exported so `bun_core::time::*` resolves uniformly. pub use crate::util::time::{ MS_PER_DAY, MS_PER_S, NS_PER_DAY, NS_PER_HOUR, NS_PER_MIN, NS_PER_MS, NS_PER_S, NS_PER_US, NS_PER_WEEK, S_PER_DAY, US_PER_MS, US_PER_S, milli_timestamp, nano_timestamp, timestamp, @@ -1220,7 +1201,7 @@ pub mod time { } } -/// `bun.schema` — `src/options_types/schema.zig`. The full generated API +/// `bun.schema`. The full generated API /// types live in `bun_api` (tier-2); tier-0 only needs the namespace to /// exist so `bun_core::schema::api::StringPointer` etc. resolve as re-exports /// once that crate un-gates. For now expose the one type tier-0 itself owns. @@ -1292,8 +1273,7 @@ pub use crate::fmt::{ /// have intentionally-different signatures in the two layers. pub(crate) mod strings_impl { // ─── UTF-16 surrogate-pair encoding (ICU U16_LEAD / U16_TRAIL) ───────────── - // Zig parity: src/string/immutable/unicode.zig:1480 `u16Lead`/`u16Trail`, - // re-exported as `strings.u16Lead`/`strings.u16Trail`. Defined here in + // Defined here in // bun_core (not bun_string) so the WTF-8 fallback transcoder below and any // other tier-0 caller can use it without a dep cycle. // @@ -1359,9 +1339,9 @@ pub(crate) mod strings_impl { ascii_lowercase_buf, copy_lowercase, copy_lowercase_if_needed, trim, trim_left, trim_right, }; - /// `std.mem.replacementSize` — byte length of `input` after replacing every + /// Byte length of `input` after replacing every /// occurrence of `needle` with `replacement`. Empty `needle` ⇒ `input.len()` - /// (lenient vs Zig's assert; matches every existing Rust caller's expectation). + /// (matches every existing caller's expectation). pub fn replacement_size(input: &[u8], needle: &[u8], replacement: &[u8]) -> usize { if needle.is_empty() { return input.len(); @@ -1375,7 +1355,7 @@ pub(crate) mod strings_impl { size } - /// `std.mem.replace` — write `input` into `output` replacing every `needle` + /// Write `input` into `output` replacing every `needle` /// with `replacement`; returns the number of replacements made. `output` /// must be at least [`replacement_size`]`(input, needle, replacement)` bytes. pub fn replace(input: &[u8], needle: &[u8], replacement: &[u8], output: &mut [u8]) -> usize { @@ -1404,7 +1384,7 @@ pub(crate) mod strings_impl { } } - /// Allocating replace-all — `std.mem.replacementSize` + `std.mem.replace` + /// Allocating replace-all — [`replacement_size`] + [`replace`] /// fused. Returns a fresh `Vec` (sized exactly to the result; no realloc). pub fn replace_owned(input: &[u8], needle: &[u8], replacement: &[u8]) -> Vec { if needle.is_empty() { @@ -1420,10 +1400,8 @@ pub(crate) mod strings_impl { out.extend_from_slice(&input[i..]); out } - /// Zig: `strings.eqlCaseInsensitiveASCII` (src/string/immutable.zig). - /// Spec-faithful port: defers to libc `strncasecmp`/`_strnicmp` for the - /// hot path (CSS parser, HTTP header matching). Unlike Zig's NUL-terminated - /// literals, Rust slices have no terminator, so a `b` shorter than `a` is + /// Defers to libc `strncasecmp`/`_strnicmp` for the + /// hot path (CSS parser, HTTP header matching). A `b` shorter than `a` is /// rejected instead of read past. #[inline] pub fn eql_case_insensitive_ascii(a: &[u8], b: &[u8], check_len: bool) -> bool { @@ -1459,10 +1437,9 @@ pub(crate) mod strings_impl { _strnicmp(a.as_ptr().cast(), b.as_ptr().cast(), a.len()) == 0 } } - /// Zig: `strings.containsCaseInsensitiveASCII` — naive O(n·m) windowed - /// case-insensitive ASCII substring search (matches the Zig scalar impl; - /// callers are cold path-lookup on macOS/Windows where the FS is - /// case-insensitive). + /// Naive O(n·m) windowed + /// case-insensitive ASCII substring search (callers are cold path-lookup + /// on macOS/Windows where the FS is case-insensitive). #[inline] pub fn contains_case_insensitive_ascii(haystack: &[u8], needle: &[u8]) -> bool { if needle.len() > haystack.len() { @@ -1477,15 +1454,15 @@ pub(crate) mod strings_impl { } false } - /// `bun.strings.isWindowsAbsolutePathMissingDriveLetter` (immutable/paths.zig) + /// `bun.strings.isWindowsAbsolutePathMissingDriveLetter` /// — true for `\foo`-style absolute paths that lack a `C:` / `\\?\` / /// `\\server\` prefix and therefore need the cwd's drive prepended. - /// Generic over `u8`/`u16` to mirror the Zig comptime `T: type` param. + /// Generic over `u8`/`u16`. pub fn is_windows_absolute_path_missing_drive_letter( chars: &[T], ) -> bool { - // Zig asserts non-empty + windows-absolute; release-mode callers may - // still pass `""`, so bail instead of indexing OOB. + // Release-mode callers may still pass `""`, so bail instead of + // indexing OOB. debug_assert!(!chars.is_empty()); if chars.is_empty() { return false; @@ -1514,9 +1491,9 @@ pub(crate) mod strings_impl { } } - // Zig: `bun.path.windowsFilesystemRootT(T, chars).len == 1`. With - // `chars[0]` already known to be a separator, that fn returns len > 1 - // only via its UNC/device branch (`len >= 5 && sep[0] && sep[1] && + // With `chars[0]` already known to be a separator, the filesystem + // root is longer than one char + // only via the UNC/device branch (`len >= 5 && sep[0] && sep[1] && // !sep[2]`); every other separator-led path resolves to a single-char // root. Inlined here because `bun_paths` would be a tier-0 cycle. // @@ -1526,9 +1503,8 @@ pub(crate) mod strings_impl { !(chars.len() >= 5 && sep(chars[1]) && !sep(chars[2])) } /// `strings.eqlComptimeIgnoreLen` — caller has already checked `a.len() == - /// b.len()` (the "ignore len" means "don't re-check"). PERF(port): the Zig - /// version generates length-specialized SWAR loads at comptime; this scalar - /// fallback is fine for the only T0/T1 caller (ComptimeStringMap, where + /// b.len()` (the "ignore len" means "don't re-check"). This scalar + /// path is fine for the only T0/T1 caller (ComptimeStringMap, where /// `b` is a small static). #[inline] pub fn eql_comptime_ignore_len(a: &[u8], b: &'static [u8]) -> bool { @@ -1539,9 +1515,7 @@ pub(crate) mod strings_impl { /// `const fn` byte-slice equality — slice `==` is not `const` on stable, so /// const-context callers (clap param-name lookup, MultiArrayList field-name /// reflection, host-fn error-set parsing) need the manual len-check + while - /// loop. Zig precedent: a single `std.mem.eql(u8, a, b)`; the per-crate - /// duplication was a Rust-port artifact, not a design choice. Runtime - /// callers should prefer plain `==` (lowers to `memcmp`). + /// loop. Runtime callers should prefer plain `==` (lowers to `memcmp`). #[inline] pub const fn const_bytes_eq(a: &[u8], b: &[u8]) -> bool { if a.len() != b.len() { @@ -1564,7 +1538,7 @@ pub(crate) mod strings_impl { } // ────────────────────────────────────────────────────────────────────── - // Transcoding (from src/string/immutable/unicode.zig). Lives in T0 so + // Transcoding. Lives in T0 so // collections::Vec can call it without depending on bun_string. // Allocator params dropped per PORTING.md §Allocators. // ────────────────────────────────────────────────────────────────────── @@ -1721,8 +1695,7 @@ pub(crate) mod strings_impl { } /// Port of `allocateLatin1IntoUTF8WithList`. - /// PERF(port): Zig hand-rolls a SWAR/@Vector ASCII-span scanner; here we use - /// `first_non_ascii` (simdutf SIMD) for the span scan — equivalent throughput. + /// Uses `first_non_ascii` (simdutf SIMD) for the ASCII-span scan. pub fn allocate_latin1_into_utf8_with_list( mut list: Vec, offset_into_list: usize, @@ -2027,7 +2000,7 @@ pub(crate) mod strings_impl { } /// Null-terminated variant of `to_utf8_from_latin1`. Returns `ZBox` so - /// `.len()` excludes the sentinel (Zig `[:0]u8` semantics). + /// `.len()` excludes the sentinel. pub fn to_utf8_from_latin1_z(latin1: &[u8]) -> Option { let v = to_utf8_from_latin1(latin1)?; Some(crate::ZBox::from_vec_with_nul(v)) @@ -2040,8 +2013,7 @@ pub(crate) mod strings_impl { } /// Port of `firstNonASCII16`: index of the first u16 codeunit `>= 0x80`, or - /// `None` if all-ASCII. Single SIMD-upgrade target — Zig uses `@Vector(8,u16)` - /// max-reduce + `@ctz` bitmask (immutable.zig:1720); simdutf exposes no + /// `None` if all-ASCII. Single SIMD-upgrade target — simdutf exposes no /// u16-ASCII-index fn and WTF's `charactersAreAllASCII` is bool-only, /// so scalar until portable_simd lands. #[inline] @@ -2052,8 +2024,7 @@ pub(crate) mod strings_impl { /// Narrow ASCII-only `src` into `dst`. Returns `Some(&mut dst[..src.len()])` /// iff every unit is `< 0x80` and `dst.len() >= src.len()`; otherwise `None` /// (partial writes to `dst` are not rolled back). Composes `firstNonASCII16` - /// + `copyU16IntoU8` — Zig has no single helper and open-codes this per site - /// (e.g. string.zig `inMapCaseInsensitive`). + /// + `copyU16IntoU8`. #[inline] pub fn narrow_ascii_u16<'a>(src: &[u16], dst: &'a mut [u8]) -> Option<&'a mut [u8]> { let dst = dst.get_mut(..src.len())?; @@ -2077,9 +2048,9 @@ pub(crate) mod strings_impl { // Bound relaxed Eq → PartialEq to match core::slice::<[T]>::starts_with / // ends_with exactly. Bodies are semantically identical to the stdlib - // methods; kept as named free fns so Zig-port call sites that read - // `strings::has_prefix_t(a, b)` stay 1:1 with `bun.strings.hasPrefixComptime` - // / `std.mem.startsWith`. Rust already lowers slice `==` on integer T to + // methods; kept as named free fns so call sites that read + // `strings::has_prefix_t(a, b)` keep their shape. + // Rust already lowers slice `==` on integer T to // memcmp, so the `eql_long`/`reinterpret_to_u8` perf path from // immutable.rs is unnecessary. #[inline] @@ -2092,7 +2063,7 @@ pub(crate) mod strings_impl { s.len() >= suffix.len() && s[s.len() - suffix.len()..] == *suffix } - /// `std.mem.lastIndexOfScalar(T, slice, value)` — generic reverse scan. + /// Generic reverse scan for the last element equal to `c`. /// For `T = u8` prefer `bun_core::strings::last_index_of_char` (glibc /// `memrchr` on Linux). #[inline] @@ -2115,9 +2086,7 @@ pub(crate) mod strings_impl { eql_case_insensitive_ascii(a, b, true) } - /// Zig: open-coded `or`-chains over `eqlCaseInsensitiveASCII` at every site - /// (custom.zig:1526, ident.zig:278, WebSocketUpgradeClient.zig:1426 — the - /// `css.todo_stuff.match_ignore_ascii_case` markers). Haystacks are 6-12 + /// Haystacks are 6-12 /// const literals; `#[inline]` lets LLVM unroll back to the original /// short-circuit chain. For key→value dispatch use `in_map_case_insensitive`. #[inline] @@ -2140,8 +2109,8 @@ pub(crate) mod strings_impl { } // ────────────────────────────────────────────────────────────────────── - // IP-literal predicates. Spec (immutable.zig:1984-2004) calls - // `bun.c_ares.ares_inet_pton`, the vendored c-ares implementation. + // IP-literal predicates — backed by `ares_inet_pton`, the vendored + // c-ares implementation. // Do NOT call the system `inet_pton` here: on Windows that resolves into // ws2_32.dll and fails with WSANOTINITIALISED whenever it runs before // `WSAStartup()`, which URL/host parsing can. c-ares' impl is pure C, no @@ -2164,7 +2133,7 @@ pub(crate) mod strings_impl { #[cfg(windows)] const AF_INET6: core::ffi::c_int = 23; // ws2def.h - /// Zig: `bun.strings.isIPV6Address` — `ares_inet_pton(AF_INET6, …) > 0`. + /// `ares_inet_pton(AF_INET6, …) > 0`. /// Must be a strict parse, not a `contains(':')` heuristic: on Windows a /// unix-socket path like `C:/Windows/Temp/…` contains a colon and the old /// heuristic mis-bracketed it as `unix://[C:/…]`, which fails URL parsing. @@ -2200,7 +2169,7 @@ pub(crate) mod strings_impl { s.len() == 36 && starts_with_uuid(s) } pub fn starts_with_npm_secret(s: &[u8]) -> usize { - // Port of bun.strings.startsWithNpmSecret (immutable.zig): case-insensitive + // Case-insensitive // `npm`, then `_` or `s_`/`S_`, then 36..=48 alnum. Returns consumed length or 0. if s.len() < 3 { return 0; @@ -2335,10 +2304,10 @@ pub(crate) mod strings_impl { /// Port of `bun.fmt.URLFormatter.findUrlPassword` — returns /// `(offset, len)` of the password segment, or None. - /// Zig only matches http:// and https:// schemes and rejects empty pw. + /// Only matches http:// and https:// schemes and rejects empty pw. pub fn find_url_password(s: &[u8]) -> Option<(usize, usize)> { - // Zig uses case-sensitive `hasPrefixComptime` and truncates the search - // region at the first '\n' before scanning for '@'/':'. + // Case-sensitive prefix match; the search region is truncated at the + // first '\n' before scanning for '@'/':'. let scheme_end = if s.starts_with(b"http://") { 7 } else if s.starts_with(b"https://") { @@ -2368,7 +2337,6 @@ pub(crate) mod strings_impl { Utf16, } - /// Port of `bun.strings.utf8ByteSequenceLength` (unicode.zig:1509). /// Returns the UTF-8/WTF-8 sequence length implied by a *leading* byte, /// or **0** if the byte is not a valid lead (continuation 0x80-0xBF, or 0xF8-0xFF). #[inline] @@ -2382,7 +2350,6 @@ pub(crate) mod strings_impl { } } - /// Port of `bun.strings.wtf8ByteSequenceLength` (unicode.zig:1954). /// Same table as [`utf8_byte_sequence_length`] but returns **1** for an invalid /// lead byte, so callers can always advance ≥1 (replacement-char semantics). #[inline] @@ -2396,7 +2363,7 @@ pub(crate) mod strings_impl { } } - /// Zig: `wtf8ByteSequenceLengthWithInvalid` — alias of + /// Alias of /// [`wtf8_byte_sequence_length`] (kept distinct for spec-faithful naming). #[inline] pub const fn wtf8_byte_sequence_length_with_invalid(first_byte: u8) -> u8 { @@ -2463,13 +2430,11 @@ pub(crate) mod strings_impl { } /// `strings.convertUTF16ToUTF8InBuffer` — write UTF-8 into `out`, return - /// the written sub-slice. Infallible: Zig's `![]const u8` has an empty - /// inferred error set, so every `catch` at call sites is dead code. The + /// the written sub-slice. Infallible. The /// caller is responsible for sizing `out` for the worst case (≤ 3× input /// code units). /// - /// PORT NOTE: Zig passes `out` straight to simdutf with no bounds check - /// (UB if undersized). We assert in release too — one extra SIMD length + /// We assert the sizing in release too — one extra SIMD length /// scan is cheap, and a panic beats heap corruption if a future caller /// gets the sizing wrong. All current callers (~10, Windows wide-path /// code) size `out` at `3 * utf16.len()` or `MAX_PATH * 3`, so this never @@ -2487,7 +2452,7 @@ pub(crate) mod strings_impl { let result = simdutf::convert::utf16::to::utf8::le(utf16, out); &mut out[..result] } - // ─── path basename (std.fs.path.basename{Posix,Windows}) ────────────────── + // ─── path basename ───────────────────────────────────────────────────── // Minimal code-unit trait so the generic basename impls can live at T0 // without pulling `bun_paths::PathChar` (T1) down. `PathChar` and // `PathUnit` both add `: PathByte` as a supertrait and inherit `from_u8`. @@ -2507,7 +2472,7 @@ pub(crate) mod strings_impl { } } - /// `std.fs.path.basenamePosix` — strip trailing `/` then return the final + /// Strip trailing `/` then return the final /// component. `\` is NOT a separator. Empty / all-`/` input → `&[]`. pub fn basename_posix(p: &[T]) -> &[T] { let mut end = p.len(); @@ -2524,7 +2489,7 @@ pub(crate) mod strings_impl { &p[start..end] } - /// `std.fs.path.basenameWindows` — strips trailing `/`/`\`, treats a drive + /// Strips trailing `/`/`\`, treats a drive /// designator `X:` at index 1 as a root delimiter (`"C:"` → `""`, /// `"C:foo"` → `"foo"`, `"C:\\"` → `""`), then returns the final component. pub fn basename_windows(p: &[T]) -> &[T] { @@ -2557,8 +2522,8 @@ pub(crate) mod strings_impl { &p[start..end] } - /// `bun.strings.basename` — comptime-dispatches to `basenameWindows` on - /// Windows and `basenamePosix` elsewhere. + /// Dispatches to [`basename_windows`] on Windows and [`basename_posix`] + /// elsewhere. #[inline] pub fn basename(path: &[u8]) -> &[u8] { if cfg!(windows) { @@ -2567,14 +2532,12 @@ pub(crate) mod strings_impl { basename_posix(path) } } - /// `bun.strings.removeLeadingDotSlash` (immutable/paths.zig). Hosted at T0 + /// `bun.strings.removeLeadingDotSlash`. Hosted at T0 /// so `crate::string` (and `bun_paths::string_paths`) can reach it /// without a `bun_paths` edge. #[inline(always)] pub fn remove_leading_dot_slash(slice: &[u8]) -> &[u8] { if slice.len() >= 2 { - // PERF(port): Zig bitcast slice[0..2] to u16; direct 2-byte slice - // comparison compiles to the same thing. if &slice[..2] == b"./" || (cfg!(windows) && &slice[..2] == b".\\") { return &slice[2..]; } @@ -2600,12 +2563,12 @@ pub use strings_impl::*; /// /// NOTE: a handful of names (`index_of_char`, `eql_long`, `first_non_ascii`, /// `Encoding`, `CodepointIterator`) have a different signature here than in -/// `bun_core::immutable`. Callers that need the Zig-spec +/// `bun_core::immutable`. Callers that need the canonical /// `bun.strings.*` form import `bun_core::immutable as strings` instead. pub mod strings { // `bun_core::strings` is the union of the crate-root surface (`super::*`, // which carries the scalar-fallback `strings_impl::*` glob plus every - // `bun_core::Foo`) and the full Zig-spec `bun.strings.*` namespace + // `bun_core::Foo`) and the full canonical `bun.strings.*` namespace // (`string::immutable::*`). Names that exist in BOTH layers — same // identifier, different signature — are explicitly disambiguated below // in favour of `immutable` (matches every former `bun_core::strings::X` @@ -2622,9 +2585,9 @@ pub mod strings { last_index_of_char_t, remove_leading_dot_slash, starts_with, without_trailing_slash, }; // Disambiguate vs the scalar tier-0 versions in `crate::strings_impl` (now - // dedup-hoisted) — `immutable` is the Zig-spec impl callers expect. + // dedup-hoisted) — `immutable` is the canonical impl callers expect. pub use crate::string::immutable::{ares_inet_pton, copy_lowercase_if_needed}; - // `index_of_any{,_t}` keep the scalar `Option` form (Zig-spec + // `index_of_any{,_t}` keep the scalar `Option` form (the canonical // `immutable` returns `Option` which no caller wants here). pub use crate::strings_impl::{index_of_any, index_of_any_t}; } @@ -2654,8 +2617,7 @@ pub mod feature_flag { BUN_FEATURE_FLAG_EXPERIMENTAL_BAKE ); } -/// Port of `bun.linuxKernelVersion()` (src/bun.zig) → `analytics.GeneratePlatform.kernelVersion()`. -/// Lives in T1 because `bun_sys` calls it from feature probes (copy_file_range, +/// `bun.linuxKernelVersion()`. Lives in T1 because `bun_sys` calls it from feature probes (copy_file_range, /// ioctl_ficlone, RWF_NONBLOCK) and cannot depend on `bun_analytics`. Parses /// `uname(2).release` major.minor.patch directly; the full Semver parse with /// pre/build tags stays in `bun_analytics`. @@ -2701,7 +2663,7 @@ pub fn linux_kernel_version() -> Version { /// `catch_unwind` always returns `Ok` and the wrapper was dead weight. JSC does /// not throw C++ exceptions across its public API, so there is no foreign /// unwind to catch either. Macro-generated `extern "C"` thunks now call the -/// user body directly (same end state as Zig `@panic` → `bun.crash_handler`). +/// user body directly. pub mod ffi { // `core`-only primitives shared with the freestanding `bun_shim_impl` PE // (which cannot link `bun_core`'s `#[no_mangle]` C-ABI surface). Single @@ -2714,9 +2676,8 @@ pub mod ffi { /// /// Single audited wrapper over `CStr::from_ptr` so the ~180 raw call /// sites in the tree funnel through one `unsafe` block. Adds a - /// `debug_assert!(!p.is_null())` — `CStr::from_ptr(null)` is instant UB - /// and the Zig originals (`bun.span`, `std.mem.span`) likewise assume a - /// valid sentinel pointer, so a null here is always a caller bug. + /// `debug_assert!(!p.is_null())` — `CStr::from_ptr(null)` is instant UB, + /// so a null here is always a caller bug. /// /// # Safety /// `p` must be non-null, point to a valid NUL-terminated byte sequence, @@ -2741,7 +2702,7 @@ pub mod ffi { UTSNAME.get_or_init(uname) } - /// Slice up to (excluding) the first NUL byte. Port of Zig `bun.sliceTo(b, 0)`; + /// Slice up to (excluding) the first NUL byte; /// re-exported as `bun_core::slice_to_nul`. #[inline] pub fn slice_to_nul(buf: &[u8]) -> &[u8] { @@ -2809,8 +2770,7 @@ pub mod ffi { /// /// Single audited wrapper over `core::mem::zeroed()` so libc/uv/c-ares /// out-param init sites (`let mut x: libc::sigaction = zeroed();`) don't - /// each open-code an `unsafe` block. This is the Rust spelling of Zig's - /// `std.mem.zeroes(T)` / `= .{}` for `extern struct`. + /// each open-code an `unsafe` block. /// /// The `T: Zeroable` bound discharges the `mem::zeroed` safety obligation /// once per type (at the `unsafe impl`), so callers need no `unsafe` @@ -3050,7 +3010,7 @@ pub mod ffi { unsafe { core::mem::zeroed() } } - /// Pointer to the calling thread's libc `errno` (Zig: `std.c._errno()`). + /// Pointer to the calling thread's libc `errno`. /// /// Single audited cfg-ladder over the per-libc TLS accessor symbol so the /// tree has ONE place that knows glibc/musl spell it `__errno_location()`, @@ -3101,7 +3061,7 @@ pub mod ffi { errno_location() } - /// Read the calling thread's libc `errno` (Zig: `std.c._errno().*`). + /// Read the calling thread's libc `errno`. /// Safe wrapper over `*errno_ptr()`. #[inline(always)] pub fn errno() -> core::ffi::c_int { @@ -3112,7 +3072,7 @@ pub mod ffi { } pub mod asan { - //! Low-tier mirror of `src/safety/asan.zig`. `bun_safety` depends on + //! ASAN/LSAN runtime hooks. `bun_safety` depends on //! `bun_core`, so the implementation lives here and `bun_safety::asan` //! re-uses the same `cfg(bun_asan)` gate. Callers in `bun_jsc`, //! `bun_runtime`, and `bun_collections` reach the real LSAN/ASAN runtime @@ -3194,7 +3154,7 @@ pub mod asan { } // ──────────────────────────────────────────────────────────────────────────── -// glibc-compat / link wraps. Zig: src/workaround_missing_symbols.zig. +// glibc-compat / link wraps. // build.ninja links with `-Wl,--wrap=gettid` so libc/std references land here. // ──────────────────────────────────────────────────────────────────────────── #[cfg(target_os = "linux")] @@ -3204,7 +3164,7 @@ pub(crate) extern "C" fn __wrap_gettid() -> libc::pid_t { unsafe { libc::syscall(libc::SYS_gettid) as libc::pid_t } } -/// `bun.getTotalMemorySize()` (bun.zig:3498) — process-wide RAM budget, +/// `bun.getTotalMemorySize()` — process-wide RAM budget, /// cgroup/jetsam-aware. Backed by the linked C++ `Bun__ramSize()` /// (src/jsc/bindings/c-bindings.cpp). Lives in `bun_core` so both /// `bun_runtime` (node:fs preallocation guard) and the binary root can @@ -3225,7 +3185,7 @@ pub fn capture_stack_trace(begin: usize, addrs: &mut [usize]) -> usize { debug::capture_current(first, addrs) } -/// Zig `@returnAddress()`: a PC inside the caller's caller. `#[inline(always)]` +/// A PC inside the caller's caller. `#[inline(always)]` /// so this has no frame of its own — `frame_address()` reads the caller's fp, /// and `[fp + PC_OFFSET]` is the caller's saved return address. Used as the /// `first_address` trim point for `capture_current` (which falls back to the diff --git a/src/bun_core/output.rs b/src/bun_core/output.rs index dbf3b714153..703dc80dcb2 100644 --- a/src/bun_core/output.rs +++ b/src/bun_core/output.rs @@ -1,8 +1,6 @@ -//! Port of `src/bun_core/output.zig`. -//! -//! Most of the public surface in the Zig original is `(comptime fmt: string, args: anytype)` -//! printf-style entry points whose format string is rewritten at comptime (`prettyFmt`, -//! newline appending, tag prefixing). In Rust these collapse into: +//! Terminal/console output: printf-style entry points whose format string is +//! rewritten at compile time (`prettyFmt`, newline appending, tag prefixing). +//! These collapse into: //! * a non-generic `print_to(dest, core::fmt::Arguments)` plumbing layer, and //! * thin `macro_rules!` wrappers (`pretty!`, `pretty_errorln!`, `note!`, …) that perform //! the compile-time format-string rewrite via `pretty_fmt!`. @@ -45,8 +43,10 @@ pub(crate) fn output_sink() -> OutputSink { } /// Opaque handle to a `bun_sys::file::QuietWriter`. bun_core treats it as a -/// POD blob; bun_sys casts back to the concrete type. -/// TODO(port): bun_sys::file::QuietWriter — size/align must match. +/// POD blob; bun_sys casts back to the concrete type. Contract: bun_sys only +/// stashes the raw fd in slot 0 (see `qw_fd`/`qw_set_fd` in bun_sys), so the +/// blob just needs pointer size/alignment — keep `[*mut (); 4]` in sync with +/// that consumer if either side changes. #[derive(Clone, Copy)] #[repr(C)] pub struct QuietWriter { @@ -87,7 +87,9 @@ impl core::fmt::Write for QuietWriter { // `qw.write_fmt(args)` resolves through `fmt::Write`. /// Opaque adapter wrapping a QuietWriter and exposing `crate::io::Writer`. -/// TODO(port): bun_sys::QuietWrite::Adapter — size/align must match. +/// Layout contract: bun_sys's concrete `SysQuietWriterAdapter` must fit in +/// these 64 bytes with `io::Writer` as its first field; const-asserted on the +/// bun_sys side next to that struct. #[repr(C, align(8))] pub struct QuietWriterAdapter { _opaque: [u8; 64], @@ -132,7 +134,7 @@ impl File { output_sink().quiet_writer_from_fd(self.0) } /// Write all bytes (best-effort; routes through QuietWriter so errors are - /// swallowed per Zig `bun.Output` semantics). Progress.rs uses this. + /// swallowed). Progress.rs uses this. #[inline] pub fn write_all(self, bytes: &[u8]) -> Result<(), crate::Error> { let mut qw = self.quiet_writer(); @@ -172,12 +174,12 @@ pub fn argv() -> impl Iterator { /// `bun.Output.debugWarn` — yellow `debug warn:` prefix to stderr in debug /// builds, through the Bun output sink (so colour/redirect logic applies), -/// followed by an explicit flush. Zig output.zig:1189-1194. +/// followed by an explicit flush. /// /// Function form takes a single [`PrettyFmtInput`] payload — callers pass /// `format_args!("template {}", x)` (the dominant convention across the /// codebase) or a bare `&str`. The payload is rendered first, then -/// ``-rewritten. For the comptime-literal fast path use the macro form. +/// ``-rewritten. For the compile-time-literal fast path use the macro form. #[inline] pub fn debug_warn(payload: impl PrettyFmtInput) { if cfg!(debug_assertions) { @@ -194,14 +196,14 @@ pub fn warn(payload: impl PrettyFmtInput) { pretty_errorln!("warn: {}", buf); } -/// `bun.Output.note` — blue `note:` prefix to stderr (output.zig:1179). +/// `bun.Output.note` — blue `note:` prefix to stderr. #[inline] pub fn note(payload: impl PrettyFmtInput) { let buf = payload.into_pretty_buf(enable_ansi_colors_stderr()); pretty_errorln!("note: {}", buf); } -/// Function-form of `Output.debug` (Zig: `pub fn debug(comptime fmt, args)`). +/// Function-form of `Output.debug`. /// The macro form is `crate::debug!`; this fn variant takes a single /// pre-formatted payload for call sites that build the message dynamically. #[inline] @@ -235,7 +237,7 @@ pub fn pretty_error(payload: impl PrettyFmtInput) { } /// Test-harness initializer: configure the output sinks without touching the -/// real stdio FDs (Zig: `Output.initTest`). Safe to call repeatedly. +/// real stdio FDs. Safe to call repeatedly. pub fn init_test() { if SOURCE_SET.get() { return; @@ -250,8 +252,7 @@ pub fn init_test() { /// `bun.Output.Source.Stdio.restore` — restore terminal to cooked mode on exit. /// Thin alias over [`crate::output::stdio::restore`] (the real impl, also in -/// this crate); the indirection exists only because Zig spells the path both -/// `Output.Source.Stdio.restore` and `Output.Stdio.restore`. +/// this crate). pub mod source { pub mod stdio { #[inline] @@ -314,15 +315,15 @@ pub static TERMINAL_SIZE: crate::AtomicCell = #[cfg(not(target_arch = "wasm32"))] pub type StreamType = File; #[cfg(target_arch = "wasm32")] -pub type StreamType = io::FixedBufferStream; // TODO(port): FixedBufferStream arrives via bun_io→core move-in. +pub type StreamType = io::FixedBufferStream; // wasm32 is not built yet; FixedBufferStream is unported. pub struct Source { pub stdout_buffer: [u8; 4096], pub stderr_buffer: [u8; 4096], pub buffered_stream_backing: QuietWriterAdapter, pub buffered_error_stream_backing: QuietWriterAdapter, - // Self-referential: point into `*_backing.new_interface`. Replaced with accessor - // methods in Rust; raw fields kept to mirror Zig field order. + // Self-referential: point into `*_backing.new_interface`. Use the accessor + // methods instead of these raw fields. // (LIFETIMES.tsv: BORROW_FIELD — self-ref into buffered_*_backing) buffered_stream: *mut io::Writer, buffered_error_stream: *mut io::Writer, @@ -335,21 +336,46 @@ pub struct Source { pub raw_stream: StreamType, pub raw_error_stream: StreamType, - // Zig: `[]u8 = &([_]u8{})` — borrowed WASM-mode write buffers, never freed by this - // file. Not owned → raw fat ptr (BORROW_FIELD-style), not `Box<[u8]>`. + // Borrowed WASM-mode write buffers, never freed by this file. + // Not owned → raw fat ptr (BORROW_FIELD-style), not `Box<[u8]>`. pub out_buffer: *mut [u8], pub err_buffer: *mut [u8], } impl Source { - // TODO(port): proper zero-init for QuietWriterAdapter / StreamType — currently relies on - // `mem::zeroed()` which is only sound if those are `#[repr(C)]` POD with no - // `NonNull`/`NonZero`/niche-enum fields. Hand-write `ZEROED`/`Default` once their - // layouts are fixed in bun_sys. - // SAFETY: byte arrays and raw `*mut` pointers are valid all-zero (null fat ptr = - // `(null, 0)`); only read after `init()` overwrites every field. See TODO above for - // the adapter/stream caveat. - pub const ZEROED: Self = unsafe { crate::ffi::zeroed_unchecked() }; + // Field-wise placeholder value for the pre-`init()` thread_local slot. + // Every field is overwritten by `init()` before use. + // + // The pre-init stream placeholder is cfg-split because the two + // `StreamType` aliases have different const surfaces: native + // `File::ZEROED` is `Fd::INVALID`, so a pre-init read fails loudly + // instead of aliasing fd 0; `FixedBufferStream` has no `ZEROED` const, + // so the wasm32 arm spells out the empty stream field-wise. + #[cfg(not(target_arch = "wasm32"))] + const ZEROED_STREAM: StreamType = StreamType::ZEROED; + #[cfg(target_arch = "wasm32")] + const ZEROED_STREAM: StreamType = StreamType { + buf: core::ptr::null_mut(), + len: 0, + pos: 0, + }; + + pub const ZEROED: Self = Self { + stdout_buffer: [0u8; 4096], + stderr_buffer: [0u8; 4096], + buffered_stream_backing: QuietWriterAdapter::uninit(), + buffered_error_stream_backing: QuietWriterAdapter::uninit(), + buffered_stream: core::ptr::null_mut(), + buffered_error_stream: core::ptr::null_mut(), + stream_backing: QuietWriterAdapter::uninit(), + error_stream_backing: QuietWriterAdapter::uninit(), + stream: core::ptr::null_mut(), + error_stream: core::ptr::null_mut(), + raw_stream: Self::ZEROED_STREAM, + raw_error_stream: Self::ZEROED_STREAM, + out_buffer: core::ptr::slice_from_raw_parts_mut(core::ptr::null_mut(), 0), + err_buffer: core::ptr::slice_from_raw_parts_mut(core::ptr::null_mut(), 0), + }; /// Accessors replacing the self-referential `*std.Io.Writer` fields. #[inline] @@ -369,12 +395,11 @@ impl Source { self.error_stream_backing.new_interface() } - // TODO(port): in-place init — `out` is the pre-allocated thread_local slot; PORTING.md - // says keep `&mut MaybeUninit` (or reshape to `-> Self`) for out-param ctors. + // The out-param shape is load-bearing: `out` is the pre-allocated + // thread_local slot, and the adapters built below capture raw pointers + // into `out.stdout_buffer`/`out.stderr_buffer`. Returning `Self` by value + // would move the struct after those pointers were captured and dangle them. pub fn init(out: &mut Source, stream: StreamType, err_stream: StreamType) { - // TODO(port): bun_alloc::USE_MIMALLOC + mimalloc::Option::ShowErrors - // are gated in bun_alloc; re-enable once bun_alloc/basic.rs is un-gated. - if cfg!(debug_assertions) && bun_alloc::USE_MIMALLOC && !SOURCE_SET.get() { bun_alloc::mimalloc::mi_option_set(bun_alloc::mimalloc::Option::show_errors, 1); } @@ -384,9 +409,8 @@ impl Source { out.raw_error_stream = err_stream; // stdout_buffer / stderr_buffer left uninitialized (overwritten by adapter writes) - // PORT NOTE: reshaped for borrowck — the Zig wrote `out.* = .{ ...undefined... }` - // then patched the self-referential pointers. In Rust we construct the backings - // directly and expose accessors instead of storing interior pointers. + // Construct the backings directly and expose accessors instead of + // storing interior pointers. out.buffered_stream_backing = out .raw_stream .quiet_writer() @@ -455,7 +479,7 @@ impl Source { } pub fn is_no_color() -> bool { - // Zig output.zig:99 — parsed bool, default false. NO_COLOR=0 → false. + // Parsed bool, default false. NO_COLOR=0 → false. env_var::NO_COLOR.get().unwrap_or(false) } @@ -694,7 +718,6 @@ pub mod windows_stdio { pub mod stdio { use super::*; - // TODO(port): move to bun_core_sys unsafe extern "C" { // Written once by C at process startup before threads; Rust only reads. // `[AtomicI32; 3]` has identical layout to C's `int32_t[3]` (`AtomicI32` @@ -886,11 +909,11 @@ pub static IS_GITHUB_ACTION: AtomicBool = AtomicBool::new(false); pub(crate) static STDERR_DESCRIPTOR_TYPE: crate::Once = crate::Once::new(); pub(crate) static STDOUT_DESCRIPTOR_TYPE: crate::Once = crate::Once::new(); -/// Downstream alias (Zig: `Output.OutputStreamDescriptor`). Several call sites +/// Downstream alias. Several call sites /// refer to it as `Output::DescriptorType` for brevity. pub type DescriptorType = OutputStreamDescriptor; -/// Safe getter (Zig: `Output.stderr_descriptor_type`). +/// Safe getter for the stderr descriptor type. #[inline] pub fn stderr_descriptor_type() -> OutputStreamDescriptor { STDERR_DESCRIPTOR_TYPE @@ -898,7 +921,7 @@ pub fn stderr_descriptor_type() -> OutputStreamDescriptor { .copied() .unwrap_or(OutputStreamDescriptor::Unknown) } -/// Safe getter (Zig: `Output.stdout_descriptor_type`). +/// Safe getter for the stdout descriptor type. #[inline] pub fn stdout_descriptor_type() -> OutputStreamDescriptor { STDOUT_DESCRIPTOR_TYPE @@ -943,14 +966,9 @@ pub fn is_ai_agent() -> bool { if env_var::REPL_ID.get().unwrap_or(false) { return true; } - // TODO: add environment variable for Gemini - // Gemini does not appear to add any environment variables to identify it. - - // TODO: add environment variable for Codex - // codex does not appear to add any environment variables to identify it. - - // TODO: add environment variable for Cursor Background Agents - // cursor does not appear to add any environment variables to identify it. + // Other agents we'd like to detect, but which do not appear to set + // any identifying environment variables: Gemini, Codex, Cursor + // Background Agents. Add checks here if they ever grow one. false } @@ -1005,8 +1023,7 @@ pub fn disable_buffering() { } } -/// RAII: `disable_buffering()` now, `enable_buffering()` on drop. Covers the -/// Zig `Output.disableBuffering(); defer Output.enableBuffering();` pair used +/// RAII: `disable_buffering()` now, `enable_buffering()` on drop. Used /// around child-process exec where the child writes directly to inherited /// stdio and Bun's buffer must not interleave. #[must_use = "dropping immediately re-enables buffering; bind to `let _scope = ...`"] @@ -1031,12 +1048,10 @@ pub fn disable_buffering_scope() -> DisableBufferingScope { #[cold] pub fn panic(args: fmt::Arguments<'_>) -> ! { - // PORT NOTE: Zig branched on enable_ansi_colors_stderr to pick the comptime-colored - // vs stripped format string. In Rust callers use `panic!(pretty_fmt!(...))` directly; - // this fn is kept for non-macro callers and writes the (already-formatted) args. - // TODO(port): branch on ENABLE_ANSI_COLORS_STDERR once the colored vs - // stripped paths actually differ; callers should wrap fmt in - // `pretty_fmt!(.., enable_ansi_colors_stderr)`. + // Callers use `panic!(pretty_fmt!(...))` + // directly; this fn is kept for non-macro callers and writes the + // (already-formatted) args. Callers that want coloring wrap the fmt in + // `pretty_fmt!(.., enable_ansi_colors_stderr)` themselves. core::panic!("{args}"); } @@ -1045,12 +1060,13 @@ pub fn raw_error_writer() -> StreamType { SOURCE.with_borrow(|s| s.raw_error_stream) } -// TODO: investigate migrating this to the buffered one. +// Possible perf follow-up: migrate callers of these unbuffered accessors to +// the buffered writer. // -// TODO(port): these accessors hand out a `&'static mut` to a thread-local -// `Source` field. The Zig original returned `*Writer` and callers used it -// briefly. Returning `&'static mut` is *unsound* if two are alive at once, but -// matches the Zig contract until callers migrate to `with_error_writer(|w| ..)`. +// TODO: these accessors hand out a `&'static mut` to a thread-local +// `Source` field, and callers only use it briefly. Returning `&'static mut` +// is *unsound* if two are alive at once — a known-unsound shim until callers +// migrate to `with_error_writer(|w| ..)`. // // `source_writer_escape` centralises the escape: one `unsafe` for all five // public `*_writer*()` accessors (nonnull-asref reduction: 5 sites → 1). @@ -1061,7 +1077,7 @@ fn source_writer_escape(project: fn(&mut Source) -> &mut io::Writer) -> &'static // SAFETY: pointer escapes the RefCell borrow; the thread-local `Source` // backing field has a stable address once `Source::init` has run, and the // returned `&'static mut` is used briefly with no two live at once (see - // TODO(port) above — known-unsound shim until callers migrate). + // TODO above — known-unsound shim until callers migrate). unsafe { &mut *p } } @@ -1143,9 +1159,8 @@ pub fn flush() { /// RAII guard that calls [`flush`] on `Drop`. /// -/// This is the Rust spelling of Zig's `defer Output.flush();` — hold one of -/// these across a scope with early returns to guarantee buffered stdout/stderr -/// is drained on every exit path. +/// Hold one of these across a scope with early returns to guarantee buffered +/// stdout/stderr is drained on every exit path. #[must_use = "FlushGuard flushes on Drop; binding to `_` drops it immediately"] pub struct FlushGuard(()); @@ -1157,7 +1172,7 @@ impl Drop for FlushGuard { } /// Returns a guard that flushes buffered stdout & stderr when it goes out of -/// scope. Equivalent to Zig's `defer Output.flush();`. +/// scope. #[inline] pub fn flush_guard() -> FlushGuard { FlushGuard(()) @@ -1179,7 +1194,6 @@ impl fmt::Display for ElapsedFormatter { const FAST: u64 = NS_PER_MS * 10; const SLOW: u64 = NS_PER_MS * 8_000; let ms = self.duration_ns as f64 / NS_PER_MS as f64; - // PERF(port): was comptime bool dispatch on `colors` — profile if hot. match self.duration_ns { 0..=FAST => { if self.colors { @@ -1214,17 +1228,15 @@ impl fmt::Display for ElapsedFormatter { } } -// PORT NOTE: Zig's `printElapsedToWithCtx` passed the raw `[...]` -// template to a `(comptime fmt, args)` printer (`prettyError`/`pretty`), which -// then routed through `prettyTo` to branch on `enable_ansi_colors_{stdout,stderr}`. -// In Rust the `pretty_fmt!` rewrite must happen at the macro call site, so the +// The `pretty_fmt!` rewrite must happen at the macro call site, so the // public entry points (`print_elapsed`/`print_elapsed_stdout` below) inline the // match and call `pretty_error!`/`pretty!` directly — those macros emit both the -// colored and stripped variants and let `pretty_to` pick at runtime. The -// intermediate helper had no way to defer the color/no-color choice without -// leaking raw `\x1b[` escapes when colors are disabled, so it was removed. +// colored and stripped variants and let `pretty_to` pick at runtime. An +// intermediate `fmt::Arguments`-taking helper had no way to defer the +// color/no-color choice without leaking raw `\x1b[` escapes when colors are +// disabled, so it was removed. -// `print_elapsed_to` intentionally removed — see PORT NOTE above. The colored +// `print_elapsed_to` intentionally removed — see the note above. The colored // template can't be deferred through an `fmt::Arguments`-taking printer without // either leaking raw escapes or stripping color, so callers must use // `print_elapsed` / `print_elapsed_stdout` (or `ElapsedFormatter` directly). @@ -1279,7 +1291,7 @@ pub fn print_timer(timer: &mut impl ReadTimer) { // Print routing // // `print` / `printError` / `pretty*` are called from thousands of sites with -// distinct comptime format strings. They used to be `noinline`, which forced +// distinct format strings. They used to be `noinline`, which forced // one ~400-byte function body per (fmt, args-type) pair — about 2,650 of them, // or ~1 MB of .text in release builds. The bodies were nearly identical: pick // buffered-vs-unbuffered writer, then `Writer.print(fmt, args)`. @@ -1298,10 +1310,9 @@ pub fn print_timer(timer: &mut impl ReadTimer) { /// `write_fmt`, which evaluates user `Display` impls that can re-enter this /// module (e.g. `debug_warn` → `print_to`, or `flush()`). If we materialized a /// `&mut io::Writer` here, the re-entrant call would produce a second `&mut` -/// aliasing the first while it is still live — UB. Zig's `destWriter()` -/// (output.zig:731-737) returns a raw `*std.Io.Writer` with no exclusivity -/// contract, so callers must do the same and route writes through the vtable -/// fn pointers directly (see `write_fmt_raw`). +/// aliasing the first while it is still live — UB. So this hands out a raw +/// pointer with no exclusivity contract, and callers must route writes +/// through the vtable fn pointers directly (see `write_fmt_raw`). #[inline(never)] fn with_dest_writer(dest: Destination, f: impl FnOnce(*mut io::Writer) -> R) -> R { debug_assert!(SOURCE_SET.get()); @@ -1373,13 +1384,13 @@ fn write_bytes(dest: Destination, bytes: &[u8]) { pub fn print_to(dest: Destination, args: fmt::Arguments<'_>) { #[cfg(target_arch = "wasm32")] { - // TODO(port): wasm console_log/console_error path via root.Uint8Array + // wasm32 is not built yet; output is dropped. let _ = (dest, args); return; } - // PERF(port): Zig had a comptime `hasNoArgs` fast path that emitted a single - // `writeBytes(dest, comptime_str)` per call site. `fmt::Arguments::as_str()` returns - // Some only when there are no interpolations, so this preserves the optimization. + // PERF: `fmt::Arguments::as_str()` returns Some only when there are no + // interpolations, giving a single `write_bytes(dest, str)` fast path per + // such call site. if let Some(s) = args.as_str() { return write_bytes(dest, s.as_bytes()); } @@ -1404,8 +1415,7 @@ pub fn print_errorable(args: fmt::Arguments<'_>) -> Result<(), crate::Error> { #[macro_export] macro_rules! println { ($fmt:expr $(, $arg:expr)* $(,)?) => {{ - // `:expr` (not `:literal`) so `concat!(..)` templates compile — - // Zig `comptime fmt: string` accepts `"a" ++ "b"` (output.zig:781). + // `:expr` (not `:literal`) so `concat!(..)` templates compile. // `concat!` accepts a nested `concat!`, so the trailing-`{}` join works. const __NL: &str = $crate::output::_needs_nl($fmt); $crate::output::print_to( @@ -1427,9 +1437,9 @@ macro_rules! debug { }; } -/// NOTE: unlike Zig output.zig:794-797, this fn form cannot inspect the -/// comptime template and therefore *always* appends `\n`. Callers must NOT -/// pass a template that already ends in `\n`. Prefer the `debug!` macro. +/// NOTE: this fn form cannot inspect the template and therefore *always* +/// appends `\n`. Callers must NOT pass a template that already ends in `\n`. +/// Prefer the `debug!` macro. #[inline] pub(crate) fn _debug(args: fmt::Arguments<'_>) { debug_assert!(SOURCE_SET.get()); @@ -1485,13 +1495,14 @@ impl Visibility { /// Runtime state for one scoped logger. One static instance per `declare_scope!`. pub struct ScopedLogger { - pub tagname: &'static str, // already lowercased + // As-declared (`stringify!`) casing; lowered for display via `_LowerTag`, + // and env/flag matching is case-insensitive. + pub tagname: &'static str, really_disable: AtomicBool, is_visible_once: std::sync::Once, lock: Mutex<()>, - out_set: AtomicBool, - // TODO(port): per-scope buffered writer (`buffer: [4096]u8` + adapter). For now route - // through `scoped_writer()` directly; could reintroduce per-scope buffering. + // There is no per-scope `[4096]u8` buffered writer; logs route + // through `scoped_writer()` directly (debug-logging perf only). } impl ScopedLogger { @@ -1501,13 +1512,12 @@ impl ScopedLogger { really_disable: AtomicBool::new(matches!(visibility, Visibility::Hidden)), is_visible_once: std::sync::Once::new(), lock: Mutex::new(()), - out_set: AtomicBool::new(false), } } fn evaluate_is_visible(&self) { - // BUN_DEBUG_ — Zig builds this with comptime `++` (no length cap), - // so size the buffer from the tag instead of a fixed `[u8; 64]`. + // BUN_DEBUG_ — tag length is uncapped, so size the buffer + // from the tag instead of a fixed `[u8; 64]`. let tag = self.tagname.as_bytes(); let prefix = b"BUN_DEBUG_"; let key_len = prefix.len() + tag.len(); @@ -1580,24 +1590,19 @@ impl ScopedLogger { return; } - // TODO(port): per-scope `[4096]u8` buffered adapter (`out`/`out_set`/`buffered_writer`) - let _ = self.out_set.load(Ordering::Relaxed); - let _lock = self.lock.lock(); let mut out = scoped_writer(); - // PERF(port): was comptime bool dispatch on use_ansi — profile if hot. // The colored/plain selection now happens at the `scoped_log!` call site // (single arg evaluation) via `_scoped_use_ansi()`. let result = out.write_fmt(args); if result.is_err() { - // Zig: write failure → disable scope and skip the flush. + // Write failure → disable scope and skip the flush. self.really_disable.store(true, Ordering::Relaxed); return; } - // TODO(port): Zig also disables on flush failure; QuietWriter::flush() - // currently returns () via the SysHooks vtable so flush errors are not - // observable here. Surface a Result once bun_sys exposes it. + // `QuietWriter::flush()` returns `()` through the OutputSink vtable, + // so flush errors are not observable here (debug logging only). out.flush(); } } @@ -1615,7 +1620,8 @@ macro_rules! declare_scope { // in downstream crates; allow it at every expansion site. #[allow(non_upper_case_globals, unreachable_pub)] pub static $name: $crate::output::ScopedLogger = $crate::output::ScopedLogger::new( - // TODO(port): lowercase tagname at compile time (Zig did std.ascii.toLower) + // Matching is case-insensitive and display lowering happens via + // `_LowerTag`. stringify!($name), $crate::output::Visibility::Hidden, ); @@ -1641,9 +1647,8 @@ macro_rules! scoped_log { if cfg!(debug_assertions) && $scope.is_visible() { const __NL: &str = $crate::output::_needs_nl($crate::pretty_fmt!($fmt, false)); // Branch on ANSI *before* `format_args!` so each `$arg` evaluates - // exactly once (Zig builds the args tuple once — output.zig:922-933). - // Prefix `[tag]` is built at runtime from `$scope.tagname` lowercased - // (Zig: output.zig:826-835 lowercases via `std.ascii.toLower`). + // exactly once. + // Prefix `[tag]` is built at runtime from `$scope.tagname` lowercased. if $crate::output::_scoped_use_ansi() { $scope.log(::core::format_args!( concat!( @@ -1701,8 +1706,7 @@ macro_rules! define_scoped_log { } /// `Display` adapter that lowercases an ASCII tag on the fly. Used by -/// `scoped_log!` so the printed `[tag]` prefix matches Zig's -/// `std.ascii.toLower`-folded `tagname` (output.zig:826-835) without needing a +/// `scoped_log!` so the printed `[tag]` prefix is lowercased without needing a /// compile-time lowercasing proc-macro. #[doc(hidden)] pub struct _LowerTag(pub &'static str); @@ -1742,10 +1746,9 @@ pub fn clear_to_end() { // - reset // - reset -/// Lowercase lookup wrapper (Zig: `Output.color_map.get(name)`). The table +/// Lowercase lookup wrapper. The table /// itself lives in `bun_output_tags` (shared with the `pretty_fmt!` proc-macro -/// so there is exactly one copy); this fn-module mirrors the Zig -/// `ComptimeStringMap` `.get()` surface. +/// so there is exactly one copy). pub mod color_map { #[inline] pub fn get(name: &[u8]) -> Option<&'static str> { @@ -1756,10 +1759,10 @@ pub mod color_map { pub use ansi::{BOLD, DIM, RESET}; pub use bun_output_tags::{ansi, ansi_b}; -/// `bun.Output.pretty(fmt, args)` — write to stdout with `` color expansion. +/// Write to stdout with `` color expansion. /// Function form: performs the `` → ANSI rewrite at runtime on the rendered /// payload (using stdout's colour state). Prefer the `pretty!` macro for literal -/// templates so the rewrite stays comptime. +/// templates so the rewrite happens at compile time. /// /// `inline(always)`: with plain `#[inline]` the `` /// monomorphization materializes once in `bun_runtime` and every other crate's @@ -1774,8 +1777,8 @@ pub fn pretty(payload: impl PrettyFmtInput) { /// `bun.Output.prettyln(fmt, args)` — `pretty()` with a trailing newline. /// Function form: performs the `` → ANSI rewrite at runtime on the rendered -/// payload and appends `\n` if the result does not already end in one (matches -/// Zig output.zig:1090-1093). Prefer the `prettyln!` macro for literal templates. +/// payload and appends `\n` if the result does not already end in one. +/// Prefer the `prettyln!` macro for literal templates. /// `inline(always)` for the same .text-layout reason as [`pretty`]. #[inline(always)] pub fn prettyln(payload: impl PrettyFmtInput) { @@ -1788,15 +1791,14 @@ pub fn prettyln(payload: impl PrettyFmtInput) { /// Compile-time `` → ANSI escape rewriter. /// -/// In Zig this was a `comptime` function building a `[:0]const u8`. In Rust the -/// equivalent must be a proc-macro because it consumes a string literal and emits +/// This must be a proc-macro because it consumes a string literal and emits /// a new string literal usable as a `format_args!` template. /// /// `pretty_fmt!("{s}", true)` → `"\x1b[31m{}\x1b[0m"` /// `pretty_fmt!("{s}", false)` → `"{}"` /// -/// The reference algorithm is `pretty_fmt_runtime` below (kept 1:1 with the Zig -/// body so the proc-macro can be tested against it). +/// The reference algorithm is `pretty_fmt_runtime` below (the proc-macro is +/// tested against it). pub use bun_core_macros::pretty_fmt; /// Input accepted by [`pretty_fmt`]: either a `&str`/`&[u8]` template or a @@ -1834,8 +1836,8 @@ impl PrettyFmtInput for fmt::Arguments<'_> { } /// `Output.prettyFmt` — runtime `` → ANSI rewrite. Const-generic -/// `ENABLE_ANSI_COLORS` mirrors the Zig `comptime is_enabled: bool` parameter -/// so callers can do `Output::pretty_fmt::("…")`. +/// `ENABLE_ANSI_COLORS` lets callers do +/// `Output::pretty_fmt::("…")`. /// /// For a runtime bool (e.g. from `enable_ansi_colors_stderr()`), see /// [`pretty_fmt_rt`]. @@ -1886,9 +1888,7 @@ impl fmt::Display for PrettyBuf { } // ── FmtTuple ────────────────────────────────────────────────────────────── -// Zig `fn print(comptime fmt: []const u8, args: anytype)` takes a tuple of -// arguments and substitutes positionals. The Rust port models `args: anytype` -// as `impl FmtTuple` so call sites can pass `()`, `(a,)`, `(a, b)`, … or a +// `impl FmtTuple` lets call sites pass `()`, `(a,)`, `(a, b)`, … or a // pre-built `fmt::Arguments<'_>` (treated as a single positional). /// Positional-argument bundle for runtime template substitution. @@ -2056,12 +2056,12 @@ pub fn pretty_fmt_args( } } -/// Runtime mirror of Zig `prettyFmt` for testing the proc-macro and for the rare -/// dynamic case. Produces the same byte sequence the Zig comptime version would. +/// Runtime `` → ANSI rewriter, used for testing the proc-macro and for +/// the rare dynamic case. /// /// Colour table lives in `bun_output_tags`; the state machine is kept duplicated /// vs `bun_core_macros::rewrite` because the two intentionally diverge in the -/// `{` arm (proc-macro rewrites Zig specs `{s}`→`{}`; this side copies braces +/// `{` arm (proc-macro rewrites specs `{s}`→`{}`; this side copies braces /// verbatim) and on unknown tags (proc-macro errors; this side emits `""`). pub fn pretty_fmt_runtime(fmt: &[u8], is_enabled: bool) -> Vec { let mut out = Vec::with_capacity(fmt.len() * 4); @@ -2111,8 +2111,9 @@ pub fn pretty_fmt_runtime(fmt: &[u8], is_enabled: bool) -> Vec { is_reset = true; break 'picker ""; } else { - // Zig: @compileError — runtime version returns empty - // TODO(port): proc-macro emits compile_error! here + // Unknown tag: the `pretty_fmt!` proc-macro rejects + // this at its call sites; this runtime path drops the + // tag. break 'picker ""; } }; @@ -2142,8 +2143,7 @@ pub fn enable_color_for(dest: Destination) -> bool { } } -/// Returns `"\n"` if `fmt` does not already end in a newline, else `""`. -/// Mirrors Zig's `if (fmt.len == 0 or fmt[fmt.len - 1] != '\n')` guard so the +/// Returns `"\n"` if `fmt` does not already end in a newline, else `""`, so the /// `*ln!` macros never emit a double newline when the caller's template already /// ends in one. #[doc(hidden)] @@ -2172,9 +2172,7 @@ pub fn _scoped_use_ansi() -> bool { /// Internal: bind each `$arg` exactly once into a `match` tuple, then dispatch /// on the per-destination color flag and call `print_to` with the appropriate -/// `pretty_fmt!`-expanded template. Mirrors Zig's `prettyTo` which receives the -/// args tuple already evaluated and only branches on the color flag -/// (output.zig:1066-1074). +/// `pretty_fmt!`-expanded template. /// /// The recursive `@go` arm zips each user arg with a name from `pool` so the /// emitted `match (&a, &b, ..)` pattern can rebind them as plain idents — both @@ -2294,8 +2292,7 @@ macro_rules! prettyln { #[macro_export] macro_rules! print_errorln { ($fmt:expr $(, $arg:expr)* $(,)?) => {{ - // `:expr` (not `:literal`) so `concat!(..)` templates compile — - // Zig `comptime fmt: string` accepts `"a" ++ "b"` (output.zig:1095). + // `:expr` (not `:literal`) so `concat!(..)` templates compile. const __NL: &str = $crate::output::_needs_nl($fmt); $crate::output::print_to( $crate::output::Destination::Stderr, @@ -2329,8 +2326,7 @@ macro_rules! pretty_errorln { /// sink, branching on a (possibly const-generic) color flag so each branch gets /// the proc-macro-expanded literal template. /// -/// Port of Zig `writer.print(comptime Output.prettyFmt(FMT, enable_ansi_colors), .{args})` -/// for `writeFormat`-style impls that take `comptime enable_ansi_colors: bool`. +/// For `writeFormat`-style impls that take an `enable_ansi_colors: bool`. /// The `pretty_fmt!` proc-macro requires a `true`/`false` *literal*, so the /// const-generic is dispatched here; only one arm executes at runtime, so each /// `$arg` evaluates exactly once. @@ -2347,8 +2343,7 @@ macro_rules! write_pretty { // ── printCommand ────────────────────────────────────────────────────────── -/// Argument shape accepted by `command`/`commandOut`. Mirrors the Zig `anytype` -/// switch over `[][]const u8` vs `[]const u8`. +/// Argument shape accepted by `command`/`commandOut`. #[derive(Clone, Copy)] pub enum CommandArgv<'a> { List(&'a [&'a [u8]]), @@ -2407,12 +2402,12 @@ pub fn print_error(args: impl core::fmt::Display) { print_to(Destination::Stderr, format_args!("{args}")); } -/// `Output.printErrorln` — function form (the `print_errorln!` macro at crate -/// root is the comptime-string variant). Takes anything `Display` so both +/// Function form (the `print_errorln!` macro at crate +/// root is the compile-time-string variant). Takes anything `Display` so both /// `format_args!(..)` and bare `&str` call sites compile; appends `\n`. /// -/// NOTE: unlike the macro (and Zig output.zig:1095-1098), this fn form cannot -/// inspect the comptime template and therefore *always* appends `\n`. Callers +/// NOTE: unlike the macro, this fn form cannot +/// inspect the template and therefore *always* appends `\n`. Callers /// must NOT pass a template that already ends in `\n` or output will contain a /// doubled newline. Prefer the `print_errorln!` macro where possible. #[inline] @@ -2421,7 +2416,7 @@ pub fn print_errorln(args: impl core::fmt::Display) { } /// `Output.enable_ansi_colors_stdout` — safe relaxed-load wrapper over the -/// startup-initialized atomic. Mirrors the Zig public-var read. +/// startup-initialized atomic. #[inline] pub fn enable_ansi_colors_stdout() -> bool { ENABLE_ANSI_COLORS_STDOUT.load(Ordering::Relaxed) @@ -2501,24 +2496,22 @@ macro_rules! debug_warn { }; } -/// Print a red error message. The first argument takes an `error_name` value, which can be either -/// be a Zig error, or a string or enum. The error name is converted to a string and displayed +/// Print a red error message. The first argument takes an `error_name` value, which can +/// be an error, a string, or an enum. The error name is converted to a string and displayed /// in place of "error:", making it useful to print things like "EACCES: Couldn't open package.json" /// -/// The Zig original switched on `@typeInfo` of `error_name`. The Rust port accepts anything +/// Accepts anything /// implementing `ErrName` (impl'd for `&[u8]`, `&str`, `bun_core::Error`, `bun_sys::Error`, /// and any `#[derive(strum::IntoStaticStr)]` enum). -// TODO(port): the comptime-literal fast path (is_comptime_name) is dropped — -// could be recovered with a proc-macro overload that detects string literals. +// Literal tags go through the same runtime rendering as everything else. // // By-value `error_name` is intentional: callers pass `"EACCES"` / `b"tag"` / // `bun_core::Error` (Copy) and `bun_sys::Error` (non-Copy, consumed). Taking // `&impl ErrName` would force `&"literal"` at every site. #[allow(clippy::needless_pass_by_value)] // by-value for call-site ergonomics; see above pub fn err(error_name: impl ErrName, fmt: &str, args: impl FmtTuple) { - // Zig concatenates `fmt` into the prettyErrorln template, whose trailing-\n - // check then sees the caller's newline. Here `fmt` is rendered into a `{}` - // arg, so strip a trailing \n and let pretty_errorln! add exactly one. + // `fmt` is rendered into a `{}` arg, so strip a trailing \n and let + // pretty_errorln! add exactly one. let fmt = fmt.strip_suffix('\n').unwrap_or(fmt); let body = pretty_fmt_args(fmt, enable_ansi_colors_stderr(), args); if let Some(e) = error_name.as_sys_err_info() { @@ -2578,7 +2571,7 @@ pub struct SysErrInfo { pub syscall: &'static str, } -/// Trait abstracting the `@typeInfo` switch in Zig `err()`. +/// Trait abstracting the error-name shapes accepted by `err()`. /// /// `as_sys_err_info()` replaces the former `as_sys_error() -> Option<&bun_sys::Error>`: /// bun_core (T0) can't name `bun_sys::Error` (T1). bun_sys impls `ErrName` for @@ -2604,9 +2597,10 @@ impl ErrName for crate::Error { (*self).name().as_bytes() } } -// TODO(port): `impl ErrName for bun_sys::Error` and `bun_sys::SystemErrno` arrive -// via move-in pass in bun_sys (orphan rule allows higher tier to impl this trait). -// TODO(port): blanket impl for `T: Into<&'static str>` (strum enums) once coherence allows. +// Higher-tier impls live with their types (orphan rule allows it): +// `bun_sys::Error` (src/sys/Error.rs) and `SystemErrno` (src/errno/lib.rs). +// A blanket impl for `T: Into<&'static str>` (strum enums) is coherence-blocked; +// each enum impls `ErrName` explicitly instead. // ── ScopedDebugWriter ───────────────────────────────────────────────────── @@ -2620,8 +2614,7 @@ pub mod scoped_debug_writer { pub(crate) static DISABLE_INSIDE_LOG: Cell = const { Cell::new(0) }; } - /// RAII guard that suppresses scoped logging for the lifetime of the guard - /// (Zig: `disable_inside_log += 1; defer disable_inside_log -= 1;`). + /// RAII guard that suppresses scoped logging for the lifetime of the guard. #[must_use] pub(crate) struct DisableGuard(()); @@ -2642,11 +2635,9 @@ pub mod scoped_debug_writer { } pub fn disable_scoped_debug_writer() { - // Zig: `if (!@inComptime())` — always runtime in Rust. scoped_debug_writer::DISABLE_INSIDE_LOG.set(scoped_debug_writer::DISABLE_INSIDE_LOG.get() + 1); } -// TODO(port): move to bun_core_sys unsafe extern "C" { /// No preconditions; returns the calling process's PID. safe fn getpid() -> c_int; @@ -2677,10 +2668,6 @@ pub(crate) fn init_scoped_debug_writer_at_startup() { bstr::BStr::new(path), )), }; - // TODO(port): bun_sys::Fd::truncate (Windows-only); add to OutputSinkVTable. - // `create_file` above already opens for writing with truncate, so the explicit - // `fd.truncate(0)` from Zig is a no-op here until the vtable entry lands. - let _ = &fd; // windows // SAFETY: single-threaded startup. unsafe { scoped_debug_writer::SCOPED_FILE_WRITER @@ -2698,11 +2685,9 @@ pub(crate) fn init_scoped_debug_writer_at_startup() { } fn scoped_writer() -> QuietWriter { - // Zig used `@compileError` here (output.zig:1320-1325) which is lazy — it - // only fires if `scopedWriter()` is referenced, and in release Zig the - // `Scoped()` no-op struct never references it. Rust's `compile_error!` is - // eager and would break every release build, so assert at runtime instead. - // All callers are already gated on `Environment::ENABLE_LOGS`. + // Assert at runtime rather than compile time (a `compile_error!` here + // would break every release build); all callers are already gated on + // `Environment::ENABLE_LOGS`. #[cfg(debug_assertions)] if !Environment::ENABLE_LOGS { unreachable!("scopedWriter() should only be called in debug mode"); @@ -2730,8 +2715,7 @@ pub fn err_fmt(formatter: impl fmt::Display) { // ────────────────────────────────────────────────────────────────────────── // Stdin readers (CYCLEBREAK §Dispatch cold path) // -// Zig's `bun.Output.buffered_stdin` is a `bun.deprecated.BufferedReader(4096, -// File.Reader)`. The concrete `File.Reader` lives in bun_sys; bun_core routes +// The concrete `File.Reader` lives in bun_sys; bun_core routes // the underlying `read(2)` through the [`OutputSinkVTable::read`] slot so the // `prompt`/`init`/`publish` callers can read stdin without naming bun_sys. // ────────────────────────────────────────────────────────────────────────── @@ -2765,17 +2749,16 @@ pub struct BufferedStdin { } impl BufferedStdin { - /// Zig `BufferedReader.reader()` — the std adapter just hands back `&mut - /// Self` (which already exposes `read`/`read_byte`). + /// Hands back `&mut Self` (which already exposes `read`/`read_byte`). #[inline] pub fn reader(&mut self) -> &mut Self { self } - /// Zig `BufferedReader.read` — fill `dest` from the buffer, refilling from + /// Fill `dest` from the buffer, refilling from /// the underlying fd until `dest` is full or EOF. Returns `Ok(0)` on EOF. /// - /// PORT NOTE: matches std `BufferedReader.read` fill-to-completion semantics + /// Matches std `BufferedReader.read` fill-to-completion semantics /// (loops on the underlying fd), not POSIX partial-read. pub fn read(&mut self, dest: &mut [u8]) -> Result { let mut written: usize = 0; @@ -2811,8 +2794,7 @@ impl BufferedStdin { } } - /// Zig `GenericReader.readByte` — `Err` on I/O error *or* EOF (matches - /// `error.EndOfStream`). + /// Read one byte — `Err` on I/O error *or* EOF (`EndOfStream`). pub fn read_byte(&mut self) -> Result { if self.start < self.end { let b = self.buf[self.start]; @@ -2826,7 +2808,7 @@ impl BufferedStdin { } } - /// Zig `GenericReader.readUntilDelimiterArrayList` — appends bytes (not + /// Appends bytes (not /// including `delimiter`) into `out`; errors with `StreamTooLong` /// semantics if `out.len()` would exceed `max_size`. pub fn read_until_delimiter_array_list( @@ -2849,14 +2831,14 @@ impl BufferedStdin { } } -/// Unbuffered stdin byte reader (`std.fs.File.stdin().readerStreaming(..)` in -/// the Zig). Each `take_byte` is a 1-byte blocking read on the process stdin. +/// Unbuffered stdin byte reader. +/// Each `take_byte` is a 1-byte blocking read on the process stdin. pub struct StdinReader { fd: Fd, } impl StdinReader { - /// Zig `Reader.takeByte` — `Err` on I/O error *or* EOF. + /// Read one byte — `Err` on I/O error *or* EOF. #[inline] pub fn take_byte(&mut self) -> Result { let mut one = [0u8; 1]; @@ -2872,7 +2854,7 @@ impl StdinReader { } } -/// `std.fs.File.stdin().readerStreaming(&buf)` — fresh, unbuffered stdin +/// Fresh, unbuffered stdin /// reader. Used by `alert()`/`confirm()` which read a handful of bytes. #[inline] pub fn stdin_reader() -> StdinReader { @@ -2892,11 +2874,9 @@ pub fn stdin_reader() -> StdinReader { #[inline] pub fn buffered_stdin() -> *mut BufferedStdin { BUFFERED_STDIN.get() - // TODO(port): self-ref pointer escape — see error_writer() } -/// `bun.Output.buffered_stdin.reader()` — same accessor as [`buffered_stdin`]; -/// the Zig spelling exposed the static itself and callers chained `.reader()`. +/// `bun.Output.buffered_stdin.reader()` — same accessor as [`buffered_stdin`]. #[inline] pub fn buffered_stdin_reader() -> *mut BufferedStdin { buffered_stdin() @@ -2947,13 +2927,12 @@ impl Synchronized { mod output_macro_tests { //! Compile-shape regression tests for the `pretty*!`/`note!`/`warn!`/ //! `debug!` wrapper macros. These don't drive a live `Source` (no I/O); - //! they assert that the macros *expand* for the shapes the Zig originals - //! accept and that the `*ln!` newline guard const-evaluates correctly. + //! they assert that the macros *expand* for the expected call shapes + //! and that the `*ln!` newline guard const-evaluates correctly. use super::_needs_nl; use bun_core_macros::pretty_fmt; - /// `note!`/`warn!`/`debug!` must accept a `concat!(..)` template — Zig's - /// `note(comptime fmt, args)` is routinely called with `"a" ++ "b"`. The + /// `note!`/`warn!`/`debug!` must accept a `concat!(..)` template. The /// `:literal` matcher rejected this; `:expr` + proc-macro `concat!` /// flattening makes it compile. #[test] @@ -3018,7 +2997,7 @@ mod output_macro_tests { #[cfg(test)] mod pretty_fmt_tests { //! Parity checks between the `pretty_fmt!` proc-macro (compile-time) and - //! `pretty_fmt_runtime` (1:1 port of Zig `prettyFmt`). Guards against the + //! `pretty_fmt_runtime`. Guards against the //! macro leaking raw ``/`` markup into help text. use super::{RESET, pretty_fmt_runtime}; use bun_core_macros::pretty_fmt; @@ -3111,8 +3090,8 @@ mod pretty_fmt_tests { #[test] fn macro_matches_runtime() { - // Spot-check macro output against the runtime reference (which is 1:1 - // with Zig `prettyFmt`) for the help-text shapes that matter. + // Spot-check macro output against the runtime reference for the + // help-text shapes that matter. macro_rules! check { ($s:literal) => {{ assert_eq!( @@ -3137,5 +3116,3 @@ mod pretty_fmt_tests { ); } } - -// ported from: src/bun_core/output.zig diff --git a/src/bun_core/result.rs b/src/bun_core/result.rs index d9d9db3dc14..a7bb932f35b 100644 --- a/src/bun_core/result.rs +++ b/src/bun_core/result.rs @@ -1,19 +1,17 @@ -// ─── bun_core::Error — Zig `anyerror` port ──────────────────────────────── +// ─── bun_core::Error — global named-error set ────────────────────────────── // -// Zig's `anyerror` is a global error set: every distinct `error.Foo` name in -// the program is assigned a unique non-zero u16 at link time, `@intFromError` -// returns that code, `@errorName` recovers the string, and two `error.Foo`s -// from different modules compare equal because the *name* is the identity. +// Every distinct error name in the program is assigned a unique non-zero u16; +// the *name* is the identity, so two errors with the same name from different +// modules compare equal. // // Rust has no link-time global enum, so we intern at runtime: a process-wide // append-only `&'static str` table guarded by an RwLock. The `err!()` macro // caches each call-site's code in a 2-byte `AtomicU16` slot (zero-init → // `.bss`), so the lock is touched once per *name-site*, not once per call — -// matching Zig's zero-cost comparison (`e == err!(Foo)` is a u16 compare -// after first use). +// `e == err!(Foo)` is a zero-cost u16 compare after first use. // // Layout is `#[repr(transparent)] NonZeroU16`, so `Option` is one u16 -// and FFI/packed-struct slots that held a Zig `anyerror` keep the same width. +// and fits FFI/packed-struct slots holding a 16-bit error code. use crate::RwLock; use core::fmt; @@ -33,8 +31,8 @@ pub struct Error(NonZeroU16); /// the `pub const` Errors below. (The errno→name map lives in bun_errno via the /// `ErrnoNames` hook; entries here are only fast-path intern hits.) const SEED: &[&str] = &[ - // — well-known Zig error-set members the runtime matches on by value — - "Unexpected", // 1 (Zig's catch-all; also `errno_map` default) + // — well-known error names the runtime matches on by value — + "Unexpected", // 1 (catch-all; also `errno_map` default) "OutOfMemory", // 2 "EndOfStream", // 3 "StreamTooLong", // 4 @@ -143,8 +141,7 @@ impl Error { pub const TODO: Self = Self::UNEXPECTED; /// Intern `name`, returning its process-unique code. Idempotent: the same - /// string (by value) always yields the same `Error`. This is the runtime - /// half of Zig's link-time `anyerror` assignment. + /// string (by value) always yields the same `Error`. /// /// `#[cold]`: only reached on a per-site cache miss (or `err!(from e)`); /// keeps the SEED scan + RwLock probe out of `.text.hot` so @@ -174,7 +171,7 @@ impl Error { Self::intern(name) } - /// Zig: `@errorName(e)`. Never allocates; the table only stores `'static`. + /// The error's name. Never allocates; the table only stores `'static`. pub fn name(self) -> &'static str { let code = self.0.get() as usize; if code <= SEED.len() { @@ -187,13 +184,13 @@ impl Error { .unwrap_or("Unexpected") } - /// Zig: `@intFromError(e)`. + /// The raw u16 code. #[inline] pub const fn as_u16(self) -> u16 { self.0.get() } - /// Zig: `@errorFromInt(n)`. `0` (the "no error" value Zig forbids) maps to + /// `0` (the "no error" value) maps to /// `Unexpected` rather than panicking, since callers feed untrusted ints. #[inline] pub const fn from_raw(code: u16) -> Self { @@ -203,17 +200,15 @@ impl Error { } } - /// Port of `bun.errnoToZigErr`: map a raw OS errno to its named error. - /// Unknown errnos collapse to `Unexpected` (matching the Zig `@memset`). + /// Map a raw OS errno to its named error. + /// Unknown errnos collapse to `Unexpected`. pub fn from_errno(errno: i32) -> Self { - // Zig builds `errno_map: [max+1]anyerror` at comptime (bun.zig:2841); - // we build the equivalent once at first use by interning every + // Build the dense errno map once at first use by interning every // platform `SystemErrno` tag name. After init, lookup is a plain - // bounds-checked array index — same cost as the Zig version. + // bounds-checked array index. static ERRNO_MAP: crate::Once> = crate::Once::new(); let map = ERRNO_MAP.get_or_init(|| { - // Index 0 ("SUCCESS") is the no-error hole → Unexpected, - // matching the Zig `@memset(&map, error.Unexpected)`. + // Index 0 ("SUCCESS") is the no-error hole → Unexpected. (0..crate::ErrnoNames::SYS.max_dense()) .map(|i| match system_errno_name(i as i32) { Some(name) => Error::intern(name), @@ -222,7 +217,7 @@ impl Error { .collect() }); - // Windows libuv errnos are negative; normalise like the Zig original. + // Windows libuv errnos are negative; take the absolute value. let n = if cfg!(windows) { errno.unsigned_abs() } else { @@ -235,8 +230,7 @@ impl Error { return e; } // Windows: fall through to the sparse UV_* range (3000..=4096) so e.g. - // `from_errno(-4058)` → `error.UV_ENOENT`, matching Zig's full-width - // `errno_map` (bun.zig:2841-2851 sizes it to `max(@intFromEnum)+1`). + // `from_errno(-4058)` → `error.UV_ENOENT`. #[cfg(windows)] if let Some(name) = system_errno_name(errno) { return Self::intern(name); @@ -265,15 +259,16 @@ impl From for Error { // Windows: `raw_os_error()` returns the raw Win32 `GetLastError()` // code (ERROR_ACCESS_DENIED=5, ERROR_SHARING_VIOLATION=32, …), // NOT a `SystemErrno`. Routing it through `ErrnoNames::SYS.name()` would - // alias garbage (5→EIO, 32→EPIPE). The Zig pipeline first runs - // `Win32Error.toSystemErrno()` (windows_errno.zig:290) before any - // `errno_map` lookup; that table lives in `bun_errno`, which is - // tier-above `bun_core` (dep cycle), so we can't call it here. - // Fall back to `Unexpected` rather than return a wrong name. - // TODO(port): plumb a Win32→SystemErrno hook (or duplicate the - // table) so `?`-propagated `io::Error`s name correctly on Windows. + // alias garbage (5→EIO, 32→EPIPE). Translate Win32 → SystemErrno + // first; that table lives in `bun_errno` + // (tier-above `bun_core`, dep cycle), reached via the `win32_name` + // method on the `ErrnoNames` link-interface. Unknown codes still + // collapse to `Unexpected` rather than aliasing a wrong name. #[cfg(windows)] - Some(_code) => Self::UNEXPECTED, + Some(code) => match crate::ErrnoNames::SYS.win32_name(code as u32) { + Some(name) => Self::intern(name), + None => Self::UNEXPECTED, + }, None => Self::UNEXPECTED, } } @@ -283,10 +278,9 @@ impl From for Error { Self::OUT_OF_MEMORY } } -/// Zig's `std.Io.Writer` error set surfaces as `error.WriteFailed` when -/// propagated through `try writer.print(…)`; the Rust port routes formatted -/// output through `core::fmt::Write`, whose only error value is the unit -/// `fmt::Error`. Map it to the same tag so `?`-propagation matches the spec. +/// Formatted output routes through `core::fmt::Write`, whose only error value +/// is the unit `fmt::Error`. Map it to the `WriteFailed` tag so `?`-propagation +/// yields a named error. impl From for Error { fn from(_: core::fmt::Error) -> Self { Self::WRITE_FAILED @@ -295,9 +289,8 @@ impl From for Error { /// Extension for `?`-propagating non-`fmt::Error` write failures (e.g. /// `std::io::Error` from `write!(&mut Vec, …)` / `Cursor` / `BufWriter`) -/// as the spec's `error.WriteFailed` tag. Bare `?` on those would route through -/// [`From`] → errno/`Unexpected`, which diverges from the Zig -/// `try writer.print(…)` contract. Replaces the open-coded +/// as the `error.WriteFailed` tag. Bare `?` on those would route through +/// [`From`] → errno/`Unexpected` instead. Replaces the open-coded /// `.map_err(|_| err!("WriteFailed"))` pattern at ~20 call sites. pub trait OrWriteFailed { fn or_write_failed(self) -> core::result::Result; @@ -343,7 +336,7 @@ macro_rules! named_error_set { /// Stamp out `impl Display + impl Error` for one or more /// `strum::IntoStaticStr`-deriving error enums whose user-facing string is -/// exactly the variant tag (Zig `@errorName(e)` semantics). Replaces the +/// exactly the variant tag. Replaces the /// hand-rolled 5-line `f.write_str(<&'static str>::from(self))` boilerplate. /// /// Kept separate from [`named_error_set!`] because not every named error set @@ -362,13 +355,12 @@ macro_rules! impl_tag_error { } // ─── coreutils_error_map ───────────────────────────────────────────────── -// Zig builds a comptime `EnumMap` with a per-OS -// `switch (Environment.os)` body (src/sys/coreutils_error_map.zig). The full -// EnumMap lives in `bun_sys::coreutils_error_map`; that crate is tier-above -// `bun_core`, so for `output.rs`'s integer-errno hot path we keep a parallel -// table here, keyed by `SystemErrno` *name* and resolved through the per-OS -// `ErrnoNames` hook — i.e. the same `errno → SystemErrno → message` -// composition the Zig does, just without the cross-crate enum. +// The full typed EnumMap lives in `bun_sys::coreutils_error_map`; that crate +// is tier-above `bun_core`, so for `output.rs`'s integer-errno hot path we +// keep a parallel table here, keyed by `SystemErrno` *name* and resolved +// through the per-OS `ErrnoNames` hook — the same +// `errno → SystemErrno → message` composition, just without the +// cross-crate enum. // // Layout: one shared BASE table (the glibc/coreutils strings — used as-is on // linux/android/windows/wasm) plus a small per-OS DELTA on macOS/FreeBSD that @@ -626,7 +618,7 @@ pub mod coreutils_error_map { }; } -/// Zig: `pub fn Result(comptime T: type, comptime E: type) type { return union(enum) { ok: T, err: E, ... } }` +/// A plain ok/err union. pub enum Result { Ok(T), Err(E), @@ -682,5 +674,3 @@ mod tests { assert_ne!(a, Error::TODO); } } - -// ported from: src/bun.zig diff --git a/src/bun_core/string/HashedString.rs b/src/bun_core/string/HashedString.rs index bced35f94a1..1f0cbd97d6b 100644 --- a/src/bun_core/string/HashedString.rs +++ b/src/bun_core/string/HashedString.rs @@ -46,9 +46,7 @@ impl HashedString { pub fn str(&self) -> &[u8] { // SAFETY: ptr and len were set together from a valid slice in `init`/`init_no_hash`; - // caller is responsible for keeping the backing buffer alive (same invariant as Zig). + // caller is responsible for keeping the backing buffer alive. unsafe { core::slice::from_raw_parts(self.ptr, self.len as usize) } } } - -// ported from: src/string/HashedString.zig diff --git a/src/bun_core/string/MutableString.rs b/src/bun_core/string/MutableString.rs index 3a7201e35a5..ea849c64c40 100644 --- a/src/bun_core/string/MutableString.rs +++ b/src/bun_core/string/MutableString.rs @@ -4,19 +4,18 @@ use bun_alloc::AllocError; /// VTable surface for `bun.ast.E.String` (CYCLEBREAK b0: GENUINE upward dep on /// `bun_ast::E::String`). Low tier defines the interface; high tier /// (`bun_js_parser`) provides `impl EStringRef for E::String`. -/// PERF(port): was inline concrete type — cold path (formatter/writer). +/// Dyn dispatch is acceptable: cold path (formatter/writer). pub trait EStringRef { fn is_utf8(&self) -> bool; fn slice(&mut self) -> &[u8]; fn slice16(&mut self) -> &[u16]; } -/// Layout-identical to Zig's `std.posix.iovec_const` -/// (`extern struct { base: [*]const u8, len: usize }`), which is defined -/// unconditionally for every target — it does NOT alias `uv_buf_t`/`WSABUF` -/// on Windows (those have reversed field order and a `u32` len). The Zig -/// spec `MutableString.toSocketBuffers` returns this shape on all platforms, -/// so there is no `cfg(windows)` split. +/// Layout-identical to POSIX `struct iovec` with a const base +/// (`{ base: *const u8, len: usize }`), used unconditionally on every target — +/// it does NOT alias `uv_buf_t`/`WSABUF` on Windows (those have reversed field +/// order and a `u32` len). `to_socket_buffers` returns this shape on all +/// platforms, so there is no `cfg(windows)` split. #[repr(C)] #[derive(Clone, Copy)] pub struct SocketBuffer { @@ -24,17 +23,14 @@ pub struct SocketBuffer { pub iov_len: usize, } -/// A growable byte buffer. In Zig this paired an `Allocator` with an -/// `ArrayListUnmanaged(u8)`; in Rust the global mimalloc allocator is implicit, -/// so this is a thin wrapper over `Vec`. +/// A growable byte buffer: a thin wrapper over `Vec` (the global mimalloc +/// allocator is implicit). #[derive(Default, Clone)] pub struct MutableString { - // Zig field `std.mem.Allocator` param — deleted (global mimalloc). pub list: Vec, } -// Zig: `Npm.Registry.BodyPool = ObjectPool(MutableString, MutableString.init2048, true, 8)` -// (src/install/npm.zig). The `bun_collections::pool::ObjectPoolType` impl +// The `bun_collections::pool::ObjectPoolType` impl // lives in `bun_collections` (trait owner) to avoid a `bun_core → // bun_collections` dep cycle now that `MutableString` is in `bun_core`. @@ -54,7 +50,6 @@ impl MutableString { } /// Returns a `std::io::Write` borrow of this buffer. - /// Zig: `pub const Writer = std.Io.GenericWriter(*@This(), Allocator.Error, writeAll)`. pub fn writer(&mut self) -> &mut Self { self } @@ -63,19 +58,16 @@ impl MutableString { self.list.is_empty() } - // Zig `deinit` only freed `list`; `Vec` drops automatically — no `Drop` impl needed. + // `Vec` drops automatically — no `Drop` impl needed. - /// Zig: `self.list.expandToCapacity()` — set `len = capacity` so callers + /// Set `len = capacity` so callers /// can index into the spare region (e.g. `read()` into `&mut list[n..]`). /// - /// Matches Zig semantics: the new tail is left **uninitialized** — callers - /// must treat `list[old_len..]` as write-only until overwritten (typically - /// by `read()`). The previous port zero-filled here, which memset the - /// entire pooled scratch buffer before every `package.json` read. + /// Callers must treat `list[old_len..]` as write-only until overwritten + /// (typically by `read()`). #[inline] pub fn expand_to_capacity(&mut self) { - // Zero only the spare region so the exposed tail is defined (Zig's - // `expandToCapacity` leaves it `undefined` — we don't, to avoid + // Zero only the spare region so the exposed tail is defined (avoids // CWE-908 uninit-memory exposure if a caller reads before write). let old = self.list.len(); self.list.resize(self.list.capacity(), 0); @@ -84,7 +76,6 @@ impl MutableString { } pub fn owns(&self, items: &[u8]) -> bool { - // Zig: bun.isSliceInBuffer(items, this.list.items.ptr[0..this.list.capacity]) // Pointer-range check against the full allocation; done with addresses // rather than forming a `&[u8]` over `[len..cap)` (uninit) bytes. let base = self.list.as_ptr() as usize; @@ -99,7 +90,9 @@ impl MutableString { } pub fn writable_n_bytes_assume_capacity(&mut self, amount: usize) -> &mut [u8] { - // SAFETY: caller fully writes the returned slice (Zig contract). + // SAFETY: caller has reserved at least `amount` bytes of spare capacity + // (debug-asserted in the callee) and fully writes the returned slice + // before reading it. unsafe { crate::vec::writable_slice_assume_capacity(&mut self.list, amount) } } @@ -120,7 +113,7 @@ impl MutableString { pub fn buffered_writer(&mut self) -> BufferedWriter<'_> { BufferedWriter { context: self, - buffer: [0u8; BufferedWriter::MAX], // PERF(port): Zig left this `undefined` + buffer: [0u8; BufferedWriter::MAX], pos: 0, } } @@ -141,7 +134,6 @@ impl MutableString { #[inline] pub fn ensure_unused_capacity(&mut self, amount: usize) -> Result<(), AllocError> { - // Zig: `pub const ensureUnusedCapacity = growIfNeeded;` self.grow_if_needed(amount) } @@ -156,10 +148,9 @@ impl MutableString { /// identifier, you're going to potentially cause trouble with non-BMP code /// points in target environments that don't support bracketed Unicode escapes. pub fn ensure_valid_identifier(str: &[u8]) -> Result, AllocError> { - // TODO(port): Zig returned `[]const u8` which could be either the input - // borrow or a fresh allocation. Rust cannot express that without a - // lifetime + Cow; for now we always return owned `Box<[u8]>` and copy - // on the borrow paths. Consider `Cow<'a, [u8]>`. + // The result could be either the input borrow or a fresh allocation; + // rather than a lifetime + Cow we always return owned `Box<[u8]>` and + // copy on the borrow paths. if str.is_empty() { return Ok(Box::<[u8]>::from(b"_".as_slice())); } @@ -175,7 +166,6 @@ impl MutableString { return Ok(Box::<[u8]>::from(b"_".as_slice())); } - // TODO(port): lexer / lexer_tables arrive from move-in (MOVE_DOWN bun_js_parser::{lexer,lexer_tables} → string) use crate::string::lexer as js_lexer; use crate::string::lexer_tables as js_lexer_tables; @@ -254,11 +244,9 @@ impl MutableString { self.list.reserve(str.len().saturating_sub(self.list.len())); if self.list.is_empty() { - // Zig: list.insertSlice(allocator, 0, str) self.list.extend_from_slice(str); } else { - // Zig: list.replaceRange(allocator, 0, str.len, str) - // TODO(port): verify Vec::splice matches ArrayList.replaceRange semantics + // Overwrite-then-extend replaces the range [0, str.len). let n = str.len().min(self.list.len()); self.list[..n].copy_from_slice(&str[..n]); if str.len() > n { @@ -285,7 +273,6 @@ impl MutableString { if items.is_empty() { return Ok(()); } - // Zig: ensureTotalCapacityPrecise(len + items.len) → reserve_exact(items.len()) self.list.reserve_exact(items.len()); // After `reserve_exact`, `extend_from_slice` is a single memcpy with // no further reallocation — same codegen as the raw `set_len` path. @@ -302,14 +289,13 @@ impl MutableString { pub fn reset_to(&mut self, index: usize) { debug_assert!(index <= self.list.capacity()); // SAFETY: index <= capacity asserted; bytes in [len..index] may be - // uninitialized — matches Zig semantics. Callers must have previously + // uninitialized. Callers must have previously // written those bytes (e.g. via writable_n_bytes). unsafe { self.list.set_len(index) }; } pub fn inflate(&mut self, amount: usize) -> Result<(), AllocError> { - // Zig MutableString.inflate: `list.resize(amount)` leaves new bytes - // uninitialized. Callers always overwrite the inflated region, so the + // Callers always overwrite the inflated region, so the // zero-fill here is technically redundant — but it lowers to a single // memset and avoids `clippy::uninit_vec` / a `set_len` over uninit bytes. self.list.resize(amount, 0); @@ -318,7 +304,6 @@ impl MutableString { #[inline] pub fn append_char_n_times(&mut self, char: u8, n: usize) -> Result<(), AllocError> { - // Zig: list.appendNTimes self.list.extend(core::iter::repeat_n(char, n)); Ok(()) } @@ -331,7 +316,6 @@ impl MutableString { #[inline] pub fn append_char_assume_capacity(&mut self, char: u8) { - // PERF(port): was assume_capacity self.list.push(char); } @@ -342,7 +326,7 @@ impl MutableString { } } -/// Growable string sink. Zig: `MutableString.writer()`. +/// Growable string sink. impl crate::io::Write for MutableString { #[inline] fn write_all(&mut self, buf: &[u8]) -> Result<(), crate::Error> { @@ -366,7 +350,6 @@ impl MutableString { #[inline] pub fn append_assume_capacity(&mut self, char: &[u8]) { - // PERF(port): was assume_capacity self.list.extend_from_slice(char); } @@ -380,20 +363,16 @@ impl MutableString { } pub fn to_owned_slice(&mut self) -> Box<[u8]> { - // Zig: bun.handleOom(self.list.toOwnedSlice(self.allocator)) core::mem::take(&mut self.list).into_boxed_slice() } pub fn to_dynamic_owned(&mut self) -> Box<[u8]> { - // TODO(port): Zig `DynamicOwned([]u8)` carried its allocator; with the - // global allocator this collapses to `Box<[u8]>`. Revisit if a distinct - // `bun_ptr::DynamicOwned` type is introduced. + // With the global allocator this collapses to `Box<[u8]>`. self.to_owned_slice() } - /// `self.allocator` must be `bun.default_allocator`. + /// Alias of [`Self::to_owned_slice`]; the global allocator is implicit. pub fn to_default_owned(&mut self) -> Box<[u8]> { - // Zig asserted allocator == default_allocator; allocator field is gone. self.to_owned_slice() } @@ -433,9 +412,7 @@ impl MutableString { } pub fn index_of(&self, str: u8) -> Option { - // TODO(port): Zig signature is `str: u8` but body calls - // `std.mem.indexOf(u8, items, str)` which expects a slice — looks like - // a latent bug in the Zig source. Porting as single-byte search. + // Single-byte search (the `str` parameter is one byte despite the name). self.list.iter().position(|&b| b == str) } @@ -443,14 +420,13 @@ impl MutableString { self.list.as_slice() == other } - /// Zig spec: `[count]std.posix.iovec_const` — that struct is - /// `{ base: [*]const u8, len: usize }` on every target (including Windows; + /// Returns `[SocketBuffer; COUNT]` — + /// `{ base: *const u8, len: usize }` on every target (including Windows; /// it is NOT `uv_buf_t`). Single implementation, no `cfg(windows)` split. pub fn to_socket_buffers( &self, ranges: [(usize, usize); COUNT], ) -> [SocketBuffer; COUNT] { - // PERF(port): Zig used `inline for` (unrolled); plain loop here. core::array::from_fn(|i| { let r = ranges[i]; let s = &self.list[r.0..r.1]; @@ -467,9 +443,6 @@ impl MutableString { } } -// Zig: `MutableString.init2048` is the default `Init` fn passed to -// (ObjectPoolType impl at L25 — uses init2048() per npm.zig spec.) - impl std::io::Write for MutableString { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.list.extend_from_slice(buf); @@ -491,8 +464,7 @@ pub struct BufferedWriter<'a> { impl<'a> BufferedWriter<'a> { const MAX: usize = BUFFERED_WRITER_MAX; - // Zig: `pub const Writer = std.Io.GenericWriter(*BufferedWriter, Allocator.Error, writeAll)` - // → `impl std::io::Write for BufferedWriter` below; `writer()` returns `&mut Self`. + // `impl std::io::Write for BufferedWriter` below; `writer()` returns `&mut Self`. pub fn flush(&mut self) -> Result<(), AllocError> { let _ = self.context.write_all(&self.buffer[0..self.pos])?; @@ -513,8 +485,6 @@ impl<'a> BufferedWriter<'a> { if pending.len() + self.pos > Self::MAX { self.flush()?; } - // PORT NOTE: reshaped for borrowck (cannot call self.remain() while - // borrowing pending.len() against self.pos). let pos = self.pos; self.buffer[pos..pos + pending.len()].copy_from_slice(pending); self.pos += pending.len(); @@ -543,13 +513,7 @@ impl<'a> BufferedWriter<'a> { if pending.len() >= Self::MAX { self.flush()?; - // PORT NOTE: Zig wrote into `this.remain()[0..bytes.len*2]` here, - // which after `flush()` is `this.buffer[0..bytes.len*2]` — but - // `bytes.len*2 > MAX`, so that indexes past the stack buffer. This - // looks like a latent bug in the Zig (should write into - // `context.list`). Porting the apparent intent: write into the - // freshly-reserved context.list tail. - // TODO(port): confirm and fix upstream. + // Write into the freshly-reserved context.list tail. let old = self.context.list.len(); // SAFETY: copy_utf16_into_utf8 writes <= bytes.len*2; trimmed below. let tail = @@ -641,5 +605,3 @@ impl<'a> std::io::Write for BufferedWriter<'a> { BufferedWriter::flush(self).map_err(|_| std::io::ErrorKind::OutOfMemory.into()) } } - -// ported from: src/string/MutableString.zig diff --git a/src/bun_core/string/SmolStr.rs b/src/bun_core/string/SmolStr.rs index dade67f31d9..cc2f58eb8c3 100644 --- a/src/bun_core/string/SmolStr.rs +++ b/src/bun_core/string/SmolStr.rs @@ -2,14 +2,14 @@ use core::mem; use bun_alloc::AllocError; -// NOTE: the tag-bit scheme below only works on little-endian systems (matches Zig comment). +// NOTE: the tag-bit scheme below only works on little-endian systems. const _: () = assert!(cfg!(target_endian = "little")); // NOTE: the packed layout assumes 64-bit pointers (`__ptr` occupies the upper 64 bits of the u128). const _: () = assert!(mem::size_of::() == 8); /// This is a string type that stores up to 15 bytes inline on the stack, and heap allocates if it is longer. /// -/// Zig layout (`packed struct(u128)`, little-endian bit order): +/// Layout (packed u128, little-endian bit order): /// bits 0..32 = `__len: u32` /// bits 32..64 = `cap: u32` /// bits 64..128 = `__ptr: [*]u8` (bit 127 is the inlined tag) @@ -24,7 +24,7 @@ impl Clone for SmolStr { return SmolStr(self.0); } // Heap-backed: dupe the bytes into a fresh Vec allocation. - // bun.handleOom: panic on OOM (matches Zig allocator semantics). + // Panic on OOM. SmolStr::from_slice(self.slice()).expect("OOM") } } @@ -62,8 +62,6 @@ impl SmolStr { // ---- public API ------------------------------------------------------- - // TODO(port): Zig `jsonStringify` participates in std.json's structural protocol; - // map to bun's JSON-serialize trait once one exists. pub fn json_stringify(&self, writer: &mut W) -> Result<(), crate::Error> where W: JsonWriter, @@ -140,10 +138,8 @@ impl SmolStr { pub fn from_slice(values: &[u8]) -> Result { if values.len() > Inlined::MAX_LEN { - // TODO(port): verify Vec::::init_capacity / append_slice_assume_capacity API. let mut baby_list = Vec::::with_capacity(values.len()); baby_list.extend_from_slice(values); - // PERF(port): was appendSliceAssumeCapacity — profile if hot. return Ok(SmolStr::from_baby_list(baby_list)); } @@ -168,7 +164,6 @@ impl SmolStr { if inlined.len() as usize + 1 > Inlined::MAX_LEN { let mut baby_list = Vec::::with_capacity(inlined.len() as usize + 1); baby_list.extend_from_slice(inlined.slice()); - // PERF(port): was appendSliceAssumeCapacity — profile if hot. baby_list.push(char); // Old value is inlined (no heap) so `Drop` is a no-op; plain assign is fine. *self = SmolStr::from_baby_list(baby_list); @@ -202,7 +197,6 @@ impl SmolStr { let mut baby_list = Vec::::with_capacity(old_len + values.len()); baby_list.extend_from_slice(inlined.slice()); baby_list.extend_from_slice(values); - // PERF(port): was appendSliceAssumeCapacity — profile if hot. // Old `*self` is inlined (no heap) so `Drop` is a no-op; plain assign is fine. *self = SmolStr::from_baby_list(baby_list); return Ok(()); @@ -234,7 +228,6 @@ impl Drop for SmolStr { fn drop(&mut self) { if !self.is_inlined() { // SAFETY: ptr/len/cap describe a Vec allocation we own; reconstruct to free. - // TODO(port): verify Vec Drop frees; else dealloc via global allocator directly. let list = unsafe { Vec::::from_raw_parts( self.ptr(), @@ -247,14 +240,14 @@ impl Drop for SmolStr { } } -// TODO(port): placeholder for the std.json `writer: anytype` protocol used by json_stringify. +/// Minimal byte-writer protocol used by `json_stringify`. pub trait JsonWriter { fn write(&mut self, bytes: &[u8]) -> Result<(), crate::Error>; } // --------------------------------------------------------------------------- -/// Zig layout (`packed struct(u128)`, little-endian bit order): +/// Layout (packed u128, little-endian bit order): /// bits 0..120 = `data: u120` (15 inline bytes) /// bits 120..127 = `__len: u7` /// bit 127 = `_tag: u1` @@ -282,7 +275,6 @@ impl Inlined { /// if `str` is longer than `MAX_LEN` pub(crate) fn init(str: &[u8]) -> Result { if str.len() > Self::MAX_LEN { - // PERF(port): @branchHint(.unlikely) — no stable Rust equivalent return Err(InlinedError::StringTooLong); } let mut inlined = Inlined::EMPTY; @@ -384,12 +376,9 @@ mod tests { #[test] fn inlined_does_not_allocate() { - // TODO(port): Zig used std.testing.allocator to assert no allocation; no direct - // equivalent here. The is_inlined() check is the observable proxy. + // The is_inlined() check is the observable proxy for "no allocation". let hello = SmolStr::from_slice(b"hello").unwrap(); assert_eq!(5, hello.len()); assert!(hello.is_inlined()); } } - -// ported from: src/string/SmolStr.zig diff --git a/src/bun_core/string/StringBuilder.rs b/src/bun_core/string/StringBuilder.rs index 38b0f14b48b..d30180c7dc8 100644 --- a/src/bun_core/string/StringBuilder.rs +++ b/src/bun_core/string/StringBuilder.rs @@ -9,10 +9,10 @@ use bun_simdutf_sys::simdutf; /// append, then `allocate()` once, then `append()` each slice. Returned slices /// point into the single backing buffer. /// -// TODO(port): the `append*` methods return `&[u8]` borrowing `self.ptr` while -// also taking `&mut self`. Zig hands out aliasing slices freely; in Rust this -// needs either an explicit `'a` on the builder, interior mutability (`Cell` -// for len), or callers must use `StringPointer` offsets instead. +// Note: the `append*` methods return `&[u8]` borrowing `self.ptr` while also +// taking `&mut self`, so callers cannot hold two returned slices at once. +// Callers that need to interleave appends +// use `StringPointer` offsets or the unsafe `append_raw` escape hatch below. #[derive(Default)] pub struct StringBuilder { pub len: usize, @@ -48,17 +48,15 @@ impl StringBuilder { } pub fn count16_z(&mut self, slice: &[u16]) { - // PORT NOTE: WStr has no len method on its DST slice yet; callers pass &[u16]. - // Zig's `elementLengthUTF16IntoUTF8` is the same simdutf length call when input - // is valid; for WTF-16 with lone surrogates the slow path overestimates by 0-1 - // bytes which is fine for a capacity reservation. + // Callers pass &[u16] (WStr has no len method on its DST slice yet). + // For WTF-16 with lone surrogates the simdutf length estimate + // overestimates by 0-1 bytes, which is fine for a capacity reservation. self.cap += simdutf::length::utf8::from::utf16::le(slice) + 1; } pub fn append16(&mut self, slice: &[u16]) -> Option<&mut ZStr> { - // PORT NOTE: fallback_allocator param dropped (global mimalloc). - // PORT NOTE: borrowck — capture buf ptr, drop the &mut borrow before - // mutating self.len, then rebuild ZStr from the raw ptr. + // Borrowck: capture buf ptr, drop the &mut borrow before mutating + // self.len, then rebuild ZStr from the raw ptr. let buf = self.writable(); let buf_ptr = buf.as_mut_ptr(); if slice.is_empty() { @@ -78,8 +76,7 @@ impl StringBuilder { Some(unsafe { ZStr::from_raw_mut(buf_ptr, count) }) } else { // Fallback: WTF-16 → WTF-8 via the slow path that handles lone surrogates. - // Zig allocated from `fallback_allocator` and handed ownership to the - // caller; the Rust signature returns a borrow into `self`, so we copy + // The signature returns a borrow into `self`, so we copy // the WTF-8 bytes into the builder's reserved buffer (count16_z reserved // enough — simdutf's length estimate is an upper bound for WTF-16) and // drop the temporary Vec normally. No `mem::forget`. @@ -135,9 +132,8 @@ impl StringBuilder { } /// Copy `slice` into the reserved buffer and return a borrow of the copied - /// bytes with an *unbound* lifetime. Mirrors Zig's untracked `[]const u8` - /// return so callers may interleave appends and stash both slices (e.g. - /// `picohttp::Header::clone`). + /// bytes with an *unbound* lifetime, so callers may interleave appends and + /// stash both slices (e.g. `picohttp::Header::clone`). /// /// # Safety /// The returned slice aliases `self.ptr` and is only valid until the @@ -163,7 +159,6 @@ impl StringBuilder { } pub fn add_concat(&mut self, slices: &[&[u8]]) -> StringPointer { - // PORT NOTE: reshaped for borrowck — capture self.len before borrowing alloc. let start = self.len; let alloc = self.allocated_slice(); let mut remain = &mut alloc[start..]; @@ -311,8 +306,8 @@ impl StringBuilder { /// After calling this, you are responsible for freeing the underlying memory. /// This StringBuilder should not be used after calling this function. pub fn move_to_slice(&mut self) -> Box<[u8]> { - // TODO(port): Zig wrote into `*[]u8` out-param and reset self. Here we - // reconstruct the Box (allocated in init_capacity/allocate) and hand it back. + // Reconstruct the Box (allocated in init_capacity/allocate) and hand + // it back. // // `take()` first: `*self = Self::default()` drops the old value, and // `Drop` frees the buffer when `ptr` is still `Some` — leaving it set @@ -323,9 +318,9 @@ impl StringBuilder { }; let cap = self.cap; *self = Self::default(); - // SAFETY: ptr came from Box::<[u8]>::new_uninit_slice(cap) leaked above; - // all `cap` bytes have been written iff caller appended everything counted. - // TODO(port): if not fully written this reads uninit bytes — Zig didn't care. + // SAFETY: ptr came from Box::<[u8]>::new_uninit_slice(cap) leaked above. + // Caller contract: every counted byte must have been appended — if not + // fully written, the returned Box exposes uninit bytes (UB to read). unsafe { crate::heap::take(std::ptr::slice_from_raw_parts_mut(ptr.as_ptr(), cap)) } } } @@ -345,5 +340,3 @@ impl Drop for StringBuilder { } } } - -// ported from: src/string/StringBuilder.zig diff --git a/src/bun_core/string/StringJoiner.rs b/src/bun_core/string/StringJoiner.rs index f70c32b7a9d..960ac4ab3f3 100644 --- a/src/bun_core/string/StringJoiner.rs +++ b/src/bun_core/string/StringJoiner.rs @@ -5,8 +5,8 @@ use crate::string::strings; use bun_alloc::AllocError; -// PORT NOTE: Zig's `std.mem.Allocator` param field dropped — global mimalloc is used for -// node and duplicated-string allocations. +// Node and duplicated-string allocations use the global allocator (mimalloc); +// there is no per-joiner allocator field. #[derive(Default)] pub struct StringJoiner<'a> { /// Total length of all nodes @@ -76,10 +76,6 @@ impl<'a> StringJoiner<'a> { self.push_owned(Box::from(data)); } - // PORT NOTE: Zig signature was `push(data: []const u8, ?Allocator param)`. - // The optional allocator only encoded ownership of `data`, which has no Rust - // analogue for a borrowed `&[u8]`; callers wanting owned semantics use - // `push_owned`/`push_cloned` instead. pub fn push(&mut self, data: &'a [u8]) { if data.is_empty() { return; @@ -143,8 +139,7 @@ impl<'a> StringJoiner<'a> { let len = self.len; self.len = 0; - // Zig: `allocator.alloc(u8, this.len)` — allocates uninitialized. - // `Vec::with_capacity` + `extend_from_slice` is also zero-fill-free + // `Vec::with_capacity` + `extend_from_slice` is zero-fill-free // (each push is a `memcpy` into spare capacity), and since the final // `len == capacity` the `into_boxed_slice` is a no-realloc move. let mut out = Vec::::with_capacity(len); @@ -311,5 +306,3 @@ mod tests { assert_eq!(&*detached.done().unwrap(), b"KEY borrowed cloned KEY"); } } - -// ported from: src/string/StringJoiner.zig diff --git a/src/bun_core/string/escapeRegExp.rs b/src/bun_core/string/escapeRegExp.rs index b78434c016b..950120e5a07 100644 --- a/src/bun_core/string/escapeRegExp.rs +++ b/src/bun_core/string/escapeRegExp.rs @@ -2,9 +2,6 @@ use crate::string::strings; const SPECIAL_CHARACTERS: &[u8] = b"|\\{}()[]^$+*?.-"; -// TODO(port): writer trait — Zig uses `*std.Io.Writer` (byte writer). Mapping to -// `&mut impl std::io::Write` per PORTING.md; swap to the concrete crate-local -// byte-writer trait once `bun_io` is wired. pub fn escape_reg_exp( input: &[u8], writer: &mut W, @@ -57,8 +54,5 @@ pub fn escape_reg_exp_for_package_name_matching( writer.write_all(remain) } -// PORT NOTE: the Zig file re-exported `jsEscapeRegExp` / `jsEscapeRegExpForPackageNameMatching` -// from `../jsc/bun_string_jsc.zig`. Per PORTING.md these `*_jsc` alias lines are deleted — -// the JS-facing wrappers live in the `*_jsc` crate as extension-trait methods. - -// ported from: src/string/escapeRegExp.zig +// The JS-facing wrappers (`jsEscapeRegExp` / `jsEscapeRegExpForPackageNameMatching`) +// live in the `*_jsc` crate as extension-trait methods. diff --git a/src/bun_core/string/identifier.rs b/src/bun_core/string/identifier.rs index 5819dfabc25..02e7e4aa8a4 100644 --- a/src/bun_core/string/identifier.rs +++ b/src/bun_core/string/identifier.rs @@ -22,12 +22,9 @@ use crate::string::strings::{CodePoint, CodepointIterator, Cursor}; /// Whole-string ES identifier check over WTF-8 bytes. /// -/// Port of `js_lexer.isIdentifier` (src/js_parser/lexer.zig:3058). Zig has -/// exactly one impl; the Rust port had triplicated it across `bun_string`, -/// `bun_ast`, and `bun_js_parser` during the move-down layering pass. This is -/// the canonical home: it sits next to the per-codepoint predicates and the -/// two-stage Unicode tables it bottoms out in, and `CodepointIterator` lives -/// in this crate. +/// This is the canonical home: it sits next to the per-codepoint predicates +/// and the two-stage Unicode tables it bottoms out in, and +/// `CodepointIterator` lives in this crate. pub fn is_identifier(text: &[u8]) -> bool { if text.is_empty() { return false; @@ -45,15 +42,14 @@ pub fn is_identifier(text: &[u8]) -> bool { true } -/// Whole-string ES identifier check over WTF-16. Port of -/// `src/js_parser/lexer.zig:isIdentifierUTF16`. +/// Whole-string ES identifier check over WTF-16. /// /// Surrogate decoding is open-coded on purpose: an unpaired high surrogate /// (0xD800..=0xDBFF not followed by a low surrogate) advances ONE unit and is -/// fed raw to `is_identifier_start/part` — exactly matching Zig -/// `lexer.zig:3091-3096`. `crate::string::strings::utf16_codepoint` would advance -/// TWO units in that case (see immutable.rs:1644 PORT NOTE), so do NOT swap it -/// in here. +/// fed raw to `is_identifier_start/part`. `crate::string::strings::utf16_codepoint` (defined in +/// `immutable/unicode.rs`, re-exported through `immutable.rs`) would advance +/// TWO units in that case — its lead-surrogate arm returns `len: 2` even when +/// the next unit is not a trail surrogate — so do NOT swap it in here. pub fn is_identifier_utf16(text: &[u16]) -> bool { let n = text.len(); if n == 0 { @@ -78,12 +74,11 @@ pub fn is_identifier_utf16(text: &[u16]) -> bool { // ────────────────────────────────────────────────────────────────────────── // The remainder of this file is auto-generated. Do not edit. -// TODO(port): re-run the identifier-table generator with .rs output instead -// of post-processing the .zig; tables below were mechanically transcribed. +// To regenerate, re-run the identifier-table generator with .rs output. // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: Zig `u21` codepoint type → `u32`. Callers must pass cp <= 0x10FFFF -// (stage1 tables are sized for that range); out-of-range indexes panic. +// Callers must pass cp <= 0x10FFFF (stage1 tables are sized for that range); +// out-of-range indexes panic. /// isIDStartESNext checks if a codepoint is valid in the isIDStartESNext category pub(crate) fn is_id_start_es_next(cp: u32) -> bool { @@ -1658,5 +1653,3 @@ mod id_continue_es_next { 281474976710655, ]; } - -// ported from: src/js_parser/lexer/identifier.zig diff --git a/src/bun_core/string/immutable.rs b/src/bun_core/string/immutable.rs index e4f4549c3ec..5934c92e1aa 100644 --- a/src/bun_core/string/immutable.rs +++ b/src/bun_core/string/immutable.rs @@ -1,4 +1,4 @@ -//! Port of `src/string/immutable.zig` — `bun.strings` namespace. +//! The `bun.strings` namespace: //! SIMD-accelerated immutable string utilities operating on `&[u8]` (NOT `&str`). use core::cmp::Ordering; @@ -105,8 +105,7 @@ pub mod unicode { cp } - /// Zig: `CodepointIterator.needsUTF8Decoding` — true iff any byte in - /// `slice` begins a multi-byte WTF-8 sequence. + /// True iff any byte in `slice` begins a multi-byte WTF-8 sequence. pub fn needs_utf8_decoding(slice: &[u8]) -> bool { let mut i = 0usize; while i < slice.len() { @@ -129,12 +128,11 @@ pub mod unicode { } impl<'a> NewCodePointIterator<'a> { - /// Zig-style cursor advance. Returns `false` at end. - // PERF(port): `#[inline]` alone is hint-only; LLVM declined to inline + /// Cursor advance. Returns `false` at end. + // PERF: `#[inline]` alone is hint-only; LLVM declined to inline // this cross-crate into `bun_js_printer::print_identifier_ascii_only` // (the multibyte slow path makes the body look heavy). Called per-byte - // of every printed identifier under `ASCII_ONLY=true`. Zig's `iter.next` - // lives in the same TU and inlines. Force it. + // of every printed identifier under `ASCII_ONLY=true`. Force it. #[inline(always)] pub fn next(&self, cursor: &mut Cursor) -> bool { let bytes = self.bytes; @@ -148,8 +146,7 @@ pub mod unicode { let first = tail[0]; cursor.i = pos as u32; // ASCII fast path — the overwhelmingly common case for JS source - // (identifiers, escape-free strings). Matches Zig's per-byte ptr - // indexing + 1-arm switch in `decodeWTF8RuneT`. + // (identifiers, escape-free strings). if first < 0x80 { cursor.c = first as CodePoint; cursor.width = 1; @@ -172,7 +169,7 @@ pub mod unicode { } } - /// Zig: `unicode.zig:containsNonBmpCodePoint` — `true` iff `text` contains any + /// `true` iff `text` contains any /// codepoint above U+FFFF (i.e. would need a UTF-16 surrogate pair). pub fn contains_non_bmp_code_point(text: &[u8]) -> bool { let iter = CodepointIterator::init(text); @@ -185,7 +182,7 @@ pub mod unicode { false } - /// Zig: `unicode.zig:containsNonBmpCodePointOrIsInvalidIdentifier` — fused + /// Fused /// "must I quote this import/export alias?" predicate for `js_printer`. /// /// Returns `true` if `text` is empty, OR any codepoint is non-BMP (>U+FFFF, @@ -208,19 +205,15 @@ pub mod unicode { false } - /// `toUTF16Literal` — port of `unicode.zig:toUTF16Literal` → - /// `std.unicode.utf8ToUtf16LeStringLiteral`. Zig evaluated this at - /// `comptime` into a `Holder.value` const yielding `[:0]const u16`; the - /// Rust runtime port returns an owned `Box<[u16]>` (no `Box::leak` per - /// PORTING.md §Forbidden). Prefer the const `crate::string::w!("…")` macro at call - /// sites with literal inputs — this fn exists for the residual runtime - /// callers that thread `&[u8]` through. + /// `toUTF16Literal` — returns an owned `Box<[u16]>`. Prefer the const + /// `crate::string::w!("…")` macro at call sites with literal inputs — + /// this fn exists for the residual runtime callers that thread `&[u8]` + /// through. pub fn to_utf16_literal(s: &[u8]) -> Box<[u16]> { if s.is_empty() { return Box::new([]); } - // `std.unicode.utf8ToUtf16LeStringLiteral` requires valid UTF-8 (Zig - // would `catch unreachable` at comptime). simdutf gives us the exact + // Input must be valid UTF-8. simdutf gives us the exact // UTF-16 code-unit length, then a validating convert. let out_len = super::simdutf::length::utf16::from::utf8(s); let mut out = vec![0u16; out_len].into_boxed_slice(); @@ -238,11 +231,7 @@ pub mod unicode { /// (invalid lead byte → 1). Stops early at EOF or a truncated trailing sequence, /// returning the slice up to the last complete codepoint boundary. /// -/// Shared body of `js_parser::Lexer::peek` / `toml::Lexer::peek` (Zig: -/// `js_lexer.zig:267`, `toml/lexer.zig:128`). Unlike the upstream Zig copies — -/// whose `*const Self` stepper never advances and re-reads the first byte `n` -/// times — this helper actually advances; the sole live caller passes ASCII so -/// the fix is unobservable. +/// Shared body of `js_parser::Lexer::peek` / `toml::Lexer::peek`. #[inline] pub fn peek_n_codepoints_wtf8(bytes: &[u8], at: usize, n: usize) -> &[u8] { let mut end = at; @@ -261,10 +250,7 @@ pub fn peek_n_codepoints_wtf8(bytes: &[u8], at: usize, n: usize) -> &[u8] { /// WTF-8 codepoint stepper shared by the JS / JSON / TOML lexers. /// -/// Zig: `js_parser/lexer.zig` `nextCodepointSlice` / `nextCodepoint` (and the -/// byte-identical copy in `parsers/toml/lexer.zig`). The Rust port grew a -/// third copy when `json_lexer.rs` was carved out of `js_parser` to break the -/// `bun_js_parser ↔ bun_interchange` crate cycle; all three call the same +/// The JS, JSON, and TOML lexers all call the same /// `wtf8_byte_sequence_length_with_invalid` / `decode_wtf8_rune_t_multibyte` /// pair defined alongside this module, so the stepper belongs here. /// @@ -297,8 +283,7 @@ pub mod lexer_step { /// EOF or a truncated trailing multibyte sequence. /// /// Split into an `#[inline(always)]` ASCII/EOF fast path plus an outlined - /// multibyte tail so the hot per-byte loop folds into every `step()` site - /// (matches Zig's per-byte `ptr[current]` increment). + /// multibyte tail so the hot per-byte loop folds into every `step()` site. #[inline(always)] pub fn next_codepoint(contents: &[u8], current: &mut usize, end: &mut usize) -> CodePoint { let len = contents.len(); @@ -332,8 +317,6 @@ pub mod lexer_step { let cp_len = wtf8_byte_sequence_length_with_invalid(first) as usize; let avail = len - *current; - // Zig spec (lexer.zig nextCodepoint): `switch (slice.len) { 0 => -1, 1 => slice[0], else => decode }` - // where `slice` is empty when `cp_len + current > len` and `cp_len` bytes otherwise. // The ASCII fast path above handled `first < 0x80`; here `first >= 0x80` but `cp_len` // may still be 1 for invalid lead bytes (0x80-0xBF, 0xF8-0xFF) — those must yield the // raw byte, NOT the EOF sentinel, so the main lex loop falls through to its syntax-error @@ -341,7 +324,7 @@ pub mod lexer_step { let code_point: CodePoint = if cp_len == 1 { first as CodePoint } else if avail < cp_len { - // truncated multibyte at EOF → Zig's empty-slice arm + // truncated multibyte at EOF -1 } else { let mut quad = [0u8; 4]; @@ -369,8 +352,7 @@ pub mod lexer_step { } } -/// Strip a leading UTF-8 BOM (`EF BB BF`) if present. Mirrors -/// `bun.strings.withoutUTF8BOM` (immutable.zig:2332 → unicode.withoutUTF8BOM). +/// Strip a leading UTF-8 BOM (`EF BB BF`) if present. #[inline] pub fn without_utf8_bom(bytes: &[u8]) -> &[u8] { if bytes.len() >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF { @@ -382,7 +364,7 @@ pub fn without_utf8_bom(bytes: &[u8]) -> &[u8] { // Transcoding suite re-exported from bun_core (T0). pub use self::unicode::to_utf16_literal; -/// `bun.strings.w` — comptime UTF-8→UTF-16 literal. In Rust this **must** be a +/// Compile-time UTF-8→UTF-16 literal. This **must** be a /// macro (callers write `bun_core::strings::w!("…")`); a `fn` returning /// `&'static [u16]` would require leaking. Re-export of the crate-root `w!`. pub use crate::string::w; @@ -467,7 +449,7 @@ pub fn contains_char(self_: &[u8], char: u8) -> bool { #[inline] pub fn contains_char_t>(self_: &[T], char: u8) -> bool { - // TODO(port): Zig dispatched on T at comptime; in Rust we branch on size_of. + // Branch on size_of (const-folded). if core::mem::size_of::() == 1 { contains_char(reinterpret_to_u8(self_), char) } else { @@ -477,10 +459,8 @@ pub fn contains_char_t>(self_: &[T], char: u #[inline] pub fn contains(self_: &[u8], str: &[u8]) -> bool { - // Zig: containsT(u8) → indexOfT(u8) → indexOf, which routes through - // std.mem.indexOf and returns None for empty needle. The generic - // index_of_t below returns Some(0) for empty, so dispatch to the - // u8-specific index_of (which matches Zig/std.mem semantics). + // The generic index_of_t below returns Some(0) for an empty needle, so + // dispatch to the u8-specific index_of (which returns None for empty). index_of(self_, str).is_some() } @@ -493,8 +473,7 @@ pub fn contains_t(self_: &[T], str: &[T]) -> bool { // reach without depending on this crate); re-export to avoid a second copy. pub use crate::strings::contains_case_insensitive_ascii; -/// Zig: `std.meta.Int(.unsigned, @bitSizeOf(usize) - 1)` — fits in 63/31 bits so -/// `?OptionalUsize` is word-sized via niche. Rust `Option` already niches; keep +/// `Option` already niches; keep /// `u32` to match call sites that take `u32` indices throughout this module. pub type OptionalUsize = u32; @@ -511,11 +490,9 @@ pub fn index_of_any16(self_: &[u16], str: &'static [u16]) -> Option(str: &[T], chars: &'static [T]) -> Option { - // TODO(port): Zig specialized T==u8 → index_of_any (highway). Rust cannot - // dispatch on type identity without specialization; callers with u8 should - // call index_of_any directly. + // Rust cannot dispatch on type identity without specialization; + // callers with u8 should call index_of_any directly (highway-accelerated). for (i, c) in str.iter().enumerate() { - // PERF(port): was `inline for` over chars — profile if hot. for a in chars { if *c == *a { return Some(OptionalUsize::try_from(i).unwrap()); @@ -533,8 +510,7 @@ pub fn contains_comptime(self_: &[u8], str: &'static [u8]) -> bool { return false; }; let mut remain = &self_[start..]; - // PERF(port): Zig used a comptime-sized integer bitcast for the comparison. - // Use slice equality; LLVM should emit equivalent code for small fixed lengths. + // PERF: slice equality; LLVM should emit good code for small fixed lengths. while remain.len() >= str.len() { if &remain[..str.len()] == str { return true; @@ -572,7 +548,7 @@ pub fn in_map_case_insensitive( #[inline] pub fn contains_any(in_: &[&[u8]], target: &[u8]) -> bool { - // TODO(port): Zig accepted `anytype` and handled both `[]const u8` and `u8` elements. + // Callers pass slices — use `contains_char` for single-byte targets. for str in in_ { if contains(str, target) { return true; @@ -638,8 +614,6 @@ pub fn is_npm_package_name_ignore_length(target: &[u8]) -> bool { // Secret-redaction scanners are canonical in crate::strings (only callers // live in bun_core/fmt.rs). Re-exported here to preserve the bun.strings.* path. -// NOTE: starts_with_npm_secret now returns usize (was u8 in the Zig-literal port); -// no external callers depended on the narrow type. pub use crate::strings::{ find_url_password, is_uuid, starts_with_npm_secret, starts_with_secret, starts_with_uuid, }; @@ -648,7 +622,6 @@ pub const UUID_LEN: usize = 36; pub fn index_any_comptime(target: &[u8], chars: &'static [u8]) -> Option { for (i, &parent) in target.iter().enumerate() { - // PERF(port): was `inline for` — profile if hot. for &char in chars { if char == parent { return Some(i); @@ -660,7 +633,6 @@ pub fn index_any_comptime(target: &[u8], chars: &'static [u8]) -> Option pub fn index_any_comptime_t(target: &[T], chars: &'static [T]) -> Option { for (i, parent) in target.iter().enumerate() { - // PERF(port): was `inline for` — profile if hot. for char in chars { if *char == *parent { return Some(i); @@ -680,7 +652,7 @@ pub fn index_equal_any(in_: &[&[u8]], target: &[u8]) -> Option { } pub fn repeating_alloc(count: usize, char: u8) -> Result, AllocError> { - // PORT NOTE: allocator param dropped (global mimalloc). + // allocator param dropped (global mimalloc). Ok(vec![char; count].into_boxed_slice()) } @@ -698,8 +670,7 @@ pub fn index_of_char_neg(self_: &[u8], char: u8) -> i32 { } pub fn index_of_signed(self_: &[u8], str: &[u8]) -> i32 { - // std.mem.indexOf returns 0 for an empty needle; bun's `index_of` returns - // None. Match Zig semantics here (immutable.zig:412). + // bun's `index_of` returns None for an empty needle; this fn returns 0. if str.is_empty() { return 0; } @@ -743,7 +714,7 @@ pub fn last_index_of(self_: &[u8], str: &[u8]) -> Option { bstr::ByteSlice::rfind(self_, str) } -/// Generic `std.mem.lastIndexOf(T, haystack, needle)`. +/// Generic reverse substring search (last occurrence of `needle`). /// For `T = u8` prefer [`last_index_of`] (SIMD memmem). pub fn last_index_of_t(haystack: &[T], needle: &[T]) -> Option { if needle.is_empty() { @@ -773,8 +744,8 @@ pub fn index_of(self_: &[u8], str: &[u8]) -> Option { } pub fn index_of_t(haystack: &[T], needle: &[T]) -> Option { - // TODO(port): Zig specialized T==u8 → index_of (memmem). Callers with u8 - // should call index_of directly; generic path uses naive search. + // Callers with u8 should call index_of directly (memmem); + // this generic path uses naive search. if needle.is_empty() { return Some(0); } @@ -832,7 +803,7 @@ impl<'a> SplitIterator<'a> { } pub fn cat(first: &[u8], second: &[u8]) -> Result, AllocError> { - // PORT NOTE: allocator param dropped (global mimalloc). + // allocator param dropped (global mimalloc). let mut out = Vec::with_capacity(first.len() + second.len()); out.extend_from_slice(first); out.extend_from_slice(second); @@ -843,10 +814,9 @@ pub fn cat(first: &[u8], second: &[u8]) -> Result, AllocError> { // // PERF NOTE: `remainder_buf` is `MaybeUninit` because `init`/`init_lower_case` // only write `[0..len]` (or `[0..16]` for the slice case) and `slice()` only -// reads `[0..remainder_len]`. Zig leaves the tail `undefined`; the original -// Rust port zeroed `[0; 31]` on every call which showed up as ~0.45% of cycles -// in the next-lint profile (~6M calls × ~24B avg waste). Tail bytes have no -// validity requirement, so we leave them uninit to match Zig. +// reads `[0..remainder_len]`. Zeroing `[0; 31]` on every call showed up as +// ~0.45% of cycles in the next-lint profile (~6M calls × ~24B avg waste). +// Tail bytes have no validity requirement, so we leave them uninit. #[repr(C)] #[derive(Copy, Clone)] pub struct StringOrTinyString { @@ -906,9 +876,7 @@ impl StringOrTinyString { } } - // PORT NOTE: Zig deinit was a no-op (commented-out free). No Drop impl. - - // PORT NOTE: plain `#[inline]` (not `#[inline(always)]`). These are tiny + // plain `#[inline]` (not `#[inline(always)]`). These are tiny // generic delegators: a length check plus a tail call into the non-generic // `init`/`init_lower_case` or the `Appender` method. `#[inline]` lets the // small fast path fold into callers (and lets duplicate `A` instantiations @@ -993,7 +961,7 @@ impl StringOrTinyString { 1..=Self::MAX => { // Inline ASCII-lowercase loop (≤31 iters). Avoids forming `&mut [u8]` // over uninit storage that `copy_lowercase` would need; semantics are - // identical (Zig's copyLowercase only ASCII-lowercases). + // identical (`copy_lowercase` only ASCII-lowercases). let dst = buf.as_mut_ptr().cast::(); for (i, &c) in stringy.iter().enumerate() { // SAFETY: i < stringy.len() <= 31 == MAX. @@ -1030,7 +998,6 @@ impl StringOrTinyString { } /// Trait for the `Appender` parameter on `StringOrTinyString::init*_append_if_needed`. -/// Zig used `comptime Appender: type` + duck-typed `.append`/`.appendLowerCase`. pub trait Appender { fn append(&mut self, s: &[u8]) -> Result<&[u8], AllocError>; fn append_lower_case(&mut self, s: &[u8]) -> Result<&[u8], AllocError>; @@ -1186,7 +1153,6 @@ pub fn quoted_alloc(self_: &[u8]) -> Result, AllocError> { } pub fn eql_any_comptime(self_: &[u8], list: &'static [&'static [u8]]) -> bool { - // PERF(port): was `inline for` — profile if hot. for item in list { if eql_comptime_check_len_with_type::(self_, item) { return true; @@ -1198,8 +1164,7 @@ pub fn eql_any_comptime(self_: &[u8], list: &'static [&'static [u8]]) -> bool { /// Count the occurrences of a character in an ASCII byte array /// uses SIMD pub fn count_char(self_: &[u8], char: u8) -> usize { - // PERF(port): Zig used @Vector(16, u8) + @popCount + @reduce. Scalar - // count here; consider portable_simd or a highway intrinsic if hot. + // PERF: scalar count; consider portable_simd or a highway intrinsic if hot. let mut total: usize = 0; for &c in self_ { total += (c == char) as usize; @@ -1210,7 +1175,6 @@ pub fn count_char(self_: &[u8], char: u8) -> usize { pub fn ends_with_any_comptime(self_: &[u8], str: &'static [u8]) -> bool { if str.len() < 10 { let last = self_[self_.len() - 1]; - // PERF(port): was `inline for` — profile if hot. for &char in str { if char == last { return true; @@ -1230,7 +1194,7 @@ pub fn eql(self_: &[u8], other: &[u8]) -> bool { } pub fn eql_comptime_t(self_: &[T], alt: &'static [u8]) -> bool { - // TODO(port): Zig dispatched on T at comptime (u16 → eql_comptime_utf16). + // Branch on size_of (const-folded): 2-byte T → eql_comptime_utf16. if core::mem::size_of::() == 2 { // `NoUninit` + size_of::()==2 lets bytemuck prove the &[T]→&[u16] // reinterpret is sound (align checked at runtime; T is u16 in practice). @@ -1248,7 +1212,7 @@ pub fn eql_comptime(self_: &[u8], alt: &'static [u8]) -> bool { pub fn eql_comptime_utf16(self_: &[u16], alt: &[u8]) -> bool { // Compare bytewise, widening each ASCII byte of `alt` on the fly — avoids // materializing (and leaking) a `&'static [u16]`. All call sites pass - // ASCII literals (Zig was `comptime`). + // ASCII literals. debug_assert!(alt.iter().all(|&b| b < 0x80)); self_.len() == alt.len() && self_ @@ -1276,8 +1240,7 @@ pub fn has_prefix_comptime_utf16(self_: &[u16], alt: &'static [u8]) -> bool { } pub fn has_prefix_comptime_type(self_: &[T], alt: &'static [T]) -> bool { - // TODO(port): Zig accepted heterogeneous `alt: anytype` and widened u8→u16 via `w(alt)`. - // Rust callers must pass the correctly-typed literal (use `crate::string::w!` for u16). + // Callers must pass the correctly-typed literal (use `crate::string::w!` for u16). self_.len() >= alt.len() && eql_comptime_check_len_with_type::(&self_[0..alt.len()], alt) } @@ -1288,16 +1251,15 @@ pub fn has_suffix_comptime(self_: &[u8], alt: &'static [u8]) -> bool { } fn eql_comptime_check_len_u8(a: &[u8], b: &[u8], check_len: bool) -> bool { - // PERF(port): Zig unrolled at comptime over b.len in usize/u32/u16/u8 chunks. - // Rust cannot iterate a runtime slice at const-eval. Slice equality compiles - // to memcmp; for short literals LLVM should emit comparable code. + // Slice equality compiles to memcmp; for short literals LLVM emits + // unrolled fixed-size compares. if check_len { return a == b; } debug_assert!(a.len() >= b.len()); // SAFETY: when `check_len`, the early-return above gives `a.len()==b.len()`. - // When `!check_len`, callers guarantee `a.len() >= b.len()` (mirrors the - // Zig `eqlComptimeCheckLenU8` contract). LLVM cannot prove the latter, so + // When `!check_len`, callers guarantee `a.len() >= b.len()` (debug-asserted + // above). LLVM cannot prove the latter, so // a checked slice would emit a real bounds check on this hot path // (lexer keyword/prefix matching) — keep the unchecked index. unsafe { a.get_unchecked(..b.len()) == b } @@ -1314,7 +1276,7 @@ fn eql_comptime_check_len_with_known_type bool { - // PORT NOTE: Zig coerced array-by-value `b` to a pointer here. The Zig - // version's `comptime` literal is unenforceable in Rust, so accept any - // slice; callers are still expected to pass literals. + // Accepts any slice; callers are still expected to pass literals. eql_comptime_check_len_with_known_type::(a, b) } @@ -1334,16 +1294,15 @@ pub fn eql_case_insensitive_ascii_ignore_length(a: &[u8], b: &[u8]) -> bool { pub use crate::strings::eql_case_insensitive_ascii_check_length; -/// Preserves Zig's triple-`i` typo (`eqlCaseInsensitiveASCIIICheckLength`); both -/// spellings are reachable from ported call sites until the next typo sweep. +/// The triple-`i` typo spelling is kept deliberately; both spellings are +/// reachable from existing call sites until the next typo sweep. #[inline] pub fn eql_case_insensitive_asciii_check_length(a: &[u8], b: &[u8]) -> bool { eql_case_insensitive_ascii(a, b, true) } -// PORT NOTE: Zig's `comptime check_len: bool` was first ported as a const -// generic, but the dominant call shape across the tree passes it as a runtime -// 3rd arg (`eql_case_insensitive_ascii(a, b, true)`). Accept it at runtime — +// `check_len` is a runtime 3rd arg because that's the dominant call shape +// across the tree (`eql_case_insensitive_ascii(a, b, true)`) — // the branch is trivially predicted/inlined; callers wanting the // length-agnostic forms still have the `_check_length` / `_ignore_length` // wrappers above. @@ -1409,9 +1368,8 @@ pub fn eql_long_t(a_str: &[T], b_str: eql_long(reinterpret_to_u8(a_str), reinterpret_to_u8(b_str), false) } -// PORT NOTE: same rationale as `eql_case_insensitive_ascii` — Zig's -// `comptime check_len: bool` becomes a runtime 3rd arg to match the dominant -// ported call shape (`eql_long(a, b, true)`). +// same rationale as `eql_case_insensitive_ascii` — `check_len` is a runtime +// 3rd arg to match the dominant call shape (`eql_long(a, b, true)`). #[inline] pub fn eql_long(a_str: &[u8], b_str: &[u8], check_len: bool) -> bool { let len = b_str.len(); @@ -1427,8 +1385,9 @@ pub fn eql_long(a_str: &[u8], b_str: &[u8], check_len: bool) -> bool { debug_assert!(b_str.len() <= a_str.len()); } - // SAFETY: a_str.len() >= b_str.len() by contract above; raw-pointer walk - // mirrors Zig's word-chunked compare exactly. + // SAFETY: a_str.len() >= b_str.len() by contract above (checked when + // `check_len`, debug-asserted otherwise), so the word-chunked raw-pointer + // walk below never reads past either slice. unsafe { let end = b_str.as_ptr().add(len); let mut a = a_str.as_ptr(); @@ -1495,7 +1454,6 @@ pub fn append(self_: &[u8], other: &[u8]) -> Box<[u8]> { #[inline] pub fn concat_alloc_t(strs: &[&[T]]) -> Result, AllocError> { - // PORT NOTE: Zig took `strs: anytype` (tuple) and inline-for'd. Slice-of-slices here. let len: usize = strs.iter().map(|s| s.len()).sum(); let mut buf = Vec::with_capacity(len); for s in strs { @@ -1534,7 +1492,7 @@ pub fn substring(self_: &[u8], start: Option, stop: Option) -> &[u // (UTF16Replacement / utf16_codepoint{,_with_fffd} — deleted; re-exported from unicode_draft above) -/// `w!("foo")` → `&'static [u16]` UTF-16 literal (ASCII-only). Zig's `bun.w`. +/// `w!("foo")` → `&'static [u16]` UTF-16 literal (ASCII-only). `bun.w`. #[macro_export] macro_rules! w { ($s:literal) => {{ @@ -1544,7 +1502,10 @@ macro_rules! w { let mut out = [0u16; __N]; let mut i = 0; while i < __N { - debug_assert!(__B[i] < 0x80, "w! is ASCII-only"); + // Const-evaluated: a non-ASCII byte is a hard compile error in + // every profile (`to_utf16_literal!` forwards here, so this + // also keeps that alias from silently mis-encoding non-ASCII). + assert!(__B[i] < 0x80, "w! is ASCII-only"); out[i] = __B[i] as u16; i += 1; } @@ -1555,7 +1516,7 @@ macro_rules! w { } /// Index of first non-ASCII byte. Thin `u32` view over the canonical -/// `crate::strings_impl::first_non_ascii` (Zig spec `firstNonASCII -> ?u32`). +/// `crate::strings_impl::first_non_ascii`. /// NOTE: must call `strings_impl` directly — `crate::strings::first_non_ascii` /// re-exports *this* function (177f671a9046), so routing through it recurses. #[inline] @@ -1563,7 +1524,7 @@ pub fn first_non_ascii(slice: &[u8]) -> Option { crate::strings_impl::first_non_ascii(slice).map(|i| i as u32) } -/// `bun.strings.isValidUTF8` — SIMD-validated UTF-8 check (immutable.zig). +/// `bun.strings.isValidUTF8` — SIMD-validated UTF-8 check. /// Wraps `simdutf::validate::utf8`; the gated `unicode_draft` adds a /// `bun.FeatureFlags.use_simdutf` toggle + scalar fallback. #[inline] @@ -1616,8 +1577,8 @@ pub fn index_of_space_or_newline_or_non_ascii(slice_: &[u8], offset: u32) -> Opt } let i = highway::index_of_space_or_newline_or_non_ascii(remaining)?; - // PORT NOTE: Zig uses @truncate here (immutable.zig:1194); match wrapping semantics - // instead of try_from().unwrap() which would panic on >4GB inputs. + // Wrapping cast instead of try_from().unwrap(), which would panic on + // >4GB inputs. Some(i as u32 + offset) } @@ -1641,8 +1602,8 @@ pub fn index_of_newline_or_non_ascii_check_start( } let i = highway::index_of_newline_or_non_ascii(remaining)?; - // PORT NOTE: Zig uses @truncate here (immutable.zig:1212); match wrapping semantics - // instead of try_from().unwrap() which would panic on >4GB inputs. + // Wrapping cast instead of try_from().unwrap(), which would panic on + // >4GB inputs. Some(i as u32 + offset) } @@ -1684,8 +1645,7 @@ pub fn index_of_needs_url_encode(slice: &[u8]) -> Option { return Some(0); } - // PERF(port): Zig used @Vector(16,u8) compare + @ctz on bitmask. Scalar loop - // here; consider portable_simd or a highway entry point if hot. + // PERF: scalar loop; consider portable_simd or a highway entry point if hot. for (i, &char) in slice.iter().enumerate() { if char > 127 || char < 0x20 @@ -1700,7 +1660,7 @@ pub fn index_of_needs_url_encode(slice: &[u8]) -> Option { || char == b'|' || char == b'~' { - // PORT NOTE: Zig uses @truncate (immutable.zig:1292); match wrapping semantics. + // Wrapping cast. return Some(i as u32); } } @@ -1709,12 +1669,11 @@ pub fn index_of_needs_url_encode(slice: &[u8]) -> Option { } pub fn index_of_char_z(slice_z: &crate::string::ZStr, char: u8) -> Option { - // Zig returned ?u63; use u64 in Rust (no u63). highway::index_of_char(slice_z.as_bytes(), char).map(|i| i as u64) } pub fn index_of_char(slice: &[u8], char: u8) -> Option { - // PORT NOTE: Zig uses @truncate (immutable.zig:1304); match wrapping semantics. + // Wrapping cast. index_of_char_usize(slice, char).map(|i| i as u32) } @@ -1757,10 +1716,10 @@ pub fn index_of_not_char(slice: &[u8], char: u8) -> Option { return Some(0); } - // PERF(port): Zig used @Vector(16,u8) != splat + @ctz. Scalar loop here. + // PERF: scalar loop; consider a SIMD entry point if hot. for (i, ¤t) in slice.iter().enumerate() { if current != char { - // PORT NOTE: Zig uses @truncate (immutable.zig:1360); match wrapping semantics. + // Wrapping cast. return Some(i as u32); } } @@ -1964,7 +1923,7 @@ pub fn trim_suffix_comptime<'a>(buffer: &'a [u8], suffix: &'static [u8]) -> &'a } } -/// Non-comptime variants — runtime prefix/suffix may borrow from a non-static +/// Runtime variants — prefix/suffix may borrow from a non-static /// buffer (`hosted_git_info`, `npm-pack-args` parsers). #[inline] pub fn trim_prefix<'a>(buffer: &'a [u8], prefix: &[u8]) -> &'a [u8] { @@ -2007,12 +1966,11 @@ pub fn index_of_line_ranges( index_of_newline_or_non_ascii_check_start::(text, 0) else { if target_line == 0 { - // PERF(port): was assume_capacity let _ = ranges.push(LineRange { start: 0, - // PORT NOTE: Zig uses @truncate(text.len) (immutable.zig:1595); match wrapping semantics. + // Wrapping cast. end: text.len() as u32, - }); // OOM/capacity: Zig aborts; port keeps fire-and-forget + }); // OOM/capacity: fire-and-forget } return ranges; }; @@ -2048,13 +2006,13 @@ pub fn index_of_line_ranges( } let _ = ranges.push(LineRange { start: 0, - // PORT NOTE: Zig uses @truncate(text.len) (immutable.zig:1635); match wrapping semantics. + // Wrapping cast. end: text.len() as u32, }); return ranges; }; - let _ = ranges.push(first_newline_range); // OOM/capacity: Zig aborts; port keeps fire-and-forget + let _ = ranges.push(first_newline_range); // OOM/capacity: fire-and-forget if target_line == 0 { return ranges; @@ -2084,7 +2042,7 @@ pub fn index_of_line_ranges( start: prev_end, end: current_end, }; - prev_end = cursor.i; // Zig: `defer prev_end = cursor.i;` + prev_end = cursor.i; r } else { LineRange { @@ -2098,10 +2056,10 @@ pub fn index_of_line_ranges( if ranges.len() == LINE_RANGE_COUNT && current_line <= target_line { let mut new_ranges = BoundedArray::::default(); - let _ = new_ranges.extend_from_slice(&ranges.as_slice()[1..]); // OOM/capacity: Zig aborts; port keeps fire-and-forget + let _ = new_ranges.extend_from_slice(&ranges.as_slice()[1..]); // OOM/capacity: fire-and-forget ranges = new_ranges; } - let _ = ranges.push(current_line_range); // OOM/capacity: Zig aborts; port keeps fire-and-forget + let _ = ranges.push(current_line_range); // OOM/capacity: fire-and-forget if current_line >= target_line { return ranges; @@ -2112,7 +2070,7 @@ pub fn index_of_line_ranges( if ranges.len() == LINE_RANGE_COUNT && current_line <= target_line { let mut new_ranges = BoundedArray::::default(); - let _ = new_ranges.extend_from_slice(&ranges.as_slice()[1..]); // OOM/capacity: Zig aborts; port keeps fire-and-forget + let _ = new_ranges.extend_from_slice(&ranges.as_slice()[1..]); // OOM/capacity: fire-and-forget ranges = new_ranges; } @@ -2130,25 +2088,23 @@ pub fn get_lines_in_text( } let mut results = BoundedArray::<&[u8], LINE_RANGE_COUNT>::default(); for range in ranges.as_slice() { - let _ = results.push(&text[range.start as usize..range.end as usize]); // OOM/capacity: Zig aborts; port keeps fire-and-forget + let _ = results.push(&text[range.start as usize..range.end as usize]); // OOM/capacity: fire-and-forget } results.as_mut_slice().reverse(); Some(results) } pub fn first_non_ascii16(slice: &[u16]) -> Option { - // PERF(port): Zig used @Vector(8,u16) max-reduce + @ctz on bitmask. Scalar - // loop here; consider portable_simd or a simdutf utf16 validator if hot. + // PERF: scalar loop; consider portable_simd or a simdutf utf16 validator if hot. for (i, &char) in slice.iter().enumerate() { if char > 127 { - // PORT NOTE: Zig uses @truncate(i) (immutable.zig:1766); match wrapping semantics. + // Wrapping cast. return Some(i as u32); } } None } -// this is std.mem.trim except it doesn't forcibly change the slice to be const pub use crate::strings::trim; pub fn trim_spaces(slice: &[u8]) -> &[u8] { @@ -2181,7 +2137,6 @@ pub fn length_of_leading_whitespace_ascii(slice: &[u8]) -> usize { } pub fn join(slices: &[&[u8]], delimiter: &[u8]) -> Result, AllocError> { - // PORT NOTE: std.mem.join — reimplemented over Vec (no allocator param). if slices.is_empty() { return Ok(Box::default()); } @@ -2197,25 +2152,23 @@ pub fn join(slices: &[&[u8]], delimiter: &[u8]) -> Result, AllocError> } // ── Lexicographic slice ordering ────────────────────────────────────────── -// Canonical home for what Zig calls `std.mem.order`. The Zig tree had three -// hand-rolled copies (bun.strings.order, md.entity.orderStrings, -// ast.e.stringCompareForJavaScript); the Rust port keeps exactly one of each -// shape here. +// Canonical home for lexicographic slice ordering; exactly one copy of each +// shape lives here. /// Lexicographic byte-slice ordering (memcmp fast path). -/// Semantically identical to `<[u8] as Ord>::cmp` / Zig `std.mem.order(u8, a, b)`. +/// Semantically identical to `<[u8] as Ord>::cmp`. /// /// Delegates to `<[u8] as Ord>::cmp` rather than an extern `libc::memcmp` call: /// the std specialisation lowers to the `memcmp` LLVM builtin, so LLVM can -/// inline the short-string fast path and skip the PLT trampoline — matching -/// what Zig gets for `bun.c.memcmp` after LTO. `#[inline(always)]` because this +/// inline the short-string fast path and skip the PLT trampoline. +/// `#[inline(always)]` because this /// sits inside `sort_unstable_by` comparators on the install hot path. #[inline(always)] pub fn order(a: &[u8], b: &[u8]) -> Ordering { a.cmp(b) } -/// Generic lexicographic slice ordering — Zig `std.mem.order(T, a, b)`. +/// Generic lexicographic slice ordering. /// For `T = u8` prefer [`order`] (memcmp fast path). #[inline] pub fn order_t(a: &[T], b: &[T]) -> Ordering { @@ -2230,17 +2183,18 @@ pub fn cmp_strings_desc(_: (), a: &[u8], b: &[u8]) -> bool { order(a, b) == Ordering::Greater } -/// Every time you read a non^2 sized integer, Zig masks off the extra bits. -/// This is a meaningful performance difference, including in release builds. +/// `u8` rather than a narrower 3-bit integer type: masking off the extra bits +/// on every read is a meaningful performance difference, including in release +/// builds. pub type U3Fast = u8; pub fn sort_asc(in_: &mut [&[u8]]) { - // TODO: experiment with simd to see if it's faster + // Perf: a SIMD comparator might be faster here; never measured. in_.sort_unstable_by(|a, b| order(a, b)); } pub fn sort_desc(in_: &mut [&[u8]]) { - // TODO: experiment with simd to see if it's faster + // Perf: a SIMD comparator might be faster here; never measured. in_.sort_unstable_by(|a, b| order(b, a)); } @@ -2260,12 +2214,11 @@ impl<'a> StringArrayByIndexSorter<'a> { #[inline] pub fn to_ascii_hex_value(character: u8) -> u8 { - // Zig parity: bun.strings.toASCIIHexValue (precondition-based, no Option). + // Precondition-based (no Option). debug_assert!(character.is_ascii_hexdigit()); crate::fmt::hex_digit_value(character).expect("ascii hex digit") } -/// Zig: `fn NewLengthSorter(comptime Type, comptime field) type`. /// Rust cannot take a field name as a const param; use an accessor fn. pub struct LengthSorter &[u8]>(pub F, core::marker::PhantomData); impl &[u8]> LengthSorter { @@ -2320,10 +2273,9 @@ impl &[u8]> GlobLengthSorter { } } -/// Reflection adapter for [`move_all_slices`]. Zig's `moveAllSlices` used -/// `std.meta.fields(Type)` to enumerate every `[]const u8` field at comptime; +/// Reflection adapter for [`move_all_slices`]. /// Rust has no field reflection, so each container type hand-implements this -/// trait (or, once landed, `#[derive(MoveSlices)]`) to yield the same set of +/// trait (or, once landed, `#[derive(MoveSlices)]`) to yield its byte-slice /// fields as `&mut &'a [u8]` so they can be re-pointed into a new backing /// buffer of lifetime `'a` without any unsafe. pub trait MoveSlices<'a> { @@ -2332,8 +2284,7 @@ pub trait MoveSlices<'a> { } /// Update all `&[u8]` fields in `container` that currently point into `from` -/// to instead point at the same offset within `to`. Port of -/// `immutable.zig:moveAllSlices`. +/// to instead point at the same offset within `to`. pub fn move_all_slices<'a, T: MoveSlices<'a> + ?Sized>( container: &mut T, from: &[u8], @@ -2344,8 +2295,6 @@ pub fn move_all_slices<'a, T: MoveSlices<'a> + ?Sized>( container.for_each_byte_slice_field(&mut |field| { let slice_start = field.as_ptr() as usize; let slice_end = slice_start + field.len(); - // `if (@intFromPtr(from.ptr) + from.len) >= @intFromPtr(slice.ptr) + slice.len - // and (@intFromPtr(from.ptr) <= @intFromPtr(slice.ptr))` if from_end >= slice_end && from_start <= slice_start { *field = move_slice(field, from, to); } @@ -2379,7 +2328,7 @@ pub const UNICODE_REPLACEMENT: u32 = 0xFFFD; // UTF-8 encoding of U+FFFD pub const UNICODE_REPLACEMENT_STR: [u8; 3] = [0xEF, 0xBF, 0xBD]; -// Spec (immutable.zig:1990, 2003) calls `bun.c_ares.ares_inet_pton`, the vendored +// Uses `ares_inet_pton`, the vendored // c-ares implementation. Do NOT call the system `inet_pton` here: on Windows that // resolves into ws2_32.dll and fails with WSANOTINITIALISED whenever it runs before // `WSAStartup()`, which URL/host parsing can. c-ares' impl is pure C, no preconditions. @@ -2506,7 +2455,7 @@ pub fn concat_if_needed( } if total_length < 1024 { - // PERF(port): was stack-fallback allocator. Use a fixed stack buffer. + // Use a fixed stack buffer. let mut stack_buf = [0u8; 1024]; let mut off: usize = 0; for arg in args { @@ -2516,9 +2465,8 @@ pub fn concat_if_needed( let stack_copy = &stack_buf[0..total_length]; for &interned in interned_strings_to_check { if eql_long(stack_copy, interned, true) { - // PERF(port): Zig stored the interned slice directly; with an - // owned `Box<[u8]>` dest we copy once. Hit at most once per - // JSX config; no leak. + // PERF: with an owned `Box<[u8]>` dest we copy once. + // Hit at most once per JSX config; no leak. *dest = Box::from(interned); return Ok(()); } @@ -2529,7 +2477,7 @@ pub fn concat_if_needed( let mut remain: &[u8] = dest; for arg in args { - // PORT NOTE: Zig has `args.len` here (likely a bug); preserved verbatim. + // `args.len` (not `arg.len`) is likely a bug; preserved verbatim. if args.len() > remain.len() { break 'brk true; } @@ -2591,7 +2539,7 @@ impl Default for QuoteEscapeFormatFlags { } /// usage: print(" string: '{}' ", format_escapes_js("hello'world!")); -// PERF(port): was comptime monomorphization (Zig `comptime flags: QuoteEscapeFormatFlags`) — profile if hot. +// PERF: `flags` is a runtime value (not monomorphized) — profile if hot. pub fn format_escapes(str: &[u8], flags: QuoteEscapeFormatFlags) -> QuoteEscapeFormat<'_> { QuoteEscapeFormat { data: str, flags } } @@ -2603,7 +2551,7 @@ pub struct QuoteEscapeFormat<'a> { impl core::fmt::Display for QuoteEscapeFormat<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - // PERF(port): Zig wrote directly to the writer; here we buffer through + // PERF: buffer through // a Vec so `write_pre_quoted_string`'s `PrinterWriter` bound is met // without an adapter for `core::fmt::Formatter`. Profile if hot. let mut buf: Vec = Vec::with_capacity(self.data.len() + 8); @@ -2611,7 +2559,7 @@ impl core::fmt::Display for QuoteEscapeFormat<'_> { self.data, &mut buf, self.flags.quote_char, - // Zig (immutable.zig:2159) hardcodes `false` here regardless of + // Hardcoded `false` regardless of // `flags.ascii_only`; the field is dead in QuoteEscapeFormat. false, self.flags.json, @@ -2626,7 +2574,7 @@ impl core::fmt::Display for QuoteEscapeFormat<'_> { /// Generic. Works on &[u8], &[u16], etc #[inline] pub fn index_of_scalar(input: &[T], scalar: T) -> Option { - // TODO(port): Zig specialized T==u8 → index_of_char_usize (highway). + // Branch on size_of (const-folded): byte-sized T → index_of_char_usize (highway). if core::mem::size_of::() == 1 { let scalar_u8 = reinterpret_to_u8(core::slice::from_ref(&scalar))[0]; return index_of_char_usize(reinterpret_to_u8(input), scalar_u8); @@ -2733,7 +2681,6 @@ pub fn percent_encode_write( writer.reserve(safe.len() + b"%FF".len() * code_point_len); // Write the safe bytes - // PERF(port): was assume_capacity writer.extend_from_slice(safe); // URL encode the code point @@ -2750,12 +2697,10 @@ pub fn percent_encode_write( // ───────────── re-exports from sibling modules ───────────── -// Unicode core is re-exported at the top of the file; the remaining submodule -// re-exports below are still incomplete. +// Unicode core is re-exported at the top of the file. Further transcoding +// helpers (unicode_draft) and path helpers (bun_paths) are re-exported on +// demand as callers need them — see the `crate::strings` re-export block below. pub use crate::string::escape_reg_exp::{escape_reg_exp, escape_reg_exp_for_package_name_matching}; -// TODO(port): re-export the rest of the transcoding suite from unicode_draft — -// to_utf8_alloc / to_utf16_alloc / convert_* / copy_*_into_* / EncodeIntoResult / BOM / etc. -// TODO(port): re-export paths::{to_w_path, basename, add_nt_path_prefix, ...} crate::declare_scope!(STR, hidden); // `log` is `bun.Output.scoped(.STR, .hidden)` — use `crate::scoped_log!(STR, ...)`. @@ -2828,7 +2773,6 @@ impl ANSIIterator { } } -// TODO(port): move to _sys unsafe extern "C" { // `&mut ANSIIterator` is ABI-identical to the C++ `ANSIIterator*` (thin // non-null pointer to a `#[repr(C)]` POD struct); C++ reads `input`/ @@ -2855,23 +2799,22 @@ pub fn to_utf8_alloc_with_type(utf16: &[u16]) -> Vec { // ───────────── minimal real impls of submodule fns ───────────── // These mirror the same-named fns in `unicode_draft` so dependents can link // against `bun_core::strings::*` directly. Each is a thin wrapper over simdutf -// or the scalar logic from the .zig source. +// or a scalar fallback. pub use crate::strings_impl::utf8_byte_sequence_length; -/// `std.mem.trimLeft(u8, str, chars)` — strip leading chars in `values_to_strip`. +/// Strip leading chars in `values_to_strip`. pub use crate::strings::trim_left; -/// `std.mem.trimRight(u8, str, chars)` — strip trailing chars in `values_to_strip`. +/// Strip trailing chars in `values_to_strip`. pub use crate::strings::trim_right; pub use crate::strings::{replace, replace_owned, replacement_size}; -// `std.fmt.parseInt` — moved down to crate::fmt; re-exported for back-compat. +// Defined in crate::fmt; re-exported here for back-compat. pub use crate::fmt::{ParseIntError, parse_int}; -/// Compare a UTF-16 string against a UTF-8 string without allocating -/// (`unicode.zig:utf16EqlString`). +/// Compare a UTF-16 string against a UTF-8 string without allocating. pub fn utf16_eql_string(text: &[u16], str: &[u8]) -> bool { if text.len() > str.len() { // UTF-16 encoding can never be longer than the UTF-8 encoding. @@ -2882,8 +2825,8 @@ pub fn utf16_eql_string(text: &[u16], str: &[u8]) -> bool { let mut j: usize = 0; let mut i: usize = 0; while i < n { - // Fixes the `|`-precedence bug ported from unicode.zig:1839 — supplementary - // code points >= U+20000 mis-decoded with the old open-coded math. + // `decode_wtf16_raw` avoids the `|`-precedence bug of the old + // open-coded math, which mis-decoded supplementary code points >= U+20000. let (cp, adv) = crate::strings::decode_wtf16_raw(&text[i..]); i += adv as usize; let width = encode_wtf8_rune(&mut temp, cp); @@ -2900,7 +2843,7 @@ pub fn utf16_eql_string(text: &[u16], str: &[u8]) -> bool { /// `strings.toUTF16AllocForReal` — like [`to_utf16_alloc`] but **always** /// returns a `Vec` (pure-ASCII inputs are widened 1:1 instead of -/// returning `None`). Port of `unicode.zig:toUTF16AllocForReal`. +/// returning `None`). pub fn to_utf16_alloc_for_real( bytes: &[u8], fail_if_invalid: bool, @@ -2918,7 +2861,7 @@ pub fn to_utf16_alloc_for_real( Ok(out) } -/// `withoutPrefix` (runtime) — strip `prefix` from `input` if present. +/// Strip `prefix` from `input` if present. /// Unlike `without_prefix_comptime`, this accepts a non-`'static` prefix. #[inline] pub fn without_prefix<'a>(input: &'a [u8], prefix: &[u8]) -> &'a [u8] { @@ -2929,26 +2872,23 @@ pub fn without_prefix<'a>(input: &'a [u8], prefix: &[u8]) -> &'a [u8] { } } -// Zig: `pub const withoutTrailingSlash = paths_.withoutTrailingSlash;` -// (immutable.zig:2380). The full `paths` submodule now lives in +// The full `paths` submodule lives in // `bun_paths::string_paths` (it depends upward on `bun_paths` resolve/pool // helpers and would cycle here). Callers reach the Windows path-shape // helpers (`to_nt_path` / `to_kernel32_path` / `from_w_path` / …) via // `bun_paths::strings::*`; this module keeps only the re-export of the // scalar `without_trailing_slash` already defined in `crate::strings`. pub use crate::strings_impl::{remove_leading_dot_slash, without_trailing_slash}; -// Zig: `pub const convertUTF16ToUTF8InBuffer = unicode.convertUTF16ToUTF8InBuffer;` -// (immutable.zig). Re-export the bun_core implementation so callers can spell +// Re-export the bun_core implementation so callers can spell // `strings::convert_utf16_to_utf8_in_buffer` without reaching into `unicode`. pub use crate::strings::convert_utf16_to_utf8_in_buffer; -// Zig: `pub const convertUTF8toUTF16InBufferZ = unicode.convertUTF8toUTF16InBufferZ;` -// — re-export the NUL-terminated variant so callers can spell +// Re-export the NUL-terminated variant so callers can spell // `strings::convert_utf8_to_utf16_in_buffer_z` (used by the Windows profilers // to widen output paths for `File::write_file_os_path`). pub use unicode_draft::convert_utf8_to_utf16_in_buffer_z; /// `strings.startsWithWindowsDriveLetterT` — true for `[A-Za-z]:` prefix -/// followed by at least one more byte (Zig: `s.len > 2`). +/// followed by at least one more byte (`s.len() > 2`). #[inline] pub fn starts_with_windows_drive_letter_t>(s: &[T]) -> bool { s.len() > 2 && s[1].into() == u32::from(b':') && { @@ -2962,7 +2902,7 @@ pub fn starts_with_windows_drive_letter_t>(s: &[T]) -> bool /// buffer (capacity ≥ `input.len()` u16). SIMD fast path via simdutf; on invalid /// UTF-8 falls back to a scalar WTF-8 decoder that emits U+FFFD for malformed /// bytes and passes unpaired surrogates through (so non-empty input never yields -/// an empty slice — fixes #8197 / the TODO at unicode.zig:1537). +/// an empty slice — fixes #8197). /// /// Panics when the output does not fit. Callers that cannot statically size /// `buf` for the worst case must use [`try_convert_utf8_to_utf16_in_buffer`]. @@ -3072,24 +3012,21 @@ fn decode_wtf8_one(s: &[u8]) -> (u32, usize) { } /// `strings.toUTF8ListWithType` — append UTF-8 transcoding of `utf16` onto -/// `list` and return the (possibly-reallocated) list. Port of -/// `unicode.zig:toUTF8ListWithType` (always uses simdutf path; Bun is built -/// with `FeatureFlags.use_simdutf = true`). +/// `list` and return the (possibly-reallocated) list. Always uses the simdutf +/// path; Bun is built with `FeatureFlags.use_simdutf = true`. pub fn to_utf8_list_with_type(mut list: Vec, utf16: &[u16]) -> Result, AllocError> { if utf16.is_empty() { return Ok(list); } - // Zig: `list.ensureTotalCapacityPrecise(length + 16)` then `convertUTF16ToUTF8`. // `convert_utf16_to_utf8_append` writes directly into `spare_capacity_mut()` and // requires the caller to pre-reserve (its doc says so explicitly); without this // reserve a fresh `Vec::new()` has a dangling `0x1` spare pointer and simdutf - // segfaults writing to it. The +16 padding mirrors Zig's SIMD over-read slack. + // segfaults writing to it. The +16 padding gives SIMD over-read slack. let length = simdutf::length::utf8::from::utf16::le(utf16); list.try_reserve(length + 16).map_err(|_| AllocError)?; - // PORT NOTE: Zig's path validates UTF-16 first then falls back to a manual - // loop on failure (`toUTF8ListWithTypeBun`). Here we route through - // `crate::strings::convert_utf16_to_utf8_append`, which already replaces - // unpaired surrogates with U+FFFD — semantically equivalent. + // Route through + // `crate::strings::convert_utf16_to_utf8_append`, which replaces + // unpaired surrogates with U+FFFD. crate::strings::convert_utf16_to_utf8_append(&mut list, utf16); Ok(list) } @@ -3113,7 +3050,7 @@ impl From for crate::Error { /// any non-ASCII byte; pure-ASCII inputs return `Ok(None)` (caller keeps the /// 8-bit form). When `fail_if_invalid` is set, invalid UTF-8 yields /// `Err(InvalidByteSequence)`; otherwise invalid sequences are replaced with -/// U+FFFD (per `unicode.zig:toUTF16Alloc`). When `sentinel` is set the result +/// U+FFFD. When `sentinel` is set the result /// includes a trailing 0 u16. pub fn to_utf16_alloc( bytes: &[u8], @@ -3164,9 +3101,9 @@ pub fn to_utf16_alloc( // Copy ASCII prefix as-is (one u16 per byte). out.extend(remaining[..i].iter().map(|&b| u16::from(b))); remaining = &remaining[i..]; - // Decode one codepoint via the same routine Zig uses - // (`convertUTF8BytesIntoUTF16`) so the number/position of U+FFFD - // emissions matches: advance by `replacement.len.max(1)`, not 1. + // Decode one codepoint via `convert_utf8_bytes_into_utf16` so the + // number/position of U+FFFD emissions stays consistent: advance by + // `replacement.len.max(1)`, not 1. let replacement = unicode_draft::convert_utf8_bytes_into_utf16(remaining); remaining = &remaining[(replacement.len as usize).max(1)..]; push_codepoint_utf16(&mut out, replacement.code_point); @@ -3181,7 +3118,7 @@ pub fn to_utf16_alloc( /// `PATTERN_KEY_COMPARE` from the Node.js ESM resolution spec — the comparator /// behind `NewGlobLengthSorter`. Returns an [`Ordering`] suitable for /// `slice.sort_by(|a, b| glob_length_compare(a, b))` to sort in **descending -/// order of specificity** (matches Zig `lessThan` returning `true` ⇒ `Less`). +/// order of specificity**. pub fn glob_length_compare(key_a: &[u8], key_b: &[u8]) -> Ordering { let star_a = index_of_char(key_a, b'*'); let star_b = index_of_char(key_b, b'*'); @@ -3224,5 +3161,3 @@ mod tests { assert!(!super::eql_case_insensitive_ascii(b"Ab", b"a", true)); } } - -// ported from: src/string/immutable.zig diff --git a/src/bun_core/string/immutable/exact_size_matcher.rs b/src/bun_core/string/immutable/exact_size_matcher.rs index b1f31b44e25..3743245d0c0 100644 --- a/src/bun_core/string/immutable/exact_size_matcher.rs +++ b/src/bun_core/string/immutable/exact_size_matcher.rs @@ -1,5 +1,3 @@ -//! Port of `src/string/immutable/exact_size_matcher.zig`. -//! //! `ExactSizeMatcher(N)` packs a short byte slice (len ≤ N) into a single //! unsigned integer so callers can `match`/`switch` on string literals as //! integer constants. Slices longer than N map to `T::MAX` (the "no match" @@ -8,23 +6,19 @@ use core::marker::PhantomData; /// Zero-sized type carrying the `MAX_BYTES` const parameter. -/// -/// Zig: `pub fn ExactSizeMatcher(comptime max_bytes: usize) type { return struct { ... } }` pub struct ExactSizeMatcher(PhantomData<[u8; MAX_BYTES]>); -// Compile-time check mirroring Zig's `switch (max_bytes) { 1,2,4,8,12,16 => {}, else => @compileError }`. -// In Rust this is enforced by only providing `ExactSizeInt` impls for the valid sizes; +// Only the valid sizes (1, 2, 4, 8, 12, 16) get `ExactSizeInt` impls; // any other `MAX_BYTES` fails to satisfy the trait bound at the call site. -/// Maps a `MAX_BYTES` value to its backing unsigned integer type -/// (`std.meta.Int(.unsigned, max_bytes * 8)` in Zig) and provides the -/// little-endian read primitive. +/// Maps a `MAX_BYTES` value to its backing unsigned integer type and provides +/// the little-endian read primitive. pub trait ExactSizeInt { /// The packed integer type (`u8`/`u16`/`u32`/`u64`/`u128`). type T: Copy + Eq + Ord; const ZERO: Self::T; const MAX: Self::T; - /// `std.mem.readInt(T, &buf, .little)` + /// Read `Self::T` from `buf` as little-endian. fn read_le(buf: &[u8; MAX_BYTES]) -> Self::T; } @@ -48,9 +42,9 @@ impl_exact_size_int!(4, u32); impl_exact_size_int!(8, u64); impl_exact_size_int!(16, u128); -// TODO(port): Zig uses `u96` for MAX_BYTES=12; Rust has no native `u96`. -// We back it with `u128` and zero-pad the high 4 bytes. `MAX` is the true -// 96-bit max so the "too long" sentinel is distinct from any 12-byte payload. +// MAX_BYTES=12 would want a `u96`; Rust has no native `u96`, so we back it +// with `u128` and zero-pad the high 4 bytes. `MAX` is the true 96-bit max so +// the "too long" sentinel is distinct from any 12-byte payload. impl ExactSizeInt<12> for ExactSizeMatcher<12> { type T = u128; const ZERO: u128 = 0; @@ -67,8 +61,6 @@ impl ExactSizeMatcher where Self: ExactSizeInt, { - /// Zig: `pub fn match(str: anytype) T` - /// /// `r#match` because `match` is a Rust keyword. #[inline] pub fn r#match(str: &[u8]) -> >::T { @@ -90,16 +82,11 @@ where } } - /// Zig: `pub fn case(comptime str: []const u8) T` - /// - /// Runtime variant. For const-position (Zig's comptime use in `switch` - /// arms), use the [`exact_case!`] macro below — `const fn` cannot call - /// the non-const trait method `read_le` on stable. + /// Runtime variant. For const-position use (e.g. in `match` arms), use + /// the [`exact_case!`] macro below — `const fn` cannot call the non-const + /// trait method `read_le` on stable. #[inline(always)] pub fn case(str: &'static [u8]) -> >::T { - // if (str.len < max_bytes) { zero-pad } else if (== max_bytes) { read } else { @compileError } - // PORT NOTE: reshaped — Zig branches on `<` vs `==` vs `@compileError`; - // here we assert `<=` (the compile error) and unify the two valid arms. assert!( str.len() <= MAX_BYTES, "str too long for ExactSizeMatcher::case" @@ -160,5 +147,3 @@ pub(crate) const fn __pad(s: &[u8]) -> [u8; N] { } out } - -// ported from: src/string/immutable/exact_size_matcher.zig diff --git a/src/bun_core/string/immutable/unicode.rs b/src/bun_core/string/immutable/unicode.rs index fffb2df1b99..2ee95cada74 100644 --- a/src/bun_core/string/immutable/unicode.rs +++ b/src/bun_core/string/immutable/unicode.rs @@ -1,5 +1,3 @@ -//! Port of `src/string/immutable/unicode.zig`. - use crate::string::WStr; use crate::string::immutable::{ U3Fast, UNICODE_REPLACEMENT as unicode_replacement, eql_comptime_ignore_len as eql_ignore_len, @@ -44,7 +42,7 @@ fn append_u16_as_u8(dst: &mut Vec, src: &[u16]) { copy_u16_into_u8(unsafe { crate::vec::writable_slice(dst, src.len()) }, src); } -// ───── canonical WTF-8 single-rune decode (Zig: unicode.zig decodeWTF8RuneT) ───── +// ───── canonical WTF-8 single-rune decode ───── // Lives in `bun_core::string::immutable::unicode_draft` (this file), re-exported // through the inline `pub mod unicode` in immutable.rs and onward as // `bun_core::strings::{decode_wtf8_rune_t, decode_wtf8_rune_t_multibyte, codepoint_size}`. @@ -53,11 +51,11 @@ fn append_u16_as_u8(dst: &mut Vec, src: &[u16]) { // instead of carrying a second body, and so md/glob/parsers can call directly. /// Integer types usable as the codepoint result of [`decode_wtf8_rune_t`]. -/// Sealed to the two instantiations Zig uses (`i32` aka `CodePoint`, and `u32`); +/// Sealed to two instantiations (`i32` aka `CodePoint`, and `u32`); /// every caller in-tree is one of these. `ZERO_VALUE` is the per-type sentinel -/// Zig threaded as a `comptime_int` (`-1` for i32, `0` for u32). +/// (`-1` for i32, `0` for u32). /// -/// PORT NOTE: bounds widened from `From` to include the bit-ops needed by +/// Bounds widened from `From` to include the bit-ops needed by /// `decode_wtf8_rune_t_multibyte` plus a `from_u32` constructor (folds in the /// previously separate `FromU32`/`FromU32Const` helper traits). pub trait CodePointZero: @@ -133,8 +131,7 @@ pub use crate::strings::codepoint_size; // contract). /// Returns `Some(u16)` (the trailing lead surrogate) when `SKIP_TRAILING_REPLACEMENT` and a -/// dangling lead surrogate is at the end; otherwise `None`. When `SKIP_TRAILING_REPLACEMENT` is -/// false the Zig version returned the list by value — in Rust the caller already owns `list`. +/// dangling lead surrogate is at the end; otherwise `None`. pub fn to_utf8_list_with_type_bun( list: &mut Vec, utf16: &[u16], @@ -208,7 +205,7 @@ pub fn allocate_latin1_into_utf8(latin1_: &[u8]) -> Result, AllocError> } // ─── CANONICAL: UTF-16 codepoint decode ───────────────────────────────────── -// Faithful port of unicode.zig:569 / :1321 / :1325 / :1361. Re-exported from +// Re-exported from // immutable.rs as `strings::{utf16_codepoint, utf16_codepoint_with_fffd, // UTF16Replacement}`; the parallel Utf16CodepointLen + duplicate struct/fns // that lived in immutable.rs are deleted in favour of this single source of @@ -216,7 +213,7 @@ pub fn allocate_latin1_into_utf8(latin1_: &[u8]) -> Result, AllocError> // TextEncoderStreamEncoder.rs ×1) resolve through the // `pub use unicode_draft::{…}` re-export with no source change. -/// `strings.UTF16Replacement` (unicode.zig:569) — decoded UTF-16 codepoint +/// `strings.UTF16Replacement` — decoded UTF-16 codepoint /// with surrogate metadata. #[derive(Clone, Copy)] pub struct UTF16Replacement { @@ -494,8 +491,6 @@ pub fn copy_u8_into_u16(output_: &mut [u16], input_: &[u8]) { let input = input_; debug_assert!(input.len() <= output.len()); - // https://zig.godbolt.org/z/9rTn1orcY - let n = input.len().min(output.len()); for i in 0..n { output[i] = u16::from(input[i]); @@ -530,10 +525,9 @@ pub fn copy_latin1_into_ascii(dest: &mut [u8], src: &[u8]) { if to.len() >= 16 && crate::Environment::ENABLE_SIMD { const VECTOR_SIZE: usize = 16; - // https://zig.godbolt.org/z/qezsY8T3W let remain_in_u64_len = remain.len() - (remain.len() % VECTOR_SIZE); let to_in_u64_len = to.len() - (to.len() % VECTOR_SIZE); - // PORT NOTE: reshaped for borrowck — operate on byte indices instead of bytesAsSlice(u64). + // Reshaped for borrowck — operate on byte indices instead of bytesAsSlice(u64). let end_vector_len = (remain_in_u64_len / 8).min(to_in_u64_len / 8); let mut idx = 0usize; // using the pointer instead of the length is super important for the codegen @@ -638,9 +632,8 @@ impl BOM { } BOM::Utf16Le => { // `trimmed_bytes` is `&[u8]` at offset 2 of a `Vec`; alignment is - // not guaranteed ≥ 2, so casting to `&[u16]` (the Zig `@alignCast` - // port) is UB. Route through the byte-level helper which copies into - // an aligned `Vec` first. + // not guaranteed ≥ 2, so casting to `&[u16]` is UB. Route through + // the byte-level helper which copies into an aligned `Vec` first. let trimmed_bytes = &bytes[Self::UTF16_LE_BYTES.len()..]; let out = crate::strings::to_utf8_alloc_from_le_bytes(trimmed_bytes); drop(bytes); @@ -654,7 +647,7 @@ impl BOM { } } - /// This is required for fs.zig's `use_shared_buffer` flag. we cannot free that pointer. + /// This is required for the fs `use_shared_buffer` flag. we cannot free that pointer. /// The returned slice will always point to the base of the input. /// /// Requires an arraylist in case it must be grown. @@ -664,7 +657,6 @@ impl BOM { let n = Self::UTF8_BYTES.len(); let len = list.len(); list.copy_within(n.., 0); - // PORT NOTE: Zig returned a subslice without truncating; we mirror by returning a slice. &list[..len - n] } BOM::Utf16Le => { @@ -675,7 +667,7 @@ impl BOM { ); list.clear(); list.extend_from_slice(&out); - // TODO(port): Zig returned `out` (the new alloc); returning list slice instead to honor + // Return the list slice (not `out`, the new alloc) to honor the // "always points to the base of the input" doc comment. &list[..] } @@ -711,9 +703,6 @@ pub enum ToUTF16Error { crate::oom_from_alloc!(ToUTF16Error); -// TODO(port): move to *_jsc — `TestingAPIs` re-exported from bun_string_jsc. -// pub const TestingAPIs = @import("../../jsc/bun_string_jsc.zig").UnicodeTestingAPIs; - pub fn to_utf16_alloc_maybe_buffered( bytes: &[u8], ) -> Result, [u8; 3], u8)>, ToUTF16Error> { @@ -810,7 +799,7 @@ pub fn to_utf16_alloc_maybe_buffered UTF16Replacement { ..Default::default() }; } - // PORT NOTE (unicode.zig:1378): Zig falls through with len=2 even when input[1] - // is not a trail surrogate; preserve that iteration behaviour. + // Falls through with len=2 even when input[1] is not a trail + // surrogate; callers depend on that iteration behaviour. UTF16Replacement { len: 2, code_point: u16_get_supplementary(c0, input[1]), @@ -912,7 +901,7 @@ pub fn utf16_codepoint(input: &[u16]) -> UTF16Replacement { } } -/// Zig: `pub fn toUTF16Literal(comptime str) [:0]const u16` → use `$crate::w!("...")` macro. +/// Alias for the `$crate::w!("...")` macro. #[macro_export] macro_rules! to_utf16_literal { ($s:literal) => { @@ -920,9 +909,8 @@ macro_rules! to_utf16_literal { }; } -/// Zig: `pub fn literal(comptime T, comptime str) *const [N:0]T`. -/// In Rust: `b"..."` for u8, `$crate::w!("...")` for u16. No runtime fn possible. -// TODO(port): callers should use byte/wide literals directly; this is a stub for diff parity. +/// `b"..."` for u8, `$crate::w!("...")` for u16. +/// New callers should use byte/wide literals directly. #[macro_export] macro_rules! literal { (u8, $s:literal) => { @@ -933,8 +921,6 @@ macro_rules! literal { }; } -// `literalLength` is comptime-only and folded into the macros above. - pub(super) use crate::strings::push_codepoint_utf16; // `unreachable_pub`: these are re-exported externally via the parent's @@ -1154,8 +1140,7 @@ pub(super) fn copy_utf16_into_utf8_with_buffer_impl usize { utf8_remaining = &utf8_remaining[i..]; - // PORT NOTE: Zig calls `utf16Codepoint` (which takes []const u16) on a []const u8 here — - // preserved as a TODO; this branch is dead when use_simdutf is true. - // TODO(port): dead non-simdutf path passes wrong slice type in Zig source + // Deliberate quirk: `utf16_codepoint` (which takes u16 units) is applied to + // raw u8 bytes here. This branch is dead when USE_SIMDUTF is true. let replacement = { let n = utf8_remaining.len() / 2; if n == 0 { @@ -1298,5 +1282,3 @@ pub fn decode_wtf8_rune_t_multibyte(p: [u8; 4], len: U3Fast, z } // `from_u32_const` folded into `CodePointZero` trait above. - -// ported from: src/string/immutable/unicode.zig diff --git a/src/bun_core/string/mod.rs b/src/bun_core/string/mod.rs index acfb82a0429..2d982725cda 100644 --- a/src/bun_core/string/mod.rs +++ b/src/bun_core/string/mod.rs @@ -1,10 +1,10 @@ -//! `bun_core::string` (formerly the `bun_string` crate) — port of -//! `src/string/string.zig` (`bun.String` and friends). +//! `bun_core::string` (formerly the `bun_string` crate) — `bun.String` and +//! friends. //! //! `String` is the FFI-compatible 5-variant tagged union shared with C++ //! (`BunString` in `src/jsc/bindings/BunString.cpp`). `ZigString` is the //! pointer-tagged borrowed view; `ZigStringSlice` is the owned-or-borrowed -//! UTF-8 byte slice that replaces Zig's allocator-vtable trick. +//! UTF-8 byte slice. //! //! Merged into `bun_core` to break the `bun_core ↔ bun_string` dep edge; //! the `bun_string` crate is now a thin re-export shim over this module. @@ -37,7 +37,7 @@ pub use write::Write; #[path = "immutable.rs"] pub mod immutable; -// Unicode ID-Start/ID-Continue two-stage tables (`js_lexer/identifier_data.zig`). +// Unicode ID-Start/ID-Continue two-stage tables. // Pure data with no upward deps; hosted here so [`lexer`], [`mutable_string`], // and [`immutable::unicode`] get full Unicode coverage without depending on // `bun_js_parser`. `bun_js_parser::lexer::identifier` re-exports this module. @@ -49,8 +49,7 @@ pub use wtf::{WTFStringImpl, WTFStringImplExt, WTFStringImplStruct}; // ────────────────────────────────────────────────────────────────────────── // `bun.String` — 5-variant tagged WTFString-or-ZigString. extern layout -// must match Zig `extern struct { tag: Tag, value: StringImpl }` (= C++ -// `BunString` in BunString.cpp), 24 bytes on 64-bit. +// must match the C++ `BunString` in BunString.cpp, 24 bytes on 64-bit. // ────────────────────────────────────────────────────────────────────────── // Canonical layout lives in `bun_alloc` (T0 TYPE_ONLY landing for // `bun.String`); re-exported so existing `bun_core::{Tag, StringImpl}` paths @@ -172,8 +171,7 @@ impl String { unsafe { self.0.value.wtf_string_impl } } - /// `bun.String.init(anytype)` — polymorphic borrow constructor - /// (string.zig:331). Mirrors the Zig `switch (@TypeOf(value))` table via + /// `bun.String.init` — polymorphic borrow constructor, expressed via the /// `Into` impls below: `String` is identity, `ZigString` is wrapped, /// byte/str slices go through `ZigString::from_bytes`. #[inline] @@ -198,11 +196,10 @@ impl String { /// `bun.String.static` — `'static` slice; converted to JS via /// `WTF::ExternalStringImpl` without copying. Generic over `str`/`[u8]` - /// so call sites may pass either `"lit"` or `b"lit"` (Zig's `[:0]const u8` - /// literal maps to both in ported code). + /// so call sites may pass either `"lit"` or `b"lit"`. #[inline] pub fn static_>(s: &'static S) -> Self { - // Zig: ZigString.init(input) — no UTF-8 mark on the static path. + // No UTF-8 mark on the static path. Self(bun_alloc::String { tag: Tag::StaticZigString, value: StringImpl { @@ -233,7 +230,7 @@ impl String { // SAFETY: s.as_ptr()/len describe a valid byte slice. unsafe { BunString__fromLatin1(s.as_ptr(), s.len()) } } - /// `bun.String.cloneUTF16` — narrows to Latin-1 if all-ASCII (string.zig:207). + /// `bun.String.cloneUTF16` — narrows to Latin-1 if all-ASCII. pub fn clone_utf16(s: &[u16]) -> Self { if s.is_empty() { return Self::EMPTY; @@ -252,7 +249,7 @@ impl String { unsafe { BunString__createAtom(s.as_ptr(), s.len()) } } /// `bun.String.tryCreateAtom` — `None` if `bytes` is non-ASCII or too long - /// to atomize (string.zig:270). + /// to atomize. pub fn try_create_atom(bytes: &[u8]) -> Option { // SAFETY: bytes describes a valid slice. let atom = unsafe { BunString__tryCreateAtom(bytes.as_ptr(), bytes.len()) }; @@ -264,7 +261,7 @@ impl String { } /// `bun.String.createAtomIfPossible` — atomized strings are interned in a /// thread-local table; falls back to a regular WTF copy if atomization - /// fails. Cannot be used cross-thread (string.zig:278). + /// fails. Cannot be used cross-thread. pub fn create_atom_if_possible(bytes: &[u8]) -> Self { if bytes.is_empty() { return Self::EMPTY; @@ -281,7 +278,7 @@ impl String { /// /// External strings are WTF strings whose bytes live elsewhere; `bytes` is /// borrowed (not copied). If `bytes.len() >= max_length()`, `callback` is - /// invoked immediately and a `dead` string is returned (string.zig:404). + /// invoked immediately and a `dead` string is returned. /// /// `Ctx` must be a pointer-sized type (raw pointer or `&T`); enforced by /// the const-assert below to keep the C-ABI cast sound. @@ -292,7 +289,7 @@ impl String { callback: ExternalStringImplFreeFunction, ) -> Self { use core::ffi::c_void; - // PORT NOTE: Zig asserted `@typeInfo(Ctx) == .pointer` at comptime. + // `Ctx` must be a pointer-sized, pointer-aligned handle. struct AssertPtrSized(core::marker::PhantomData); impl AssertPtrSized { const OK: () = { @@ -309,11 +306,11 @@ impl String { callback(ctx, bytes.as_ptr().cast_mut().cast::(), bytes.len()); return Self::DEAD; } - // PORT NOTE: Zig asserted `@typeInfo(Ctx) == .pointer` (raw pointer, no - // destructor). The Rust const-assert only checks size, so an owning - // pointer-sized `Ctx` (e.g. `Box`) would otherwise be dropped here - // and later double-freed by the WTF finalizer. Ownership transfers to - // the external string; suppress the local drop. + // The const-assert above only checks size/alignment, so an owning + // pointer-sized `Ctx` (e.g. + // `Box`) would otherwise be dropped here and later double-freed by + // the WTF finalizer. Ownership transfers to the external string; + // suppress the local drop. let ctx = core::mem::ManuallyDrop::new(ctx); // SAFETY: Ctx is pointer-sized and pointer-aligned (const-asserted // above); read the bits as `*mut c_void`. @@ -343,8 +340,7 @@ impl String { } /// Max `WTF::StringImpl` length (in characters, not bytes). - /// Reads the process-wide [`STRING_ALLOCATION_LIMIT`] data slot - /// (`jsc::VirtualMachine::string_allocation_limit` in Zig). + /// Reads the process-wide [`STRING_ALLOCATION_LIMIT`] data slot. #[inline] pub fn max_length() -> usize { STRING_ALLOCATION_LIMIT.load(Ordering::Relaxed) @@ -352,7 +348,7 @@ impl String { /// `bun.String.createStaticExternal` — wraps `bytes` in a /// `WTF::ExternalStringImpl` that will **never** be freed. Only use for - /// dynamically-allocated data with process lifetime (string.zig:427). + /// dynamically-allocated data with process lifetime. pub fn create_static_external(bytes: &[u8], is_latin1: bool) -> Self { debug_assert!(!bytes.is_empty()); // SAFETY: bytes describes a valid slice; C++ side stores ptr/len @@ -360,12 +356,10 @@ impl String { unsafe { BunString__createStaticExternal(bytes.as_ptr(), bytes.len(), is_latin1) } } /// `bun.String.createFormat` — formats `args` into a temporary buffer and - /// copies the result into a fresh WTF-backed string. Port collapses Zig's - /// `(comptime fmt, args: anytype)` into [`core::fmt::Arguments`]. + /// copies the result into a fresh WTF-backed string. pub fn create_format(args: core::fmt::Arguments<'_>) -> Self { use core::fmt::Write; - // PORT NOTE: Zig used a 512-byte stackFallback; this is a cold path - // (error messages), so a heap buffer is fine. + // Cold path (error messages), so a heap buffer is fine. if let Some(s) = args.as_str() { return Self::clone_utf8(s.as_bytes()); } @@ -374,8 +368,8 @@ impl String { Self::clone_utf8(buf.as_bytes()) } /// Returns `(String, ptr)` where `ptr` is `len` writable bytes — or - /// `(dead, null)` if WTF allocation failed (string.zig:128 checks - /// `tag == .Dead` before using the buffer). + /// `(dead, null)` if WTF allocation failed (check `tag == .Dead` before + /// using the buffer). pub fn create_uninitialized_latin1(len: usize) -> (Self, &'static mut [u8]) { let s = BunString__fromLatin1Unitialized(len); if s.0.tag != Tag::WTFStringImpl { @@ -384,8 +378,8 @@ impl String { debug_assert_eq!(s.as_wtf().ref_count(), 1); // SAFETY: WTF tag verified above; impl has a writable latin1 buffer of // `len`. `ptr` points at `len` writable bytes owned by the new WTF - // impl; the `'static` lifetime mirrors Zig's `[]u8` return (lifetime - // is actually tied to `s` — caller must not outlive it). + // impl; the `'static` lifetime is actually tied to `s` — caller must + // not outlive it. let buf = unsafe { let ptr = (*s.0.value.wtf_string_impl).m_ptr.latin1.cast_mut(); core::slice::from_raw_parts_mut(ptr, len) @@ -420,8 +414,7 @@ impl String { // Do NOT call `into_boxed_slice()` — when `len < capacity` it issues a // shrink_to_fit realloc. mimalloc's `mi_free` only needs the original // base pointer (capacity is recovered from the heap), so leaking the - // spare capacity to the ExternalStringImpl finalizer is correct and - // matches Zig's `allocator.alloc(u8, n)` → `to[0..wrote]` pattern. + // spare capacity to the ExternalStringImpl finalizer is correct. let mut bytes = core::mem::ManuallyDrop::new(bytes); let (ptr, len) = (bytes.as_mut_ptr(), bytes.len()); // SAFETY: ownership transferred to WTF::ExternalStringImpl, which frees @@ -461,7 +454,7 @@ impl String { pub fn to_wtf_string(&mut self) { BunString__toWTFString(self) } - /// Zig: `bun.String.init(WTFStringImpl)` / `WTFString.adopt` — wrap a raw + /// `bun.String.init(WTFStringImpl)` / `WTFString.adopt` — wrap a raw /// `*mut WTFStringImplStruct`, **adopting** the existing +1 ref (no inc). /// Inverse of [`leak_wtf_impl`]. Null → `String::EMPTY`. #[inline] @@ -476,7 +469,7 @@ impl String { }, }) } - /// Zig: `bun.String{...}.value.WTFStringImpl` — extract the raw `*mut WTFStringImplStruct` + /// Extract the raw `*mut WTFStringImplStruct` /// from a WTF-backed string, transferring ownership of the +1 ref to the caller. Returns /// null for non-WTF tags. Used by SQL data-cell paths that hand the impl pointer to C++. #[inline] @@ -513,9 +506,7 @@ impl String { /// Debug-only guard for the `Send`/`Sync` contract: panics if this /// `String` wraps a non-thread-safe `WTF::StringImpl`. Intended for the /// hand-off point where a `String` is stored into a value that will cross - /// threads (worker task payloads, channel sends, `Arc`-shared state) — - /// the Rust spelling of Zig's `bun.assert(str.isThreadSafe())` before a - /// thread-pool dispatch. + /// threads (worker task payloads, channel sends, `Arc`-shared state). #[inline(always)] #[track_caller] pub fn debug_assert_thread_safe(&self) { @@ -618,8 +609,7 @@ impl String { /// Narrow this string into `dst` iff it is non-empty, fits, and every code /// unit is ASCII (`< 0x80`). UTF-16 narrows via /// [`strings::narrow_ascii_u16`]; 8-bit copies after rejecting any high - /// Latin-1 byte. Returns `Some(&mut dst[..len])` on success. Zig open-codes - /// this per call site (e.g. `inMapCaseInsensitive`, `Encoding.from`). + /// Latin-1 byte. Returns `Some(&mut dst[..len])` on success. pub fn ascii_into<'a>(&self, dst: &'a mut [u8]) -> Option<&'a mut [u8]> { let len = self.length(); if len == 0 || len > dst.len() { @@ -638,7 +628,7 @@ impl String { } } - /// `bun.String.inMapCaseInsensitive` (string.zig) — case-insensitive ASCII + /// `bun.String.inMapCaseInsensitive` — case-insensitive ASCII /// lookup against a phf map whose keys are lowercase ASCII. UTF-16 inputs /// are narrowed (non-ASCII code unit ⇒ miss); 8-bit inputs delegate /// straight to [`strings::in_map_case_insensitive`]. @@ -651,21 +641,20 @@ impl String { } } - /// `bun.String.trunc` (string.zig:317) — clamp to `len` code units. The + /// `bun.String.trunc` — clamp to `len` code units. The /// returned `String` borrows the same storage; for `WTFStringImpl` this /// downgrades to a `ZigString` view (no ref taken), so the original must /// outlive the result. pub fn trunc(&self, len: usize) -> String { if self.length() <= len { - // PORT NOTE: Zig returns `this` by value with no refcount bump; - // `String` is `Copy` here (POD #[repr(C)]), so a plain copy - // matches the Zig pass-by-value semantics. + // No refcount bump: `String` is `Copy` here (POD #[repr(C)]), so a + // plain copy preserves the borrow semantics. return *self; } String::init(self.to_zig_string().trunc(len)) } - /// `bun.String.substring` (string.zig:669) — borrowed slice from `start_index` + /// `bun.String.substring` — borrowed slice from `start_index` /// to end. The returned `String` borrows the same underlying storage; for /// `WTFStringImpl` this downgrades to a `ZigString` view (no ref taken), so /// the original must outlive the result. @@ -674,7 +663,7 @@ impl String { self.substring_with_len(start_index.min(len), len) } - /// `bun.String.substringWithLen` (string.zig:674). + /// `bun.String.substringWithLen`. pub fn substring_with_len(&self, start_index: usize, end_index: usize) -> String { match self.0.tag { Tag::ZigString | Tag::StaticZigString => { @@ -737,7 +726,7 @@ impl String { } } /// Returns `Some(utf8_bytes)` only if this is already valid UTF-8 with no - /// transcoding needed (string.zig:571 `asUTF8`). + /// transcoding needed. pub fn as_utf8(&self) -> Option<&[u8]> { match self.0.tag { Tag::WTFStringImpl => { @@ -769,17 +758,17 @@ impl String { } pub fn eql_utf8(&self, other: &[u8]) -> bool { - // PORT NOTE: no `as_utf8()` fast-path here — for a 16-bit ZigString, + // No `as_utf8()` fast-path here — for a 16-bit ZigString, // `as_utf8()` would call `slice()` (which debug-asserts !is_16bit) and - // `is_all_ascii` on the wrong byte view. Match Zig's `eqlUTF8` and go - // straight through encoding-aware `to_utf8_without_ref`. + // `is_all_ascii` on the wrong byte view. Go straight through + // encoding-aware `to_utf8_without_ref`. self.to_utf8_without_ref().slice() == other } pub fn eql_comptime>(&self, lit: &S) -> bool { self.eql_utf8(lit.as_ref()) } - /// Port of `bun.String.githubAction` (string.zig). Returns a `Display` + /// `bun.String.githubAction` — returns a `Display` /// formatter that escapes the string for GitHub Actions annotation output /// (`%0A` for newlines, ANSI stripped). Encoding-aware: materialises a /// UTF-8 view inside `fmt` so 16-bit / WTF-backed strings are handled. @@ -788,7 +777,7 @@ impl String { StringGithubActionFormatter { text: self } } - /// Port of `bun.String.hasPrefixComptime` (string.zig). ASCII-only prefix + /// `bun.String.hasPrefixComptime` — ASCII-only prefix /// check that avoids materialising the whole UTF-8 view when the /// underlying encoding is 8-bit; falls back to `to_utf8_without_ref` for /// 16-bit / WTF-backed strings. @@ -811,7 +800,7 @@ impl String { } /// `bun.String.fromBytes` — borrow `value` without copying or refcounting; - /// auto-tags UTF-8 if `value` contains any non-ASCII byte (string.zig:504). + /// auto-tags UTF-8 if `value` contains any non-ASCII byte. #[inline] pub fn from_bytes(value: &[u8]) -> Self { Self::init(ZigString::from_bytes(value)) @@ -819,7 +808,7 @@ impl String { /// `bun.String.clone` — produce an owned, WTF-backed copy of `self`. /// WTF-backed inputs just bump the refcount; ZigString inputs are copied - /// into a fresh WTF::StringImpl (string.zig:244). + /// into a fresh WTF::StringImpl. pub fn clone(&self) -> Self { if self.0.tag == Tag::WTFStringImpl { return self.dupe_ref(); @@ -832,7 +821,7 @@ impl String { let (new, chars) = Self::create_uninitialized_utf16(len); if new.0.tag != Tag::Dead { // SAFETY: tag ≠ WTFStringImpl is excluded above so - // `value.zig` is the active variant. + // `value.zig_string` is the active variant. chars.copy_from_slice(self.as_zig().utf16_slice()); } return new; @@ -849,13 +838,13 @@ impl String { } } - /// `bun.String.eql` — encoding-aware equality (string.zig:1014). + /// `bun.String.eql` — encoding-aware equality. pub fn eql(&self, other: &Self) -> bool { self.to_zig_string().eql(other.to_zig_string()) } /// `bun.String.utf8ByteLength` — exact number of UTF-8 bytes needed to - /// encode `self` (string.zig:292). + /// encode `self`. pub fn utf8_byte_length(&self) -> usize { match self.0.tag { Tag::WTFStringImpl => self.as_wtf().utf8_byte_length(), @@ -865,7 +854,7 @@ impl String { } /// `bun.String.utf16ByteLength` — number of bytes the UTF-16LE encoding of - /// `self` would occupy (string.zig:301). + /// `self` would occupy. pub fn utf16_byte_length(&self) -> usize { match self.0.tag { Tag::WTFStringImpl => self.as_wtf().utf16_byte_length(), @@ -875,7 +864,7 @@ impl String { } /// `bun.String.latin1ByteLength` — number of bytes the Latin-1 encoding of - /// `self` would occupy (string.zig:309). + /// `self` would occupy. pub fn latin1_byte_length(&self) -> usize { match self.0.tag { Tag::WTFStringImpl => self.as_wtf().latin1_byte_length(), @@ -896,7 +885,7 @@ impl String { // would invert the crate graph. See PORTING.md §Dep-cycle. /// `bun.String.visibleWidthExcludeANSIColors` — terminal column width of - /// `self`, treating ANSI escape sequences as zero-width (string.zig). + /// `self`, treating ANSI escape sequences as zero-width. /// Dispatches on encoding to [`strings::visible::width::exclude_ansi_colors`]. pub fn visible_width_exclude_ansi_colors(&self, ambiguous_as_wide: bool) -> usize { use crate::string::strings::visible::width::exclude_ansi_colors as w; @@ -911,14 +900,14 @@ impl String { w::latin1(self.latin1()) } - /// `bun.String.isGlobal` (string.zig:63) — true iff this is a `ZigString` + /// `bun.String.isGlobal` — true iff this is a `ZigString` /// whose pointer is tagged as globally-allocated (mimalloc heap). #[inline] pub fn is_global(&self) -> bool { self.0.tag == Tag::ZigString && self.as_zig().is_globally_allocated() } - /// `bun.String.createIfDifferent` (string.zig:117) — if `other` already + /// `bun.String.createIfDifferent` — if `other` already /// holds `utf8_slice` verbatim (and is WTF-backed), return a `dupe_ref`; /// otherwise allocate a fresh WTF-backed copy of `utf8_slice`. pub fn create_if_different(other: &String, utf8_slice: &[u8]) -> String { @@ -928,21 +917,21 @@ impl String { Self::clone_utf8(utf8_slice) } - /// `bun.String.createAtomASCII` — same as [`create_atom`]; the Zig name - /// documents the ASCII-only precondition (string.zig:265). + /// `bun.String.createAtomASCII` — same as [`create_atom`]; the name + /// documents the ASCII-only precondition. #[inline] pub fn create_atom_ascii(s: &[u8]) -> Self { Self::create_atom(s) } /// `bun.String.initLatin1OrASCIIView` — borrow `value` as a Latin-1/ASCII - /// 8-bit `ZigString` view without UTF-8-tagging it (string.zig:491). + /// 8-bit `ZigString` view without UTF-8-tagging it. #[inline] pub fn init_latin1_or_ascii_view(value: &[u8]) -> Self { Self::init(ZigString::init(value)) } - /// `bun.String.encoding` (string.zig:594) — coarse encoding classifier. + /// `bun.String.encoding` — coarse encoding classifier. pub fn encoding(&self) -> strings::EncodingNonAscii { if self.is_utf16() { strings::EncodingNonAscii::Utf16 @@ -953,7 +942,7 @@ impl String { } } - /// `bun.String.canBeUTF8` (string.zig:654) — true iff `self`'s 8-bit bytes + /// `bun.String.canBeUTF8` — true iff `self`'s 8-bit bytes /// are valid UTF-8 (i.e. either UTF-8-tagged or all-ASCII). pub fn can_be_utf8(&self) -> bool { match self.0.tag { @@ -973,7 +962,7 @@ impl String { } } - /// `bun.String.utf8` (string.zig:646) — raw UTF-8 byte slice. Debug-asserts + /// `bun.String.utf8` — raw UTF-8 byte slice. Debug-asserts /// `self` is a UTF-8-safe `ZigString`/`StaticZigString` (use [`as_utf8`] for /// the checked variant). #[inline] @@ -984,19 +973,18 @@ impl String { } /// `bun.String.toUTF8Owned` — like [`to_utf8_without_ref`] but guarantees - /// the returned slice owns its buffer (string.zig:724). + /// the returned slice owns its buffer. pub fn to_utf8_owned(&self) -> ZigStringSlice { self.to_utf8_without_ref().clone_if_borrowed() } - /// `bun.String.toUTF8Bytes` — owned `Vec` of `self` as UTF-8 - /// (string.zig:729). + /// `bun.String.toUTF8Bytes` — owned `Vec` of `self` as UTF-8. #[inline] pub fn to_utf8_bytes(&self) -> Vec { self.to_utf8_owned().into_vec() } - /// `bun.String.toOwnedSliceReturningAllASCII` (string.zig:81) — returns + /// `bun.String.toOwnedSliceReturningAllASCII` — returns /// `(utf8_bytes, is_all_ascii)`. `false` means at least one non-ASCII byte. pub fn to_owned_slice_returning_all_ascii(&self) -> (Vec, bool) { match self.0.tag { @@ -1021,7 +1009,7 @@ impl String { } } - /// `bun.String.toSlice` (string.zig:734) — consume `self` into a + /// `bun.String.toSlice` — consume `self` into a /// [`SliceWithUnderlyingString`], leaving `self` as [`EMPTY`]. #[inline] pub fn to_slice(&mut self) -> SliceWithUnderlyingString { @@ -1042,7 +1030,7 @@ impl String { } } - /// `bun.String.toThreadSafeSlice` (string.zig:742) — like [`to_slice`] but + /// `bun.String.toThreadSafeSlice` — like [`to_slice`] but /// guarantees the resulting buffer is safe to send to another thread. pub fn to_thread_safe_slice(&mut self) -> SliceWithUnderlyingString { if self.0.tag == Tag::WTFStringImpl { @@ -1060,9 +1048,9 @@ impl String { } // Thread-safe impl. If `slice` is borrowed (all-ASCII Latin-1), // re-use the impl's storage: take a single ref for `underlying` and - // hand back a non-owning `WtfBorrowed` view pinned by it. (Zig took - // two refs plus a WTF allocator; the second ref was redundant since - // `underlying` already keeps the impl alive.) + // hand back a non-owning `WtfBorrowed` view pinned by it; a second + // ref would be redundant since `underlying` already keeps the impl + // alive. if let ZigStringSlice::Static(ptr, len) = slice { self.ref_(); let string_impl = self.wtf_ptr(); @@ -1088,7 +1076,7 @@ impl String { self.to_slice() } - /// `bun.String.charAt` (string.zig:831) — code unit at `index`, widened to + /// `bun.String.charAt` — code unit at `index`, widened to /// `u16` regardless of encoding. Caller must ensure `index < self.length()`. #[inline] pub fn char_at(&self, index: usize) -> u16 { @@ -1107,7 +1095,7 @@ impl String { } } - /// `bun.String.indexOfAsciiChar` (string.zig:842). + /// `bun.String.indexOfAsciiChar`. pub fn index_of_ascii_char(&self, chr: u8) -> Option { debug_assert!(chr < 128); if self.is_utf16() { @@ -1117,14 +1105,14 @@ impl String { } } - /// `bun.String.eqlBytes` (string.zig:983) — raw byte-slice equality + /// `bun.String.eqlBytes` — raw byte-slice equality /// (encoding-unaware). #[inline] pub fn eql_bytes(&self, value: &[u8]) -> bool { strings::eql_long(self.byte_slice(), value, true) } - /// `bun.String.toThreadSafeEnsureRef` (string.zig:1001) — like + /// `bun.String.toThreadSafeEnsureRef` — like /// [`to_thread_safe`] but leaves the result with one extra ref. pub fn to_thread_safe_ensure_ref(&mut self) { if self.0.tag == Tag::WTFStringImpl { @@ -1133,7 +1121,7 @@ impl String { } } - /// `bun.String.estimatedSize` (string.zig:1021) — owned allocation size in + /// `bun.String.estimatedSize` — owned allocation size in /// bytes (not character count). `0` for static/empty/dead. pub fn estimated_size(&self) -> usize { match self.0.tag { @@ -1148,7 +1136,7 @@ impl String { // free fns / extension trait in `bun_jsc::string` (would otherwise create // a `bun_string ↔ bun_jsc` dependency cycle). } -// `bun.String.init(anytype)` dispatch table (string.zig:331) — Rust side is +// `bun.String.init` dispatch table — // expressed as `From` impls feeding `String::init>`. The // `String → String` identity case is covered by the std blanket `From for T`. impl From for String { @@ -1164,14 +1152,14 @@ impl From<&ZigString> for String { } } impl From<&[u8]> for String { - /// `[]const u8` arm — `ZigString.fromBytes` (auto-marks UTF-8 if non-ASCII). + /// Byte-slice arm — `ZigString::from_bytes` (auto-marks UTF-8 if non-ASCII). #[inline] fn from(s: &[u8]) -> Self { Self::from(ZigString::from_bytes(s)) } } impl From<&'static [u8; N]> for String { - /// `*const [N:0]u8` arm — Zig string literal (string.zig:340-350): empty + /// `&'static [u8; N]` arm — string literal: empty /// → `Tag::Empty`, otherwise `String.static(value)` → `Tag::StaticZigString`. /// Restricted to `&'static` so the static-tag invariant holds. #[inline] @@ -1190,13 +1178,13 @@ impl From<&str> for String { } } impl From<&[u16]> for String { - /// `[]const u16` arm — `ZigString.from16Slice` (sets UTF-16 + global bits). + /// `&[u16]` arm — `ZigString::from16_slice` (sets UTF-16 + global bits). #[inline] fn from(s: &[u16]) -> Self { Self::from(ZigString::from16_slice(s)) } } -/// `WTFStringImpl` arm of `bun.String.init` (string.zig:331) — wrap an existing +/// `WTFStringImpl` arm of `bun.String.init` — wrap an existing /// `*WTFStringImplStruct` without touching its refcount. impl From for String { #[inline] @@ -1235,7 +1223,7 @@ impl Default for String { // created thread-safe, so sending/sharing a non-thread-safe impl across // threads and then `ref_()`/`deref()`ing it is a data race. // -// We keep the blanket impls to match the Zig `bun.String` / C++ `BunString` +// We keep the blanket impls to match the C++ `BunString` // FFI contract (the type must round-trip by value through `extern "C"` and sit // in `Send + Sync` containers), and instead enforce the invariant at the // boundary: any code that moves a `String` to another thread MUST first call @@ -1253,11 +1241,10 @@ unsafe impl Sync for String {} // `OwnedString` — RAII `defer s.deref()`. // // `String` is intentionally `#[derive(Copy)]` so it stays bit-identical to the -// C++ `BunString` for FFI by-value passing (matching Zig's value-type -// `bun.String`). That precludes `impl Drop for String`. Instead, sites that +// C++ `BunString` for FFI by-value passing. That precludes +// `impl Drop for String`. Instead, sites that // receive a +1 ref (any `clone*`/`create*`/`to_bun_string` constructor) wrap -// it in `OwnedString` to get scope-exit `deref()` — the Rust spelling of Zig's -// pervasive `defer s.deref()`. +// it in `OwnedString` to get scope-exit `deref()`. // // Prefer this over ad-hoc `scopeguard::guard(s, |s| s.deref())` so the // pattern is greppable and `?`-early-returns can't skip the release. @@ -1271,7 +1258,7 @@ impl OwnedString { Self(s) } /// Disarm: return the inner `String` without `deref()`ing it (transfers - /// the +1 to the caller — Zig's "no defer, returned by value"). + /// the +1 to the caller). #[inline] pub fn into_inner(self) -> String { core::mem::ManuallyDrop::new(self).0 @@ -1286,8 +1273,7 @@ impl OwnedString { /// `*const BunString` array (e.g. `BunString__createArray`). Sound because /// `OwnedString` is `#[repr(transparent)]` over `String`; the borrow keeps /// every element alive for the call, and `Drop` still runs on the owning - /// slice afterwards — the Rust spelling of Zig's - /// `defer { for (items) |s| s.deref(); }` around `toJSArray`. + /// slice afterwards. #[inline] pub fn as_raw_slice(owned: &[OwnedString]) -> &[String] { // `#[repr(transparent)]` over `String` ⇒ bytemuck's safe slice peel. @@ -1330,8 +1316,6 @@ impl Default for OwnedString { impl Clone for OwnedString { /// Bumps the WTF refcount (or copies a `ZigString` into a fresh /// WTF::StringImpl) and wraps the resulting +1 in a new `OwnedString`. - /// Mirrors Zig's `s.clone()` followed by an implicit `defer deref` on the - /// new value. #[inline] fn clone(&self) -> Self { Self(self.0.clone()) @@ -1380,7 +1364,7 @@ impl core::fmt::Display for ZigStringGithubActionFormatter { } impl core::fmt::Display for ZigString { - // ZigString.zig `format()` — encoding-aware `{f}` formatter. + // Encoding-aware `Display` formatter. fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { if self.is_utf8() { return write!(f, "{}", crate::fmt::s(self.slice())); @@ -1445,8 +1429,8 @@ impl ZigString { pub const fn init(s: &[u8]) -> Self { Self(bun_alloc::ZigString::init(s)) } - /// `ZigString.init` for `'static` literals — alias for callers spelling it - /// `init_static` (matches Zig `ZigString.init` with comptime-known string). + /// [`init`](Self::init) for `'static` literals — alias for callers + /// spelling it `init_static`. #[inline] pub const fn init_static(s: &'static [u8]) -> Self { Self(bun_alloc::ZigString::init(s)) @@ -1465,7 +1449,7 @@ impl ZigString { // Ownership transferred to JSC: `mark_global()` tags the buffer so // `Zig::toString*` adopts it into a WTF string and `mi_free`s it on // string death. `heap::release` is the hand-off-to-foreign-owner - // spelling (Zig `ZigString.dupeForJS` never frees `utf16` locally). + // spelling. let leaked: &'static mut [u16] = crate::heap::release(utf16.into_boxed_slice()); let mut out = ZigString::init_utf16(leaked); out.mark_global(); @@ -1493,13 +1477,13 @@ impl ZigString { } /// `ZigString.from16Slice` — wraps a globally-allocated UTF-16 buffer - /// (sets both the 16-bit and global ptr-tags). ZigString.zig:533. + /// (sets both the 16-bit and global ptr-tags). #[inline] pub fn from16_slice(slice: &[u16]) -> Self { Self::from16(slice.as_ptr(), slice.len()) } - /// `ZigString.from16` — globally-allocated memory only (ZigString.zig:547). + /// `ZigString.from16` — globally-allocated memory only. /// Marks UTF-16 + global; caller must ensure the buffer was allocated by /// `bun.default_allocator` (mimalloc) since `deinitGlobal` will free it. #[inline] @@ -1510,15 +1494,14 @@ impl ZigString { z } - /// Alias of [`is_16bit`] (Zig spelled it `is16Bit`; per PORTING.md acronym - /// rule that becomes `is_16_bit`). + /// Alias of [`is_16bit`]. #[inline] pub fn is_16_bit(&self) -> bool { self.0.is_16bit() } /// `ZigString.fromBytes` — borrow `slice`; if it contains any non-ASCII - /// byte, sets the UTF-8 ptr-tag (ZigString.zig:14). + /// byte, sets the UTF-8 ptr-tag. #[inline] pub fn from_bytes(slice: &[u8]) -> Self { if !strings::is_all_ascii(slice) { @@ -1528,9 +1511,8 @@ impl ZigString { } } - /// `ZigString.static` — wraps a `'static` ASCII literal. Zig returned a - /// `*const ZigString` to a comptime-interned holder; Rust callers consume - /// the value directly (ZigString is `Copy`), so we return by value. + /// `ZigString.static` — wraps a `'static` ASCII literal; returns by value + /// (`ZigString` is `Copy`). /// Generic over `str`/`[u8]` so either `"lit"` or `b"lit"` is accepted. #[inline] pub fn static_>(slice: &'static S) -> Self { @@ -1543,7 +1525,7 @@ impl ZigString { } /// `ZigString.utf8ByteLength` — exact UTF-8 byte length needed to encode - /// this string (ZigString.zig:221). UTF-16 → simdutf length; Latin-1 + /// this string. UTF-16 → simdutf length; Latin-1 /// → simdutf utf8-from-latin1 length; UTF-8 → `len`. pub fn utf8_byte_length(self) -> usize { if self.is_utf8() { @@ -1552,15 +1534,14 @@ impl ZigString { if self.is_16bit() { return crate::strings::element_length_utf16_into_utf8(self.utf16_slice()); } - // Latin-1 path (ZigString.zig delegates to encoding.byteLengthU8(.utf8), - // which is `simdutf.length.utf8.from.latin1` for the latin1 case). + // Latin-1 path: simdutf utf8-from-latin1 length. let s = self.slice(); // SAFETY: s describes a valid byte slice. unsafe { bun_simdutf_sys::simdutf::simdutf__utf8_length_from_latin1(s.as_ptr(), s.len()) } } /// `ZigString.utf16ByteLength` — number of bytes the UTF-16LE encoding of - /// this string would occupy (ZigString.zig:199). + /// this string would occupy. pub fn utf16_byte_length(self) -> usize { if self.is_utf8() { let s = self.slice(); @@ -1576,11 +1557,10 @@ impl ZigString { self.len * 2 } - /// `ZigString.latin1ByteLength` (ZigString.zig:211). + /// `ZigString.latin1ByteLength`. pub fn latin1_byte_length(self) -> usize { if self.is_utf8() { - // PORT NOTE: Zig: `@panic("TODO")` — never implemented for UTF-8 - // sources. Match Zig behaviour. + // Never implemented for UTF-8 sources. unreachable!("ZigString.latin1ByteLength from UTF-8 — unimplemented in Zig"); } self.len @@ -1590,7 +1570,7 @@ impl ZigString { // `bun_runtime::webcore::encoding::ZigStringEncode` (extension trait); the // encoder bodies live in `bun_runtime`. - /// Port of `ZigString.githubAction` (ZigString.zig). Returns a `Display` + /// `ZigString.githubAction` — returns a `Display` /// formatter that escapes the string for GitHub Actions annotation output /// (`%0A` for newlines, ANSI stripped). Encoding-aware via `to_slice`. #[inline] @@ -1614,10 +1594,9 @@ impl ZigString { crate::ZBox::from_vec_with_nul(list) } - /// `ZigString.indexOfAny` (ZigString.zig:89) — first index whose code unit + /// `ZigString.indexOfAny` — first index whose code unit /// matches any byte in `chars`. The 16-bit branch narrows each unit to the - /// Latin-1 range before comparing (mirrors Zig's comptime widening of the - /// `[]const u8` needle to `u16` inside `strings.indexOfAny16`). + /// Latin-1 range before comparing. pub fn index_of_any(self, chars: &'static [u8]) -> Option { if self.is_16bit() { self.utf16_slice() @@ -1629,7 +1608,7 @@ impl ZigString { } /// `ZigString.charAt` — first/nth code unit, widened to `u16` regardless - /// of encoding (ZigString.zig:615). Caller must ensure `i < self.len`. + /// of encoding. Caller must ensure `i < self.len`. #[inline] pub fn char_at(self, i: usize) -> u16 { debug_assert!(i < self.len); @@ -1640,26 +1619,23 @@ impl ZigString { } } - /// `ZigString.eqlComptime` — encoding-aware equality against a `'static` - /// ASCII literal (ZigString.zig:272). UTF-16 inputs go through the + /// Encoding-aware equality against a `'static` + /// ASCII literal. UTF-16 inputs go through the /// per-unit `eql_comptime_utf16` path; 8-bit inputs compare bytes - /// directly. The Zig version `@compileError`s on non-ASCII `other`; in - /// Rust we cannot enforce that at compile time, so it falls through to - /// the byte compare (caller is expected to pass ASCII). + /// directly. Non-ASCII `other` is not rejected at compile time; it falls + /// through to the byte compare (caller is expected to pass ASCII). pub fn eql_comptime>(self, other: &S) -> bool { let other = other.as_ref(); if self.is_16bit() { return strings::eql_comptime_utf16(self.utf16_slice(), other); } - // PORT NOTE: Zig branched on `comptime strings.isAllASCII(other)`; - // demoted to runtime length-check + byte compare. if self.len != other.len() { return false; } strings::eql_comptime_ignore_len(self.slice(), other) } - /// `ZigString.eql` — encoding-aware equality (ZigString.zig). + /// `ZigString.eql` — encoding-aware equality. pub fn eql(self, other: Self) -> bool { if self.len == 0 || other.len == 0 { return self.len == other.len; @@ -1672,7 +1648,7 @@ impl ZigString { if !l16 && !r16 { return self.slice() == other.slice(); } - // Mixed encoding — go through the UTF-8 view (matches Zig's slow path). + // Mixed encoding — go through the UTF-8 view. self.to_slice().slice() == other.to_slice().slice() } @@ -1695,7 +1671,7 @@ impl ZigString { strings::is_all_ascii(self.slice()) } - /// `ZigString.hasPrefixChar` (ZigString.zig). + /// `ZigString.hasPrefixChar`. pub fn has_prefix_char(&self, char: u8) -> bool { if self.len == 0 { return false; @@ -1719,9 +1695,9 @@ impl ZigString { } /// `ZigString.detectEncoding` — if the (currently-untagged) bytes contain - /// any non-ASCII, mark the pointer as UTF-16. Mirrors ZigString.zig's - /// `detectEncoding` (which assumes the bytes were sourced from a - /// JS-produced 8-bit string and need re-widening on non-ASCII). + /// any non-ASCII, mark the pointer as UTF-16 (assumes the bytes were + /// sourced from a JS-produced 8-bit string and need re-widening on + /// non-ASCII). #[inline] pub fn detect_encoding(&mut self) { if !strings::is_all_ascii(self.slice()) { @@ -1872,7 +1848,7 @@ impl ZigString { // size. Flag bits stripped by `untagged`. unsafe { core::slice::from_raw_parts(Self::untagged(self.tagged_ptr()), bytes) } } - /// `ZigString.substringWithLen` (ZigString.zig:166) — re-wrap a sub-range + /// `ZigString.substringWithLen` — re-wrap a sub-range /// of the underlying storage, preserving the UTF-8/16-bit/global tag bits. pub fn substring_with_len(self, start_index: usize, end_index: usize) -> ZigString { if self.is_16bit() { @@ -1891,12 +1867,12 @@ impl ZigString { } out } - /// `ZigString.substring` (ZigString.zig:183). + /// `ZigString.substring`. #[inline] pub fn substring(self, start_index: usize) -> ZigString { self.substring_with_len(start_index.min(self.len), self.len) } - /// `ZigString.trunc` (ZigString.zig:268) — clamp `len`, preserving the + /// `ZigString.trunc` — clamp `len`, preserving the /// pointer (and its tag bits) verbatim. #[inline] pub fn trunc(self, len: usize) -> ZigString { @@ -1927,15 +1903,14 @@ impl ZigString { } /// `ZigString.toOwnedSlice` — allocate a fresh UTF-8 `Vec` regardless - /// of the source encoding (ZigString.zig:239). UTF-16 → transcode; UTF-8 → + /// of the source encoding. UTF-16 → transcode; UTF-8 → /// copy; Latin-1 → transcode (or copy if all-ASCII). /// /// The returned buffer is NUL-terminated one byte past `len()` (the - /// terminator is *not* included in `len()`), matching ZigString.zig:243-245 - /// so `sliceZBuf` / C-string consumers can read `as_ptr()` directly. + /// terminator is *not* included in `len()`) so `sliceZBuf` / C-string + /// consumers can read `as_ptr()` directly. pub fn to_owned_slice(&self) -> Vec { - // Write a NUL sentinel at `v[len]` without bumping `len` (mirrors - // ZigString.zig:243-245 / `dupeZ`). + // Write a NUL sentinel at `v[len]` without bumping `len`. #[inline] fn with_sentinel(mut v: Vec) -> Vec { v.reserve_exact(1); @@ -1947,7 +1922,7 @@ impl ZigString { if self.len == 0 { return Vec::new(); } - // PORT NOTE: order matches ZigString.zig:233-253 — `isUTF8()` is tested + // Order matters — `isUTF8()` is tested // before `is16Bit()` so a string with both tags set takes the UTF-8 arm. if self.is_utf8() { return with_sentinel(self.slice().to_vec()); @@ -1960,12 +1935,12 @@ impl ZigString { with_sentinel(crate::strings::to_utf8_from_latin1(bytes).unwrap_or_else(|| bytes.to_vec())) } - /// `ZigString.toSliceClone` — the returned slice is *always* heap-owned - /// (ZigString.zig:693). Unlike `to_slice`, this never borrows the source + /// `ZigString.toSliceClone` — the returned slice is *always* heap-owned. + /// Unlike `to_slice`, this never borrows the source /// bytes, so the result outlives a GC'd `JSString` that produced `self`. /// - /// PORT NOTE: Zig returned `OOM!Slice`; with mimalloc as the global - /// allocator OOM aborts the process, so this is infallible. + /// Infallible: with mimalloc as the global allocator, OOM aborts the + /// process. pub fn to_slice_clone(&self) -> ZigStringSlice { if self.len == 0 { return ZigStringSlice::EMPTY; @@ -1977,11 +1952,9 @@ impl ZigString { /// the end (`slice().as_ptr()` is a valid C string of length `slice().len()`). /// `slice()` itself does *not* include the terminator. /// - /// PORT NOTE: the Zig method this targets was never instantiated (lazy - /// compilation); JSString/JSValue callers reached for it but no `.zig` - /// caller forced codegen. Semantics here match `toOwnedSliceZ` wrapped in - /// a `Slice` so `JSValue::to_slice_z` / `JSString::to_slice_z` get the - /// `[:0]` guarantee they document. + /// Semantics match `toOwnedSliceZ` wrapped in a `Slice` so + /// `JSValue::to_slice_z` / `JSString::to_slice_z` get the `[:0]` + /// guarantee they document. pub fn to_slice_z(&self) -> ZigStringSlice { if self.len == 0 { // Static "" already points at a NUL byte. @@ -1997,8 +1970,8 @@ impl ZigString { } } -/// `ZigString.Slice` — a borrowed-or-owned UTF-8 byte slice. Replaces the -/// Zig allocator-vtable trick (`StringImplAllocator` etc.) with explicit ownership. +/// `ZigString.Slice` — a borrowed-or-owned UTF-8 byte slice with explicit +/// ownership. pub enum ZigStringSlice { /// Borrowed; never freed (`fromUTF8NeverFree`). Static(*const u8, usize), @@ -2099,7 +2072,7 @@ impl Drop for ZigStringSlice { } } -/// `bun.SliceWithUnderlyingString` (string.zig:1035) — a UTF-8 byte view paired +/// `bun.SliceWithUnderlyingString` — a UTF-8 byte view paired /// with the `bun.String` that may back it. `utf8` is either a borrowed/owned /// byte slice or empty; `underlying` is the `String` whose ref keeps `utf8` /// alive when WTF-backed. @@ -2142,7 +2115,6 @@ impl SliceWithUnderlyingString { } /// `fromUTF8` — wrap a borrowed UTF-8 slice (caller keeps it alive). - /// Zig assumed `default_allocator`; Rust port uses `Static` (no free). #[inline] pub fn from_utf8(utf8: &[u8]) -> SliceWithUnderlyingString { SliceWithUnderlyingString { @@ -2160,9 +2132,8 @@ impl SliceWithUnderlyingString { } /// `deinit` — release `utf8`'s allocation (if any) and deref `underlying`. - /// Explicit for parity with Zig call sites; `Drop` is intentionally not - /// implemented because `underlying: String` is `Copy` (matches Zig manual - /// `defer .deinit()` pattern). + /// `Drop` is intentionally not implemented because `underlying: String` + /// is `Copy`. pub fn deinit(self) { // `utf8` drops via ZigStringSlice::Drop. self.underlying.deref(); @@ -2170,7 +2141,7 @@ impl SliceWithUnderlyingString { /// `toThreadSafe` — if `underlying` is WTF-backed, migrate it to a /// thread-safe impl and re-derive `utf8` if it was a ref-counted view - /// into the old impl (string.zig:1090). + /// into the old impl. pub fn to_thread_safe(&mut self) { if self.underlying.0.tag == Tag::WTFStringImpl { let orig = self.underlying.wtf_ptr(); @@ -2212,17 +2183,16 @@ impl ZigStringSlice { } /// True iff this slice owns a heap allocation that would be freed on - /// `Drop`. Replaces the Zig `slice.allocator.get().is_some()` idiom: in - /// Rust the allocator is implicit in the variant. `WtfBorrowed` is *not* + /// `Drop`. The allocator is implicit in the variant. `WtfBorrowed` is *not* /// allocated — it borrows storage pinned by someone else. #[inline] pub fn is_allocated(&self) -> bool { matches!(self, Self::Owned(_) | Self::WTF { .. }) } - /// True iff this slice is a view into a `WTF::StringImpl` (the Zig - /// `String.isWTFAllocator(slice.allocator)` check) — whether it holds its - /// own ref (`WTF`) or is pinned by an `underlying` (`WtfBorrowed`). Used by + /// True iff this slice is a view into a `WTF::StringImpl` — whether it + /// holds its own ref (`WTF`) or is pinned by an `underlying` + /// (`WtfBorrowed`). Used by /// [`SliceWithUnderlyingString::to_thread_safe`] to decide whether the view /// must be re-derived after a thread-safe migration. #[inline] @@ -2285,13 +2255,13 @@ impl ZigStringSlice { pub use crate::{WStr, ZStr}; /// `bun_core::zig_string` — module path so callers can spell `ZigString.Slice` -/// as `zig_string::Slice` (matches the Zig namespace `ZigString.Slice`). +/// as `zig_string::Slice`. pub mod zig_string { pub use super::ZigString; pub use super::ZigStringSlice as Slice; impl super::ZigStringSlice { - /// `ZigString.Slice.empty` — Rust idiom is `EMPTY`, but several - /// dependents call `.empty()` (matching Zig's `.empty`). + /// `ZigString.Slice.empty` — the idiomatic constant is `EMPTY`, but + /// several dependents call `.empty()`. #[inline] pub const fn empty() -> Self { Self::Static(core::ptr::null(), 0) @@ -2336,7 +2306,7 @@ pub mod encoding { } pub use encoding::Encoding as NodeEncoding; -// `strings` is the canonical Zig namespace name; alias to the real module. +// `strings` is the canonical `bun.strings` namespace name; alias to the real module. pub use immutable as strings; // ────────────────────────────────────────────────────────────────────────── @@ -2427,7 +2397,7 @@ pub mod lexer_tables { } } -/// `jsc::VirtualMachine::string_allocation_limit` (VirtualMachine.zig:14) — +/// `jsc::VirtualMachine::string_allocation_limit` — /// process-wide WTF::StringImpl character-count cap, exported for C++ as /// `Bun__stringSyntheticAllocationLimit`. The value lives here (not `bun_jsc`) /// because [`String::max_length`] / `create_external*` need it without an @@ -2437,7 +2407,7 @@ pub mod lexer_tables { pub static STRING_ALLOCATION_LIMIT: AtomicUsize = AtomicUsize::new(u32::MAX as usize); // ────────────────────────────────────────────────────────────────────────── -// move-in: printer (MOVE_DOWN ← src/js_printer/js_printer.zig) +// move-in: printer (MOVE_DOWN ← `bun_js_printer`) // // Self-contained string-quoting helpers used by `strings::format_escapes`, // `bun_sourcemap::Chunk` (JSON serialization), and `bun_ast::Expr`. @@ -2498,9 +2468,9 @@ pub mod printer { } } - /// Port of `js_printer.writePreQuotedString`. - /// PERF(port): was comptime-monomorphized over (quote_char, ascii_only, json, - /// encoding); demoted to runtime params — profile if it shows up on a hot path. + /// Same algorithm as `bun_js_printer::write_pre_quoted_string`. + /// PERF: (quote_char, ascii_only, json, encoding) are runtime params — + /// profile if it shows up on a hot path. pub fn write_pre_quoted_string( text_in: &[u8], writer: &mut W, @@ -2651,7 +2621,7 @@ pub mod printer { bytes: &mut MutableString, ascii_only: bool, ) -> Result<(), crate::Error> { - // PERF(port): Zig pre-grew via estimateLengthForUTF8 — profile if it shows up on a hot path. + // PERF: consider pre-growing via an estimated UTF-8 length — profile if it shows up on a hot path. bytes.append_char(b'"')?; write_pre_quoted_string(text, bytes, b'"', ascii_only, true, StrEncoding::Utf8)?; bytes.append_char(b'"').expect("unreachable"); @@ -2661,22 +2631,20 @@ pub mod printer { pub use printer::quote_for_json; // ────────────────────────────────────────────────────────────────────────── -// Top-level free helpers (move-ins from misc Zig namespaces). +// Top-level free helpers. // ────────────────────────────────────────────────────────────────────────── /// `bun.sliceTo(buf, 0)` — slice up to (not including) the first NUL byte, -/// or the whole buffer if none. Port of `std.mem.sliceTo` for `u8`/`0`. +/// or the whole buffer if none. /// Sunk to `crate::ffi` so tier-1 crates (cares_sys, sys) can share it; /// re-exported here for the existing `bun_core::slice_to_nul` callers. pub use crate::ffi::{slice_to_nul, slice_to_nul_mut}; -/// move-in: `cheap_prefix_normalizer` (MOVE_DOWN ← `bundle_v2.zig`). -/// /// Pure path-string helper used by the bundler chunk writer and `css::printer`. /// Returns `[prefix', suffix']` such that concatenating them produces a /// reasonably-normalized path (collapses `./` leading and avoids `//`). -/// Matches the .zig spec `[2]string` return shape so bundler call-sites can -/// index it directly. +/// The two-element-array return shape lets bundler call-sites index it +/// directly. pub fn cheap_prefix_normalizer<'a>(prefix: &'a [u8], suffix: &'a [u8]) -> [&'a [u8]; 2] { if prefix.is_empty() { let suffix_no_slash = strings::remove_leading_dot_slash(suffix); @@ -2699,8 +2667,7 @@ pub fn cheap_prefix_normalizer<'a>(prefix: &'a [u8], suffix: &'a [u8]) -> [&'a [ { return [prefix, &suffix[1..]]; } - // It gets really complicated if we try to deal with URLs more than this - // (see bundle_v2.zig comment block). + // It gets really complicated if we try to deal with URLs more than this. } [prefix, strings::remove_leading_dot_slash(suffix)] diff --git a/src/bun_core/string/write.rs b/src/bun_core/string/write.rs index 9a4447106ea..08d04f0262f 100644 --- a/src/bun_core/string/write.rs +++ b/src/bun_core/string/write.rs @@ -1,4 +1,4 @@ -//! Canonical byte-oriented `Write` trait — single port of Zig `std.Io.Writer`. +//! Canonical byte-oriented `Write` trait. //! //! HOSTED IN `bun_string` so the trait sits *below* every consumer in the dep //! DAG: `bun_io → bun_string` already exists, and `bun_string` already depends diff --git a/src/bun_core/string/wtf.rs b/src/bun_core/string/wtf.rs index af41a549646..99520ff7043 100644 --- a/src/bun_core/string/wtf.rs +++ b/src/bun_core/string/wtf.rs @@ -1,6 +1,5 @@ -use crate::string::strings; -// TODO(port): ZigString.Slice is a nested type in Zig; in Rust it lives alongside ZigString. use crate::string::ZigStringSlice; +use crate::string::strings; // Canonical layout lives in `bun_alloc` (lowest-tier crate) so the // `is_wtf_allocator` vtable-identity check is a local pointer compare with no @@ -46,8 +45,7 @@ impl WTFStringImplExt for WTFStringImplStruct { fn to_latin1_slice(&self) -> ZigStringSlice { self.r#ref(); let s = self.latin1_slice(); - // ZigStringSlice::WTF derefs `self` on Drop — replaces the Zig - // StringImplAllocator vtable trick with explicit ownership. + // ZigStringSlice::WTF derefs `self` on Drop. // SAFETY: `self` is a live WTF::StringImpl with refcount just bumped above; // we store only a `*const` (never materialize `&mut`) and the matching // deref happens via FFI on Drop. Mutation of m_ref_count is C++-side @@ -112,8 +110,8 @@ impl WTFStringImplExt for WTFStringImplStruct { ZigStringSlice::init_owned(strings::to_utf8_alloc(self.utf16_slice())) } - /// Allocates a NUL-terminated UTF-8 copy. Port of `toOwnedSliceZ`. - /// `.len()` excludes the sentinel (Zig `[:0]u8` semantics). + /// Allocates a NUL-terminated UTF-8 copy. + /// `.len()` excludes the sentinel. fn to_owned_slice_z(&self) -> crate::ZBox { if self.is_8bit() { if let Some(utf8) = strings::to_utf8_from_latin1_z(self.latin1_slice()) { @@ -149,10 +147,7 @@ impl WTFStringImplExt for WTFStringImplStruct { if self.is_8bit() { let input = self.latin1_slice(); if !input.is_empty() { - // Port: latin1→utf8 length is just elementLengthLatin1IntoUTF8 - // (each high byte becomes 2 utf8 bytes). The Zig went through - // jsc.WebCore.encoding.byteLengthU8 but for Utf8 target that - // reduces to the same arithmetic. + // latin1→utf8 length: each high byte becomes 2 utf8 bytes. strings::element_length_latin1_into_utf8(input) } else { 0 @@ -177,14 +172,7 @@ impl WTFStringImplExt for WTFStringImplStruct { } } -// PORT NOTE: Zig's `StringImplAllocator` was a `std.mem.Allocator` vtable trick -// (alloc() bumped ref, free() dropped it) so a `ZigString.Slice` would deref the -// WTFStringImpl when freed. Replaced by `ZigStringSlice::WTF { .. }` explicit -// ownership variant — see `to_latin1_slice` above. No allocator trait needed. - // `WTF.parseDouble` canonical now lives in bun_core::fmt (tier-0) so // `bun_interchange` (yaml/toml) and `bun_js_parser::lexer` can call it without -// any string/jsc dep. Re-exported here to keep the Zig namespace shape. +// any string/jsc dep. Re-exported here for back-compat. pub use crate::fmt::{InvalidCharacter, parse_double}; - -// ported from: src/string/wtf.zig diff --git a/src/bun_core/thread_id.rs b/src/bun_core/thread_id.rs index 9a1195f6059..252d1005ef3 100644 --- a/src/bun_core/thread_id.rs +++ b/src/bun_core/thread_id.rs @@ -2,9 +2,8 @@ //! for storing in an atomic and printing in panics so it lines up with what a //! debugger / `top -H` / Instruments shows. //! -//! Ground truth is Zig's `std.Thread.Id` / `std.Thread.getCurrentId()` -//! (vendor/zig/lib/std/Thread.zig). This is the single Rust port of that -//! per-OS ladder; every other crate re-exports or widens from here: +//! This is the single per-OS ladder; every other crate re-exports or widens +//! from here: //! * `bun_safety::thread_id` → `pub use bun_core::thread_id::*;` //! * `bun_threading::current_thread_id` → `current() as u64` //! * `bun_core::util::debug_thread_id` → `current() as u64` (debug-only) @@ -13,9 +12,9 @@ //! process-local monotonic counter (no `MAX`, no atomic repr, not the kernel //! TID), whereas every consumer (`CriticalSection`, `ThreadLock`, `ThreadCell`) //! needs a plain integer it can store in an atomic and compare against a -//! sentinel — exactly Zig's semantics. +//! sentinel. -// ── ThreadId width (mirrors Zig `std.Thread.Id` switch) ─────────────────── +// ── ThreadId width ───────────────────────────────────────────────────────── // linux / *bsd / haiku / wasi / serenity → u32 // macOS / iOS / watchOS / tvOS / visionOS → u64 // Windows → DWORD (u32) @@ -60,7 +59,7 @@ pub type ThreadId = u64; )))] pub type ThreadId = usize; -// ── Atomic wrapper (Zig: `std.atomic.Value(Thread.Id)`) ─────────────────── +// ── Atomic wrapper ───────────────────────────────────────────────────────── // Width-matched alias so `CriticalSection` can `compare_exchange` on it directly. #[cfg(any( target_os = "linux", @@ -103,14 +102,10 @@ pub type AtomicThreadId = core::sync::atomic::AtomicU64; pub type AtomicThreadId = core::sync::atomic::AtomicUsize; /// A value that does not alias any other thread ID. -/// See `Thread/Mutex/Recursive.zig` in the Zig standard library. -// Zig: `pub const invalid = std.math.maxInt(std.Thread.Id);` pub const INVALID: ThreadId = ThreadId::MAX; -/// Per-thread cache of [`current()`]. Zig's `LinuxThreadImpl.getCurrentId()` -/// reads a `threadlocal var tls_thread_id` set once at thread start -/// (vendor/zig/lib/std/Thread.zig:841,885); the Rust port called the syscall -/// (`gettid`/`pthread_threadid_np`/`GetCurrentThreadId`) on every call. The +/// Per-thread cache of [`current()`]. Without it, every call paid a syscall +/// (`gettid`/`pthread_threadid_np`/`GetCurrentThreadId`). The /// bundler's `Worker::get(ctx)` calls `current()` once per scheduled task — /// parse, line-offset table, quoted source contents, compile-result /// generation, link step 5 — so a 19 K-module build paid ~109 K `gettid` @@ -119,20 +114,17 @@ pub const INVALID: ThreadId = ThreadId::MAX; /// `0` is the unset sentinel: kernel TIDs / `pthread_threadid_np` IDs / /// Win32 thread IDs are all nonzero. A bare `#[thread_local]` slot (not the /// `thread_local!` macro) so this is a single TLS load with no `LocalKey` -/// initialization-state branch or destructor registration — same as Zig's -/// `threadlocal var`. +/// initialization-state branch or destructor registration. #[thread_local] static TLS_THREAD_ID: core::cell::Cell = core::cell::Cell::new(0); /// Returns the platform's notion of the calling thread's ID. /// -/// Port of Zig `std.Thread.getCurrentId()` (`PosixThreadImpl` / `WindowsThreadImpl` / -/// `LinuxThreadImpl`). Attempts to use OS-specific primitives so the value matches what +/// Attempts to use OS-specific primitives so the value matches what /// debuggers/tracers report; falls back to `pthread_self()` as a `usize` on unknown targets. /// /// Cached per-thread after the first call (see [`TLS_THREAD_ID`]); subsequent -/// calls are a single TLS read with no syscall, matching Zig's -/// `tls_thread_id` slot. Lazy rather than set-at-spawn so threads not started +/// calls are a single TLS read with no syscall. Lazy rather than set-at-spawn so threads not started /// by Bun's pool (FFI callbacks, the main thread) still get a valid ID. #[inline] pub fn current() -> ThreadId { @@ -149,7 +141,6 @@ pub fn current() -> ThreadId { fn current_uncached() -> ThreadId { #[cfg(any(target_os = "linux", target_os = "android"))] { - // Zig: `LinuxThreadImpl.getCurrentId()` → `linux.gettid()`. // SAFETY: `gettid` takes no arguments and cannot fail. return unsafe { libc::gettid() } as ThreadId; } @@ -161,7 +152,6 @@ fn current_uncached() -> ThreadId { target_os = "visionos", ))] { - // Zig: `pthread_threadid_np(null, &thread_id)`. unsafe extern "C" { fn pthread_threadid_np( thread: *mut core::ffi::c_void, @@ -229,7 +219,6 @@ fn current_uncached() -> ThreadId { target_os = "visionos", )))] { - // Zig fallback: `@intFromPtr(c.pthread_self())`. unsafe extern "C" { // safe: no args; infallible. safe fn pthread_self() -> usize; @@ -237,5 +226,3 @@ fn current_uncached() -> ThreadId { return pthread_self() as ThreadId; } } - -// ported from: vendor/zig/lib/std/Thread.zig (Id / getCurrentId) diff --git a/src/bun_core/tty.rs b/src/bun_core/tty.rs index 1e0fc8f7d6a..fd86bef2d20 100644 --- a/src/bun_core/tty.rs +++ b/src/bun_core/tty.rs @@ -1,7 +1,7 @@ use core::ffi::c_int; // ─── MOVE-IN: Winsize (TYPE_ONLY from bun_sys → bun_core) ───────────────── -// Zig: `std.posix.winsize` — used by output.rs::TERMINAL_SIZE. Field names +// Used by output.rs::TERMINAL_SIZE. Field names // match the move-out forward-ref in output.rs (row/col, not ws_row/ws_col). #[repr(C)] #[derive(Clone, Copy, Default, Debug)] @@ -29,8 +29,7 @@ pub fn set_mode(fd: c_int, mode: Mode) -> c_int { } /// RAII guard: sets `fd` to [`Mode::Raw`] on construction and restores -/// [`Mode::Normal`] on `Drop`. Replaces the Zig -/// `defer { _ = bun.tty.set_mode(0, .Normal); }` pattern at call sites. +/// [`Mode::Normal`] on `Drop`. pub struct RawModeGuard { fd: c_int, } @@ -50,9 +49,6 @@ impl Drop for RawModeGuard { } } -// TODO(port): move to bun_core_sys (or appropriate *_sys crate) unsafe extern "C" { safe fn Bun__ttySetMode(fd: c_int, mode: c_int) -> c_int; } - -// ported from: src/bun_core/tty.zig diff --git a/src/bun_core/util.rs b/src/bun_core/util.rs index bb320020afa..c1606ab207c 100644 --- a/src/bun_core/util.rs +++ b/src/bun_core/util.rs @@ -1,10 +1,9 @@ -// ─── std.mem.bytesAsSlice / sliceAsBytes ───────────────────────────────────── -/// Zig `std.mem.bytesAsSlice(T, bytes)` for `&mut [u8]` → `&mut [T]`. +// ─── byte ↔ typed-slice reinterpretation ───────────────────────────────────── +/// Reinterpret `&mut [u8]` as `&mut [T]`. /// /// SAFETY (caller-upheld): -/// * `bytes.as_ptr()` must be aligned to `align_of::()` — Zig spells this -/// as `@alignCast`, which is a *checked* operation (illegal-behavior trap in -/// safe builds). We mirror that with a hard `assert!` rather than +/// * `bytes.as_ptr()` must be aligned to `align_of::()`. We enforce this +/// with a hard `assert!` rather than /// `debug_assert!`: forming a misaligned `&mut [T]` is instant UB in Rust /// even if never dereferenced, so this must not be silently elided in /// release. The check is a single AND+CMP and every current call site is @@ -12,7 +11,7 @@ /// * `T` must be plain-old-data — every byte pattern in `bytes[..len/size]` /// must be a valid `T` (callers use `u16`/`u32` only), /// * the trailing `len % size_of::()` bytes are silently dropped from the -/// reinterpreted view, matching Zig's `bytesAsSlice` semantics. +/// reinterpreted view. #[inline] pub unsafe fn bytes_as_slice_mut(bytes: &mut [u8]) -> &mut [T] { assert!( @@ -26,7 +25,7 @@ pub unsafe fn bytes_as_slice_mut(bytes: &mut [u8]) -> &mut [T] { } // ─── Unaligned ───────────────────────────────────────────────────────────── -/// Port of Zig's `align(1) T` element type. Rust references and slices require +/// Element wrapper with alignment 1. Rust references and slices require /// natural alignment for `T`; producing a `&[u16]` from an odd address is /// instant UB even if never dereferenced. `#[repr(packed)]` on this wrapper /// drops the alignment requirement to 1, so `&[Unaligned]` is the sound @@ -58,7 +57,7 @@ impl Unaligned { } /// Reinterpret `&[Unaligned]` as `&[T]` once the caller has proven - /// `ptr` is naturally aligned (Zig `@alignCast`). Panics in debug if not. + /// `ptr` is naturally aligned. Panics in debug if not. /// Empty slices need no cast: their dangling pointer has align 1, not /// `align_of::()`, so they are returned as `&[]` directly. #[inline] @@ -96,16 +95,15 @@ impl Unaligned { // ════════════════════════════════════════════════════════════════════════════ // Low-tier primitives hoisted into bun_core. // Forward-referenced as `crate::X` by Global.rs / output.rs / fmt.rs / env.rs. -// Source bodies extracted from the corresponding .zig (ground truth). // ════════════════════════════════════════════════════════════════════════════ // ─── ZStr / WStr / zstr! (from bun_str) ─────────────────────────────────── -// Zig: `[:0]const u8` / `[:0]const u16` — slice with sentinel. Rust models the +// NUL-terminated byte / u16 strings. Rust models the // borrowed forms as DSTs over the byte/u16 slice (NUL not counted in len). // TYPE_ONLY move-down; full impls (from_raw, as_cstr, …) live in bun_str which // re-exports these via `pub use bun_core::{ZStr, WStr}`. -/// Borrowed `[:0]const u8` — bytes are valid UTF-8-ish, len excludes the NUL. +/// Borrowed NUL-terminated byte string — bytes are valid UTF-8-ish, len excludes the NUL. #[repr(transparent)] pub struct ZStr([u8]); @@ -152,7 +150,7 @@ impl ZStr { debug_assert_eq!(buf[len], 0, "ZStr::from_buf: missing NUL at buf[len]"); // SAFETY: `buf[..=len]` is in-bounds (debug-asserted above; release // relies on caller upholding the documented `buf[len] == 0` - // precondition, same contract as Zig `[:0]const u8` slicing). + // precondition). unsafe { Self::from_raw(buf.as_ptr(), len) } } /// Borrow `buf[..buf.len()-1]` as a `&ZStr`, where the last byte of `buf` @@ -160,7 +158,7 @@ impl ZStr { /// most common call shape: a slice that already includes its trailing NUL /// (e.g. a `Vec` with `0` pushed, or `CStr::to_bytes_with_nul`). /// Debug-asserts the trailing NUL; release relies on the documented - /// precondition (same contract as Zig `[:0]const u8` slicing). + /// precondition. #[inline] pub fn from_slice_with_nul(buf: &[u8]) -> &ZStr { debug_assert!(!buf.is_empty(), "ZStr::from_slice_with_nul: empty slice"); @@ -208,7 +206,7 @@ impl ZStr { /// `CStr::from_bytes_with_nul_unchecked` call sites through one audited /// `unsafe`. Debug-asserts no interior NUL (CStr's extra invariant over /// ZStr); release relies on the construction-site contract (path/host - /// bytes never embed NUL — same assumption Zig `[:0]const u8` → C makes). + /// bytes never embed NUL). #[inline] pub fn as_cstr(&self) -> &core::ffi::CStr { debug_assert!( @@ -250,7 +248,7 @@ impl ZStr { // The slice metadata of the returned `Box` covers `bytes.len() + 1` // (i.e. INCLUDES the trailing NUL) so `Drop` deallocates the full // allocation; `as_bytes()` will therefore include the trailing NUL. - // TODO(port): retire once all `Box` fields are migrated to `ZBox`. + // Retire once all `Box` fields are migrated to `ZBox`. pub fn boxed(bytes: &[u8]) -> Box { let mut v = Vec::with_capacity(bytes.len() + 1); v.extend_from_slice(bytes); @@ -263,12 +261,11 @@ impl ZStr { } /// Owned, heap-allocated, NUL-terminated byte string. `.len()` / `Deref` -/// **exclude** the trailing NUL — Zig `[:0]u8` semantics. This is the owned -/// counterpart of `&ZStr`; use it where Zig returned an allocated `[:0]u8`. +/// **exclude** the trailing NUL. This is the owned counterpart of `&ZStr`. #[derive(Clone)] pub struct ZBox(Box<[u8]>); // invariant: last byte == 0 impl Default for ZBox { - /// Zig: `[:0]const u8 = ""` field default — an empty NUL-terminated string. + /// An empty NUL-terminated string. #[inline] fn default() -> Self { ZBox(Box::new([0u8; 1])) @@ -283,8 +280,7 @@ impl ZBox { } ZBox(v.into_boxed_slice()) } - /// Copy `bytes` into a new NUL-terminated allocation. Port of Zig - /// `allocator.dupeZ(u8, bytes)`. + /// Copy `bytes` into a new NUL-terminated allocation. #[inline] pub fn from_bytes(bytes: impl AsRef<[u8]>) -> ZBox { let bytes = bytes.as_ref(); @@ -293,8 +289,7 @@ impl ZBox { v.push(0); ZBox(v.into_boxed_slice()) } - /// Take ownership of `v` and append a trailing NUL. Port of Zig - /// `list.toOwnedSliceSentinel(0)`. + /// Take ownership of `v` and append a trailing NUL. #[inline] pub fn from_vec(mut v: Vec) -> ZBox { v.push(0); @@ -355,8 +350,6 @@ impl core::ops::Deref for ZStr { /// `bun.getenvZ` — read an environment variable. Returns the value as borrowed /// process-static bytes (env block lives for the process). On POSIX wraps /// `libc::getenv`; on Windows scans `environ` case-insensitively. -/// -/// Port of `bun.zig:getenvZ` / `getenvZAnyCase`. pub fn getenv_z(key: &ZStr) -> Option<&'static [u8]> { #[cfg(not(any(unix, windows)))] { @@ -371,16 +364,15 @@ pub fn getenv_z(key: &ZStr) -> Option<&'static [u8]> { return None; } // SAFETY: getenv returns a pointer into the process env block, valid for - // process lifetime (modulo setenv races — same caveat as Zig original). + // process lifetime (modulo setenv races). let len = libc::strlen(p); return Some(core::slice::from_raw_parts(p.cast::(), len)); } #[cfg(windows)] { - // Windows env names are case-insensitive (Zig spec: `getenvZ` on - // Windows delegates to `getenvZAnyCase`). Walk the WTF-8 env block - // populated at startup by `bun_sys::windows::env::convert_env_to_wtf8` - // (main.zig:47). The block is `Box::leak`'d for process lifetime so + // Windows env names are case-insensitive. Walk the WTF-8 env block + // populated at startup by `bun_sys::windows::env::convert_env_to_wtf8`. + // The block is `Box::leak`'d for process lifetime so // `'static` borrows here are sound. getenv_z_any_case(key) } @@ -463,7 +455,7 @@ pub fn getenv_z_any_case(key: &ZStr) -> Option<&'static [u8]> { } } -/// Borrowed `[:0]const u16` (Windows wide string). +/// Borrowed NUL-terminated u16 string (Windows wide string); len excludes the NUL. #[repr(transparent)] pub struct WStr([u16]); @@ -484,8 +476,8 @@ impl WStr { /// form of [`from_raw`] for the dominant call shape: a stack `WPathBuffer` /// filled to `len` with a NUL written at `buf[len]`. The slice bound proves /// `buf[..=len]` lies in one allocation; the NUL is debug-asserted (release - /// relies on the documented `buf[len] == 0` precondition — same contract as - /// Zig `[:0]const u16` slicing). Mirrors [`ZStr::from_buf`]. + /// relies on the documented `buf[len] == 0` precondition). Mirrors + /// [`ZStr::from_buf`]. #[inline] pub fn from_buf(buf: &[u16], len: usize) -> &WStr { debug_assert!(len < buf.len(), "WStr::from_buf: NUL must lie within buf"); @@ -575,8 +567,7 @@ impl AsRef<[u16]> for WStr { } /// `wstr!("lit")` → `&'static [u16; N+1]` (NUL-terminated). Compile-time -/// ASCII→UTF-16LE widening for Windows path / API literals; mirrors Zig -/// `bun.strings.w("lit")` / `std.unicode.utf8ToUtf16LeStringLiteral`. +/// ASCII→UTF-16LE widening for Windows path / API literals. /// /// Restricted to ASCII (`debug_assert` in the const evaluator) — every call /// site is a hard-coded path component (`"node_modules"`, `".git"`, etc.). @@ -599,7 +590,7 @@ macro_rules! wstr { }}; } -/// `zstr!("lit")` → `&'static ZStr`. Mirrors Zig `"lit"` which is `*const [N:0]u8`. +/// `zstr!("lit")` → `&'static ZStr`. #[macro_export] macro_rules! zstr { ($s:literal) => {{ @@ -651,7 +642,7 @@ pub struct Mutex(std::sync::Mutex); /// callers can name it in return types (e.g. `rare_data::ProxyEnvStorage::lock`). pub type MutexGuard<'a, T> = std::sync::MutexGuard<'a, T>; -/// Zig `Guarded(T)` — same wrapper, different spelling. +/// Alias for [`Mutex`]. pub type Guarded = Mutex; impl Mutex { @@ -740,17 +731,15 @@ impl Default for RwLock { } // ─── Path primitives (from bun_paths) ───────────────────────────────────── -// Zig: src/paths/paths.zig lines 13-20. -// Zig uses `std.fs.max_path_bytes` which is platform-dependent. pub const MAX_PATH_BYTES: usize = if cfg!(target_arch = "wasm32") { 1024 } else if cfg!(windows) { - // std.os.windows.PATH_MAX_WIDE * 3 + 1 (UTF-8 worst-case from UTF-16). + // Windows PATH_MAX_WIDE (32767) * 3 + 1 (UTF-8 worst-case from UTF-16). 32767 * 3 + 1 } else if cfg!(any(target_os = "linux", target_os = "android")) { 4096 // Linux libc::PATH_MAX } else { - // macOS / iOS / FreeBSD / OpenBSD / NetBSD / DragonFly / Solaris (std/c.zig PATH_MAX) + // macOS / iOS / FreeBSD / OpenBSD / NetBSD / DragonFly / Solaris PATH_MAX 1024 }; pub const PATH_MAX_WIDE: usize = 32767; @@ -768,7 +757,7 @@ pub type OSPathSliceZ = ZStr; pub use bun_alloc::SEP; -/// Zig: `[MAX_PATH_BYTES]u8` stack buffer (`var buf: bun.PathBuffer = undefined`). +/// `[u8; MAX_PATH_BYTES]` stack buffer for path syscalls. /// /// Canonical definition; `bun_paths::PathBuffer` re-exports this so the two /// crates share ONE nominal type and callers can pass a `bun_paths` buffer to @@ -777,7 +766,7 @@ pub use bun_alloc::SEP; /// NOTE on alignment: `os_path_kernel32` (Windows) reinterprets a /// `&mut PathBuffer` as `&mut [u16]` via [`bytes_as_slice_mut`]. The language /// only guarantees align=1 for `[u8; N]`, so that reinterpret is guarded by a -/// hard `assert!` (mirroring Zig `@alignCast`). We do *not* bump this struct +/// hard `assert!`. We do *not* bump this struct /// to `#[repr(align(2))]` because several call sites reinterpret an arbitrary /// `&mut [u8]` *as* `PathBuffer`, and raising the nominal alignment would /// make *those* casts unsound instead. In practice every `PathBuffer` fed to @@ -787,21 +776,20 @@ pub use bun_alloc::SEP; pub struct PathBuffer(pub [u8; MAX_PATH_BYTES]); impl PathBuffer { pub const ZEROED: Self = Self([0; MAX_PATH_BYTES]); - /// Zig `= undefined`. The bytes are immediately overwritten by the syscall + /// The bytes are immediately overwritten by the syscall /// that fills it, so the initial contents are never observed. /// /// On Windows `MAX_PATH_BYTES` is 98 302 (vs 4 096 Linux / 1 024 macOS), so /// the previous `Self::ZEROED` body here was a ~100 KB `memset` at every /// one of the ~400 call sites — turning hot loops (glob scan, module load, /// stack-trace formatting) into multi-GB zero-fill workloads and timing out - /// the leak/stress tests. Match the Zig spec and leave the bytes uninit. + /// the leak/stress tests. Leave the bytes uninit. #[inline] #[allow(invalid_value, clippy::uninit_assumed_init)] pub fn uninit() -> Self { // SAFETY: `PathBuffer` is `repr(transparent)` over `[u8; N]`; every bit // pattern is a valid `u8`, and callers treat this as a write-only - // scratch buffer (length-tracked) exactly like Zig - // `var buf: bun.PathBuffer = undefined`. No byte is read before being + // scratch buffer (length-tracked). No byte is read before being // written by the consuming syscall / encoder. unsafe { core::mem::MaybeUninit::uninit().assume_init() } } @@ -834,12 +822,12 @@ impl core::ops::DerefMut for PathBuffer { } } -/// Zig: `[PATH_MAX_WIDE]u16`. Same newtype shape as [`PathBuffer`]. +/// `[u16; PATH_MAX_WIDE]` wide path buffer. Same newtype shape as [`PathBuffer`]. #[repr(transparent)] pub struct WPathBuffer(pub [u16; PATH_MAX_WIDE]); impl WPathBuffer { pub const ZEROED: Self = Self([0; PATH_MAX_WIDE]); - /// Zig `= undefined`. See [`PathBuffer::uninit`] — `PATH_MAX_WIDE` is + /// See [`PathBuffer::uninit`] — `PATH_MAX_WIDE` is /// 32 767 `u16`s (~64 KB), and these are allocated per Windows syscall /// for UTF-8→UTF-16 path conversion, so zero-initialising dominated the /// hot path on Windows. @@ -848,8 +836,7 @@ impl WPathBuffer { pub fn uninit() -> Self { // SAFETY: `repr(transparent)` over `[u16; N]`; every bit pattern is a // valid `u16`. Callers treat this as a write-only scratch buffer and - // track the written length out-of-band — mirrors Zig - // `var wbuf: bun.WPathBuffer = undefined`. + // track the written length out-of-band. unsafe { core::mem::MaybeUninit::uninit().assume_init() } } /// Inherent `as_slice` so `wbuf.as_slice()` resolves here instead of the @@ -887,8 +874,7 @@ pub type OSPathBuffer = WPathBuffer; #[cfg(not(windows))] pub type OSPathBuffer = PathBuffer; -/// Zig: `bun.Dirname.dirname(u8, path)` → `std.fs.path.dirnamePosix` / -/// `dirnameWindows`. Faithful port (handles trailing-sep stripping and root). +/// Directory portion of `path` (handles trailing-sep stripping and root). pub fn dirname(path: &[u8]) -> Option<&[u8]> { use crate::path_sep::is_sep_native as is_sep; @@ -914,19 +900,17 @@ pub fn dirname(path: &[u8]) -> Option<&[u8]> { while i > root_end { i -= 1; if is_sep(path[i]) { - // Zig `std.fs.path.dirnamePosix/Windows` returns up to (excluding) - // the separator found — it does NOT collapse a preceding run of - // separators, so `/foo//bar` → `/foo/`. Preserve that contract for - // re-export parity with `bun_paths::dirname`. + // Return up to (excluding) the separator found — do NOT collapse a + // preceding run of separators, so `/foo//bar` → `/foo/`. Preserve + // that contract for re-export parity with `bun_paths::dirname`. return Some(&path[..i]); } } // No separator AFTER root, but content past it (e.g. "/foo", "C:\foo"): - // Zig returns the root prefix iff the root itself ends in a separator + // return the root prefix iff the root itself ends in a separator // (`"/foo"` → `"/"`, `"C:\\foo"` → `"C:\\"`). A bare drive prefix with no - // separator (`"C:foo"`, root_end==2) falls through to `None`, matching - // `std.fs.path.dirnameWindows`. Root-only inputs ("/", "C:\") have - // `end == root_end` and also fall through. + // separator (`"C:foo"`, root_end==2) falls through to `None`. Root-only + // inputs ("/", "C:\") have `end == root_end` and also fall through. if root_end > 0 && end > root_end && is_sep(path[root_end - 1]) { return Some(&path[..root_end]); } @@ -938,7 +922,7 @@ pub fn dirname(path: &[u8]) -> Option<&[u8]> { // Full method set (close, makeLibUVOwned, …) stays in bun_sys which re-exports // `pub use bun_core::Fd as FD;` and adds inherent impls there. -// Zig backing_int (fd.zig:1): c_int on posix, u64 on Windows. +// Backing int: c_int on posix, u64 on Windows. #[cfg(not(windows))] type FdBacking = i32; #[cfg(windows)] @@ -948,7 +932,7 @@ type FdBacking = u64; #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct Fd(pub FdBacking); -// Zig packed struct(u64) { value: u63, kind: u1 } — fields are LSB-first, so +// Packed u64 { value: u63, kind: u1 } — fields are LSB-first, so // `value` is bits 0..63, `kind` is bit 63. (.system=0, .uv=1) #[cfg(windows)] const FD_KIND_BIT: u64 = 1u64 << 63; @@ -956,14 +940,13 @@ const FD_KIND_BIT: u64 = 1u64 << 63; const FD_VALUE_MASK: u64 = FD_KIND_BIT - 1; impl Fd { - /// Zig fd.zig:33-35: { kind=.system, value.as_system = minInt(field_type) }. /// posix: minInt(c_int); windows: minInt(u63) = 0, kind=0 → all-zero u64. #[cfg(not(windows))] pub const INVALID: Fd = Fd(i32::MIN); #[cfg(windows)] pub const INVALID: Fd = Fd(0); - /// Zig `bun.invalid_fd` / `FD.invalid` — function form of [`Fd::INVALID`] + /// Function form of [`Fd::INVALID`] /// for call sites that read better as a constructor (`Fd::invalid()`). #[inline] pub const fn invalid() -> Fd { @@ -991,7 +974,6 @@ impl Fd { #[inline] pub fn from_system(h: *mut core::ffi::c_void) -> Fd { // kind=.system (bit 63 = 0); WindowsHandleNumber is u63. - // Zig fd.zig:48 asserts `@intFromPtr(value) <= maxInt(u63)`. debug_assert!((h as u64) <= FD_VALUE_MASK); Fd((h as u64) & FD_VALUE_MASK) } @@ -1186,7 +1168,7 @@ impl Fd { pub fn decode_windows(self) -> DecodeWindows { match self.kind() { FdKind::System => { - // Zig `numberToHandle`: `if (handle == 0) return INVALID_HANDLE_VALUE`. + // A stored value of 0 decodes to INVALID_HANDLE_VALUE. let n = self.value_as_system(); let h = if n == 0 { usize::MAX } else { n as usize }; DecodeWindows::Windows(h as *mut core::ffi::c_void) @@ -1196,12 +1178,12 @@ impl Fd { } } - /// Zig `FD.makeLibUVOwned` (`fd.zig`): on Windows, convert a system-kind + /// On Windows, convert a system-kind /// `Fd` (raw `HANDLE`) into a libuv-kind `Fd` (CRT `_open_osfhandle`-backed /// `int`) so libuv `uv_fs_*` APIs can consume it. uv-kind passes through. /// On POSIX this is the identity (libuv fd == posix fd). /// - /// Returns `Err(())` (= Zig's `error.SystemFdQuotaExceeded`) when + /// Returns `Err(())` when /// `uv_open_osfhandle` returns `-1`; the caller decides whether to close /// the original handle (see `make_libuv_owned_for_syscall`). #[inline] @@ -1293,13 +1275,13 @@ impl Fd { } } -/// `std.posix.fd_t` — `c_int` on POSIX, `HANDLE` (`*anyopaque`) on Windows. +/// Native file-descriptor type — `c_int` on POSIX, `HANDLE` on Windows. #[cfg(not(windows))] pub type FdNative = i32; #[cfg(windows)] pub type FdNative = *mut core::ffi::c_void; -/// Zig `Kind` — tag in bit 63 on Windows, `enum(u0)` (zero-width) on POSIX. +/// Fd kind — tag in bit 63 on Windows; single-variant on POSIX. #[cfg(not(windows))] #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -1351,7 +1333,7 @@ impl Stdio { } } -/// Niche-packed `Option` (`enum(backing_int) { none = @bitCast(invalid), _ }`). +/// Niche-packed `Option`: the invalid-fd bit pattern is the `none` sentinel. /// Use instead of encoding the invalid value directly. #[repr(transparent)] #[derive(Copy, Clone, Eq, PartialEq)] @@ -1464,9 +1446,10 @@ pub unsafe fn fd_path_raw(fd: Fd, buf: *mut u8, cap: usize) -> isize { } /// Wide-char fd → path (Windows `GetFinalPathNameByHandleW`). Returns code -/// units written (>0), <0 on error, 0 on non-Windows. Body is a single -/// kernel32 call, so it lives at T0 — moved down from `bun_sys` per -/// PORTING.md (no cross-crate extern). +/// units written (>0), -1 on lookup failure, +/// -2 when the buffer is too small, 0 on +/// non-Windows. Body is a single kernel32 call, so it lives at T0 — moved +/// down from `bun_sys` per PORTING.md (no cross-crate extern). /// /// SAFETY: `buf` must be valid for `cap` writable `u16` units. pub unsafe fn fd_path_raw_w(fd: Fd, buf: *mut u16, cap: usize) -> isize { @@ -1483,13 +1466,17 @@ pub unsafe fn fd_path_raw_w(fd: Fd, buf: *mut u16, cap: usize) -> isize { // VOLUME_NAME_DOS (0) — matches `bun_sys::windows::GetFinalPathNameByHandle` default. // SAFETY: buf has `cap` u16 units; handle from Fd::native(). let n = unsafe { GetFinalPathNameByHandleW(fd.native(), buf, cap as u32, 0) } as usize; - // Zig `bun.windows.GetFinalPathNameByHandle`: `if (return_length >= - // out_buffer.len) return error.NameTooLong;` — `>=` because a return + // The size check below is `>=` because a return // value equal to `cap` is the buffer-too-small sentinel (required size // including NUL), not a successful write of `cap` chars. - if n == 0 || n >= cap { + if n == 0 { + // Lookup failure. return -1; } + if n >= cap { + // Buffer too small. + return -2; + } // Strip the `\\?\` prefix if present so callers see a plain DOS path // (matches `bun_sys::windows::GetFinalPathNameByHandle` post-processing). // Work entirely through raw-pointer reads/writes — never form a `&[u16]` @@ -1566,7 +1553,7 @@ impl core::fmt::Display for Fd { } } -/// Zig fd.zig module-level statics + Windows libuv/PEB FFI shims (T0 → no +/// Fd module-level statics + Windows libuv/PEB FFI shims (T0 → no /// crate dep, just `extern` symbols; libuv is linked into the final binary). pub mod fd { use super::Fd; @@ -1600,9 +1587,8 @@ pub mod fd { pub use crate::windows_sys::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}; #[cfg(windows)] pub fn is_stdio_handle(id: u32, handle: *mut c_void) -> bool { - // Zig: `const h = std.os.windows.GetStdHandle(id) catch return false; - // return handle == h;` — the Zig wrapper maps both NULL and - // INVALID_HANDLE_VALUE to an error, so use the Option-returning + // The `GetStdHandle` wrapper maps both NULL and + // INVALID_HANDLE_VALUE to `None`, so use the Option-returning // wrapper here. Without the INVALID_HANDLE_VALUE filter, a detached // console (GetStdHandle → INVALID_HANDLE_VALUE) compared against // `Fd::INVALID.native()` (= INVALID_HANDLE_VALUE) would spuriously @@ -1642,9 +1628,7 @@ pub mod fd { } #[cfg(windows)] pub fn windows_current_directory_handle() -> *mut c_void { - // Zig spec (`fd.zig:70`): `FD.cwd() = .fromNative(std.fs.cwd().fd)`, - // and Zig's `std.fs.cwd()` on Windows reads - // `peb().ProcessParameters.CurrentDirectory.Handle`. Offset 0x48 on + // Reads `peb().ProcessParameters.CurrentDirectory.Handle`. Offset 0x48 on // x64, asserted in `bun_core::windows_sys`. The OS updates this handle // on `SetCurrentDirectoryW`, so re-read on every call rather than // caching. @@ -1658,10 +1642,10 @@ pub mod fd { } // ─── FileKind / Mode / kind_from_mode (from bun_sys) ────────────────────── -// Zig: src/sys/sys.zig — pure S_IFMT arithmetic, no syscalls (libarchive_sys req). -pub type Mode = u32; // std.posix.mode_t +// Pure S_IFMT arithmetic, no syscalls (libarchive_sys req). +pub type Mode = u32; // POSIX `mode_t` -/// `stat` mode-flag constants and predicates (Zig: `std.posix.S`). +/// `stat` mode-flag constants and predicates. /// /// Values are POSIX-standard octal — frozen since 1988 and identical across /// linux/darwin/freebsd. Typed against the cross-platform `Mode` (= `u32`) @@ -1821,24 +1805,24 @@ pub mod io { } // ════════════════════════════════════════════════════════════════════════ - // trait Write — canonical byte-level write sink (port of Zig - // `std.Io.Writer`). Lives in `bun_core` (not `bun_io`) so leaf crates + // trait Write — canonical byte-level write sink. + // Lives in `bun_core` (not `bun_io`) so leaf crates // below `bun_io` in the dep graph — `bun_string`, `bun_collections`, // `bun_url` — can implement it without an upward dep. `bun_io` re-exports // this verbatim as `bun_io::Write`. // ════════════════════════════════════════════════════════════════════════ use core::fmt; - /// Byte-level write sink — port of Zig `std.Io.Writer`. + /// Byte-level write sink. /// /// Only [`write_all`](Write::write_all) is required; every other method has /// a default in terms of it. Object-safe: `&mut dyn Write` works. Generic /// helpers that would break object safety carry `where Self: Sized`. pub trait Write { - /// Write the entire buffer. Zig: `writeAll`. + /// Write the entire buffer. fn write_all(&mut self, buf: &[u8]) -> Result<(), crate::Error>; - /// Flush any internal buffer to the underlying sink. Zig: `flush`. + /// Flush any internal buffer to the underlying sink. /// Unbuffered sinks leave the default no-op. #[inline] fn flush(&mut self) -> Result<(), crate::Error> { @@ -1847,8 +1831,7 @@ pub mod io { /// Total bytes written to this sink so far. /// - /// Only implemented for in-memory / counting sinks; the default panics, - /// matching the Zig `@panic("css: got bad writer type")` fallthrough. + /// Only implemented for in-memory / counting sinks; the default panics. #[inline] fn written_len(&self) -> usize { panic!("io::Write::written_len: writer does not track bytes written"); @@ -1856,7 +1839,7 @@ pub mod io { // ── provided helpers ──────────────────────────────────────────────── - /// Zig: `writeByte`. + /// Write a single byte. #[inline] fn write_byte(&mut self, byte: u8) -> Result<(), crate::Error> { self.write_all(core::slice::from_ref(&byte)) @@ -1868,7 +1851,7 @@ pub mod io { self.write_all(s.as_bytes()) } - /// Write `n` copies of `byte`. Zig: `splatByteAll` / `writeByteNTimes`. + /// Write `n` copies of `byte`. fn splat_byte_all(&mut self, byte: u8, n: usize) -> Result<(), crate::Error> { let chunk = [byte; 256]; let mut remain = n; @@ -1880,7 +1863,7 @@ pub mod io { Ok(()) } - /// Formatted write. Zig: `print(fmt, args)`. Enables `write!(w, ...)`. + /// Formatted write. Enables `write!(w, ...)`. fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<(), crate::Error> { struct Bridge<'a, W: ?Sized> { sink: &'a mut W, @@ -1908,14 +1891,13 @@ pub mod io { } } - /// Alias for [`write_fmt`](Write::write_fmt) under the Zig spelling. + /// Alias for [`write_fmt`](Write::write_fmt). #[inline] fn print(&mut self, args: fmt::Arguments<'_>) -> Result<(), crate::Error> { self.write_fmt(args) } /// Write an integer in little-endian byte order. - /// Zig: `writeInt(T, val, .little)`. #[inline] fn write_int_le(&mut self, val: I) -> Result<(), crate::Error> where @@ -1970,7 +1952,7 @@ pub mod io { } } - /// In-memory growable sink. Zig: `std.Io.Writer.Allocating`. + /// In-memory growable sink. impl Write for Vec { #[inline] fn write_all(&mut self, buf: &[u8]) -> Result<(), crate::Error> { @@ -2103,8 +2085,7 @@ impl Version { /// (e.g. HTTP-thread-only buffers, main-thread-only CLI state) or externally /// synchronized. For anything actually shared across threads, use /// `Atomic*` / `OnceLock` / `Mutex` instead — `RacyCell` is the last resort -/// for scratch buffers and FFI-shaped globals where the Zig already proved -/// thread-affinity. +/// for scratch buffers and FFI-shaped globals with proven thread-affinity. #[repr(transparent)] pub struct RacyCell(core::cell::Cell); // SAFETY: by construction, callers promise external synchronization or @@ -2166,7 +2147,7 @@ impl Default for RacyCell { // `locked_at` is `Cell` so `lock()`/`lock_or_assert()` can take `&self` // (callers like `RefCount::assert_single_threaded` only have `&self`). The // whole point of ThreadLock is asserting single-threaded access, so the -// unsynchronized write to `locked_at` is exactly the Zig semantics — if two +// unsynchronized write to `locked_at` is fine — if two // threads race here, the `owning_thread.swap` panic fires first. pub struct ThreadLock { #[cfg(debug_assertions)] @@ -2196,8 +2177,7 @@ impl ThreadLock { s.lock(); s } - /// Zig `initLockedIfNonComptime` — Zig comptime evaluation has no thread; - /// in Rust there is no comptime execution, so this is just `init_locked`. + /// Same as [`Self::init_locked`]; kept under the legacy name. #[inline] pub fn init_locked_if_non_comptime() -> Self { Self::init_locked() @@ -2212,7 +2192,7 @@ impl ThreadLock { self.lock(); ThreadLockGuard(core::ptr::from_ref::(self)) } - /// Zig `lockOrAssert` — acquire if unlocked, else assert this thread holds it. + /// Acquire if unlocked, else assert this thread holds it. #[inline] pub fn lock_or_assert(&self) { #[cfg(debug_assertions)] @@ -2258,7 +2238,7 @@ impl ThreadLock { pub fn unlock(&self) { #[cfg(debug_assertions)] { - self.assert_locked(); // Zig: assert current thread holds it before reset. + self.assert_locked(); // assert current thread holds it before reset. self.owning_thread .store(INVALID_THREAD_ID, core::sync::atomic::Ordering::Release); // assert_locked above proved we are the unique holder. @@ -2320,7 +2300,7 @@ fn thread_id() -> u64 { crate::thread_id::current() as u64 } -// ─── StackCheck (from bun.zig) ─────────────────────────────────────────── +// ─── StackCheck ──────────────────────────────────────────────────────────── // Thin FFI wrapper; configure_thread() is all output.rs needs. #[derive(Clone, Copy)] pub struct StackCheck { @@ -2340,7 +2320,7 @@ unsafe extern "C" { safe fn clock_gettime(clk_id: libc::clockid_t, tp: &mut libc::timespec) -> core::ffi::c_int; } impl Default for StackCheck { - /// Zig `.{}` — `cached_stack_end` defaults to `0`, so + /// `cached_stack_end` defaults to `0`, so /// `is_safe_to_recurse()` always reports true until `init`/`update`. #[inline] fn default() -> Self { @@ -2365,10 +2345,10 @@ impl StackCheck { self.cached_stack_end = Bun__StackCheck__getMaxStack() as usize; } /// Is there enough stack space to safely recurse? - /// Zig: `> 256K` on Windows, `> 128K` elsewhere (bun.zig:3762). + /// Threshold: `> 256K` on Windows, `> 128K` elsewhere. #[inline] pub fn is_safe_to_recurse(self) -> bool { - // Zig uses `-|` (saturating sub): if probe < end (already past limit), + // Saturating sub: if probe < end (already past limit), // result saturates to 0 → "not safe". wrapping_sub would yield a huge // positive and incorrectly return true. let remaining = Self::frame_address().saturating_sub(self.cached_stack_end); @@ -2402,8 +2382,8 @@ impl StackCheck { /// use-after-return detection is on, which makes the comparison against /// `cached_stack_end` useless. /// - /// Zig uses `@frameAddress()` (rbp/x29), but Zig builds always keep frame - /// pointers. Rust release builds omit them, leaving rbp/x29 as a + /// Reading the frame pointer (rbp/x29) would not work either: + /// Rust release builds omit frame pointers, leaving rbp/x29 as a /// general-purpose register with arbitrary contents — reading it there /// makes `is_safe_to_recurse()` spuriously fail at depth 0. The stack /// pointer is always valid and is what we actually want to measure. @@ -2436,15 +2416,14 @@ impl StackCheck { } // ────────────────────────────────────────────────────────────────────────── -// Small helpers from src/bun.zig that downstream crates need. +// Small helpers that downstream crates need. // ────────────────────────────────────────────────────────────────────────── -/// Zig `bun.Generation` (bun.zig:1926) — bumped each rebuild/rescan to -/// invalidate stale cache entries. +/// Bumped each rebuild/rescan to invalidate stale cache entries. pub type Generation = u16; // ── Ordinal ─────────────────────────────────────────────────────────────── -// Port of `OrdinalT(c_int)` (bun.zig:3421). ABI-equivalent of WTF::OrdinalNumber: +// ABI-equivalent of WTF::OrdinalNumber: // a zero-based index where -1 means "invalid". Represented as a transparent // newtype rather than a Rust enum so the full `c_int` range round-trips across // FFI without UB. @@ -2497,8 +2476,7 @@ impl Default for Ordinal { } // ── Once ────────────────────────────────────────────────────────────────── -// Port of `bun.Once(f)` (bun.zig:3637). Zig parameterizes over a comptime fn -// and stores the payload; Rust callers use two shapes: +// One-shot initialization cell. Callers use two shapes: // * `Once` — fn supplied at `.call(f)` / `.get_or_init(f)` time // * `Once T>` — fn supplied at construction (PackageManagerDirectories.rs) // @@ -2507,15 +2485,13 @@ impl Default for Ordinal { // `OnceLock::initialize` + 30 `LazyLock` monomorphizations (~36.7 KB) whose // shared callee `std::sys::sync::once::futex::Once::call` lives in libstd's // own CGU — every hot-path `get_or_init` paid a cross-CGU call + futex-aware -// state machine even when the value was already initialised. The Zig original -// is a plain `bool` flag + payload; this matches it: post-init reads are one -// Acquire load + cmp inlined into the caller. Pattern proven at +// state machine even when the value was already initialised. Here post-init +// reads are one Acquire load + cmp inlined into the caller. Pattern proven at // `bun_alloc/lib.rs::bss_heap_init`'s accessor macro. // // Contention: startup is single-threaded for every current call site; the // rare cross-thread race spins on `yield_now()` (no futex). No poisoning — -// a panic mid-init resets to UNINIT so the next call retries (Zig has no -// poisoning either). +// a panic mid-init resets to UNINIT so the next call retries. const ONCE_UNINIT: u8 = 0; const ONCE_BUSY: u8 = 1; const ONCE_DONE: u8 = 2; @@ -2600,7 +2576,7 @@ impl Once { fn init_slow(&self, f: impl FnOnce() -> T) -> &T { if once_claim_slow(&self.state) { // Reset to UNINIT if `f` unwinds so a later retry isn't deadlocked - // on a permanently-BUSY slot (Zig has no poisoning; neither do we). + // on a permanently-BUSY slot (no poisoning). struct Reset<'a>(&'a core::sync::atomic::AtomicU8); impl Drop for Reset<'_> { #[inline] @@ -2712,7 +2688,6 @@ macro_rules! run_once { } // ── Pollable / is_readable / is_writable ────────────────────────────────── -// Port of `bun.PollFlag` + `bun.isReadable` / `bun.isWritable` (bun.zig:637). // Named `Pollable` to match the original draft callers (io/PipeReader.rs). // D050 dedup: this is the single canonical copy; `bun::`/`bun_sys::` re-export. #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -2721,11 +2696,11 @@ pub enum Pollable { NotReady, Hup, } -/// Zig `bun.PollFlag` — original name kept as an alias. +/// Alias for [`Pollable`]. pub type PollFlag = Pollable; impl Pollable { - /// Zig `@tagName(rc)` — lowercase tag name for the `[sys]` debug log. + /// Lowercase tag name for the `[sys]` debug log. #[inline] pub const fn tag_name(self) -> &'static str { match self { @@ -2736,12 +2711,11 @@ impl Pollable { } } -// Zig `global_scope_log = sys.syslog` (bun.zig:636) → `Output.scoped(.SYS, .visible)`. // bun_core sits below bun_sys, so we re-declare the scope locally instead of // pulling `bun_sys::syslog!` (tier inversion). Same `[sys]` tag at runtime. crate::declare_scope!(SYS, visible); -/// Non-blocking poll for readability. POSIX-only (Zig panics on Windows). +/// Non-blocking poll for readability. POSIX-only. #[cfg(not(windows))] pub fn is_readable(fd: Fd) -> Pollable { debug_assert!(fd.is_valid()); @@ -2776,7 +2750,7 @@ pub fn is_readable(fd: Fd) -> Pollable { } #[cfg(windows)] pub fn is_readable(_fd: Fd) -> Pollable { - // Zig bun.zig:639 — `@panic("TODO on Windows")`; no callers reach this on Windows. + // No callers reach this on Windows. panic!("TODO on Windows"); } @@ -2784,7 +2758,6 @@ pub fn is_readable(_fd: Fd) -> Pollable { #[cfg(not(windows))] pub fn is_writable(fd: Fd) -> Pollable { debug_assert!(fd.is_valid()); - // bun.zig:692 — POLLOUT | POLLERR | POLLHUP. let mut polls = [libc::pollfd { fd: fd.native(), events: libc::POLLOUT | libc::POLLERR | libc::POLLHUP, @@ -2816,11 +2789,11 @@ pub fn is_writable(fd: Fd) -> Pollable { } #[cfg(windows)] pub fn is_writable(fd: Fd) -> Pollable { - // Zig bun.zig:668-685 — WSAPoll(POLLWRNORM). bun_core can't depend on + // bun_core can't depend on // bun_sys (tier inversion), so go to bun_windows_sys::ws2_32 directly. use bun_windows_sys::ws2_32; let mut polls = [ws2_32::WSAPOLLFD { - // HANDLE → SOCKET pointer reinterpretation; matches Zig `fd.asSocketFd()`. + // HANDLE → SOCKET pointer reinterpretation. fd: fd.native() as usize, events: ws2_32::POLLWRNORM, revents: 0, @@ -2835,8 +2808,7 @@ pub fn is_writable(fd: Fd) -> Pollable { result, polls[0].revents ); - // PORT NOTE: faithful port of bun.zig:679 — yes, the `WRNORM`-set branch - // returns `.hup` (not `.ready`). Kept verbatim to match upstream behaviour. + // Yes, the `WRNORM`-set branch returns `.hup` (not `.ready`); intentional. if result && (polls[0].revents & ws2_32::POLLWRNORM) != 0 { Pollable::Hup } else if result { @@ -2847,11 +2819,11 @@ pub fn is_writable(fd: Fd) -> Pollable { } // ── csprng ──────────────────────────────────────────────────────────────── -// Zig calls `BoringSSL.c.RAND_bytes` (bun.zig:621). bun_core sits below +// bun_core sits below // boringssl_sys in the crate graph, so we go to the OS CSPRNG directly: // getrandom(2) on Linux, SecRandomCopyBytes/getentropy on Darwin, // RtlGenRandom on Windows. All are the same entropy source BoringSSL seeds -// from. PERF(port): if a hot path needs the BoringSSL DRBG, install a +// from. PERF: if a hot path needs the BoringSSL DRBG, install a // vtable hook from bun_runtime at startup. pub fn csprng(bytes: &mut [u8]) { #[cfg(any(target_os = "linux", target_os = "android"))] @@ -2905,20 +2877,20 @@ pub fn csprng(bytes: &mut [u8]) { } // ── self_exe_path ───────────────────────────────────────────────────────── -// Port of `bun.selfExePath` (bun.zig:3011). Memoized into a process-lifetime +// Memoized into a process-lifetime // static buffer; thread-safe via `Once`. Returns a `&'static ZStr`. pub fn self_exe_path() -> Result<&'static ZStr, crate::Error> { static CELL: Once> = Once::new(); let r = CELL.get_or_init(|| { let path = std::env::current_exe().map_err(crate::Error::from)?; - // PORT NOTE: Zig's `std.fs.selfExePath` resolves symlinks. Rust's - // `current_exe()` already does on Linux (`readlink /proc/self/exe`), + // Symlink resolution: Rust's + // `current_exe()` already resolves on Linux (`readlink /proc/self/exe`), // but on Darwin it returns the raw `_NSGetExecutablePath` result and on // Windows it returns the raw `GetModuleFileNameW` result — neither // realpaths, so a symlinked argv0 (Darwin) or an un-normalized // `C:\a\.\b\bun.exe` load path (Windows) leaks through to - // `process.execPath` / `process.argv[0]`. Zig's Windows impl calls - // `realpathW` (GetFinalPathNameByHandle); match that here so parent and + // `process.execPath` / `process.argv[0]`. Realpath + // (GetFinalPathNameByHandle on Windows) here so parent and // child agree on argv[0] regardless of how the binary was invoked // (test/js/node/process/process-args.test.js, // test/js/node/test/parallel/test-process-execpath.js). @@ -2931,17 +2903,16 @@ pub fn self_exe_path() -> Result<&'static ZStr, crate::Error> { } #[cfg(windows)] { - // PORT NOTE: Zig stored the WTF-8 form. `into_string()` rejects unpaired + // Store the WTF-8 form. `into_string()` rejects unpaired // surrogates; fall back to the lossy form (Windows exe paths are valid // Unicode in practice). let mut s = path .into_os_string() .into_string() .unwrap_or_else(|os| os.to_string_lossy().into_owned()); - // `canonicalize()` on Windows returns a verbatim `\\?\` path; Zig's - // `realpathW` strips that back to a plain DOS path before WTF-8 - // encoding, so do the same (Node's `process.execPath` is never - // verbatim-prefixed). + // `canonicalize()` on Windows returns a verbatim `\\?\` path; strip + // that back to a plain DOS path before WTF-8 encoding (Node's + // `process.execPath` is never verbatim-prefixed). if let Some(rest) = s.strip_prefix(r"\\?\UNC\") { s = format!(r"\\{}", rest); } else if let Some(rest) = s.strip_prefix(r"\\?\") { @@ -2957,7 +2928,7 @@ pub fn self_exe_path() -> Result<&'static ZStr, crate::Error> { } // ── get_thread_count ────────────────────────────────────────────────────── -// Port of `bun.getThreadCount` (bun.zig:3597). Clamped to [2, 1024]; honours +// Clamped to [2, 1024]; honours // UV_THREADPOOL_SIZE / GOMAXPROCS overrides. pub fn get_thread_count() -> u16 { static CELL: Once = Once::new(); @@ -2978,7 +2949,7 @@ pub fn get_thread_count() -> u16 { } // Windows: `getenv_z` is currently a no-op (no narrow C // environ to borrow from); honour the override via - // `std::env::var` so behaviour matches Zig `bun.getThreadCount` + // `std::env::var` so behaviour matches // on all platforms. POSIX keeps the borrow path above. #[cfg(windows)] if let Ok(s) = std::env::var( @@ -2995,14 +2966,13 @@ pub fn get_thread_count() -> u16 { None }; let raw = from_env().unwrap_or_else(|| { - // Zig (bun.zig:3621) calls `jsc.wtf.numberOfProcessorCores()` — // `WTF::numberOfProcessorCores()` → sysconf(_SC_NPROCESSORS_ONLN) // on POSIX / GetSystemInfo on Windows. **Not** the same as // `std::thread::available_parallelism()`, which on Linux also // consults sched_getaffinity + cgroup cpu.max quota; on // cgroup-limited CI runners or P/E-core machines the two diverge, // changing bundler `max_threads` (and per-thread mimalloc arena - // RSS) vs the Zig binary. Declare the C symbol locally — `jsc` + // RSS). Declare the C symbol locally — `jsc` // is above `bun_core` in the crate DAG so we can't `use` it, but // the symbol is always linked (wtf-bindings.cpp). unsafe extern "C" { @@ -3015,9 +2985,8 @@ pub fn get_thread_count() -> u16 { } // ── errno_to_zig_err ────────────────────────────────────────────────────── -// Port of `bun.errnoToZigErr` (bun.zig:2854). Zig indexes into a comptime -// `errno_map: [N]anyerror`; the Rust intern table reproduces that mapping in -// `Error::from_errno` (errno → tag name → interned code). +// The intern table maps in `Error::from_errno` +// (errno → tag name → interned code). #[inline] pub fn errno_to_zig_err(errno: i32) -> crate::Error { debug_assert!(errno != 0); @@ -3025,9 +2994,7 @@ pub fn errno_to_zig_err(errno: i32) -> crate::Error { } // ── time ────────────────────────────────────────────────────────────────── -// Port of `std.time` (vendor/zig/lib/std/time.zig:80-107) — the full -// `comptime_int` constant ladder plus `{nano,milli,}timestamp()`. Zig's -// `comptime_int` coerces to any numeric type; Rust callers `as`-cast at the +// Time-unit constants plus `{nano,milli,}timestamp()`. Callers `as`-cast at the // use-site (`NS_PER_S as i128`, `MS_PER_S as f64`, …). Every value fits in // `u64` (and the ≤per-second constants in `i32`), so all such casts — // including to `f64` — are lossless. @@ -3049,7 +3016,7 @@ pub mod time { // s pub const S_PER_DAY: u32 = 86_400; - /// `std.time.nanoTimestamp()` — wall-clock nanoseconds since the Unix epoch. + /// Wall-clock nanoseconds since the Unix epoch. #[inline] pub fn nano_timestamp() -> i128 { #[cfg(unix)] @@ -3070,18 +3037,18 @@ pub mod time { } } } - /// `std.time.milliTimestamp()` + /// Wall-clock milliseconds since the Unix epoch. #[inline] pub fn milli_timestamp() -> i64 { (nano_timestamp() / NS_PER_MS as i128) as i64 } - /// `std.time.timestamp()` — wall-clock seconds since the Unix epoch. + /// Wall-clock seconds since the Unix epoch. #[inline] pub fn timestamp() -> i64 { (nano_timestamp() / NS_PER_S as i128) as i64 } - /// `std.time.Timer` — monotonic stopwatch. + /// Monotonic stopwatch. #[derive(Clone, Copy, Debug)] pub struct Timer { start: std::time::Instant, @@ -3112,14 +3079,13 @@ pub mod time { } // ── runtime_embed_file ──────────────────────────────────────────────────── -// Port of `bun.runtimeEmbedFile` (bun.zig:2938). The Zig version comptime- -// captures `sub_path` to manufacture a per-call-site `static once` cache; Rust -// can't do that from a plain fn without leaking, so the canonical port is the +// A per-call-site `static once` cache cannot be manufactured +// from a plain fn without leaking, so the canonical form is the // `runtime_embed_file!` macro below (per-site `OnceLock` — sanctioned // by PORTING.md §Forbidden, "true process-lifetime singleton"). The fn form is // kept so existing draft callers type-check; it's only reachable when the -// `codegen_embed` feature is off (debug fast-iteration), where panicking with a -// migration hint is the same UX as the Zig `Output.panic` on read failure. +// `codegen_embed` feature is off (debug fast-iteration), where it panics with +// a migration hint. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum EmbedKind { Codegen, @@ -3160,8 +3126,8 @@ pub fn __runtime_embed_load(kind: EmbedKind, sub: &'static str) -> String { }) } -/// Per-call-site embedded file. `($root, $sub_path)` mirrors the Zig -/// signature; `$root` must be one of the bare idents `Codegen` / +/// Per-call-site embedded file. +/// `$root` must be one of the bare idents `Codegen` / /// `CodegenEager` / `Src` / `SrcEager` and `$sub_path` a string literal. /// /// The `cfg(bun_codegen_embed)` split lives **inside** the macro so call @@ -3223,12 +3189,11 @@ macro_rules! __runtime_embed_impl { } // ── StringBuilder ───────────────────────────────────────────────────────── -// Port of src/string/StringBuilder.zig. Count-then-allocate-then-append arena +// Count-then-allocate-then-append arena // for building a single contiguous buffer. Allocator param dropped per // PORTING.md §Allocators (always `bun.default_allocator`). // -// PORT NOTE: returned sub-slices borrow `*self`, but in Zig they alias the -// final `allocated_slice()` and outlive the builder. To keep that pattern +// Returned sub-slices borrow `*self`. To let spans outlive the builder // without self-referential lifetimes, callers stash `(offset, len)` via // `StringPointer` (see install/hosted_git_info.rs). // @@ -3286,9 +3251,9 @@ impl PartialEq<&[u8; N]> for ZStr { } } -// ── Hasher trait (Zig "anytype with .update([]const u8)") ───────────────── -// Used by `bun_core::write_any_to_hasher` and bundler/css hashing. Mirrors -// the minimal Zig hasher protocol — *not* `core::hash::Hasher` because Bun's +// ── Hasher trait ─────────────────────────────────────────────────────────── +// Used by `bun_core::write_any_to_hasher` and bundler/css hashing. +// *Not* `core::hash::Hasher` because Bun's // hashers (Wyhash, XxHash64, sha1) expose `.update(&[u8])` + `.final()`. pub trait Hasher { fn update(&mut self, bytes: &[u8]); @@ -3306,8 +3271,7 @@ impl Hasher for H { /// direct `bytemuck` dep. pub use bytemuck::NoUninit; -/// Port of Zig `std.mem.asBytes(&v)`: reinterpret a value's storage as a -/// borrowed byte slice. +/// Reinterpret a value's storage as a borrowed byte slice. /// /// Safe: the [`bytemuck::NoUninit`] bound statically guarantees `T` is `Copy`, /// `'static`, and contains no uninitialized (padding) bytes, so every byte of @@ -3328,20 +3292,16 @@ pub fn bytes_of_mut(v: &mut T) -> &mut [u8] { } // ─── Slice reinterpretation (canonical) ─────────────────────────────────────── -// Port of Zig `bun.reinterpretSlice` / `std.mem.bytesAsSlice` / `sliceAsBytes`. -// Zig has ONE polymorphic `reinterpretSlice(comptime T, slice: anytype)` that -// handles const+mut via comptime; Rust splits by mutability and offers two -// safety surfaces: +// Split by mutability, with two safety surfaces: // - `cast_slice` / `cast_slice_mut` → SAFE, bytemuck-bounded, panics on // misalign or `len % size_of::() != 0`. Use for Pod↔Pod (u8↔u16 etc.). // - `bytes_as_slice_mut` → UNSAFE escape hatch, unbounded `T`, -// TRUNCATES trailing bytes (Zig `@divTrunc`). Use only when `T` is not +// TRUNCATES trailing bytes. Use only when `T` is not // `AnyBitPattern` or the input length is intentionally not a multiple. // Every current caller targets `u16` over an even-length buffer, so the safe // path is the default. -/// Port of Zig `std.mem.sliceAsBytes` / `bun.reinterpretSlice` for the -/// read-only `&[A]` → `&[B]` direction. Safe: the [`bytemuck::NoUninit`] bound +/// Read-only `&[A]` → `&[B]` reinterpretation. Safe: the [`bytemuck::NoUninit`] bound /// on `A` guarantees every source byte is initialized, and /// [`bytemuck::AnyBitPattern`] on `B` guarantees every byte pattern is a valid /// `B`. Panics if size/alignment don't divide evenly (same as `bytemuck`). @@ -3359,7 +3319,7 @@ pub fn cast_slice_mut(a: &mut [A]) -> &mut [ bytemuck::cast_slice_mut(a) } -/// Port of Zig `std.mem.sliceAsBytes`: reinterpret `&[T]` as `&[u8]`. +/// Reinterpret `&[T]` as `&[u8]`. /// /// This is [`cast_slice`] with the output type fixed to `u8`, so callers never /// need a `::<_, u8>` turbofish. Safe: [`bytemuck::NoUninit`] guarantees every @@ -3372,9 +3332,8 @@ pub fn slice_as_bytes(s: &[T]) -> &[u8] { } // ─── extern_union_accessors! ────────────────────────────────────────────────── -// Zig accesses bare-union fields inline (`this.value.npm`) with no ceremony; the -// Rust port wraps each read in a tag-asserted `unsafe` accessor so call sites -// stay safe. Four crates hand-rolled the same accessor shape (Resolution, Bin, +// Wraps each bare-union field read in a tag-asserted `unsafe` accessor so call +// sites stay safe. Four crates hand-rolled the same accessor shape (Resolution, Bin, // DependencyVersion, PackageManager Task) — this macro is the single definition. // // Emits, per arm, `pub fn $field(&self) -> &$Ty` and optionally @@ -3460,8 +3419,8 @@ macro_rules! extern_union_accessors { }; } -/// Port of `bun.writeAnyToHasher`. Zig fed `std.mem.asBytes(&thing)`; Rust -/// can't take a generic by-value-as-bytes safely without `bytemuck`, so this +/// Feed a value's raw bytes to a hasher. Taking a generic by-value-as-bytes +/// safely requires a `bytemuck` bound, so this /// accepts anything that is itself viewable as bytes (covers the actual call /// sites: `u8` tags, `usize` lengths, `Index` newtypes). #[inline] @@ -3494,9 +3453,9 @@ impl AsBytes for &T { } // ── GenericIndex ────────────────────────────────────────────────────────── -// Port of `bun.GenericIndex(backing_int, uid)` (bun.zig:3513). Zig used a -// distinct enum-per-uid for nominal typing; Rust gets that via a phantom -// marker. `MAX` is reserved as the "none" sentinel for `Optional`. +// Nominally-typed index newtype; the phantom +// marker keeps indexes for different containers distinct. +// `MAX` is reserved as the "none" sentinel for `Optional`. // // NOTE on const-ness: hand-rolled monomorphic sites used `const fn init/get`. // The generic impl cannot be `const fn` on stable (trait-bound `I::NULL_VALUE` @@ -3569,7 +3528,7 @@ impl GenericIndex { I::to_usize(self.get()) } /// `init()` from a `usize` source (Vec length etc.). Debug-panics on - /// truncation, mirroring Zig `@intCast`. + /// truncation. #[inline] pub fn from_usize(n: usize) -> Self { Self::init(I::from_usize(n)) @@ -3653,7 +3612,7 @@ impl GenericIndexOptional { } } -/// Backing-integer bound for `GenericIndex` (replaces Zig's `comptime backing_int: type`). +/// Backing-integer bound for `GenericIndex`. pub trait GenericIndexInt: Copy + Eq + PartialOrd { const NULL_VALUE: Self; fn to_usize(self) -> usize; @@ -3671,9 +3630,9 @@ macro_rules! generic_index_int { ($($t:ty),*) => { $( )* } } generic_index_int!(u8, u16, u32, u64, usize, i32, i64); -/// Generic-integer bound replacing Zig's `comptime T: type` + `@typeInfo(T).Int` -/// in `validateIntegerRange` / `validateBigIntRange` / `getInteger` -/// (src/jsc/JSGlobalObject.zig). Provides the small surface those callers need: +/// Generic-integer bound used by `validateIntegerRange` / +/// `validateBigIntRange` / `getInteger` +/// (JSGlobalObject). Provides the small surface those callers need: /// signedness, range as `i128`, and lossy/wrapping casts from the JSC numeric /// carriers (i32 / f64 / i64 / u64). pub trait Integer: Copy + Default { @@ -3709,9 +3668,8 @@ impl_integer!( /// Primitive integers transcodable as native-endian raw bytes. /// -/// Replaces Zig's `comptime T: type` + `std.mem.readIntSliceNative` / -/// `std.mem.asBytes` / `@bitCast` reflection pattern with an explicit trait -/// bound. Shared by the peechy wire codec (`bun_analytics::SchemaInt`) and the +/// Explicit trait +/// bound shared by the peechy wire codec (`bun_analytics::SchemaInt`) and the /// MySQL protocol reader (`bun_sql::ReadableInt`), which re-export this under /// their local names. pub trait NativeEndianInt: Copy + 'static { @@ -3742,23 +3700,22 @@ macro_rules! impl_native_endian_int { impl_native_endian_int!(u8, i8, u16, i16, u32, i32, u64, i64); // ── mach_port ───────────────────────────────────────────────────────────── -// Zig: `if (Environment.isMac) std.c.mach_port_t else u32`. #[cfg(target_os = "macos")] pub type mach_port = libc::mach_port_t; #[cfg(not(target_os = "macos"))] pub type mach_port = u32; // ── rand ────────────────────────────────────────────────────────────────── -// `std.Random.DefaultPrng` is xoshiro256++ in Zig stdlib. Port the exact -// algorithm so `bun.fastRandom()` output is reproducible across the rewrite. +// xoshiro256++; the exact algorithm keeps `bun.fastRandom()` output +// reproducible. pub mod rand { - /// xoshiro256++ — `std.Random.DefaultPrng`. + /// xoshiro256++ pseudo-random number generator. #[derive(Clone, Copy)] pub struct DefaultPrng { s: [u64; 4], } impl DefaultPrng { - /// Seed via splitmix64 (matches Zig stdlib `Xoshiro256.init`). + /// Seed via splitmix64. pub fn init(seed: u64) -> Self { let mut sm = seed; let mut s = [0u64; 4]; @@ -3798,8 +3755,8 @@ pub fn fast_random() -> u64 { fn random_seed() -> u64 { let mut v = SEED.load(O::Relaxed); while v == 0 { - // Spec (bun.zig:575) gates on `Environment.isDebug or Environment.is_canary`; - // bun_core has no `canary` cargo feature yet, so debug-only for now (no + // Should also apply to canary builds, but bun_core has no `canary` + // cargo feature yet, so debug-only for now (no // regression vs. either pre-dedup copy — tracked separately). #[cfg(debug_assertions)] if let Some(n) = crate::env_var::BUN_DEBUG_HASH_RANDOM_SEED.get() { @@ -3832,12 +3789,12 @@ pub fn fast_random() -> u64 { // the xxhash64 entry point that ETag/bundler need. pub mod hash { pub use bun_hash::XxHash64; - /// `std.hash.XxHash64.hash(seed, bytes)`. + /// One-shot seeded XXH64 over `bytes`. #[inline] pub fn xxhash64(seed: u64, bytes: &[u8]) -> u64 { bun_hash::XxHash64::hash(seed, bytes) } - /// Wyhash one-shot (Zig `bun.hash`). + /// Wyhash one-shot (`bun.hash`). #[inline] pub fn wyhash(bytes: &[u8]) -> u64 { crate::deprecated::RapidHash::hash(0, bytes) @@ -3845,8 +3802,8 @@ pub mod hash { } // ── base64 ──────────────────────────────────────────────────────────────── -// Thin simdutf-backed encoders + scalar decoder. Port of the subset of -// `src/base64/base64.zig` that tier-0/1 callers need (npm auth, sourcemaps, +// Thin simdutf-backed encoders + scalar decoder — the subset +// tier-0/1 callers need (npm auth, sourcemaps, // ansi_renderer). Full URL-safe / streaming variants stay in bun_base64. pub mod base64 { use bun_simdutf_sys::simdutf; @@ -3865,13 +3822,13 @@ pub mod base64 { simdutf::base64::encode(source, dest, false) } - /// `std.base64.standard.Encoder.calcSize` — alias of `encode_len` taking a length. + /// Encoded output size for a standard-base64 input of `source_len` bytes. #[inline] pub const fn standard_encoder_calc_size(source_len: usize) -> usize { source_len.div_ceil(3) * 4 } - /// `std.base64.standard.Encoder.encode` returning the written sub-slice. + /// Standard-base64 encode, returning the written sub-slice of `dest`. pub fn standard_encode<'a>(dest: &'a mut [u8], source: &[u8]) -> &'a [u8] { let n = encode(dest, source); &dest[..n] @@ -3884,7 +3841,7 @@ pub mod base64 { pub fail: bool, } - /// `bun.base64.decode`. Scalar fallback (PERF(port): simdutf path in + /// Scalar fallback base64 decode (the SIMD path lives in /// bun_base64). Tolerates missing padding; stops at first invalid char. pub fn decode(dest: &mut [u8], source: &[u8]) -> DecodeResult { const INV: u8 = 0xFF; @@ -3971,15 +3928,15 @@ pub fn dupe_z(bytes: &[u8]) -> *const core::ffi::c_char { /// case used by http SSLConfig — re-exported from `bun_alloc` so the /// secure-zero core stays single-sourced. Pairs with [`dupe_z`]. pub use bun_alloc::free_sensitive_cstr as free_sensitive; -/// Port of `std.crypto.secureZero` — re-exported from `bun_alloc`. +/// Optimization-resistant memory zeroing — re-exported from `bun_alloc`. pub use bun_alloc::secure_zero; // ── argv ────────────────────────────────────────────────────────────────── // `bun.argv` — process argv as a slice of NUL-terminated byte strings. -// Zig: `pub var argv: [][:0]const u8`. The owned `ZBox` backing for the +// The owned `ZBox` backing for the // initial OS argv lives in `ARGV_STORAGE`; `ARGV` is the mutable *view* // slice that call sites read (and that `set_argv` swaps for the -// `--compile` exec-argv splicing path in `cli.zig`). Exposed via a tiny +// `--compile` exec-argv splicing path). Exposed via a tiny // `Argv` wrapper so call sites can use it both as a slice (`.get(0)`, // `.iter()`, `.len()`, `.as_slice()`) and as an `IntoIterator` // for `for arg in argv()`. @@ -3995,8 +3952,7 @@ static ARGV_INIT: std::sync::Once = std::sync::Once::new(); /// this; on **musl-static** the `.init_array` constructor is invoked with no /// arguments (musl's `__libc_start_main` does not forward `(argc,argv,envp)` /// to constructors), so `std::env::args_os()` returns empty and we must read -/// the kernel-provided block ourselves. Zig's `_start` writes `std.os.argv` -/// directly from the stack — this is the equivalent for a clang-linked +/// the kernel-provided block ourselves from a clang-linked /// `extern "C" fn main`. static OS_ARGC: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0); static OS_ARGV: core::sync::atomic::AtomicPtr<*const core::ffi::c_char> = @@ -4006,9 +3962,6 @@ static OS_ARGV: core::sync::atomic::AtomicPtr<*const core::ffi::c_char> = /// the very first call in `main`, before the crash handler (whose panic path /// dumps the command line) or anything else that might call [`argv()`]. /// -/// Matches Zig `bun.initArgv` which on POSIX wraps `std.os.argv` (set by -/// Zig's own `_start` from the kernel-provided argv block). -/// /// # Safety /// `argv` must point to `argc` valid NUL-terminated C strings that live for /// the entire process (the kernel/crt argv block does). Calling this after @@ -4038,9 +3991,9 @@ fn argv_storage() -> &'static [ZBox] { // Windows: the CRT-provided `char** argv` captured by `init_argv` is // ANSI-encoded (CP_ACP) — `WideCharToMultiByte` lossy-converts the // UTF-16 command line, replacing unrepresentable code points with `?`. - // Zig's `initArgv` (bun.zig) goes straight to `GetCommandLineW` + - // `CommandLineToArgvW` and converts each UTF-16 arg to WTF-8 itself; - // do the same here so non-ASCII argv (e.g. `bun -e "🌊 测试"`) + // Go straight to `GetCommandLineW` + + // `CommandLineToArgvW` and convert each UTF-16 arg to WTF-8 ourselves + // so non-ASCII argv (e.g. `bun -e "🌊 测试"`) // round-trips. See https://github.com/oven-sh/bun/issues/11610. #[cfg(windows)] { @@ -4048,7 +4001,7 @@ fn argv_storage() -> &'static [ZBox] { let mut argc: core::ffi::c_int = 0; // SAFETY: `GetCommandLineW` returns a process-static buffer; // `CommandLineToArgvW` allocates its own array (lifetime managed - // by the system per Zig spec — intentionally not `LocalFree`d, the + // by the system — intentionally not `LocalFree`d, the // argv strings are referenced for the process lifetime). let argvw = unsafe { CommandLineToArgvW(GetCommandLineW(), &mut argc) }; if !argvw.is_null() { @@ -4066,7 +4019,7 @@ fn argv_storage() -> &'static [ZBox] { .collect(); } // Fall through to `args_os` if `CommandLineToArgvW` failed (OOM / - // INVAL) — Zig returns an error there; we degrade to libstd's + // INVAL) — degrade to libstd's // own `GetCommandLineW`-backed parser instead of aborting. } #[cfg(not(windows))] @@ -4097,7 +4050,7 @@ fn argv_view_init() { let storage: &'static [ZBox] = argv_storage(); // ARGV_STORAGE is process-static via `Once`; `as_zstr` borrows for `'static`. let mut view: Vec<&'static ZStr> = storage.iter().map(ZBox::as_zstr).collect(); - // Zig `initArgv`: splice BUN_OPTIONS tokens after argv[0]. + // Splice BUN_OPTIONS tokens after argv[0]. if let Some(opts) = crate::env_var::BUN_OPTIONS.get() { let original_len = view.len(); append_options_env::<&'static ZStr>(opts, &mut view); @@ -4138,7 +4091,7 @@ impl Argv { i: 0, } } - /// Borrow the underlying `[&ZStr]` view (Zig: `bun.argv[..]`). + /// Borrow the underlying `[&ZStr]` view. #[inline] pub fn as_slice(&self) -> &'static [&'static ZStr] { self.0 @@ -4178,16 +4131,14 @@ pub fn argv() -> Argv { Argv(argv_view()) } -// ─── BUN_OPTIONS argv injection (bun.zig: bun_options_argc / appendOptionsEnv) ── +// ─── BUN_OPTIONS argv injection ────────────────────────────────────────────── /// Number of arguments injected into `argv` by the `BUN_OPTIONS` environment /// variable. Set once during single-threaded startup (`init_argv`). static BUN_OPTIONS_ARGC: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0); -/// Zig: `bun.bun_options_argc` — read accessor. +/// Read accessor for the injected-arg count. /// -/// Forces the lazy `argv_view()` init before reading: in Zig `initArgv()` -/// runs eagerly in `main()` so `bun.bun_options_argc` is always populated by -/// the time `cli.zig` reads it; here `argv()` is lazy, so a caller that reads +/// Forces the lazy `argv_view()` init before reading: `argv()` is lazy, so a caller that reads /// `bun_options_argc()` *before* `argv()` (e.g. the standalone-executable /// path in `Command::start`) would otherwise see 0 and miscount the /// BUN_OPTIONS-injected args when computing the passthrough offset. @@ -4196,23 +4147,23 @@ pub fn bun_options_argc() -> usize { let _ = argv_view(); BUN_OPTIONS_ARGC.load(core::sync::atomic::Ordering::Relaxed) } -/// Zig: `bun.bun_options_argc = n` — write accessor (single-threaded startup). +/// Write accessor (single-threaded startup). #[inline] pub fn set_bun_options_argc(n: usize) { BUN_OPTIONS_ARGC.store(n, core::sync::atomic::Ordering::Relaxed); } -/// Trait for arg types accepted by [`append_options_env`] (replaces Zig -/// `comptime ArgType` in `bun.appendOptionsEnv`). Impl'd for `bun_core::String` +/// Trait for arg types accepted by [`append_options_env`]. +/// Impl'd for `bun_core::String` /// and `Box` in their owning crates. pub trait OptionsEnvArg { fn from_slice(s: &[u8]) -> Self; fn from_buf(buf: Vec) -> Self; } -/// Zig `[:0]const u8` arm of `appendOptionsEnv`: `default_allocator.allocSentinel` -/// + never freed (process-lifetime argv storage). The leaked allocation matches -/// the Zig alloc/free pairing exactly — argv entries live for the process. +/// Leaked-allocation arm of `append_options_env` (process-lifetime argv +/// storage) — argv entries live for the process, so the allocation is +/// intentionally never freed. impl OptionsEnvArg for &'static ZStr { fn from_slice(s: &[u8]) -> Self { let mut v = Vec::with_capacity(s.len() + 1); @@ -4244,9 +4195,9 @@ impl OptionsEnvArg for Box { } } -/// Zig: `bun.appendOptionsEnv` — parse a `BUN_OPTIONS`-style string +/// Parse a `BUN_OPTIONS`-style string /// (`--flag=value --flag2 "quoted value" bare`) and insert each token into -/// `args` starting at index 1 (Zig callers prepend a placeholder at [0]). +/// `args` starting at index 1 (callers prepend a placeholder at [0]). pub fn append_options_env(env: &[u8], args: &mut Vec) { let mut i: usize = 0; let mut offset_in_args: usize = 1; @@ -4368,8 +4319,7 @@ pub fn append_options_env(env: &[u8], args: &mut Vec) { } } -/// `bun.argv = slice` — swap the global argv view. Zig assigns the slice -/// directly (`bun.argv = full_argv[0..n]`); call sites are single-threaded +/// Swap the global argv view. Call sites are single-threaded /// startup (CLI parsing in the `--compile` path), so this writes the static /// without synchronization. /// @@ -4395,8 +4345,8 @@ pub fn intern_argv(v: Vec<&'static ZStr>) -> &'static [&'static ZStr] { } // ── getcwd ──────────────────────────────────────────────────────────────── -/// Port of `bun.getcwd(buf)` → `Maybe([:0]u8)`. Writes into the caller's -/// `PathBuffer` and returns the NUL-terminated slice on success. +/// Writes the current working directory into the caller's `PathBuffer` and +/// returns the NUL-terminated slice on success. pub fn getcwd(buf: &mut PathBuffer) -> Result<&ZStr, crate::Error> { #[cfg(unix)] // SAFETY: `buf` provides `MAX_PATH_BYTES` writable bytes for `getcwd`; on @@ -4412,8 +4362,8 @@ pub fn getcwd(buf: &mut PathBuffer) -> Result<&ZStr, crate::Error> { } #[cfg(windows)] { - // Zig `bun.getcwd` → `std.posix.getcwd`, which on Windows wraps - // `kernel32.GetCurrentDirectoryW` and transcodes WTF-16 → WTF-8. + // Windows: wrap + // `kernel32.GetCurrentDirectoryW` and transcode WTF-16 → WTF-8. unsafe extern "system" { fn GetCurrentDirectoryW(nBufferLength: u32, lpBuffer: *mut u16) -> u32; } @@ -4427,8 +4377,7 @@ pub fn getcwd(buf: &mut PathBuffer) -> Result<&ZStr, crate::Error> { return Err(crate::err!(NameTooLong)); } // WTF-16 → WTF-8 into the caller's `PathBuffer`. Surrogate pairs are - // combined; unpaired surrogates are encoded as 3-byte WTF-8 (matches - // Zig's `std.unicode.wtf16LeToWtf8`). + // combined; unpaired surrogates are encoded as 3-byte WTF-8. let src = &wbuf.0[..n]; let out = &mut buf.0; let mut wi = 0usize; @@ -4502,17 +4451,19 @@ pub fn which<'a>(buf: &'a mut PathBuffer, path: &[u8], cwd: &[u8], bin: &[u8]) - } #[cfg(not(unix))] { - // TODO(port): Windows X_OK via GetFileAttributesW; defer to bun_which. + // No X_OK probe here: this tier-0 helper is only reached from the + // linux/freebsd `spawn_sync_inherit` path. Windows callers resolve + // executables via `bun_which` (PATHEXT-aware) instead. } None }; - // Absolute `bin` → probe it directly without joining `cwd` (which.zig:35-42). + // Absolute `bin` → probe it directly without joining `cwd`. if crate::path_sep::is_absolute_native(bin) { return check(buf, b"", bin).map(|n| ZStr::from_buf(&buf.0, n)); } if has_sep { - // Relative with separator → resolve against cwd only. Zig trims - // trailing '/' from cwd and strips a leading "./" from bin. + // Relative with separator → resolve against cwd only; trim + // trailing '/' from cwd and strip a leading "./" from bin. let cwd = { let mut c = cwd; while let [rest @ .., b'/'] = c { @@ -4523,7 +4474,7 @@ pub fn which<'a>(buf: &'a mut PathBuffer, path: &[u8], cwd: &[u8], bin: &[u8]) - let bin = bin.strip_prefix(b"./").unwrap_or(bin); return check(buf, cwd, bin).map(|n| ZStr::from_buf(&buf.0, n)); } - // Bare names go straight to PATH (which.zig:44-63) — do NOT consult cwd. + // Bare names go straight to PATH — do NOT consult cwd. let delim: u8 = if cfg!(windows) { b';' } else { b':' }; for dir in path.split(|&b| b == delim) { if dir.is_empty() { @@ -4537,7 +4488,7 @@ pub fn which<'a>(buf: &'a mut PathBuffer, path: &[u8], cwd: &[u8], bin: &[u8]) - } // ── auto_reload_on_crash / reload_process group ─────────────────────────── -// Port of `bun.zig:1527-1686`. Full body of `reloadProcess` depends on +// Full body of `reloadProcess` depends on // `bun.spawn` (tier-4); the crash-handler only needs the flag + the // thread-coordination helpers + a best-effort POSIX `execve` path. use core::sync::atomic::{AtomicBool, Ordering as AOrdering}; @@ -4562,7 +4513,7 @@ pub fn is_process_reload_in_progress_on_another_thread() -> bool { && !RELOAD_IN_PROGRESS_ON_CURRENT_THREAD.with(|c| c.get()) } -/// Zig: `bun.exitThread()` — terminate the current OS thread without unwinding. +/// Terminate the current OS thread without unwinding. /// POSIX `pthread_exit`; Windows `ExitThread`. Called from worker `shutdown()`. pub fn exit_thread() -> ! { #[cfg(unix)] @@ -4585,7 +4536,7 @@ pub fn exit_thread() -> ! { } } -/// Zig: `bun.deleteAllPoolsForThreadExit()` — release thread-local pooled +/// Release thread-local pooled /// buffers (PathBuffer pool, ObjectPool, …) before the thread terminates so /// the backing storage is returned to mimalloc rather than leaked with the /// TLS block. @@ -4615,7 +4566,7 @@ pub fn maybe_handle_panic_during_process_reload() { crate::output::flush(); #[cfg(debug_assertions)] crate::output::debug_warn("panic() called during process reload, ignoring\n"); - // Zig: `bun.exitThread()`. POSIX `pthread_exit`; Windows `ExitThread`. + // POSIX `pthread_exit`; Windows `ExitThread`. exit_thread(); } // Spin if pthread_exit was a no-op (pathological). @@ -4672,7 +4623,7 @@ pub fn reload_process(clear_terminal: bool, may_return: bool) { // memory-safety preconditions. safe fn TerminateProcess(h: *mut core::ffi::c_void, code: u32) -> i32; } - // = 3224497970, bun.windows.watcher_reload_exit (windows.zig). Parent + // = 3224497970. Parent // watcher-manager compares the child's exit code against exactly this. const WATCHER_RELOAD_EXIT: u32 = 0xC031_EF32; let rc = TerminateProcess(GetCurrentProcess(), WATCHER_RELOAD_EXIT); @@ -4706,8 +4657,7 @@ pub fn reload_process(clear_terminal: bool, may_return: bool) { on_before_reload_process_linux(); } - // We clone argv so that the memory address isn't the same as the libc one - // (mirrors Zig `allocator.dupeZ` per entry). + // We clone argv so that the memory address isn't the same as the libc one. let args = argv_storage(); let dupe_argv: Vec = args .iter() @@ -4718,7 +4668,7 @@ pub fn reload_process(clear_terminal: bool, may_return: bool) { newargv.push(core::ptr::null()); // We clone envp so that the memory address of environment variables isn't - // the same as the libc one (mirrors Zig `allocSentinel` + `dupeZ` loop). + // the same as the libc one. let mut dupe_env: Vec = Vec::new(); let mut p = c_environ(); while !p.is_null() && !(*p).is_null() { @@ -4747,8 +4697,7 @@ pub fn reload_process(clear_terminal: bool, may_return: bool) { #[cfg(not(any(unix, windows)))] { - // Zig: `else @compileError("unsupported platform for reloadProcess")`. - // Faithful port — Bun only targets POSIX + Windows; any other target + // Bun only targets POSIX + Windows; any other target // is a build-time error, not a runtime panic. let _ = (clear_terminal, may_return); compile_error!("unsupported platform for reload_process"); @@ -4803,9 +4752,9 @@ pub mod spawn_ffi { /// Matches `bun_spawn_request_file_action_t`. /// /// ABI: this struct crosses FFI to `posix_spawn_bun` via `*const Action` - /// (see [`ActionsList`]) and must match spawn.zig's `extern struct` / - /// bun-spawn.cpp's C struct exactly. `path` is `?[*:0]const u8` on the - /// Zig/C side — an 8-byte thin nullable pointer — so it MUST be + /// (see [`ActionsList`]) and must match + /// bun-spawn.cpp's C struct exactly. `path` on the + /// C side is an 8-byte thin nullable pointer — so it MUST be /// `*const c_char` here, not `Option` (which is a 16-byte fat /// pointer and would shift `fds`/`flags`/`mode`). #[repr(C)] @@ -4881,7 +4830,34 @@ pub mod spawn_ffi { } } +/// Stdin disposition for [`spawn_sync_inherit`] / [`spawn_sync_inherit_no_stdin`]. +/// The `Ignore` case wires fd 0 to `/dev/null` (`NUL` on Windows) so the +/// child never blocks on a TTY read. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum StdinBehavior { + Inherit, + Ignore, +} + +/// Spawn argv, inherit stdout/stderr, **ignore stdin** (fd 0 ← /dev/null), +/// wait. +pub fn spawn_sync_inherit_no_stdin(argv: &[impl AsRef<[u8]>]) -> Result { + spawn_sync_inherit_impl(argv, StdinBehavior::Ignore) +} + pub fn spawn_sync_inherit(argv: &[impl AsRef<[u8]>]) -> Result { + spawn_sync_inherit_impl(argv, StdinBehavior::Inherit) +} + +fn spawn_sync_inherit_impl( + argv: &[impl AsRef<[u8]>], + stdin: StdinBehavior, +) -> Result { + // Empty argv: fail like the Windows path below instead of panicking on + // the `argv[0]` reads in the per-OS unix arms. + if argv.is_empty() { + return Err(crate::err!("FileNotFound")); + } #[cfg(unix)] // SAFETY: argv strings are owned `ZBox`es (NUL-terminated) kept alive in // `cargs` for the duration of the spawn; `ptrs`/`environ` are null- @@ -4920,12 +4896,24 @@ pub fn spawn_sync_inherit(argv: &[impl AsRef<[u8]>]) -> Result]) -> Result(), environ.cast::<*mut core::ffi::c_char>(), ); + if !actions_ptr.is_null() { + libc::posix_spawn_file_actions_destroy(&raw mut actions); + } if rc != 0 { return Err(crate::Error::from_errno(rc)); } @@ -4980,6 +4995,16 @@ pub fn spawn_sync_inherit(argv: &[impl AsRef<[u8]>]) -> Result]) -> Result]) -> Result]) -> Result crate::err!("FileNotFound"), std::io::ErrorKind::PermissionDenied => crate::err!("AccessDenied"), @@ -5055,16 +5081,16 @@ pub fn spawn_sync_inherit(argv: &[impl AsRef<[u8]>]) -> Result Timespec { let mut sec = self.sec.wrapping_sub(other.sec); let mut nsec = self.nsec.wrapping_sub(other.nsec); @@ -5121,7 +5147,7 @@ impl Timespec { return self.nsec.max(0) as u64; } let s_ns = (self.sec as u64).saturating_mul(Self::NS_PER_S as u64); - // Zig-exact (bun.zig:3313 returns maxInt(i64)) + // Saturates at i64::MAX (not u64::MAX) on overflow. s_ns.checked_add(self.nsec.max(0) as u64) .unwrap_or(i64::MAX as u64) } @@ -5198,7 +5224,7 @@ impl Timespec { if a.order(&b).is_gt() { a } else { b } } - /// `bun.timespec.orderIgnoreEpoch` (bun.zig:3405) — EPOCH = "no timeout", treated as +∞. + /// `bun.timespec.orderIgnoreEpoch` — EPOCH = "no timeout", treated as +∞. pub fn order_ignore_epoch(a: Timespec, b: Timespec) -> core::cmp::Ordering { if a == b { return core::cmp::Ordering::Equal; @@ -5211,7 +5237,7 @@ impl Timespec { } a.order(&b) } - /// `bun.timespec.minIgnoreEpoch` (bun.zig:3411). + /// `bun.timespec.minIgnoreEpoch`. #[inline] pub fn min_ignore_epoch(self, b: Timespec) -> Timespec { if Self::order_ignore_epoch(self, b).is_lt() { @@ -5289,9 +5315,9 @@ pub enum TimespecMockMode { ForceRealTime, } -/// `bun_core::timespec::Mode` namespace shim — Zig nested it under the struct; -/// Rust can't do inherent associated types stably, so expose a module with the -/// same path. Callers write `bun_core::timespec_mode::AllowMockedTime` or use +/// `bun_core::timespec_mode::Mode` namespace shim — +/// Rust can't do inherent associated types stably, so expose a module +/// instead. Callers write `bun_core::timespec_mode::AllowMockedTime` or use /// the `Timespec::now_allow_mocked_time()` helper. pub mod timespec_mode { pub use super::TimespecMockMode::*; @@ -5327,9 +5353,9 @@ pub mod mock_time { } // ── f16 ─────────────────────────────────────────────────────────────────── -// Zig's native `f16` (IEEE-754 binary16). Rust's `f16` is still nightly-only, +// IEEE-754 binary16. Rust's `f16` is still nightly-only, // so model it as a transparent `u16` bit-container with `f64` widening for the -// one hot caller (ConsoleObject Float16Array printing). PERF(port): scalar +// one hot caller (ConsoleObject Float16Array printing). PERF: scalar // soft-float decode; revisit once `core::f16` stabilizes. #[allow(non_camel_case_types)] #[repr(transparent)] @@ -5346,7 +5372,7 @@ impl f16 { self.0 } - /// Widen to `f64` (exact). Port of Zig `@floatCast(f64, h)`. + /// Widen to `f64` (exact). pub fn to_f64(self) -> f64 { let h = self.0 as u32; let sign = (h >> 15) & 1; @@ -5396,9 +5422,9 @@ impl core::fmt::Display for f16 { } // ── perf ────────────────────────────────────────────────────────────────── -// Port of `bun.perf` (src/perf/perf.zig). The Linux ftrace backend is +// `bun.perf`. The Linux ftrace backend is // libc-only, so it folds in directly and `bun_core::perf::trace("X")` is real -// instrumentation on Linux. macOS: the Zig backend wraps `Bun__signpost_emit` +// instrumentation on Linux. macOS: signposts wrap `Bun__signpost_emit` // (c-bindings.cpp) which keys on the codegen `PerfEvent` int — that table // lives in `bun_perf` (T2, owns generated_perf_trace_events), so T0 reports // disabled on macOS. **No functional divergence today**: `bun_perf`'s Darwin @@ -5406,7 +5432,7 @@ impl core::fmt::Display for f16 { // stub whose `end()` is a no-op, so neither tier emits signposts yet. When // `Bun__signpost_emit` is wired, callers above T0 use `bun_perf::trace`; T0 // callsites (audited r5) are bundler/parser hot paths where Linux ftrace is -// the profiling target. Windows/other platforms are no-ops in Zig too. +// the profiling target. Windows/other platforms are no-ops. pub mod perf { #[cfg(any(target_os = "linux", target_os = "android"))] use core::sync::atomic::AtomicBool; @@ -5530,7 +5556,7 @@ pub mod perf { let duration = crate::Timespec::now(crate::TimespecMockMode::ForceRealTime) .ns() .saturating_sub(self.start_time); - // Zig passed `@tagName(event).ptr` (NUL-terminated). Build a small + // The FFI wants a NUL-terminated name. Build a small // stack CString from the &'static str literal. let mut buf = [0u8; 96]; let n = self.name.len().min(buf.len() - 1); @@ -5548,7 +5574,7 @@ pub mod perf { /// Single source of truth for the Linux ftrace FFI decls (defined in /// `src/jsc/bindings/linux_perf_tracing.cpp`). Re-exported so `bun_perf` /// (the canonical signpost/ftrace entry point) imports these instead of - /// re-declaring them — see src/perf/perf.zig:127-129 for the spec. + /// re-declaring them. #[cfg(any(target_os = "linux", target_os = "android"))] pub mod sys { unsafe extern "C" { @@ -5565,16 +5591,15 @@ pub mod perf { } // ── form_data ───────────────────────────────────────────────────────────── -// Port of `bun.FormData.{Encoding, AsyncFormData, getBoundary}` (src/runtime/ -// webcore/FormData.zig:16-95). The JSC-touching parts (`toJS`, the field map, +// `bun.FormData.{Encoding, AsyncFormData, getBoundary}`. +// The JSC-touching parts (`toJS`, the field map, // multipart parser) stay in `bun_runtime::webcore::form_data`; T0 owns only // the encoding-detection types so `Request`/`Response`/`Body` can name them // without a runtime→core cycle. Per PORTING.md §JSC: `to_js` is an extension // method that lives in the higher-tier crate. pub mod form_data { /// `FormData.Encoding` — `union(enum) { URLEncoded, Multipart: []const u8 }`. - /// `Multipart` owns its boundary (Zig `AsyncFormData.init` duped it; here - /// the Box moves in directly). + /// `Multipart` owns its boundary (the Box moves in directly). #[derive(Debug)] pub enum Encoding { URLEncoded, @@ -5661,8 +5686,7 @@ pub mod form_data { } /// `FormData.AsyncFormData` — heap-allocated, owns its `Encoding`. - /// PORT NOTE: Zig stored `std.mem.Allocator param`; deleted (non-AST - /// crate, global mimalloc per §Allocators). `deinit` becomes `Drop` on the + /// Cleanup is `Drop` on the /// `Box`/`Box<[u8]>` fields — no explicit impl needed. #[derive(Debug)] pub struct AsyncFormData { @@ -5672,14 +5696,10 @@ pub mod form_data { impl AsyncFormData { #[inline] pub fn init(encoding: Encoding) -> Box { - // Zig duped `encoding.Multipart` here so the struct owned its - // boundary; with `Box<[u8]>` ownership has already transferred. + // With `Box<[u8]>`, boundary ownership has already transferred. Box::new(AsyncFormData { encoding }) } } } -/// Zig `bun.FormData` namespace — capitalized alias for callers that ported -/// `bun.FormData.AsyncFormData` verbatim. +/// `bun.FormData` — capitalized namespace alias. pub use form_data as FormData; - -// ported from: src/bun_core/util.zig diff --git a/src/bun_core/windows_sys.rs b/src/bun_core/windows_sys.rs index 06b54e2a9c3..a04d5a7be00 100644 --- a/src/bun_core/windows_sys.rs +++ b/src/bun_core/windows_sys.rs @@ -23,8 +23,7 @@ pub(crate) const ENABLE_PROCESSED_OUTPUT: DWORD = 0x0001; pub(crate) const ENABLE_WRAP_AT_EOL_OUTPUT: DWORD = 0x0002; pub(crate) const ENABLE_VIRTUAL_TERMINAL_PROCESSING: DWORD = 0x0004; -/// Wrapper that returns `None` on `INVALID_HANDLE_VALUE` (matches -/// `std.os.windows.GetStdHandle` error-union semantics). +/// Wrapper that returns `None` on `INVALID_HANDLE_VALUE` or a null handle. #[inline] pub fn GetStdHandle(std_handle: DWORD) -> Option { let h = kernel32::GetStdHandle(std_handle); @@ -36,7 +35,7 @@ pub fn GetStdHandle(std_handle: DWORD) -> Option { } // ────────────────────────────────────────────────────────────────────────── -// PEB access (`std.os.windows.peb()`). `bun_core::output::windows_stdio` +// PEB access. `bun_core::output::windows_stdio` // reads `ProcessParameters.hStd{Input,Output,Error}` to snapshot the console // handles before libuv touches them. Canonical structs/asm live in the tier-0 // `bun_windows_sys` leaf and are re-exported here for the @@ -56,7 +55,7 @@ unsafe impl crate::ffi::Zeroable for CONSOLE_SCREEN_BUFFER_INFO {} // kernel32 externs are owned by the tier-0 leaf `bun_windows_sys`; re-export // so existing `crate::windows_sys::kernel32::*` / `c::*` callers resolve. pub use bun_windows_sys::kernel32; -// `c::` alias used by `output.rs` (Zig's `bun.c` namespace). +// `c::` alias used by `output.rs`. pub use kernel32 as c; /// `bun.windows.libuv` — only `uv_disable_stdio_inheritance` is called from diff --git a/src/bun_core/wtf.rs b/src/bun_core/wtf.rs index 37ba8c83210..e38522acc20 100644 --- a/src/bun_core/wtf.rs +++ b/src/bun_core/wtf.rs @@ -9,9 +9,9 @@ //! which forwards to `WTF::parseES5Date` in //! vendor/WebKit `Source/WTF/wtf/DateMath.{h,cpp}`. //! -//! PORT NOTE: WTF's `parseES5Date` sets an `isLocalTime` out-param so the JS +//! Note: WTF's `parseES5Date` sets an `isLocalTime` out-param so the JS //! `Date` constructor can later apply the VM's tz offset. The C shim discards -//! it (matching `src/jsc/WTF.zig`), so local-time inputs return their naive +//! it, so local-time inputs return their naive //! UTC value here too. unsafe extern "C" { @@ -65,5 +65,3 @@ pub use crate::string::wtf::{ InvalidCharacter, RefPtr, StringImpl, WTFString, WTFStringImpl, WTFStringImplExt, WTFStringImplStruct, parse_double, }; - -// ported from: src/jsc/WTF.zig diff --git a/src/bun_core_macros/lib.rs b/src/bun_core_macros/lib.rs index f4947f4288b..eb54d5f8d3f 100644 --- a/src/bun_core_macros/lib.rs +++ b/src/bun_core_macros/lib.rs @@ -1,7 +1,6 @@ //! Proc-macros for `bun_core`. //! -//! `pretty_fmt!(FMT, true|false)` is the compile-time port of Zig's -//! `Output.prettyFmt` (`src/bun_core/output.zig`). It rewrites Bun's `` +//! `pretty_fmt!(FMT, true|false)` rewrites Bun's `` //! color markup into ANSI escape sequences (or strips them when the second //! argument is `false`) and emits a string *literal* so the result is usable as //! a `format_args!` / `concat!` template. @@ -80,12 +79,12 @@ fn eval_literal(expr: &Expr, out: &mut String) -> Result<(), syn::Error> { use bun_output_tags::{RESET, color_for}; -/// 1:1 port of `prettyFmt` from output.zig, plus Zig→Rust format-spec rewrites +/// Rewrites `` markup plus legacy format specs /// (`{s}`/`{d}` → `{}`, `{any}`/`{?}` → `{:?}`). /// /// Colour table lives in `bun_output_tags`; the state machine is kept duplicated /// vs `bun_core::output::pretty_fmt_runtime` because the two intentionally -/// diverge in the `{` arm (this side rewrites Zig specs `{s}`→`{}` for the +/// diverge in the `{` arm (this side rewrites legacy specs `{s}`→`{}` for the /// emitted `format_args!` template; runtime copies braces verbatim) and on /// unknown tags (this side `Err`→`compile_error!`; runtime emits `""`). fn rewrite(fmt: &str, is_enabled: bool) -> Result { @@ -111,11 +110,11 @@ fn rewrite(fmt: &str, is_enabled: bool) -> Result { } } b'>' => { - // stray closer — Zig drops it + // stray closer — dropped i += 1; } b'{' => { - // copy `{ ... }` verbatim, optionally rewriting Zig-style specs + // copy `{ ... }` verbatim, optionally rewriting legacy specs let start = i; while i < bytes.len() && bytes[i] != b'}' { i += 1; @@ -207,9 +206,8 @@ pub fn pretty_fmt(input: TokenStream) -> TokenStream { // There is no type-based fallback. An earlier draft fell back on "the unique // field whose type's last path segment is `Cell`", but that matched any // `Cell<_>` (e.g. `Cell`), turning the helpful "no ref_count field -// found" diagnostic into a buried type-mismatch inside generated code. The -// Zig spec (`@FieldType(T, "ref_count")` in src/ptr/ref_count.zig) requires -// the literal name anyway, so rules 1+2 are sufficient and exhaustive. +// found" diagnostic into a buried type-mismatch inside generated code. +// Rules 1+2 are sufficient and exhaustive. // // Custom destructor: `#[ref_count(destroy = Self::deinit)]` on the struct // routes the trait's `destroy` to that path instead of the default @@ -552,9 +550,7 @@ pub fn derive_thread_safe_ref_counted(input: TokenStream) -> TokenStream { // #[derive(RefCounted)] — intrusive single-thread `RefCount` mixin // ────────────────────────────────────────────────────────────────────────── // -// Third sibling of CellRefCounted / ThreadSafeRefCounted. Ports Zig's -// `bun.ptr.RefCount(@This(), "ref_count", destructor, .{ .debug_name = … })` -// comptime mixin (src/ptr/ref_count.zig:67) — the form taken by ~17 Rust +// Third sibling of CellRefCounted / ThreadSafeRefCounted — replaces the ~17 // hand-rolls that all spell out `type DestructorCtx = (); get_ref_count = // &raw mut (*this).ref_count; destructor = drop(heap::take(this))`. // @@ -562,7 +558,6 @@ pub fn derive_thread_safe_ref_counted(input: TokenStream) -> TokenStream { // #[ref_count(destroy = )] — `unsafe fn(*mut Self)`; default is // `drop(::bun_core::heap::take(this))` // #[ref_count(debug_name = "Name")] — overrides `RefCounted::debug_name()` -// (Zig `.{ .debug_name = … }` option) // // Field selection follows the shared `find_ref_count_field` rules (a // `#[ref_count]`-annotated field, else a field literally named `ref_count`). @@ -668,12 +663,10 @@ pub fn derive_ref_counted(input: TokenStream) -> TokenStream { // #[derive(EnumTag)] // ────────────────────────────────────────────────────────────────────────── // -// Rust port of Zig's `union(Tag)` / `std.meta.Tag(T)` language built-in. -// Every Zig tagged-union ported to a Rust `enum` lost the implicit -// data→discriminant projection and grew a hand-written +// Derives the data→discriminant projection that otherwise needs a +// hand-written // `fn tag(&self) -> Tag { match self { Self::A(..) => Tag::A, … } }` (14 -// copies, 160+ arms total — see ast/expr.rs, ast/stmt.rs, shell_parser, etc.; -// stmt.rs:466 literally comments "Zig got this for free from `union(Tag)`"). +// copies, 160+ arms total — see ast/expr.rs, ast/stmt.rs, shell_parser, etc.). // // Two modes: // @@ -745,7 +738,7 @@ pub fn derive_enum_tag(input: TokenStream) -> TokenStream { let tag_idents2 = tag_idents.clone(); return quote! { impl #impl_g #name #ty_g #where_g { - /// Data → discriminant projection (Zig `union(Tag)` built-in). + /// Data → discriminant projection. #[inline] pub const fn tag(&self) -> #tag_path { match self { @@ -772,7 +765,7 @@ pub fn derive_enum_tag(input: TokenStream) -> TokenStream { #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum #tag_name { #( #tag_variants, )* } impl #impl_g #name #ty_g #where_g { - /// Data → discriminant projection (Zig `union(Tag)` built-in). + /// Data → discriminant projection. #[inline] pub const fn tag(&self) -> #tag_name { match self { diff --git a/src/bun_output_tags/lib.rs b/src/bun_output_tags/lib.rs index 3b5859c4d65..239866f1505 100644 --- a/src/bun_output_tags/lib.rs +++ b/src/bun_output_tags/lib.rs @@ -2,17 +2,13 @@ //! named ANSI escape constants used directly by REPL / diff-printer / multi-run //! output. Zero-dep `#![no_std]` leaf so both the proc-macro crate and runtime //! crates can import it without a cycle. -//! -//! Port of `color_map` from `src/bun_core/output.zig:965-1056`. Zig re-declares -//! the escapes per-file (repl.zig, printDiff.zig, multi_run.zig); the Rust port -//! collapses them onto [`ansi`]. #![no_std] /// Named ANSI SGR escape sequences. One canonical literal per colour/attribute; /// every other crate aliases this module rather than re-declaring the bytes. /// -/// `WHITE` is SGR 37 (normal). printDiff.zig:177 uses SGR 97 — that is +/// `WHITE` is SGR 37 (normal); SGR 97 is /// [`BRIGHT_WHITE`], kept distinct so diff output stays byte-identical. pub mod ansi { pub const RESET: &str = "\x1b[0m"; @@ -94,8 +90,7 @@ pub fn color_for(name: &str) -> Option<&'static str> { None } -/// Byte-slice form of [`color_for`] for callers working over `&[u8]` templates -/// (mirrors Zig's `ComptimeStringMap.get`). +/// Byte-slice form of [`color_for`] for callers working over `&[u8]` templates. #[inline] pub fn color_for_bytes(name: &[u8]) -> Option<&'static str> { for &(k, v) in COLOR_TABLE { diff --git a/src/bundler/AstBuilder.rs b/src/bundler/AstBuilder.rs index e728bea1731..6b1a3b1612e 100644 --- a/src/bundler/AstBuilder.rs +++ b/src/bundler/AstBuilder.rs @@ -38,7 +38,7 @@ use bun_paths::fs::{Path as FsPath, PathName}; pub struct AstBuilder<'a, 'bump> { pub bump: &'bump Bump, pub source: &'a Source, - pub source_index: u32, // Zig: u31 + pub source_index: u32, pub stmts: Vec, pub scopes: Vec<*mut Scope>, pub symbols: Vec, @@ -58,12 +58,11 @@ pub struct AstBuilder<'a, 'bump> { // stub fields for ImportScanner duck typing // -// Zig used `comptime` zero-sized fields (`options`, `import_items_for_namespace`) -// and a `parser_features` decl so `ImportScanner.scan` could duck-type over both -// the real parser and `AstBuilder`. In Rust this becomes a trait that both impl. -// TODO(port): define `ImportScannerHost` trait in `bun_js_parser` and impl it here. +// The scanner transform is open-coded in `to_bundled_ast` for the stmt shapes +// AstBuilder emits; if `ImportScanner` ever grows a host trait in +// `bun_js_parser`, these stubs are the surface it would formalize. impl<'a, 'bump> AstBuilder<'a, 'bump> { - // stub for ImportScanner duck typing — Zig: `comptime import_items_for_namespace: struct { fn get(_, _) ?Map { return null; } }` + // stub for ImportScanner duck typing pub fn import_items_for_namespace_get( &self, _ref: Ref, @@ -94,8 +93,8 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { export_star_import_records: Vec::new(), declared_symbols: Default::default(), hot_reloading, - module_ref: Ref::NONE, // overwritten below (Zig: undefined) - hmr_api_ref: Ref::NONE, // overwritten below (Zig: undefined) + module_ref: Ref::NONE, // overwritten below + hmr_api_ref: Ref::NONE, // overwritten below }; ab.module_ref = ab.new_symbol(SymbolKind::Other, b"module")?; ab.hmr_api_ref = ab.new_symbol(SymbolKind::Other, b"hmr")?; @@ -123,7 +122,6 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { unsafe { &mut *self.current_scope } } - // PORT NOTE: Zig signature lacks `!` but body uses `try` — porting as fallible. pub fn push_scope(&mut self, kind: ScopeKind) -> Result<*mut Scope, OOM> { self.scopes.reserve(1); self.current_scope_mut().children.ensure_unused_capacity(1); @@ -139,7 +137,6 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { .children .append_assume_capacity(NonNull::new(scope).expect("bump alloc non-null").into()); self.scopes.push(self.current_scope); - // PERF(port): was appendAssumeCapacity — profile if it shows up on a hot path self.current_scope = scope; Ok(scope) } @@ -199,14 +196,13 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { let non_unique = MutableString::ensure_valid_identifier(path_name.non_unique_name_string_base())?; let name = strings::append(b"import_", &non_unique); - // PORT NOTE: copy into the arena so the raw `*const [u8]` stored on the - // Symbol outlives this stack frame (Zig used the parser arena arena). + // Copy into the arena so the raw `*const [u8]` stored on the + // Symbol outlives this stack frame. let name: &[u8] = self.bump.alloc_slice_copy(&name); let namespace_ref = self.new_symbol(SymbolKind::Other, name)?; let clauses: &mut [ClauseItem] = self.bump.alloc_slice_fill_default(N); - // Zig: `inline for` — all elements are `[]const u8`, so a plain loop suffices. for ((import_id, out_ref), clause) in identifiers_to_import .iter() .zip(out.iter_mut()) @@ -254,7 +250,6 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { pub fn append_stmt(&mut self, data: T) -> Result<(), OOM> { self.stmts.reserve(1); self.stmts.push(self.new_stmt(data)); - // PERF(port): was appendAssumeCapacity — profile if it shows up on a hot path Ok(()) } @@ -273,7 +268,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { Ok(ref_) } - // PORT NOTE: returns `BundledAst<'static>` (== `JSAst`) directly. The only + // Returns `BundledAst<'bump>` (== `JSAst`) directly. The only // `'arena`-carrying field, `url_for_css`, is always set to `b""` here, and // every other field stores arena data via raw pointers / `StoreSlice`, so // nothing borrows `&mut self` past this call. @@ -286,8 +281,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { let module_scope = self.current_scope; let mut parts = bun_ast::PartList::with_capacity_in(2, self.bump); - // PORT NOTE: Zig grew len then wrote `parts.mut(i).* = ...`, which is a - // bitwise store on the SoA slot. In Rust `*parts.mut_(i) = ...` first + // `*parts.mut_(i) = ...` on a grown-but-uninitialized slot first // *drops* the (uninitialized) prior `Part` — and `Part` carries Drop // fields (`Vec`/`HashMap`), so that drop frees garbage and corrupts the // heap (observed downstream as `printStmt` reading a junk `Stmt` @@ -323,12 +317,10 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { let module_scope_ref = unsafe { &*module_scope }; let generated_len = module_scope_ref.generated.len(); top_level_symbols_to_parts.ensure_total_capacity(generated_len)?; - // PORT NOTE: reshaped — Zig grew `entries` then wrote keys/values columns - // in lockstep + `reIndex`. Rust `ArrayHashMap` keeps keys/values in private - // `Vec`s and rebuilds hashes on every `put_assume_capacity`, so a plain - // pre-reserved insert loop is equivalent (and `re_index` is a no-op here). - // Zig shallow-copied a single `Vec(u32){1}`; `Vec` is move-only - // in Rust, so allocate a fresh one per key. + // `ArrayHashMap` keeps keys/values in private `Vec`s and rebuilds + // hashes on every `put_assume_capacity`, so a plain pre-reserved + // insert loop suffices (and `re_index` is a no-op here). `Vec` is + // move-only, so allocate a fresh one per key. for &ref_ in module_scope_ref.generated.slice() { top_level_symbols_to_parts .put_assume_capacity(ref_, bun_alloc::AstAlloc::vec_from_slice(&[1])); @@ -338,22 +330,18 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { // For more details on this section, look at js_parser.toAST // This is mimicking how it calls ImportScanner // - // PORT NOTE: Zig duck-typed `ImportScanner.scan(AstBuilder, ...)` and - // `ConvertESMExportsForHmr.{convertStmt,finalize}` over `AstBuilder` - // via `anytype`. The Rust `ImportScanner` is currently monomorphized + // `ImportScanner` is currently monomorphized // over the concrete `P<'_, TS, J, SCAN>` parser only (see // ImportScanner.rs:30), so the equivalent transform is open-coded here // for the stmt shapes `AstBuilder` callers actually emit (`S.Import`, // `S.Local{is_export}`, `S.ExportDefault(expr)`). Without this, the // generated server-component proxy keeps raw `export` keywords inside // the HMR function wrapper and JSC rejects the chunk with - // `SyntaxError: Unexpected keyword 'export'`. - // TODO(port): replace with a `ParserLike` trait so the real - // `ImportScanner`/`ConvertESMExportsForHmr` can accept `AstBuilder`. + // `SyntaxError: Unexpected keyword 'export'`. If `ImportScanner` / + // `ConvertESMExportsForHmr` ever grow a `ParserLike` host trait, this + // open-coded transform can be replaced by the real implementations. // - // PORT NOTE: Zig assigned `p.stmts.items` directly — its - // `ArrayListUnmanaged` storage is owned by `worker.allocator` and - // outlives the `AstBuilder` stack value. Rust's `Vec` would + // A `Vec` would // drop with `self`, leaving `parts[1].stmts` dangling once the // builder goes out of scope (UAF in the printer). Copy the `Copy` // `Stmt`s/`*mut Scope`s into the bump arena so the returned @@ -430,8 +418,8 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { // (`registerClientReference(...)`), which is not // `canBeMoved()` — generate a temp const binding and // reference it from `export_props`. - // SAFETY: `StmtOrExpr` lives in the arena; bitwise read - // matches Zig's value copy (no Drop fields touched). + // SAFETY: `StmtOrExpr` lives in the arena and has no + // Drop fields, so a bitwise read is a plain value copy. let value = unsafe { core::ptr::read(&raw const st.value) }.to_expr(); let temp_id = self.generate_temp_ref(Some(b"default_export")); parts[1].declared_symbols.append(DeclaredSymbol { @@ -516,12 +504,12 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { } else { // Non-HMR path: mirror `ImportScanner.scan(AstBuilder, p, stmts, // false, false, {})` for the stmt shapes AstBuilder callers emit - // (`S.Import`, `S.Local{is_export}`, `S.ExportDefault`). The Zig - // duck-typed scanner is what populates `named_exports` / + // (`S.Import`, `S.Local{is_export}`, `S.ExportDefault`). This + // scan is what populates `named_exports` / // `named_imports` / `import_records_for_current_part`; without it // the linker can't bind imports against this generated module // (e.g. `import { ssrManifest } from "bun:bake/server"` → - // "No matching export"). See PORT NOTE above re: monomorphization. + // "No matching export"). See the note above re: monomorphization. let in_stmts = core::mem::take(&mut self.stmts); for stmt in in_stmts.iter() { match stmt.data { @@ -569,7 +557,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { ); // SAFETY: module_scope is a live arena allocation. `Scope` is no-Drop - // arena POD; Zig bitwise-copied it (`module_scope.*`). + // arena POD, so a bitwise read is a plain value copy. let module_scope_value: Scope = unsafe { core::ptr::read(module_scope) }; Ok(crate::BundledAst { @@ -614,7 +602,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { pub fn generate_temp_ref(&mut self, name: Option<&[u8]>) -> Ref { self.new_symbol(SymbolKind::Other, name.unwrap_or(b"temp")) .expect("OOM") - // Zig: bun.handleOom — Rust aborts on OOM by default; explicit expect for clarity + // Rust aborts on OOM by default; explicit expect for clarity } pub fn record_export(&mut self, _loc: Loc, alias: &[u8], ref_: Ref) -> Result<(), OOM> { @@ -640,7 +628,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { match binding.data { B::BMissing(_) => {} B::BIdentifier(ident) => { - // PORT NOTE: reshaped for borrowck — capture original_name before calling &mut self method + // Reshaped for borrowck — capture original_name before calling &mut self method let original_name = self.symbols[ident.r#ref.inner_index() as usize].original_name; self.record_export(binding.loc, original_name.slice(), ident.r#ref) .expect("unreachable"); @@ -664,7 +652,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { Output::panic(args); } - /// Zig: `@"module.exports"` — Rust identifiers can't contain `.` + /// Builds the `module.exports` member expression. pub fn module_exports(&self, loc: Loc) -> Expr { self.new_expr(E::Dot { name: b"exports".into(), @@ -683,5 +671,3 @@ use bun_ast::Ref; pub use crate::DeferredBatchTask::DeferredBatchTask; pub use crate::ParseTask; pub use crate::ThreadPool; - -// ported from: src/bundler/AstBuilder.zig diff --git a/src/bundler/BundleThread.rs b/src/bundler/BundleThread.rs index 5e4c689e785..301468e5ea7 100644 --- a/src/bundler/BundleThread.rs +++ b/src/bundler/BundleThread.rs @@ -14,13 +14,11 @@ pub(crate) extern "C" fn timer_callback(_: *mut bun_sys::windows::libuv::Timer) /// Port of `std.Thread.ResetEvent` — single-shot manual-reset event used to /// block `spawn()` until the bundle thread has initialized its `Waker`. -// PORT NOTE: re-export `bun_threading::ResetEvent` (futex-backed); the local -// `wait()`/`set()`/`Default` API is identical, and the futex impl preserves -// the "set-before-wait does not deadlock" property the parking_lot draft had. +// Re-exports `bun_threading::ResetEvent` (futex-backed); the futex impl +// preserves the "set-before-wait does not deadlock" property `spawn()` relies on. pub use bun_threading::ResetEvent; /// Result of a `Bun.build` invocation handed back to the JS thread. -// PORT NOTE: mirrors `BundleV2.JSBundleCompletionTask.Result` (bundle_v2.zig). // Defined here (not re-exported from `bundle_v2`) because the un-gated // `bundle_v2` module keeps the draft body private; T6 (`bundler_jsc`) consumes // this via the `CompletionStruct` trait. @@ -44,7 +42,7 @@ pub enum BundleV2Result { /// /// - `configureBundler` is used to configure `Bundler`. /// - `completeOnBundleThread` is used to tell the task that it is done. -// PORT NOTE: trait bound lives on the `impl` (not the struct) so the +// The trait bound lives on the `impl` (not the struct) so the // `singleton` static can name `BundleThread` before T6 // provides the `CompletionStruct` impl for the forward-decl. pub struct BundleThread { @@ -56,49 +54,34 @@ pub struct BundleThread { pub generation: bun_core::Generation, } -/// Trait capturing the interface the Zig `CompletionStruct: type` parameter -/// must satisfy. +/// Trait capturing the interface a completion task must satisfy. /// -/// Zig used comptime duck typing and additionally asserted (via `@compileError`) -/// that the *only* valid instantiation is `BundleV2.JSBundleCompletionTask`. -/// The body of `generateInNewThread` directly touched JSBundleCompletionTask -/// fields (`.result`, `.log`, `.plugins`, `.config.files`, `.transpiler`); in -/// Rust those become trait accessors so the generic `BundleThread` stays +/// The trait accessors keep the generic `BundleThread` /// layout-agnostic. The concrete impl lives in T6 (`bun_bundler_jsc`). pub trait CompletionStruct: Node + Send + 'static { - /// Zig: `completion.configureBundler(transpiler, arena)` — `arena` - /// is the per-build mimalloc heap that backs `transpiler`, so the two - /// share lifetime `'a` (option fields like `optimize_imports: &'a StringSet` - /// borrow from `bump`). + /// `bump` is the per-build mimalloc heap that backs `transpiler`, so the + /// two share lifetime `'a` (option fields like `optimize_imports: &'a + /// StringSet` borrow from `bump`). fn configure_bundler<'a>( &mut self, transpiler: &mut Transpiler<'a>, bump: &'a Arena, ) -> Result<(), bun_core::Error>; - /// Zig: `completion.completeOnBundleThread()` fn complete_on_bundle_thread(&mut self); - /// Zig: `completion.result = .{ .err | .value }` fn set_result(&mut self, result: BundleV2Result); - /// Zig: `completion.log = out_log` fn set_log(&mut self, log: bun_ast::Log); - /// Zig: `completion.transpiler = this` fn set_transpiler(&mut self, this: *mut BundleV2<'_>); - /// Zig: `completion.plugins` fn plugins(&self) -> Option>; - /// Zig: `if (completion.config.files.map.count() > 0) &completion.config.files else null` - /// — folded into a single accessor so the opaque `FileMap` layout stays in T6. + /// Returns the file map if non-empty; a single accessor so the opaque + /// `FileMap` layout stays in T6. fn file_map(&mut self) -> Option>; - /// Zig: `switch (CompletionStruct) { BundleV2.JSBundleCompletionTask => completion, … }` - /// — the comptime type-switch collapses to a §Dispatch handle (erased - /// owner + `&'static` vtable) the impl provides, so the bundler can read - /// `result == .err` / `jsc_event_loop.enqueueTaskConcurrent` without - /// naming the concrete struct. + /// Returns a §Dispatch handle (erased owner + `&'static` vtable) the impl + /// provides, so the bundler can read `result == .err` / + /// `jsc_event_loop.enqueueTaskConcurrent` without naming the concrete + /// struct. fn as_js_bundle_completion_task(&mut self) -> dispatch::CompletionHandle; - /// Zig: `const transpiler = try arena.create(bun.Transpiler);` followed by - /// `try completion.configureBundler(transpiler, arena);` — Zig left the - /// `Transpiler` `undefined` and let `configureBundler` initialize it in place. - /// Rust's `Transpiler<'a>` has borrow-carrying fields (`arena: &'a Arena`, + /// `Transpiler<'a>` has borrow-carrying fields (`arena: &'a Arena`, /// `resolver: Resolver<'a>`) that cannot be zero-init'd, so the allocate + /// configure pair is folded into one trait call returning the /// arena-allocated, fully-configured transpiler. @@ -111,16 +94,10 @@ pub trait CompletionStruct: Node + Send + 'static { bump: &'a Arena, ) -> Result<&'a mut Transpiler<'a>, bun_core::Error>; - /// Zig: `try BundleV2.init(transpiler, null, arena, jsc.AnyEventLoop.init(arena), - /// false, jsc.WorkPool.get(), heap)` → wire `this.{plugins,completion,file_map}` - /// from `self` → `completion.transpiler = this` → `this.runFromJSInNewThread(...)` - /// → on success `self.set_result(Value(..))` → `this.deinitWithoutFreeingArena()`; - /// on error drain `this.linker.source_maps.*_wait_group` then deinit. + /// Constructs the `BundleV2`, wires `plugins`/`completion`/`file_map`, + /// and runs the bundle. /// - /// PORT NOTE: the `BundleV2` impl does not yet expose `init` / - /// `run_from_js_in_new_thread` (pending the linker pipeline). The Zig - /// `@compileError` already - /// proves this body is `JSBundleCompletionTask`-specific, so the + /// This body is `JSBundleCompletionTask`-specific, so the /// construction + run is delegated to the trait impl in T6, which has /// access to the concrete event-loop / work-pool wiring. The shared /// scaffolding (arena, AST arena push/pop, log copy, @@ -138,24 +115,16 @@ pub trait CompletionStruct: Node + Send + 'static { impl BundleThread { /// To initialize, put this somewhere in memory, and then call `spawn()` - // PORT NOTE: Zig `uninitialized` left `waker` as `undefined`. We can't use - // `mem::zeroed()` here — on macOS `Waker` holds a `Box<[u8]>` and on - // Windows a `&'static` loop, both of which have a NonNull validity - // invariant (zeroing them is *language-level* UB even if never read). + // We can't use + // `mem::zeroed()` here — the platform `Waker`s hold NonNull-validity + // fields (a `Box<[u8]>` on macOS, a niche-optimised `Option` on + // Windows), so zeroing them is *language-level* UB even if never read. // `placeholder()` yields a fully-initialized inert value instead. // `ready_event.wait()` in `spawn()` blocks until `thread_main` overwrites // it via `ptr::write`, so the placeholder is never observed live. pub fn uninitialized() -> Self { Self { - #[cfg(unix)] waker: Async::Waker::placeholder(), - #[cfg(windows)] - // TODO(port,windows): `Waker { loop_: &'static _ }` is also - // NonNull; provide a `placeholder()` once the Windows event-loop - // port lands. Kept as-is here to avoid an untestable change. - // SAFETY: see TODO — this is technically invalid_value UB on - // Windows; the field is overwritten before any read. - waker: unsafe { bun_core::ffi::zeroed_unchecked() }, queue: UnboundedQueue::new(), generation: 0, ready_event: ResetEvent::default(), @@ -168,8 +137,8 @@ impl BundleThread { /// `*instance`; callers must only touch it via the raw-pointer methods on this /// impl (e.g. `enqueue`) and never materialize a `&mut Self`. pub unsafe fn spawn(instance: *mut Self) -> std::io::Result> { - // PORT NOTE: `std.Thread.spawn(.{}, threadMain, .{instance})` → - // `std::thread::Builder` so the spawn error is surfaced (Zig used `try`). + // `std::thread::Builder` (not `std::thread::spawn`) so the spawn error + // is surfaced to the caller. struct SendPtr(*mut T); // SAFETY: the pointer is only dereferenced on the bundle thread via raw // projections; `BundleThread` itself is never moved across threads. @@ -225,12 +194,11 @@ impl BundleThread { // SAFETY: raw-ptr field projection; spawning thread is blocked in `ready_event.wait()`. unsafe { (*instance).ready_event.set() }; - // PORT NOTE: libuv Timer lives on stack for the lifetime of this never-returning fn. + // The libuv Timer lives on stack for the lifetime of this never-returning fn. // It MUST be declared at function scope (not inside the `#[cfg(windows)] { ... }` // block below) because `timer.init()`/`timer.start()` register `&timer`'s address // into the uv loop's intrusive handle queue / timer min-heap, and `waker.wait()` - // (→ `uv_run`) in the `loop {}` below dereferences that address. Matches Zig spec - // (BundleThread.zig:77) which hoists `var timer` to `threadMain` scope. + // (→ `uv_run`) in the `loop {}` below dereferences that address. #[cfg(windows)] let mut timer: bun_sys::windows::libuv::Timer = bun_core::ffi::zeroed(); #[cfg(windows)] @@ -258,8 +226,8 @@ impl BundleThread { // SAFETY: `generation` is only read/written on this (bundle) thread. let generation = unsafe { (*instance).generation }; // `panic = "abort"` → a Rust panic on this thread enters the - // crash-handler hook and aborts the whole process (matching Zig's - // `@panic`). No `catch_unwind` — there is nothing to catch. + // crash-handler hook and aborts the whole process. + // No `catch_unwind` — there is nothing to catch. match Self::generate_in_new_thread(completion, generation) { Ok(()) => {} Err(err) => { @@ -290,8 +258,6 @@ impl BundleThread { completion: &mut C, generation: bun_core::Generation, ) -> Result<(), bun_core::Error> { - // PORT NOTE: `ThreadLocalArena.init()` → `bun_alloc::Arena::new()` (bumpalo - // bump arena; `defer heap.deinit()` is handled by Drop). let heap = Arena::new(); let bump = &heap; @@ -300,18 +266,14 @@ impl BundleThread { ast_memory_store.reset(); ast_memory_store.push(); - // Zig: `const transpiler = try arena.create(bun.Transpiler);` - // `try completion.configureBundler(transpiler, arena);` - // Folded — see `create_and_configure_transpiler` doc. + // Allocate + configure folded — see `create_and_configure_transpiler` doc. let transpiler = completion.create_and_configure_transpiler(bump)?; transpiler.resolver.generation = generation; - // Zig: `const this = try BundleV2.init(transpiler, null, arena, - // jsc.AnyEventLoop.init(arena), false, jsc.WorkPool.get(), heap);` - // followed by field wiring + `runFromJSInNewThread`. Delegated — see + // Construction + run delegated — see // `init_and_run` doc. Reborrow `transpiler` through a raw ptr so - // `completion` can be borrowed again below (Zig stored `*Transpiler`). + // `completion` can be borrowed again below. let transpiler_ptr: *mut Transpiler<'_> = transpiler; let run = completion.init_and_run( // SAFETY: `transpiler` lives in `bump` for the duration of `heap`. @@ -322,9 +284,7 @@ impl BundleThread { std::ptr::from_ref(bun_threading::work_pool::WorkPool::get()).cast_mut(), ); - // PORT NOTE: Zig's overlapping `defer { ast_memory_store.pop(); - // this.deinitWithoutFreeingArena(); }` + `errdefer { wait_groups; copy log }` - // captured ≥2 disjoint &mut borrows. Restructured as straight-line: log copy + // Straight-line teardown: log copy // runs on both paths; `completeOnBundleThread` only on success (the error // path's `set_result(Err)` + complete happens in `thread_main`). The // `deinitWithoutFreeingArena` + wait-group drain live inside `init_and_run` @@ -333,7 +293,7 @@ impl BundleThread { // SAFETY: `transpiler.log` is the arena-allocated `*mut Log` set up by // `configure_bundler`; valid for the lifetime of `heap`. Raw deref so the // `&'a mut Transpiler` consumed by `init_and_run` above is not reborrowed. - let _ = unsafe { (*(*transpiler_ptr).log).append_to_with_recycled(&mut out_log, true) }; // logger OOM-only (Zig: catch unreachable) + let _ = unsafe { (*(*transpiler_ptr).log).append_to_with_recycled(&mut out_log, true) }; // logger OOM-only completion.set_log(out_log); if run.is_ok() { @@ -342,11 +302,9 @@ impl BundleThread { ast_memory_store.pop(); - // Zig allocated `transpiler` / `ast_memory_store` from the arena and - // relied on `defer heap.deinit()` to bulk-free them. That works there - // because every container they hold (`Resolver` caches, `BundleOptions` - // strings, the AST allocator's own `mi_heap` handle, …) is itself - // arena-backed. The Rust port replaced those containers with global-heap + // `transpiler` / `ast_memory_store` are arena-allocated, but their + // containers (`Resolver` caches, `BundleOptions` strings, the AST + // allocator's own `mi_heap` handle, …) live on the global heap as // `Vec`/`Box`/`HashMap`, so dropping `heap` (`mi_heap_destroy`) reclaims // the struct bytes but never runs `Transpiler::drop` / // `ASTMemoryAllocator::drop` — leaking the resolver's directory/file @@ -375,11 +333,10 @@ impl BundleThread { /// Lazily-initialized singleton. This is used for `Bun.build` since the /// bundle thread may not be needed. -// PORT NOTE: Zig had a per-monomorphization `singleton` struct with -// `static var instance`. Rust forbids generic statics, so the storage is +// Rust forbids generic statics, so the storage is // type-erased (`*mut ()`) and the accessor functions are generic over `C`. -// The Zig source already `@compileError`s for any `CompletionStruct` other than -// `JSBundleCompletionTask`, so in practice exactly one `C` is ever used and the +// In practice exactly one `C` (`JSBundleCompletionTask`) is ever used — see +// `get`'s safety contract — so the // erased static is sound. T6 (`bun_bundler_jsc`) calls these with its concrete // completion-task type. pub mod singleton { @@ -422,7 +379,7 @@ pub mod singleton { /// Returns the raw singleton pointer. The bundle thread runs `thread_main` /// against this allocation for the process lifetime, so callers MUST NOT - /// materialize `&mut BundleThread` from it (Zig `*Self` aliasing semantics). + /// materialize `&mut BundleThread` from it. /// Use `BundleThread::enqueue(get(), ...)` instead. /// /// # Safety @@ -453,5 +410,3 @@ pub mod singleton { pub use crate::DeferredBatchTask; pub use crate::ParseTask; pub use crate::ThreadPool; - -// ported from: src/bundler/BundleThread.zig diff --git a/src/bundler/Cargo.toml b/src/bundler/Cargo.toml index 8da369721f5..9c1828e5dfd 100644 --- a/src/bundler/Cargo.toml +++ b/src/bundler/Cargo.toml @@ -27,10 +27,7 @@ bumpalo.workspace = true typed-arena.workspace = true bun_base64.workspace = true bun_alloc.workspace = true -# TODO(b2-blocked): bun_analytics — adding the dep triggers a fresh build of -# upstream crates (css/logger/js_parser) with in-progress breakage. Re-add once -# the tree is green; usages in options.rs are gated or stubbed locally. -# bun_analytics.workspace = true +bun_analytics.workspace = true bun_resolver.workspace = true # `bun_resolve_builtins` only depends on `bun_options_types` + `bun_string` # (both already in this graph), so no cycle. diff --git a/src/bundler/Chunk.rs b/src/bundler/Chunk.rs index cb06d1ec675..5b44fc174a2 100644 --- a/src/bundler/Chunk.rs +++ b/src/bundler/Chunk.rs @@ -8,7 +8,7 @@ use bun_ast::{ImportKind, ImportRecord}; use bun_ast::{Ref, Stmt}; use bun_collections::{ArrayHashMap, AutoBitSet, VecExt}; use bun_core::{FeatureFlags, Output}; -// PORT NOTE: `bun.ast.Index` is mirrored as both `crate::Index` +// Note: `bun.ast.Index` is mirrored as both `crate::Index` // (`bun_ast::Index`) and `bun_ast::Index` via a // TYPE_ONLY split. `CssImportOrderKind::SourceIndex` carries the js_parser // flavor because its sole producer (`findImportedFilesInCSSOrder`) constructs @@ -36,9 +36,12 @@ pub struct ChunkImport { pub import_kind: ImportKind, } -// TODO(port): arena lifetime — string/slice fields below borrow from the bundler arena -// (no deinit in Zig). Currently uses &'static [u8] / Box<[T]> as placeholders; -// should thread a `'bump` lifetime or use arena slice newtypes. +// Lifetime note: string/slice fields below conceptually borrow from the +// bundler arena. The borrow is erased to +// `&'static [u8]` (the arena is owned by `BundleV2` and outlives every +// `Chunk`; see the lifetime-erasure note on `LinkerGraph::bump`) or owns a +// `Box<[T]>` instead of threading a `'bump` lifetime through the chunk +// pipeline. pub struct Chunk { /// This is a random string and is used to represent the output path of this /// chunk before the final output path has been computed. See OutputPiece @@ -56,9 +59,7 @@ pub struct Chunk { /// We must not keep pointers to this type until all chunks have been allocated. pub entry_bits: AutoBitSet, - /// PORT NOTE: Zig stored this as an arena-owned `[]const u8` (linker arena); - /// the Rust `Chunk` owns it as a `Box<[u8]>` so dropping the chunk slice - /// frees it (matches `c.arena().dupe(u8, ..)` ownership without leaking). + /// Owned as a `Box<[u8]>` so dropping the chunk slice frees it. pub final_rel_path: Box<[u8]>, /// The path template used to generate `final_rel_path` pub template: PathTemplate, @@ -75,17 +76,15 @@ pub struct Chunk { pub intermediate_output: IntermediateOutput, pub isolated_hash: u64, - // TODO(port): was `= undefined` in Zig (set before use). The Zig field is - // the `renamer.Renamer` union; the Rust enum borrows from the symbol table - // (`Renamer<'r,'src>`), which can't live in a 'static-ish struct yet. - // `ChunkRenamer` is an owned-erased placeholder (see `crate::bun_renamer`). + // Set before use. The borrowed enum (`Renamer<'r,'src>`) + // borrows from the symbol table and so can't live in this owning struct. + // `ChunkRenamer` is the owning equivalent (see `crate::bun_renamer`). pub renamer: bun_renamer::ChunkRenamer, pub compile_results_for_chunk: CompileResultSlots, /// Pre-built JSON fragment for this chunk's metafile output entry. /// Generated during parallel chunk generation, joined at the end. - /// PORT NOTE: owned `Box<[u8]>` (was arena-owned `[]const u8` in Zig). pub metafile_chunk_json: Box<[u8]>, /// Pack boolean flags to reduce padding overhead. @@ -113,8 +112,8 @@ impl Default for Content { // `computeCrossChunkDependencies`, `generateChunksInParallel`). Raw-pointer // fields (`Layers::Borrowed`, `ChunkRenamer` arena) // point into bundler-arena storage that outlives the -// pool join and is only mutated by the owning task. Zig has no Send/Sync -// distinction; mirror `InputFile`'s blanket impls (bundle_v2.rs). +// pool join and is only mutated by the owning task; this mirrors +// `InputFile`'s blanket impls (bundle_v2.rs). // // CONCURRENCY: during the `generate_compile_result_for_*_chunk` fan-out, many // `PendingPartRange` tasks share ONE `*mut Chunk` and each writes a disjoint @@ -122,10 +121,10 @@ impl Default for Content { // (UnsafeCell-per-slot) so the per-task write is routed through interior // mutability and never requires an aliased `&mut Chunk` / // `&mut [CompileResult]` — see [`Chunk::write_compile_result_slot`]. -// `files_with_parts_in_chunk` values are bumped via atomic RMW (Zig -// `@atomicRmw`); the renamer is fully populated before fan-out and treated as +// `files_with_parts_in_chunk` values are bumped via atomic RMW; +// the renamer is fully populated before fan-out and treated as // read-only by the printer. -// TODO(ub-audit): `Renamer<'r>` still borrows `&'r mut {Number,Minify}Renamer`, +// Caveat: `Renamer<'r>` still borrows `&'r mut {Number,Minify}Renamer`, // so the per-chunk renamer is reborrowed mutably from each part-range task; // the printer never writes through it, but the borrow should become `&'r`. unsafe impl Send for Chunk {} @@ -134,11 +133,11 @@ unsafe impl Send for Chunk {} // `files_with_parts_in_chunk` atomic counters; the remaining fields are // frozen before fan-out and read single-threaded after the pool join — // **except** `renamer`, which the per-part-range printer reborrows `&mut` -// from each worker (read-only in practice). See `TODO(ub-audit)` above: +// from each worker (read-only in practice). See the renamer caveat above: // once `Renamer<'r>` borrows `&'r` instead of `&'r mut`, this caveat (and // the matching split-borrow in `generate_compile_result_for_js_chunk`) goes // away. Pre-existing; this impl mirrors `unsafe impl Send for Chunk` and -// the Zig single-pointer fan-out it ports. +// the single-pointer fan-out the workers use. unsafe impl Sync for Chunk {} /// Disjoint-slot output buffer for [`Chunk::compile_results_for_chunk`]. @@ -182,6 +181,14 @@ impl CompileResultSlots { // SAFETY: reads happen only after the pool join; no concurrent writer. self.0.iter().map(|c| unsafe { &*c.get() }) } + + /// Post-join exclusive access to one slot (e.g. to transfer ownership of + /// the result out of the chunk). `&mut self` proves no concurrent writer, + /// so `UnsafeCell::get_mut` needs no unsafe here. + #[inline] + pub fn get_mut(&mut self, i: usize) -> &mut CompileResult { + self.0[i].get_mut() + } } impl core::ops::Index for CompileResultSlots { @@ -198,7 +205,6 @@ impl Default for Chunk { Chunk { unique_key: b"", files_with_parts_in_chunk: ArrayHashMap::new(), - // Zig: `entry_bits: AutoBitSet = undefined` — static-arm zero init. entry_bits: AutoBitSet::init_empty(0).expect("static AutoBitSet"), final_rel_path: Box::default(), template: PathTemplate::default(), @@ -293,8 +299,7 @@ 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). - // PORT NOTE: reshaped for borrowck — Zig calls getJSChunkForHTML(chunks) and then - // indexes into the same `chunks`. Here we scan immutably for the JS chunk, copy the + // 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: { @@ -347,7 +352,6 @@ impl Order { /// equidistant to an entry point, then break the tie by sorting on the /// stable source index derived from the DFS over all entry points. pub(crate) fn sort(a: &mut [Order]) { - // std.sort.pdq → unstable sort a.sort_unstable_by(|a, b| { if Order::less_than(Order::default(), *a, *b) { core::cmp::Ordering::Less @@ -385,9 +389,7 @@ pub enum IntermediateOutput { /// Owns the joined output buffer alongside the `OutputPiece` slices that /// point into it. /// -/// PORT NOTE: In Zig, `breakOutputIntoPieces` calls `j.done(alloc)` with the -/// per-worker arena, so the joined buffer outlives the chunk by construction -/// and `OutputPiece.data` stays valid. The Rust `StringJoiner::done()` +/// Note: `StringJoiner::done()` /// returns a `Box<[u8]>`; if that box is dropped at the end of /// `break_output_into_pieces`, every piece's `data` slice dangles (ASAN /// use-after-poison in `generate_isolated_hash`). Keep the box alive next to @@ -425,18 +427,16 @@ pub struct CodeResult { pub shifts: Vec, } -// PORT NOTE: Zig used `std.mem.Allocator`; the Rust crate exposes a global -// mimalloc — we don't need a vtable here yet. `()` is kept as a token so the -// caller's `Option<&DynAlloc>` plumbing matches the Zig signature; the actual +// We don't need an allocator vtable here yet. `()` is kept as a token for the +// caller's `Option<&DynAlloc>` plumbing; the actual // allocation goes through `alloc_buf` (global mimalloc) regardless. Real // arena threading (page_allocator vs default_allocator) lands when // `bun_alloc::Allocator` is a stable trait object. type DynAlloc = (); -/// `arena.alloc(u8, n)` — until `DynAlloc` is a real trait object, route -/// through the global arena. PERF(port): Zig picked page_allocator for -/// `n >= 512KiB`; mimalloc handles large allocations via mmap already so this -/// is a behavior match in practice. +/// Until `DynAlloc` is a real trait object, route +/// through the global arena; mimalloc handles large allocations via mmap +/// already. #[inline] fn alloc_buf(_arena: DynAlloc, n: usize) -> Result, AllocError> { // Zero-fill is required for soundness: `set_len` over uninit bytes violates @@ -449,9 +449,9 @@ fn alloc_buf(_arena: DynAlloc, n: usize) -> Result, AllocError> { Ok(v.into_boxed_slice()) } -/// Extract the `OutputFile` index from a trailing `AdditionalFile` entry. -/// Zig: `files.last().output_file` (untagged-union field read; bundler always -/// pushes `.output_file = …` for asset additional-files, see bundle_v2.zig). +/// Extract the `OutputFile` index from a trailing `AdditionalFile` entry +/// (the bundler always pushes `.output_file = …` for asset additional-files, +/// see bundle_v2.rs). #[inline] fn additional_output_file_index(f: &AdditionalFile) -> usize { match *f { @@ -464,8 +464,8 @@ fn additional_output_file_index(f: &AdditionalFile) -> usize { impl IntermediateOutput { pub fn allocator_for_size(_size: usize) -> &'static DynAlloc { - // PERF(port): Zig picks page_allocator for large buffers vs mimalloc default. - // TODO(port): expose page_allocator / default_allocator as &'static dyn Allocator + // mimalloc serves large allocations via mmap already, so the global + // allocator suffices (see `alloc_buf`). &() } @@ -544,13 +544,13 @@ impl IntermediateOutput { parse_graph: &Graph, linker_graph: &LinkerGraph<'_>, import_prefix: &[u8], - // PORT NOTE: Zig passed `*Chunk` / `[]Chunk` (freely aliased — `chunk` - // is `&chunks[i]`). The body only reads both, so take `&` to avoid + // Note: `chunk` aliases `&chunks[i]`. The body only reads both, so + // take `&` to avoid // overlapping `&mut Chunk` + `&mut [Chunk]` UB at every call site. chunk: &Chunk, chunks: &[Chunk], - // PORT NOTE: `?*usize` in Zig — accept both `&mut usize` and - // `Option<&mut usize>` so call sites that ported either way compile. + // Accept both `&mut usize` and + // `Option<&mut usize>` so call sites spelled either way compile. display_size: impl Into>, force_absolute_path: bool, enable_source_map_shifts: bool, @@ -595,11 +595,11 @@ impl IntermediateOutput { parse_graph: &Graph, linker_graph: &LinkerGraph<'_>, import_prefix: &[u8], - // See `code()` PORT NOTE — `chunk` aliases `chunks[i]`; body is read-only. + // See `code()` note — `chunk` aliases `chunks[i]`; body is read-only. chunk: &Chunk, chunks: &[Chunk], - // PORT NOTE: `?*usize` in Zig — accept both `&mut usize` and - // `Option<&mut usize>` so call sites that ported either way compile. + // Accept both `&mut usize` and + // `Option<&mut usize>` so call sites spelled either way compile. display_size: impl Into>, force_absolute_path: bool, enable_source_map_shifts: bool, @@ -640,7 +640,7 @@ impl IntermediateOutput { graph: &Graph, linker_graph: &LinkerGraph<'_>, import_prefix: &[u8], - // See `code()` PORT NOTE — `chunk` aliases `chunks[i]`; body is read-only. + // See `code()` note — `chunk` aliases `chunks[i]`; body is read-only. chunk: &Chunk, chunks: &[Chunk], display_size: Option<&mut usize>, @@ -671,7 +671,6 @@ impl IntermediateOutput { }; if ENABLE_SOURCE_MAP_SHIFTS { - // PERF(port): was assume_capacity shifts.push(shift); } @@ -851,7 +850,6 @@ impl IntermediateOutput { _ => {} } shift.after.advance(content); - // PERF(port): was assume_capacity shifts.push(shift); } // For chunk content, escape closing tags ( QueryKind { - // Zig `enum(u3)` type-checks the field on assignment so 5..=7 are - // unrepresentable; match exhaustively (out-of-range tag would be UB). + // Tags 5..=7 are never assigned; match exhaustively + // (an out-of-range tag would be a bug, not UB). match (self.0 >> 29) as u8 { 0 => QueryKind::None, 1 => QueryKind::Asset, @@ -1181,8 +1175,7 @@ pub(crate) const UNIQUE_KEY_LEN: usize = UNIQUE_KEY_PREFIX_LEN + 1 + 8; /// 25-byte unique-key wire format `{hex16(prefix)}{KIND}{index:08}` shared by /// every emitter (ParseTask file/napi/sqlite loaders, server-component /// boundaries, HTML-import manifest, chunk IDs) and consumed by exactly one -/// scanner (`LinkerContext::break_output_into_pieces`). Mirrors Zig -/// `"{f}{LETTER}{d:0>8}"` with `bun.fmt.hexIntLower` byte-for-byte. +/// scanner (`LinkerContext::break_output_into_pieces`). #[derive(Clone, Copy)] pub(crate) struct UniqueKey { pub prefix: u64, @@ -1249,7 +1242,6 @@ impl EntryPoint { (self.0 >> 63) & 1 != 0 } - // Zig callers mutate packed fields directly (e.g. `chunk.entry_point.is_entry_point = true`). #[inline] pub fn set_source_index(&mut self, v: u32) { self.0 = (self.0 & !0xFFFF_FFFF) | (v as u64); @@ -1279,7 +1271,9 @@ pub struct JavaScriptChunk { pub parts_in_chunk_in_order: Box<[PartRange]>, // for code splitting - // TODO(port): Zig uses ArrayHashMapUnmanaged(Ref, string, Ref.ArrayHashCtx, false) — custom hash ctx + // The map hashes via `Ref`'s `Hash` impl. Values + // are `&'static`-erased slices into bundler-owned storage (see the + // lifetime note on `Chunk`). pub exports_to_other_chunks: ArrayHashMap, pub imports_from_other_chunks: ImportsFromOtherChunks, pub cross_chunk_prefix_stmts: Vec, @@ -1313,7 +1307,7 @@ pub struct CssChunk { impl Drop for CssChunk { fn drop(&mut self) { - // Zig `asts: []BundlerStyleSheet` is an arena slice of bitwise shallow + // `asts` is a slice of bitwise shallow // copies (see `prepareCssAstsForChunk` `ptr::read`). Multiple slots may // alias the same source AST's heap buffers when a file is imported more // than once, so element-wise drop would double-free. @@ -1323,10 +1317,8 @@ impl Drop for CssChunk { } } -/// Zig: `const CssImportKind = enum { source_index, external_path, import_layers }` is the -/// (private) tag enum for `CssImportOrder.kind: union(enum) { ... }`. In Rust the tagged -/// union is `CssImportOrderKind`; callers that switch on `css_import.kind` reference it via -/// the Zig-spelled name, so re-export it here. +/// Alias for `CssImportOrderKind`; callers that switch on `css_import.kind` +/// reference it via this name, so re-export it here. pub type CssImportKind = CssImportOrderKind; pub struct CssImportOrder { @@ -1364,11 +1356,11 @@ pub enum CssImportOrderKind { SourceIndex(Index), } -// TODO(port): bun.ptr.Cow(Vec, { copy = deepCloneInfallible, deinit = clearAndFree }) -// LayerName payload allocations live in the arena, so the Zig deinit is a shallow clearAndFree. -// `std::borrow::Cow<'_, Vec<_>>` requires `Vec: Clone` (not implemented). Port the -// Zig `bun.ptr.Cow` shape directly: a tag + raw pointer for the borrowed arm. Should -// thread `'bump` (arena-borrowed) and confirm Clone semantics match deepCloneInfallible. +// Copy-on-write list of layer names. +// `std::borrow::Cow<'_, Vec<_>>` requires `Vec: Clone` (not implemented), so this +// is a hand-rolled Cow: a tag + raw pointer for the borrowed +// arm. LayerName payload allocations live in the arena, so dropping the owned +// arm is a shallow free and `to_owned` deep-clones element-wise. pub enum Layers { /// Borrowed from another `CssImportOrder`'s `Layers` or the parsed stylesheet. Borrowed(bun_ptr::BackRef>), @@ -1384,28 +1376,28 @@ impl Layers { } } - /// Zig: `Chunk.CssImportOrder.Layers.borrow(ptr)` — Cow::Borrowed. + /// Cow::Borrowed constructor. /// /// Takes `NonNull` (not `&Vec`) because the sole caller in /// `findImportedFilesInCSSOrder.rs` type-puns the lifetime-erased shadow /// `crate::bun_css::LayerName` to the real `::bun_css::LayerName` via a /// raw-pointer cast — that nominal-type erasure cannot go through `&`. /// The pointee is arena-owned storage that outlives the chunk pipeline - /// (see TODO(port) above re: `'bump`); `BackRef` encapsulates that + /// (see the lifetime note on `Chunk`); `BackRef` encapsulates that /// invariant so `inner()`/`to_owned()` deref sites are safe. #[inline] pub(crate) fn borrow(p: core::ptr::NonNull>) -> Self { Layers::Borrowed(bun_ptr::BackRef::from(p)) } - /// Zig: `bun.ptr.Cow.replace` — drop owned (arena-backed, so no-op) and + /// Drop owned (arena-backed, so no-op) and /// install a fresh owned value. #[inline] pub(crate) fn replace(&mut self, new: Vec) { *self = Layers::Owned(new); } - /// Zig: `bun.ptr.Cow.toOwned` — if borrowed, deep-clone into an owned + /// If borrowed, deep-clone into an owned /// list and return `&mut` to it; if already owned, return as-is. pub(crate) fn to_owned(&mut self) -> &mut Vec { if let Layers::Borrowed(p) = *self { @@ -1422,8 +1414,7 @@ impl CssImportOrder { pub(crate) fn hash(&self, hasher: &mut H) { // TODO: conditions, condition_import_records - // Zig: bun.writeAnyToHasher(hasher, std.meta.activeTag(this.kind)) — feeds the small-int - // tag bytes. core::mem::Discriminant is opaque/pointer-sized; hash an explicit u8 instead. + // core::mem::Discriminant is opaque/pointer-sized; hash an explicit u8 tag instead. let tag: u8 = match &self.kind { CssImportOrderKind::Layers(_) => 0, CssImportOrderKind::ExternalPath(_) => 1, @@ -1446,9 +1437,8 @@ impl CssImportOrder { hasher.update(b"\x00"); } CssImportOrderKind::ExternalPath(path) => hasher.update(path.text), - // PORT NOTE: `Index` is a `#[repr(transparent)]` u32 newtype but - // doesn't impl `AsBytes`; hash the inner u32 (Zig hashed the - // `Index.Int` bytes directly). + // Note: `Index` is a `#[repr(transparent)]` u32 newtype but + // doesn't impl `AsBytes`; hash the inner u32. CssImportOrderKind::SourceIndex(idx) => { bun_core::write_any_to_hasher(hasher, idx.get()) } @@ -1467,7 +1457,7 @@ impl CssImportOrder { #[allow(dead_code)] pub(crate) struct CssImportOrderDebug<'a, 'ctx> { inner: &'a CssImportOrder, - // PORT NOTE: split lifetimes — `LinkerContext<'ctx>` is invariant over `'ctx`, + // Note: split lifetimes — `LinkerContext<'ctx>` is invariant over `'ctx`, // so coupling the borrow lifetime to the struct param (`&'a LinkerContext<'a>`) // forces every caller's `&CssImportOrder` and `&LinkerContext` to share one // region. The Display impl only reads `ctx.parse_graph` (a raw `*mut Graph`), @@ -1623,7 +1613,7 @@ impl Content { } } -// Re-exports (Zig: pub const X = ...) +// Re-exports pub use crate::DeferredBatchTask::DeferredBatchTask; pub use crate::ParseTask; pub use crate::ThreadPool; @@ -1656,5 +1646,3 @@ pub mod bun_renamer { } } } - -// ported from: src/bundler/Chunk.zig diff --git a/src/bundler/DeferredBatchTask.rs b/src/bundler/DeferredBatchTask.rs index fb5197da6f2..b5fbbc71dac 100644 --- a/src/bundler/DeferredBatchTask.rs +++ b/src/bundler/DeferredBatchTask.rs @@ -17,14 +17,14 @@ pub use crate::bundle_v2::dispatch::CompletionDispatch; #[derive(Default)] pub struct DeferredBatchTask { - // Zig: `running: if (Environment.isDebug) bool else u0` — zero-sized in release. + // Debug-only flag; zero-sized in release. #[cfg(debug_assertions)] running: bool, } impl DeferredBatchTask { pub fn init(&mut self) { - // PORT NOTE: kept as `&mut self` (not `-> Self`) — this struct is embedded + // Kept as `&mut self` (not `-> Self`) — this struct is embedded // by value in BundleV2 (recovered via container_of in `get_bundle_v2`), so // it is reset in place, never separately constructed. #[cfg(debug_assertions)] @@ -35,8 +35,8 @@ impl DeferredBatchTask { pub fn get_bundle_v2(&mut self) -> &mut BundleV2<'static> { // SAFETY: `self` is always the `drain_defer_task` field of a live `BundleV2`; - // this struct is never instantiated standalone. Lifetime erased to 'static - // (mirrors Zig raw `*BundleV2`); callers must not outlive the owning bundle. + // this struct is never instantiated standalone. Lifetime erased to 'static; + // callers must not outlive the owning bundle. unsafe { &mut *bun_core::from_field_ptr!( BundleV2<'static>, @@ -59,27 +59,24 @@ impl DeferredBatchTask { std::ptr::from_mut::(self).cast::<()>(), )); - // Zig: `getBundleV2().jsLoopForPlugins().enqueueTaskConcurrent(task)`. self.get_bundle_v2().enqueue_on_js_loop_for_plugins(task); } pub fn run_on_js_thread(&mut self) { - // PORT NOTE: reshaped for borrowck — Zig's `defer this.deinit()` only resets - // the debug `running` flag; since nothing follows `drainDeferred`, ignoring - // its error and resetting the flag afterwards is equivalent on both paths. + // `deinit` only resets + // the debug `running` flag; nothing follows `drain_deferred`, so + // resetting the flag afterwards covers both paths. { let bv2 = self.get_bundle_v2(); - // Zig: `if (bv2.completion) |c| c.result == .err else false` let rejected = bv2.completion.map(|c| c.result_is_err()).unwrap_or(false); - // Zig: `bv2.plugins.?.drainDeferred(rejected) catch return;` - // `catch return` collapses to discarding the void result — see + // The void result is discarded — see // `Plugin::drain_deferred` for the exception-scope note. bv2.plugins_mut().expect("plugins").drain_deferred(rejected); } self.deinit(); } - // PORT NOTE: not `impl Drop` — this struct is an intrusive field of `BundleV2` + // Not `impl Drop` — this struct is an intrusive field of `BundleV2` // and `deinit` is a debug-flag reset, not resource teardown. fn deinit(&mut self) { #[cfg(debug_assertions)] @@ -88,5 +85,3 @@ impl DeferredBatchTask { } } } - -// ported from: src/bundler/DeferredBatchTask.zig diff --git a/src/bundler/Graph.rs b/src/bundler/Graph.rs index b2f2c3fd9ce..4de30205a97 100644 --- a/src/bundler/Graph.rs +++ b/src/bundler/Graph.rs @@ -18,8 +18,8 @@ use bun_ast::Index; pub(crate) use crate::IndexInt; pub struct Graph<'a> { - // bundle_v2.zig:992 allocates this from `this.arena()` (the `self.heap` arena) and - // bundle_v2.zig:2248 calls `pool.deinit()`, so this is arena-owned but self-referential + // `BundleV2::init` allocates this from the `self.heap` arena and + // `BundleV2::deinit` calls `pool.deinit()`, so this is arena-owned but self-referential // (sibling field). `BackRef` (not raw `NonNull`) so the read accessor `pool()` is // safe — the BACKREF invariant (pointee outlives holder) holds for the entire // bundle pass. @@ -27,7 +27,6 @@ pub struct Graph<'a> { pub heap: &'a ThreadLocalArena, /// Mapping user-specified entry points to their Source Index - // PERF(port): Zig fed this ArrayList from `self.heap` (self-referential arena). pub entry_points: Vec, /// Maps entry point source indices to their original specifiers (for virtual entries resolved by plugins) pub entry_point_original_names: IndexStringMap, @@ -35,8 +34,8 @@ pub struct Graph<'a> { pub input_files: MultiArrayList, /// Every source index has an associated Ast /// When a parse is in progress / queued, it is `Ast.empty` - // PORT NOTE: BundledAst<'arena> borrows from self.heap (sibling-field self-ref); - // 'static here is a placeholder. TODO(refactor): thread the lifetime via raw ptr or Ouroboros. + // `JSAst<'a>` borrows from the arena behind `self.heap`; `'a` ties the AST + // entries to that arena's lifetime (sibling-field relationship). pub ast: MultiArrayList>, /// During the scan + parse phase, this value keeps a count of the remaining @@ -78,7 +77,6 @@ pub struct Graph<'a> { /// pre-allocations without re-iterating the file listing. pub css_file_count: usize, - // PERF(port): Zig fed this ArrayList from `self.heap` (self-referential arena). pub additional_output_files: Vec, pub kit_referenced_server_data: bool, @@ -103,9 +101,8 @@ pub struct InputFile { pub secondary_path: AstVec, pub loader: options::Loader, pub side_effects: SideEffects, - // PORT NOTE: Zig stored `arena: std.mem.Allocator = bun.default_allocator` - // here so deinit could free `source`/`secondary_path` with the right alloc. - // In Rust the owned fields (Box/Vec) carry their arena; field dropped. + // No `arena` field — the owned fields + // (Box/Vec) carry their allocator. pub additional_files: AstVec, pub unique_key_for_additional_file: Box<[u8], AstAlloc>, pub content_hash_for_additional_file: u64, @@ -156,7 +153,7 @@ impl<'a> Graph<'a> { pub fn new(heap: &'a ThreadLocalArena) -> Self { Self { // Self-referential arena pointer; real value wired in - // `BundleV2::init` before any use (Graph.zig has `= undefined`). + // `BundleV2::init` before any use. pool: bun_ptr::BackRef::from(NonNull::::dangling()), heap, entry_points: Vec::new(), @@ -181,8 +178,8 @@ impl<'a> Graph<'a> { impl<'a> Graph<'a> { /// Shared borrow of the bundler `ThreadPool`. /// - /// `pool` is arena-allocated in `BundleV2::init` (bundle_v2.zig:992) and - /// torn down in `BundleV2::deinit` (bundle_v2.zig:2248). It is non-null + /// `pool` is arena-allocated in `BundleV2::init` and + /// torn down in `BundleV2::deinit`. It is non-null /// and valid for the entire bundle pass; see LIFETIMES.tsv row 170 /// (BACKREF). All `ThreadPool` driver methods (`schedule`, `start`, /// `worker_pool`, `schedule_inside_thread_pool`) take `&self`, so callers @@ -237,10 +234,8 @@ impl<'a> Graph<'a> { } } -// Spec: `side_effects: _resolver.SideEffects` (Graph.zig:74). The resolver +// The resolver // crate re-exports the canonical enum from `bun_options_types`; re-export it // here so `InputFile` and the derived `items_side_effects()` SoA accessor share // the same type that `LinkerContext::mark_file_live_for_tree_shaking` expects. use bun_ast::SideEffects; - -// ported from: src/bundler/Graph.zig diff --git a/src/bundler/HTMLImportManifest.rs b/src/bundler/HTMLImportManifest.rs index 21b1df4849b..1c0ac0698f0 100644 --- a/src/bundler/HTMLImportManifest.rs +++ b/src/bundler/HTMLImportManifest.rs @@ -70,8 +70,6 @@ impl<'a> fmt::Display for HTMLImportManifest<'a> { &mut adapter, ) { Ok(()) => Ok(()), - // We use std.fmt.count for this - // Zig: error.NoSpaceLeft => unreachable, error.OutOfMemory => return error.OutOfMemory Err(_) => Err(fmt::Error), } } @@ -97,7 +95,7 @@ fn write_entry_item( bun_js_printer::write_json_string::<_, { Encoding::Utf8 }>(path, writer)?; writer.write_all(b",\"loader\":\"")?; - // Zig: @tagName(loader) — strum is configured snake_case to match. + // strum is configured snake_case, so this prints the lowercase tag name. writer.write_all(<&'static str>::from(loader).as_bytes())?; writer.write_all(b"\",\"isEntry\":")?; writer.write_all(if kind == OutputKind::EntryPoint { @@ -136,7 +134,6 @@ pub fn write_escaped_json( chunks: &[Chunk], writer: &mut W, ) -> Result<(), bun_core::Error> { - // PERF(port): was stack-fallback (std.heap.stackFallback(4096)). let mut bytes: Vec = Vec::new(); write(index, graph, linker_graph, chunks, &mut bytes)?; bun_js_printer::write_pre_quoted_string::<_, b'"', false, true, { Encoding::Utf8 }>( @@ -146,7 +143,6 @@ pub fn write_escaped_json( } /// Newtype wrapper produced by [`HTMLImportManifest::format_escaped_json`]. -/// Mirrors Zig's `std.fmt.Alt(HTMLImportManifest, escapedJSONFormatter)`. pub struct EscapedJson<'a>(pub HTMLImportManifest<'a>); impl<'a> fmt::Display for EscapedJson<'a> { @@ -160,8 +156,6 @@ impl<'a> fmt::Display for EscapedJson<'a> { &mut adapter, ) { Ok(()) => Ok(()), - // We use std.fmt.count for this - // Zig: error.WriteFailed => unreachable, error.OutOfMemory => return error.WriteFailed Err(_) => Err(fmt::Error), } } @@ -385,5 +379,3 @@ pub mod html_import_manifest { Ok(()) } } - -// ported from: src/bundler/HTMLImportManifest.zig diff --git a/src/bundler/HTMLScanner.rs b/src/bundler/HTMLScanner.rs index 7d8b17e1e54..e13b69bc61f 100644 --- a/src/bundler/HTMLScanner.rs +++ b/src/bundler/HTMLScanner.rs @@ -16,7 +16,7 @@ bun_core::declare_scope!(HTMLScanner, hidden); pub(crate) struct HTMLScanner<'a> { // arena field dropped — global mimalloc (see PORTING.md §Allocators). - pub import_records: Vec, // Zig: ImportRecord.List + pub import_records: Vec, pub log: &'a mut Log, pub source: &'a Source, } @@ -91,8 +91,7 @@ impl<'a> HTMLScanner<'a> { } pub(crate) fn on_html_parse_error(&mut self, message: &[u8]) { - // bun.handleOom → Rust Vec/Box allocations abort on OOM; just call. - // Zig `Log.addError` dupes via `log.msgs.allocator`; here `IntoText for + // Vec/Box allocations abort on OOM; just call. `IntoText for // Vec` → `Cow::Owned`, so the Log owns and drops the copy. let _ = self .log @@ -115,15 +114,13 @@ impl<'a> HTMLScanner<'a> { } } -// Zig: const processor = HTMLProcessor(HTMLScanner, false); type Processor<'a> = HTMLProcessor, false>; // ─────────────────────────────────────────────────────────────────────────── // HTMLProcessor — generic over visitor `T` and `VISIT_DOCUMENT_TAGS` // ─────────────────────────────────────────────────────────────────────────── -/// Trait capturing the duck-typed methods Zig's `HTMLProcessor` calls on `T`. -/// Zig used `anytype`-style structural calls; Rust needs an explicit bound. +/// Trait capturing the methods `HTMLProcessor` calls on `T`. pub(crate) trait HTMLProcessorHandler { fn on_tag( &mut self, @@ -135,9 +132,9 @@ pub(crate) trait HTMLProcessorHandler { fn on_write_html(&mut self, bytes: &[u8]); fn on_html_parse_error(&mut self, message: &[u8]); - // Only required when VISIT_DOCUMENT_TAGS == true. - // TODO(port): split into a separate trait if const-generic specialization - // is unwieldy; Zig only references these inside `if (visit_document_tags)`. + // Only required when VISIT_DOCUMENT_TAGS == true; `run` only calls + // these when visiting document tags, so the defaults are never + // reached for handlers that don't visit document tags. fn on_body_tag(&mut self, _element: &mut lol::Element) -> bool { unreachable!() } @@ -266,10 +263,9 @@ const SELECTOR_CAP: usize = TAG_HANDLERS.len() + 3; // ── lol-html DirectiveCallback / OutputSink adapters ────────────────────── // `lol_html::DirectiveCallback` allows one impl per (UserData, -// Container) pair, but Zig registered 16 distinct comptime fn-values against -// the same `*T`. We instead allocate one user-data record *per selector* -// holding `(*mut T, tag_index)`; the trait body is `generateHandlerForTag`'s -// body with `tag_info` looked up at runtime via the index. +// Container) pair, so we allocate one user-data record *per selector* +// holding `(*mut T, tag_index)`; the shared trait body looks up +// `tag_info` at runtime via the index. struct TagUserData { this: *mut T, @@ -286,7 +282,6 @@ impl lol::DirectiveCallback for TagUserDa .unwrap_or(false); if has { let value = element.get_attribute(tag_info.url_attribute); - // Zig: defer value.deinit() let _value_guard = scopeguard::guard(value, |v| v.deinit()); if value.len > 0 { bun_core::scoped_log!( @@ -378,7 +373,6 @@ impl let this_ptr: *mut T = this; let builder = lol::HTMLRewriterBuilder::init(); - // Zig: defer builder.deinit() let _builder_guard = scopeguard::guard(builder, |b| { // SAFETY: `b` came from `HTMLRewriterBuilder::init()` and has not // been freed. @@ -387,7 +381,6 @@ impl let mut selectors: BoundedArray<*mut lol::HTMLSelector, SELECTOR_CAP> = BoundedArray::default(); - // Zig: defer for (selectors.slice()) |s| s.deinit() let mut selectors_guard = scopeguard::guard( &mut selectors, |selectors: &mut BoundedArray<*mut lol::HTMLSelector, SELECTOR_CAP>| { @@ -430,7 +423,6 @@ impl } if VISIT_DOCUMENT_TAGS { - // Zig: inline for (.{ "body", "head", "html" }, &.{ T.onBodyTag, ... }) for (i, tag) in [b"body" as &[u8], b"head", b"html"].into_iter().enumerate() { let head_selector = lol::HTMLSelector::parse(tag).map_err(lol_err)?; selectors.append_assume_capacity(head_selector); @@ -454,15 +446,14 @@ impl let mut sink = Sink::(this_ptr); - // PORT NOTE: Zig `errdefer { ... this.onHTMLParseError(last_error) }` - // reshaped — fallible tail wrapped in an inner block so the side effect - // runs on error without a scopeguard double-borrowing `this`. + // The fallible tail is wrapped in an inner block so the + // on-error side effect (`on_html_parse_error`) + // runs without a scopeguard double-borrowing `this`. let res: Result<(), Error> = (|| { // SAFETY: `builder` is a live FFI handle. let rewriter = unsafe { &mut *builder } .build(lol::Encoding::UTF8, memory_settings, false, &raw mut sink) .map_err(lol_err)?; - // Zig: defer rewriter.deinit() let _rewriter_guard = scopeguard::guard(rewriter, |r| { // SAFETY: `r` came from `build` and has not been freed. unsafe { lol::HTMLRewriter::destroy(r) } @@ -476,7 +467,6 @@ impl if res.is_err() { let last_error = lol::HTMLString::last_error(); - // Zig: defer last_error.deinit() let _last_error_guard = scopeguard::guard(last_error, |e| e.deinit()); if last_error.len > 0 { // The rewriter (sole user of `this_ptr`-derived aliases) was @@ -489,5 +479,3 @@ impl res } } - -// ported from: src/bundler/HTMLScanner.zig diff --git a/src/bundler/IndexStringMap.rs b/src/bundler/IndexStringMap.rs index 34bd7a3f661..74abab6b897 100644 --- a/src/bundler/IndexStringMap.rs +++ b/src/bundler/IndexStringMap.rs @@ -1,6 +1,6 @@ use bun_collections::ArrayHashMap; -/// `Index.Int` in Zig — the underlying integer repr. +/// The underlying integer repr of `Index`. pub(crate) use crate::IndexInt; #[derive(Default)] @@ -8,9 +8,6 @@ pub struct IndexStringMap { map: ArrayHashMap>, } -// PORT NOTE: `deinit` only freed owned values + the map; with `Box<[u8]>` values and -// `ArrayHashMap`'s own Drop, no explicit `impl Drop` is needed. - impl IndexStringMap { pub fn get(&self, index: IndexInt) -> Option<&[u8]> { self.map.get(&index).map(|v| v.as_ref()) @@ -27,5 +24,3 @@ impl IndexStringMap { Ok(()) } } - -// ported from: src/bundler/IndexStringMap.zig diff --git a/src/bundler/LinkerContext.rs b/src/bundler/LinkerContext.rs index dc5971ab2a5..6bb2dec44cb 100644 --- a/src/bundler/LinkerContext.rs +++ b/src/bundler/LinkerContext.rs @@ -1,5 +1,3 @@ -//! Port of src/bundler/LinkerContext.zig - use crate::mal_prelude::*; use core::sync::atomic::{AtomicU32, Ordering}; @@ -11,18 +9,17 @@ use bun_core::{MutableString, string_joiner::StringJoiner, strings}; use bun_sourcemap::{ self as SourceMap, DebugIDFormatter, LineOffsetTable, SourceMapPieces, SourceMapState, }; -// PORT NOTE: alias the *module* (not the `ThreadPool` struct) so +// Note: alias the *module* (not the `ThreadPool` struct) so // `ThreadPoolLib::Task` / `ThreadPoolLib::Batch` resolve as nested items. +use crate::bake_types as bake; use bun_ast::{ImportKind, ImportRecord}; use bun_threading::{WaitGroup, thread_pool as ThreadPoolLib}; -// TODO(port): bake_types arrives from move-in (TYPE_ONLY → bundler) -use crate::bake_types as bake; use crate::BundledAst as JSAst; use bun_ast::{ Binding, DeclaredSymbol, Dependency, ExportsKind, Expr, NamedImport, Part, Ref, Stmt, TlaCheck, }; -// PORT NOTE: `crate::Index` (= `bun_ast::Index`) — the +// Note: `crate::Index` (= `bun_ast::Index`) — the // bundler's source-index newtype. `bun_ast::Index` is layout-identical // but a distinct type; LinkerGraph/JSMeta/etc. are typed against the crate // re-export, so use that here. @@ -42,7 +39,7 @@ use crate::{ LinkerGraph, MangledProps, PartRange, StableRef, WrapKind, }; -/// `bun.jsc.AnyEventLoop` (LinkerContext.zig:28). `bun_event_loop` is a +/// `bun_event_loop` is a /// lower-tier crate, so the bundler can name the real enum (the `Js` arm /// holds an erased `*mut jsc::EventLoop` driven through a vtable). Stored as /// a pointer because the linker borrows the loop owned by the @@ -56,7 +53,7 @@ bun_core::declare_scope!(TreeShake, hidden); // CYCLEBREAK(b0): vtable instance for `bun_crash_handler::BundleGenerateChunkVTable` // (cold-path §Dispatch — crash trace only). crash_handler (T1) holds erased // `(*const LinkerContext, *const Chunk, *const PartRange)`; bundler supplies -// the formatter that knows their layout. Mirrors src/crash_handler/crash_handler.zig:135. +// the formatter that knows their layout. // ══════════════════════════════════════════════════════════════════════════ #[cfg(feature = "show_crash_trace")] bun_crash_handler::link_impl_BundleGenerateChunkCtx! { @@ -90,7 +87,7 @@ bun_crash_handler::link_impl_BundleGenerateChunkCtx! { } } -/// Helper for call-sites that previously wrote `Action::BundleGenerateChunk(.{...})`. +/// Helper for constructing a crash-trace `Action::BundleGenerateChunk`. #[cfg(feature = "show_crash_trace")] #[inline] pub(crate) fn bundle_generate_chunk_action( @@ -111,7 +108,7 @@ pub(crate) fn bundle_generate_chunk_action( }) } -// Scoped-log wrappers (LinkerContext.zig:2, :2705); re-exported so `linker_context/*` submodules import directly. +// Scoped-log wrappers; re-exported so `linker_context/*` submodules import directly. bun_core::define_scoped_log!(debug, crate::linker_context_mod::LinkerCtx); pub(crate) use debug; bun_core::define_scoped_log!(debug_tree_shake, crate::linker_context_mod::TreeShake); @@ -215,8 +212,7 @@ pub struct LinkerContext<'a> { // `SourceMapDataTask`. The raw-pointer fields (`parse_graph`, `resolver`, // `r#loop`, `framework`) are backrefs into `BundleV2`/`Transpiler` whose // lifetimes strictly outlive every parallel section, and per-thread writes go -// to disjoint SoA slots (see `compute_line_offsets`). This mirrors Zig's -// freely-aliased `*LinkerContext`. +// to disjoint SoA slots (see `compute_line_offsets`). unsafe impl<'a> Send for LinkerContext<'a> {} // SAFETY: see the `Send` impl above — same backref-lifetime / disjoint-write invariants. unsafe impl<'a> Sync for LinkerContext<'a> {} @@ -249,7 +245,7 @@ impl<'a> Default for LinkerContext<'a> { impl<'a> LinkerContext<'a> { /// container_of: `*LinkerContext` → `*BundleV2` via the embedded `.linker` - /// field. Mirrors Zig `@fieldParentPtr("linker", c)`. Returns raw; caller + /// field. Returns raw; caller /// decides `&*` vs `&mut *` per local aliasing rules (several callers run /// on worker-pool threads and MUST NOT materialize `&mut BundleV2`). /// @@ -398,7 +394,6 @@ impl<'a> LinkerContext<'a> { } pub fn mark_pending_task_done(&self) { - // Zig: `.monotonic` → Rust `Relaxed` (LLVM `monotonic` == C11 `relaxed`). self.pending_task_count.fetch_sub(1, Ordering::Relaxed); } @@ -411,10 +406,8 @@ impl<'a> LinkerContext<'a> { && record.source_index.get() != source_index } - /// Spec: `LinkerContext.zig:checkForMemoryCorruption`. - /// - /// PORT NOTE: the Zig body calls `parse_graph.heap.helpCatchMemoryIssues()` - /// (a `MimallocArena` debug hook). `Graph.heap` is currently + /// Note: this should call a `MimallocArena` debug hook + /// (`helpCatchMemoryIssues`), but `Graph.heap` is currently /// `bun_alloc::Arena = bumpalo::Bump`, which has no such hook, so this is a /// no-op until the arena type is swapped to the real `MimallocArena`. The /// call sites are already gated on `FeatureFlags::HELP_CATCH_MEMORY_ISSUES`. @@ -422,14 +415,14 @@ impl<'a> LinkerContext<'a> { pub fn check_for_memory_corruption(&self) { // For this to work, you need mimalloc's debug build enabled. // make mimalloc-debug - // TODO(port): `unsafe { (*self.parse_graph).heap.help_catch_memory_issues() }` - // once `Graph.heap: MimallocArena`. + // Becomes `unsafe { (*self.parse_graph).heap.help_catch_memory_issues() }` + // if `Graph.heap` ever grows the `MimallocArena` debug hook (see the + // doc comment above). } } // Local re-exports for the tree-shaking impl below. `EntryPoint::Kind` -// and `SideEffects` live in sibling modules; code here used to reference them -// via Zig-style nested paths. Re-export so `EntryPoint::Kind` here is the +// and `SideEffects` live in sibling modules. Re-export so `EntryPoint::Kind` here is the // *same type* `items_entry_point_kind()` returns. #[allow(non_snake_case)] pub mod EntryPoint { @@ -441,7 +434,7 @@ type DeclaredSymbolList = bun_ast::DeclaredSymbolList; impl<'a> LinkerContext<'a> { pub fn arena(&self) -> &Bump { - // TODO(port): bundler is an AST crate; LinkerGraph owns the arena + // LinkerGraph owns (a backref to) the bundle arena; see `LinkerGraph::arena`. self.graph.arena() } @@ -482,7 +475,7 @@ impl<'a> LinkerContext<'a> { } /// `bundle` is taken as a raw `*mut` because the caller invokes this as - /// `self.linker.load(self, …)` (Zig spec bundle_v2.zig:2574) — `self` *is* + /// `self.linker.load(self, …)` — `self` *is* /// `(*bundle).linker`, so a `&mut BundleV2` here would alias the receiver /// under Stacked Borrows. This body only reaches into fields of `*bundle` /// that are disjoint from `linker` (`graph`, `transpiler`, @@ -511,11 +504,11 @@ impl<'a> LinkerContext<'a> { // the bundle's lifetime; `resolver`/`log`/`options` are stable fields. let transpiler = unsafe { &mut *(*bundle).transpiler }; self.graph.code_splitting = transpiler.options.code_splitting; - // Mirrors Zig's pointer assignment; `transpiler.log` is the canonical + // `transpiler.log` is the canonical // `*mut Log` (same value aliased into `linker.log` / `resolver.log`). self.log = transpiler.log; - // PORT NOTE: lifetime — `self.resolver` is `ParentRef>` + // Note: lifetime — `self.resolver` is `ParentRef>` // but `transpiler.resolver` is `Resolver<'_>` (anonymous `bundle` // lifetime); erase via a pointer cast (LIFETIMES.tsv: GRAPHBACKED — // resolver outlives the link step). Read-only — `from_raw` provenance @@ -527,9 +520,8 @@ impl<'a> LinkerContext<'a> { }); self.cycle_detector = Vec::new(); - // PORT NOTE: `reachable_files` is `Vec`; clone the - // caller-owned slice into the linker arena. PERF(port): Zig pointed at - // the slice in-place; revisit once Vec grows a borrowed-view ctor. + // Note: `reachable_files` is `Vec`; clone the + // caller-owned slice into the linker arena. self.graph.reachable_files = reachable.to_vec(); // SAFETY: parse_graph is valid backref just assigned above @@ -543,8 +535,6 @@ impl<'a> LinkerContext<'a> { // SAFETY: parse_graph backref unsafe { &(*self.parse_graph).entry_point_original_names }, )?; - // PERF(port): was arena bulk-free — `dynamic_import_entry_points` is - // now a global-alloc `ArrayHashMap`; clearing drops it. dyn_entry_points.clear_retaining_capacity(); let runtime_named_exports = @@ -572,7 +562,7 @@ impl<'a> LinkerContext<'a> { } if self.options.output_format == Format::Cjs || self.options.output_format == Format::Iife { - // PORT NOTE: reshaped for borrowck — `Slice` is a value-type + // Note: reshaped for borrowck — `Slice` is a value-type // snapshot of column pointers (does not borrow `self.graph.ast`), // so `split_mut()` on the local can coexist with the // `self.graph.meta` borrow below. The slab does not reallocate for @@ -612,8 +602,7 @@ impl<'a> LinkerContext<'a> { debug_assert!(self.options.source_maps != SourceMapOption::None); self.source_maps.line_offset_wait_group = WaitGroup::init_with_count(reachable.len()); self.source_maps.quoted_contents_wait_group = WaitGroup::init_with_count(reachable.len()); - // TODO(port): arena alloc of task arrays - // PORT NOTE: `SourceMapDataTask` is not `Clone` (embeds an intrusive + // Note: `SourceMapDataTask` is not `Clone` (embeds an intrusive // `ThreadPoolLib::Task` node); build via iterator instead of `vec![x;n]`. self.source_maps.line_offset_tasks = (0..reachable.len()) .map(|_| SourceMapDataTask::default()) @@ -624,7 +613,7 @@ impl<'a> LinkerContext<'a> { .collect::>() .into_boxed_slice(); - // PORT NOTE: erase `'a` → `'static` for the task backref. The tasks are + // Note: erase `'a` → `'static` for the task backref. The tasks are // joined before `self` is dropped (see `SourceMapData.*_wait_group`). // SAFETY: write provenance from `ptr::from_mut`; outlives every task. let ctx: Option>> = Some(unsafe { @@ -681,9 +670,9 @@ impl<'a> LinkerContext<'a> { // field of `BundleV2.linker` (= `*self`). The two are disjoint, and no // other `&`/`&mut` to `BundleV2.graph` is live for this scope — // `self.graph` below is `LinkerGraph`, a distinct allocation. - // PORT NOTE: reshaped for borrowck — Zig held overlapping `&`/`&mut` - // into `parse_graph.html_imports` and `parse_graph.input_files`; here - // we go through raw pointers and reborrow per use. + // Note: go through raw pointers and reborrow per use to avoid holding + // overlapping `&`/`&mut` into `parse_graph.html_imports` and + // `parse_graph.input_files`. let parse_graph: *mut Graph<'a> = self.parse_graph; // SAFETY: see above; sole accessor of `html_imports` for this scope. let server_len = unsafe { (*parse_graph).html_imports.server_source_indices.len() }; @@ -719,8 +708,7 @@ impl<'a> LinkerContext<'a> { }; // S.LazyExport is a call to __jsonParse. Each accessor returns - // `Option`; `.unwrap()` mirrors Zig's untagged-union field - // reads (panic on shape mismatch). + // `Option`; `.unwrap()` panics on shape mismatch. let original_ref = (*self.graph.ast.items_parts()[html_import as usize][1].stmts) [0] .data @@ -915,8 +903,8 @@ impl<'a> LinkerContext<'a> { self.graph.parts_live = parts_live; } - // PORT NOTE: reshaped for borrowck — these slices alias into self.graph; - // Zig held them simultaneously. The SoA columns are physically disjoint + // Note: these slices alias into self.graph. + // The SoA columns are physically disjoint // and the underlying slabs don't reallocate during tree-shaking, so we // cache raw column base pointers and reborrow at each recursive call. let parts: *mut [bun_ast::PartList<'a>] = self.graph.ast.items_parts_mut(); @@ -1031,7 +1019,7 @@ impl<'a> LinkerContext<'a> { let worker = crate::thread_pool::Worker::get(ctx.bundle()); let mut worker = scopeguard::guard(worker, |w| w.unget()); let worker: &mut crate::thread_pool::Worker = &mut **worker; - // PORT NOTE: dispatch on a discriminant copy so `chunk` isn't borrowed + // Note: dispatch on a discriminant copy so `chunk` isn't borrowed // across the post-process call (which takes `&mut Chunk`). let result = match chunk.content { crate::chunk::Content::Javascript(_) => { @@ -1073,7 +1061,7 @@ impl<'a> LinkerContext<'a> { chunk_index: usize, ) { let _ = chunk_index; - // PORT NOTE: reshaped for borrowck — `rename_symbols_in_chunk` needs + // Note: reshaped for borrowck — `rename_symbols_in_chunk` needs // `&mut Chunk` and a borrow of `chunk.content.javascript.files_in_chunk_order` // simultaneously; cache the files slice via raw pointer (it lives in // the chunk arena, address-stable for the renamer pass). @@ -1100,9 +1088,6 @@ impl<'a> LinkerContext<'a> { ) -> Result { let _trace = bun::perf::trace("Bundler.generateSourceMapForChunk"); - // PERF(port): Zig threaded `worker.arena` through StringJoiner / - // MutableString; the Rust ports use the global mimalloc, so the joiner - // is arena-free here. Revisit when arena threading lands. let mut j = StringJoiner::default(); let sources = self.parse_graph().input_files.items_source(); @@ -1118,7 +1103,6 @@ impl<'a> LinkerContext<'a> { // Which source index in the generated sourcemap, referred to // as the "mapping source index" within this function to be distinct. let mut source_id_map: ArrayHashMap = ArrayHashMap::new(); - // PERF(port): was arena bulk-free — source_id_map drops at scope exit let source_indices = results.items_source_index(); @@ -1129,11 +1113,8 @@ impl<'a> LinkerContext<'a> { let path = &sources[index as usize].path; source_id_map.put_no_clobber(index, 0)?; - // PORT NOTE: Zig mutated a local copy's `path.pretty` from the - // worker arena; we keep the relative path in a local owned - // buffer instead (drops at scope exit — same lifetime as the - // arena slice). - // + // Note: the relative path lives in a local owned buffer + // (drops at scope exit). let rel_path_storage; let pretty: &[u8] = if path.is_file() { rel_path_storage = @@ -1145,8 +1126,8 @@ impl<'a> LinkerContext<'a> { let mut quote_buf = MutableString::init(pretty.len() + 2)?; js_printer::quote_for_json(pretty, &mut quote_buf, false)?; - // PERF(port): was arena-backed; `to_default_owned` moves the - // buffer into the joiner (joiner owns it until `done`). + // `to_default_owned` moves the buffer into the joiner + // (joiner owns it until `done`). j.push_owned(quote_buf.to_default_owned()); } @@ -1172,7 +1153,7 @@ impl<'a> LinkerContext<'a> { }; let mut quote_buf = MutableString::init(pretty.len() + ", ".len() + 2)?; - quote_buf.append_assume_capacity(b", "); // PERF(port): was assume_capacity + quote_buf.append_assume_capacity(b", "); js_printer::quote_for_json(pretty, &mut quote_buf, false)?; j.push_owned(quote_buf.to_default_owned()); } @@ -1247,7 +1228,6 @@ impl<'a> LinkerContext<'a> { if FeatureFlags::SOURCE_MAP_DEBUG_ID { j.push_static(b"\",\n \"debugId\": \""); - // TODO(port): allocPrint into arena — using Vec + write! let mut buf = Vec::::new(); use std::io::Write; write!(&mut buf, "{}", DebugIDFormatter { id: isolated_hash }) @@ -1298,9 +1278,17 @@ pub enum LinkError { bun_core::oom_from_alloc!(LinkError); impl From for LinkError { - fn from(_: BunError) -> Self { - // TODO(port): narrow error set — Zig's `try this.load()` is `!void` (anyerror) - LinkError::BuildFailed + fn from(e: BunError) -> Self { + // OOM keeps its identity through + // `load()`, so OOMs travelling as `bun_core::Error` must not be + // misreported as build failures. Everything else collapses to + // `BuildFailed`; user-facing diagnostics flow through the bundler `Log`, + // not this variant. + if e == BunError::OUT_OF_MEMORY { + LinkError::OutOfMemory + } else { + LinkError::BuildFailed + } } } bun_core::named_error_set!(LinkError); @@ -1394,7 +1382,6 @@ impl Default for SourceMapDataTask { Self { ctx: None, source_index: 0, - // Spec `LinkerContext.zig:101`: default task callback is `&runLineOffset`. thread_task: ThreadPoolLib::Task { node: ThreadPoolLib::Node::default(), callback: Self::run_line_offset, @@ -1474,11 +1461,8 @@ impl SourceMapDataTask { // stack traces can show the source code, even after incremental // rebuilds occur. // - // PORT NOTE: Zig branched on `worker.ctx.transpiler.options.dev_server` - // to pick `dev.arena()` vs `worker.arena`, but - // `computeQuotedSourceContents` discards the arena parameter - // (`_: std.mem.Allocator`) — it always allocates via - // `bun.default_allocator` internally. The branch is a no-op, so we + // Note: `compute_quoted_source_contents` ignores which arena it is + // handed (it allocates via the default allocator internally), so we // pass the worker arena unconditionally; `DevServerHandle` does not // expose an arena accessor (§Dispatch). SourceMapData::compute_quoted_source_contents(ctx, worker.arena(), task.source_index); @@ -1486,14 +1470,11 @@ impl SourceMapDataTask { } } -// TODO(port): see SourceMapDataTask above. - impl SourceMapData { /// Runs concurrently across the worker pool (one task per `source_index`). /// Takes [`ParentRef`](bun_ptr::ParentRef) (not `&mut`) - /// because Zig's `*LinkerContext` freely aliases across threads — - /// materializing `&mut LinkerContext` here while peer tasks hold the same - /// pointer would be aliased-mut UB. `ParentRef::Deref` yields + /// because peer tasks on other threads hold the same pointer — + /// materializing `&mut LinkerContext` here would be aliased-mut UB. `ParentRef::Deref` yields /// `&LinkerContext` (SharedReadOnly) for all SoA-header reads; each task /// writes only `graph.files[source_index].line_offset_table` (disjoint by /// `source_index`) via a raw column pointer. @@ -1546,7 +1527,7 @@ impl SourceMapData { *line_offset_table = LineOffsetTable::generate_in::( &source.contents, // We don't support sourcemaps for source files with more than 2^31 lines - (approximate_line_count as u32 & 0x7FFF_FFFF) as i32, // @intCast(@truncate to u31) + (approximate_line_count as u32 & 0x7FFF_FFFF) as i32, ) .expect("OOM"); } @@ -1605,7 +1586,7 @@ impl SourceMapData { // other fields are POD. #[derive(Clone, Default)] pub struct MatchImport { - alias: bun_ast::StoreStr, // Zig string borrowed from AST arena + alias: bun_ast::StoreStr, // string borrowed from AST arena kind: MatchImportKind, namespace_ref: Ref, source_index: u32, @@ -1642,10 +1623,10 @@ pub struct ChunkMeta { pub(crate) type ChunkMetaMap = ArrayHashMap; -/// PORT NOTE: raw-pointer fields (was `&'a mut`) because `each_ptr` requires +/// Note: raw-pointer fields (was `&'a mut`) because `each_ptr` requires /// `Ctx: Sync + Copy` and the same context is observed from every worker /// thread. Each task only writes to its own `*mut Chunk` slot; reads of -/// `c`/`chunks` are disjoint or read-only per the Zig spec. +/// `c`/`chunks` are disjoint or read-only. #[derive(Clone, Copy)] pub struct GenerateChunkCtx<'a> { pub c: bun_ptr::ParentRef>, @@ -1662,7 +1643,8 @@ pub struct GenerateChunkCtx<'a> { /// [`bun_ptr::BackRef::as_ptr`], shared reads go through safe `Deref`. pub chunk: bun_ptr::BackRef, } -// SAFETY: see PORT NOTE above — mirrors Zig's freely-aliased `*LinkerContext`. +// SAFETY: see note above — each task writes only its own `*mut Chunk` slot; +// shared reads are read-only. unsafe impl<'a> Send for GenerateChunkCtx<'a> {} // SAFETY: see the `Send` impl above — same backref-lifetime / disjoint-write invariants. unsafe impl<'a> Sync for GenerateChunkCtx<'a> {} @@ -1709,13 +1691,12 @@ pub struct PendingPartRange<'a> { /// callbacks: recover the intrusive [`PendingPartRange`] from `task`, extract /// the raw `*mut LinkerContext` / `*mut Chunk` from its [`GenerateChunkCtx`], /// and acquire the per-thread [`Worker`](crate::thread_pool::Worker) (returned -/// as a scopeguard that calls `unget()` on drop — Zig: `defer worker.unget()`). +/// as a scopeguard that calls `unget()` on drop). /// /// `GenerateChunkCtx.{c, chunk}` are raw `*mut T` (Copy), so reading them /// through `&GenerateChunkCtx` preserves the mutable provenance they were -/// constructed with in `generate_chunks_in_parallel`. This mirrors Zig's -/// `*LinkerContext` / `*Chunk` semantics where many `PendingPartRange` tasks -/// share one `chunk_ctx` across worker threads. +/// constructed with in `generate_chunks_in_parallel` — many `PendingPartRange` +/// tasks share one `chunk_ctx` across worker threads. /// /// # Safety /// `task` must point to the `task` field of a live `PendingPartRange` scheduled @@ -1796,7 +1777,7 @@ impl<'a> LinkerContext<'a> { .path_with_pretty_initialized(&source.path, arena) .expect("OOM"); } - // PORT NOTE: `Path::assert_pretty_is_valid` lives on the + // Note: `Path::assert_pretty_is_valid` lives on the // resolver-side `Path<'a>`; the logger `Path` has no // such debug hook yet. debug_assert!(source.path.text.as_ptr() != source.path.pretty.as_ptr()); @@ -1902,8 +1883,7 @@ impl<'a> LinkerContext<'a> { meta_flags: &mut [crate::js_meta::Flags], ast_import_records: &[bun_ast::import_record::List<'a>], ) -> Result { - // PORT NOTE: reshaped for borrowck — Zig held &mut tla_checks[source_index] across recursive - // calls that also mutate tla_checks. We re-index after each recursion. + // Note: re-index `tla_checks` after each recursion — recursive calls also mutate it. if tla_checks[source_index as usize].depth == 0 { tla_checks[source_index as usize].depth = 1; if tla_keywords[source_index as usize].len > 0 { @@ -2207,15 +2187,7 @@ impl<'a> LinkerContext<'a> { // across `RequireOrImportMetaCallback::init(self)` (`&mut self`) below. let parse_graph = unsafe { &*self.parse_graph }; - // PORT NOTE: `Options.arena` / `source_map_allocator` were removed in - // the Rust port (printer uses global mimalloc + the explicit `bump` - // argument to `print_with_writer`). The dev-server source-map-arena - // selection is folded into TODO(port) until arena threading lands. - let _ = self.dev_server.is_some() - && parse_graph.input_files.items_loader()[source_index.get() as usize] - .is_javascript_like(); - - // PORT NOTE: reshaped for borrowck — `Options` borrows `ts_enums` / + // Note: reshaped for borrowck — `Options` borrows `ts_enums` / // `line_offset_tables` / `mangled_props` from `self.graph`, but the // `require_or_import_meta_for_source_callback` field below needs // `&mut self`. Detach the read-only borrows via raw-pointer round-trip @@ -2288,29 +2260,25 @@ impl<'a> LinkerContext<'a> { }; writer.buffer.reset(); - // PORT NOTE: Zig moved `*writer` into the printer by value and wrote it - // back via `defer writer.* = printer.ctx;`. `BufferWriter` isn't - // `Clone`/`Default` in Rust; move it through `mem::replace` with a - // freshly-initialized writer instead. + // Note: `BufferWriter` isn't `Clone`/`Default`; move it through + // `mem::replace` with a freshly-initialized writer. let mut printer = js_printer::BufferPrinter::init(core::mem::replace( writer, js_printer::BufferWriter::init(), )); - // PORT NOTE: Zig's `ast.toAST()` bitwise-copies every field of - // `*const BundledAst` into a stack `Ast` that is never deinit'd. The - // Rust collections aren't `Copy`, so mirror the Zig shallow copy via - // `ptr::read` + `ManuallyDrop` (the resulting `Ast` aliases `ast`'s - // storage; dropping it would double-free). + // Note: shallow bitwise copy via `ptr::read` + `ManuallyDrop` — the + // resulting `Ast` aliases `ast`'s storage; dropping it would + // double-free. // SAFETY: `ast` is a valid `&BundledAst` for the duration of this call; // the read is a bitwise copy whose result is never dropped. let printer_ast = core::mem::ManuallyDrop::new(unsafe { core::ptr::read(ast) }.to_ast()); - // PORT NOTE: `print_with_writer<'a>` requires `Renamer<'a,'a>` (the + // Note: `print_with_writer<'a>` requires `Renamer<'a,'a>` (the // printer struct stores it with a single lifetime), but `Renamer`'s // `'src` is invariant behind `&mut`, so the caller's `Renamer<'r,'src>` // cannot unify with the local `'a` picked from `alloc`/`mangled_props`. - // Zig threads it as a raw pointer (no lifetimes). Rebind via a + // Rebind via a // lifetime-only cast — sound because the renamer's borrowed data // (symbol map, source) strictly outlives this call. // SAFETY: lifetime-only erase; layout identical across instantiations. @@ -2320,7 +2288,6 @@ impl<'a> LinkerContext<'a> { let enable_source_maps = self.options.source_maps != SourceMapOption::None && !source_index.is_runtime(); - // PERF(port): was comptime bool dispatch — profile if it shows up on a hot path. let result = if enable_source_maps { js_printer::print_with_writer::<&mut js_printer::BufferPrinter, true>( &mut printer, @@ -2389,7 +2356,6 @@ impl<'a> LinkerContext<'a> { let all_sources: &[Source] = unsafe { (*self.parse_graph).input_files.items_source() }; // Collect all local css names - // PERF(port): was stack-fallback alloc let mut local_css_names: HashMap = HashMap::new(); for (source_index, maybe_css_ast) in all_css_asts.iter().enumerate() { @@ -2402,8 +2368,6 @@ impl<'a> LinkerContext<'a> { let mut symbol = symbol_; if symbol.kind == bun_ast::symbol::Kind::LocalCss { let r#ref = 'follow: { - // PORT NOTE: Zig set `.tag = .symbol` after `init`; - // `Ref` is packed in Rust — construct via `new`. let mut r#ref = Ref::new( u32::try_from(inner_index).expect("int cast"), u32::try_from(source_index).expect("int cast"), @@ -2427,8 +2391,7 @@ impl<'a> LinkerContext<'a> { // SAFETY: `Symbol.original_name` is a `*const [u8]` arena // pointer; valid for the link step. let original_name: &[u8] = symbol.original_name.slice(); - // PERF(port): was stack-fallback alloc. The hash itself - // is short-lived; use a scratch bump. + // The hash itself is short-lived; use a scratch bump. let scratch = ::bun_alloc::Arena::new(); let path_hash = ::bun_base64::wyhash_url_safe( &scratch, @@ -2446,7 +2409,7 @@ impl<'a> LinkerContext<'a> { bstr::BStr::new(path_hash) ) .expect("infallible: in-memory write"); - // TODO(port): arena() is arena; mangled_props key/value lifetime + // The map owns its boxed values (freed with `mangled_props`). self.mangled_props .put(r#ref, final_generated_name.into_boxed_slice()) .expect("OOM"); @@ -2473,7 +2436,7 @@ impl<'a> LinkerContext<'a> { chunk_visit_map.set(index as usize); // Visit the other chunks that this chunk imports before visiting this chunk - // PORT NOTE: reshaped for borrowck — collect imports first to avoid aliasing &chunks[index] with recursive &mut chunks + // Note: reshaped for borrowck — collect imports first to avoid aliasing &chunks[index] with recursive &mut chunks let cross_chunk_imports: Vec = chunks[index as usize] .cross_chunk_imports .slice() @@ -2493,7 +2456,7 @@ impl<'a> LinkerContext<'a> { // express cross-chunk dependencies via `cross_chunk_imports` above, but // HTML (and CSS) chunks only reference other chunks through pieces, so // recurse on those too. - // PORT NOTE: reshaped for borrowck — collect piece queries first so the + // Note: reshaped for borrowck — collect piece queries first so the // `&chunks[index]` borrow is dropped before the recursive `&mut chunks` // calls in the Chunk/Scb arms below. `final_rel_path` is re-indexed per // Asset arm (not hoisted) because it is now `Box<[u8]>` (not `Copy`). @@ -2563,7 +2526,6 @@ impl<'a> LinkerContext<'a> { // Mix in the hash for this chunk let chunk = &chunks[index as usize]; - // PORT NOTE: Zig `std.mem.asBytes(&u64)` → native-endian byte view. hash.write(&chunk.isolated_hash.to_ne_bytes()); } @@ -2575,7 +2537,6 @@ impl<'a> LinkerContext<'a> { ) { list.clear(); list.reserve(export_refs.count()); - // PORT NOTE: Zig set .items.len = count() then indexed; Rust pushes for &export_ref in export_refs.keys() { #[cfg(debug_assertions)] { @@ -2646,8 +2607,6 @@ pub struct TreeShakeCtx<'a, 'r> { pub struct CodeSplitCtx<'a, 'r> { pub distances: &'r mut [u32], - // Spec (LinkerContext.zig:1579) passes `parts: []Vec(Part)` and only - // reads it. pub parts: &'r [bun_ast::PartList<'a>], pub import_records: &'r [bun_ast::import_record::List<'a>], pub file_entry_bits: &'r mut [AutoBitSet], @@ -2771,8 +2730,7 @@ impl<'a> LinkerContext<'a> { .path .pretty ), - // PORT NOTE: Zig printed `target.bakeGraph()` (a `bake.Graph` tag); - // `bake_graph()` lives in `bun_bake` (tier-6 — would back-edge). + // Note: `bake_graph()` lives in `bun_bake` (tier-6 — would back-edge). // The debug log only needs a stable label, so print the `Target` // tag directly via its `IntoStaticStr` derive. <&'static str>::from(parse_graph.ast.items_target()[source_index as usize]), @@ -2824,7 +2782,7 @@ impl<'a> LinkerContext<'a> { let part_count = ctx.parts[source_index as usize].len(); for part_index in 0..part_count { - // PORT NOTE: reshaped for borrowck — re-borrow part each iteration since recursion mutates `parts` + // Note: reshaped for borrowck — re-borrow part each iteration since recursion mutates `parts` let part = &ctx.parts[source_index as usize].as_slice()[part_index]; let mut can_be_removed_if_unused = part.can_be_removed_if_unused; @@ -2837,8 +2795,7 @@ impl<'a> LinkerContext<'a> { // Also include any statement-level imports. Iterate by index so we // don't hold a borrow of `part`/`parts` across the recursive call — // the recursion never resizes this part's `import_record_indices`, - // so re-slicing each iteration is sound and matches Zig's plain - // `for (part.import_record_indices.slice())`. + // so re-slicing each iteration is sound. let import_indices_len = part.import_record_indices.len(); for ii in 0..import_indices_len { let import_index = ctx.parts[source_index as usize].as_slice()[part_index] @@ -2932,8 +2889,6 @@ impl<'a> LinkerContext<'a> { } else { Loc::EMPTY.start }, - // Zig used `@tagName(stmts[0].data)`. `StmtData::tag()` → `StmtTag` which - // derives `strum::IntoStaticStr`. if !stmts.is_empty() { <&'static str>::from(stmts[0].data.tag()) } else { @@ -2992,9 +2947,7 @@ impl<'a> LinkerContext<'a> { // `scanImportsAndExports.rs` callees. // // `linker_context/scanImportsAndExports.rs` calls these `LinkerContext` -// methods inherently. Real ports of the `LinkerContext.zig` / -// `linker_context/doStep5.zig` / `linker_context/generateCodeForLazyExport.zig` -// bodies. +// methods inherently. // ══════════════════════════════════════════════════════════════════════════ // Local imports. `AstFlags` / `DeclaredSymbolList` @@ -3002,13 +2955,13 @@ impl<'a> LinkerContext<'a> { use bun_ast::symbol::Use as SymbolUse; use bun_ast::{DependencyList, ImportItemStatus, PartSymbolUseMap}; -// `bundle_v2.zig:ImportTracker.{Status,Iterator}` — canonical definition lives -// in `bundle_v2.rs` (matches Zig spec location). Re-exported here so the 30+ +// `ImportTracker::{Status,Iterator}`'s canonical definition lives +// in `bundle_v2.rs`. Re-exported here so the 30+ // unqualified uses in `advance_import_tracker` / `match_import_with_export` // below resolve unchanged. pub use crate::bundle_v2::{ImportTrackerIterator, ImportTrackerStatus}; -/// Field-wise eq for `ImportTracker`, matching Zig's `eql(ImportTracker)` shape. +/// Field-wise eq for `ImportTracker`. #[inline] fn import_tracker_eq(a: &ImportTracker, b: &ImportTracker) -> bool { a.source_index.get() == b.source_index.get() @@ -3017,32 +2970,32 @@ fn import_tracker_eq(a: &ImportTracker, b: &ImportTracker) -> bool { } impl<'a> LinkerContext<'a> { - /// Spec: `LinkerContext.zig:1298 runtimeFunction`. + /// Looks up the symbol `Ref` for a named export of the runtime module. #[inline] pub fn runtime_function(&self, name: &[u8]) -> Ref { self.graph.runtime_function(name) } - /// Spec: `LinkerContext.zig:2150 topLevelSymbolsToParts`. + /// Returns the part indices within file `id` that declare the + /// top-level symbol `ref`. #[inline] pub fn top_level_symbols_to_parts(&self, id: u32, r#ref: Ref) -> &[u32] { self.graph.top_level_symbol_to_parts(id, r#ref) } - /// Spec: `LinkerContext.zig:2154 topLevelSymbolsToPartsForRuntime`. + /// Returns the part indices in the runtime module that declare the + /// top-level symbol `ref`. #[inline] pub fn top_level_symbols_to_parts_for_runtime(&self, r#ref: Ref) -> &[u32] { self.top_level_symbols_to_parts(Index::RUNTIME.get(), r#ref) } - /// Spec: `LinkerContext.zig:489 source_`. - /// - /// PORT NOTE: returns `'static` so callers can hold the source across a + /// Note: returns `'static` so callers can hold the source across a /// `&mut self.log` borrow; the underlying `parse_graph.input_files` slab /// is append-only and outlives the link step (LIFETIMES.tsv: GRAPHBACKED). #[inline] pub fn get_source>(&self, index: I) -> &'static Source { - // PORT NOTE: Zig spec is `index: anytype`; callers pass both `u32` and + // Note: callers pass both `u32` and // `usize`. Route through `TryInto` so the SoA index works for // either width without forcing `as`-casts at every call site. let index: usize = match index.try_into() { @@ -3056,8 +3009,6 @@ impl<'a> LinkerContext<'a> { unsafe { &*core::ptr::from_ref(&(*self.parse_graph).input_files.items_source()[index]) } } - /// Spec: `LinkerContext.zig:496 scanCSSImports`. - /// /// `log` is an explicit parameter (not `self.log`) because the dev-server /// caller (`finish_from_bake_dev_server`) runs this *before* `load()` has /// initialized `self.log`, passing a stack-local `Log` instead. @@ -3121,12 +3072,13 @@ impl<'a> LinkerContext<'a> { } } - /// Spec: `LinkerContext.zig:2158 createWrapperForFile`. + /// Creates the synthetic wrapper part (CommonJS or ESM) for a wrapped + /// file and records its part index in `wrapper_part_index`. pub fn create_wrapper_for_file( &mut self, wrap: WrapKind, wrapper_ref: Ref, - // PORT NOTE: `crate::Index` (`bun_ast::Index`), + // Note: `crate::Index` (`bun_ast::Index`), // not `bun_ast::Index` — the SoA `wrapper_part_index` column is // typed via the crate-root re-export. wrapper_part_index: &mut crate::Index, @@ -3151,10 +3103,8 @@ impl<'a> LinkerContext<'a> { let common_js_parts = self.top_level_symbols_to_parts_for_runtime(self.cjs_runtime_ref); - // PORT NOTE: reshaped for borrowck — Zig held `runtime_parts` - // simultaneously with the mutable graph borrows below; the inner - // loop is empty (`if r#ref.eql(...) continue;` only) so it's a - // no-op kept for parity with the original. + // Note: the inner loop is intentionally a no-op + // (`if r#ref.eql(...) continue;` only). for &part_id in common_js_parts { let runtime_parts = self.graph.ast.items_parts()[Index::RUNTIME.get() as usize].as_slice(); @@ -3350,12 +3300,11 @@ impl<'a> LinkerContext<'a> { } } - /// Spec: `LinkerContext.zig:1710 advanceImportTracker`. + /// Follows one step of an import chain: resolves what `tracker`'s import + /// points to in the target file and reports the match status. pub fn advance_import_tracker(&mut self, tracker: &ImportTracker) -> ImportTrackerIterator { let id = tracker.source_index.get(); - // PORT NOTE: reshaped for borrowck — Zig held `&mut named_imports[id]` - // and `&import_records[id]` simultaneously; here we read `named_import` - // out first, then borrow the rest. + // Note: read `named_import` out first, then borrow the rest. let named_import: &NamedImport = match self.graph.ast.items_named_imports()[id as usize].get(&tracker.import_ref) { Some(ni) => ni, @@ -3529,15 +3478,17 @@ impl<'a> LinkerContext<'a> { } } - /// Spec: `LinkerContext.zig:1443 matchImportWithExport`. + /// Walks an import chain (through re-exports) to its final target and + /// returns how the import should be bound, collecting any re-export + /// dependencies along the way. pub fn match_import_with_export( &mut self, init_tracker: ImportTracker, re_exports: &mut bun_alloc::AstVec, ) -> MatchImport { let cycle_detector_top = self.cycle_detector.len(); - // PORT NOTE: Zig's `defer cycle_detector.shrinkRetainingCapacity` is - // lowered to an explicit `truncate` after the `'loop_` below — the only + // Note: `cycle_detector` is restored by an explicit + // `truncate` after the `'loop_` below — the only // exits are the three `return`s that follow it, so a single post-loop // truncate covers every path. A scopeguard holding a raw `*mut` into // `self.cycle_detector` would be invalidated by the `&mut self` @@ -3842,7 +3793,6 @@ impl<'a> LinkerContext<'a> { part_index: dep, source_index: bun_ast::Index::init(tracker.source_index.get()), }); - // PERF(port): was assume_capacity } } @@ -3893,25 +3843,24 @@ impl<'a> LinkerContext<'a> { result } - /// Spec: `LinkerContext.zig:2471 matchImportsWithExportsForFile`. + /// Resolves every named import in one file to its matching export, + /// recording the bindings in `imports_to_bind`. pub(crate) fn match_imports_with_exports_for_file( &mut self, named_imports_ptr: *const crate::bundled_ast::NamedImports, imports_to_bind: &mut crate::RefImportData, source_index: crate::IndexInt, ) { - // PORT NOTE: Zig clones into a local, sorts, iterates, then writes back. - // `ArrayHashMap` has no in-place key sort and `NamedImport` is non-Clone - // (owns a `Vec`), so we sort an index vector over the live + // Note: `ArrayHashMap` has no in-place key sort and `NamedImport` is + // non-Clone (owns a `Vec`), so we sort an index vector over the live // keys/values instead — same observable iteration order (ascending - // `inner_index`). The write-back is a no-op here since we never mutate - // the map. + // `inner_index`). We never mutate the map. // - // The Zig clone existed to break the alias between this parameter and + // This parameter aliases // `self.graph.ast.named_imports[source_index]`, which // `match_import_with_export` re-reads via the SoA column. Taking the // parameter as a raw `*const` (no uniqueness assertion) and reading - // through it preserves that alias-safety without the clone: no live + // through it preserves that alias-safety: no live // `&`/`&mut` to the column element spans the `&mut self` call below. // // SAFETY: `named_imports_ptr` points into the `graph.ast.named_imports` @@ -4065,11 +4014,8 @@ impl<'a> LinkerContext<'a> { } } - /// Spec: `linker_context/generateCodeForLazyExport.zig`. - /// /// Thin inherent-method shim so callers can write - /// `this.generate_code_for_lazy_export(id)` (matches Zig's - /// `pub const generateCodeForLazyExport = @import(...)`). The full body — + /// `this.generate_code_for_lazy_export(id)`. The full body — /// including the CSS-modules `composes`/`local_scope` Visitor — lives in /// `linker_context/generateCodeForLazyExport.rs`. #[inline] @@ -4083,7 +4029,8 @@ impl<'a> LinkerContext<'a> { ) } - /// Spec: `LinkerContext.zig:503 generateNamedExportInFile`. + /// Synthesizes a named export symbol in a file (creating a new part for + /// it) and returns the symbol's `Ref` and the part index. pub fn generate_named_export_in_file( &mut self, source_index: crate::IndexInt, @@ -4154,11 +4101,8 @@ impl<'a> LinkerContext<'a> { return Ok(crate::chunk::IntermediateOutput::Joiner(core::mem::take(j))); } - // PORT NOTE: Zig had `errdefer j.deinit()` around the initCapacity — Drop handles it. let mut pieces: Vec = Vec::with_capacity(count as usize); - // errdefer pieces.deinit() — Drop handles it - // PORT NOTE: Zig used `j.done(alloc)` (worker arena), so the joined - // buffer outlived this function. The Rust `StringJoiner::done()` + // Note: `StringJoiner::done()` // returns a `Box<[u8]>`; we must keep it alive alongside the pieces // (each `OutputPiece` stores a raw `*const u8` into it). It is moved // into the returned `OutputPieces` below. @@ -4233,7 +4177,7 @@ impl<'a> LinkerContext<'a> { _ => unreachable!(), } - // PORT NOTE: `Query` is a packed `u32` (`index: u29`, `kind: u3`); + // Note: `Query` is a packed `u32` (`index: u29`, `kind: u3`); // construct via `new` rather than field-init. pieces.push(OutputPiece::init( &output[0..boundary], @@ -4250,11 +4194,11 @@ impl<'a> LinkerContext<'a> { } } -// PartialEq for MatchImport (needed for std.meta.eql in match_import_with_export) +// PartialEq for MatchImport (used by match_import_with_export) impl PartialEq for MatchImport { fn eq(&self, other: &Self) -> bool { - // PORT NOTE: Zig `std.meta.eql` on a slice compares ptr+len, not contents — - // compare the raw fat pointer (address + length metadata). + // Note: intentionally compares the raw fat pointer + // (address + length metadata), not contents. std::ptr::eq(self.alias.as_raw(), other.alias.as_raw()) && self.kind == other.kind && self.namespace_ref == other.namespace_ref @@ -4271,8 +4215,8 @@ impl PartialEq for MatchImport { // ────────────────────────────────────────────────────────────────────────── pub struct StmtList { - // TODO(port): arena field dropped — Vec uses global mimalloc; bundler is AST crate but - // these are temporary scratch buffers, not arena-backed in the original (uses generic arena param) + // Temporary scratch buffers: plain `Vec`s on the global allocator + // (cleared/reused per chunk, freed by Drop). pub inside_wrapper_prefix: InsideWrapperPrefix, pub outside_wrapper_prefix: Vec, pub inside_wrapper_suffix: Vec, @@ -4304,9 +4248,6 @@ impl InsideWrapperPrefix { } } -// TODO(port): `Expr`/`Stmt` builder helpers (`E::Call`, `S::SExpr` etc.) -// — bun_js_parser AST builder surface not yet stable. - impl InsideWrapperPrefix { pub(crate) fn append_non_dependency(&mut self, stmt: Stmt) -> Result<(), AllocError> { self.stmts.push(stmt); @@ -4353,9 +4294,9 @@ impl InsideWrapperPrefix { return Ok(()); } - // PORT NOTE: deep AST mutation chain — `s_expr_mut`/`e_await_mut`/ - // `e_call_mut`/`e_array_mut` return `Option`; `.unwrap()` mirrors Zig's - // untagged-union field reads (panic on shape mismatch). + // Note: deep AST mutation chain — `s_expr_mut`/`e_await_mut`/ + // `e_call_mut`/`e_array_mut` return `Option`; `.unwrap()` panics on + // shape mismatch. let mut first_dep_call_expr = self.stmts[self.sync_dependencies_end] .data .s_expr_mut() @@ -4399,7 +4340,6 @@ impl InsideWrapperPrefix { let mut items = bun_ast::ExprNodeList::init_capacity(2); items.append_slice_assume_capacity(&[first_dep_call_expr, call_expr]); - // PERF(port): was assume_capacity let mut args = bun_ast::ExprNodeList::init_capacity(1); args.append_assume_capacity(Expr::init( @@ -4409,7 +4349,6 @@ impl InsideWrapperPrefix { }, Loc::EMPTY, )); - // PERF(port): was assume_capacity let promise_all_call = Expr::init( E::Call { @@ -4484,5 +4423,3 @@ pub enum StmtListWhich { InsideWrapperSuffix, AllStmts, } - -// ported from: src/bundler/LinkerContext.zig diff --git a/src/bundler/LinkerGraph.rs b/src/bundler/LinkerGraph.rs index 6ab2d8502cd..329a26df380 100644 --- a/src/bundler/LinkerGraph.rs +++ b/src/bundler/LinkerGraph.rs @@ -12,10 +12,9 @@ use bun_core::RawSlice; use crate::IndexStringMap::IndexStringMap; use crate::{ImportTracker, Index, JSAst, Part, Ref, UseDirective, import_record, index, part}; // `items_()` column accessors — bring the `*ListExt` traits into scope. -// PORT NOTE: `BundledAstColumns` is emitted by `` -// on `BundledAst`; un-gating here is paired with that derive landing in -// `crate::bundled_ast` (same dependency `scanImportsAndExports.rs` -// already imports as `BundledAstField`). +// Note: `BundledAstColumns` is emitted by `bun_collections::multi_array_columns!` +// on `BundledAst` in `crate::bundled_ast` (the same macro output +// `scanImportsAndExports.rs` already imports as `BundledAstField`). bun_core::declare_scope!(LinkerGraph, visible); pub mod entry_point { @@ -200,12 +199,12 @@ pub struct LinkerGraph<'a> { pub entry_points: entry_point::List, pub symbols: symbol::Map, - // PORT NOTE: lifetime-erased. Zig stores `std.mem.Allocator`; the Rust + // Note: lifetime-erased. The // arena is owned by `BundleV2` and outlives every `LinkerGraph` — kept as // a raw pointer (matching `LinkerContext.parse_graph: *mut Graph`) so the // struct stays `'static`-ish and `LinkerContext`/`Chunk` callers don't - // grow a `'bump` parameter yet. TODO(refactor): thread `'bump` once `Chunk` - // and `html_import_manifest` gain lifetimes. + // grow a `'bump` parameter; threading `'bump` would require `Chunk` and + // `html_import_manifest` to gain lifetimes first. pub bump: bun_ptr::BackRef, pub code_splitting: bool, @@ -236,7 +235,7 @@ pub struct LinkerGraph<'a> { } // SAFETY: `LinkerGraph` is shared read-mostly across worker threads during -// linking (matches Zig, which has no Send/Sync). What makes `&LinkerGraph` +// linking. What makes `&LinkerGraph` // sound to hold concurrently: // // - `bump: *const Arena` is a backref into `BundleV2`; the arena is frozen @@ -277,7 +276,6 @@ impl<'a> LinkerGraph<'a> { impl<'a> LinkerGraph<'a> { pub fn init(bump: &Arena, file_count: usize) -> Result { - // TODO(port): narrow error set Ok(LinkerGraph { files: FileList::default(), files_live: BitSet::init_empty(file_count)?, @@ -304,8 +302,8 @@ impl Default for LinkerGraph<'_> { parts_live: Vec::new(), entry_points: entry_point::List::default(), symbols: symbol::Map::default(), - // PORT NOTE: `bump` is a backref assigned in `init`/`LinkerContext::load`; - // dangling sentinel mirrors Zig's `undefined` (never read before assignment). + // Note: `bump` is a backref assigned in `init`/`LinkerContext::load`; + // dangling sentinel (never read before assignment). bump: bun_ptr::BackRef::from(core::ptr::NonNull::dangling()), code_splitting: false, ast: MultiArrayList::default(), @@ -345,21 +343,18 @@ pub fn generate_new_symbol( ) -> Ref { let source_symbols = &mut symbols.symbols_for_source.slice_mut()[source_index as usize]; - // PORT NOTE: Zig built `Ref.init(..)` then assigned `ref.tag = .symbol`. - // The Rust `Ref` is a packed `u64` with no public `tag` field, so use - // the `Ref::new` constructor that takes the tag explicitly. let ref_ = Ref::new( - source_symbols.len() as u32, // @truncate (u32 → u31 in pack()) - source_index, // @truncate + source_symbols.len() as u32, // narrows to u31 in pack() + source_index, RefTag::Symbol, ); // TODO: will this crash on resize due to using threadlocal mimalloc heap? source_symbols.push(Symbol { kind, - // PORT NOTE: `Symbol.original_name` is a `StoreStr` — - // arena-owned slice whose lifetime is erased (matches the Zig - // `[]const u8`); caller guarantees it outlives the symbol table. + // Note: `Symbol.original_name` is a `StoreStr` — + // arena-owned slice whose lifetime is erased; + // caller guarantees it outlives the symbol table. original_name: bun_ast::StoreStr::new(original_name), ..Default::default() }); @@ -390,18 +385,11 @@ pub(crate) fn add_part_to_file( id: u32, part: Part, ) -> Result { - let part_id = parts[id as usize].len() as u32; // @truncate (u32) + let part_id = parts[id as usize].len() as u32; parts[id as usize].push(part); - // PORT NOTE: borrowck reshape. The Zig closure simultaneously holds - // * `&mut parts[part_id].declared_symbols` (column `parts` of `ast`) - // * `&meta.top_level_symbol_to_parts_overlay[id]` (`meta`) - // * `&ast.top_level_symbols_to_parts[id]` (another `ast` column) - // and additionally caches `*?*TopLevelSymbolToParts` across calls. - // The two `ast` columns now arrive pre-split, so no detach/reattach is - // needed. The overlay-pointer cache is dropped — re-index `meta` each - // call (O(1); the cache was a Zig micro-opt that does not survive - // Stacked Borrows). + // Note: the two `ast` columns arrive pre-split, so no detach/reattach is + // needed; re-index `meta` on each call (O(1)). let declared_symbols: &mut DeclaredSymbolList = &mut parts[id as usize][part_id as usize].declared_symbols; @@ -447,7 +435,7 @@ pub fn generate_symbol_import_and_use( part_index: u32, ref_: Ref, use_count: u32, - // PORT NOTE: callers are split between `crate::Index` (options_types) + // Note: callers are split between `crate::Index` (options_types) // and the structurally identical `bun_ast::Index` until the two newtypes // unify. Accept either via `Into` and normalize once. source_index_to_import_from: impl Into, @@ -514,11 +502,11 @@ pub fn generate_symbol_import_and_use( debug_assert_eq!(part_ids.len(), new_dependencies.len()); for (part_id, dependency) in part_ids.iter().zip(new_dependencies.iter_mut()) { *dependency = Dependency { - // PORT NOTE: `Dependency.source_index` is the structurally + // Note: `Dependency.source_index` is the structurally // identical `bun_ast::Index`; convert by value until the // two `Index` newtypes unify. source_index: bun_ast::Index::init(source_index_to_import_from.get()), - part_index: *part_id, // @truncate (already u32) + part_index: *part_id, // already u32 }; } Ok(()) @@ -663,7 +651,6 @@ impl<'a> LinkerGraph<'a> { dynamic_import_entry_points: &[index::Int], entry_point_original_names: &IndexStringMap, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let scb = server_component_boundaries.slice(); self.files.set_capacity(sources.len())?; self.files.zero(); @@ -671,7 +658,7 @@ impl<'a> LinkerGraph<'a> { // SAFETY: capacity reserved above; columns zeroed by `zero()`. unsafe { self.files.set_len(sources.len()) }; - // PORT NOTE: `Slice` caches raw column pointers and does not borrow + // Note: `Slice` caches raw column pointers and does not borrow // `self.files`, so the `split_mut()` borrows (tied to the local // `files_slice`) can stay live across other `&mut self.*` accesses // below. The columns are not reallocated during `load`. @@ -690,9 +677,9 @@ impl<'a> LinkerGraph<'a> { // SAFETY: capacity reserved; columns initialized below. unsafe { self.entry_points.set_len(entry_points.len()) }; - // PORT NOTE: borrowck reshape — Zig held `source_indices` / - // `path_strings` / `output_path_was_auto_generated` simultaneously - // (disjoint columns of the same `MultiArrayList`). `split_mut()` + // Note: `source_indices` / `path_strings` / + // `output_path_was_auto_generated` are disjoint columns of the + // same `MultiArrayList`. `split_mut()` // hands out all three at once; `self.entry_points` is not // reallocated until after `path_strings`/`source_indices` are done // with (the next `append_assume_capacity` is within the @@ -746,12 +733,7 @@ impl<'a> LinkerGraph<'a> { let import_records_len = self.ast.items_import_records().len(); self.meta.set_capacity(import_records_len)?; - // PORT NOTE: Zig does `meta.len = ast.len; meta.zero()` — a raw - // memset(0) is the valid empty state for Zig's unmanaged - // containers. Rust `Vec`/`Box` require a non-null dangling - // pointer when empty, so zeroed bytes violate their invariants - // (`slice::from_raw_parts` null-check trips on first read). Fill - // each slot with `Default` instead. + // Fill each slot with `Default`. let ast_len = self.ast.len(); debug_assert!(ast_len <= import_records_len); for _ in 0..ast_len { @@ -823,8 +805,7 @@ impl<'a> LinkerGraph<'a> { // Setup files { // set it to max value so that if we access an invalid one, it crashes - // PORT NOTE: Zig used `@memset(sliceAsBytes(...), 255)` to fill raw - // bytes; here we fill with `Index::INVALID` whose bytes are all + // Note: fill with `Index::INVALID` whose bytes are all // 0xFF (`#[repr(transparent)]` over `u32::MAX`). let stable_source_indices = self .arena() @@ -842,9 +823,7 @@ impl<'a> LinkerGraph<'a> { } { - // PORT NOTE: Zig built a borrowed `Symbol.NestedList` over the - // `ast.items(.symbols)` column then `clone`d it (memcpy). The Rust - // `Vec::clone` requires `T: Clone` which `Symbol` does not + // Note: `Vec::clone` requires `T: Clone` which `Symbol` does not // derive (it carries a raw `*const [u8]`), so spell out the // bitwise copy explicitly — `Symbol` has no `Drop` impl. let src_symbols: &[symbol::List] = self.ast.items_symbols(); @@ -864,25 +843,6 @@ impl<'a> LinkerGraph<'a> { } // TODO: const_values - // { - // var const_values = this.const_values; - // var count: usize = 0; - // - // for (this.ast.items(.const_values)) |const_value| { - // count += const_value.count(); - // } - // - // if (count > 0) { - // try const_values.ensureTotalCapacity(this.arena, count); - // for (this.ast.items(.const_values)) |const_value| { - // for (const_value.keys(), const_value.values()) |key, value| { - // const_values.putAssumeCapacityNoClobber(key, value); - // } - // } - // } - // - // this.const_values = const_values; - // } { let mut count: usize = 0; @@ -894,10 +854,7 @@ impl<'a> LinkerGraph<'a> { for ts_enums in self.ast.items_ts_enums().iter() { debug_assert_eq!(ts_enums.keys().len(), ts_enums.values().len()); for (key, value) in ts_enums.keys().iter().zip(ts_enums.values().iter()) { - // PERF(port): was assume_capacity_no_clobber - // PORT NOTE: Zig copied the inner `StringHashMap` by - // value (shallow struct copy). Rust clones the backing - // `HashMap`; the per-file maps are not mutated after + // Note: the per-file maps are not mutated after // this point so aliasing is not required. self.ts_enums.put_assume_capacity(*key, value.clone()); } @@ -919,7 +876,6 @@ impl<'a> LinkerGraph<'a> { .expect("unreachable"); debug_assert_eq!(src.keys().len(), src.values().len()); for (key, value) in src.keys().iter().zip(src.values().iter()) { - // PERF(port): was assume_capacity_no_clobber resolved.put_assume_capacity( key, js_meta::ResolvedExport { @@ -937,7 +893,7 @@ impl<'a> LinkerGraph<'a> { Ok(()) } - /// Port of `LinkerGraph.zig:takeAstOwnership`. `clone_ast` left each + /// `clone_ast` left each /// `PartList`/import-record list with its allocator handle pointing at /// the per-worker `mi_heap` that built it; re-tag to `heap` (the /// bundle-thread arena) so linker-side `add_part_to_file` pushes call @@ -945,13 +901,12 @@ impl<'a> LinkerGraph<'a> { /// owns `heap`. Zero-copy: only files the linker actually grows pay a /// (lazy, mimalloc-internal) cross-heap migration on first realloc. /// - /// Zig is a release no-op because `BabyList` passes the allocator at each - /// `append` call site; the Rust `Vec` stores it, so swap here. - /// Zig also transfers `part.dependencies` and `symbols`; the Rust port's + /// `Vec` stores its allocator, so swap it here. + /// `part.dependencies` and `symbols` need no transfer: /// `DependencyList` is `Vec<_, AstAlloc>` (linker-side grows just route /// through whichever thread's `AstAlloc` state is active — `AstAlloc` is a /// ZST, so there is nothing to retag) and new symbols feed through - /// `self.symbols: symbol::Map` (global) — neither needs transfer here. + /// `self.symbols: symbol::Map` (global). pub fn take_ast_ownership(&mut self, heap: &'a Arena) { for v in self.ast.items_import_records_mut() { bun_alloc::transfer_arena(v, heap); @@ -962,7 +917,6 @@ impl<'a> LinkerGraph<'a> { } pub fn propagate_async_dependencies(&mut self) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set struct State<'a> { visited: AutoBitSet, import_records: &'a [import_record::List<'a>], @@ -1076,8 +1030,7 @@ impl File { impl Default for File { fn default() -> Self { Self { - // PORT NOTE: Zig had `entry_bits: AutoBitSet = undefined` — using an - // empty static-arm bitset here; load() overwrites before any read. + // Note: empty static-arm bitset; load() overwrites before any read. entry_bits: AutoBitSet::init_empty(0).expect("static AutoBitSet"), input_file: Index::source(0u32), distance_from_entry_point: u32::MAX, @@ -1102,5 +1055,3 @@ bun_collections::multi_array_columns! { quoted_source_contents: Option>, } } - -// ported from: src/bundler/LinkerGraph.zig diff --git a/src/bundler/OutputFile.rs b/src/bundler/OutputFile.rs index c3a1d8fefb0..9aa4f42e4dc 100644 --- a/src/bundler/OutputFile.rs +++ b/src/bundler/OutputFile.rs @@ -116,7 +116,6 @@ pub struct BakeExtra { pub bake_is_runtime: bool, } -// Zig: `pub const Index = bun.GenericIndex(u32, OutputFile);` pub type Index = bun_core::GenericIndex; pub type IndexOptional = bun_core::GenericIndexOptional; @@ -127,8 +126,7 @@ pub type IndexOptional = bun_core::GenericIndexOptional; // We may use a different system call #[derive(Clone)] pub struct FileOperation { - // TODO(port): lifetime — Zig never frees `pathname`; may be borrowed from - // `Options.output_path`. Using owned `Box<[u8]>` for now. + // Owned copy so the field has a single, obvious lifetime. pub pathname: Box<[u8]>, pub fd: Fd, pub dir: Fd, @@ -163,9 +161,8 @@ impl FileOperation { pub fn get_pathname(&self) -> &[u8] { if self.is_tmpdir { - // PORT NOTE: `resolve_path.joinAbs` writes into a threadlocal buffer in - // Zig; the Rust port returns a borrow into that TLS buffer (`'static`), - // which coerces to the `&self` lifetime here. + // Note: `join_abs` returns a borrow into a threadlocal buffer + // (`'static`), which coerces to the `&self` lifetime here. return resolve_path::join_abs::(RealFS::tmpdir_path(), &self.pathname); } &self.pathname @@ -190,19 +187,15 @@ pub enum Value { Move(FileOperation), Copy(FileOperation), Noop, - Buffer { - // Zig carried `arena: std.mem.Allocator` alongside `bytes`; in Rust the - // global mimalloc arena backs `Box<[u8]>`, so the field is dropped. - bytes: Box<[u8]>, - }, - // PORT NOTE: boxed to avoid blowing up `Value`'s inline size (`resolver::Result` + Buffer { bytes: Box<[u8]> }, + // Note: boxed to avoid blowing up `Value`'s inline size (`resolver::Result` // is several hundred bytes). Pending(Box), Saved(SavedFile), } -// Zig `bun.copy(OutputFile, dst, src)` is a bitwise memcpy used to splice -// finished output files into the final list. The `Pending` arm is never present +// Cloning is only used to splice finished output files into the final list. +// The `Pending` arm is never present // at that stage (only `buffer`/`copy`/`saved` are produced by `init`), so its // clone is intentionally unreachable rather than forcing `resolver::Result` to // be `Clone`. @@ -239,10 +232,8 @@ impl Value { // Use ExternalStringImpl to avoid cloning the string, at // the cost of allocating space to remember the arena. // - // Zig boxed a `FreeContext { arena }` and passed an `extern "C"` - // callback that frees the slice via that arena then destroys the - // context. With the global arena, the context collapses to the - // (ptr, len) pair already passed to the callback. + // The free callback needs no context beyond the + // (ptr, len) pair it is already passed. extern "C" fn on_free(_ctx: *mut c_void, buffer: *mut c_void, len: usize) { // SAFETY: `buffer`/`len` were produced by `heap::alloc` on a // `Box<[u8]>` below; reconstructing and dropping is sound. @@ -257,7 +248,7 @@ impl Value { // (= `Box::leak`) yields a `&'static mut [u8]` borrow of the // now-JSC-owned allocation; `on_free` reclaims it on GC. let bytes: &'static mut [u8] = bun_core::heap::release(bytes); - // latin1 flag = true (matches Zig). + // latin1 flag = true. BunString::create_external::<*mut c_void>( bytes, true, @@ -266,9 +257,8 @@ impl Value { ) } Value::Pending(_) => unreachable!(), - // Zig: `else => |tag| bun.todoPanic(@src(), "handle .{s}", .{@tagName(tag)})` - // — an intentional shipped runtime panic for `.move`/`.copy`/`.saved`, - // not a port placeholder. + // An intentional shipped runtime panic for `.move`/`.copy`/`.saved`, + // not a placeholder. other => bun_core::todo_panic!("handle .{}", <&'static str>::from(other.kind())), } } @@ -278,12 +268,9 @@ impl Value { /// callback (zero-copy). Caller guarantees `self` outlives every use of the /// returned string. /// - /// This is the faithful port of Zig's `Value.toBunString` as called from - /// `bake/production.zig` (`pt.bundled_outputs[i].value.toBunString()`): Zig - /// passes the union by value so the slice is aliased in place, and /// `PerThread.bundled_outputs` owns the bytes for the entire prerender /// phase. The consuming [`Self::to_bun_string`] cannot be used there - /// because the Rust `Vec` is only borrowed. + /// because the `Vec` is only borrowed. pub fn to_bun_string_ref(&self) -> BunString { match self { Value::Noop => BunString::EMPTY, @@ -292,7 +279,7 @@ impl Value { return BunString::EMPTY; } extern "C" fn noop(_: *mut c_void, _: *mut c_void, _: usize) {} - // latin1 = true (matches Zig). + // latin1 = true. BunString::create_external::<*mut c_void>( bytes, true, @@ -317,7 +304,6 @@ impl Value { } } -/// `OutputFile.zig:SavedFile` (TYPE_ONLY move-in from bundler_jsc). #[derive(Default, Clone, Copy)] pub struct SavedFile { pub byte_size: u64, @@ -325,8 +311,7 @@ pub struct SavedFile { impl OutputFile { pub fn init_pending(loader: Loader, pending: bun_resolver::Result) -> OutputFile { - // PORT NOTE: Zig copied the whole `Fs.Path` struct (`pending.pathConst().?.*`). - // The Rust `bun_paths::fs::Path<'static>` and `bun_resolver::fs::Path<'static>` are + // Note: `bun_paths::fs::Path<'static>` and `bun_resolver::fs::Path<'static>` are // distinct nominal types with identical layout; re-init from `text` (the // resolver path borrows arena/static memory, so the `'static` bound holds). let src_path = fs::Path::init(pending.path_const().expect("path").text); @@ -357,9 +342,6 @@ impl OutputFile { ) -> OutputFile { let mut res = Self::init_file(file, pathname, size); if let Value::Copy(op) = &mut res.value { - // PORT NOTE: Zig wrote `res.value.copy.dir_handle = .fromStdDir(dir)` but - // `FileOperation` has no `dir_handle` field — looks like a latent bug; the - // intended field is `dir`. op.dir = dir; } res @@ -442,7 +424,6 @@ impl OutputFile { } } - // TODO(port): narrow error set pub fn write_to_disk(&self, root_dir: Fd, root_dir_path: &[u8]) -> Result<(), Error> { match &self.value { Value::Noop => {} @@ -453,8 +434,7 @@ impl OutputFile { let mut rel_path: &[u8] = &self.dest_path; if self.dest_path.len() > root_dir_path.len() { rel_path = resolve_path::relative(root_dir_path, &self.dest_path); - // Zig: `std.fs.path.dirname` returns `null` when there's no - // separator; the Rust port returns `b""` instead. + // `dirname` returns `b""` when there's no separator. let parent = resolve_path::dirname::(rel_path); if !parent.is_empty() { bun_sys::Dir::borrow(&root_dir).make_path(parent)?; @@ -465,11 +445,7 @@ impl OutputFile { let _ = bun_sys::write_file_with_path_buffer( &mut path_buf, &bun_sys::WriteFileArgs { - data: bun_sys::WriteFileData::Buffer { - // Zig built a JSC ArrayBuffer view over `bytes` via - // `@constCast`; the Rust side just borrows the slice. - buffer: bytes, - }, + data: bun_sys::WriteFileData::Buffer { buffer: bytes }, encoding: bun_sys::WriteFileEncoding::Buffer, mode: if self.is_executable { 0o755 } else { 0o644 }, dirfd: root_dir, @@ -488,14 +464,11 @@ impl OutputFile { Ok(()) } - // TODO(port): narrow error set pub fn move_to(&self, _: &[u8], rel_path: &[u8], dir: Fd) -> Result<(), Error> { let Value::Move(mv) = &self.value else { unreachable!() }; - // Zig: `std.posix.toPosixPath` + `bun.sliceTo(.., 0)` to NUL-terminate both - // paths into stack buffers. Mirrored with `resolve_path::z` over two - // `PathBuffer`s. + // NUL-terminate both paths into stack buffers via `resolve_path::z`. let mut src_buf = PathBuffer::uninit(); let mut dst_buf = PathBuffer::uninit(); let src = resolve_path::z(mv.get_pathname(), &mut src_buf); @@ -504,11 +477,7 @@ impl OutputFile { Ok(()) } - // TODO(port): narrow error set pub fn copy_to(&self, _: &[u8], rel_path: &[u8], dir: Fd) -> Result<(), Error> { - // PORT NOTE: Zig used `dir.stdDir().createFile(rel_path, .{})` and - // `std.fs.cwd().openFile(...)`. Mapped to `bun_sys::openat` (which takes - // a NUL-terminated `&ZStr`). let mut out_buf = PathBuffer::uninit(); let fd_out = bun_sys::openat( dir, @@ -538,9 +507,5 @@ impl OutputFile { } } -// Zig: `pub const toJS = @import("../bundler_jsc/output_file_jsc.zig").toJS;` -// Zig: `pub const toBlob = @import("../bundler_jsc/output_file_jsc.zig").toBlob;` -// Deleted per PORTING.md — `to_js` / `to_blob` become extension-trait methods that +// `to_js` / `to_blob` are extension-trait methods that // live in `bun_bundler_jsc`; the base type carries no jsc reference. - -// ported from: src/bundler/OutputFile.zig diff --git a/src/bundler/ParseTask.rs b/src/bundler/ParseTask.rs index 8795ba98ed5..67402efd94a 100644 --- a/src/bundler/ParseTask.rs +++ b/src/bundler/ParseTask.rs @@ -1,5 +1,3 @@ -//! Port of src/bundler/ParseTask.zig -//! //! A `ParseTask` is the unit of work scheduled on the thread pool for each //! source file the bundler needs to parse. It carries everything needed to //! read the file (or use already-loaded contents), run the JS/CSS/etc. parser, @@ -16,14 +14,12 @@ use bun_collections::VecExt; use bun_core::strings; use bun_core::{self, Error as AnyError, FeatureFlags, declare_scope, err, scoped_log}; use bun_sys::Fd; -// PORT NOTE: Zig `bun.threading.ThreadPool` is the *module*; the draft used the -// struct alias which made `ThreadPoolLib::Task` unresolvable. Import the module. use bun_threading::thread_pool as ThreadPoolLib; use bun_ast::Index; use bun_ast::{self as ast, E, Expr, G, Part}; use bun_js_parser as js_parser; -// PORT NOTE: `BundledAst<'arena>` — the bundler graph stores `'static`-erased +// `BundledAst<'arena>` — the bundler graph stores `'static`-erased // ASTs (arena outlives the link step). Use the crate-level alias so the // `Success`/helper signatures don't carry an explicit `'static` everywhere. use crate::JSAst; @@ -44,13 +40,12 @@ use bun_resolver::{self as _resolver, Resolver}; declare_scope!(ParseTask, hidden); -/// `bun.jsc.EventLoopTask` (ParseTask.zig:Result.task). T6 type erased here. #[allow(non_snake_case)] mod EventLoop { pub(super) type Task = bun_event_loop::ConcurrentTask::ConcurrentTask; } -// PORT NOTE: the per-file parse arena is held as `bump: &'static Bump` (the +// the per-file parse arena is held as `bump: &'static Bump` (the // worker arena is pinned for the entire bundle pass — see `run_with_source_code`), // so `bump.alloc_*` / `ArenaString::into_bump_str` already yield `&'static` // borrows directly. No erasure helper is needed; `StoreStr::new` covers the @@ -60,7 +55,7 @@ mod EventLoop { // live on the canonical `impl Plugin` in `bundle_v2.rs::api::JSBundler` next to // the other FFI wrappers; `bundler::JSBundlerPlugin` re-exports that type. // -// PORT NOTE: `FileMap::get` now lives on the real `JSBundler::FileMap` in +// `FileMap::get` now lives on the real `JSBundler::FileMap` in // bundle_v2.rs (no longer an opaque forward-decl). The placeholder // always-miss `get` shim that used to sit here has been removed so the two // inherent impls don't collide. @@ -73,8 +68,9 @@ mod EventLoop { #[enum_tag(existing = ContentsOrFdTag)] pub enum ContentsOrFd { Fd { dir: Fd, file: Fd }, - // TODO(port): arena lifetime — contents may be arena-owned, plugin-owned, - // or &'static (runtime). Using &'static as a placeholder. + // The `'static` is ownership-erased: contents may be arena-owned, + // plugin-owned, or truly static (runtime source). The producer keeps the + // backing allocation alive for the duration of the bundle pass. Contents(&'static [u8]), } @@ -89,7 +85,7 @@ pub(crate) enum ContentsOrFdTag { // ─────────────────────────────────────────────────────────────────────────── pub struct ParseTask { - // PORT NOTE: lifetime-erased `'static` — paths borrow from `DirnameStore` + // lifetime-erased `'static` — paths borrow from `DirnameStore` // (process-lifetime BSS string pool); see `bun_resolver::fs::Path<'a>`. pub path: Fs::Path<'static>, pub secondary_path_for_commonjs_interop: Option>, @@ -113,7 +109,7 @@ pub struct ParseTask { pub module_type: options::ModuleType, pub emit_decorator_metadata: bool, pub experimental_decorators: bool, - /// BACKREF (LIFETIMES.tsv) — Zig `*BundleV2` is mutable; written through in + /// BACKREF (LIFETIMES.tsv) — written through in /// `on_complete`. `None` only in the `default()` placeholder; every /// scheduled task has it set via `init` / `bundle_v2.rs` write-sites. pub ctx: Option>>, @@ -228,7 +224,7 @@ impl ParseTask { pub fn init( resolve_result: &_resolver::Result, source_index: Index, - // Zig `ctx: *BundleV2` — take `*mut` so the stored BACKREF retains + // Take `*mut` so the stored BACKREF retains // write provenance for `on_complete` (a `&BundleV2` param would shrink // provenance to read-only, making the later `&mut *ctx` UB). ctx: *mut BundleV2<'_>, @@ -261,7 +257,7 @@ impl ParseTask { side_effects: resolve_result.primary_side_effects_data, // D042: resolver-side and bundler-side `jsx::Pragma` are the SAME // nominal type (`bun_options_types::jsx::Pragma`). Preserves - // jsxImportSource/runtime/etc. from tsconfig.json (.zig:122). + // jsxImportSource/runtime/etc. from tsconfig.json. jsx: resolve_result.jsx.clone(), source_index, module_type: resolve_result.module_type, @@ -285,14 +281,11 @@ impl ParseTask { stage: ParseTaskStage::NeedsSourceCode, tree_shaking: false, is_entry_point: false, - // TODO(port): Zig struct-field defaults; Rust has no per-field - // default syntax. Consider impl Default for ParseTask and use - // `..Default::default()` here. } } /// Re-export of `parse_worker::get_runtime_source` as an associated fn so - /// callers can spell it `ParseTask::get_runtime_source` (matches Zig). + /// callers can spell it `ParseTask::get_runtime_source`. #[inline] pub fn get_runtime_source(target: options::Target) -> RuntimeSource { parse_worker::get_runtime_source(target) @@ -538,10 +531,6 @@ pub mod parse_worker { ) } }; - // PERF(port): Zig built one comptime string per Target variant via - // `inline else`. Here we use `const_format::concatcp!` per arm; the match - // itself is runtime but each arm yields a &'static str. Profile if the - // extra match matters (it shouldn't — called once). let parse_task = ParseTask { ctx: None, @@ -576,7 +565,7 @@ pub mod parse_worker { is_entry_point: false, }; let source = Source { - // PORT NOTE: `bun_ast::Source.path` is `bun_paths::fs::Path<'static>`, distinct + // `bun_ast::Source.path` is `bun_paths::fs::Path<'static>`, distinct // from `bun_resolver::fs::Path` (TYPE_ONLY mirror). Construct // directly rather than `clone()` across the type boundary. path: bun_paths::fs::Path { @@ -587,7 +576,7 @@ pub mod parse_worker { is_symlink: false, }, contents: std::borrow::Cow::Borrowed(runtime_code.as_bytes()), - // PORT NOTE: `Source.index` is `bun_ast::Index` (newtype `u32`), + // `Source.index` is `bun_ast::Index` (newtype `u32`), // distinct from `bun_ast::Index`. Runtime source is index 0. index: bun_ast::Index(Index::RUNTIME.get()), ..Default::default() @@ -596,9 +585,6 @@ pub mod parse_worker { } pub fn get_runtime_source(target: options::Target) -> RuntimeSource { - // PERF(port): Zig `switch (target) { inline else => |t| comptime ... }` - // monomorphized per variant at comptime. Runtime dispatch here is fine - // since each arm in `get_runtime_source_comptime` already yields static data. get_runtime_source_comptime(target) } @@ -610,7 +596,7 @@ pub mod parse_worker { // (`Parser::to_lazy_export_ast`); `bun_css::BundlerStyleSheet` (gated // upstream); `Expr::init` overload set for arbitrary `E::*` defaults. - // PORT NOTE: `transpiler: *mut Transpiler` (raw, Zig `*Transpiler`). Callers + // `transpiler: *mut Transpiler` stays raw. Callers // (`get_ast`, `run_with_source_code`) may also hold a raw pointer to // `(*transpiler).resolver`; materializing `&mut Transpiler` here would assert // exclusive access to the whole struct and invalidate that sibling pointer. @@ -662,11 +648,10 @@ pub mod parse_worker { // ─────────────────────────────────────────────────────────────────────────── // CSS Symbol bridge — `bun_ast::Symbol` ↔ `bun_ast::Symbol` // - // Both port the same Zig `js_ast.Symbol` (Symbol.zig). // `StylesheetExtra.symbols` is `Vec`; // `new_lazy_export_ast_impl` takes `Vec`. Convert // field-by-field so CSS-module local refs (`ref.inner_index()`) index a - // populated symbol table (.zig:613). + // populated symbol table. // ─────────────────────────────────────────────────────────────────────────── fn css_symbols_to_parser_symbols( @@ -724,8 +709,8 @@ pub mod parse_worker { // The signature now names the real `ParserOptions`; body un-gates in lockstep // with the above. - // PORT NOTE: `transpiler`/`resolver` are raw `*mut` (Zig `*Transpiler` / - // `*Resolver`). In Zig the caller passes `resolver = &transpiler.resolver`, so + // `transpiler`/`resolver` are raw `*mut`. The caller may pass + // `resolver = &transpiler.resolver`, so // the two may point into the same allocation. Taking `&mut Transpiler` + // `&mut Resolver` would be aliased-`&mut` UB. We instead reborrow only the // disjoint `(*transpiler).options` field, never the whole struct. @@ -752,24 +737,18 @@ pub mod parse_worker { match loader { Loader::Jsx | Loader::Tsx | Loader::Js | Loader::Ts => { let _trace = perf::trace("Bundler.ParseJS"); - // PORT NOTE: `ParserOptions` is not `Clone` (holds `&'a mut MacroContext`). - // Zig (.zig:335-342) passes the *same* `opts` (bitwise copy) to the - // empty-AST fallback; since Rust moves `opts` into `.parse()`, + // `ParserOptions` is not `Clone` (holds `&'a mut MacroContext`). + // The empty-AST fallback needs the same options; since `opts` + // moves into `.parse()`, // snapshot a faithful field-by-field copy via // `Options::clone_for_lazy_export` (co-located with the struct so // field drift is a hard error) before the move. let fallback_opts = opts.clone_for_lazy_export(); let module_type = opts.module_type; - return if let Some(res) = (crate::cache::JavaScript {}).parse( - bump, // TODO(port): zig passed transpiler.arena - opts, - &topts.define, - log, - source, - )? { - // PORT NOTE: Zig's `js_parser.Result` is a bare-union whose - // `.ast` field is read unconditionally. The Rust port models it - // as an enum; `Cached`/`AlreadyBundled` are runtime-loader + return if let Some(res) = + (crate::cache::JavaScript {}).parse(bump, opts, &topts.define, log, source)? + { + // `Cached`/`AlreadyBundled` are runtime-loader // states that never reach the bundler's `getAST`, so unwrap. match res { bun_js_parser::Result::Ast(ast) => Ok(JSAst::init(*ast)), @@ -783,8 +762,6 @@ pub mod parse_worker { } else { get_empty_ast::(log, transpiler, fallback_opts, bump, source) }; - // PERF(port): Zig used `switch (bool) { inline else => |as_undefined| ... }` - // to monomorphize the RootType. Expanded to two calls. } Loader::Json | Loader::Jsonc => { let _trace = perf::trace("Bundler.ParseJSON"); @@ -793,7 +770,7 @@ pub mod parse_worker { } else { bun_resolver::tsconfig_json::JsonMode::Json }; - // SAFETY: `resolver` is a live `*mut Resolver` (Zig `*Resolver`); + // SAFETY: `resolver` is a live `*mut Resolver`; // `caches` is disjoint from `(*transpiler).options` reborrowed above. let root: Expr = unsafe { &mut (*resolver).caches.json } .parse_json(log, source, mode, true)? @@ -814,8 +791,7 @@ pub mod parse_worker { Loader::Toml => { let _trace = perf::trace("Bundler.ParseTOML"); let mut temp_log = Log::init(); - // PORT NOTE: Zig `defer { temp_log.cloneToWithRecycled(log, true); - // temp_log.msgs.clearAndFree() }` runs on the error path too. + // `temp_log` must flush into `log` on the error path too. // scopeguard would alias `log`/`temp_log` (both borrowed mutably // below); reshape as a closure so every `?` exits through one // post-amble that flushes `temp_log`. @@ -918,7 +894,7 @@ pub mod parse_worker { Some(source), Loc::EMPTY, b"Failed to render markdown to HTML", - ); // logger OOM-only (Zig: catch unreachable) + ); // logger OOM-only return Err(err!("ParserError")); } }; @@ -954,7 +930,7 @@ pub mod parse_worker { Loader::SqliteEmbedded | Loader::Sqlite => { if !topts.target.is_bun() { - // logger OOM-only (Zig: catch unreachable) + // logger OOM-only let _ = log.add_error( Some(source), Loc::EMPTY, @@ -1075,7 +1051,7 @@ pub mod parse_worker { Loader::Napi => { // (dap-eval-cb "source.contents.ptr") if topts.target == options::Target::Browser { - // logger OOM-only (Zig: catch unreachable) + // logger OOM-only let _ = log.add_error( Some(source), Loc::EMPTY, @@ -1142,7 +1118,7 @@ pub mod parse_worker { )); } Loader::Html => { - // PORT NOTE: scope the scanner so its `&mut log` / `&source` + // scope the scanner so its `&mut log` / `&source` // borrows release before `new_lazy_export_ast` re-borrows them. let import_records = { let mut scanner = HTMLScanner::init(log, source); @@ -1209,18 +1185,18 @@ pub mod parse_worker { let mut import_records = Vec::::default(); let source_code = &source.contents; let mut temp_log = Log::init(); - // PORT NOTE: Zig `defer { temp_log.appendToMaybeRecycled(log, source) }` — - // folded into linear control flow (scopeguard would alias `log`/`temp_log`). + // `temp_log` is flushed into `log` on every exit path via linear + // control flow (scopeguard would alias `log`/`temp_log`). const CSS_MODULE_SUFFIX: &[u8] = b".module.css"; let enable_css_modules = source.path.pretty.len() > CSS_MODULE_SUFFIX.len() && &source.path.pretty[source.path.pretty.len() - CSS_MODULE_SUFFIX.len()..] == CSS_MODULE_SUFFIX; - // PORT NOTE: `parse_bundler` takes `ParserOptions<'static>` (the + // `parse_bundler` takes `ParserOptions<'static>` (the // `'a` on `ParserOptions` is PhantomData-only; storage is a raw // `NonNull`). Construct via `default(None)` to get `'static`, // then poke the logger pointer in directly — `temp_log` outlives - // all parsing/minification below (mirrors Zig's `*Log` aliasing). + // all parsing/minification below. let parser_options = { let mut parseropts = bun_css::ParserOptions::default(None); parseropts.logger = Some(core::ptr::NonNull::from(&mut temp_log)); @@ -1240,7 +1216,7 @@ pub mod parse_worker { ) { Ok(v) => v, Err(e) => { - // .zig:587 — surface the actual CSS parse diagnostic. + // Surface the actual CSS parse diagnostic. let _ = e.add_to_logger(&mut temp_log, source); let _ = temp_log.append_to_maybe_recycled(log, source); return Err(err!("SyntaxError")); @@ -1261,7 +1237,7 @@ pub mod parse_worker { }, &extra, ) { - // .zig:604 — surface the actual minify diagnostic. + // Surface the actual minify diagnostic. let _ = e.add_to_logger(&mut temp_log, source); let _ = temp_log.append_to_maybe_recycled(log, source); return Err(err!("MinifyError")); @@ -1271,15 +1247,13 @@ pub mod parse_worker { } // If this is a css module, the final exports object wil be set in `generateCodeForLazyExport`. let root = Expr::init(E::Object::default(), Loc { start: 0 }); - // PORT NOTE: `StylesheetExtra.symbols` is + // `StylesheetExtra.symbols` is // `Vec`; `new_lazy_export_ast_impl` takes - // `Vec`. Both port the same Zig - // `js_ast.Symbol`; convert field-by-field so CSS-module local refs - // index a populated symbol table (.zig:613). + // `Vec`. Convert field-by-field so CSS-module local refs + // index a populated symbol table. let symbols = css_symbols_to_parser_symbols(&extra.symbols, bump); - // PORT NOTE: Zig `defer temp_log.appendToMaybeRecycled(log, source)` - // (.zig:564-566) flushes on EVERY exit including this `try`; mirror - // by matching explicitly so accumulated CSS-module diagnostics are + // `temp_log` flushes into `log` on EVERY exit; match explicitly + // so accumulated CSS-module diagnostics are // not dropped on the error path. let lazy = js_parser::new_lazy_export_ast_impl( bump, @@ -1385,8 +1359,8 @@ pub mod parse_worker { // `bake_types::Framework.built_in_modules` value variant carrying `&[u8]` (vs // `Box<[u8]>` here) and `resolver.caches.fs.read_file_with_allocator` shape. - // PORT NOTE: `transpiler`/`resolver` are raw `*mut` (Zig `*Transpiler` / - // `*Resolver`). Callers pass `resolver = &mut (*transpiler).resolver`; taking + // `transpiler`/`resolver` are raw `*mut`. + // Callers pass `resolver = &mut (*transpiler).resolver`; taking // `&mut Transpiler` + `&mut Resolver` would be aliased-`&mut` UB. We only // touch the disjoint `(*transpiler).fs` and `(*resolver).caches.fs` fields. fn get_code_for_parse_task_without_plugins( @@ -1478,7 +1452,7 @@ pub mod parse_worker { read_arena, ) { Ok(e) => { - // PORT NOTE: `bun_resolver::cache::Entry` ↔ `crate::cache::Entry` + // `bun_resolver::cache::Entry` ↔ `crate::cache::Entry` // are structurally identical twins; convert // by-variant so ownership of `Owned(Vec)` transfers. use bun_resolver::cache::Contents as RC; @@ -1501,7 +1475,12 @@ pub mod parse_worker { } Err(e) => { let source = Source::init_empty_file( - // TODO(port): zig duped via log.msgs.arena + // `file_path.text` + // borrows either the process-lifetime DirnameStore + // pool (resolver paths) or, after the + // `BuiltInModule::Import` reassignment above, the + // framework-owned `built_in_modules` storage held by + // the BundleV2 — both outlive the log's consumption. file_path.text, ); if e == err!("ENOENT") || e == err!("FileNotFound") { @@ -1550,7 +1529,7 @@ pub mod parse_worker { // `dispatch::PluginVTable` slot). Also calls the gated // `get_code_for_parse_task_without_plugins`. - // PORT NOTE: `transpiler`/`resolver` are raw `*mut` — see + // `transpiler`/`resolver` are raw `*mut` — see // `get_code_for_parse_task_without_plugins`. #[allow(clippy::too_many_arguments)] fn get_code_for_parse_task<'b>( @@ -1618,7 +1597,7 @@ pub mod parse_worker { pub struct OnBeforeParsePlugin<'a, 'b: 'a> { task: &'a mut ParseTask, log: &'a mut Log, - // PORT NOTE: raw `*mut` (Zig `*Transpiler` / `*Resolver`). Callers pass + // raw `*mut`. Callers pass // `resolver = &mut (*transpiler).resolver`; storing `&'a mut Transpiler<'b>` // alongside `&'a mut Resolver<'b>` would be aliased-`&mut` UB. The data // lifetime `'b` is retained on the pointee for variance only. @@ -1628,14 +1607,14 @@ pub mod parse_worker { file_path: &'a mut Fs::Path<'b>, loader: &'a mut Loader, deferred_error: Option, - // Zig `*i32`. `fetch_source_code` and `OnBeforeParsePlugin__isDone` re-enter + // `fetch_source_code` and `OnBeforeParsePlugin__isDone` re-enter // via FFI while the outer `run` call has already handed this same i32 to // C++, so a `&'a mut i32` here would be aliased-`&mut` UB. `Cell` is // `repr(transparent)` over `UnsafeCell`; FFI receives `Cell::as_ptr()` // (a real `*mut i32`) and Rust callers use safe `.get()/.set()`. should_continue_running: &'a core::cell::Cell, - // Raw pointer (Zig: `?*OnBeforeParseResult`). Must stay raw — the pointee + // Raw pointer. Must stay raw — the pointee // is `OnBeforeParseResultWrapper.result`, and `get_wrapper` walks back to // the parent via offset_of; a `&mut` here would (a) shrink provenance to // the inner field and (b) alias with any `&`/`&mut` to the wrapper. @@ -1712,11 +1691,10 @@ pub mod parse_worker { } } - // Restore the Zig comptime asserts (`ParseTask.zig:808-818`) the port dropped. // These structs are passed by-pointer to **third-party** native plugins via // `packages/bun-native-bundler-plugin-api/bundler_plugin.h`, so layout drift // is a silent ABI break for every plugin in the wild. Literals are the 64-bit - // C layout; TODO(port): replace with codegen-probed constants. + // C layout from `bundler_plugin.h`. bun_core::assert_ffi_layout!( OnBeforeParseArguments, 64, 8; struct_size @ 0, context @ 8, path_ptr @ 16, path_len @ 24, @@ -1777,14 +1755,12 @@ pub mod parse_worker { } pub(crate) fn append(&self, log: &mut Log, namespace: &'static [u8]) { - // Zig (ParseTask.zig:874-884) passes `this.path()` through and dupes - // `source_line_text` via `log.msgs.arena`. `Location.{file,line_text}` + // `Location.{file,line_text}` // are `&'static [u8]` here; `Log::dupe` copies into Log-owned storage // (freed when the Log drops) and returns a lifetime-erased borrow — // the "alloc-dupe into the log arena" pattern. We dupe `path` too: - // Zig stored it unduped (a raw slice into C-plugin memory that may be - // freed after `log_fn` returns — a latent UAF in the spec); duping is - // strictly safer and matches the intent. + // a raw slice into C-plugin memory may be + // freed after `log_fn` returns, so duping is required. let source_line_text = self.source_line_text(); let file = log.dupe(self.path()); let line_text = if !source_line_text.is_empty() { @@ -1854,9 +1830,8 @@ pub mod parse_worker { pub loader: Loader, #[cfg(debug_assertions)] pub check: u32, // Value to ensure OnBeforeParseResult is wrapped in this struct - // TODO(port): zig used `if (debug) u32 else u0`; in release this field - // must be zero-sized to keep extern layout matching headers. Verify - // with static_assert against bun.c. + // (the `cfg(debug_assertions)` gate + // above removes the field entirely in release.) pub result: OnBeforeParseResult, } @@ -1950,10 +1925,7 @@ pub mod parse_worker { return 1; } }; - // PORT NOTE: in Zig (`.zig:953-975`) `entry.contents` is a slice into - // the worker arena (`this.arena`) with no destructor, so storing - // `entry.contents.ptr` and letting `entry` go out of scope is sound. - // In Rust `Contents::Owned(Vec)` (the file-read path — see + // `Contents::Owned(Vec)` (the file-read path — see // `.rs:1287` / `resolver/lib.rs:2285`) frees on drop, which would // leave `result.source_ptr` / `wrapper.original_source` dangling for // the native plugin and `OnBeforeParsePlugin::run` to read through. @@ -2021,8 +1993,8 @@ pub mod parse_worker { pub(crate) unsafe extern "C" fn OnBeforeParsePlugin__isDone( this: *mut OnBeforeParsePlugin<'_, '_>, ) -> i32 { - // SAFETY: called from C++ with valid ptr. Read via raw pointers (mirrors - // Zig `@fieldParentPtr`) — `wrapper.result` aliases `*result`, so forming + // SAFETY: called from C++ with valid ptr. Read via raw pointers + // — `wrapper.result` aliases `*result`, so forming // overlapping references would be UB, and a `&mut`-derived `*mut` would // lack provenance over the enclosing wrapper. unsafe { @@ -2055,7 +2027,6 @@ pub mod parse_worker { impl<'a, 'b: 'a> OnBeforeParsePlugin<'a, 'b> { pub fn run( &mut self, - // TODO(port): jsc::api arrives from move-in (TYPE_ONLY → bundler) plugin: &bundler::JSBundlerPlugin, from_plugin: &mut bool, ) -> core::result::Result { @@ -2163,7 +2134,7 @@ pub mod parse_worker { // a second `&mut` via `&mut *args.context` while `&mut self` // is live would be aliased-`&mut` UB. self.log.errors += 1; - let _ = self.log.add_msg(msg); // logger OOM-only (Zig: catch unreachable) + let _ = self.log.add_msg(msg); // logger OOM-only return Err(err!("InvalidNativePlugin")); } @@ -2177,7 +2148,7 @@ pub mod parse_worker { if !wrapper.result.source_ptr.is_null() { let ptr = wrapper.result.source_ptr; - // PORT NOTE: `ExternalFreeFunction.function` is `Option`; + // `ExternalFreeFunction.function` is `Option`; // `OnBeforeParseResult.free_user_context` is `Option` (safe ABI per // the C header). Coerce safe→unsafe via cast. let free_fn = wrapper @@ -2251,14 +2222,14 @@ pub mod parse_worker { // `has_created` ⇒ `data`/`transpiler` were initialized in `create()`. let data = this.data.as_mut().expect("Worker.data set in create()"); - // PORT NOTE: `resolver` is a field of `*transpiler` (Zig - // `&transpiler.resolver`). Hold both as raw `*mut` and never materialize + // `resolver` is a field of + // `*transpiler`. Hold both as raw `*mut` and never materialize // `&mut Transpiler` while `resolver` is live — the callee chain takes raw // pointers and reborrows disjoint fields only. // SAFETY: `data.transpiler` is initialized (see above) and pinned for the // bundle pass. let transpiler: *mut Transpiler<'static> = &raw mut data.transpiler; - // PORT NOTE: errdefer transpiler.resetStore() — reshaped: call on the err + // errdefer transpiler.resetStore() — reshaped: call on the err // path explicitly (scopeguard would alias `transpiler` access below). // SAFETY: `transpiler` is live; `resolver` projects a field of it. let resolver: *mut Resolver = unsafe { core::ptr::addr_of_mut!((*transpiler).resolver) }; @@ -2307,7 +2278,7 @@ pub mod parse_worker { log: &mut Log, entry: &mut CacheEntry, ) -> core::result::Result { - // PORT NOTE: reshaped for borrowck — `transpiler_for_target` borrows `this` + // reshaped for borrowck — `transpiler_for_target` borrows `this` // mutably; we may need to call it again below (server-components branch), // so hold it as a raw pointer and reborrow per use site. // @@ -2317,8 +2288,8 @@ pub mod parse_worker { // directly — that retag of the parent `&mut` pops the SharedRW tag chain // backing the first `transpiler` (and the `resolver` field-projection // derived from it), making the later `(*resolver)` derefs in `get_ast` UB. - // Zig (.zig:1124, .zig:1189) rebinds `transpiler` while keeping `resolver` - // pointing into the original — valid in Zig (no aliasing model); in Rust + // `transpiler` may be rebound below while `resolver` keeps + // pointing into the original; // both calls' `&mut self` must be children of the same raw so neither pops // the other's tag chain. let worker_raw: *mut crate::Worker = this; @@ -2331,14 +2302,14 @@ pub mod parse_worker { // SAFETY: `worker_raw` just derived from the live `this: &mut Worker`. let mut transpiler: *mut Transpiler<'static> = std::ptr::from_mut(unsafe { (*worker_raw).transpiler_for_target(task.known_target) }); - // PORT NOTE: Zig errdefers (`transpiler.resetStore()` .zig:1123 and - // `if (.fd) entry.deinit(arena)` .zig:1148) are reshaped into the + // Error-path cleanup (`transpiler.resetStore()` and + // `if (.fd) entry.deinit(arena)`) is reshaped into the // explicit `match ast_result { Err(e) => ... }` cleanup below — scopeguard // would alias the `&mut Transpiler` / `&mut CacheEntry` borrows that // follow. There are no other fallible `?` between here and that match. - // PORT NOTE: `resolver` is a field of `*transpiler` (Zig - // `&transpiler.resolver`). Keep raw — never materialize `&mut Transpiler` - // while a `&mut` derived from `resolver` is live. Per Zig, `resolver` is + // `resolver` is a field of + // `*transpiler`. Keep raw — never materialize `&mut Transpiler` + // while a `&mut` derived from `resolver` is live. `resolver` is // bound *before* the possible `transpiler` reassignment below and stays // pointing into the original target's transpiler. // SAFETY: `transpiler` just derived from a live `&mut`. @@ -2417,10 +2388,10 @@ pub mod parse_worker { { // separate_ssr_graph makes boundaries switch to client because the server file uses that generated file as input. // this is not done when there is one server graph because it is easier for plugins to deal with. - // SAFETY: route through `worker_raw` (see top-of-function PORT NOTE) + // SAFETY: route through `worker_raw` (see top-of-function note) // so this call's `&mut self` is a child of the same raw and does not // pop the SharedRW tag backing `resolver` (which still points into the - // original target's transpiler per Zig .zig:1189). + // original target's transpiler). transpiler = std::ptr::from_mut(unsafe { (*worker_raw).transpiler_for_target(options::Target::Browser) }); @@ -2432,7 +2403,7 @@ pub mod parse_worker { // Allocated in the worker arena so `js_parser::new_lazy_export_ast`'s // `&'bump Source` parameter is satisfied (`bump` is the same arena). let source: &'static Source = bump.alloc(Source { - // PORT NOTE: `Source.path` is `bun_paths::fs::Path<'static>`, distinct from + // `Source.path` is `bun_paths::fs::Path<'static>`, distinct from // `bun_resolver::fs::Path` (TYPE_ONLY mirror). Construct // field-by-field across the type boundary. path: bun_paths::fs::Path { @@ -2443,7 +2414,7 @@ pub mod parse_worker { is_symlink: file_path.is_symlink, }, index: bun_ast::Index(task.source_index.get()), - // PORT NOTE: `entry.contents` is owned by `task.stage` (written back by + // `entry.contents` is owned by `task.stage` (written back by // the caller after parse — see `ParseTask::run`). `Source` is stored in // `Success` which lives no longer than the `ParseTask` itself, so this // borrow is sound. Routed through the audited `StoreStr` arena-erasure @@ -2481,7 +2452,7 @@ pub mod parse_worker { // D042: `crate::options::jsx::Pragma` IS `bun_js_parser::options::JSX::Pragma` // (both re-export `bun_options_types::jsx::Pragma`). `to_parser_jsx_pragma` // applies the `_None → Automatic` runtime fold the old `From` bridge did so - // parser-side `== Automatic` checks keep their semantics (.zig:1207). + // parser-side `== Automatic` checks keep their semantics. let mut opts = ParserOptions::init( crate::transpiler::to_parser_jsx_pragma(task.jsx.clone()), loader, @@ -2496,8 +2467,8 @@ pub mod parse_worker { // `Transpiler.macro_context` is `Option` // (same nominal type as `ParserOptions.macro_context`'s pointee). Reborrow // through the raw `*mut Transpiler` so the `&mut MacroContext` is disjoint - // from `topts` (which borrows `(*transpiler).options`). `.unwrap()` mirrors - // Zig `transpiler.macro_context.?` — caller (`BundleV2::init`) guarantees + // from `topts` (which borrows `(*transpiler).options`). `.unwrap()` is + // sound — caller (`BundleV2::init`) guarantees // it is set before any ParseTask runs. // SAFETY: `transpiler` is live; `macro_context` is a disjoint field. // `'static` erasure: the context outlives the parse. @@ -2517,8 +2488,8 @@ pub mod parse_worker { opts.features.trim_unused_imports = loader.is_typescript() || topts.trim_unused_imports.unwrap_or(false); opts.features.inlining = topts.minify_syntax; - // TODO(port): TYPE_ONLY divergence — `bun_options_types::Format` vs - // `bun_js_parser::options::Format`. Map by discriminant. + // `bun_options_types::Format` and `bun_js_parser::options::Format` are + // distinct enums; map explicitly. opts.output_format = match output_format { options::Format::Esm => js_parser::options::Format::Esm, options::Format::Cjs => js_parser::options::Format::Cjs, @@ -2536,9 +2507,9 @@ pub mod parse_worker { opts.features.standard_decorators = !loader.is_typescript() || !(task.experimental_decorators || task.emit_decorator_metadata); opts.features.unwrap_commonjs_packages = topts.unwrap_commonjs_packages; - // PORT NOTE: Zig stores a `*const StringSet` (shared); Rust models it as + // Modeled as // `Option>` on both sides, so we deep-clone (small — - // CLI-supplied flag set). PERF(port): retype + // CLI-supplied flag set). PERF: retype // `RuntimeFeatures.bundler_feature_flags` to `Option<&'a StringSet>` so // this clone disappears. opts.features.bundler_feature_flags = topts @@ -2586,7 +2557,7 @@ pub mod parse_worker { // `transpiler.options.framework: Option<&bake_types::Framework>` // vs `opts.framework: Option<&js_parser::options::Framework>` — both // TYPE_ONLY mirrors of `bake.Framework`. Project the fields the parser - // reads (Parser.zig:1415,1433) into the parser-side mirror and bump-alloc + // reads into the parser-side mirror and bump-alloc // so `opts` can borrow it. opts.framework = topts.framework.map(|f| { // `Framework` is bump-allocated below, so `Drop` never runs — use arena-owned slices. @@ -2677,12 +2648,10 @@ pub mod parse_worker { } else { get_empty_ast::(log, transpiler, opts, bump, source) }; - // PERF(port): Zig used `switch (bool) { inline else => |as_undefined| ... }` - // to monomorphize. Expanded to if/else. let mut ast = match ast_result { Ok(a) => a, Err(e) => { - // Zig errdefers (.zig:1123, .zig:1148): reset the AST store + // Error-path cleanup: reset the AST store // unconditionally, and free the owned `entry.contents` only when // it was sourced from `.fd` (the `.contents` variant is borrowed — // freeing it would double-free in `BundleV2.deinit`). @@ -2719,7 +2688,6 @@ pub mod parse_worker { ast, source: source.clone(), log: core::mem::take(log), - // PORT NOTE: Zig returned `log.*` by value; here we take ownership. use_directive, unique_key_for_additional_file: unique_key_for_additional_file.key, side_effects: task.side_effects, @@ -2754,7 +2722,7 @@ pub mod parse_worker { // SAFETY: ctx backref valid for the bundle pass (outlives this task). let ctx = unsafe { this.ctx() }; let worker: &mut crate::Worker = crate::Worker::get(ctx); - // PORT NOTE: `defer worker.unget()` — handled at function exit (scopeguard + // `defer worker.unget()` — handled at function exit (scopeguard // would alias the `&mut worker` borrows below). scoped_log!( ParseTask, @@ -2801,9 +2769,9 @@ pub mod parse_worker { } } - // PORT NOTE: reshaped for borrowck — `this` and `this.stage.needs_parse` - // both borrowed mutably. Zig (.zig:1369) passes `&this.stage.needs_parse` - // in-place so the entry's `Contents::Owned` buffer survives in + // reshaped for borrowck — `this` and `this.stage.needs_parse` + // both borrowed mutably. The entry must live + // in-place so its `Contents::Owned` buffer survives in // `task.stage` for the bundle's lifetime (Success.source.contents // borrows it via the arena-erased `StoreStr` path). Take it out, parse, then *write it // back* on every path before `break 'value` so dropping the local @@ -2852,10 +2820,9 @@ pub mod parse_worker { let result = Box::new(Result { ctx: this.ctx.expect("ParseTask.ctx unset"), - // Zig `.task = .{}` (.zig:1407) — default-init, NOT `undefined`. task: EventLoop::Task::default(), value, - // PORT NOTE: `ExternalFreeFunction` is POD in Zig (copied); Rust port + // `ExternalFreeFunction` // doesn't derive `Copy`, so move it out (task is consumed here). external: core::mem::take(&mut this.external_free_function), watcher_data: match this.contents_or_fd { @@ -2871,11 +2838,10 @@ pub mod parse_worker { // `ParseTask` is arena-owned (no Drop); `jsx` may hold owned slices from tsconfig. drop(core::mem::take(&mut this.jsx)); - // Zig matched `worker.ctx.loop().*` on `AnyEventLoop::{js, mini}`. // `worker.ctx` is a `BackRef` (safe `Deref`); the BACKREF deref // of `linker.r#loop` is centralised in `LinkerContext::any_loop_mut`. // - // Zig `worker.ctx.loop().*` is non-optional (.zig:1416) — `BundleV2::init` + // The loop is effectively non-optional — `BundleV2::init` // always sets `linker.r#loop` before scheduling any ParseTask. Running // `on_complete` inline on the worker thread would violate // `BundleV2::on_parse_task_complete`'s threading contract (it mutates the @@ -2908,7 +2874,7 @@ pub mod parse_worker { } } } - // Zig: `defer worker.unget()` — runs at function exit, i.e. after enqueue. + // Runs at function exit, i.e. after enqueue. worker.unget(); } @@ -2924,19 +2890,17 @@ pub mod parse_worker { fn on_complete_mini(result: *mut Result, ctx: *mut BundleV2<'static>) { // SAFETY: callback contract — `result` was heap-allocated above; `ctx` is - // the BACKREF stashed in `result.ctx` (Zig passed `BundleV2` as ParentContext). + // the BACKREF stashed in `result.ctx`. BundleV2::on_parse_task_complete(unsafe { &mut *result }, unsafe { &mut *ctx }); // SAFETY: `result` is uniquely owned (callback contract). drop_result_owned_fields(unsafe { &mut *result }); - // Zig: `defer bun.default_allocator.destroy(parse_result)` (bundle_v2.zig). - // Zig's `destroy` is *struct-only* (no field deinit). 954e9ccb mapped this - // to `drop(heap::take(result))`, but that runs full Drop glue: + // `drop(heap::take(result))` would run full Drop glue: // `on_parse_task_complete` SWAPS `result.value.Success.source` with the // graph's placeholder and moves `result.ast` out, so post-swap // `result.value` holds the *placeholder* `Source` whose // `contents: Cow::Borrowed` may alias plugin-/loader-provided bytes the // graph's swapped-in Source still references (asan use-after-poison at - // process_files_to_copy:4241 in bundler_loader/_plugin tests). Match Zig: + // process_files_to_copy:4241 in bundler_loader/_plugin tests). So: // dealloc the box without running Drop. // SAFETY: `result` came from `bun_core::heap::into_raw(Box)` // above; uniquely owned. Dealloc with the same layout, no field Drop. @@ -2973,5 +2937,3 @@ pub use parse_worker::{FileLoaderHash, OnBeforeParsePlugin, get_runtime_source, // ─────────────────────────────────────────────────────────────────────────── pub use crate::DeferredBatchTask::DeferredBatchTask; - -// ported from: src/bundler/ParseTask.zig diff --git a/src/bundler/PathToSourceIndexMap.rs b/src/bundler/PathToSourceIndexMap.rs index e8c04ddf2ce..626c65b08b6 100644 --- a/src/bundler/PathToSourceIndexMap.rs +++ b/src/bundler/PathToSourceIndexMap.rs @@ -28,7 +28,7 @@ pub struct PathToSourceIndexMap { pub type Map = StringHashMap; -/// Mirrors Zig's `Map.GetOrPutResult` — std `HashMap::entry` doesn't expose +/// std `HashMap::entry` doesn't expose /// `found_existing` + value-ptr together, so we hand-roll a thin shim. pub(crate) type GetOrPutResult<'a> = bun_collections::string_hash_map::GetOrPutResult<'a, IndexInt>; @@ -49,11 +49,10 @@ impl PathToSourceIndexMap { self.put(path.path_text(), value) } - // Takes `&[u8]` (not `impl AsRef<[u8]>`) to mirror Zig's `text: []const u8` - // and to avoid E0283 inference ambiguity at `.into()` call sites in bundle_v2. + // Takes `&[u8]` (not `impl AsRef<[u8]>`) + // to avoid E0283 inference ambiguity at `.into()` call sites in bundle_v2. pub fn put(&mut self, text: &[u8], value: IndexInt) -> Result<(), bun_alloc::AllocError> { - // PERF(port): Zig used StringHashMapUnmanaged with arena-borrowed keys (no copy); - // bun_collections::StringHashMap is keyed by `Box<[u8]>`, so we dupe here. + // PERF: bun_collections::StringHashMap is keyed by `Box<[u8]>`, so we dupe here. // Revisit once StringHashMap gains a borrowed-key variant. self.map.put(text, value) } @@ -69,7 +68,7 @@ impl PathToSourceIndexMap { &mut self, text: impl AsRef<[u8]>, ) -> Result, bun_alloc::AllocError> { - // PERF(port): see note in `put` re: key duplication. + // PERF: see note in `put` re: key duplication. self.map.get_or_put(text.as_ref()) } @@ -81,5 +80,3 @@ impl PathToSourceIndexMap { self.remove(path.path_text()) } } - -// ported from: src/bundler/PathToSourceIndexMap.zig diff --git a/src/bundler/ServerComponentParseTask.rs b/src/bundler/ServerComponentParseTask.rs index 72a46ff7772..e8a51b602cd 100644 --- a/src/bundler/ServerComponentParseTask.rs +++ b/src/bundler/ServerComponentParseTask.rs @@ -32,7 +32,7 @@ pub use crate::parse_task::ParseTask; pub(crate) struct ServerComponentParseTask { pub task: ThreadPoolTask, pub data: Data, - // BACKREF (LIFETIMES.tsv) — Zig `*BundleV2` is mutable; written through in `on_complete`. + // BACKREF (LIFETIMES.tsv) — written through in `on_complete`. // `ParentRef` (write-provenance via `NonNull::from(&mut self)` at construction) // so deref sites are safe; `None` only for the FRU `Default` placeholder. pub ctx: Option>>, @@ -56,7 +56,7 @@ pub struct ReferenceProxy { } pub struct ClientEntryWrapper { - // TODO(port): lifetime — Zig `[]const u8` borrowed from caller; never freed in this file. + // Owned copy. pub path: Box<[u8]>, } @@ -82,7 +82,7 @@ fn task_callback_wrap(thread_pool_task: *mut ThreadPoolTask) { .ctx .expect("ServerComponentParseTask.ctx set at enqueue"); let worker = Worker::get(ctx.get()); - // PORT NOTE: `defer worker.unget()` — handled at end of fn (no early returns). + // `defer worker.unget()` — handled at end of fn (no early returns). let mut log = Log::new(); // SAFETY: `worker.arena` is set in `Worker::create` to point at the @@ -99,7 +99,7 @@ fn task_callback_wrap(thread_pool_task: *mut ThreadPoolTask) { // `ctx` already a `ParentRef` with write provenance // (constructed from `NonNull::from(&mut self)` in `bundle_v2.rs`). ctx, - // SAFETY: Zig leaves `.task = undefined`; consumer overwrites before read. + // Placeholder; consumer overwrites before read. task: Default::default(), value, external: ExternalFreeFunction::NONE, @@ -107,11 +107,10 @@ fn task_callback_wrap(thread_pool_task: *mut ThreadPoolTask) { }); let result = bun_core::heap::into_raw(result); - // Zig matched `worker.ctx.loop().*` on `AnyEventLoop::{js, mini}`. // `worker.ctx` is a `BackRef` (safe `Deref`); the BACKREF deref // of `linker.r#loop` is centralised in `LinkerContext::any_loop_mut`. // - // Zig `worker.ctx.loop().*` is non-optional (.zig:52) — `BundleV2::init` + // The loop is effectively non-optional — `BundleV2::init` // always sets `linker.r#loop` before scheduling any ServerComponentParseTask. // Running `on_complete` inline on the worker thread would violate // `BundleV2::on_parse_task_complete`'s threading contract (it mutates the @@ -144,7 +143,7 @@ fn task_callback_wrap(thread_pool_task: *mut ThreadPoolTask) { } } } - // Zig: `defer worker.unget()` — runs at function exit, i.e. after enqueue. + // Runs at function exit, i.e. after enqueue. worker.unget(); } @@ -165,8 +164,8 @@ fn task_callback( .ctx .as_deref() .expect("ServerComponentParseTask.ctx set at enqueue"); - // PORT NOTE: `Source` is not `Clone`; the original is consumed here - // (Zig copied by value). Take it up-front so `ab`'s borrow of it ends + // `Source` is not `Clone`; the original is consumed here. + // Take it up-front so `ab`'s borrow of it ends // (via NLL) before we move it into `Success`. let source = core::mem::take(&mut task.source); let mut ab = AstBuilder::init(bump, &source, ctx.transpiler().options.hot_module_reloading)?; @@ -185,8 +184,7 @@ fn task_callback( let hmr_api_ref = ab.hmr_api_ref; let mut bundled_ast: JSAst = ab.to_bundled_ast(target)?; - // `wrapper_ref` is used to hold the HMR api ref (see comment in - // `src/ast/Ast.zig`) + // `wrapper_ref` is used to hold the HMR api ref. bundled_ast.wrapper_ref = hmr_api_ref; Ok(Success { @@ -203,8 +201,7 @@ fn task_callback( } impl Default for ServerComponentParseTask { - /// Mirrors Zig's `task: ThreadPoolLib.Task = .{ .callback = &taskCallbackWrap }` - /// default-field-value. Callers (`bundle_v2.rs`) supply `data`/`ctx`/`source` + /// Callers (`bundle_v2.rs`) supply `data`/`ctx`/`source` /// via FRU and rely on this for the intrusive `task` link. fn default() -> Self { Self { @@ -274,7 +271,6 @@ fn generate_client_reference_proxy( if ctx.transpiler().options.has_dev_server() { b.bump.alloc_slice_copy(data.other_source.path.pretty) } else { - // PERF(port): was arena allocPrint — profile if it shows up on a hot path let mut buf = bun_alloc::ArenaString::new_in(b.bump); write!( &mut buf, @@ -297,7 +293,6 @@ fn generate_client_reference_proxy( // This error message is taken from // https://github.com/facebook/react/blob/c5b9375767e2c4102d7e5559d383523736f1c902/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js#L323-L354 let err_msg_string: &[u8] = { - // PERF(port): was arena allocPrint — profile if it shows up on a hot path let mut buf = bun_alloc::ArenaString::new_in(b.bump); if is_default { write!( @@ -394,5 +389,3 @@ fn generate_client_reference_proxy( Ok(()) } - -// ported from: src/bundler/ServerComponentParseTask.zig diff --git a/src/bundler/ThreadPool.rs b/src/bundler/ThreadPool.rs index 236f17412b9..4db4d57ad31 100644 --- a/src/bundler/ThreadPool.rs +++ b/src/bundler/ThreadPool.rs @@ -1,4 +1,4 @@ -//! Port of `src/bundler/ThreadPool.zig` — the bundler-side worker pool that +//! The bundler-side worker pool that //! wraps `bun_threading::thread_pool::ThreadPool` and owns the per-thread //! [`Worker`] state (mimalloc arena, per-thread `Transpiler` clone, AST store). //! @@ -11,7 +11,7 @@ use core::mem::{ManuallyDrop, MaybeUninit}; use core::ptr::{self, NonNull}; use core::sync::atomic::{AtomicUsize, Ordering}; -use bun_alloc::Arena as ThreadLocalArena; // Zig: bun.allocators.MimallocArena +use bun_alloc::Arena as ThreadLocalArena; use bun_collections::{ArrayHashMap, MapEntry}; use bun_core::{self, env_var, output as Output}; use bun_sys::Fd; @@ -19,11 +19,11 @@ use bun_threading::{Mutex, thread_pool as ThreadPoolLib}; use crate::cache::{Contents, Entry as CacheEntry, ExternalFreeFunction}; use crate::linker_context_mod::StmtList; -// PORT NOTE: `crate::options::Target` is the lower-tier `bun_options_types` +// `crate::options::Target` is the lower-tier `bun_options_types` // enum (re-exported for downstream crates); `BundleOptions.target` is the // file-backed `options_impl::Target`. Compare against the latter so -// `primary.options.target == target` type-checks. TODO(refactor): collapse -// the two enums into one (see lib.rs `pub mod options` shadow note). +// `primary.options.target == target` type-checks. (The two enums could be +// collapsed into one; see lib.rs `pub mod options` shadow note.) use crate::BundleV2; use crate::options_impl::Target; use crate::parse_task::{ContentsOrFd, ParseTask, ParseTaskStage}; @@ -42,19 +42,18 @@ pub struct ThreadPool { /// On Windows, this seemed to be a small performance improvement. /// On Linux, this was a performance regression. /// In some benchmarks on macOS, this yielded up to a 60% performance improvement in microbenchmarks that load ~10,000 files. - // PORT NOTE: Zig left this `undefined` when `!uses_io_pool()`; `Option` makes - // that explicit. `ParentRef` (not raw `NonNull`) because the pointee is the + // `None` when `!uses_io_pool()`. + // `ParentRef` (not raw `NonNull`) because the pointee is the // module-static `io_thread_pool::THREAD_POOL`, live while `ref_count > 0`, // and all `ThreadPoolLib` driver methods (`schedule`, `warm`, // `wake_for_idle_events`) take `&self` — so the safe `Deref` projection is // sufficient and the per-read `unsafe { p.as_ref() }` disappears. pub io_pool: Option>, - // TODO(port): lifetime — TSV class UNKNOWN. Conditionally owned via - // `worker_pool_is_owned`; kept raw so callers (bundle_v2.rs draft) can - // dereference for `wake_for_idle_events()` without a borrow on `ThreadPool`. + // Conditionally owned via `worker_pool_is_owned`; kept raw so callers + // (bundle_v2.rs) can dereference for `wake_for_idle_events()` without a + // borrow on `ThreadPool`. pub worker_pool: *mut ThreadPoolLib::ThreadPool, pub worker_pool_is_owned: bool, - // PORT NOTE: Zig had `workers_assignments` + sibling `workers_assignments_lock`. // Per PORTING.md §Concurrency ("Mutex owns T"), the lock is folded into // the field so `get_worker` can take `&self` — `Worker::get` is entered // concurrently from arbitrary worker-pool threads, and a `&mut self` here @@ -72,8 +71,9 @@ pub struct ThreadPool { // SAFETY: `ThreadPool` is shared across worker threads; the only mutated // field (`workers_assignments`) is guarded by its `bun_threading::Guarded`, and -// the raw-pointer fields are externally synchronized exactly as in the Zig -// source. +// the raw-pointer fields are set during init, before the pool is shared with +// worker threads, and only read thereafter (mutation in `deinit` happens on the +// owning thread after the workers are done). unsafe impl Send for ThreadPool {} // SAFETY: `&ThreadPool` is read concurrently from worker-pool threads via // `get_worker(&self)`; the only field mutated under `&self` is @@ -83,8 +83,7 @@ unsafe impl Sync for ThreadPool {} impl Default for ThreadPool { /// Placeholder so `bundle_v2` can `arena().alloc(ThreadPool::default())` - /// before overwriting with [`ThreadPool::init`]. Mirrors Zig's - /// `arena.create(ThreadPool)` which yields uninit memory. + /// before overwriting with [`ThreadPool::init`]. fn default() -> Self { Self { io_pool: None, @@ -107,9 +106,9 @@ mod io_thread_pool { bun_core::RacyCell::new(MaybeUninit::uninit()); /// Protects initialization and deinitialization of the IO thread pool. static MUTEX: Mutex = { - // PORT NOTE: `Mutex` derives `Default` but `Default::default()` isn't - // `const`. The Zig source used `bun.threading.Mutex{}` (zero-init); - // an all-zero `Mutex` is the documented unlocked state on every impl. + // `Mutex` derives `Default` but `Default::default()` isn't + // `const`. An all-zero `Mutex` is the documented unlocked state on + // every impl. // SAFETY: `Mutex` is `repr(Rust)` over an atomic / Futex word; zero is // the valid initial value (matches `#[derive(Default)]`). unsafe { bun_core::ffi::zeroed_unchecked() } @@ -161,9 +160,8 @@ mod io_thread_pool { // 2 means initialized and referenced by one `ThreadPool`. REF_COUNT.store(2, Ordering::Release); } else { - // PORT NOTE: Zig fell through to `return &thread_pool` without - // bumping the ref count here, which is a latent bug in the source - // (the racing acquirer's reference isn't counted). Mirrored. + // NOTE: a racing acquirer that reaches here does not bump the ref + // count — a latent under-count, preserved intentionally. } // Just initialized (or observed initialized) above. `UnsafeCell::get` never returns null. NonNull::new(THREAD_POOL.get().cast::()) @@ -200,15 +198,13 @@ mod io_thread_pool { unsafe { (*THREAD_POOL.get()).assume_init_drop(); } - // PORT NOTE: Zig source falls off the end of a `bool`-returning fn here - // (`thread_pool = undefined;` is the last statement). Assuming `true`. true } } impl ThreadPool { /// Inherent associated type so call sites that wrote - /// `ThreadPool::Worker::get(ctx)` (matching Zig's `ThreadPool.Worker`) + /// `ThreadPool::Worker::get(ctx)` /// resolve without a separate module path. pub type Worker = Worker; @@ -222,17 +218,13 @@ impl ThreadPool { // end-to-end. worker_pool: Option>, ) -> Result { - // PORT NOTE: Spec ThreadPool.zig:85 allocated via the bundle arena - // (`v2.arena().create`), so the `false` ownership flag was - // harmless — the arena reclaimed it. Here we `heap::alloc` (global + // The pool is `heap::alloc`'d (global // heap), so `deinit()` must `heap::take` it back; record ownership. let owned = worker_pool.is_none(); let pool: *mut ThreadPoolLib::ThreadPool = match worker_pool { Some(p) => p.as_ptr(), None => { let cpu_count = bun_core::get_thread_count(); - // PERF(port): was `v2.arena().create(ThreadPoolLib)` — - // using heap::alloc (global mimalloc). let pool = bun_core::heap::into_raw(Box::new(ThreadPoolLib::ThreadPool::init( ThreadPoolLib::Config { max_threads: u32::from(cpu_count), @@ -267,7 +259,7 @@ impl ThreadPool { } } - /// Explicit teardown — Zig callers spell `pool.deinit()` (no Drop on + /// Explicit teardown (no Drop on /// `ThreadPool` because `Graph.pool` is `NonNull` and the arena /// owns the storage). pub fn deinit(&mut self) { @@ -347,8 +339,7 @@ impl ThreadPool { let ContentsOrFd::Contents(contents) = parse_task.contents_or_fd else { unreachable!() }; - // PORT NOTE: Zig moved the `[]const u8` slice into the cache entry - // by value. `cache::Contents` has no borrowed-slice variant; the + // `cache::Contents` has no borrowed-slice variant; the // contract (see ParseTask.rs `run_with_source_code` defer) is that // `entry.deinit()` is *skipped* when `contents_or_fd == .contents`, // so an `External` provenance tag (no-op deinit) is the correct @@ -396,7 +387,7 @@ impl ThreadPool { } } - // PORT NOTE: takes `*mut` (Zig: `*ParseTask`) so callers can pass either a + // takes `*mut` so callers can pass either a // raw heap pointer (e.g. `load.parse_task`) or a `&mut` (auto-coerces). pub fn schedule(&self, parse_task: *mut ParseTask) { self.schedule_with_options(parse_task, false); @@ -406,9 +397,9 @@ impl ThreadPool { self.schedule_with_options(parse_task, true); } - // PORT NOTE: returns `&'static mut` — the `Worker` is `heap::alloc`'d + // returns `&'static mut` — the `Worker` is `heap::alloc`'d // below and lives until `Worker::deinit`; detaching from `&self` lets - // callers re-borrow `ThreadPool` while holding the worker (Zig: `*Worker`). + // callers re-borrow `ThreadPool` while holding the worker. // Takes `&self` (not `&mut`) because this is called concurrently from // worker-pool threads via `Worker::get`; mutation goes through the // `bun_threading::Guarded` on `workers_assignments`. @@ -417,9 +408,9 @@ impl ThreadPool { // pure `current_thread_id() → *mut Worker` re-read after first touch, so // every subsequent call from the same thread for the same pool is a TLS // load + pointer compare. The lock is only taken on first touch per - // `(thread, pool)` pair. Zig (ThreadPool.zig:180) takes the lock on every - // call; on a 19 K-module build that's ~100 K contended acquisitions, which - // perf attributes ~97 % of the build's futex traffic to. + // `(thread, pool)` pair. Taking the lock on every + // call would mean ~100 K contended acquisitions on a 19 K-module build — + // ~97 % of the build's futex traffic per perf. #[inline] pub fn get_worker(&self, id: ThreadId) -> &'static mut Worker { let (generation, worker) = TLS_WORKER.get(); @@ -507,17 +498,16 @@ static POOL_GENERATION: core::sync::atomic::AtomicU64 = core::sync::atomic::Atom /// `deinit_task`/`arena` fields are self-referential); never moved after /// `get_worker` boxes it. pub struct Worker { - /// Thread-local arena. `None` until [`Worker::create`] runs (Zig wrote - /// `undefined`); every read site is post-`has_created`. + /// Thread-local arena. `None` until [`Worker::create`] runs; + /// every read site is post-`has_created`. pub heap: Option, /// Thread-local memory arena /// All allocations are freed in `deinit` at the very end of bundling. - // PORT NOTE: self-referential borrow of `heap` — `BackRef` (not a real + // self-referential borrow of `heap` — `BackRef` (not a real // `&'self ThreadLocalArena`) so it can be reseated in `create()` without a // self-borrow and so call sites read it via safe `Deref` instead of - // open-coding a raw deref. Zig stored the `std.mem.Allocator` vtable; here - // it's just `&heap`. Dangling until `create()` runs; every read site is + // open-coding a raw deref. Dangling until `create()` runs; every read site is // post-`has_created`. pub arena: bun_ptr::BackRef, @@ -549,7 +539,7 @@ pub struct Worker { impl Worker { /// Reborrow the self-referential `arena` (= `&self.heap`) as a shared /// reference. `BackRef` field, so the deref is encapsulated in - /// [`bun_ptr::BackRef::get`]; see PORT NOTE on the field. + /// [`bun_ptr::BackRef::get`]; see note on the field. /// /// `arena` is set to `&self.heap` in [`Worker::create`] before any caller /// can observe the `Worker`, and is never dangling after that point. The @@ -571,13 +561,12 @@ impl Worker { } pub struct WorkerData { - // TODO(port): lifetime — TSV class ARENA (`&'arena mut bun_ast::Log`); kept - // raw because the arena is the sibling field `Worker.heap`. + // Kept raw because the pointee's arena + // is the sibling field `Worker.heap`, which Rust cannot express as a borrow. pub log: *mut bun_ast::Log, pub estimated_input_lines_of_code: usize, - // PORT NOTE: lifetime erased to `'static` — the inner `&'a Arena` borrows - // `Worker.heap`, which Rust can't express on a sibling field. Zig used - // `transpiler: Transpiler` with a copied `std.mem.Allocator`. + // lifetime erased to `'static` — the inner `&'a Arena` borrows + // `Worker.heap`, which Rust can't express on a sibling field. // // Owned (no `MaybeUninit`): `Transpiler::for_worker` deep-clones every // `Drop`-carrying field, so `WorkerData`'s drop (via @@ -620,8 +609,8 @@ impl Worker { // (the bundler thread running an inline parse). `deinit_soon` is // also called from the bundler thread (`deinit_without_freeing_arena` // iterates `workers_assignments`), so we are on the owning thread - // and can tear down synchronously. Zig spec (ThreadPool.zig:232) - // silently no-ops here, leaking the Worker — including its + // and can tear down synchronously. Skipping teardown here would + // leak the Worker — including its // `ast_memory_store` mi_heap (every `AstAlloc` buffer the inline // parse produced) and `data.transpiler` per `Bun.build()` call. // @@ -668,10 +657,9 @@ impl Worker { // and `create()` may overwrite it // via `*ast_memory_store = ...`. Dropped exactly once here, *outside* // the `has_created` guard so the default-constructed arena is freed - // even when `create()` never ran (Zig left it `undefined`; Rust does - // not). Ordered before `heap = None` in case the TODO(port) in - // `ASTMemoryAllocator::new` ever threads `arena_ref` (= `&self.heap`) - // through. + // even when `create()` never ran. + // Ordered before `heap = None` in case `ASTMemoryAllocator::new` + // ever threads `arena_ref` (= `&self.heap`) through. unsafe { ManuallyDrop::drop(&mut worker.ast_memory_store) }; if worker.has_created { worker.heap = None; @@ -683,9 +671,9 @@ impl Worker { unsafe { bun_core::heap::destroy(this) }; } - // PORT NOTE: returns `&'static mut` (detached) — the `Worker` is + // returns `&'static mut` (detached) — the `Worker` is // heap-pinned (heap::alloc in `get_worker`) and outlives any `ctx` - // borrow; Zig returned `*Worker`. Tying it to `ctx`'s lifetime would + // borrow. Tying it to `ctx`'s lifetime would // forbid the `worker` ↔ `ctx` re-borrows in `ParseTask::run_*`. pub fn get(ctx: &BundleV2<'_>) -> &'static mut Worker { // SAFETY: `ctx` is a BACKREF; `graph.pool` is a `NonNull` @@ -715,7 +703,7 @@ impl Worker { } fn create(&mut self, ctx: &BundleV2<'_>) { - // PORT NOTE: `bun_perf::trace` takes a generated `PerfEvent` enum, and + // `bun_perf::trace` takes a generated `PerfEvent` enum, and // the generator hasn't emitted `Bundler.Worker.create` yet (only // `_Stub`). Dropped to avoid mis-attributing the span. // let _trace = bun_perf::trace("Bundler.Worker.create"); @@ -734,7 +722,7 @@ impl Worker { let arena_ref: &'static ThreadLocalArena = unsafe { bun_ptr::detach_lifetime_ref(self.arena.get()) }; - // Zig: `.{ .arena = this.arena }` then `reset()`. The Rust + // The // ASTMemoryAllocator owns its bump arena internally and ignores the // passed fallback (see ASTMemoryAllocator::new doc). *self.ast_memory_store = bun_ast::ASTMemoryAllocator::new(arena_ref); @@ -742,8 +730,7 @@ impl Worker { let log: *mut bun_ast::Log = arena_ref.alloc(bun_ast::Log::init()); self.ctx = bun_ptr::BackRef::from(NonNull::from(ctx).cast::>()); - // PERF(port): was `bun.ArenaAllocator.init(this.arena)` — using a - // fresh Bump (no nested-arena type yet). + // Use a fresh Bump (no nested-arena type yet). self.temporary_arena = Some(bun_alloc::Arena::new()); self.stmt_list = Some(StmtList::init()); let data = self.data.insert(WorkerData { @@ -759,9 +746,9 @@ impl Worker { bun_core::scoped_log!(ThreadPool, "Worker.create()"); } - /// Build a per-worker `Transpiler` from `from` (Zig: `transpiler.* = from.*`). + /// Build a per-worker `Transpiler` from `from`. /// - /// PORT NOTE: reshaped for borrowck — associated fn (no `&mut self`) so + /// reshaped for borrowck — associated fn (no `&mut self`) so /// callers can borrow `self.data.log` disjointly. The returned value is a /// fully-owned `Transpiler` whose `Drop` is sound; `wire_after_move` must /// be called once it is at its final address. @@ -813,5 +800,3 @@ impl Worker { } } } - -// ported from: src/bundler/ThreadPool.zig diff --git a/src/bundler/analyze_transpiled_module.rs b/src/bundler/analyze_transpiled_module.rs index 8c5b6b30821..e01a50021bd 100644 --- a/src/bundler/analyze_transpiled_module.rs +++ b/src/bundler/analyze_transpiled_module.rs @@ -25,7 +25,7 @@ pub use bun_js_printer::analyze_transpiled_module::{ /// `bundler_jsc::analyze_jsc::to_js_module_record`. pub type RequestedModuleValue = FetchParameters; -/// Legacy name used by `linker_context::postProcessJSChunk` — the Zig side +/// Legacy name used by `linker_context::postProcessJSChunk` — the type was /// renamed `ImportAttributes` → `FetchParameters` but the bundler call site /// still spells `ImportAttributes::None`. pub(crate) type ImportAttributes = FetchParameters; @@ -34,14 +34,14 @@ pub(crate) type ImportAttributes = FetchParameters; // RecordKind // ────────────────────────────────────────────────────────────────────────── -/// Non-exhaustive `enum(u8)` in Zig — any byte value is representable, so model +/// Any byte value is representable, so model /// as a transparent newtype with associated consts (a `#[repr(u8)] enum` would /// be UB for unknown discriminants read out of the serialized buffer). #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq)] pub struct RecordKind(pub u8); // SAFETY: `#[repr(transparent)]` over `u8` — no padding, every bit pattern is -// a valid `u8` (Zig modeled this as a non-exhaustive `enum(u8)`). `Pod` lets +// a valid `u8`. `Pod` lets // `bytemuck::{cast_slice,try_cast_slice}` reinterpret byte buffers and the // printer-crate `#[repr(u8)]` enum into `&[RecordKind]` without `unsafe`. unsafe impl bytemuck::Zeroable for RecordKind {} @@ -116,19 +116,17 @@ bitflags::bitflags! { } impl Flags { - /// Zig: `Flags.contains_import_meta` packed-struct field. Exposed as a + /// Exposed as a /// method so downstream callers (e.g. `bundler_jsc::analyze_jsc`) can read /// the bit without depending on the bitflags const name. #[inline] pub const fn contains_import_meta(self) -> bool { self.contains(Flags::CONTAINS_IMPORT_META) } - /// Zig: `Flags.is_typescript` packed-struct field. #[inline] pub const fn is_typescript(self) -> bool { self.contains(Flags::IS_TYPESCRIPT) } - /// Zig: `Flags.has_tla` packed-struct field. #[inline] pub const fn has_tla(self) -> bool { self.contains(Flags::HAS_TLA) @@ -154,8 +152,7 @@ bun_core::named_error_set!(ModuleInfoError); /// Alignment: the on-disk format pads every multi-byte field to a 4-byte /// offset, and [`Self::create`] allocates the backing buffer with 4-byte /// alignment ([`MODULE_INFO_ALIGN`]), so every `RawSlice` here is properly -/// aligned for `T` and `.slice()` is sound. (Zig used `[]align(1) const T` -/// because its allocator didn't guarantee the base; we do instead.) +/// aligned for `T` and `.slice()` is sound. pub struct ModuleInfoDeserialized { pub strings_buf: bun_ptr::RawSlice, pub strings_lens: bun_ptr::RawSlice, @@ -232,9 +229,7 @@ impl ModuleInfoDeserialized { unsafe { match (*this).owner { Owner::ModuleInfo(mi) => { - // PORT NOTE: Zig recovered the parent via - // `@fieldParentPtr("_deserialized", self)`. The Rust port - // stores the `*mut ModuleInfo` directly because the printer + // The `*mut ModuleInfo` is stored directly because the printer // crate's `ModuleInfo` no longer embeds this struct. drop(bun_core::heap::take(mi)); drop(bun_core::heap::take(this)); @@ -342,14 +337,12 @@ impl ModuleInfoDeserialized { /// cache or standalone module graph). Returns `None` instead of panicking on /// corrupt/truncated data. pub fn create_from_cached_record(source: &[u8]) -> Option> { - // PORT NOTE: Zig matched on error.OutOfMemory → bun.outOfMemory(); in - // Rust, allocation failure aborts via the global arena, so only + // Allocation failure aborts via the global arena, so only // BadModuleInfo remains. Self::create(source).ok() } pub fn serialize(&self, writer: &mut impl bun_io::Write) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let record_kinds = self.record_kinds(); writer.write_all(&(record_kinds.len() as u32).to_le_bytes())?; writer.write_all(slice_as_bytes(record_kinds))?; @@ -438,9 +431,7 @@ unsafe fn free_aligned_dup(slice: *mut [u8]) { /// or its length is not a multiple of `size_of::()` (i.e. the format's /// internal padding was violated). /// -/// (Zig used `std.mem.bytesAsSlice` → `[]align(1) const T`; Rust has no -/// under-aligned reference type, so we guarantee alignment instead via -/// `bytemuck::try_cast_slice`, which checks both alignment and size.) +/// (`bytemuck::try_cast_slice` checks both alignment and size.) #[inline] fn bytes_as_slice(bytes: &[u8]) -> Result<&[T], ModuleInfoError> { bytemuck::try_cast_slice(bytes).map_err(|_| ModuleInfoError::BadModuleInfo) @@ -470,8 +461,7 @@ pub trait ModuleInfoExt { /// `this` must originate from `heap::alloc(ModuleInfo::create(..))`. unsafe fn destroy_raw(this: *mut ModuleInfo); /// Finalize and box the raw-pointer `ModuleInfoDeserialized` view, taking - /// ownership of `self`. Replaces the Zig pattern of writing into the - /// embedded `_deserialized` field and handing out a `&mut` to it. + /// ownership of `self`. fn into_deserialized(self: Box) -> Box; } @@ -482,14 +472,13 @@ impl ModuleInfoExt for ModuleInfo { drop(unsafe { bun_core::heap::take(this) }); } fn into_deserialized(mut self: Box) -> Box { - // PORT NOTE: Zig wrote a self-referential `_deserialized` view inside - // `ModuleInfo` during `finalize()`. The Rust printer-crate `ModuleInfo` - // exposes a borrowed `as_deserialized()` instead; here we materialise the + // The printer-crate `ModuleInfo` + // exposes a borrowed `as_deserialized()`; here we materialise the // raw-pointer FFI shape and tie its lifetime to the leaked `Box`. if !self.finalized { let _ = self.finalize(); } - // PORT NOTE: reshaped for borrowck — capture lifetime-erased `RawSlice` + // Reshaped for borrowck — capture lifetime-erased `RawSlice` // views before `heap::into_raw(self)` consumes the box. let (strings_buf, strings_lens, rm_keys, rm_values, rm_phases, buffer, record_kinds, flags); { @@ -533,7 +522,6 @@ impl ModuleInfoExt for ModuleInfo { // zig__renderDiff, zig__ModuleInfoDeserialized__toJSModuleRecord, and the // JSModuleRecord/IdentifierArray opaques: see bun_bundler_jsc::analyze_jsc -// (Zig `comptime { _ = @import }` force-reference dropped per porting guide.) #[unsafe(no_mangle)] pub(crate) extern "C" fn zig__ModuleInfo__destroy(info: *mut ModuleInfo) { @@ -558,8 +546,5 @@ pub(crate) extern "C" fn zig_log(msg: *const c_char) { let msg = unsafe { NonNull::new(msg.cast_mut()).unwrap_unchecked() }; // SAFETY: `msg` is non-null and points to a NUL-terminated C string per the contract above. let bytes = unsafe { bun_core::ffi::cstr(msg.as_ptr()) }.to_bytes(); - // Zig: `Output.errorWriter().print("{s}\n", .{bytes}) catch {}`. bun_core::Output::print_error(format_args!("{}\n", bstr::BStr::new(bytes))); } - -// ported from: src/bundler/analyze_transpiled_module.zig diff --git a/src/bundler/barrel_imports.rs b/src/bundler/barrel_imports.rs index c58fcf28a43..3f7c2f5efe7 100644 --- a/src/bundler/barrel_imports.rs +++ b/src/bundler/barrel_imports.rs @@ -52,7 +52,7 @@ impl RequestedExports { } } -// PORT NOTE: `original_alias` is stored as the raw arena `*const [u8]` (the +// `original_alias` is stored as the raw arena `*const [u8]` (the // `NamedImport.alias` representation) instead of a tied `&'a [u8]` so the BFS // loop can hold a `BarrelExportResolution` across a `&mut graph.ast` reborrow // without the borrow checker seeing an overlap. The pointee outlives the @@ -155,7 +155,6 @@ fn apply_barrel_optimization_impl( // Build the set of needed import_record_indices from already-requested // export names. Export * records are always needed. - // PERF(port): was stack-fallback (8192) — profile if hot. let mut needed_records: ArrayHashMap = ArrayHashMap::default(); for record_idx in ast.export_star_import_records.iter() { @@ -181,7 +180,7 @@ fn apply_barrel_optimization_impl( // handles the case where file A imports Alpha from the barrel (previous // build) and file B adds Beta (current build). Without this, Alpha would // be re-deferred because only B's requests are in requested_exports. - // PORT NOTE: `DevServerHandle` is `Copy`; copied out so the `&self` borrow + // `DevServerHandle` is `Copy`; copied out so the `&self` borrow // doesn't conflict with later `&mut this.requested_exports`. let dev_handle = this.dev_server_handle().copied(); if let Some(dev) = dev_handle { @@ -208,7 +207,6 @@ fn apply_barrel_optimization_impl( // import records that share a path with any needed record. if dev_handle.is_some() { // Collect paths of needed records. - // PERF(port): was stack-fallback (4096) — profile if hot. let mut needed_paths: StringArrayHashMap<()> = StringArrayHashMap::default(); for rec_idx in needed_records.keys() { @@ -238,7 +236,7 @@ fn apply_barrel_optimization_impl( // Mark unneeded named re-export records as is_unused. let mut has_deferrals = false; - // PORT NOTE: reshaped for borrowck — collect (ref_, import_record_index) + // Borrowck: collect (ref_, import_record_index) // pairs first so we can mutate `ast.import_records` without aliasing // `ast.named_exports`/`ast.named_imports`. for i in 0..ast.named_exports.count() { @@ -303,7 +301,7 @@ fn un_defer_record(import_records: &mut import_record::List, record_idx: u32) -> } /// BFS work queue item: un-defer an export from a barrel. -// PORT NOTE: 'a borrows arena-backed AST alias strings. +// `'a` borrows arena-backed AST alias strings. struct BarrelWorkItem<'a> { barrel_source_index: u32, alias: &'a [u8], @@ -370,10 +368,9 @@ pub(crate) fn schedule_barrel_deferred_imports( result_source_index: u32, result_ast_target: bun_ast::Target, ) -> Result { - // PORT NOTE: Zig passed `*ParseTask.Result.Success` and read `result.ast` - // after `graph.ast.set(idx, result.ast)` value-copied it. Rust *moves* - // `result.ast` into `graph.ast`, so this fn reads the just-written - // `graph.ast[result_source_index]` instead. Phase 1/2 only read + // `result.ast` is *moved* + // into `graph.ast` before this fn runs, so this fn reads the just-written + // `graph.ast[result_source_index]`. Phase 1/2 only read // `import_records` / `named_imports` for THIS index; the BFS (Phase 3) // takes fresh `&mut graph.ast` borrows after these raw reads are dead. // @@ -388,7 +385,7 @@ pub(crate) fn schedule_barrel_deferred_imports( let file_named_imports: bun_ptr::BackRef = bun_ptr::BackRef::new(&this.graph.ast.items_named_imports()[result_source_index as usize]); - // PORT NOTE: `DevServerHandle` copied out so `&mut this.*` field borrows + // `DevServerHandle` copied out so `&mut this.*` field borrows // don't conflict with the `&self` accessor. let dev_handle = this.dev_server_handle().copied(); @@ -402,7 +399,7 @@ pub(crate) fn schedule_barrel_deferred_imports( // barrel optimization requires source_indices to seed requested_exports and to // BFS un-defer records. Resolve paths → source_indices here as a fallback. // - // PORT NOTE: reshaped for borrowck — `path_to_source_index_map` borrows + // Borrowck: `path_to_source_index_map` borrows // `&mut this.graph`; wrap in `BackRef` so the long-lived read borrow // doesn't conflict with `&mut this.requested_exports` / // `&mut this.graph.ast` below. The map is not mutated for the duration of @@ -417,7 +414,7 @@ pub(crate) fn schedule_barrel_deferred_imports( None }; - // See PORT NOTE above — read-only deref valid through Phase 2. + // Read-only deref — valid through Phase 2 (see the raw-read note above). let file_import_records = file_import_records.get(); // Build a set of import_record_indices that have named_imports entries, @@ -451,7 +448,7 @@ pub(crate) fn schedule_barrel_deferred_imports( } } - // See PORT NOTE above — read-only deref valid through Phase 2. + // Read-only deref — valid through Phase 2 (see the raw-read note above). for ni in file_named_imports.values() { if ni.import_record_index as usize >= file_import_records.len() { continue; @@ -543,10 +540,9 @@ pub(crate) fn schedule_barrel_deferred_imports( // Build work queue from this file's named_imports, then propagate // through chains of barrels. Only runs real work when barrels exist // (targets with deferred records). - // PERF(port): was stack-fallback (8192) — profile if hot. let mut queue: Vec = Vec::new(); - // See PORT NOTE above — read-only deref valid through Phase 2. + // Read-only deref — valid through Phase 2 (see the raw-read note above). for ni in file_named_imports.values() { if ni.import_record_index as usize >= file_import_records.len() { continue; @@ -661,13 +657,12 @@ pub(crate) fn schedule_barrel_deferred_imports( // dedup via requested_exports to prevent cycles. let initial_queue_len = queue.len(); - // PERF(port): was stack-fallback (1024) — profile if hot. let mut barrels_to_resolve: ArrayHashMap = ArrayHashMap::default(); let mut newly_scheduled: i32 = 0; let mut qi: usize = 0; while qi < queue.len() { - // PORT NOTE: reshaped for borrowck — copy item fields out before pushing to queue later + // Copy item fields out before pushing to `queue` later (borrowck). let item_barrel_idx = queue[qi].barrel_source_index; let item_alias = queue[qi].alias; let item_is_star = queue[qi].is_star; @@ -705,11 +700,11 @@ pub(crate) fn schedule_barrel_deferred_imports( // Use a helper to get barrel_ir freshly each time, since // resolveBarrelRecords can reallocate graph.ast and invalidate pointers. - // PORT NOTE: reshaped for borrowck — re-borrow graph.ast column after each mutation + // Re-borrow the `graph.ast` column after each mutation (borrowck). let barrel_ir = &mut this.graph.ast.items_import_records_mut()[barrel_idx as usize]; if item_is_star { - // PORT NOTE: reshaped for borrowck — read flags by index, then mutate + // Read flags by index, then mutate (borrowck). let len = barrel_ir.len(); for idx in 0..len { let flags = barrel_ir.as_slice()[idx].flags; @@ -733,7 +728,7 @@ pub(crate) fn schedule_barrel_deferred_imports( ); let Some(resolution) = resolution else { // Name not in named re-exports — might come from export *. - // PORT NOTE: clone the small u32 slice so iterating it doesn't + // Clone the small u32 slice so iterating it doesn't // alias the mutable `barrel_ir` borrow taken inside the loop. let star_records: Vec = this.graph.ast.items_export_star_import_records()[barrel_idx as usize].to_vec(); @@ -773,7 +768,7 @@ pub(crate) fn schedule_barrel_deferred_imports( } // `original_alias` is an arena-backed `StoreStr` valid for the - // bundler-arena lifetime (see `BarrelExportResolution` PORT NOTE). + // bundler-arena lifetime (see the comment on `BarrelExportResolution`). let propagate_alias: &[u8] = match resolution.original_alias { Some(p) => p.slice(), None => alias, @@ -820,5 +815,3 @@ pub(crate) fn schedule_barrel_deferred_imports( fn persist_barrel_export(dev: &crate::dispatch::DevServerHandle, barrel_path: &[u8], alias: &[u8]) { dev.register_barrel_export(barrel_path, alias) } - -// ported from: src/bundler/barrel_imports.zig diff --git a/src/bundler/bundle_v2.rs b/src/bundler/bundle_v2.rs index 5c73a36ce9a..6c390e509c6 100644 --- a/src/bundler/bundle_v2.rs +++ b/src/bundler/bundle_v2.rs @@ -40,7 +40,7 @@ use crate::transpiler::Transpiler; use crate::{Index, IndexInt, LinkerContext}; // ── re-exports so callers can reference these via `bundle_v2::…` ── -/// `BundleThread` (BundleThread.zig) — owns the worker pool + completion +/// `BundleThread` — owns the worker pool + completion /// queue for `BundleV2`. Re-exported so callers reference `bundle_v2::BundleThread`. pub use crate::BundleThread::BundleThread; pub use crate::ParseTask; @@ -61,10 +61,9 @@ pub struct PendingImport { } pub struct BundleV2<'a> { - // PORT NOTE: Zig stored `*Transpiler` (and aliased the same pointer into - // `ssr_transpiler` when SSR graph isn't separate). `ssr_transpiler` stays - // `*mut` so the alias is legal; `transpiler` is `&'a mut` for ergonomic - // field access throughout the bundler bodies. + // `ssr_transpiler` may alias this same transpiler when the SSR graph + // isn't separate, so it stays `*mut`; `transpiler` is `&'a mut` for + // ergonomic field access throughout the bundler bodies. pub transpiler: &'a mut Transpiler<'a>, /// When Server Components is enabled, this is used for the client bundles /// and `transpiler` is used for the server bundles. @@ -89,8 +88,7 @@ pub struct BundleV2<'a> { /// When Bun Bake is used, the resolved framework is passed here. pub framework: Option, pub graph: Graph<'a>, - // `LinkerContext<'a>` borrows the same arena lifetime as `transpiler` - // (Zig stored both as raw pointers into the bundler heap). + // `LinkerContext<'a>` borrows the same arena lifetime as `transpiler`. pub linker: LinkerContext<'a>, // The hot reloader (`jsc::hot_reloader::NewHotReloader`) owns the // boxed `Watcher`; bundler only ever calls `Watcher::add_file` on it. @@ -138,7 +136,7 @@ pub struct BundleV2<'a> { /// deduplication is free. /// /// Indexed by `source_index` (dense `0..module_count`); a `Vec>` - /// instead of the Zig `AutoArrayHashMap` because the key space is + /// instead of a hash map because the key space is /// dense and this is probed once per import in `on_parse_task_complete` /// (the main-thread parse-phase throughput limiter). pub requested_exports: Vec>, @@ -147,12 +145,10 @@ pub struct BundleV2<'a> { bun_core::declare_scope!(Bundle, visible); bun_core::declare_scope!(scan_counter, visible); -/// `bundle_v2.zig` `ResolveQueue = std.StringArrayHashMap(*ParseTask)`. /// Values are raw `*mut ParseTask` (arena-owned by `graph.heap`); the map only /// dedups by path during a single `on_parse_task_complete` pass. pub(crate) type ResolveQueue = StringHashMap<*mut ParseTask>; -/// `bundle_v2.zig:BakeOptions`. pub struct BakeOptions<'a> { pub framework: bake::Framework, pub client_transpiler: NonNull>, @@ -162,8 +158,8 @@ pub struct BakeOptions<'a> { impl<'a> BundleV2<'a> { // ── raw-ptr accessors ───────────────────────────────────────────────── - // PORT NOTE: `transpiler`/`ssr_transpiler` are `*mut` because Zig stored - // aliased `*Transpiler` (same pointer in both slots when no SSR graph). + // `ssr_transpiler` is `*mut` because it may alias `transpiler` + // (same pointer in both slots when no SSR graph). // Callers go through these accessors so the unsafe deref is centralized. #[inline] pub fn transpiler(&self) -> &Transpiler<'a> { @@ -246,7 +242,7 @@ impl<'a> BundleV2<'a> { pub fn transpiler_for_target(&mut self, target: options::Target) -> &mut Transpiler<'a> { // SAFETY: all three pointers are live for `'a` (set in `init`); the // `client_transpiler` arm is only reached when bake populated it. - // bundle_v2.zig:247-263 — outside of server-components / dev-server, + // Outside of server-components / dev-server, // the only case that doesn't return the main transpiler is a // browser-target request from a server-side build, which lazily // spins up a client transpiler. @@ -259,7 +255,6 @@ impl<'a> BundleV2<'a> { // overlapping `client_transpiler_ref()` borrow. return unsafe { p.assume_mut() }; } - // bundle_v2.zig:250-252 — `client_transpiler orelse initializeClientTranspiler() catch panic`. return self.initialize_client_transpiler().unwrap_or_else(|e| { panic!("Failed to initialize client transpiler: {}", e.name()) }); @@ -277,7 +272,7 @@ impl<'a> BundleV2<'a> { } } - // PORT NOTE: draft `on_parse_task_complete` / `deinit_without_freeing_arena` + // draft `on_parse_task_complete` / `deinit_without_freeing_arena` // removed — canonical bodies live in the later impl blocks below. } // ══════════════════════════════════════════════════════════════════════════ @@ -294,7 +289,6 @@ pub mod bv2_impl { // // # Memory management // - // Zig is not a managed language, so we have to be careful about memory management. // Manually freeing memory is error-prone and tedious, but garbage collection // is slow and reference counting incurs a performance penalty. // @@ -345,6 +339,7 @@ pub mod bv2_impl { use crate::{bun_css, import_record}; use bun_alloc::{AllocError, Arena as ThreadLocalArena}; + use self::bake_types as bake; use bun_ast::server_component_boundary; use bun_ast::{Binding, E, Expr, G, S}; use bun_ast::{ImportKind, ImportRecord}; @@ -355,21 +350,17 @@ pub mod bv2_impl { use bun_resolver::fs::PathResolverExt as _; use bun_resolver::{self as _resolver, is_package_path}; use bun_threading::ThreadPool as ThreadPoolLib; - // TODO(b0): bake_types arrives from move-in (TYPE_ONLY Side/Graph/BuiltInModule/Framework → bundler) - use self::bake_types as bake; /// CYCLEBREAK(b0) TYPE_ONLY: pure value types from bake that bundler needs without /// depending on the full DevServer. Move-in pass keeps these as the canonical defs; /// bun_bake (post tier-6 collapse: bun_runtime::bake) re-exports from here. pub mod bake_types { - /// Mirrors src/bake/lib.zig `Side`. #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug, core::marker::ConstParamTy)] pub enum Side { Client = 0, Server = 1, } - /// Mirrors src/bake/lib.zig `Graph`. #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum Graph { @@ -377,7 +368,7 @@ pub mod bv2_impl { Server = 1, Ssr = 2, } - /// Zig `@tagName(graph)` — used for the per-file `// path (target)` comment + /// Used for the per-file `// path (target)` comment /// in postProcessJSChunk and friends. impl From for &'static str { fn from(g: Graph) -> Self { @@ -396,7 +387,7 @@ pub mod bv2_impl { } } } - /// Mirrors src/bake/DevServer.zig `FileKind` (the type of `CacheEntry.kind`). + /// The type of `CacheEntry.kind`. #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum CacheKind { @@ -409,17 +400,17 @@ pub mod bv2_impl { pub struct CacheEntry { pub kind: CacheKind, } - /// Mirrors src/bake/DevServer.zig `ASSET_PREFIX` (= INTERNAL_PREFIX ++ "/asset" = "/_bun/asset"). + /// INTERNAL_PREFIX ++ "/asset" = "/_bun/asset". pub(crate) const ASSET_PREFIX: &str = "/_bun/asset"; - /// Mirrors src/bake/bake.zig:355 `BuiltInModule = union(enum)`. TYPE_ONLY moved + /// TYPE_ONLY moved /// down to bundler (T5); bake (in runtime, T6) constructs values of this type. pub enum BuiltInModule { Import(Box<[u8]>), Code(Box<[u8]>), } - /// Mirrors src/bake/DevServer.zig `EntryPointList.Flags` (`packed struct(u8)`). + /// `EntryPointList` flags. #[repr(transparent)] #[derive(Copy, Clone, Default, Eq, PartialEq)] pub struct EntryPointFlags(pub u8); @@ -447,7 +438,7 @@ pub mod bv2_impl { } } - /// Mirrors src/bake/DevServer.zig `EntryPointList`. TYPE_ONLY moved down; bundler + /// TYPE_ONLY moved down; bundler /// reads `.set` (count/keys/values) in `enqueue_entry_points_dev_server`. #[derive(Default)] pub struct EntryPointList { @@ -461,12 +452,9 @@ pub mod bv2_impl { } } - /// Mirrors src/bake/bake.zig `Framework`. TYPE_ONLY subset of the fields - /// the bundler/parser actually consult (see ParseTask.zig:1253 - /// `opts.framework = transpiler.options.framework`); `file_system_router_types` + /// TYPE_ONLY subset of the `Framework` fields + /// the bundler/parser actually consult; `file_system_router_types` /// stays in T6 because only `bake::FrameworkRouter` reads it. - // TODO(b0-genuine): remaining Framework field `file_system_router_types` - // stays in T6; only bake::FrameworkRouter reads it. #[non_exhaustive] pub struct Framework { pub built_in_modules: bun_collections::StringArrayHashMap, @@ -480,14 +468,11 @@ pub mod bv2_impl { /// `linker_context::generateChunksInParallel` to gate `BakeExtra`. pub is_built_in_react: bool, /// Read by `entry_points.rs` (FallbackEntryPoint/ClientEntryPoint::generate). - /// In Zig this lives on the legacy package_json `Framework`; the duck-typed - /// `comptime TranspilerType` callers reach it through `options.framework.?`. pub client_css_in_js: crate::options::ClientCssInJs, } impl Framework { /// Construct the bundler-side TYPE_ONLY view. Called from - /// `bun_runtime::bake::Framework::init_transpiler_with_options` - /// (spec bake.zig:778 `out.options.framework = framework`); the + /// `bun_runtime::bake::Framework::init_transpiler_with_options`; the /// runtime owns the canonical `bake.Framework` and projects the /// fields the bundler reads. pub fn new( @@ -505,7 +490,7 @@ pub mod bv2_impl { } } } - /// Mirrors src/bake/bake.zig `Framework.ServerComponents` — full string + /// `Framework.ServerComponents` — full string /// surface so the parser-side projection (ParseTask.rs `run_with_source_code`) /// can forward user-configured `serverRegisterServerReference` / /// `clientRegisterServerReference` instead of hardcoding defaults. @@ -517,13 +502,12 @@ pub mod bv2_impl { pub server_register_server_reference: Box<[u8]>, pub client_register_server_reference: Box<[u8]>, } - /// Mirrors src/bake/bake.zig `Framework.ReactFastRefresh`. #[derive(Clone)] pub struct ReactFastRefresh { pub import_source: Box<[u8]>, } - /// Mirrors src/bake/bake.zig:840 `HmrRuntime`. TYPE_ONLY moved down so the + /// TYPE_ONLY moved down so the /// linker can splice the runtime preamble without depending on bun_bake. #[derive(Clone, Copy)] pub struct HmrRuntime { @@ -533,7 +517,7 @@ pub mod bv2_impl { } impl HmrRuntime { pub const fn init(code: &'static [u8]) -> Self { - // const-fn line counter (mirrors `std.mem.count(u8, code, "\n")`). + // const-fn newline counter. let mut n: u32 = 0; let mut i = 0usize; while i < code.len() { @@ -551,7 +535,7 @@ pub mod bv2_impl { /// Alias used at the crate root (`crate::HmrRuntimeSide`); identical to `Side`. pub type HmrRuntimeSide = Side; - /// Mirrors src/bake/bake.zig:855 `getHmrRuntime`. MOVE_DOWN bake→bundler: + /// MOVE_DOWN bake→bundler: /// the codegen'd `bake.client.js` / `bake.server.js` are loaded via /// `bun_core::runtime_embed_file!` (same per-site `OnceLock` cache /// `js_parser/runtime.rs` uses for `runtime.out.js`), so the storage lives @@ -559,9 +543,8 @@ pub mod bv2_impl { /// own `&'static ZStr` flavour for JSC/C++ handoff; this bundler-side copy /// only needs `&[u8]` for the chunk preamble + sourcemap line skip, so the /// NUL-termination dance is unnecessary. Per-side `OnceLock` - /// memoizes the `\n` count (Zig const-eval'd it on the `@embedFile` arm; - /// `runtime_embed_file!` already caches the file load, this caches the - /// `init` scan so repeat calls are a `Copy`). + /// memoizes the `\n` count (`runtime_embed_file!` already caches the file + /// load, this caches the `init` scan so repeat calls are a `Copy`). pub fn get_hmr_runtime(side: Side) -> HmrRuntime { static CLIENT: std::sync::OnceLock = std::sync::OnceLock::new(); static SERVER: std::sync::OnceLock = std::sync::OnceLock::new(); @@ -580,14 +563,12 @@ pub mod bv2_impl { } } - /// Mirrors src/bake/bake.zig:936 `server_virtual_source` / :942 `client_virtual_source`. /// `bun_ast::Source` is not `const`-constructible (owns a `fs::Path`), so these - /// are lazy statics. PERF(port): was `pub const` in Zig. + /// are lazy statics. pub(crate) static SERVER_VIRTUAL_SOURCE: std::sync::LazyLock = std::sync::LazyLock::new(|| { - // Port of `Fs.Path.initForKitBuiltIn("bun", "bake/server")` (fs.zig:1992) — - // inlined because `bun_paths::fs::Path<'static>` is the local TYPE_ONLY stub and - // does not yet expose that constructor. + // Inlined because `bun_paths::fs::Path<'static>` is the local TYPE_ONLY stub and + // does not expose a built-in-path constructor. bun_ast::Source { path: bun_paths::fs::Path { pretty: b"bun:bake/server", @@ -613,7 +594,7 @@ pub mod bv2_impl { ..Default::default() }); - /// Canonical port of src/bake/production.zig:844 `EntryPointMap`. + /// `EntryPointMap`. /// Lives in the bundler (lower tier) so both `bun_runtime::bake::production` /// and `BundleV2::generate_from_bake_production_cli` share ONE nominal type /// (PORTING.md §Layering). Router-integration methods (`InsertionHandler`) @@ -639,10 +620,9 @@ pub mod bv2_impl { } } - /// `EntryPointMap.InputFile`. Zig packed `Side` into the slice-len word - /// for a 16-byte key; the Rust `Hash`/`Eq` impls below are content-based - /// (not byte-layout), so that packing is not load-bearing here — store a - /// `RawSlice` instead and let `bun_ptr` encapsulate the unsafe re-borrow. + /// `EntryPointMap.InputFile`. The `Hash`/`Eq` impls below are content-based + /// (not byte-layout) — store a + /// `RawSlice` and let `bun_ptr` encapsulate the unsafe re-borrow. /// `RawSlice: Send + Sync`, so no manual auto-trait impls are needed. #[derive(Copy, Clone)] pub struct InputFile { @@ -678,7 +658,7 @@ pub mod bv2_impl { impl Eq for InputFile {} /// Value side is `OutputFile.Index` — left as a placeholder until the - /// bundle is indexed (Zig leaves it `undefined`); the bundler never reads it. + /// bundle is indexed; the bundler never reads it. pub use crate::output_file::Index as OutputFileIndex; pub type EntryPointHashMap = bun_collections::ArrayHashMap; @@ -689,9 +669,8 @@ pub mod bv2_impl { /// `OpaqueFileId` is the insertion index into this map. pub files: EntryPointHashMap, /// Owned backing storage for the duped path bytes that `InputFile` - /// keys point into (raw ptr+len). Mirrors Zig's `map.arena.dupe` - /// against `bun.default_allocator` — kept here so the allocations - /// drop with the map (PORTING.md §Forbidden: no `Box::leak`). + /// keys point into (raw ptr+len) — kept here so the allocations + /// drop with the map (no `Box::leak`). pub owned_paths: Vec>, } impl EntryPointMap { @@ -707,22 +686,18 @@ pub mod bv2_impl { if let Some(index) = self.files.get_index(&probe) { return Ok(OpaqueFileId::init(index as u32)); } - // Zig: `gop.key_ptr.* = InputFile.init(try map.arena.dupe(u8, abs_path), side);` - // The Zig `errdefer map.files.swapRemoveAt(gop.index)` only guards the - // `arena.dupe`, which is infallible in Rust, so no rollback needed. let owned: Box<[u8]> = Box::<[u8]>::from(abs_path); let key = InputFile::init(&owned, side); self.owned_paths.push(owned); let index = self.files.count(); // Value is the post-bundle output index; left as a placeholder until - // the bundle is indexed (production.zig:873 leaves it `undefined`). + // the bundle is indexed. self.files.put_no_clobber(key, OutputFileIndex::init(0))?; Ok(OpaqueFileId::init(index as u32)) } } } } - // TODO(b0): jsc::api arrives from move-in (TYPE_ONLY → bundler) use self::api as jsc_api; /// CYCLEBREAK(b0) TYPE_ONLY: data-only halves of `jsc::api::JSBundler` and @@ -730,7 +705,7 @@ pub mod bv2_impl { /// JSC. The JS-thread halves (dispatch onto the JS event loop, `toJS`, plugin /// FFI bodies) stay in tier-6 (`bun_runtime::api`) and re-export these. pub mod api { - /// Mirrors src/runtime/api/JSBundler.zig:1799 `BuildArtifact.OutputKind`. + /// `BuildArtifact.OutputKind`. /// Canonical definition lives in `crate::options::OutputKind`; re-exported /// here so the documented CYCLEBREAK path `api::build_artifact::OutputKind` /// keeps resolving. @@ -738,7 +713,7 @@ pub mod bv2_impl { pub use crate::options::OutputKind; } - /// Mirrors src/runtime/api/JSBundler.zig:3 `JSBundler` — TYPE_ONLY subset. + /// `JSBundler` — TYPE_ONLY subset. /// Exposed as a module (not a struct) so callers can write /// `api::JSBundler::Load` / `api::JSBundler::Resolve::MiniImportRecord`. #[allow(non_snake_case)] @@ -814,11 +789,10 @@ pub mod bv2_impl { ) -> i32; } impl Plugin { - /// `Plugin.drainDeferred` (JSBundler.zig) — resolve every onLoad - /// `.defer()` promise. Zig wraps the FFI in `fromJSHostCallGeneric` - /// for exception-scope tracking and returns `JSError!void`; the - /// only bundler caller (`DeferredBatchTask::run_on_js_thread`) is - /// `catch return`, so the void FFI call is the observable + /// `Plugin.drainDeferred` — resolve every onLoad + /// `.defer()` promise. The + /// only bundler caller (`DeferredBatchTask::run_on_js_thread`) + /// ignores failures, so the void FFI call is the observable /// behaviour at this tier. pub fn drain_deferred(&mut self, rejected: bool) { JSBundlerPlugin__drainDeferred(self, rejected) @@ -925,8 +899,8 @@ pub mod bv2_impl { } /// Mirrors `JSBundler.FileMap` — virtual in-memory files for the build. - /// The Zig value type is `jsc.Node.BlobOrStringOrBuffer` (T6); bundler - /// only ever reads `.slice()`, so the moved-down map stores raw bytes. + /// The bundler only ever reads `.slice()`, so the moved-down map + /// stores raw bytes. /// `bun_runtime`'s `from_js` parses JS values via `BlobOrStringOrBuffer` /// in async (owning-copy) mode and inserts the extracted bytes here. #[derive(Default)] @@ -1097,7 +1071,7 @@ pub mod bv2_impl { } } - /// Mirrors `JSBundler.Resolve.MiniImportRecord` (zig:1242). + /// Owned snapshot of an import record handed to onResolve plugins. #[derive(Clone, Default)] pub struct MiniImportRecord { pub kind: ImportKind, @@ -1134,7 +1108,7 @@ pub mod bv2_impl { } } - /// Mirrors `JSBundler.Resolve` (zig:1234). Both `js_task` and `task` + /// Both `js_task` and `task` /// are the real lower-tier `bun_event_loop` types, so `dispatch()` / /// `run_on_js_thread()` are implemented inherently (no T6 hook). pub struct Resolve { @@ -1160,7 +1134,7 @@ pub mod bv2_impl { pub fn init(bv2: &mut BundleV2<'_>, record: MiniImportRecord) -> Self { Self { // SAFETY: lifetime erased — Resolve is owned by the dispatch - // chain and never outlives `bv2` (mirrors Zig raw `*BundleV2`). + // chain and never outlives `bv2`. bv2: std::ptr::from_mut::>(bv2).cast::>(), import_record: record, value: ResolveValue::Pending, @@ -1169,10 +1143,6 @@ pub mod bv2_impl { } } /// Hops to the JS thread to call the `onResolve` plugin chain. - /// Zig spec (JSBundler.zig:1311): - /// `this.js_task = AnyTask.init(this); - /// bv2.jsLoopForPlugins().enqueueTaskConcurrent( - /// jsc.ConcurrentTask.create(this.js_task.task()))` pub fn dispatch(&mut self) { self.js_task = bun_event_loop::AnyTask::AnyTask { ctx: core::ptr::NonNull::new( @@ -1188,7 +1158,7 @@ pub mod bv2_impl { } pub fn run_on_js_thread(&mut self) { let kind = self.import_record.kind; - // PORT NOTE: reshaped for borrowck — capture the erased self + // reshaped for borrowck — capture the erased self // pointer before borrowing fields immutably for the FFI call. let self_ptr = std::ptr::from_mut::(self).cast::(); // SAFETY: `bv2` is a valid backref set by `init`; the plugin @@ -1238,7 +1208,7 @@ pub mod bv2_impl { } } - /// Mirrors `JSBundler.Load` (zig:1369). + /// Task driving an onLoad plugin invocation for one source file. pub struct Load { pub bv2: *mut BundleV2<'static>, pub source_index: bun_ast::Index, @@ -1315,10 +1285,6 @@ pub mod bv2_impl { self.parse_task().known_target.bake_graph() } /// Hops to the JS thread to call the `onLoad` plugin chain. - /// Zig spec (JSBundler.zig:1449): - /// `this.js_task = AnyTask.init(this); - /// let concurrent_task = jsc.ConcurrentTask.createFrom(&this.js_task); - /// bv2.jsLoopForPlugins().enqueueTaskConcurrent(concurrent_task)` pub fn dispatch(&mut self) { self.js_task = bun_event_loop::AnyTask::AnyTask { ctx: core::ptr::NonNull::new( @@ -1337,7 +1303,7 @@ pub mod bv2_impl { pub fn run_on_js_thread(&mut self) { let is_server_side = self.bake_graph() != crate::bake_types::Graph::Client; let default_loader = self.default_loader; - // PORT NOTE: reshaped for borrowck — capture the erased self + // reshaped for borrowck — capture the erased self // pointer before borrowing fields immutably for the FFI call. let self_ptr = std::ptr::from_mut::(self).cast::(); // SAFETY: `bv2` is a valid backref set by `init`; the plugin @@ -1366,8 +1332,7 @@ pub mod bv2_impl { } } - /// `SavedFile` is a unit struct in Zig - /// (src/bundler_jsc/output_file_jsc.zig:4) — its only member is `toJS`, which + /// `SavedFile`'s only member is `toJS`, which /// is JSC-bound and stays in T6. The bundler stores it as an `OutputFile` value /// tag, so a unit struct here is sufficient. pub mod saved_file { @@ -1451,8 +1416,7 @@ pub mod bv2_impl { unsafe extern "Rust" { /// Defined `#[no_mangle]` in `bun_jsc::hot_reloader`. Installs a /// `NewHotReloader` watcher on the given - /// `BundleV2` (Zig: `Watcher.enableHotModuleReloading(this, null)` in - /// `BundleV2.init` — bundle_v2.zig:994). The bundler can't name the + /// `BundleV2`. The bundler can't name the /// reloader generic (T6), so this is a definer-prefixed extern hook. /// `'static` matches the impl-side signature; the sole caller /// (`bun build --watch`) leaks the `Box` via @@ -1477,9 +1441,8 @@ pub mod bv2_impl { unsafe { __bun_jsc_enable_hot_module_reloading_for_bundler(bv2) } } - /// Bytecode generation entry point for the linker. Mirrors the Zig - /// `jsc.VirtualMachine.is_bundler_thread_for_bytecode_cache = true; - /// jsc.initialize(false); jsc.CachedBytecode.generate(...)` sequence. + /// Bytecode generation entry point for the linker: marks the calling + /// thread as bundler-for-bytecode-cache, initializes JSC, and generates. #[inline] pub fn generate_cached_bytecode( format: crate::options_impl::Format, @@ -1489,18 +1452,17 @@ pub mod bv2_impl { __bun_jsc_generate_cached_bytecode(format, source, source_provider_url) } - /// CYCLEBREAK GENUINE: `JSBundleCompletionTask` (JSBundler.zig) — the + /// CYCLEBREAK GENUINE: `JSBundleCompletionTask` — the /// concrete struct lives in `bun_runtime` (its fields name `Config`/ /// `Plugin`/`HTMLBundle::Route`). The bundler reads exactly two things - /// from it (`result == .err` and `jsc_event_loop.enqueueTaskConcurrent`), - /// so the high tier hands the bundler an erased owner + `&'static` vtable - /// pair (same shape as [`DevServerHandle`]). PERF(port): was direct field - /// access in Zig. + /// from it (whether the result is an error, and the concurrent-task + /// enqueue), so the high tier hands the bundler an erased owner + + /// `&'static` vtable pair (same shape as [`DevServerHandle`]). pub struct CompletionDispatch { - /// Zig: `completion.result == .err` + /// Whether the completion result is an error. pub result_is_err: unsafe fn(core::ptr::NonNull) -> bool, - /// Zig: `completion.jsc_event_loop.enqueueTaskConcurrent(task)` — folds - /// the field access + enqueue so the bundler needn't name `*jsc.EventLoop`. + /// Folds the event-loop field access + enqueue so the bundler + /// needn't name the JSC event-loop type. pub enqueue_task_concurrent: unsafe fn( core::ptr::NonNull, *mut bun_event_loop::ConcurrentTask::ConcurrentTask, @@ -1543,7 +1505,7 @@ pub mod bv2_impl { /// (`Option>`). pub use crate::linker_context_mod::EventLoop; - // `JSBundleCompletionTask` (JSBundler.zig) — typed-ptr marker for + // `JSBundleCompletionTask` — typed-ptr marker for // `BundleV2.completion`. The concrete struct lives in `bun_runtime` (its // fields name `Config`/`Plugin`/`HTMLBundle::Route`); the bundler only ever // holds a `NonNull` inside [`dispatch::CompletionHandle`] @@ -1586,11 +1548,9 @@ pub mod bv2_impl { pub use super::{BakeOptions, BundleV2, PendingImport}; impl<'a> BundleV2<'a> { - /// Zig: `jsLoopForPlugins().enqueueTaskConcurrent(task)`. The Rust port - /// folds the lookup + enqueue so the bundler never dereferences + /// Folds the JS-loop lookup + enqueue so the bundler never dereferences /// `JSBundleCompletionTask` (its layout lives in `bun_runtime`); the /// `completion` handle carries the `&'static` vtable. - /// PERF(port): was inline `switch (this.loop().*)` + direct field access. pub fn enqueue_on_js_loop_for_plugins( &mut self, task: NonNull, @@ -1602,7 +1562,7 @@ pub mod bv2_impl { return; } // From bake where the loop running the bundle is also the loop running - // the plugins (Zig: `switch (this.loop().*) { .js => |l| l, .mini => @panic }`). + // the plugins. // `any_loop_mut` centralises the BACKREF deref of `linker.r#loop`. match &*self.any_loop_mut() { bun_event_loop::AnyEventLoop::Js { owner } => { @@ -1625,11 +1585,7 @@ pub mod bv2_impl { } pub fn initialize_client_transpiler(&mut self) -> Result<&mut Transpiler<'a>, Error> { - // bundle_v2.zig:198-241. - // - // PORT NOTE: Zig does `client_transpiler.* = this_transpiler.*` (bitwise - // struct copy into an arena slot — no destructors). The Rust port - // builds a fresh owned `Transpiler` via `Transpiler::for_worker` + // Builds a fresh owned `Transpiler` via `Transpiler::for_worker` // (per-field deep clone), mutates the browser-specific options with // ordinary assignment (every field is owned by the clone, so `Drop` on // the overwritten value is correct), then boxes it on the global heap @@ -1688,21 +1644,17 @@ pub mod bv2_impl { // deep-cloned `BundleOptions`/`Resolver` fields; `arena.alloc` would // leak them (bumpalo never drops). let mut boxed: Box> = Box::new(ct); - // Zig: `setLog` / `setAllocator` / `linker.resolver = &resolver` / - // `macro_context = MacroContext.init(transpiler)` / - // `resolver.caches = CacheSet.init(alloc)` — all handled by - // `for_worker` + `wire_after_move`. + // Log/allocator/linker-resolver/macro-context/cache wiring is all + // handled by `for_worker` + `wire_after_move`. boxed.wire_after_move(); // `configure_defines` early-returns on `options.defines_loaded` (cloned // as `true`); kept for spec parity. boxed.configure_defines()?; - // Zig: `client_transpiler.resolver.opts = client_transpiler.options;` — - // re-project the resolver subset now that `target`/`conditions` etc. + // Re-project the resolver subset now that `target`/`conditions` etc. // have been overwritten for the browser. boxed.sync_resolver_opts(); - // Zig: `client_transpiler.resolver.env_loader = client_transpiler.env;` boxed.resolver.env_loader = NonNull::new(this_env.cast()); // Park the owning Box first, then derive both the published `NonNull` @@ -1725,7 +1677,7 @@ pub mod bv2_impl { bake_graph: bake::Graph, ) -> &mut bun_ast::Log { if let Some(dev) = self.dev_server_handle() { - // CYCLEBREAK GENUINE: DevServer → vtable. PERF(port): was inline switch. + // CYCLEBREAK GENUINE: DevServer → vtable. // SAFETY: owner is a live *mut DevServer per handle invariant. return unsafe { &mut *dev.log_for_resolution_failures(abs_path, bake_graph) }; } @@ -1742,7 +1694,6 @@ pub mod bv2_impl { pub all_loaders: &'a [Loader], pub all_urls_for_css: &'a [&'a [u8]], pub redirects: &'a [u32], - // PORT NOTE: Zig copied the map by value (cheap shallow copy). The Rust // `PathToSourceIndexMap` is `!Clone` and the field is unread in `visit`, so // store a raw backref to satisfy the struct shape without forcing `Clone`. pub redirect_map: *const PathToSourceIndexMap, @@ -1808,7 +1759,7 @@ pub mod bv2_impl { let import_record_list_id = source_index; // when there are no import records, v index will be invalid if (import_record_list_id.get() as usize) < self.all_import_records.len() { - // PORT NOTE: reshaped for borrowck — split borrow of all_import_records + // reshaped for borrowck — split borrow of all_import_records let import_records_len = self.all_import_records[import_record_list_id.get() as usize].len() as usize; for ir_idx in 0..import_records_len { @@ -1821,7 +1772,7 @@ pub mod bv2_impl { while let Some(redirect_id) = get_redirect_id(self.redirects[other_source.get() as usize]) { - // PORT NOTE: reshaped for borrowck — copy out the redirect target's + // reshaped for borrowck — copy out the redirect target's // (source_index, path) before re-borrowing `all_import_records` mutably. let (other_src_idx, other_path) = { let other_import_records = @@ -1904,8 +1855,8 @@ pub mod bv2_impl { } /// RAII guard returned by [`BundleV2::decrement_scan_counter_on_drop`]. - /// Decrements the bundle's pending-scan counter when dropped, mirroring Zig's - /// `defer this.decrementScanCounter()` without holding a unique borrow across + /// Decrements the bundle's pending-scan counter when dropped, without + /// holding a unique borrow across /// the body. Stores a raw pointer; caller guarantees the `BundleV2` outlives it. pub struct ScanCounterGuard { bv2: *mut BundleV2<'static>, @@ -1923,12 +1874,11 @@ pub mod bv2_impl { impl<'a> BundleV2<'a> { pub fn find_reachable_files(&mut self) -> Result, Error> { - // RAII guard — `Ctx` ends the span on Drop (Zig: `defer trace.end()`). + // RAII guard — `Ctx` ends the span on Drop. let _trace = crate::perf::trace("Bundler.findReachableFiles"); // Create a quick index for server-component boundaries. // We need to mark the generated files as reachable, or else many files will appear missing. - // PERF(port): was stack-fallback let scb_bitset = if self.graph.server_component_boundaries.list.len() > 0 { Some( self.graph @@ -1947,10 +1897,9 @@ pub mod bv2_impl { self.dynamic_import_entry_points = ArrayHashMap::new(); - // PORT NOTE: reshaped for borrowck — hoist the values that would + // reshaped for borrowck — hoist the values that would // otherwise re-borrow `self`/`self.graph` while the visitor holds - // disjoint column refs (Zig pulled multiple `items(.field)` columns at - // once with no aliasing model). + // disjoint column refs. let redirect_map: *const PathToSourceIndexMap = std::ptr::from_ref(self.path_to_source_index_map(self.transpiler.options.target)); // Always materialize a valid slice; when the boundary list is empty @@ -1959,7 +1908,7 @@ pub mod bv2_impl { // when `scb_bitset` is `None`). let scb_list = self.graph.server_component_boundaries.slice(); - // PORT NOTE: reshaped for borrowck — `Slice` is a value-type + // reshaped for borrowck — `Slice` is a value-type // snapshot of column pointers (does not borrow `self.graph.ast`), so // `split_mut()` on the local can coexist with the shared borrows // below. The slab does not resize for the duration of this function. @@ -2021,12 +1970,12 @@ pub mod bv2_impl { } } - // PORT NOTE: reshaped for borrowck — release the visitor's `&mut` + // reshaped for borrowck — release the visitor's `&mut` // borrows on the two bitsets and `input_files` columns before the // cleanup loop reads them. let ReachableFileVisitor { reachable, .. } = visitor; - // PORT NOTE: reshaped for borrowck — three disjoint mutable SoA + // reshaped for borrowck — three disjoint mutable SoA // columns via `split_mut()` on a value-type `Slice` snapshot. let mut input_files_slice = self.graph.input_files.slice(); let input_files_cols = input_files_slice.split_mut(); @@ -2057,8 +2006,8 @@ pub mod bv2_impl { if self.graph.pending_items == 0 { let this: *mut Self = self; - // PORT NOTE: reshaped for borrowck — Zig passed `&self.graph` and - // `self` to the same call. Take a raw ptr so the two `&mut` don't + // reshaped for borrowck — `&self.graph` and + // `self` go to the same call. Take a raw ptr so the two `&mut` don't // overlap from rustc's view. // SAFETY: `drain_deferred_tasks` only touches `self.graph.deferred_*` // fields and the `BundleV2` callback surface; no aliasing UB. @@ -2072,9 +2021,7 @@ pub mod bv2_impl { } pub fn wait_for_parse(&mut self) { - // bundle_v2.zig:488-491 — `this.loop().tick(this, &isDone)`. - // - // PORT NOTE: `tick_raw` (not `tick`) — `is_done` reborrows `*ctx` as + // `tick_raw` (not `tick`) — `is_done` reborrows `*ctx` as // `&mut BundleV2`, and `BundleV2` (via `linker.r#loop`) owns the // `AnyEventLoop` slot, so holding `&mut AnyEventLoop` across the // callback would be a Stacked-Borrows violation. @@ -2085,8 +2032,8 @@ pub mod bv2_impl { .as_ptr(); // SAFETY: `any_loop` points into `self.linker.r#loop`, valid for the // duration of this call; `self_ptr` is the live `&mut self`. The - // callback's `'static` lifetime erasure mirrors the Zig - // `*anyopaque` cast — `is_done` only touches by-value fields. + // callback's `'static` lifetime erasure is storage-only — + // `is_done` only touches by-value fields. unsafe { bun_event_loop::AnyEventLoop::tick_raw(any_loop, self_ptr.cast(), |ctx| { (*ctx.cast::>()).is_done() @@ -2130,8 +2077,8 @@ pub mod bv2_impl { // pick the "module" field and the package is imported with "require" then // code expecting a function will crash. // - // PORT NOTE: reshaped for borrowck — Zig pulled the mutable - // `import_records` column alongside shared columns. `split_mut()` on a + // reshaped for borrowck — the mutable `import_records` column is + // needed alongside shared columns. `split_mut()` on a // value-type `Slice` snapshot yields the one mutable column without // borrowing `self.graph.ast`; read the per-target map through the // disjoint `build_graphs` field instead of the `&mut self` accessor. @@ -2175,11 +2122,13 @@ pub mod bv2_impl { import_record: &jsc_api::JSBundler::MiniImportRecord, target: options::Target, ) { - // PORT NOTE: reshaped for borrowck — Zig held a `*Transpiler` raw pointer alongside - // other `this.*` accesses. `transpiler_for_target` borrows `&mut self`, so launder + // reshaped for borrowck — `transpiler_for_target` borrows `&mut self`, so launder // through a raw pointer to keep `*self` available below. - // SAFETY: the returned `&mut Transpiler` lives for `'a` (set in `init`), is not - // invalidated by anything called here, and Zig aliased it identically. + // SAFETY: the returned `&mut Transpiler` lives for `'a` (set in `init`) and is not + // invalidated by anything called here. No second `&mut` to the same transpiler is + // created while a `&mut` reborrow derived from this raw pointer is live; the later + // direct `self.transpiler.options.*` accesses are shared reads that occur after the + // last `&mut *transpiler` deref on their control path. let transpiler: *mut Transpiler<'a> = self.transpiler_for_target(target); let source_dir = Fs::PathName::init(&import_record.source_file).dir_with_trailing_slash(); @@ -2193,7 +2142,7 @@ pub mod bv2_impl { ) { let file_map_result = _file_map_result; let mut path_primary = file_map_result.path_pair.primary; - // PORT NOTE: reshaped for borrowck — `get_or_put` borrows `*self` mutably via + // reshaped for borrowck — `get_or_put` borrows `*self` mutably via // `self.graph`; capture the slot as `*mut u32` so subsequent `self.*` calls // type-check. SAFETY: `path_to_source_index_map(target)` is not mutated again // until after the last `*value_ptr` access below. @@ -2307,7 +2256,7 @@ pub mod bv2_impl { } let handles_import_errors; - // PORT NOTE: reshaped for borrowck — `log_for_resolution_failures` borrows + // reshaped for borrowck — `log_for_resolution_failures` borrows // `&mut self`; the returned log is backed by either a DevServer-owned slot or // `*self.transpiler.log` (both raw-pointer-derived), so detach the lifetime // so `self.graph.*` / `self.transpiler.*` reads below type-check. @@ -2400,9 +2349,9 @@ pub mod bv2_impl { let out_source_index: Option; - // PORT NOTE(borrowck): Zig held a `*Fs.Path` into `resolve_result` while - // also reading other fields and re-borrowing `self`. Rust borrowck rejects - // that, so we clone the active path out and operate on an owned value. + // borrowck: a `&mut` into `resolve_result` can't be held while + // also reading other fields and re-borrowing `self`, + // so we clone the active path out and operate on an owned value. let mut path: Fs::Path<'static> = match resolve_result.path() { Some(p) => *p, None => { @@ -2438,7 +2387,7 @@ pub mod bv2_impl { path.assert_pretty_is_valid(); path.assert_file_path_is_absolute(); - // PORT NOTE(borrowck): split Zig's `getOrPut` into get-then-put so the map + // borrowck: get-then-put (instead of a single get-or-put) so the map // borrow doesn't span `enqueue_parse_task` (which needs `&mut self`). if let Some(existing) = self.path_to_source_index_map(target).get(path.text) { out_source_index = Some(Index::init(existing)); @@ -2446,8 +2395,7 @@ pub mod bv2_impl { path = self .path_with_pretty_initialized(&path, target) .expect("oom"); - // PORT NOTE: Zig wrote through `path.* = …` (a `*Fs.Path` into - // `resolve_result.path_pair`); the borrowck-reshape above cloned + // The borrowck-reshape above cloned // `path` out, so write the prettified path back so // `ParseTask::init(&resolve_result, ..)` (via `enqueue_parse_task`) // sees the relativized `pretty`. @@ -2499,7 +2447,7 @@ pub mod bv2_impl { // For example, it is silly to bundle index.css depended on by client+server twice. // It makes sense to separate these for JS because the target affects DCE if self.transpiler.options.server_components && !loader.is_javascript_like() { - // PORT NOTE: reshaped for borrowck — cannot hold two `&mut` into + // reshaped for borrowck — cannot hold two `&mut` into // `self.graph` simultaneously, so re-derive the map per insert. let key_text: Box<[u8]> = path.text.to_vec().into_boxed_slice(); let main_target = self.transpiler.options.target; @@ -2541,7 +2489,7 @@ pub mod bv2_impl { target: options::Target, ) -> Result<(), Error> { // TODO: plugins with non-file namespaces - // PORT NOTE(borrowck): split Zig's `getOrPut` into get-then-put so the map + // borrowck: get-then-put (instead of a single get-or-put) so the map // borrow doesn't span the resolver / `&mut self` calls below. if self .path_to_source_index_map(target) @@ -2566,14 +2514,14 @@ pub mod bv2_impl { path = self.path_with_pretty_initialized(&path, target)?; path.assert_pretty_is_valid(); - // PORT NOTE: see `enqueue_entry_item` — write the prettified path back + // see `enqueue_entry_item` — write the prettified path back // into `result` so `ParseTask::init(&result, ..)` reads the relativized - // `pretty` (Zig mutated `result.path_pair.primary` in place via `path.*`). + // `pretty`. result.path_pair.primary = path; self.path_to_source_index_map(target) .put(path_slice, source_index.get()) .expect("oom"); - let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: Zig aborts; port keeps fire-and-forget + let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: fire-and-forget self.graph.input_files.append(crate::Graph::InputFile { source: bun_ast::Source { @@ -2586,7 +2534,7 @@ pub mod bv2_impl { side_effects: result.primary_side_effects_data, ..Default::default() })?; - // Arena-owned (Zig: `arena.create(ParseTask)`); freed on heap reset. + // Arena-owned; freed on heap reset. let task_val = ParseTask::init(&result, source_index, self); // SAFETY: arena outlives the bundle pass; reborrow `*mut` as `&mut`. let task: &mut ParseTask = self.arena_create(task_val); @@ -2628,7 +2576,7 @@ pub mod bv2_impl { target: options::Target, ) -> Result, Error> { let result = &mut *resolve; - // PORT NOTE(borrowck): clone the active path out so we don't hold a `&mut` + // borrowck: clone the active path out so we don't hold a `&mut` // into `result` across the `&mut self` calls below. let mut path: Fs::Path<'static> = match result.path() { Some(p) => *p, @@ -2636,7 +2584,7 @@ pub mod bv2_impl { }; path.assert_file_path_is_absolute(); - // PORT NOTE(borrowck): split Zig's `getOrPut` into get-then-put. + // borrowck: get-then-put instead of a single get-or-put. if self .path_to_source_index_map(target) .get(path.text) @@ -2659,13 +2607,8 @@ pub mod bv2_impl { .into_static() }; path.assert_pretty_is_valid(); - // PORT NOTE: intern via `dupe_alloc` BEFORE writing back into `result` / - // the path-to-source-index map. Zig didn't need this — its dev-server - // `EntryPointList` keys borrow `dev.server_graph.bundled_files.keys()` - // (DevServer-owned), and `genericPathWithPrettyInitialized` returns the - // input `Path` unchanged for `node`-namespace built-ins (e.g. - // `bun-framework-react/server.tsx`), so `path.text` stayed a borrow of - // long-lived storage. The Rust port rebuilds a fresh + // intern via `dupe_alloc` BEFORE writing back into `result` / + // the path-to-source-index map. The dev-server path builds a fresh // `bake_types::EntryPointList` with `Box<[u8]>` keys (DevServer.rs:3027) // that drops as soon as `enqueue_entry_points_dev_server` returns; // `resolve_with_framework` then lifetime-erases that key into the @@ -2675,12 +2618,10 @@ pub mod bv2_impl { // 'bun-framework-react/server.tsx'" when the worker can no longer match // `built_in_modules`. path = path.dupe_alloc(self.arena()).expect("oom"); - // PORT NOTE: Zig's `var path = result.path()` is a `*Fs.Path` *into* - // `result.path_pair`, so the `path.* = pathWithPrettyInitialized(...)` - // assignment mutates the resolver result in place. The borrowck-reshape + // The borrowck-reshape // above cloned `path` out, which left `result.path_pair` with the // unrelativized `pretty` — and `ParseTask::init(&result, ..)` reads - // exactly that field, so the source comment header lost its + // exactly that field, so the source comment header would lose its // `top_level_dir`-relative path. Write the prettified path back here. if let Some(p) = result.path() { *p = path; @@ -2688,7 +2629,7 @@ pub mod bv2_impl { self.path_to_source_index_map(target) .put(path.text, source_index.get()) .expect("oom"); - let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: Zig aborts; port keeps fire-and-forget + let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: fire-and-forget let side_effects = result.primary_side_effects_data; self.graph.input_files.append(crate::Graph::InputFile { @@ -2702,7 +2643,7 @@ pub mod bv2_impl { side_effects, ..Default::default() })?; - // Arena-owned (Zig: `arena.create(ParseTask)`); freed on heap reset. + // Arena-owned; freed on heap reset. let task_val = ParseTask::init(result, source_index, self); // SAFETY: arena outlives the bundle pass; reborrow `*mut` as `&mut`. let task: &mut ParseTask = self.arena_create(task_val); @@ -2757,7 +2698,7 @@ pub mod bv2_impl { thread_pool: Option>, heap: &'a ThreadLocalArena, ) -> Result>, Error> { - // TODO(port): arena-allocate self via bump.alloc — Box::new is wrong arena (Zig: arena.create(@This()) on arena) + // The Box is heap-owned and dropped by the caller. transpiler.env().load_tracy(); transpiler.options.mark_builtins_as_external = @@ -2765,7 +2706,9 @@ pub mod bv2_impl { transpiler.resolver.opts.mark_builtins_as_external = transpiler.options.target.is_bun() || transpiler.options.target == Target::Node; - // SAFETY: aliased *mut for `ssr_transpiler` (Zig stored both as raw ptrs). + // SAFETY: `ssr_transpiler` intentionally aliases `transpiler` via a + // raw `*mut` until bake installs a separate SSR transpiler; all + // derefs go through the centralized accessors. let ssr_alias: *mut Transpiler<'a> = std::ptr::from_mut(transpiler); let mut this = Box::new(BundleV2 { transpiler, @@ -2828,9 +2771,7 @@ pub mod bv2_impl { } } } - // PORT NOTE: Zig wired `heap.arena()` into `transpiler.arena` / - // `resolver.arena` / `linker.arena` / `log.msgs.arena`. The - // Rust `Transpiler<'a>`/`Resolver<'a>` store `&'a Arena` and `Log.msgs` + // `Transpiler<'a>`/`Resolver<'a>` store `&'a Arena` and `Log.msgs` // is a `Vec` (global alloc), so only `linker.graph.bump` needs the // backref into the now-stable `this.graph.heap` slot. this.linker.graph.bump = bun_ptr::BackRef::new(this.graph.heap); @@ -2887,7 +2828,7 @@ pub mod bv2_impl { this.linker.dev_server = this.dev_server; - // Arena-owned (Zig: `arena.create(ThreadPool)`). Coerce to `*mut` + // Arena-owned. Coerce to `*mut` // immediately so the `&this` borrow from `arena()` ends before // `ThreadPool::init` takes `&mut this`. let pool: *mut ThreadPool = @@ -2903,14 +2844,12 @@ pub mod bv2_impl { bun_ptr::BackRef::from(NonNull::new(pool).expect("arena allocation is non-null")); // Install the watcher only after `ThreadPool::init()` has succeeded — // the `?` above is the last early-return in this fn, so the watcher's - // raw `*mut BundleV2` can't outlive the box it points at. (Zig installs - // it before `pool.* = try .init(..)`, but the Rust caller drops the box - // on every error path until `generate_from_cli` leaks it.) + // raw `*mut BundleV2` can't outlive the box it points at (the caller + // drops the box on every error path until `generate_from_cli` leaks it). if cli_watch_flag { // CYCLEBREAK GENUINE: hot_reloader is T6; runtime constructs the // `dispatch::WatcherHandle` (erased owner + `&'static WatcherVTable`) - // via this extern hook and writes `bun_watcher` (Zig: - // `Watcher.enableHotModuleReloading(this, null)` — bundle_v2.zig:994). + // via this extern hook and writes `bun_watcher`. dispatch::enable_hot_module_reloading_for_bundler(core::ptr::from_mut(&mut *this)); } // `Graph::pool` wraps the `BackRef` deref; `start()` takes `&self`. @@ -2923,12 +2862,11 @@ pub mod bv2_impl { } /// Allocate `value` into the bundler's arena (`self.graph.heap`) and return - /// a `&'r mut T` whose lifetime is decoupled from `&self`. Mirrors Zig - /// `arena.create(T)` — the arena owns the slab and reclaims it on + /// a `&'r mut T` whose lifetime is decoupled from `&self`. + /// The arena owns the slab and reclaims it on /// `deinit_without_freeing_arena` / `heap.reset()`. The unbounded `'r` /// releases the `&self` borrow at the call site so callers can immediately - /// reborrow `&mut self` (PORTING.md §Allocators: `bump.alloc(init)` → - /// `&'bump mut T`). + /// reborrow `&mut self`. /// /// SAFETY (encapsulated): the arena slab is pinned and outlives every /// `&mut T` handed out here (freed only at `heap.reset()` after all @@ -2971,7 +2909,7 @@ pub mod bv2_impl { } } - /// RAII form of Zig's `defer this.decrementScanCounter()`. Captures `self` as + /// RAII guard that decrements the scan counter on drop. Captures `self` as /// a raw pointer so the returned guard does not hold a `&mut` borrow for the /// rest of the scope; the caller must ensure `self` outlives the guard. pub fn decrement_scan_counter_on_drop(&mut self) -> ScanCounterGuard { @@ -2980,8 +2918,8 @@ pub mod bv2_impl { } } - // PORT NOTE: split because data type varies by variant — cannot express `switch(variant)`-typed param with const-generic enum on stable - // TODO(port): comptime variant enum param + dependent data type — split into three monomorphic fns + // A const-generic enum param with variant-dependent data cannot be + // expressed on stable Rust, so this is split into three monomorphic fns. pub fn enqueue_entry_points_normal>( &mut self, data: &[P], @@ -3061,9 +2999,9 @@ pub mod bv2_impl { debug_assert_eq!(files.set.keys().len(), files.set.values().len()); for (abs_path, flags) in files.set.keys().iter().zip(files.set.values().iter()) { // Ensure we have the proper conditions set for client-side entrypoints. - // SAFETY: Zig stores `transpiler` as a raw `*Transpiler` across the loop body; - // mirror with `*mut` so it doesn't keep `self` borrowed through the plugin - // dispatch / dev_server calls below. + // SAFETY: hold the transpiler as a `*mut` across the loop body + // so it doesn't keep `self` borrowed through the plugin + // dispatch / dev_server calls below; the pointee lives for `'a`. let transpiler: *mut Transpiler<'a> = if flags.client() && !flags.server() && !flags.ssr() { std::ptr::from_mut(self.transpiler_for_target(Target::Browser)) @@ -3217,7 +3155,7 @@ pub mod bv2_impl { })?; // try this.graph.entry_points.append(arena, Index.runtime); - let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: Zig aborts; port keeps fire-and-forget + let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: fire-and-forget self.path_to_source_index_map(self.transpiler.options.target) .put(&b"bun:wrap"[..], Index::RUNTIME.get()) .expect("oom"); @@ -3241,7 +3179,6 @@ pub mod bv2_impl { fn clone_ast(&mut self) -> Result<(), Error> { let _trace = crate::perf::trace("Bundler.cloneAST"); - // TODO(port): bun.safety.alloc.assertEq self.linker.graph.ast = self.graph.ast.clone()?; for module_scope in self.linker.graph.ast.items_module_scope_mut() { @@ -3335,7 +3272,7 @@ pub mod bv2_impl { // "production build" part of Bake. let keys = named_exports_array[*source_id as usize].keys(); - // PORT NOTE: `G::Property: !Clone` — build via iterator instead of `vec![v; n]`. + // `G::Property: !Clone` — build via iterator instead of `vec![v; n]`. let mut client_manifest_items: Box<[G::Property]> = (0..keys.len()).map(|_| G::Property::default()).collect(); @@ -3531,7 +3468,7 @@ pub mod bv2_impl { known_target: options::Target, ) -> Result { let source_index = Index::init(u32::try_from(self.graph.ast.len()).expect("int cast")); - let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: Zig aborts; port keeps fire-and-forget + let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: fire-and-forget self.graph.input_files.append(crate::Graph::InputFile { source: core::mem::take(source), @@ -3539,9 +3476,9 @@ pub mod bv2_impl { side_effects: loader.side_effects(), ..Default::default() })?; - // PORT NOTE: `ParseTask::init` takes `bun_ast::Index`; both Index newtypes + // `ParseTask::init` takes `bun_ast::Index`; both Index newtypes // are `repr(transparent)` u32 so reconstruct via `.get()`. - // Arena-owned (Zig: `arena.create(ParseTask)`); freed on heap reset. + // Arena-owned; freed on heap reset. let task_val = ParseTask::init( resolve_result, bun_ast::Index::init(source_index.get()), @@ -3584,7 +3521,7 @@ pub mod bv2_impl { known_target: options::Target, ) -> Result { let source_index = Index::init(u32::try_from(self.graph.ast.len()).expect("int cast")); - let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: Zig aborts; port keeps fire-and-forget + let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: fire-and-forget self.graph.input_files.append(crate::Graph::InputFile { source: core::mem::take(source), @@ -3595,11 +3532,9 @@ pub mod bv2_impl { // `core::mem::take` moved the real `Source` into `graph.input_files`, // leaving `*source` as `Default`. Read path/contents back from the // graph's stored copy (where the data now lives for the rest of the - // bundle pass) so the `ParseTask` below sees the actual source bytes — - // matches Zig, which copies `source.*` by value and then reads the - // still-intact original. + // bundle pass) so the `ParseTask` below sees the actual source bytes. let stored = &self.graph.input_files.items_source()[source_index.get() as usize]; - // PORT NOTE: Zig had a single `fs.Path`; Rust split it into + // The path type is split into // `bun_paths::fs::Path<'static>` (on `Source`) and `bun_resolver::fs::Path` // (on `ParseTask`). Convert field-by-field — `pretty`/`namespace` MUST // be preserved here (the SCB `separate_ssr_graph=false` caller passes a @@ -3686,7 +3621,7 @@ pub mod bv2_impl { let mut new_source = source_without_index; let source_index = self.graph.input_files.len(); new_source.index = bun_ast::Index(source_index as u32); - // PORT NOTE: `bun_ast::Source: !Clone` — manually dup the (all-Clone) fields. + // `bun_ast::Source: !Clone` — manually dup the (all-Clone) fields. let task_source = bun_ast::Source { path: new_source.path, contents: new_source.contents.clone(), @@ -3700,14 +3635,14 @@ pub mod bv2_impl { side_effects: bun_ast::SideEffects::HasSideEffects, ..Default::default() })?; - let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: Zig aborts; port keeps fire-and-forget + let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: fire-and-forget - // PORT NOTE: `bun.new(ServerComponentParseTask, …)` — heap-owned by the + // `bun.new(ServerComponentParseTask, …)` — heap-owned by the // worker pool; freed via `bun.destroy` in `on_complete` after the // result posts back to the bundle thread. let task = bun_core::heap::into_raw(Box::new(ServerComponentParseTask { data, - // Lifetime-erase `'a` → `'static` for the BACKREF (matches Zig `*BundleV2`). + // Lifetime-erase `'a` → `'static` for the BACKREF. // `NonNull::from(&mut *self)` carries write provenance for `assume_mut` // in `on_complete`; `ParentRef::from(NonNull)` is the safe wrapper. ctx: Some(bun_ptr::ParentRef::from( @@ -3746,8 +3681,7 @@ pub mod bv2_impl { /// Callback contract for [`DependenciesScanner`]. Each call site's local /// `Analyzer` struct implements this; [`DependenciesScanner::new`] erases the - /// concrete type behind a monomorphized trampoline — Rust's analogue of Zig's - /// one-liner `.onFetch = @ptrCast(&Analyzer.onAnalyze)` (bundle_v2.zig:1492). + /// concrete type behind a monomorphized trampoline. pub trait OnDependenciesAnalyze { fn on_analyze( &mut self, @@ -3758,8 +3692,7 @@ pub mod bv2_impl { impl DependenciesScanner { /// Type-erase `analyzer` into the `(ctx, on_fetch)` pair. The returned /// scanner borrows `*analyzer` for its lifetime: caller must keep - /// `analyzer` alive and exclusively owned until the scan completes - /// (mirrors Zig's stack-local `Analyzer` + `*anyopaque` ctx pattern). + /// `analyzer` alive and exclusively owned until the scan completes. pub fn new( analyzer: &mut A, entry_points: Box<[Box<[u8]>]>, @@ -3855,7 +3788,7 @@ pub mod bv2_impl { &raw const *this.transpiler.options.entry_points; // SAFETY: `transpiler.options.entry_points` is borrowed only for the duration // of `enqueue_entry_points_normal`, which never frees/reallocates it; raw-ptr - // sidestep for the `&mut self` overlap (Zig stored both as raw `*Transpiler`). + // sidestep for the `&mut self` overlap. this.enqueue_entry_points_normal(unsafe { &*entry_points })?; if this.transpiler.log().has_errors() { @@ -3891,14 +3824,13 @@ pub mod bv2_impl { // touches fields disjoint from `this.linker` (`graph`, `transpiler`, // `dynamic_import_entry_points`, scalar reads) via `addr_of_mut!`/place // projection, so the `&mut this.linker` receiver and `*bundle_ptr` never produce - // overlapping `&mut`. (Zig stored all as raw ptrs — bundle_v2.zig:1939.) + // overlapping `&mut`. let mut chunks = unsafe { let bundle_ptr: *mut BundleV2 = &raw mut *this; // `Graph::entry_points: Vec` and `link()` takes `&[Index]` — // both are `crate::Index` (= `bun_ast::Index`), so no cast is needed. let ep = (*bundle_ptr).graph.entry_points.as_slice(); - // Spec passes `this.graph.server_component_boundaries` by value-copy - // (Zig struct copy), leaving the original intact for + // `this.graph.server_component_boundaries` must stay intact for // `StaticRouteVisitor` (generateChunksInParallel) to read via // `parse_graph`. Borrow — do NOT `take`, which would empty the // graph slot and drop the moved-out `MultiArrayList` heap inside @@ -3928,7 +3860,7 @@ pub mod bv2_impl { )?; this.dump_pool_stats("print"); - // Generate metafile if requested (CLI writes files in build_command.zig) + // Generate metafile if requested (the CLI build command writes the files) let metafile: Option> = if this.linker.options.metafile { match crate::linker_context::metafile_builder::generate( &mut this.linker, @@ -3944,7 +3876,7 @@ pub mod bv2_impl { None }; - // Markdown is generated later in build_command.zig for CLI + // Markdown is generated later by the CLI build command Ok(BuildResult { output_files, metafile, @@ -3954,9 +3886,7 @@ pub mod bv2_impl { // Under `--watch` the watcher thread holds `*mut BundleV2` (via the // reloader's `ctx`) and dereferences it in `on_file_update` after this - // function returns. In Zig the `BundleV2` is arena-allocated and the - // arena is never freed (the caller diverges into `exitOrWatch`); in - // Rust it's `Box`-allocated, so leak it here to match the spec lifetime. + // function returns, so leak the Box to keep the pointee alive. // Bounded leak: the next file change `execve()`s the process anyway. if enable_reloading { let _ = Box::into_raw(this); @@ -3973,10 +3903,10 @@ pub mod bv2_impl { /// test entry points transitively depend on a given set of source files. /// /// The caller owns the returned BundleV2. Dupe anything needed out of - /// the graph and then call `deinit_without_freeing_arena()` — in the - /// Rust port the AST columns (`Vec` / `Vec` / …) live on + /// the graph and then call `deinit_without_freeing_arena()` — the + /// AST columns (`Vec` / `Vec` / …) live on /// the global heap, not in `graph.heap`, so leaving the bundle alive is - /// no longer the bounded arena leak the Zig original described. The + /// not a bounded arena leak. The /// worker pool is owned (created with `thread_pool: None`), so tearing /// it down does not touch the runtime VM's parse threads. pub fn scan_module_graph_from_cli( @@ -4063,7 +3993,7 @@ pub mod bv2_impl { let mut chunks = unsafe { let bundle_ptr: *mut BundleV2 = &raw mut *this; let ep = (*bundle_ptr).graph.entry_points.as_slice(); - // Spec: value-copy (original preserved for `StaticRouteVisitor`). + // Value-copy (original preserved for `StaticRouteVisitor`). // Borrow — do NOT `take` (see `generate_from_cli`). let scbs = &(*bundle_ptr).graph.server_component_boundaries; // Project `.linker` via `bundle_ptr` so no second `Box::deref_mut` @@ -4111,7 +4041,7 @@ pub mod bv2_impl { .zip(scbs.list.items_ssr_source_index().iter()) { for idx in [*original_index, *ssr_index] { - self.graph.entry_points.push(bun_ast::Index::init(idx)); // PERF(port): was assume_capacity + self.graph.entry_points.push(bun_ast::Index::init(idx)); } } } @@ -4120,14 +4050,12 @@ pub mod bv2_impl { pub fn process_files_to_copy(&mut self, reachable_files: &[Index]) -> Result<(), Error> { if self.graph.estimated_file_loader_count > 0 { - // PORT NOTE: Zig per-file `arena` column dropped — Box owns its alloc. // SAFETY: MultiArrayList columns are disjoint backing storage; raw-ptr // sidestep so we can hold several read-only column slices, one mutable // column slice (`additional_files`), and call `transpiler_for_target` - // (which needs `&mut self`) inside the loop. Zig accessed all of these - // as raw `.items(.field)` slices with no borrow-checking. + // (which needs `&mut self`) inside the loop. let self_ptr: *mut Self = self; - // SAFETY: see PORT NOTE above — disjoint MultiArrayList columns, + // SAFETY: see note above — disjoint MultiArrayList columns, // raw-ptr sidestep for split-borrow against `transpiler_for_target` // inside the loop. All six column derefs share the same invariant. let ( @@ -4221,9 +4149,9 @@ pub mod bv2_impl { let loader = loaders[index]; - // Zig hands the existing `source.contents` buffer to the - // OutputFile (with its allocator) — no copy. Mirror that by - // moving the contents out instead of `to_vec()`-cloning, + // Hand the existing `source.contents` buffer to the + // OutputFile — no copy: move the contents + // out instead of `to_vec()`-cloning, // which is prohibitively expensive for large assets. let contents_len = source.contents.len(); let contents = match core::mem::take(&mut source.contents) { @@ -4261,7 +4189,7 @@ pub mod bv2_impl { } pub fn on_load_async(&mut self, load: &mut jsc_api::JSBundler::Load) { - // Dispatch to the loop that *owns* `BundleV2` (Zig: `switch (this.loop().*)`). + // Dispatch to the loop that *owns* `BundleV2`. // For `Bun.build` this is a Mini loop running on the bundler thread, so // `on_load` must land there — not on the JS plugin loop — or it will // mutate `graph` / allocate from `graph.heap` off-thread. @@ -4365,7 +4293,7 @@ pub mod bv2_impl { load.source_index.get(), core::mem::discriminant(&load.value) ); - // PORT NOTE: `helpCatchMemoryIssues` was a mimalloc TLH probe; bumpalo has no equivalent. + // `helpCatchMemoryIssues` was a mimalloc TLH probe; bumpalo has no equivalent. let _ = FeatureFlags::HELP_CATCH_MEMORY_ISSUES; // `log_mut()` returns an unbounded `&mut Log` (backref to the // arena/DevServer-owned log) so the `&mut this.graph.*` reborrows @@ -4453,8 +4381,7 @@ pub mod bv2_impl { // watched files and dirs to their respective dependants. let fd = if bun_watcher::REQUIRES_FILE_DESCRIPTORS { let mut buf = bun_paths::path_buffer_pool::get(); - // PORT NOTE: Zig used `std.posix.toPosixPath` (copy + NUL- - // terminate); on kqueue platforms paths are already + // On kqueue platforms paths are already // posix-separated so `z()` alone suffices. match bun_sys::open( bun_paths::resolve_path::z(load.path.as_ref(), &mut *buf), @@ -4468,7 +4395,7 @@ pub mod bv2_impl { bun_sys::Fd::INVALID }; - // Zig: `_ = this.bun_watcher.?.addFile(...) catch {};` + // Failures to watch are intentionally ignored. let _ = this.bun_watcher_mut().unwrap().add_file::( fd, &load.path, @@ -4533,7 +4460,7 @@ pub mod bv2_impl { impl<'a> BundleV2<'a> { pub fn on_resolve(resolve: &mut jsc_api::JSBundler::Resolve, this: &mut BundleV2) { - // Zig: `defer this.decrementScanCounter()`. RAII guard captures `this` + // RAII guard captures `this` // as a raw pointer so it does not hold a unique borrow across the body. let _dec_guard = this.decrement_scan_counter_on_drop(); // `Resolve` is arena-allocated (no Drop); free its owned heap fields on every exit path. @@ -4560,7 +4487,7 @@ pub mod bv2_impl { core::mem::discriminant(&resolve.value) ); - // PORT NOTE: `helpCatchMemoryIssues` was a mimalloc TLH probe; bumpalo has no equivalent. + // `helpCatchMemoryIssues` was a mimalloc TLH probe; bumpalo has no equivalent. let _ = FeatureFlags::HELP_CATCH_MEMORY_ISSUES; match resolve.value.consume() { @@ -4601,8 +4528,7 @@ pub mod bv2_impl { return; } - // SAFETY: Zig's `logForResolutionFailures` returns `*Log` (raw ptr). - // Holding the `&mut bun_ast::Log` borrow would alias `&this.graph` + // SAFETY: Holding the `&mut bun_ast::Log` borrow would alias `&this.graph` // below; detach the lifetime so borrowck releases `this`. The log // lives in `this.transpiler`/`this.framework`, disjoint from // `graph.input_files`. @@ -4647,8 +4573,7 @@ pub mod bv2_impl { // allocations are moved into `this.free_list` below (in the // `!found_existing` branch) and thus outlive `BundleV2`. Erase // to `'static` so `Fs::Path<'static>` can borrow them across - // `path_with_pretty_initialized` / `ParseTask` (mirrors Zig's - // untracked-slice ownership). In the `found_existing`/`external` + // `path_with_pretty_initialized` / `ParseTask`. In the `found_existing`/`external` // branches `path` is dead before the boxes drop, so the dangling // `'static` is never observed. let (result_path_static, result_ns_static): (&'static [u8], &'static [u8]) = unsafe { @@ -4691,7 +4616,7 @@ pub mod bv2_impl { resolve.import_record.original_target, ) .expect("oom"); - // PORT NOTE: `GetOrPutResult` has no `key_ptr` — `get_or_put` already + // `GetOrPutResult` has no `key_ptr` — `get_or_put` already // duped the key into the map (see PathToSourceIndexMap.rs). // We need to parse this @@ -4700,7 +4625,7 @@ pub mod bv2_impl { // SAFETY: map slot from `get_or_put` above; map not mutated since. unsafe { *value_ptr = source_index.get() }; out_source_index = Some(source_index); - let _ = this.graph.ast.append(JSAst::empty_in(this.graph.heap)); // OOM/capacity: Zig aborts; port keeps fire-and-forget + let _ = this.graph.ast.append(JSAst::empty_in(this.graph.heap)); // OOM/capacity: fire-and-forget let loader = path .loader(&this.transpiler.options.loaders) .unwrap_or(Loader::File); @@ -4709,8 +4634,7 @@ pub mod bv2_impl { .input_files .append(crate::Graph::InputFile { source: bun_ast::Source { - // PORT NOTE: Zig assigned `path` (Fs.Path) directly; - // shim to the field-identical `bun_paths::fs::Path<'static>`. + // Shim to the field-identical `bun_paths::fs::Path<'static>`. path: path_as_static(&path), contents: std::borrow::Cow::Borrowed(&b""[..]), index: bun_ast::Index(source_index.get()), @@ -4748,7 +4672,7 @@ pub mod bv2_impl { known_target: resolve.import_record.original_target, ..Default::default() }; - // Arena-owned (Zig: `arena.create(ParseTask)`). + // Arena-owned. // SAFETY: arena outlives the bundle pass. let task: &mut ParseTask = this.arena_create(task_val); task.task.node.next = core::ptr::null_mut(); @@ -4775,7 +4699,6 @@ pub mod bv2_impl { } else { // SAFETY: map slot from `get_or_put` above; map not mutated since. out_source_index = Some(Index::init(unsafe { *value_ptr })); - // PORT NOTE: Zig freed result.{namespace,path} here; Rust drops below. drop(result.namespace); drop(result.path); } @@ -4860,12 +4783,10 @@ pub mod bv2_impl { } } - // Zig spec (bundle_v2.zig:2229): `defer { this.graph.{ast,input_files, - // entry_points,entry_point_original_names}.deinit(this.allocator()) }`. - // In Zig those `MultiArrayList`s only free their slab — every per-element + // Every per-element // payload (file contents, quoted source-map JSON, line-offset tables, …) - // lives in `this.graph.heap` / a per-worker `mi_heap_t`, so the caller's - // `defer heap.deinit()` bulk-frees them. The Rust port now matches that: + // lives in `this.graph.heap` / a per-worker `mi_heap_t`, so the + // arena teardown bulk-frees them: // `LinkerGraph.File.line_offset_table` // is `List` (slab + `columns_for_non_ascii` payloads in the // worker AST heap, see `compute_line_offsets`), and every @@ -4904,7 +4825,7 @@ pub mod bv2_impl { } // Drop the lazily-created client transpiler (if any) before tearing - // down workers — matches the .zig spec ordering where the arena slot + // down workers — the slot // is invalidated ahead of `pool.workers_assignments` so no worker can // observe a half-torn-down transpiler. Clear the `client_transpiler` // alias first so it never dangles past the Box drop; in the @@ -4923,7 +4844,7 @@ pub mod bv2_impl { self.owned_client_transpiler = None; } - // bundle_v2.zig:1426-1437 — worker-assignment teardown. + // Worker-assignment teardown. let pool = self.graph.pool_mut(); { let mut assignments = pool.workers_assignments.lock(); @@ -4989,7 +4910,7 @@ pub mod bv2_impl { let mut chunks = unsafe { let bundle_ptr: *mut BundleV2 = self; let ep = (*bundle_ptr).graph.entry_points.as_slice(); - // Spec: value-copy (original preserved for `StaticRouteVisitor`). + // Value-copy (original preserved for `StaticRouteVisitor`). // Borrow — do NOT `take` (see `generate_from_cli`). let scbs = &(*bundle_ptr).graph.server_component_boundaries; // Project `.linker` via `bundle_ptr` so no `&mut *self` reborrow @@ -5083,9 +5004,8 @@ pub mod bv2_impl { output_kind: crate::options::OutputKind, ) -> Result<(), Error> { if !outdir.is_empty() { - // Open the output directory and write the metafile relative to it. - // PORT NOTE: Zig used `bun.FD.cwd().makeOpenPath()` + - // `NodeFS.writeFileWithPathBuffer`. Route through `bun_sys::File`. + // Open the output directory and write the metafile relative to it, + // routed through `bun_sys::File`. let mut buf = bun_paths::path_buffer_pool::get(); let joined = bun_paths::resolve_path::join_string_buf::< bun_paths::resolve_path::platform::Auto, @@ -5169,16 +5089,16 @@ pub mod bv2_impl { Ok(ctx) } - // TODO(b0-genuine): body has deep DevServer field access (current_bundle.start_data, - // css_entry_points, etc.). After tier-6 collapse this fn should be HOISTED into + // The body has deep DevServer field access (current_bundle.start_data, + // css_entry_points, etc.). After tier-6 collapse this fn should be hoisted into // bun_runtime::bake (which can name DevServer concretely) and call back into BundleV2 // helpers. Until then the entry-point fields are reached through the vtable. pub fn finish_from_bake_dev_server( &mut self, dev_server: &dispatch::DevServerHandle, ) -> Result<(), AllocError> { - // SAFETY: DevServer guarantees `current_bundle` is Some during finish (DevServer.zig:2237). - // The vtable slot returns `*mut ()` derived from `&mut dev.current_bundle.?.start_data`; + // SAFETY: DevServer guarantees `current_bundle` is Some during finish. + // The vtable slot returns `*mut ()` derived from the current bundle's `start_data`; // DevServer holds it exclusively for the duration of finalize, so the `&mut DevServerInput` // here is mut-valid and unaliased until this fn returns. let start = unsafe { @@ -5205,7 +5125,7 @@ pub mod bv2_impl { let asts = self.graph.ast.slice(); let css_asts = asts.items_css(); - // PORT NOTE: SoA columns are physically disjoint slabs but rustc cannot + // SoA columns are physically disjoint slabs but rustc cannot // see that through `&Slice`. Route the two columns we mutate (`parts`, // `import_records`) through `split_raw()` (root-provenance `*mut [T]`, // no `&mut` intermediate) so the per-index `&mut` does not conflict @@ -5220,11 +5140,10 @@ pub mod bv2_impl { let input_files = self.graph.input_files.slice(); let loaders = input_files.items_loader(); let sources = input_files.items_source(); - // TODO(port): multi-zip iteration over MultiArrayList slices [1..] for index in 1..self.graph.ast.len() { - // SAFETY: `index < ast.len()`; see PORT NOTE above for column aliasing. + // SAFETY: `index < ast.len()`; see note above for column aliasing. let part_list = unsafe { &mut *parts_col.add(index) }; - // SAFETY: `index < ast.len()`; see PORT NOTE above for column aliasing. + // SAFETY: `index < ast.len()`; see note above for column aliasing. let import_records = unsafe { &mut *import_records_col.add(index) }; let maybe_css = &css_asts[index]; let target = asts.items_target()[index]; @@ -5238,15 +5157,14 @@ pub mod bv2_impl { // This means the file can become an error after // resolution, which is not usually the case. css_total_files - .push(Index::init(u32::try_from(index).expect("int cast"))); // PERF(port): was assume_capacity + .push(Index::init(u32::try_from(index).expect("int cast"))); let mut log = bun_ast::Log::init(); if LinkerContext::scan_css_imports( u32::try_from(index).expect("int cast"), import_records.as_slice(), - // PORT NOTE: `scan_css_imports` takes the column as a raw + // `scan_css_imports` takes the column as a raw // `*const` slice (the scanImportsAndExports caller holds raw - // SoA pointers); it only reads via `is_none()`. Zig spec - // (`LinkerContext.zig:496`) types this `[]const ?*...`. + // SoA pointers); it only reads via `is_none()`. std::ptr::from_ref(css_asts), sources, loaders, @@ -5281,7 +5199,7 @@ pub mod bv2_impl { (), )?; } else { - js_files.push(Index::init(u32::try_from(index).expect("int cast"))); // PERF(port): was assume_capacity + js_files.push(Index::init(u32::try_from(index).expect("int cast"))); // Part liveness for HMR is seeded after `linker.load` // (every part of every JS file is marked live). @@ -5344,7 +5262,6 @@ pub mod bv2_impl { } } - // TODO(port): leak js_files into arena — Zig returned .items // SAFETY: `alloc_slice_copy` returns into the bundler arena which outlives // this function. Erase the `&self` lifetime via `*const` so the borrow on // `self.arena()` does not extend across the `&mut self` calls below @@ -5376,7 +5293,7 @@ pub mod bv2_impl { unsafe { let bundle_ptr: *mut BundleV2 = self; let ep = (*bundle_ptr).graph.entry_points.as_slice(); - // Spec: value-copy (original preserved). Borrow — do NOT `take`. + // Value-copy (original preserved). Borrow — do NOT `take`. let scbs = &(*bundle_ptr).graph.server_component_boundaries; // Project `.linker` via `bundle_ptr` so no `&mut *self` reborrow // retag invalidates `ep`/`scbs` (SB hygiene). @@ -5411,7 +5328,6 @@ pub mod bv2_impl { } } self.linker.compute_data_for_source_map(js_reachable_files); - // TODO(port): errdefer { bun.outOfMemory() } — caller cannot recover /* arena: help_catch_memory_issues — no-op (mimalloc TLH check) */ @@ -5431,7 +5347,7 @@ pub mod bv2_impl { }; } - // PORT NOTE: `Chunk: !Default` (Vec fields). Allocate via Vec then + // `Chunk: !Default` (Vec fields). Allocate via Vec then // leak into the arena. let mut chunks: Vec = Vec::with_capacity(1 + start.css_entry_points.count() + html_files.count()); @@ -5489,7 +5405,7 @@ pub mod bv2_impl { ..Chunk::default() }); } - // Arena-owned (Zig allocates `chunks` from `this.arena()`); the + // Arena-owned; the // `DevServerOutput` lifetime is documented as "tied to the bundler's // arena". `alloc_slice_fill_iter` moves each `Chunk` into the bump. let chunks: *mut [Chunk] = @@ -5504,7 +5420,6 @@ pub mod bv2_impl { chunks, ) .map_err(|_| AllocError)?; - // TODO(port): errdefer { bun.outOfMemory() } — caller cannot recover /* arena: help_catch_memory_issues — no-op (mimalloc TLH check) */ @@ -5529,10 +5444,10 @@ pub mod bv2_impl { original_target: options::Target, ) -> bool { if let Some(plugins) = self.plugins_ref() { - // PORT NOTE: `ImportRecord.path` is `bun_paths::fs::Path`; `has_any_matches` + // `ImportRecord.path` is `bun_paths::fs::Path`; `has_any_matches` // takes the structurally-identical `bun_resolver::fs::Path`. Rebuild the - // resolver-crate variant from the same backing slices (Zig has a single - // `Fs.Path` type — the FFI side only reads `.text` / `.namespace`). + // resolver-crate variant from the same backing slices (the FFI side + // only reads `.text` / `.namespace`). let match_path = Fs::Path::init_with_namespace( import_record.path.text, import_record.path.namespace, @@ -5547,7 +5462,7 @@ pub mod bv2_impl { ); self.increment_scan_counter(); - // Arena-owned (Zig: `arena.create(Resolve)`); the dispatch + // Arena-owned; the dispatch // chain holds the raw `*mut Resolve` until the JS thread calls // back, at which point the bundle pass is still alive. // SAFETY: arena outlives the bundle pass. @@ -5590,7 +5505,7 @@ pub mod bv2_impl { bstr::BStr::new(entry_point) ); - // Arena-owned (Zig: `arena.create(Resolve)`). + // Arena-owned. // SAFETY: arena outlives the bundle pass. let resolve: &mut jsc_api::JSBundler::Resolve = self.arena_create(jsc_api::JSBundler::Resolve::default()); @@ -5633,8 +5548,7 @@ pub mod bv2_impl { let Ok(maybe_decoded) = data_url.decode_data() else { return false; }; - // Zig: `this.free_list.append(decoded); parse.contents_or_fd = .{ .contents = decoded };` - // — the SAME allocation is both tracked for free at `deinit` and + // The SAME allocation is both tracked for free at `deinit` and // borrowed as the parse-task contents. `free_list` owns it for the // bundle's lifetime; `ParseTask` is strictly shorter-lived, so the // raw-slice borrow is sound. No clone, no leak. @@ -5665,7 +5579,7 @@ pub mod bv2_impl { bstr::BStr::new(&parse.path.namespace), bstr::BStr::new(&parse.path.text) ); - // Arena-owned (Zig: `arena.create(Load)`); the dispatch + // Arena-owned; the dispatch // chain holds the raw `*mut Load` until the JS thread calls back. let load_val = jsc_api::JSBundler::Load::init(self, parse); // SAFETY: arena outlives the bundle pass. @@ -5711,9 +5625,9 @@ pub mod bv2_impl { self.graph.ast.ensure_unused_capacity(2)?; self.graph.input_files.ensure_unused_capacity(2)?; - // PORT NOTE: Zig copied `bake.server_virtual_source` by value. The Rust - // statics are `LazyLock` and `Source` is not `Clone`, so rebuild - // an owned `Source` from the static's clonable fields (`path`, `index`). + // The statics are `LazyLock` and `Source` is not `Clone`, so + // rebuild an owned `Source` from the static's clonable fields + // (`path`, `index`). let server_source = bun_ast::Source { path: bake::SERVER_VIRTUAL_SOURCE.path, index: bake::SERVER_VIRTUAL_SOURCE.index, @@ -5725,20 +5639,20 @@ pub mod bv2_impl { ..Default::default() }; - // OOM/capacity: Zig aborts; port keeps fire-and-forget + // OOM/capacity: fire-and-forget let _ = self.graph.input_files.append(crate::Graph::InputFile { source: server_source, loader: Loader::Js, side_effects: bun_ast::SideEffects::NoSideEffectsPureData, ..Default::default() - }); // PERF(port): was assume_capacity - // OOM/capacity: Zig aborts; port keeps fire-and-forget + }); + // OOM/capacity: fire-and-forget let _ = self.graph.input_files.append(crate::Graph::InputFile { source: client_source, loader: Loader::Js, side_effects: bun_ast::SideEffects::NoSideEffectsPureData, ..Default::default() - }); // PERF(port): was assume_capacity + }); debug_assert!( self.graph.input_files.items_source()[Index::BAKE_SERVER_DATA.get() as usize] @@ -5753,13 +5667,13 @@ pub mod bv2_impl { == Index::BAKE_CLIENT_DATA.get() ); - let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // PERF(port): was assume_capacity - let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // PERF(port): was assume_capacity + let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); + let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); Ok(()) } // See barrel_imports.rs for barrel optimization implementation. - // PORT NOTE: Zig `pub usingnamespace`-style method aliases. `pub use` is not + // `pub use` is not // permitted in `impl` blocks; the underlying fns live in `barrel_imports` and // take `&mut BundleV2` directly — callers reach them as free functions. // (was: pub use barrel_imports::{apply_barrel_optimization, schedule_barrel_deferred_imports}) @@ -5913,7 +5827,6 @@ pub mod bv2_impl { if let Some(fw) = &self.framework { if fw.server_components.is_some() { - // PERF(port): was comptime bool dispatch — profile if hot. let is_server = ctx.target.is_server_side(); let src = if is_server { &bake::SERVER_VIRTUAL_SOURCE @@ -6012,11 +5925,11 @@ pub mod bv2_impl { continue; } - // PORT NOTE: borrowck — `transpiler_for_target` returns `&mut Transpiler` + // borrowck — `transpiler_for_target` returns `&mut Transpiler` // tied to `&mut self`, but the underlying storage is raw `*mut Transpiler` // backrefs valid for `'a` (see `init`). Compute the raw ptr first, then // deref once, so the `&mut self` borrow doesn't span the rest of the loop - // body (Zig held all of these as raw ptrs and aliased freely). + // body. let (transpiler_ptr, bake_graph, target): ( *mut Transpiler<'a>, bake::Graph, @@ -6063,7 +5976,7 @@ pub mod bv2_impl { ctx.target, ) }; - // SAFETY: see PORT NOTE above — raw `*mut Transpiler` lives for `'a`. + // SAFETY: see note above — raw `*mut Transpiler` lives for `'a`. let transpiler: &mut Transpiler<'a> = unsafe { &mut *transpiler_ptr }; // Check the FileMap first for in-memory files @@ -6114,7 +6027,7 @@ pub mod bv2_impl { bstr::BStr::new(&path_primary.text) ); file_map_result.path_pair.primary = path_primary; - // Arena-owned (Zig: `arena.create(ParseTask)`). + // Arena-owned. let resolve_task_val = ParseTask::init(&file_map_result, bun_ast::Index::INVALID, self); // SAFETY: arena outlives the bundle pass. @@ -6146,7 +6059,7 @@ pub mod bv2_impl { ) { Ok(r) => break r, Err(err) => { - // PORT NOTE: borrowck — `log_for_resolution_failures` returns + // borrowck — `log_for_resolution_failures` returns // `&mut Log` tied to `&mut self`, but it's always a raw-ptr // deref (DevServer vtable or `transpiler.log`). Detach via // `*mut` so later `self.*` reads don't conflict. @@ -6313,13 +6226,12 @@ pub mod bv2_impl { continue; } - // PORT NOTE: borrowck — Zig `Result.path()` returns `?*Path` (raw), - // letting the loop body keep reading other `resolve_result` fields - // (`.flags`, `.path_pair`, `.primary_side_effects_data`, `.jsx`). - // The Rust port returns `Option<&mut Path>`, which would lock the - // whole struct. Detach via raw ptr to mirror the Zig aliasing. + // borrowck — `Result.path()` returns `Option<&mut Path>`, which + // would lock the whole struct while the loop body still needs to + // read other `resolve_result` fields (`.flags`, `.path_pair`, + // `.primary_side_effects_data`, `.jsx`). Detach via raw ptr. let path: &mut Fs::Path = match resolve_result.path() { - // SAFETY: `resolve_result` outlives this borrow; see PORT NOTE above. + // SAFETY: `resolve_result` outlives this borrow; see note above. Some(p) => unsafe { bun_ptr::detach_lifetime_mut::(p) }, None => { import_record.path.is_disabled = true; @@ -6479,7 +6391,7 @@ pub mod bv2_impl { import_record.path = path_as_static(path); // key already interned by get_or_put — no key_ptr on StringHashMapGetOrPut bun_core::scoped_log!(Bundle, "created ParseTask: {}", bstr::BStr::new(&path.text)); - // Arena-owned (Zig: `arena.create(ParseTask)`). + // Arena-owned. let resolve_task_val = ParseTask::init(&resolve_result, bun_ast::Index::INVALID, self); // SAFETY: arena outlives the bundle pass. @@ -6531,8 +6443,8 @@ pub mod bv2_impl { importer_source_index: IndexInt, ) -> i32 { let mut diff: i32 = 0; - // PORT NOTE: reshaped for borrowck — Zig freely aliased `graph` and the - // path map across the loop body. Here we (a) capture a raw self ptr for + // reshaped for borrowck — `graph` and the + // path map are both needed across the loop body. We (a) capture a raw self ptr for // ParseTask.ctx, (b) hoist dev_server check, and (c) scope the map // borrow to the get_or_put so later `self.graph.*` writes don't overlap. // SAFETY: write provenance from `ptr::from_mut`; outlives every ParseTask. @@ -6607,7 +6519,7 @@ pub mod bv2_impl { .input_files .append(new_input_file) .expect("unreachable"); - let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: Zig aborts; port keeps fire-and-forget + let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: fire-and-forget if is_html_entrypoint { self.ensure_client_transpiler(); @@ -6647,7 +6559,7 @@ pub mod bv2_impl { // ParseTask is arena-allocated; the slab itself is reclaimed on // arena reset, but its heap-owned fields (path/jsx clones) need - // their destructors run now (Zig: `value.deinit()`). + // their destructors run now. // SAFETY: `value` is a live arena slot; not used after this. unsafe { core::ptr::drop_in_place(value) }; } @@ -6693,8 +6605,8 @@ pub mod bv2_impl { import_records: &mut import_record::List, ctx: PatchImportRecordsCtx, ) { - // PORT NOTE: Zig aliased `const graph = &this.graph;`. Borrowck rejects - // holding that across the `&mut self.graph.build_graphs[...]` borrow + // Borrowck rejects holding a `&self.graph` alias + // across the `&mut self.graph.build_graphs[...]` borrow // below, so address the disjoint `self.graph.*` fields directly instead. let input_file_loaders = self.graph.input_files.items_loader(); let save_import_record_source_index = ctx.force_save @@ -6733,7 +6645,7 @@ pub mod bv2_impl { if let Some(compare) = get_redirect_id(ctx.redirect_import_record_index) { if compare == i as u32 { - let _ = path_to_source_index_map.put(ctx.source_path, source_index); // OOM-only Result (Zig: catch unreachable) + let _ = path_to_source_index_map.put(ctx.source_path, source_index); // OOM-only Result } } } @@ -6750,7 +6662,7 @@ pub mod bv2_impl { // 1. Create the ast right here // 2. Create a separate "virutal" module that becomes the manifest later on. // 3. Add it to the graph - // PORT NOTE: Zig aliased `graph = &this.graph;` — re-borrow `self.graph` + // Re-borrow `self.graph` // at each use so the `self.*` method calls below don't conflict. let heap = self.graph.heap; let empty_html_file_source: &mut bun_ast::Source = self.arena_create(bun_ast::Source { @@ -6822,12 +6734,12 @@ pub mod bv2_impl { let fake_source_index = fake_input_file.source.index; self.graph.input_files.append(fake_input_file)?; - let _ = self.graph.ast.append(ast_for_html_entrypoint); // OOM/capacity: Zig aborts; port keeps fire-and-forget + let _ = self.graph.ast.append(ast_for_html_entrypoint); // OOM/capacity: fire-and-forget import_record.source_index = Index::init(fake_source_index.0); let _ = self .path_to_source_index_map(target) - .put(path_text, fake_source_index.0); // OOM-only Result (Zig: catch unreachable) + .put(path_text, fake_source_index.0); // OOM-only Result self.graph .html_imports .server_source_indices @@ -6850,8 +6762,8 @@ pub mod bv2_impl { pub fn on_parse_task_complete(parse_result: &mut parse_task::Result, this: &mut BundleV2) { let _trace = crate::perf::trace("Bundler.onParseTaskComplete"); - // PORT NOTE: Zig aliased `const graph = &this.graph;`. Borrowck rejects - // holding that across the `this.*` method calls below (each takes + // Borrowck rejects holding a `&this.graph` alias + // across the `this.*` method calls below (each takes // `&mut BundleV2`), so re-borrow `this.graph` at each use site instead. if parse_result.external.function.is_some() { let source = match &parse_result.value { @@ -6860,7 +6772,7 @@ pub mod bv2_impl { parse_task::ResultValue::Success(val) => val.source.index.0, }; let loader: Loader = this.graph.input_files.items_loader()[source as usize]; - // PORT NOTE: `InputFile.arena` column dropped in the Rust port; + // `InputFile.arena` column dropped in the Rust port; // stash the finalizer regardless so plugin-owned bytes are freed. let _ = loader; this.finalizers @@ -6868,11 +6780,10 @@ pub mod bv2_impl { } // defer bun.default_allocator.destroy(parse_result) — caller owns Box and drops at end - // TODO(port): parse_result is heap-allocated by worker; reconstruct heap::take at scope exit let mut diff: i32 = -1; - // PORT NOTE: Zig used `defer { graph.pending_items += diff; … }` — - // hoisted to tail position (see end of fn) so the closure doesn't + // The pending-items adjustment is + // hoisted to tail position (see end of fn) so a deferred closure doesn't // double-borrow `graph`/`this`. let mut resolve_queue = ResolveQueue::default(); @@ -6894,14 +6805,14 @@ pub mod bv2_impl { parse_task::ResultValue::Err(data) => data.source_index.get(), parse_task::ResultValue::Success(val) => val.source.index.0, }; - // PORT NOTE: borrowck — read source path/loader before + // borrowck — read source path/loader before // `should_add_watcher(&self)` so the column borrow is released. let source_path = this.graph.input_files.items_source()[source_index as usize] .path .text; let loader = this.graph.input_files.items_loader()[source_index as usize]; if this.should_add_watcher(source_path) { - // PORT NOTE: const generic `CLONE_FILE_PATH = isWindows` + // const generic `CLONE_FILE_PATH = isWindows` // matches `cfg!(windows)` at compile time. let _ = this .bun_watcher_mut() @@ -6965,7 +6876,6 @@ pub mod bv2_impl { &mut result.source.contents, ); } - // PORT NOTE: Zig kept `source` as a stable pointer into the SoA. // Borrowck forbids holding `&input_files.source[i]` while writing // other `input_files` columns through the MultiArrayList accessor // methods (each takes `&mut input_files`), so copy out the @@ -7052,7 +6962,7 @@ pub mod bv2_impl { // Set is_export_star_target for barrel optimization. // In dev server mode, source_index is not saved on JS import // records, so fall back to resolving via the path map. - // PORT NOTE: split-borrow `Graph` fields directly so the + // split-borrow `Graph` fields directly so the // `&build_graphs[target]` lookup doesn't lock out // `input_files.items_flags_mut()` (disjoint columns). let result_ast_target = result.ast.target; @@ -7074,12 +6984,9 @@ pub mod bv2_impl { } result.ast.import_records = import_records; - // PORT NOTE: Zig reads `result.ast.named_exports` / - // `result.source` *after* `graph.ast.set(…)` (Zig structs are - // value types so the `set` is a shallow copy). The Rust port - // moves `result.ast` into `graph.ast` and swapped `result.source` - // earlier, so snapshot the data the use-directive block needs - // *before* the move. Only paid for files that hit the SCB gate. + // `result.ast` is moved into `graph.ast` and `result.source` was + // swapped earlier, so snapshot the data the use-directive block + // needs *before* the move. Only paid for files that hit the SCB gate. let named_exports_for_scb = if result.use_directive != crate::UseDirective::None && { let separate = this @@ -7134,9 +7041,9 @@ pub mod bv2_impl { .unwrap() .separate_ssr_graph; - // PORT NOTE: `result.source` was swapped into - // `graph.input_files` earlier; re-borrow it from the SoA. - // `.clone()` materializes the value-copy Zig got for free. + // `result.source` was swapped into + // `graph.input_files` earlier; re-borrow it from the SoA + // and `.clone()` where an owned copy is needed. let source_loader: Loader = this.graph.input_files.items_loader()[result_source_index]; @@ -7160,7 +7067,7 @@ pub mod bv2_impl { let mut ssr_source = this.graph.input_files.items_source()[result_source_index].clone(); - // PORT NOTE: `path_with_pretty_initialized` takes/returns + // `path_with_pretty_initialized` takes/returns // `Fs::Path` (`bun_resolver::fs::Path`); bridge through // `fs_path_from_logger`/`fs_path_to_logger` until the // three `Path` mirrors unify. @@ -7250,7 +7157,6 @@ pub mod bv2_impl { err.log .clone_to_with_recycled(this.transpiler.log_mut(), true); } else { - // PORT NOTE: Zig used `@tagName(err.step)`. let step_name = match err.step { crate::parse_task::Step::Pending => "pending", crate::parse_task::Step::ReadFile => "read_file", @@ -7657,8 +7563,6 @@ pub mod bv2_impl { ProbablyTypescriptType, } - /// `bundle_v2.zig:ImportTracker.Iterator`. - /// /// `import_data` is a raw slice into /// `graph.meta[i].resolved_exports[..].potentially_ambiguous_export_star_refs`. /// The graph SoA is never reallocated during `match_import_with_export`, so @@ -7753,7 +7657,7 @@ pub mod bv2_impl { } impl ExternalFreeFunctionAllocator { - // TODO(refactor): could implement `bun_alloc::Allocator` instead of the manual vtable. + // (Could implement `bun_alloc::Allocator` instead of the manual vtable.) fn free(ext_free_function: *mut c_void, _: &mut [u8], _: bun_alloc::Alignment, _: usize) { // SAFETY: ptr was created by ExternalFreeFunctionAllocator::create @@ -7767,8 +7671,7 @@ pub mod bv2_impl { } /// `pub` so `bun_runtime::allocators::register_safety_vtables` can push the - /// address into the `bun_safety` registry (Zig spec: `bun.bundle_v2. - /// allocatorHasPointer` is one of the `safety/alloc.zig:hasPtr` arms). + /// address into the `bun_safety` registry. pub static EXTERNAL_FREE_VTABLE: bun_alloc::AllocatorVTable = bun_alloc::AllocatorVTable { alloc: |_, _, _, _| core::ptr::null_mut(), resize: |_, _, _, _, _| false, @@ -7792,6 +7695,4 @@ pub mod bv2_impl { // C++ binding for lazy metafile getter (defined in BundlerMetafile.cpp) // Uses jsc.conv (SYSV_ABI on Windows x64) for proper calling convention // Sets up metafile object with { json: , markdown?: string } - - // ported from: src/bundler/bundle_v2.zig } diff --git a/src/bundler/bundled_ast.rs b/src/bundler/bundled_ast.rs index 02a103c1daf..849428d03a5 100644 --- a/src/bundler/bundled_ast.rs +++ b/src/bundler/bundled_ast.rs @@ -11,7 +11,7 @@ // erasure that forced every bundler/linker call site to `.cast()` back. pub use bun_css::BundlerStyleSheet; -/// Arena-owned handle to a parsed CSS stylesheet (Zig: `*bun.css.BundlerStyleSheet`). +/// Arena-owned handle to a parsed CSS stylesheet. /// /// The pointee lives in a per-file `Bump` whose ownership is held by /// `Graph.heap` (bumps are `Pin>` owned by the @@ -39,13 +39,9 @@ pub(crate) type NamedExports = bun_ast::ast_result::NamedExports; pub(crate) type NamedImports = bun_ast::ast_result::NamedImports; pub type TopLevelSymbolToParts = bun_ast::ast_result::TopLevelSymbolToParts; -// PORT NOTE: Zig stores `MultiArrayList(BundledAst)` on `Graph.ast` / -// `LinkerGraph.ast` and the bundler indexes columns via `.items(.field)` -// (see `linker_context/scanImportsAndExports.zig`, `LinkerContext.zig`). -// `` generates the `BundledAstField` enum + -// `BundledAstColumns`/`BundledAstColumns` (`items_named_imports()`, -// `items_named_exports()`, …) that those callers expect at -// `crate::bundled_ast::*`. +// `multi_array_columns!` generates the `BundledAstField` enum + +// `BundledAstColumns` (`items_named_imports()`, +// `items_named_exports()`, …) at `crate::bundled_ast::*`. // // 26 fields ≤ `multi_array_list::MAX_FIELDS` (32). @@ -59,17 +55,17 @@ pub struct BundledAst<'arena> { /// they can be manipulated efficiently without a full AST traversal pub import_records: import_record::List<'arena>, - // PORT NOTE: Ast.hashbang is `StoreStr`; mirror it here so init/to_ast can + // Ast.hashbang is `StoreStr`; mirror it here so init/to_ast can // round-trip. pub hashbang: StoreStr, pub parts: part::List<'arena>, - // Zig: `?*bun.css.BundlerStyleSheet`. See `CssAstRef` doc for the arena - // drop-order invariant that backs the safe `Deref`. + // See `CssAstRef` doc for the arena drop-order invariant that backs the + // safe `Deref`. pub css: CssCol, pub url_for_css: &'arena [u8], pub symbols: symbol::List<'arena>, pub module_scope: Scope, - // TODO(port): Zig used `= undefined`; only valid when flags.HAS_CHAR_FREQ is set. + // Only meaningful when flags.HAS_CHAR_FREQ is set; zero-initialized otherwise. pub char_freq: CharFreq, pub exports_ref: Ref, pub module_ref: Ref, @@ -141,7 +137,7 @@ bitflags::bitflags! { // closure. const USES_EXPORTS_REF = 1 << 0; const USES_MODULE_REF = 1 << 1; - // const USES_REQUIRE_REF = 1 << 2; (commented out in Zig; bit positions still match field order) + // const USES_REQUIRE_REF = 1 << 2; const USES_EXPORT_KEYWORD = 1 << 2; const HAS_CHAR_FREQ = 1 << 3; const FORCE_CJS_TO_ESM = 1 << 4; @@ -154,10 +150,9 @@ bitflags::bitflags! { } impl<'arena> BundledAst<'arena> { - // Zig: `pub const empty = BundledAst.init(Ast.empty)` (comptime). The three `ArenaVec` - // fields prevent `const fn` here, but spell out the defaults directly instead of - // round-tripping through `Ast::empty_in` + `init` — this runs once per discovered - // module on the main thread. + // The three `ArenaVec` fields prevent `const fn` here, but spell out the + // defaults directly instead of round-tripping through `Ast::empty_in` + + // `init` — this runs once per discovered module on the main thread. pub fn empty_in(arena: &'arena bun_alloc::Arena) -> Self { Self { approximate_newline_count: 0, @@ -189,9 +184,8 @@ impl<'arena> BundledAst<'arena> { } } - // PORT NOTE: Zig's `*const BundledAst` bitwise-copies every field; the Rust - // collection types aren't Copy, so consume `self` to move them (toAST is a - // one-shot conversion back to the fat Ast). + // The collection types aren't Copy, so consume `self` to move them (to_ast + // is a one-shot conversion back to the fat Ast). pub fn to_ast(self) -> Ast<'arena> { let arena: &'arena bun_alloc::Arena = *self.parts.allocator(); Ast { @@ -303,7 +297,7 @@ impl<'arena> BundledAst<'arena> { // This list may be mutated later, so we should store the capacity symbols: ast.symbols, module_scope: ast.module_scope, - // Only read when flags.HAS_CHAR_FREQ is set; Zig used `orelse undefined`. + // Only read when flags.HAS_CHAR_FREQ is set. char_freq: ast.char_freq.unwrap_or_default(), exports_ref: ast.exports_ref, module_ref: ast.module_ref, @@ -368,10 +362,9 @@ impl<'arena> BundledAst<'arena> { let encode_len = bun_base64::encode_len(contents); let data_url_prefix_len = b"data:".len() + mime_type.len() + b";base64,".len(); let total_buffer_len = data_url_prefix_len + encode_len; - // PERF(port): was arena alloc via `arena.alloc(u8, n)`; using bumpalo here. let encoded: &mut [u8] = bump.alloc_slice_fill_copy(total_buffer_len, 0u8); - // PORT NOTE: Zig's std.fmt.bufPrint with `{s}` writes raw bytes; BStr's Display - // would emit 3-byte U+FFFD for non-UTF-8 input and overflow the fixed prefix slice. + // Write raw bytes directly; BStr's Display would emit 3-byte + // U+FFFD for non-UTF-8 input and overflow the fixed prefix slice. encoded[..5].copy_from_slice(b"data:"); encoded[5..5 + mime_type.len()].copy_from_slice(mime_type); encoded[5 + mime_type.len()..data_url_prefix_len].copy_from_slice(b";base64,"); @@ -381,5 +374,3 @@ impl<'arena> BundledAst<'arena> { } } } - -// ported from: src/js_parser/ast/BundledAst.zig diff --git a/src/bundler/cache.rs b/src/bundler/cache.rs index a75ff3c8dd0..aee00128dd5 100644 --- a/src/bundler/cache.rs +++ b/src/bundler/cache.rs @@ -25,21 +25,20 @@ use js_parser::defines::Define; use bun_ast::RuntimeTranspilerCache; /// Bump when the cache wire format or parser output changes. Mirrors -/// `expected_version` in src/jsc/RuntimeTranspilerCache.zig. +/// `EXPECTED_VERSION` in src/jsc/RuntimeTranspilerCache.rs. pub const RUNTIME_TRANSPILER_CACHE_VERSION: u32 = 20; -/// Mirrors the Zig `pub var is_disabled` mutable global — written by T6 -/// (src/runtime/cli/Arguments.zig:1603, src/jsc/VirtualMachine.zig:1383) and -/// flipped lazily on cache-dir resolution failure. Module-level so those -/// writers can reach it; `disabled()` reads it. +/// Written by CLI argument parsing and `VirtualMachine` init, and flipped +/// lazily on cache-dir resolution failure. Module-level so those writers can +/// reach it; `disabled()` reads it. pub static DISABLED: AtomicBool = AtomicBool::new(false); /// Extension surface for the canonical `RuntimeTranspilerCache` (defined in /// `bun_js_parser`). Separate trait so the env-var-dependent bodies stay in /// this crate without an orphan-rule violation. pub trait RuntimeTranspilerCacheExt { - /// Mirrors the Zig `pub var is_disabled` namespaced const — kept as an - /// associated fn so call-sites read `RuntimeTranspilerCache::disabled()`. + /// Kept as an associated fn so call-sites read + /// `RuntimeTranspilerCache::disabled()`. fn disabled() -> bool; fn set_disabled(v: bool); } @@ -60,12 +59,10 @@ impl RuntimeTranspilerCacheExt for RuntimeTranspilerCache { } } -/// Mirrors `RuntimeTranspilerCache.Encoding` (RuntimeTranspilerCache.zig:405). -/// -/// PORT NOTE: this is the on-disk wire enum for `Metadata.output_encoding` — +/// This is the on-disk wire enum for `Metadata.output_encoding` — /// NOT `js_parser::ExportsKind` (an unrelated `#[repr(u8)]` enum that happens /// to start at 0). The bundler-side cache loader maps `Latin1`/`Utf16` blobs -/// into a `bun.String` (RuntimeTranspilerCache.zig:310-318) and only feeds +/// into a `bun.String` and only feeds /// `Utf8` through `cloneUTF8`; callers must dispatch on these discriminants. #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -77,9 +74,7 @@ pub enum CacheEncoding { Latin1 = 3, } -/// Mirrors `RuntimeTranspilerCache.ModuleType` (RuntimeTranspilerCache.zig:399). -/// -/// PORT NOTE: NOT `options::ModuleType` — the on-disk wire enum has `Esm`/`Cjs` +/// On-disk wire enum — NOT `options::ModuleType`: it has `Esm`/`Cjs` /// **swapped** relative to the in-memory parser/options enum (`Unknown=0, /// Cjs=1, Esm=2`). Comparing `metadata.module_type` against /// `options::ModuleType::Cjs as u8` would test for `.esm`. @@ -124,8 +119,8 @@ pub struct RuntimeTranspilerCacheMetadata { } impl Default for RuntimeTranspilerCacheMetadata { - /// Spec (src/jsc/RuntimeTranspilerCache.zig:42) defaults - /// `cache_version: u32 = expected_version` — derived `Default` would zero it, + /// The wire format defaults `cache_version` to the expected version — + /// derived `Default` would zero it, /// causing every freshly-written entry to be rejected as `error.StaleCache` /// on first read. fn default() -> Self { @@ -156,9 +151,8 @@ pub struct Set { } impl Set { - /// Port of `Set.init` (cache.zig:6). PORT NOTE: `arena` is unused — - /// `MutableString::init`/`JavaScript::init` source from the global heap in - /// the Rust port; param kept so callers match the Zig signature + /// `arena` is unused — `MutableString::init`/`JavaScript::init` source + /// from the global heap; param kept for caller compatibility /// (`crate::cache::Set::init(alloc)`). pub fn init(_arena: &Bump) -> Set { Set { @@ -206,8 +200,7 @@ impl Default for Fs { // ══════════════════════════════════════════════════════════════════════════ pub use bun_resolver::cache::{Contents, Entry, ExternalFreeFunction}; -/// Legacy alias — several call sites import `crate::cache::CacheEntry` -/// (mirrors Zig's `bun.transpiler.cache.Fs.Entry` qualified name). +/// Legacy alias — several call sites import `crate::cache::CacheEntry`. pub type CacheEntry = Entry; impl Fs { @@ -225,9 +218,9 @@ impl Fs { /// When we need to suspend/resume something that has pointers into the shared buffer, we need to /// switch out the shared buffer so that it is not in use. /// - /// Ownership transfer: in Zig (cache.zig:77/79) the field is overwritten WITHOUT freeing - /// the old buffer, because the suspended parse keeps pointers into it (see ModuleLoader.zig:488, - /// "this shared buffer is about to become owned by the AsyncModule struct"). In Rust, plain + /// Ownership transfer: the old buffer must NOT be freed here, because the + /// suspended parse keeps pointers into it (the shared buffer becomes owned + /// by the AsyncModule struct in the module loader). Plain /// field assignment would drop+free the old buffer → use-after-free on resume. So we return /// the detached buffer; the caller MUST take ownership of it and keep it alive for as long as /// `parse_result.source.contents` may be read. @@ -241,9 +234,8 @@ impl Fs { } } - // TODO(port): Zig `Fs.deinit` references `c.entries` which is not a field on `Fs` — - // dead code (Zig lazy compilation never instantiated it). No Drop impl needed beyond - // the auto-drop of `shared_buffer` / `macro_shared_buffer`. + // No Drop impl needed beyond the auto-drop of `shared_buffer` / + // `macro_shared_buffer`. } // File reads route through the canonical `bun_resolver::fs::read_file_contents` @@ -323,24 +315,20 @@ impl Fs { self.read_file_with_allocator(_fs, path, dirname_fd, use_shared_buffer, _file_handle, None) } - /// Port of `Fs.readFileWithAllocator` (cache.zig:146). - /// - /// PORT NOTE: `comptime use_shared_buffer` is taken at runtime — the live + /// `use_shared_buffer` is taken at runtime — the live /// callers (`ParseTask::get_code_for_parse_task_without_plugins`, /// `Transpiler::parse`) pass a value computed from runtime state, and the /// resolver's `FsCache` forward-decl already pinned this shape. - /// PERF(port): re-monomorphize once both callers stabilize. + /// PERF: re-monomorphize once both callers stabilize. /// - /// `arena` restores the Zig `allocator` param: when + /// `arena`: when /// `!use_shared_buffer && arena.is_some()` the file body is read straight /// into `arena` (`Contents::Arena`), so the bytes are bulk-freed by /// `mi_heap_destroy` when the per-call `MimallocArena` (the per-job arena /// from `RuntimeTranspilerStore` / `ParseTask`) drops — instead of round- /// tripping through the worker thread's *default* mimalloc heap, which is /// never destroyed and retains the fresh page for the process lifetime. - /// `None` keeps the global-heap `Contents::Owned(Vec)` path. Zig: - /// `transpiler.zig:838-839` passed `if (use_shared_buffer) - /// bun.default_allocator else this_parse.allocator`. + /// `None` keeps the global-heap `Contents::Owned(Vec)` path. pub fn read_file_with_allocator( &mut self, _fs: &mut fs_mod::FileSystem, @@ -352,9 +340,8 @@ impl Fs { ) -> Result { let rfs = &_fs.fs; - // PORT NOTE: reshaped — Zig declared `file_handle = undefined` then assigned on each - // branch; restructured into a single let-expression to avoid `mem::zeroed()` on a - // type that may have niche (NonZero) fields. + // Single let-expression assigning `file_handle` on each branch, avoiding + // `mem::zeroed()` on a type that may have niche (NonZero) fields. let mut _owned: Option = None; let will_close: bool; let fd: Fd = if let Some(f) = _file_handle { @@ -405,13 +392,12 @@ impl Fs { fd ); - // PORT NOTE: reshaped for borrowck — capture `stream` scalar before borrowing + // Borrowck: capture `stream` scalar before borrowing // the shared buffer. let stream = self.stream; let contents = match (use_shared_buffer, arena) { - // Zig: `readFileWithHandleAndAllocator(this_parse.allocator, …)` — - // read straight into the per-call arena so the source bytes are + // Read straight into the per-call arena so the source bytes are // reclaimed by `mi_heap_destroy` instead of pinning a fresh page in // the worker thread's default heap (one `mi_malloc` + `munmap` pair // per transpiled module → one bump allocation in a wholesale-reset @@ -525,14 +511,13 @@ impl JavaScript { let result = match parser.parse() { Ok(r) => r, Err(err) => { - // PORT NOTE: `Parser::parse` consumes `self` (Zig took `*Parser` - // — by-ref — but the Rust port owns the inner `P` by value), so - // `parser` is gone in this arm. The `&'a mut temp_log` it held - // is released, so read `temp_log.errors` directly. The lexer - // range is lost; fall back to `Range::None` (Zig used - // `parser.lexer.range()`). - // TODO(port): thread the failing token range through the - // `Err` payload once `_parse` returns a `(Error, Range)` pair. + // `Parser::parse` consumes `self`, so `parser` is gone in this + // arm. The `&'a mut temp_log` it held is released, so read + // `temp_log.errors` directly. The lexer range is lost; fall + // back to `Range::None`. + // TODO: thread the failing token range through the `Err` + // payload (make `_parse` return a `(Error, Range)` pair) so the + // diagnostic points at the failing token. if temp_log.errors == 0 { log.add_range_error(Some(source), bun_ast::Range::None, err.name().as_bytes()); } @@ -559,8 +544,8 @@ impl JavaScript { } let mut temp_log = bun_ast::Log::init(); - // PORT NOTE: reshaped for borrowck — Zig `defer temp_log.appendToMaybeRecycled(log, source)`; - // scopeguard cannot capture &mut temp_log while it's used below. Explicit calls at each exit. + // scopeguard cannot capture &mut temp_log while it's used below; + // explicit `append_to_maybe_recycled` calls at each exit. let mut parser = match js_parser::Parser::init(opts, &mut temp_log, source, defines, bump) { Ok(p) => p, @@ -581,5 +566,3 @@ impl JavaScript { // the resolver already depends on `bun_parsers::json_parser`, so the // vtable seam was redundant. pub use bun_resolver::tsconfig_json::{JsonCache as Json, JsonMode}; - -// ported from: src/bundler/cache.zig diff --git a/src/bundler/defines.rs b/src/bundler/defines.rs index 125ec1ae115..5560bb3d7a3 100644 --- a/src/bundler/defines.rs +++ b/src/bundler/defines.rs @@ -25,14 +25,13 @@ pub use bun_js_parser::defines::{ UserDefinesArray, are_parts_equal, }; -/// Alias for `Options` so `options.rs` can write `DefineData::init(DefineDataInit { .. })` -/// (mirrors Zig's anonymous-struct init). +/// Alias for `Options` so `options.rs` can write `DefineData::init(DefineDataInit { .. })`. pub type DefineDataInit<'a> = Options<'a>; /// Alias for `ExprData` so `options.rs` can write `DefineValue::EUndefined(..)`. pub(crate) use bun_ast::ExprData as DefineValue; -// `Expr::Data` stores `Number`/`Undefined` inline (not via pointer), so the -// `_PTR` indirection from Zig disappears. +// `Expr::Data` stores `Number`/`Undefined` inline (not via pointer), so no +// pointer indirection is needed for these constants. pub struct Globals; impl Globals { pub const UNDEFINED: bun_ast::E::Undefined = bun_ast::E::Undefined; @@ -63,13 +62,12 @@ fn defines_path() -> FsPath<'static> { p } -// Zig: `pub const Data = DefineData;` inside `Define` pub type Data = DefineData; // ══════════════════════════════════════════════════════════════════════════ // `bun_dotenv::DefineStore` impls. dotenv (T2) calls through the link-interface // handle; bundler (T5) owns the concrete `E::String` + `DefineData` construction. -// Mirrors src/dotenv/env_loader.zig:399 `copyForDefine` — `to_string` is a +// `to_string` is a // `StringHashMap` (= UserDefines), `to_json` is a // `StringHashMap>` (= RawDefines / framework defaults). // ══════════════════════════════════════════════════════════════════════════ @@ -80,10 +78,9 @@ fn env_string_store_put( key: &[u8], value: &[u8], ) -> Result<(), bun_core::Error> { - // Zig (env_loader.zig:461) allocates the `E.String` slab via the passed - // `allocator` (= `Transpiler.allocator`), NOT the thread-local + // The `E.String` slab must NOT live in the thread-local // `Expr.Data.Store` — `configureDefines` resets that store on return, so - // the env-define payloads must outlive it. Mirror with `bump` (the + // the env-define payloads must outlive it. Allocate from `bump` (the // transpiler arena) so the slab is bulk-freed with the `Define` table // instead of leaking a `Box` per env var. Value bytes alias the long-lived // env-map storage. @@ -100,7 +97,7 @@ fn env_string_store_put( Ok(()) } -/// Port of `Loader.copyForDefine` (env_loader.zig:399). Moved up from +/// Moved up from /// `bun_dotenv` so it can name `DefineData` / `E::String` directly instead of /// dispatching through a vtable — it only reads `loader.map.map.{keys,values}()`, /// all of which are public. @@ -130,18 +127,16 @@ pub fn copy_env_for_define( } } - // PORT NOTE: Zig pre-counted `key_buf_len`/`e_strings_to_allocate` to size two bump - // allocations, then `iter.reset()` and re-walked. With per-entry copies the pre-sizing - // pass is dead — emit directly. PERF(port): was single-buffer key arena; now per-entry Vec reuse. + // With per-entry copies no pre-sizing + // pass is needed — emit directly. PERF: was single-buffer key arena; now per-entry Vec reuse. if behavior != DotEnvBehavior::Disable && behavior != DotEnvBehavior::LoadAllWithoutInlining { if behavior == DotEnvBehavior::Prefix { debug_assert!(!prefix.is_empty()); } - // PORT NOTE: Zig's `if (key_buf_len > 0)` gate (env_loader.zig:455) is behavioral, - // not just a sizing optimization — when `behavior == .prefix` and NO env key starts + // When `behavior == .prefix` and NO env key starts // with `prefix`, the entire second walk (including the framework-hash `else` arm) - // is skipped. Mirror that by pre-scanning for a prefix match before emitting. + // must be skipped. Pre-scan for a prefix match before emitting. let any_prefix_match = if behavior == DotEnvBehavior::Prefix { env.map .map @@ -154,7 +149,7 @@ pub fn copy_env_for_define( if any_prefix_match { let mut key_buf: Vec = Vec::new(); - // PORT NOTE: borrowck — iterate parallel slices instead of `iterator()` so the + // borrowck — iterate parallel slices instead of `iterator()` so the // map borrow stays shared while we write into the define stores. let keys = env.map.map.keys(); let values = env.map.map.values(); @@ -232,17 +227,15 @@ impl DefineExt for Define { ) -> Result<(), bun_alloc::AllocError> { let key = global[global.len() - 1]; let parts: Vec> = global.iter().map(|p| Box::<[u8]>::from(*p)).collect(); - // PORT NOTE: reshaped for borrowck — getOrPut split into entry-style match. + // reshaped for borrowck — getOrPut split into entry-style match. if let Some(existing) = self.dots.get_mut(key) { let mut list: Vec = Vec::with_capacity(existing.len() + 1); - // PERF(port): was appendSliceAssumeCapacity — profile if hot. list.extend_from_slice(existing); - // PERF(port): was appendAssumeCapacity — profile if hot. list.push(DotDefine { parts, data: value_define.clone(), }); - // Zig: define.arena.free(gpe.value_ptr.*); — handled by Vec drop on assign + // The old value is freed by Vec drop on assign. *existing = list; } else { let list: Vec = vec![DotDefine { @@ -390,7 +383,6 @@ impl DefineDataExt for DefineData { log: &mut bun_ast::Log, bump: &bun_alloc::Arena, ) -> Result<(), bun_core::Error> { - // PERF(port): was putAssumeCapacity — profile if hot. user_defines.put_assume_capacity( key, ::parse( @@ -413,7 +405,6 @@ impl DefineDataExt for DefineData { log: &mut bun_ast::Log, bump: &bun_alloc::Arena, ) -> Result { - // TODO(port): narrow error set let mut key_splitter = key.split(|b| *b == b'.'); while let Some(part) = key_splitter.next() { if !js_lexer::is_identifier(part) { @@ -465,9 +456,9 @@ impl DefineDataExt for DefineData { return Ok(DefineData { value, - // PORT NOTE: upstream `DefineData` now owns `original_name: - // Option>` (js_parser/lib.rs:1369) instead of the - // borrowed `ptr`/`len` pair (Zig's 48→40-byte packing). Dupe + // upstream `DefineData` now owns `original_name: + // Option>` (js_parser/lib.rs:1369) instead of a + // borrowed `ptr`/`len` pair. Dupe // the value bytes — these are tiny startup-time copies. original_name: if !value_str.is_empty() { Some(Box::<[u8]>::from(value_str)) @@ -512,8 +503,7 @@ impl DefineDataExt for DefineData { }); } - // Zig parsed against a stack-local `Source` then `Expr.Data.deepClone`d - // into the arena. We dupe `value_str` into `bump` first so every string + // We dupe `value_str` into `bump` first so every string // slice the JSON lexer hands back already points into the long-lived // arena (the `E::String.data` bytes survive without per-string dup). // @@ -570,7 +560,7 @@ impl DefineDataExt for DefineData { bump: &bun_alloc::Arena, ) -> Result { let mut user_defines = UserDefines::default(); - user_defines.reserve((defines.len() + drop.len()) as u32 as usize); // @truncate + user_defines.reserve((defines.len() + drop.len()) as u32 as usize); for (key, value) in defines.keys().iter().zip(defines.values().iter()) { ::from_mergeable_input_entry( &mut user_defines, @@ -601,8 +591,5 @@ impl DefineDataExt for DefineData { } } -// Zig `deinit` freed `dots` values, cleared maps, and destroyed `self`. -// In Rust: `dots: StringHashMap>` and `identifiers` drop their +// `dots: StringHashMap>` and `identifiers` drop their // contents automatically; `Box` frees `self`. No explicit Drop needed. - -// ported from: src/bundler/defines.zig diff --git a/src/bundler/entry_points.rs b/src/bundler/entry_points.rs index d345e0d9d67..01d1c7581da 100644 --- a/src/bundler/entry_points.rs +++ b/src/bundler/entry_points.rs @@ -10,7 +10,7 @@ use bun_wyhash::{self, Wyhash11}; use crate::Transpiler; use bun_js_parser as js_ast; -// PORT NOTE: `Path`/`PathName` come from the lower-tier `bun_paths::fs` shim +// `Path`/`PathName` come from the lower-tier `bun_paths::fs` shim // (lifetime-erased `'static` slices) so `bun_ast::Source` field types line up; // `FileSystem` is the real `bun_resolver::fs` singleton now that // `bun_resolver` is in this crate's dep set. @@ -39,17 +39,12 @@ impl Default for FallbackEntryPoint { } impl FallbackEntryPoint { - // TODO(port): crate::options::Framework / ClientCssInJs — `options` - // module is still gated; body also touched `bun_resolver::fs` (see - // PORTING.md §Forbidden) before un-gating. pub fn generate( entry: &mut FallbackEntryPoint, input_path: &[u8], transpiler: &mut TranspilerType, ) -> Result<(), bun_core::Error> - // TODO(port): narrow error set where - // TODO(port): TranspilerType trait bound — body reads `.options.framework` and `.arena`. TranspilerType: TranspilerLike, { // This is *extremely* naive. @@ -70,26 +65,23 @@ impl FallbackEntryPoint { .client_css_in_js != ClientCssInJs::AutoOnImportCss; - // PORT NOTE: self-referential — when the rendered code fits in + // self-referential — when the rendered code fits in // `entry.code_buffer` the Source borrows it (disjoint-field write to // `entry.source` while `entry.code_buffer` is shared-borrowed). On - // overflow the Source owns the bytes via `Cow::Owned` (Zig allocated - // from `transpiler.arena`; here the Source owns it directly so Drop + // overflow the Source owns the bytes via `Cow::Owned` (so Drop // frees it). - // PORT NOTE: assemble bytes directly (not `write!`+`BStr`) so a - // non-UTF-8 byte in `input_path` is emitted verbatim like Zig `{s}`, + // assemble bytes directly (not `write!`+`BStr`) so a + // non-UTF-8 byte in `input_path` is emitted verbatim, // not lossily replaced with U+FFFD by `BStr as Display`. macro_rules! render_into_entry { ($prefix:expr, $suffix:expr) => {{ let prefix: &[u8] = $prefix; let suffix: &[u8] = $suffix; - // PERF(port): was std.fmt.count + bufPrint/allocPrint stack-fallback — profile if hot. let count = prefix.len() + input_path.len() + suffix.len(); if count < entry.code_buffer.len() { let buf = &mut entry.code_buffer; buf[..prefix.len()].copy_from_slice(prefix); - buf[prefix.len()..prefix.len() + input_path.len()] - .copy_from_slice(input_path); + buf[prefix.len()..prefix.len() + input_path.len()].copy_from_slice(input_path); buf[prefix.len() + input_path.len()..count].copy_from_slice(suffix); entry.source = bun_ast::Source::init_path_string(input_path, &entry.code_buffer[..count]); @@ -138,7 +130,7 @@ impl ClientEntryPoint { strings::starts_with(b"entry.", extname) } - // PORT NOTE: takes the lifetime-generic `bun_paths::fs::PathName<'_>` (not the + // takes the lifetime-generic `bun_paths::fs::PathName<'_>` (not the // `'static`-field `bun_paths::fs::PathName<'static>`) so callers with a borrowed path // (e.g. `bun_runtime::filesystem_router::get_script_src_string`) needn't forge // `'static`. The body only copies `dir`/`base`/`ext` into `outbuffer`. @@ -151,7 +143,7 @@ impl ClientEntryPoint { let mut generated_path = Fs::FileSystem::get().abs_buf(&joined_base_and_dir_parts, outbuffer); - // PORT NOTE: reshaped for borrowck — capture len, drop borrow, re-borrow outbuffer. + // reshaped for borrowck — capture len, drop borrow, re-borrow outbuffer. let mut len = generated_path.len(); outbuffer[len..len + b".entry".len()].copy_from_slice(b".entry"); len += b".entry".len(); @@ -185,9 +177,7 @@ impl ClientEntryPoint { original_path: &Fs::PathName, client: &[u8], ) -> Result<(), bun_core::Error> - // TODO(port): narrow error set where - // TODO(port): TranspilerType trait bound — body reads `.options.framework`. TranspilerType: TranspilerLike, { let entry = self; @@ -210,8 +200,10 @@ impl ClientEntryPoint { .client_css_in_js != ClientCssInJs::AutoOnImportCss; - // TODO(port): self-referential — `code` borrows `entry.code_buffer` and is stored into - // `entry.source`. See note in FallbackEntryPoint::generate. + // INVARIANT: self-referential — `code` borrows `entry.code_buffer` and is + // stored into `entry.source` (lifetime erased), so `entry` must not move + // or drop while `entry.source` is in use. See note in + // FallbackEntryPoint::generate. let code: &[u8] = if disable_css_imports { let mut cursor = std::io::Cursor::new(&mut entry.code_buffer[..]); write!( @@ -279,7 +271,6 @@ impl ServerEntryPoint { is_hot_reload_enabled: bool, path_to_use: &[u8], ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set // Use the global arena so this buffer's lifetime is decoupled // from whichever arena the caller's VM happens to be using; the // slice is read later from `getHardcodedModule` which outlives any @@ -352,7 +343,7 @@ impl ServerEntryPoint { }; // Free the previous buffer on regenerate (hot reload) instead of - // leaking it. `contents` is either "" or a prior allocPrint result. + // leaking it. `contents` is either "" or a previously generated buffer. // (Handled implicitly: assigning to `Box<[u8]>` drops the old one.) entry.contents = code.into_boxed_slice(); entry.generated = true; @@ -395,7 +386,7 @@ impl MacroEntryPoint { let hash = hasher.final_(); let fmt = bun_fmt::hex_int_lower::<16>(hash); - // PORT NOTE: reshaped for borrowck — capture cursor position, drop &mut + // reshaped for borrowck — capture cursor position, drop &mut // borrow, then re-borrow `buf` immutably. let n = { let mut cursor = std::io::Cursor::new(&mut buf[..]); @@ -415,12 +406,10 @@ impl MacroEntryPoint { } pub fn generate_id_from_specifier(specifier: &[u8]) -> i32 { - // Same-size bitcast u32 → i32 (matches Zig `@bitCast`). + // Same-size bitcast u32 → i32. (bun_wyhash::hash(specifier) as u32) as i32 } - // TODO(port): bun_ast::Macro + bun_resolver::fs::PathName — - // see `generate_id`. pub fn generate( entry: &mut MacroEntryPoint, _: &mut Transpiler, @@ -429,13 +418,12 @@ impl MacroEntryPoint { macro_id: i32, macro_label_: &[u8], ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let dir_to_use: &[u8] = if import_path.dir.is_empty() { b"" } else { import_path.dir_with_trailing_slash() }; - // PORT NOTE: reshaped for borrowck — capture the label length, write the + // reshaped for borrowck — capture the label length, write the // body via a scoped &mut borrow, then re-borrow `code_buffer` immutably // for the (label, code) slices passed to `init_path_string`. let label_len = macro_label_.len(); @@ -521,9 +509,10 @@ impl MacroEntryPoint { cursor.position() as usize }; - // TODO(port): self-referential — `macro_label`/`code` borrow `entry.code_buffer` - // and are stored into `entry.source` (lifetime erased via `IntoStr`); restructure - // so Source owns its bytes (or use a raw-ptr slice). + // INVARIANT: self-referential — `macro_label`/`code` borrow + // `entry.code_buffer` and are stored into `entry.source` (lifetime erased + // via `IntoStr`), so `entry` must not move or drop while `entry.source` + // is in use. let macro_label: &[u8] = &entry.code_buffer[..label_len]; let code: &[u8] = &entry.code_buffer[label_len..label_len + code_len]; entry.source = bun_ast::Source::init_path_string(macro_label, code); @@ -533,13 +522,10 @@ impl MacroEntryPoint { } } -// TODO(port): `TranspilerLike` is a placeholder for the duck-typed -// `comptime TranspilerType: type` param used by FallbackEntryPoint/ClientEntryPoint. -// Replace with the concrete `Transpiler` type or a real trait. +// Trait abstraction over the transpiler types used by +// FallbackEntryPoint/ClientEntryPoint. pub trait TranspilerLike { fn options(&self) -> &crate::options::Options<'_>; } use crate::options::ClientCssInJs; - -// ported from: src/bundler/entry_points.zig diff --git a/src/bundler/lib.rs b/src/bundler/lib.rs index 49775a49070..b724ef145fd 100644 --- a/src/bundler/lib.rs +++ b/src/bundler/lib.rs @@ -1,6 +1,6 @@ #![feature(inherent_associated_types)] #![feature(adt_const_params, allocator_api, thread_local)] -#![allow(incomplete_features)] // inherent_associated_types — used only for ThreadPool::Worker path compat with Zig +#![allow(incomplete_features)] // inherent_associated_types — used only for the ThreadPool::Worker path #![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)] #![warn(unused_must_use)] @@ -110,14 +110,11 @@ pub mod options_impl; pub mod parse_task; pub mod transpiler; -/// `linker_context/` submodule directory. Declared inline (no `mod.rs`) so -/// paths stay 1:1 with the Zig directory. +/// `linker_context/` submodule directory. Declared inline (no `mod.rs`). pub mod linker_context { #[path = "scanImportsAndExports.rs"] pub mod scan_imports_and_exports; - // Each module maps 1:1 to a `.zig` of the same basename. - #[path = "computeChunks.rs"] pub mod compute_chunks; @@ -188,7 +185,7 @@ pub mod linker_context { pub mod static_route_visitor; // ── Re-exports so `crate::linker_context::{debug, LinkerContext, …}` - // resolves at every submodule call-site (mirrors Zig's `@import("./LinkerContext.zig")`). + // resolves at every submodule call-site. pub use crate::linker_context_mod::{ ChunkMeta, GenerateChunkCtx, LinkerContext, PendingPartRange, }; @@ -226,14 +223,12 @@ pub use parse_task::ParseTask; pub use thread_pool::{ThreadPool, Worker}; /// See `transpiler`. pub use transpiler::Transpiler; -/// `bundle_v2.zig:AdditionalFile`. pub enum AdditionalFile { SourceIndex(u32), OutputFile(u32), } -/// `bun.ast.Index` — source-index newtype. Re-exported here because every -/// `*.zig` in this crate aliases it as `pub const Index = bun.ast.Index`. +/// `bun_ast::Index` — source-index newtype, re-exported for crate-wide use. pub(crate) use bun_ast::{Index, IndexInt}; // Re-export the `options` module. `Loader`/`Target` live in @@ -246,7 +241,7 @@ pub mod options { pub use super::options_impl::*; pub use super::output_file::BakeExtra; pub use super::output_file::IndexOptional; - /// `OutputFile.init` argument struct (`options.zig:OutputFile.Options`). + /// `OutputFile.init` argument struct. pub use super::output_file::Options as OutputFileInit; pub use super::output_file::OptionsData as OutputFileData; pub use super::output_file::Value as OutputValue; @@ -256,8 +251,7 @@ pub mod options { pub use bun_options_types::schema::api::DotEnvBehavior as EnvBehavior; pub type Options<'a> = super::BundleOptions<'a>; - /// `jsc.API.BuildArtifact.OutputKind` (JSBundler.zig:1799). Re-exported by - /// `options.zig` callers via `OutputFile.output_kind`. + /// Output kind of a build artifact (`OutputFile.output_kind`). /// /// `IntoStaticStr` provides the JS-facing tag (`"entry-point"` etc.) so /// `bun_runtime::api::BuildArtifact` can spell `<&str>::from(kind)` without @@ -285,7 +279,6 @@ pub mod options { } impl OutputKind { - /// JSBundler.zig:1809. pub fn is_file_in_standalone_mode(self) -> bool { !matches!( self, @@ -298,12 +291,12 @@ pub mod options { } } - /// `bun.bake.Side` (bake.zig:874) — which graph an output belongs to. + /// Which graph an output belongs to. /// Re-export of the canonical def in `crate::bake_types` (bundle_v2.rs). pub use crate::bake_types::Side; - /// Legacy `options::Framework` (referenced by `resolver/package_json.zig`'s - /// `FrameworkRouterPair`). The full struct is `bun.bake.Framework` which + /// Legacy `options::Framework` (referenced by the resolver's + /// `FrameworkRouterPair`). The full struct is `bake::Framework` which /// lives in a higher-tier crate; minimal real struct lives in `bake_types`. pub use crate::bake_types::Framework; @@ -313,7 +306,7 @@ pub mod options { // inline shadows produced 4+ incompatible `jsx::Pragma`/`Runtime` types and // a `&'static [&'static [u8]]` `factory`/`fragment` that could not hold the // heap allocation from `member_list_to_components_if_different` - // (options.zig:1296) without `Box::leak` (PORTING.md §Forbidden patterns). + // without `Box::leak`. } /// Re-export so `crate::RuntimeTranspilerCache` resolves for `transpiler::ParseOptions` @@ -340,9 +333,8 @@ pub use bundle_v2::dispatch; // ── link-interfaces (must be at crate root so `$crate::__alias` resolves) ── // Re-exported through `bundle_v2::dispatch` for existing call sites. -// Erased handle to `bake::DevServer`. PORT NOTE: Zig takes -// `*const DevServerOutput` but mutates through the `chunks: []Chunk` slice it -// holds; in Rust the struct stores `&'a mut [Chunk]`, hence `*mut`. +// Erased handle to `bake::DevServer`. The struct stores a `&'a mut [Chunk]` +// it mutates through, hence `*mut`. bun_dispatch::link_interface! { pub DevServerHandle[Bake] { fn barrel_needed_exports() -> *mut bun_collections::StringArrayHashMap>; @@ -387,8 +379,7 @@ bun_dispatch::link_interface! { } } -// `OutputFile.Options` defaults (`options.zig:OutputFile.Options` field -// default-initializers). Kept here rather than in `OutputFile.rs` so the +// `OutputFile.Options` field defaults. Kept here rather than in `OutputFile.rs` so the // derive-free struct stays codegen-friendly while every `init(..)` call site // can use struct-update syntax. impl Default for output_file::OptionsData { diff --git a/src/bundler/linker.rs b/src/bundler/linker.rs index 20c38df88bf..bbbe993f92b 100644 --- a/src/bundler/linker.rs +++ b/src/bundler/linker.rs @@ -1,6 +1,4 @@ // This file is the old linker, used by Bun.Transpiler. -// -// Port of `src/bundler/linker.zig`. use std::io::Write as _; @@ -8,10 +6,10 @@ use bun_ast::Log; use bun_ast::{ImportKind, ImportRecord, ImportRecordFlags, ImportRecordTag}; use bun_collections::HashMap; use bun_paths::{self, SEP}; -// PORT NOTE: two `fs` shapes are in play here. `bun_resolver::fs` (`Fs`) holds +// two `fs` shapes are in play here. `bun_resolver::fs` (`Fs`) holds // the singleton `FileSystem` / `DirnameStore`; `bun_paths::fs` (`PFs`) defines // the `Path`/`PathName` value types that `ImportRecord.path` is typed against. -// Both port `src/resolver/fs.zig`; B-3 collapses them. Until then, construct +// B-3 collapses them. Until then, construct // `import_record.path` via `PFs::Path` so the field assignment unifies. use bun_core::strings; use bun_paths::fs as PFs; @@ -35,20 +33,19 @@ bun_core::named_error_set!(CSSResolveError); type HashedFileNameMap = HashMap; -// PORT NOTE: `_transpiler.Transpiler.isCacheEnabled` is gated in the draft body -// (`transpiler.rs:1111`). The Zig value is a hard `false` (`const isCacheEnabled -// = false;`); inline it here so `get_hashed_filename` compiles without depending +// `_transpiler.Transpiler.isCacheEnabled` is gated in the draft body +// (`transpiler.rs:1111`). The value is a hard `false`; +// inline it here so `get_hashed_filename` compiles without depending // on the gated `Transpiler` impl. const IS_CACHE_ENABLED: bool = false; pub struct Linker { // arena field dropped — global mimalloc (callers pass `bun.default_allocator`) - // PORT NOTE: Zig stored borrowed `*BundleOptions` / `*Log` / `*Resolver` / - // `*ResolveQueue` / `*ResolveResults` / `*FileSystem`. The un-gated - // `Transpiler` struct owns those values directly and also owns `linker: - // crate::Linker` by value, so storing Rust references here would alias + // The un-gated + // `Transpiler` struct owns these values directly and also owns `linker: + // crate::Linker` by value, so storing references here would alias // `&mut self` on every `transpiler.linker.link(...)` call. Use raw - // pointers (matching Zig's `*T`) and dereference at use-site; same + // pointers and dereference at use-site; same // contract as `transpiler::set_log`'s `linker.log = log as *mut _`. pub options: *mut BundleOptions<'static>, pub fs: *mut Fs::FileSystem, @@ -77,12 +74,9 @@ pub struct TaggedResolution { } // ── relative_paths_list singleton ──────────────────────────────────────── -// Zig: `const ImportPathsList = allocators.BSSStringList(512, 128); -// pub var relative_paths_list: *ImportPathsList = undefined;` -// -// `bun_alloc::BSSStringList` encodes the Zig generics as +// `bun_alloc::BSSStringList` encodes the parameters as // `COUNT = _COUNT * 2`, `ITEM_LENGTH = _ITEM_LENGTH + 1` (see `bun_alloc/lib.rs`). -// PORT NOTE: `bss_string_list!` would be the canonical declare-site macro but +// `bss_string_list!` would be the canonical declare-site macro but // expands to `core::cell::SyncUnsafeCell`, and `bun_bundler` does not (yet) // enable `#![feature(sync_unsafe_cell)]`. Use the heap-allocating `init()` // fallback under a `LazyLock` instead — same lifetime semantics @@ -94,7 +88,7 @@ pub(crate) type ImportPathsList = bun_alloc::BSSStringList<{ 512 * 2 }, { 128 + /// it can sit inside a `LazyLock`. The underlying list serializes its own /// mutation through an internal `Mutex` (see `BSSStringList::append`), so /// sharing the raw pointer across threads is sound; the `&mut self` receiver -/// on `append` is a Zig-port artifact, not an exclusivity requirement. +/// on `append` is not an exclusivity requirement. struct ImportPathsListPtr(core::ptr::NonNull); // SAFETY: `BSSStringList` guards every mutating method with `self.mutex`, and // the allocation is process-lifetime (never freed). The pointer is therefore @@ -146,8 +140,7 @@ mod hardcoded_module { /// Intern a byte buffer into the process-lifetime `relative_paths_list` /// `BSSStringList` singleton. /// -/// Zig used `linker.arena.dupe(u8, ...)` / `allocPrint` with -/// `bun.default_allocator` and never frees the result — the linker is a +/// The linker is a /// per-transpile singleton whose output paths flow into `ImportRecord.path: /// Path<'static>`. PORTING.md §Forbidden bans `Vec::leak`/`Box::leak` for /// fabricating `&'static [u8]`; route through the `relative_paths_list` @@ -271,10 +264,9 @@ impl Linker { resolve_results: *mut ResolveResults, fs: *mut Fs::FileSystem, ) -> Self { - // Zig wrote `relative_paths_list = ImportPathsList.init(arena);` - // here; the `LazyLock` accessor handles that lazily on first - // `intern_path()` / `relative_paths_list()` call, so no eager poke - // is needed (it was startup overhead for non-bundling code paths). + // The `LazyLock` accessor initializes `relative_paths_list` lazily on + // first `intern_path()` / `relative_paths_list()` call, so no eager + // poke is needed (it would be startup overhead for non-bundling code paths). Self { options, fs, @@ -292,11 +284,10 @@ impl Linker { } /// Re-seat the self-referential back-pointers after the owning - /// `Transpiler` has been moved to its final address. Port of the - /// post-copy fixups in ThreadPool.zig:310 / bundle_v2.zig:230 — those - /// only re-assign the pointer fields and do NOT reset + /// `Transpiler` has been moved to its final address. Only re-assigns the + /// pointer fields; does NOT reset /// `import_counter` / `plugin_runner` / `tagged_resolutions` / - /// `any_needs_runtime`, so neither does this. Use instead of `init` from + /// `any_needs_runtime`. Use instead of `init` from /// `Transpiler::wire_after_move`. pub fn reseat_self_refs( &mut self, @@ -315,22 +306,19 @@ impl Linker { self.fs = fs; } - /// Accessor for the `relative_paths_list` singleton (Zig: - /// `Linker.relative_paths_list`). Returns `*mut` because the Zig contract - /// is a global `*Self` pointer — fabricating `&'static mut` here would - /// alias on every call. + /// Accessor for the `relative_paths_list` singleton. Returns `*mut` + /// because the contract is a global pointer — fabricating `&'static mut` + /// here would alias on every call. #[inline] pub fn relative_paths_list() -> *mut ImportPathsList { relative_paths_list_ptr() } // ── getModKey / getHashedFilename ──────────────────────────────────── - // PORT NOTE: Zig's `Fs.FileSystem.RealFS.ModKey` is a nested decl; the - // Rust port hoists `ModKey` to module scope (`bun_resolver::fs::ModKey`) + // `ModKey` lives at module scope (`bun_resolver::fs::ModKey`) // alongside `RealFS`. `file_path` is typed `PFs::Path` (not `Fs::Path`) // so `get_hashed_filename` — whose callers all build `PFs::Path` — can - // forward directly; only `.text` is read, and both ports define it as - // `&[u8]`. + // forward directly; only `.text` (a `&[u8]`) is read. pub fn get_mod_key( &mut self, file_path: &PFs::Path<'_>, @@ -352,7 +340,7 @@ impl Linker { }; let file = bun_sys::File::borrow(&raw_fd); Fs::FileSystem::set_max_fd(file.handle().native()); - // PORT NOTE: spec called `Fs.FileSystem.RealFS.ModKey.generate(&this.fs.fs, + // spec called `Fs.FileSystem.RealFS.ModKey.generate(&this.fs.fs, // path, file)`; both leading args are unread (fs.rs:1386). The inline // `bun_resolver::fs::RealFS` (which `self.fs.fs` is) and the full-port // `fs_full::RealFS` are distinct types, so route through the @@ -374,15 +362,24 @@ impl Linker { } let modkey = self.get_mod_key(file_path, fd)?; - // PORT NOTE: `ModKey::hash_name` writes into a 1 KiB threadlocal and - // returns a `'static` slice into it (matches Zig's `hash_name_buf` - // threadlocal). Spec passes `file_path.text` even though the param is - // named `basename`; preserved verbatim. - let hash_name = modkey.hash_name(file_path.text)?; + // `ModKey::hash_name` writes into a caller-supplied buffer (1 KiB) + // and returns a borrow of it; `dupe` copies the bytes into the + // process-lifetime interner to satisfy this fn's `'static` return. + // Note: `IS_CACHE_ENABLED` is a hard `const false` (see above), so + // the `hashed_filenames` cache never dedups — every call interns a + // fresh copy for the life of the process. Accepted: the `'static` + // return contract forces a copy anyway, and the alternative (the old + // threadlocal slice return) was unsound. `dupe` also aborts on OOM + // where the old path propagated `?` — consistent with the + // `bun.handleOom` idiom for interner allocations. + // Spec passes `file_path.text` even though the param is named + // `basename`; preserved verbatim. + let mut hash_name_buf = [0u8; 1024]; + let hash_name = dupe(modkey.hash_name(file_path.text, &mut hash_name_buf)?); if IS_CACHE_ENABLED { let hashed = bun_wyhash::hash(file_path.text); - self.hashed_filenames.insert(hashed, dupe(hash_name)); + self.hashed_filenames.insert(hashed, hash_name); } Ok(hash_name) @@ -391,7 +388,7 @@ impl Linker { /// This modifies the Ast in-place! It resolves import records and /// generates paths. /// - /// PORT NOTE: `comptime import_path_format` demoted to a runtime arg — + /// `import_path_format` is a runtime arg rather than a const generic — /// `options::ImportPathFormat` doesn't derive `ConstParamTy`, and the /// crate doesn't enable `adt_const_params`. All callers pass a literal, /// and the inner `generate_import_path` body is a single `match` either @@ -423,10 +420,7 @@ impl Linker { | options::Loader::Js | options::Loader::Ts | options::Loader::Tsx => { - // PORT NOTE: reshaped for borrowck — Zig iterated - // `result.ast.import_records.slice()` while also reading other - // `result.*` fields and (in the not-found branch) borrowing - // `&result.source`. Iterate by index, take field-disjoint + // Iterate by index, take field-disjoint // borrows (`&result.source` + `&mut result.ast.*`) where // needed, and hoist `is_pending_import` (which borrows the // whole `result`) before any `ast` mut borrow. @@ -528,8 +522,7 @@ impl Linker { // by the owning `Transpiler` to a live JSC-heap // `PluginRunner`; the transpiler is single-threaded // and holds no other borrow of it for the duration - // of `on_resolve`. Shared access here matches Zig - // `*PluginRunner` (linker.zig:176-193). + // of `on_resolve`, so shared access is sound. let runner = unsafe { &*runner }; if let Some(path) = runner.on_resolve( import_record.path.text, @@ -567,15 +560,14 @@ impl Linker { if had_resolve_errors { return Err(bun_core::err!("ResolveMessage")); } - // PERF(port): Zig clearAndFree; Vec drop at scope end frees. + // Vec drop at scope end frees. externals.clear(); let _ = externals; Ok(()) } - // PORT NOTE: reshaped for borrowck — Zig passed `&mut self` + `&mut - // ImportRecord` (a sub-borrow of `result.ast`) + `&mut ParseResult`. In - // Rust those overlap; pass the disjoint pieces explicitly. + // Takes the disjoint pieces explicitly rather than `&mut self` plus + // overlapping sub-borrows of `result`. fn when_module_not_found( log: &mut Log, target: BundleTarget, @@ -660,11 +652,11 @@ impl Linker { } if namespace == b"bun" || namespace == b"file" || namespace.is_empty() { - // PORT NOTE: `linker.fs.relative` is a thin wrapper over + // `linker.fs.relative` is a thin wrapper over // `bun.path.relative`; the inline `bun_resolver::fs` // module doesn't expose it yet, so call the path layer // directly. The threadlocal-buffer result must be - // dup'd to outlive this call (Zig leaked into Path). + // dup'd to outlive this call. let relative_name = dupe(bun_paths::resolve_path::relative(source_dir, source_path)); Ok(PFs::Path::init_with_pretty(source_path, relative_name)) @@ -729,8 +721,6 @@ impl Linker { } } - // PORT NOTE: `fs.relativeTo(source_path)` == - // `relative(fs.top_level_dir, source_path)` in Zig. let top_level_dir = self.fs().top_level_dir; let mut base: &[u8] = bun_paths::resolve_path::relative(top_level_dir, source_path); @@ -780,10 +770,7 @@ impl Linker { ) -> Result { let hash_key = self.resolve_result_hash_key(&resolve_result); - // PORT NOTE: Zig `getOrPut` → `HashMap::entry`; `found_existing` is - // whether the key was already present. Matches Zig - // `linker.resolve_results.getOrPut` / `linker.resolve_queue.writeItem` - // (linker.zig:387-390). + // `found_existing` is whether the key was already present. let found_existing = self.resolve_results_mut().contains_key(&hash_key); if !found_existing { self.resolve_results_mut().insert(hash_key, ()); @@ -793,5 +780,3 @@ impl Linker { Ok(!found_existing) } } - -// ported from: src/bundler/linker.zig diff --git a/src/bundler/linker_context/MetafileBuilder.rs b/src/bundler/linker_context/MetafileBuilder.rs index f7ecff60a64..ced1444b4a0 100644 --- a/src/bundler/linker_context/MetafileBuilder.rs +++ b/src/bundler/linker_context/MetafileBuilder.rs @@ -396,7 +396,7 @@ pub fn generate(c: &mut LinkerContext, chunks: &mut [Chunk]) -> Result first_output = false; j.push_static(b"\n "); - // PORT NOTE: Zig pushes a borrowed slice; push_static borrows for the + // `push_static` borrows for the // lifetime of the joiner (`chunk.metafile_chunk_json: Box<[u8]>` outlives `j`). j.push_static(&chunk.metafile_chunk_json); } @@ -422,9 +422,8 @@ pub fn generate(c: &mut LinkerContext, chunks: &mut [Chunk]) -> Result )?; // Get final output with all chunk references resolved. - // PORT NOTE: Zig passes `&chunks[0]` as the dummy chunk and `chunks` as the - // full slice (aliased). `code()` takes both as `&` now, so pass `&chunks[0]` - // directly — overlapping shared borrows are fine. + // `code()` takes the dummy chunk and the full slice as `&`, so pass + // `&chunks[0]` directly — overlapping shared borrows are fine. let code_result = intermediate.code( None, parse_graph, @@ -449,11 +448,10 @@ fn write_json_string(writer: &mut impl Write, str: &[u8]) -> std::io::Result<()> } // ────────────────────────────────────────────────────────────────────────── -// Minimal `std.json.Value`-shaped tree for `generate_markdown`. +// Minimal dynamic JSON value tree for `generate_markdown`. // -// PORT NOTE: Zig's `generateMarkdown` re-parses the metafile JSON via -// `std.json.parseFromSlice(std.json.Value, …)` — a generic dynamic-tree parse. -// The Rust crates available here (`bun_parsers::json`) only expose an +// `generate_markdown` re-parses the metafile JSON as a dynamic tree. +// The crates available here (`bun_parsers::json`) only expose an // AST-expr parser, so a small self-contained Value/parser is provided below // covering exactly the subset the metafile format uses. // ────────────────────────────────────────────────────────────────────────── @@ -711,10 +709,8 @@ impl<'a> JsonParser<'a> { } // ────────────────────────────────────────────────────────────────────────── -// generate_markdown helper structs (local to the function in Zig; hoisted here) -// PORT NOTE: lifetime <'a> ties borrowed slices to the parsed JSON value's -// lifetime. The Zig originals were anonymous structs holding []const u8 that -// borrowed from the std.json parse arena. +// generate_markdown helper structs. +// Lifetime <'a> ties borrowed slices to the parsed JSON value's lifetime. // ────────────────────────────────────────────────────────────────────────── struct InputFileInfo<'a> { @@ -1627,5 +1623,3 @@ fn strip_parent_refs(path: &[u8]) -> &[u8] { } result } - -// ported from: src/bundler/linker_context/MetafileBuilder.zig diff --git a/src/bundler/linker_context/OutputFileListBuilder.rs b/src/bundler/linker_context/OutputFileListBuilder.rs index 6632f0ebfb5..e332f4561f4 100644 --- a/src/bundler/linker_context/OutputFileListBuilder.rs +++ b/src/bundler/linker_context/OutputFileListBuilder.rs @@ -55,7 +55,6 @@ impl OutputFileList { let (length, supplementary_file_count) = OutputFileList::calculate_output_file_list_capacity(c, chunks); let mut output_files: Vec = Vec::with_capacity(length as usize); - // PERF(port): was appendNTimesAssumeCapacity. output_files.resize_with(length as usize, OutputFile::zero_value); Ok(Self { @@ -64,7 +63,7 @@ impl OutputFileList { index_for_sourcemaps_and_bytecode: if supplementary_file_count == 0 { None } else { - Some(chunks.len() as u32) // @truncate + Some(chunks.len() as u32) }, additional_output_files_start: u32::try_from(chunks.len()).expect("int cast") + supplementary_file_count, @@ -195,8 +194,8 @@ impl OutputFileList { self.index_for_sourcemaps_and_bytecode.unwrap_or(0), self.additional_output_files_start, ); - // PORT NOTE: Zig did bitwise memcpy (ownership move). `OutputFile` is not - // `Clone`, so drain by value into the target window. + // Ownership move: `OutputFile` is not `Clone`, so drain by value into + // the target window. let len = additional_output_files.len(); let dest = self.get_mutable_additional_output_files(); for (i, of) in additional_output_files.drain(..).enumerate() { @@ -221,5 +220,3 @@ impl OutputFileList { Some(result) } } - -// ported from: src/bundler/linker_context/OutputFileListBuilder.zig diff --git a/src/bundler/linker_context/StaticRouteVisitor.rs b/src/bundler/linker_context/StaticRouteVisitor.rs index d5a63f4238b..cdafe617963 100644 --- a/src/bundler/linker_context/StaticRouteVisitor.rs +++ b/src/bundler/linker_context/StaticRouteVisitor.rs @@ -1,7 +1,7 @@ //! The `is_fully_static(source_index)` function returns whether or not //! `source_index` imports a file with `"use client"`. //! -//! TODO: Could we move this into the ReachableFileVisitor inside `bundle_v2.zig`? +//! TODO: Could we move this into the ReachableFileVisitor inside `bundle_v2.rs`? use crate::mal_prelude::*; use bun_collections::{ArrayHashMap, AutoBitSet}; @@ -16,10 +16,6 @@ pub struct StaticRouteVisitor<'a> { pub visited: AutoBitSet, } -// PORT NOTE: Zig `deinit` only freed `cache` and `visited` with the default -// arena. Both are now owned types with `Drop`, so no explicit `impl Drop` -// is needed. - impl<'a> StaticRouteVisitor<'a> { /// This the quickest, simplest, dumbest way I can think of doing this. /// Investigate performance. It can have false negatives (it doesn't properly @@ -33,7 +29,7 @@ impl<'a> StaticRouteVisitor<'a> { return false; } - // PORT NOTE: `self.c` is `&'a LinkerContext` (Copy), so these slice + // `self.c` is `&'a LinkerContext` (Copy), so these slice // borrows are tied to `'a`, not to `&self`, and do not conflict with // the `&mut self` call below. `parse_graph()` is the safe backref // accessor (one centralized `unsafe`, see `LinkerContext::parse_graph`). @@ -117,5 +113,3 @@ impl<'a> StaticRouteVisitor<'a> { result } } - -// ported from: src/bundler/linker_context/StaticRouteVisitor.zig diff --git a/src/bundler/linker_context/computeChunks.rs b/src/bundler/linker_context/computeChunks.rs index 2079e74be18..e0614f8793b 100644 --- a/src/bundler/linker_context/computeChunks.rs +++ b/src/bundler/linker_context/computeChunks.rs @@ -31,7 +31,6 @@ fn make_flags(has_html_chunk: bool, is_browser_chunk_from_server_build: bool) -> f } -// TODO(port): narrow error set #[inline(never)] pub fn compute_chunks( this: &mut LinkerContext, @@ -41,12 +40,10 @@ pub fn compute_chunks( debug_assert!(this.dev_server.is_none()); // use - // PERF(port): was stack-fallback (std.heap.stackFallback(4096, ...)) — profile if hot. - // PERF(port): was arena bulk-free — temp allocations freed at end of fn let arena = Arena::new(); let temp = &arena; - // TODO(port): StringArrayHashMap keyed by arena-allocated &[u8]; using ArrayHashMap<&[u8], Chunk> here. + // Keys borrow from `temp`; the map and the arena are both dropped at end of fn. let mut js_chunks: ArrayHashMap<&[u8], Chunk> = ArrayHashMap::new(); js_chunks.reserve(this.graph.entry_points.len()); @@ -65,15 +62,15 @@ pub fn compute_chunks( let parse_graph = unsafe { &*this.parse_graph }; // `bump` is a `BackRef` into `BundleV2.graph.arena`, valid for the link step. // Hoisted so the loop can hold disjoint &mut borrows into `this.graph`. - // PORT NOTE: `BundlerStyleSheet::empty()` no longer takes an arena in Rust; kept for + // `BundlerStyleSheet::empty()` no longer takes an arena in Rust; kept for // when arena threading lands. let _arena: &Arena = this.graph.arena(); - // PORT NOTE: borrowck escape hatch — the SoA column slices below hold disjoint + // borrowck escape hatch — the SoA column slices below hold disjoint // immutable borrows into `this.graph` while several helpers (and the BundleV2 - // back-pointer recovery) still want `&mut LinkerContext`. The Zig original - // freely aliases; TODO(refactor): thread split borrows through `LinkerGraph` - // instead of laundering through a raw pointer. + // back-pointer recovery) still want `&mut LinkerContext`. Split borrows + // could eventually be threaded through + // `LinkerGraph` instead of laundering through a raw pointer. let this_ptr: *mut LinkerContext = this; let entry_source_indices = this.graph.entry_points.items_source_index(); @@ -89,9 +86,9 @@ pub fn compute_chunks( // Create chunks for entry points for (entry_id_, &source_index) in entry_source_indices.iter().enumerate() { - let entry_bit = entry_id_ as chunk::EntryPointId; // @truncate + let entry_bit = entry_id_ as chunk::EntryPointId; - // PORT NOTE: reshaped for borrowck — set the bit through a scoped &mut, then keep an + // reshaped for borrowck — set the bit through a scoped &mut, then keep an // owned clone so the `this.graph.files` borrow does not span the helper calls below // that need `&LinkerContext` / `&mut LinkerContext`. let entry_bits: AutoBitSet = { @@ -116,7 +113,7 @@ pub fn compute_chunks( .alloc_slice_copy(entry_point_chunk_bits.bytes(this.graph.entry_points.len())); } else { // Force HTML chunks to always be generated, even if there's an identical JS file. - // PORT NOTE: Zig used a Formatter struct; build the byte key directly since + // Build the byte key directly since // entry_bits is arbitrary bytes (not UTF-8) and cannot go through fmt::Display. let mut v = bun_alloc::ArenaVec::new_in(temp); v.push((!has_html_chunk) as u8); @@ -145,7 +142,7 @@ pub fn compute_chunks( } if css_asts[source_index as usize].is_some() { - // SAFETY: see `this_ptr` PORT NOTE above — the helper only reads from + // SAFETY: see `this_ptr` note above — the helper only reads from // `this.graph` columns disjoint from the slices we hold here. let order = find_imported_files_in_css_order( unsafe { &mut *this_ptr }, @@ -221,7 +218,7 @@ pub fn compute_chunks( let css_source_indices = find_imported_css_files_in_js_order(this, temp, Index::init(source_index)); if css_source_indices.len() > 0 { - // SAFETY: see `this_ptr` PORT NOTE above. + // SAFETY: see `this_ptr` note above. let order = find_imported_files_in_css_order( unsafe { &mut *this_ptr }, temp, @@ -284,7 +281,7 @@ pub fn compute_chunks( } } } - // PORT NOTE: reshaped for borrowck — re-borrow file_entry_bits after the loop above mutated it + // reshaped for borrowck — re-borrow file_entry_bits after the loop above mutated it let file_entry_bits: &mut [AutoBitSet] = this.graph.files.items_entry_bits_mut(); let css_reprs = this.graph.ast.items_css(); @@ -350,8 +347,7 @@ pub fn compute_chunks( *entry.value_ptr = AtomicUsize::new(0); // Initialize byte count to 0 } } else { - // PORT NOTE: Zig used a local `Handler` struct passed to entry_bits.forEach; - // in Rust we pass a context struct + fn pointer. + // Pass a context struct + fn pointer to entry_bits' forEach. struct Handler<'a> { chunks: &'a mut [Chunk], source_id: u32, @@ -394,7 +390,6 @@ pub fn compute_chunks( let mut sorted_keys = Vec::<&[u8]>::init_capacity(js_chunks.count()); - // PERF(port): was assume_capacity sorted_keys.append_slice_assume_capacity(js_chunks.keys()); // sort by entry_point_id to ensure the main entry point (id=0) comes first, @@ -443,13 +438,11 @@ pub fn compute_chunks( if let chunk::Content::Javascript(js) = &chunk.content { if js.css_chunks.len() > 0 { - // PERF(port): was assume_capacity js_chunk_indices_with_css.append_assume_capacity(sorted_chunks.len() as u32); } } - // PERF(port): was assume_capacity - // PORT NOTE: `Chunk` is not `Clone` (Zig is move-by-value); take by index. + // `Chunk` is not `Clone`; take by index. let idx = js_chunks.get_index(&key).expect("unreachable"); let owned = core::mem::take(&mut js_chunks.values_mut()[idx]); let has_html = owned.flags.contains(chunk::Flags::HAS_HTML_CHUNK); @@ -459,7 +452,6 @@ pub fn compute_chunks( if has_html { if let Some(html_idx) = html_chunks.get_index(&key) { let (_, html_chunk) = html_chunks.swap_remove_at(html_idx); - // PERF(port): was assume_capacity sorted_chunks.append_assume_capacity(html_chunk); } } @@ -479,7 +471,6 @@ pub fn compute_chunks( for (sorted_index, &key) in (sorted_chunks.len() as usize..).zip(sorted_css_keys.iter()) { let index = css_chunks.get_index(&key).expect("unreachable"); - // PERF(port): was assume_capacity let owned = core::mem::take(&mut css_chunks.values_mut()[index]); sorted_chunks.append_assume_capacity(owned); remapped_css_indexes[index] = u32::try_from(sorted_index).expect("int cast"); @@ -503,8 +494,6 @@ pub fn compute_chunks( } break 'sort_chunks sorted_chunks; - // TODO(port): return type — Zig returns []Chunk allocated by this.arena(); here we return Box<[Chunk]>. - // Confirm ownership of `chunks` slice (sorted_chunks Vec backing storage). }; let chunks: &mut [Chunk] = sorted_chunks.slice_mut(); @@ -540,8 +529,8 @@ pub fn compute_chunks( let unique_key_item_len = chunk::UNIQUE_KEY_LEN; let mut unique_key_builder = bun_core::StringBuilder::init_capacity(unique_key_item_len * chunks.len()); - // PORT NOTE: in Zig `unique_key_buf` aliases the builder's backing buffer and - // every `chunk.unique_key` is a slice into it. Mirror that: the builder never + // `unique_key_buf` aliases the builder's backing buffer and + // every `chunk.unique_key` is a slice into it: the builder never // reallocates after `init_capacity`, so each `fmt()` returns a stable subslice // that we detach to `&'static [u8]` (BACKREF) and transfer ownership of the // single allocation into `this.unique_key_buf` afterwards. @@ -553,7 +542,7 @@ pub fn compute_chunks( let bv2: &mut BundleV2 = unsafe { &mut *LinkerContext::bundle_v2_ptr(this_ptr) }; let kinds = this.graph.files.items_entry_point_kind(); let output_paths = this.graph.entry_points.items_output_path(); - // PORT NOTE: re-borrow after `find_all_imported_parts_in_js_order` released `&mut this`. + // re-borrow after `find_all_imported_parts_in_js_order` released `&mut this`. let ast_targets = this.graph.ast.items_target(); for (chunk_id, chunk) in chunks.iter_mut().enumerate() { // Assign a unique key to each chunk. This key encodes the index directly so @@ -692,14 +681,13 @@ pub fn compute_chunks( } // Transfer ownership of the single backing buffer; every `chunk.unique_key` - // above borrows into it. (Zig's `errdefer` freed the builder and cleared - // `unique_key_buf`; in Rust the builder `Drop`s on error and `unique_key_buf` + // above borrows into it. (The builder `Drop`s on error and `unique_key_buf` // is only assigned here on success, so no rollback guard is needed.) this.unique_key_buf = unique_key_builder.move_to_slice(); + // The caller-owned + // `Box<[Chunk]>` transfers the `sorted_chunks` Vec backing storage. Ok(sorted_chunks.to_owned_slice()) - // TODO(port): return type — Zig returns []Chunk allocated by this.arena(); here we return Box<[Chunk]>. - // Confirm ownership of `chunks` slice (sorted_chunks Vec backing storage). } pub use crate::DeferredBatchTask; @@ -709,5 +697,3 @@ pub use crate::ThreadPool; // Local type aliases referenced above. use crate::chunk; use crate::options::{Loader, Target}; - -// ported from: src/bundler/linker_context/computeChunks.zig diff --git a/src/bundler/linker_context/computeCrossChunkDependencies.rs b/src/bundler/linker_context/computeCrossChunkDependencies.rs index 5139e28de46..08738701fa6 100644 --- a/src/bundler/linker_context/computeCrossChunkDependencies.rs +++ b/src/bundler/linker_context/computeCrossChunkDependencies.rs @@ -31,8 +31,7 @@ pub fn compute_cross_chunk_dependencies( // defer { meta.*.deinit(); free(chunk_metas) } — handled by Drop { - // PORT NOTE: Zig heap-allocated this via c.arena().create() and destroyed it at - // scope end; in Rust we construct on the stack and let it drop. + // Constructed on the stack and dropped at scope end. // // `ctx` / `symbols` / `chunks` are stored as raw pointers so the struct does not // hold a borrow on `c` or `chunks` across the sequential `walk` loop below. @@ -47,7 +46,7 @@ pub fn compute_cross_chunk_dependencies( // Lifetime-erase the `LinkerContext<'_>` so the struct's `'a` (which // ties only the local SoA-column borrows) is not forced to equal the // LinkerContext's invariant `'_`. `NonNull::from(&mut *c)` preserves - // `c`'s Unique provenance (see PORT NOTE above). + // `c`'s Unique provenance (see note above). let ctx_ref = bun_ptr::BackRef::from( core::ptr::NonNull::from(&mut *c).cast::>(), ); @@ -86,7 +85,7 @@ pub fn compute_cross_chunk_dependencies( pub(crate) struct CrossChunkDependencies<'a, 'bump> { chunk_meta: &'a mut [ChunkMeta], - // PORT NOTE: `BackRef` — the same `[Chunk]` slice is also iterated mutably by + // `BackRef` — the same `[Chunk]` slice is also iterated mutably by // the caller's sequential `walk` loop; `walk` only reads `chunks[other].unique_key` // (disjoint from the per-iteration `&mut Chunk`). The slice outlives the struct // (caller stack frame). @@ -100,8 +99,8 @@ pub(crate) struct CrossChunkDependencies<'a, 'bump> { exports_refs: &'a [Ref], sorted_and_filtered_export_aliases: &'a [js_meta::SortedAndFilteredExportAliases], resolved_exports: &'a [ResolvedExports], - // PORT NOTE: `BackRef` — Zig stores `*LinkerContext` / `*Symbol.Map` and freely - // aliases `c.graph` columns alongside; borrowck cannot express that split, so + // `BackRef` — `walk` aliases `c.graph` columns alongside the + // `LinkerContext` / `Symbol.Map`; borrowck cannot express that split, so // opt out here via `BackRef` (safe `Deref` at each use site in `walk`). Lifetime // erased (`'static`) so the outer `CrossChunkDependencies<'_>` borrow is not tied // to the LinkerContext's own invariant lifetime parameter. @@ -128,7 +127,7 @@ impl<'a, 'bump> CrossChunkDependencies<'a, 'bump> { pub(crate) fn walk(&mut self, chunk: &mut Chunk, chunk_index: usize) { let deps = self; // `ctx` / `chunks` are `BackRef`s into `LinkerContext` / the caller's chunk - // slice, valid for the link pass (see PORT NOTE on the struct fields). + // slice, valid for the link pass (see note on the struct fields). // `chunks` aliases the slice the caller iterates mutably but is only read here. let ctx: &LinkerContext<'_> = deps.ctx.get(); // `BackRef` into `LinkerContext.graph.symbols`, valid for the link @@ -137,8 +136,7 @@ impl<'a, 'bump> CrossChunkDependencies<'a, 'bump> { let symbols: &bun_ast::symbol::Map = deps.symbols.get(); let _chunks: &[Chunk] = deps.chunks.get(); let chunk_meta = &mut deps.chunk_meta[chunk_index]; - // PORT NOTE: reshaped for borrowck — Zig held `&chunk_meta` and `&chunk_meta.imports` - // simultaneously; here we go through `chunk_meta.imports` / `chunk_meta.dynamic_imports`. + // Go through `chunk_meta.imports` / `chunk_meta.dynamic_imports`. let entry_point_chunk_indices = deps.entry_point_chunk_indices; // Go over each file in this chunk @@ -172,16 +170,18 @@ impl<'a, 'bump> CrossChunkDependencies<'a, 'bump> { { let other_chunk_index = entry_point_chunk_indices[import_record.source_index.get() as usize]; + // Slice copy (fat pointer): + // `path.text` borrows the chunk's + // `unique_key` backing buffer (`LinkerContext.unique_key_buf`), + // which outlives the link pass. import_record.path.text = _chunks[other_chunk_index as usize].unique_key; - // TODO(port): Zig assigns the slice by pointer (no copy); decide - // ownership of `path.text` vs `unique_key`. import_record.source_index = Index::INVALID; // Track this cross-chunk dynamic import so we make sure to // include its hash when we're calculating the hashes of all // dependencies of this chunk. if other_chunk_index as usize != chunk_index { - let _ = chunk_meta.dynamic_imports.put(other_chunk_index, ()); // OOM-only Result (Zig: catch unreachable) + let _ = chunk_meta.dynamic_imports.put(other_chunk_index, ()); // OOM-only Result } } } @@ -249,7 +249,7 @@ impl<'a, 'bump> CrossChunkDependencies<'a, 'bump> { // be moved to a separate chunk than the use of a symbol even if // the definition and use of that symbol are originally from the // same source file. - let _ = chunk_meta.imports.put(ref_to_use, ()); // OOM-only Result (Zig: catch unreachable) + let _ = chunk_meta.imports.put(ref_to_use, ()); // OOM-only Result } } } @@ -292,14 +292,14 @@ impl<'a, 'bump> CrossChunkDependencies<'a, 'bump> { debug!("Cross-chunk export: {}", bstr::BStr::new(name),); } - let _ = chunk_meta.imports.put(target_ref, ()); // OOM-only Result (Zig: catch unreachable) + let _ = chunk_meta.imports.put(target_ref, ()); // OOM-only Result } } // Ensure "exports" is included if the current output format needs it // https://github.com/evanw/esbuild/blob/v0.27.2/internal/linker/linker.go#L1049-L1051 if flags.force_include_exports_for_entry_point { - // Zig parity: result intentionally discarded + // result intentionally discarded let _ = chunk_meta.imports.put( deps.exports_refs[chunk.entry_point.source_index() as usize], (), @@ -309,7 +309,7 @@ impl<'a, 'bump> CrossChunkDependencies<'a, 'bump> { // Include the wrapper if present // https://github.com/evanw/esbuild/blob/v0.27.2/internal/linker/linker.go#L1053-L1056 if flags.wrap != WrapKind::None { - // Zig parity: result intentionally discarded + // result intentionally discarded let _ = chunk_meta.imports.put( deps.wrapper_refs[chunk.entry_point.source_index() as usize], (), @@ -326,9 +326,8 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( chunk_metas: &mut [ChunkMeta], ) -> Result<(), bun_alloc::AllocError> { // Mark imported symbols as exported in the chunk from which they are declared - // PORT NOTE: reshaped for borrowck — Zig zips (chunks, chunk_metas, 0..) and also indexes - // chunk_metas[other_chunk_index] / chunks[other_chunk_index] inside the loop body. We - // iterate by index and re-borrow per access. + // The loop body also indexes chunk_metas[other_chunk_index] / + // chunks[other_chunk_index], so iterate by index and re-borrow per access. debug_assert_eq!(chunks.len(), chunk_metas.len()); for chunk_index in 0..chunks.len() { if !matches!(chunks[chunk_index].content, chunk::Content::Javascript(_)) { @@ -336,10 +335,9 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( } // Find all uses in this chunk of symbols from other chunks - // PORT NOTE: reshaped for borrowck — collect keys first to avoid holding a borrow on + // reshaped for borrowck — collect keys first to avoid holding a borrow on // chunk_metas[chunk_index] while mutating chunk_metas[other_chunk_index]. let import_refs: Vec = chunk_metas[chunk_index].imports.keys().to_vec(); - // PERF(port): was direct iteration over .keys() without copy — profile if it shows up on a hot path for import_ref in import_refs { let symbol = c.graph.symbols.get_const(import_ref).unwrap(); @@ -449,8 +447,6 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( debug!("Generating cross-chunk exports"); let mut stable_ref_list: Vec = Vec::new(); - // PERF(port): was arena-backed std.ArrayList — profile if it shows up on a hot path - // defer stable_ref_list.deinit() — handled by Drop debug_assert_eq!(chunks.len(), chunk_metas.len()); for (chunk, chunk_meta) in chunks.iter_mut().zip(chunk_metas.iter_mut()) { @@ -469,7 +465,6 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( c.arena(), ); repr.exports_to_other_chunks.reserve(stable_ref_list.len()); - // PERF(port): was ensureUnusedCapacity — profile if it shows up on a hot path r.clear_retaining_capacity(); for stable_ref in stable_ref_list.iter() { @@ -507,18 +502,17 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( original_name: bun_ast::StoreStr::new(b"" as &[u8]), }); - // `alias` points into the link-pass arena (see PORT NOTE above), + // `alias` points into the link-pass arena (see note above), // which outlives `exports_to_other_chunks`; `.slice()` re-borrows // under the StoreStr arena contract. - let _ = repr.exports_to_other_chunks.put(ref_, alias.slice()); // OOM-only Result (Zig: catch unreachable) - // PERF(port): was putAssumeCapacity — profile if it shows up on a hot path + let _ = repr.exports_to_other_chunks.put(ref_, alias.slice()); // OOM-only Result } if clause_items.len() > 0 { let mut stmts = Vec::::init_capacity(1); let items_ptr = bun_ast::StoreSlice::new_mut(clause_items.into_bump_slice_mut()); - // Zig: `c.allocator().create(S.ExportClause)` + struct literal — + // Allocated directly from the arena — // bypasses Stmt.Data.Store (not pushed on this thread here). let export_clause = c.arena().alloc(bun_ast::S::ExportClause { items: items_ptr, @@ -528,7 +522,6 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( bun_ast::StoreRef::from_bump(export_clause), bun_ast::Loc::EMPTY, )); - // PERF(port): was appendAssumeCapacity — profile if it shows up on a hot path repr.cross_chunk_suffix_stmts = stmts; } } @@ -544,8 +537,7 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( debug!("Generating cross-chunk imports"); let mut list: Vec = Vec::new(); // defer list.deinit() — handled by Drop - // PORT NOTE: reshaped for borrowck — Zig's `for (chunks) |*chunk|` aliases the same - // slice it passes to `sortedCrossChunkImports`. We move the per-chunk fields we + // We move the per-chunk fields we // mutate (`imports_from_other_chunks`, `cross_chunk_imports`) out via `take`, drop // the `chunk` borrow, hand the whole `chunks` slice to `sorted_cross_chunk_imports` // (which only reads `chunks[other].exports_to_other_chunks` — disjoint), then write @@ -562,8 +554,8 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( ); let mut cross_chunk_imports = core::mem::take(&mut chunks[chunk_index].cross_chunk_imports); - // PORT NOTE: reshaped for borrowck — Zig copies the Vec by value, mutates, - // then writes back; we `take` to express the same move-out/move-in. + // `take` expresses the move-out/move-in: mutate the Vec, then + // write it back at loop end. let mut cross_chunk_prefix_stmts = Vec::::default(); CrossChunkImport::sorted_cross_chunk_imports( @@ -594,7 +586,6 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( alias_loc: bun_ast::Loc::EMPTY, original_name: bun_ast::StoreStr::new(b"" as &[u8]), }); - // PERF(port): was appendAssumeCapacity — profile if it shows up on a hot path } cross_chunk_imports.push(chunk::ChunkImport { @@ -602,7 +593,7 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( chunk_index: cross_chunk_import.chunk_index, }); let items_ptr = bun_ast::StoreSlice::new_mut(clauses.into_bump_slice_mut()); - // Zig: `c.allocator().create(S.Import)` + struct literal — + // Allocated directly from the arena — // bypasses Stmt.Data.Store (not pushed on this thread here). let import = c.arena().alloc(bun_ast::S::Import { items: items_ptr, @@ -631,8 +622,6 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( pub use crate::{DeferredBatchTask, ParseTask, ThreadPool}; -// `bun.options.Format` is the bundler output-format enum (Esm/Cjs/Iife/...); -// alias to keep callsites parallel with the Zig `c.options.output_format`. +// `Format` is the bundler output-format enum (Esm/Cjs/Iife/...); +// aliased so callsites read as `c.options.output_format`. use crate::options::Format as OutputFormat; - -// ported from: src/bundler/linker_context/computeCrossChunkDependencies.zig diff --git a/src/bundler/linker_context/convertStmtsForChunk.rs b/src/bundler/linker_context/convertStmtsForChunk.rs index 7b7dbf328bb..71b3c148d5b 100644 --- a/src/bundler/linker_context/convertStmtsForChunk.rs +++ b/src/bundler/linker_context/convertStmtsForChunk.rs @@ -178,7 +178,6 @@ pub fn convert_stmts_for_chunk( // Prefix this module with "__reExport(exports, ns, module.exports)" let export_star_ref = c.runtime_function(b"__reExport"); let args_len = 2 + usize::from(module_exports_for_export.is_some()); - // PERF(port): was arena alloc of [Expr; N] — using Vec→Box let mut args: Vec = Vec::with_capacity(args_len); args.push(Expr::init( E::Identifier { @@ -196,7 +195,9 @@ pub fn convert_stmts_for_chunk( )); if let Some(mod_) = module_exports_for_export { - // TODO(port): Zig writes args[3] which is out-of-bounds (len is 3); preserved as args[2] — verify intent. + // Per the "__reExport(exports, ns, module.exports)" + // call shape above, this is the third argument, + // so append at index 2. args.push(mod_); } @@ -294,7 +295,6 @@ pub fn convert_stmts_for_chunk( // Prefix this module with "__reExport(exports, require(path), module.exports)" let export_star_ref = c.runtime_function(b"__reExport"); let args_len = 2 + usize::from(module_exports_for_export.is_some()); - // PERF(port): was arena alloc of [Expr; N] — using Vec→Box let mut args: Vec = Vec::with_capacity(args_len); args.push(Expr::init( E::Identifier { @@ -408,7 +408,7 @@ pub fn convert_stmts_for_chunk( // Be c areful to not modify the original statement stmt = Stmt::alloc( S::Function { - // SAFETY: shallow bitwise copy of arena-backed G::Fn (matches Zig `s.func`). + // SAFETY: shallow bitwise copy of arena-backed G::Fn. func: unsafe { core::ptr::read(&raw const s.func) }, }, stmt.loc, @@ -428,7 +428,7 @@ pub fn convert_stmts_for_chunk( // Be careful to not modify the original statement stmt = Stmt::alloc( S::Class { - // SAFETY: shallow bitwise copy of arena-backed E::Class (matches Zig `s.class`). + // SAFETY: shallow bitwise copy of arena-backed E::Class. class: unsafe { core::ptr::read(&raw const s.class) }, is_export: false, }, @@ -441,7 +441,7 @@ pub fn convert_stmts_for_chunk( // Strip the "export" keyword while bundling if should_strip_exports && s.is_export { // Be careful to not modify the original statement - // SAFETY: shallow bitwise copy of arena-backed S::Local (matches Zig `s.*`). + // SAFETY: shallow bitwise copy of arena-backed S::Local. let copied: S::Local = unsafe { core::ptr::read(s.as_ptr()) }; stmt = Stmt::alloc(copied, stmt.loc); stmt.data.s_local_mut().unwrap().is_export = false; @@ -518,7 +518,7 @@ pub fn convert_stmts_for_chunk( // Be careful to not modify the original statement stmt = Stmt::alloc( S::Function { - // SAFETY: shallow bitwise copy of arena-backed G::Fn (matches Zig `s2.func`). + // SAFETY: shallow bitwise copy of arena-backed G::Fn. func: unsafe { core::ptr::read(&raw const s2.func) }, @@ -536,7 +536,7 @@ pub fn convert_stmts_for_chunk( // Be careful to not modify the original statement stmt = Stmt::alloc( S::Class { - // SAFETY: shallow bitwise copy of arena-backed E::Class (matches Zig `s2.class`). + // SAFETY: shallow bitwise copy of arena-backed E::Class. class: unsafe { core::ptr::read(&raw const s2.class) }, @@ -606,5 +606,3 @@ pub fn convert_stmts_for_chunk( pub use crate::DeferredBatchTask::DeferredBatchTask; pub use crate::ParseTask; pub use crate::ThreadPool; - -// ported from: src/bundler/linker_context/convertStmtsForChunk.zig diff --git a/src/bundler/linker_context/convertStmtsForChunkForDevServer.rs b/src/bundler/linker_context/convertStmtsForChunkForDevServer.rs index ae25173e490..c0e1898dfde 100644 --- a/src/bundler/linker_context/convertStmtsForChunkForDevServer.rs +++ b/src/bundler/linker_context/convertStmtsForChunkForDevServer.rs @@ -183,7 +183,6 @@ pub fn convert_stmts_for_chunk_for_dev_server<'bump>( }, default_value: None, }); - // PERF(port): was assume_capacity-adjacent (arena append) esm_callbacks.push(Expr::init(E::Arrow::NOOP_RETURN_UNDEFINED, Loc::EMPTY)); } else { let binding = Binding::alloc( @@ -276,7 +275,7 @@ pub fn convert_stmts_for_chunk_for_dev_server<'bump>( Loc::EMPTY, ))?; // hmr.onUpdate = [ ... ]; - // PORT NOTE: reshaped for borrowck — capture len before moving esm_callbacks + // Capture len before moving `esm_callbacks` (borrowck). let callbacks_len = esm_callbacks.len(); stmts .inside_wrapper_prefix @@ -317,5 +316,3 @@ pub fn convert_stmts_for_chunk_for_dev_server<'bump>( pub use crate::DeferredBatchTask::DeferredBatchTask; pub use crate::ParseTask; pub use crate::ThreadPool; - -// ported from: src/bundler/linker_context/convertStmtsForChunkForDevServer.zig diff --git a/src/bundler/linker_context/doStep5.rs b/src/bundler/linker_context/doStep5.rs index 1742f67874c..f7fb2a3c802 100644 --- a/src/bundler/linker_context/doStep5.rs +++ b/src/bundler/linker_context/doStep5.rs @@ -1,6 +1,4 @@ -//! Port of `src/bundler/linker_context/doStep5.zig`. -//! -//! PORT NOTE: like `scanImportsAndExports.rs`, the Zig body holds many +//! Like `scanImportsAndExports.rs`, this step needs many //! overlapping `&mut` column slices out of `LinkerGraph.{ast,meta}` while also //! calling `&self` methods. The SoA columns are physically disjoint and never //! reallocate during this step, so we cache raw column pointers and deref at @@ -68,8 +66,8 @@ impl LinkerContext<'_> { // form `&mut BundleV2` here (concurrent tasks would alias it). let bundle_v2: &BundleV2<'_> = unsafe { &*LinkerContext::bundle_v2_ptr(this) }; let worker = ThreadPool::Worker::get(bundle_v2); - // Zig: `defer worker.unget()`. `Worker::get` returns the thread-local worker - // (not RAII), so balance explicitly via scopeguard. + // `Worker::get` returns the thread-local worker + // (not RAII), so balance with `unget` explicitly via scopeguard. let worker = scopeguard::guard(worker, |w| w.unget()); // we must use this arena here @@ -160,7 +158,6 @@ impl LinkerContext<'_> { re_exports_count += inner_count; aliases.push(alias); - // PERF(port): was appendAssumeCapacity } } // TODO: can this be u32 instead of a string? @@ -203,7 +200,7 @@ impl LinkerContext<'_> { // Each part tracks the other parts it depends on within this file let mut local_dependencies: HashMap = HashMap::default(); - // PORT NOTE: reshaped for borrowck — multiple `&mut` into graph SoA; + // Multiple `&mut` into graph SoA are needed here; // raw per-row pointers via `split_raw()` so concurrent tasks never // hold overlapping `&mut [T]`. let parts_slice: *mut [Part] = row_mut!(ast.parts, bun_ast::PartList, id).as_mut_slice(); @@ -216,11 +213,11 @@ impl LinkerContext<'_> { // the lookup entirely for files with no imports (≈ all leaf modules). let named_imports_is_empty = unsafe { (*named_imports).is_empty() }; - // PERF(port): hoist this file's two `top_level_symbols_to_parts` - // sub-maps. The Zig version reaches them through - // `c.topLevelSymbolsToParts(id, ref)` per symbol-use, which is fine - // when the underlying ArrayHashMap has its index_header (O(1) get). - // In the port, perf showed `find_hash` falling through to the linear + // PERF: hoist this file's two `top_level_symbols_to_parts` + // sub-maps rather than going through + // `c.topLevelSymbolsToParts(id, ref)` per symbol-use — that is fine + // when the underlying ArrayHashMap has its index_header (O(1) get), + // but perf showed `find_hash` falling through to the linear // scan branch here (≈87% of step5 self-time on three.js), so we (a) // hoist the per-file column pointer math out of the J×K inner loop // and (b) ensure the accelerator index is built on the large @@ -249,9 +246,9 @@ impl LinkerContext<'_> { // Now that all files have been parsed, determine which property // accesses off of imported symbols are inlined enum values and // which ones aren't - // PORT NOTE: reshaped for borrowck — Zig iterates keys()/values() while - // holding a mutable getPtr into part.symbol_uses; collect refs first. - // PERF(port): the property-use map is empty for the overwhelming + // We cannot iterate keys()/values() while + // holding a mutable pointer into part.symbol_uses; collect refs first. + // PERF: the property-use map is empty for the overwhelming // majority of parts (it only fills for `import * as ns`/enum // property accesses); skip the `to_vec()` alloc-round-trip in // that case. @@ -304,50 +301,12 @@ impl LinkerContext<'_> { // TODO: inline function calls here // TODO: Inline cross-module constants - // if (c.graph.const_values.count() > 0) { - // // First, find any symbol usage that points to a constant value. - // // This will be pretty rare. - // const first_constant_i: ?usize = brk: { - // for (part.symbol_uses.keys(), 0..) |ref, j| { - // if (c.graph.const_values.contains(ref)) { - // break :brk j; - // } - // } - // - // break :brk null; - // }; - // if (first_constant_i) |j| { - // var end_i: usize = 0; - // // symbol_uses is an array - // var keys = part.symbol_uses.keys()[j..]; - // var values = part.symbol_uses.values()[j..]; - // for (keys, values) |ref, val| { - // if (c.graph.const_values.contains(ref)) { - // continue; - // } - // - // keys[end_i] = ref; - // values[end_i] = val; - // end_i += 1; - // } - // part.symbol_uses.entries.len = end_i + j; - // - // if (part.symbol_uses.entries.len == 0 and part.can_be_removed_if_unused) { - // part.tag = .dead_due_to_inlining; - // part.dependencies.len = 0; - // continue :outer; - // } - // - // part.symbol_uses.reIndex(arena) catch unreachable; - // } - // } if false { break 'outer; - } // this `if` is here to preserve the unused - // block label from the above commented code. + } // this `if` preserves the otherwise-unused block label. // Now that we know this, we can determine cross-part dependencies - // PERF(port): iterate the keys slice directly (the index-based + // PERF: iterate the keys slice directly (the index-based // form re-loaded `keys.len()` and bounds-checked each access). let part_index_u32 = part_index as u32; let dependencies = &mut part.dependencies; @@ -399,12 +358,10 @@ impl LinkerContext<'_> { } } - /// Spec: `linker_context/doStep5.zig:createExportsForFile`. - /// /// WARNING: This method is run in parallel over all files. Do not mutate data /// for other files within this method or you will create a data race. /// - /// PORT NOTE: takes `&self` (read-only) plus the three SoA row cells it + /// Takes `&self` (read-only) plus the three SoA row cells it /// mutates as explicit `&mut` params, so the parallel `do_step5` dispatch /// never forms a concurrent `&mut LinkerContext` / whole-column `&mut [T]`. #[allow(clippy::too_many_arguments)] @@ -420,10 +377,17 @@ impl LinkerContext<'_> { ast_flags: &mut AstFlags, ast_parts: &mut bun_ast::PartList, ) { - // PORT NOTE: Zig toggled `Stmt.Disabler`/`Expr.Disabler` (debug-only - // re-entrancy guards around the global Store). `Disabler::scope()` - // calls `disable()` and re-`enable()`s on drop — currently no-op stubs - // until the thread-local toggle lands (`js_parser/ast/mod.rs`). + // `Stmt.Disabler`/`Expr.Disabler` are debug-only guards + // around the global thread-local block store. `Disabler::scope()` + // calls `disable()` and re-`enable()`s on drop. In debug builds the + // disabler only fires when `Store::append` falls through to that + // global slab — i.e. when no `ASTMemoryAllocator` scope is installed. + // Bundler workers always install one (`Worker::get` pushes + // `ast_memory_store`), so appends in this step — including + // `Stmt::assign` below — route to the worker allocator and bypass the + // check entirely. The guard + // exists to catch accidental global-slab use if this code ever runs + // without that allocator installed. let _stmt_guard = bun_ast::stmt::Disabler::scope(); let _expr_guard = bun_ast::expr::Disabler::scope(); @@ -451,8 +415,8 @@ impl LinkerContext<'_> { // + 1 if we need to do module.exports = __toCommonJS(exports) force_include_exports_for_entry_point as usize; - // PORT NOTE: Zig used `Stmt.Batcher` (preallocated arena slice + - // cursor). `Batcher::::init` requires `T: Default` which `Stmt` + // `Batcher::::init` (preallocated arena slice + cursor) requires + // `T: Default` which `Stmt` // doesn't satisfy, so we hand-roll the same shape: one arena slab of // `stmts_count` `MaybeUninit`, sliced front-to-back. `eat1` // becomes a `write` + sub-slice carve. The slab is held as a safe @@ -539,7 +503,6 @@ impl LinkerContext<'_> { )), ..Default::default() }); - // PERF(port): was appendAssumeCapacity ns_export_symbol_uses .put_assume_capacity(exp_data.import_ref, SymbolUse { count_estimate: 1 }); @@ -562,7 +525,7 @@ impl LinkerContext<'_> { let all_export_stmts_len = needs_exports_variable as usize + (!properties.is_empty()) as usize + force_include_exports_for_entry_point as usize; - // PORT NOTE: the trailing `all_export_stmts_len` slots of `stmts_slab` + // The trailing `all_export_stmts_len` slots of `stmts_slab` // (after the per-export `eat1`s above) are filled below in the order // {var exports={}, __export(...), module.exports=__toCommonJS(...)}. let all_export_stmts_base = stmts_head; @@ -602,9 +565,9 @@ impl LinkerContext<'_> { let mut export_ref = Ref::NONE; if !properties.is_empty() { export_ref = self.runtime_function(b"__export"); - // PORT NOTE: `bumpalo::Vec` → `Vec` via the global heap; + // `bumpalo::Vec` → `Vec` via the global heap; // `G::PropertyList` is `Vec` and currently has no - // arena-backed `move_from_list`, so re-own. PERF(port). + // arena-backed `move_from_list`, so re-own. let mut owned_props: Vec = Vec::with_capacity(properties.len()); owned_props.extend(properties.drain(..)); emit_export_stmt!(Stmt::allocate( @@ -724,5 +687,3 @@ impl LinkerContext<'_> { } } } - -// ported from: src/bundler/linker_context/doStep5.zig diff --git a/src/bundler/linker_context/findAllImportedPartsInJSOrder.rs b/src/bundler/linker_context/findAllImportedPartsInJSOrder.rs index 37e2a974744..e99ce4cb31c 100644 --- a/src/bundler/linker_context/findAllImportedPartsInJSOrder.rs +++ b/src/bundler/linker_context/findAllImportedPartsInJSOrder.rs @@ -17,7 +17,7 @@ pub fn find_all_imported_parts_in_js_order( let mut part_ranges_shared: Vec = Vec::new(); let mut parts_prefix_shared: Vec = Vec::new(); - // PERF(port): temp_arena dropped — these arena-fed scratch lists could become + // PERF: these scratch lists could become // `bun_alloc::ArenaVec<'bump, PartRange>` with a threaded `&'bump Bump` // (introduces lifetimes on this fn + visitor). Profile if hot. for (index, chunk) in chunks.iter_mut().enumerate() { @@ -47,12 +47,10 @@ pub fn find_imported_parts_in_js_order( ) -> Result<(), bun_core::Error> { let mut chunk_order_array: Vec = Vec::with_capacity(chunk.files_with_parts_in_chunk.count()); - // PERF(port): this.arena() dropped — was per-LinkerContext arena; profile if hot. { let distances = this.graph.files.items_distance_from_entry_point(); let stable_source_indices = this.graph.stable_source_indices.slice(); for &source_index in chunk.files_with_parts_in_chunk.keys() { - // PERF(port): was appendAssumeCapacity chunk_order_array.push(Order { source_index, distance: distances[source_index as usize], @@ -66,13 +64,13 @@ pub fn find_imported_parts_in_js_order( part_ranges_shared.clear(); parts_prefix_shared.clear(); - // PORT NOTE: reshaped for borrowck — capture before constructing visitor + // Capture before constructing the visitor (borrowck). let with_code_splitting = this.graph.code_splitting; let with_scb = this.graph.is_scb_bitset.bit_length > 0; - // PORT NOTE: the Zig visitor holds a *LinkerContext alongside SoA column slices - // borrowed from it, and mutates one column (`entry_point_chunk_index`). Rust - // borrowck forbids the latter through a shared `&LinkerContext`, so cache that + // The visitor holds a LinkerContext alongside SoA column slices + // borrowed from it, and mutates one column (`entry_point_chunk_index`). + // Borrowck forbids the latter through a shared `&LinkerContext`, so cache that // single mutable column as a raw `*mut [u32]` (provenance via the // `MultiArrayList.bytes: *mut u8` raw-pointer field — see // `scanImportsAndExports.rs` for the same pattern). All other `c.*` accesses @@ -82,7 +80,6 @@ pub fn find_imported_parts_in_js_order( let (files_in_chunk_order, parts_in_chunk_order) = { let mut visitor = FindImportedPartsVisitor { - // PERF(port): files/visited were this.arena() arena — profile if hot. files: Vec::new(), part_ranges: core::mem::take(part_ranges_shared), parts_prefix: core::mem::take(parts_prefix_shared), @@ -97,7 +94,6 @@ pub fn find_imported_parts_in_js_order( entry_point_chunk_indices, }; - // PERF(port): was comptime bool dispatch (nested `inline else`) — profile if hot. match (with_code_splitting, with_scb) { (true, true) => run_visits::(&mut visitor, &chunk_order_array), (true, false) => run_visits::(&mut visitor, &chunk_order_array), @@ -105,15 +101,13 @@ pub fn find_imported_parts_in_js_order( (false, false) => run_visits::(&mut visitor, &chunk_order_array), } - // PERF(port): was this.arena() arena — profile if hot. let mut parts_in_chunk_order: Vec = Vec::with_capacity(visitor.part_ranges.len() + visitor.parts_prefix.len()); // bun.concat: parts_prefix first, then part_ranges parts_in_chunk_order.extend_from_slice(&visitor.parts_prefix); parts_in_chunk_order.extend_from_slice(&visitor.part_ranges); - // Zig `defer { part_ranges_shared.* = visitor.part_ranges; ... visitor.visited.deinit(); }` - // No fallible ops remain past this point in Rust, so plain move-back is equivalent. + // No fallible ops remain past this point, so plain move-back works. *part_ranges_shared = visitor.part_ranges; *parts_prefix_shared = visitor.parts_prefix; // visitor.visited dropped implicitly @@ -121,7 +115,6 @@ pub fn find_imported_parts_in_js_order( (visitor.files, parts_in_chunk_order) }; - // PORT NOTE: `chunk.content.javascript` union field access → enum match. match &mut chunk.content { chunk::Content::Javascript(js) => { js.files_in_chunk_order = files_in_chunk_order.into_boxed_slice(); @@ -158,7 +151,7 @@ pub struct FindImportedPartsVisitor<'a, 'ctx> { pub entry_point: EntryPoint, pub chunk_index: u32, /// Raw column pointer into `c.graph.files` for the single mutable write in - /// `visit` (see PORT NOTE above). + /// `visit` (see the raw-pointer note above). entry_point_chunk_indices: *mut [u32], } @@ -290,5 +283,3 @@ impl<'a, 'ctx> FindImportedPartsVisitor<'a, 'ctx> { } } } - -// ported from: src/bundler/linker_context/findAllImportedPartsInJSOrder.zig diff --git a/src/bundler/linker_context/findImportedCSSFilesInJSOrder.rs b/src/bundler/linker_context/findImportedCSSFilesInJSOrder.rs index 722922097c6..5b3caae4a73 100644 --- a/src/bundler/linker_context/findImportedCSSFilesInJSOrder.rs +++ b/src/bundler/linker_context/findImportedCSSFilesInJSOrder.rs @@ -30,7 +30,6 @@ pub fn find_imported_css_files_in_js_order( _temp: &Arena, entry_point: Index, ) -> Vec { - // PERF(port): was arena bulk-free (DynamicBitSet now Box<[usize]>-backed). let mut visited = BitSet::init_empty(this.graph.files.len()).expect("oom"); let mut order: Vec = Vec::new(); @@ -38,8 +37,7 @@ pub fn find_imported_css_files_in_js_order( let all_loaders = this.parse_graph().input_files.items_loader(); let all_parts = this.graph.ast.items_parts(); - // Zig uses a local `struct { fn visit }.visit` to get a recursive local fn. - // Rust nested `fn` items can recurse directly. + // Nested `fn` item so the visitor can recurse directly. #[allow(clippy::too_many_arguments)] fn visit( c: &LinkerContext, @@ -104,5 +102,3 @@ pub fn find_imported_css_files_in_js_order( order } - -// ported from: src/bundler/linker_context/findImportedCSSFilesInJSOrder.zig diff --git a/src/bundler/linker_context/findImportedFilesInCSSOrder.rs b/src/bundler/linker_context/findImportedFilesInCSSOrder.rs index f196118950b..9efe3423f9d 100644 --- a/src/bundler/linker_context/findImportedFilesInCSSOrder.rs +++ b/src/bundler/linker_context/findImportedFilesInCSSOrder.rs @@ -14,10 +14,9 @@ use crate::linker_context_mod::debug; use crate::{Index, LinkerContext}; use bun_ast::Index as AstIndex; -// PORT NOTE: Zig `entry.*` / `@memcpy` are bitwise copies of arena-backed -// `CssImportOrder` values (the inner `Vec`s point into bump arenas and are -// never individually freed). Rust's `Vec` has `Drop`, so a literal `*entry` -// is not `Copy`. We replicate the Zig bitwise-copy semantics here. +// `CssImportOrder` values are arena-backed (the inner `Vec`s point into bump +// arenas and are never individually freed). Rust's `Vec` has `Drop`, so a +// literal `*entry` is not `Copy`; we duplicate the values bitwise instead. // `conditions` slabs come from `deep_clone_conditions`, which allocates them // from the `LinkerGraph` arena (`graph.heap`). The `Vec` headers aliasing a // slab are `mem::forget`'d everywhere (`CssImportOrder::drop` + the @@ -28,17 +27,16 @@ use bun_ast::Index as AstIndex; unsafe fn bitwise_copy(src: &T) -> T { // SAFETY: `src` is a valid aligned `&T`; the `unsafe fn` contract requires // the caller to ensure the duplicated value's `Drop` is suppressed (arena - // ownership, see PORT NOTE above) so the aliased buffer is never freed twice. + // ownership, see the bitwise-copy note above) so the aliased buffer is never freed twice. unsafe { core::ptr::read(src) } } -/// Zig: `order.len = wip_order.len; @memcpy(order.slice(), wip_order.slice()); -/// wip_order.clearRetainingCapacity();` — bitwise move of arena-backed entries -/// from `wip` back into `order`'s buffer (which always has `cap >= wip.len`). +/// Bitwise move of arena-backed entries from `wip` back into `order`'s +/// buffer (which always has `cap >= wip.len`). #[inline] fn memcpy_and_reset(order: &mut Vec, wip: &mut Vec) { debug_assert!(order.capacity() >= wip.len()); - // PORT NOTE: do not Drop `order`'s prior entries — they were already + // Do not Drop `order`'s prior entries — they were already // bitwise-copied into `wip` (see `bitwise_copy` callers above), so dropping // here would double-free their `conditions` buffers. // SAFETY: `set_len(0)` is unconditionally sound (0 ≤ capacity; shrinking @@ -46,7 +44,7 @@ fn memcpy_and_reset(order: &mut Vec, wip: &mut Vec( css_asts: &'a [crate::bundled_ast::CssCol], all_import_records: &'a [bun_ast::import_record::List<'a>], - // PORT NOTE: Zig's `graph: *LinkerGraph` is never read in `visit()`; - // dropped here to avoid an aliasing `&mut this.graph` borrow against + // No `graph` field — `visit()` never reads it, and holding one would + // create an aliasing `&mut this.graph` borrow against // `arena`/`css_asts` (which already borrow `this.graph`). // `BackRef` (not `&'a Graph`) so the visitor's `'a` borrow stays // disjoint from `LinkerContext` (constructed from the raw `parse_graph` @@ -156,9 +154,8 @@ pub fn find_imported_files_in_css_order<'a>( // }) // } - // PORT NOTE: `defer { _ = visitor.visited.pop(); }` — explicit pop at end. - // The defer is registered AFTER the `orelse return` above, so skipping the - // pop on that early-return path matches the original semantics. + // `visited.pop()` happens at the end of this function; the early + // return above intentionally skips it. // Iterate over the top-level "@import" rules let mut import_record_idx: usize = 0; @@ -237,9 +234,8 @@ pub fn find_imported_files_in_css_order<'a>( } else { self.order.push(CssImportOrder { kind: CssImportOrderKind::ExternalPath(record.path), - // PORT NOTE: Zig `wrapping_conditions.*` is a bitwise struct copy. // SAFETY: arena-backed `Vec` header; the pushed - // `CssImportOrder` never drops it (see PORT NOTE at + // `CssImportOrder` never drops it (see the note at // `bitwise_copy`), so the aliased buffer is freed once // with the arena. conditions: unsafe { bitwise_copy(wrapping_conditions) }, @@ -284,25 +280,23 @@ pub fn find_imported_files_in_css_order<'a>( } // Accumulate imports in depth-first postorder self.order.push(CssImportOrder { - // PORT NOTE: `crate::Index` (= `bun_ast::Index`) and the + // `crate::Index` (= `bun_ast::Index`) and the // `bun_ast::Index` carried by `CssImportOrderKind::SourceIndex` - // are TYPE_ONLY mirrors of the same Zig `bun.ast.Index`; + // are the same underlying index type; // both are `#[repr(transparent)]` over `u32`. kind: CssImportOrderKind::SourceIndex(AstIndex(source_index.get())), - // PORT NOTE: Zig `wrapping_conditions.*` is a bitwise struct copy. // SAFETY: arena-backed `Vec` header; `CssImportOrder` suppresses - // `Drop` on it (see PORT NOTE at `bitwise_copy`), so the aliased + // `Drop` on it (see the note at `bitwise_copy`), so the aliased // buffer is freed once with the arena. conditions: unsafe { bitwise_copy(wrapping_conditions) }, condition_import_records: Vec::new(), }); - // PORT NOTE: explicit pop replacing `defer { _ = visitor.visited.pop(); }` let _ = self.visited.pop(); } } - // PORT NOTE: reshaped for borrowck — read MultiArrayList columns before constructing visitor. + // Read MultiArrayList columns before constructing the visitor (borrowck). let css_asts_slice: &[crate::bundled_ast::CssCol] = this.graph.ast.items_css(); let all_import_records_slice = this.graph.ast.items_import_records(); let arena = this.graph.arena(); @@ -387,11 +381,10 @@ pub fn find_imported_files_in_css_order<'a>( let mut external_path_duplicates: StringArrayHashMap> = StringArrayHashMap::new(); let mut i: u32 = order.len() as u32; - // PORT NOTE: reshaped for borrowck — `order.at(i)` and `order.mut_(i)` + // Borrowck: `order.at(i)` and `order.mut_(i)` // cannot overlap, and `is_conditional_import_redundant` needs to read // both `entry.conditions` and `order.at(j).conditions`. Hold raw - // pointers into the Vec buffer (Zig used the same pattern via - // `*const` slice returns); `order.mut_(i)` only writes `.kind` and + // pointers into the Vec buffer; `order.mut_(i)` only writes `.kind` and // never reallocates, so the conditions pointer stays valid. let order_ptr = order.as_mut_ptr(); 'next_backward: while i != 0 { @@ -413,7 +406,7 @@ pub fn find_imported_files_in_css_order<'a>( // So we should keep the @layer rules so that the cascade ordering of layers // is preserved // - // PORT NOTE: `crate::bun_css::LayerName` (lifetime-erased + // `crate::bun_css::LayerName` (lifetime-erased // shadow) and `::bun_css::LayerName` are distinct nominal // types until the ungate shadow is removed; cast through // `NonNull` to satisfy `Layers::borrow`. @@ -458,7 +451,7 @@ pub fn find_imported_files_in_css_order<'a>( // copy instead of the last copy like other things in CSS. { struct DuplicateEntry { - // PORT NOTE: lifetime-erased slice header — borrows either + // Lifetime-erased slice header — borrows either // `css_asts[..].layer_names` (real `::bun_css::LayerName`) or // `Layers::inner()` (shadow `LayerName`). Both nominal types should // be reconciled; until then we compare via @@ -564,7 +557,7 @@ pub fn find_imported_files_in_css_order<'a>( // layers_post_import let layers_key: *const [LayerName] = match &entry.kind { CssImportOrderKind::SourceIndex(idx) => { - // PORT NOTE: see LayerName nominal-type note above. + // See the LayerName nominal-type note above. std::ptr::from_ref::<[_]>( css_asts[idx.get() as usize] .as_deref() @@ -579,7 +572,7 @@ pub fn find_imported_files_in_css_order<'a>( // SAFETY: every match arm yields a pointer to a live slice (`css_asts` // arena, `entry.kind`'s `Layers`, or a static empty); the source-index // arm is a `*const [_]`-level cast between layout-identical `LayerName` - // shadows (see PORT NOTE above). Valid for this loop iteration. + // shadows (see the note above). Valid for this loop iteration. let layers_key: &[LayerName] = unsafe { &*layers_key }; let mut index: usize = 0; while index < layer_duplicates.len() as usize { @@ -741,8 +734,6 @@ pub fn find_imported_files_in_css_order<'a>( order } -/// Zig: `wrapping_conditions.deepCloneInfallible(visitor.arena)`. -/// /// The returned list is later bitwise-copied into `CssImportOrder` entries via /// `bitwise_copy(wrapping_conditions)`, so callers `mem::forget` the local after /// the recursive `visit()` to keep the aliased buffer alive. The slab is @@ -772,15 +763,13 @@ fn deep_clone_conditions(list: &Vec, arena: &Arena) -> Vec) -> Vec { let mut out = Vec::::init_capacity(list.len() as usize); for r in list.slice_const() { - // PORT NOTE: `ImportRecord` is plain-old-data in Zig (no destructor); - // `Path<'static>` slices borrow resolver storage. Bitwise copy matches - // the Zig `clone(arena)` semantics. + // `ImportRecord` is plain-old-data; its `Path<'static>` slices borrow + // resolver storage. // SAFETY: `ImportRecord` is POD (borrowed slices, no owning `Drop`); a // bitwise duplicate aliasing the same resolver storage is sound and // neither copy frees it. @@ -919,11 +908,9 @@ impl CssOrderDebugStep { } fn debug_css_order(this: &LinkerContext, order: &Vec, step: CssOrderDebugStep) { - // PERF(port): `step` was a comptime enum param; debug-only so demoted to runtime. + // `step` is a runtime param; this path is debug-only. #[cfg(debug_assertions)] { - // PORT NOTE: comptime `"BUN_DEBUG_CSS_ORDER_" ++ @tagName(step)` — - // runtime concat is fine here (debug-only). let tag = step.tag_name(); let env_var = format!("BUN_DEBUG_CSS_ORDER_{}\0", tag); let enable_all = bun_core::env_var::BUN_DEBUG_CSS_ORDER @@ -1014,5 +1001,3 @@ fn debug_css_order_impl( pub use crate::DeferredBatchTask; pub use crate::ParseTask; pub use crate::ThreadPool; - -// ported from: src/bundler/linker_context/findImportedFilesInCSSOrder.zig diff --git a/src/bundler/linker_context/generateChunksInParallel.rs b/src/bundler/linker_context/generateChunksInParallel.rs index 6a5954e67de..f77ca1dcbb4 100644 --- a/src/bundler/linker_context/generateChunksInParallel.rs +++ b/src/bundler/linker_context/generateChunksInParallel.rs @@ -5,8 +5,6 @@ use std::io::Write as _; use bun_collections::AutoBitSet; use bun_collections::StringArrayHashMap; use bun_collections::StringHashMap; -// PORT NOTE: Zig `bun.threading.ThreadPool` is the *module*; `Batch`/`Task` -// are free types in that module, not associated types on the struct. use bun_core::String as BunString; use bun_core::strings; use bun_paths as path; @@ -35,21 +33,18 @@ use crate::linker_context::static_route_visitor::StaticRouteVisitor; use crate::linker_context::write_output_files_to_disk::write_output_files_to_disk; use crate::linker_context_mod::{GenerateChunkCtx, PendingPartRange}; -/// `bun.BYTECODE_EXTENSION` (bun.zig). Local because `bun_core` doesn't -/// re-export it; matches `bun.rs::bytecode_extension`. +/// Bytecode output file extension (also defined in `writeOutputFilesToDisk.rs`). const BYTECODE_EXTENSION: &str = ".jsc"; bun_core::declare_scope!(PartRanges, hidden); -// PORT NOTE: `Chunk.final_rel_path` / `metafile_chunk_json` are owned -// `Box<[u8]>` (Zig stored them as linker-arena `[]const u8`); assignments +// `Chunk.final_rel_path` / `metafile_chunk_json` are owned +// `Box<[u8]>`; assignments // below move the boxed buffer directly — no lifetime promotion needed. use crate::linker_context_mod::debug; -// TODO(port): Zig's return type is `!if (is_dev_server) void else ArrayList(OutputFile)`. -// Rust const generics cannot vary the return type, so we always return -// `Vec` and the IS_DEV_SERVER path returns an empty Vec. Could be -// split into two monomorphized wrappers if the unused Vec matters. +// Const generics cannot vary the return type, so we always return +// `Vec` and the IS_DEV_SERVER path returns an empty Vec. pub fn generate_chunks_in_parallel( c: &mut LinkerContext, chunks: &mut [Chunk], @@ -66,7 +61,6 @@ pub fn generate_chunks_in_parallel( { // TODO: instead of running a renamer per chunk, run it per file debug!(" START {} renamers", chunks.len()); - // PORT NOTE: Zig `defer debug(...)` is moved to end-of-scope explicitly below. let ctx = GenerateChunkCtx { chunk: bun_ptr::BackRef::new_mut(&mut chunks[0]), // SAFETY: `c` is the live `&mut LinkerContext` for the link step; @@ -84,7 +78,7 @@ pub fn generate_chunks_in_parallel( if c.source_maps.line_offset_tasks.len() > 0 { debug!(" START {} source maps (line offset)", chunks.len()); c.source_maps.line_offset_wait_group.wait(); - // PORT NOTE: `c.arena().free(...)` + `.len = 0` → Vec drop semantics. + // `c.arena().free(...)` + `.len = 0` → Vec drop semantics. c.source_maps.line_offset_tasks = Box::default(); debug!(" DONE {} source maps (line offset)", chunks.len()); } @@ -107,7 +101,6 @@ pub fn generate_chunks_in_parallel( debug!(" START {} prepare CSS ast (total count)", total_count); let mut batch = ThreadPoolLib::Batch::default(); - // PERF(port): was c.arena().alloc — using Vec on global mimalloc let mut tasks: Vec = Vec::with_capacity(total_count); for chunk in chunks.iter_mut() { if chunk.content.is_css() { @@ -117,7 +110,7 @@ pub fn generate_chunks_in_parallel( callback: prepare_css_asts_for_chunk, }, chunk: std::ptr::from_mut::(chunk), - // PORT NOTE: `PrepareCssAstTask.linker` is `*mut LinkerContext<'static>` + // `PrepareCssAstTask.linker` is `*mut LinkerContext<'static>` // (raw ptr is invariant); `.cast()` erases the inner `'a` to satisfy it. linker: std::ptr::from_mut::(c).cast(), }); @@ -142,14 +135,13 @@ pub fn generate_chunks_in_parallel( } { - // PERF(port): was c.arena().alloc — using Vec on global mimalloc let mut chunk_contexts: Vec = Vec::with_capacity(chunks.len()); { let mut total_count: usize = 0; - // PORT NOTE: `GenerateChunkCtx` fields are raw pointers; capture them + // `GenerateChunkCtx` fields are raw pointers; capture them // before the `iter_mut()` borrow so the same `*mut [Chunk]` can be - // stored in every ctx (Zig stores `[]Chunk` by value). + // stored in every ctx. // SAFETY: `c` is the live `&mut LinkerContext` for the link step. let c_ref = unsafe { bun_ptr::ParentRef::from_raw_mut(std::ptr::from_mut::(c)) }; @@ -186,7 +178,6 @@ pub fn generate_chunks_in_parallel( debug_assert_eq!(chunks.len(), chunk_contexts.len()); debug!(" START {} compiling part ranges", total_count); - // PERF(port): was c.arena().alloc — using Vec on global mimalloc. // Pre-reserved to `total_count` so pushes never reallocate; the // batch holds raw pointers into this buffer. let mut combined_part_ranges: Vec = Vec::with_capacity(total_count); @@ -223,8 +214,8 @@ pub fn generate_chunks_in_parallel( node: ThreadPoolLib::Node::default(), callback: generate_compile_result_for_js_chunk, }, - // SAFETY: `PendingPartRange.ctx` is `&'a GenerateChunkCtx<'a>` - // (Zig: `*GenerateChunkCtx`), conflating the borrow with + // SAFETY: `PendingPartRange.ctx` is `&'a GenerateChunkCtx<'a>`, + // conflating the borrow with // LinkerContext's `'a`. Launder via raw ptr so borrowck // doesn't pin `chunk_contexts` for `'a`; tasks complete // before `chunk_contexts` drops (we `wait_for_all` below). @@ -246,8 +237,8 @@ pub fn generate_chunks_in_parallel( node: ThreadPoolLib::Node::default(), callback: generate_compile_result_for_css_chunk, }, - // SAFETY: `PendingPartRange.ctx` is `&'a GenerateChunkCtx<'a>` - // (Zig: `*GenerateChunkCtx`), conflating the borrow with + // SAFETY: `PendingPartRange.ctx` is `&'a GenerateChunkCtx<'a>`, + // conflating the borrow with // LinkerContext's `'a`. Launder via raw ptr so borrowck // doesn't pin `chunk_contexts` for `'a`; tasks complete // before `chunk_contexts` drops (we `wait_for_all` below). @@ -268,8 +259,8 @@ pub fn generate_chunks_in_parallel( node: ThreadPoolLib::Node::default(), callback: generate_compile_result_for_html_chunk, }, - // SAFETY: `PendingPartRange.ctx` is `&'a GenerateChunkCtx<'a>` - // (Zig: `*GenerateChunkCtx`), conflating the borrow with + // SAFETY: `PendingPartRange.ctx` is `&'a GenerateChunkCtx<'a>`, + // conflating the borrow with // LinkerContext's `'a`. Launder via raw ptr so borrowck // doesn't pin `chunk_contexts` for `'a`; tasks complete // before `chunk_contexts` drops (we `wait_for_all` below). @@ -353,7 +344,7 @@ pub fn generate_chunks_in_parallel( // Compute the final hashes of each chunk, then use those to create the final // paths of each chunk. This can technically be done in parallel but it // probably doesn't matter so much because we're not hashing that much data. - // PORT NOTE: reshaped for borrowck — index loop so `chunks` can be passed + // Reshaped for borrowck — index loop so `chunks` can be passed // whole to `append_isolated_hashes_for_imported_chunks` and then indexed. for index in 0..chunks.len() { let mut hash = ContentHasher::default(); @@ -368,10 +359,10 @@ pub fn generate_chunks_in_parallel( chunk.template.placeholder.hash = Some(hash.digest()); let mut rel_path: Vec = Vec::new(); - // PORT NOTE: use the byte-writer (`PathTemplate::print`) directly — + // Use the byte-writer (`PathTemplate::print`) directly — // routing through `Display`/`write!` goes via `from_utf8_lossy`, // which would replace non-UTF-8 dir bytes with U+FFFD and corrupt - // the output path. Zig's `std.fmt.allocPrint` writes raw bytes. + // the output path. chunk .template .print(&mut rel_path) @@ -447,7 +438,6 @@ pub fn generate_chunks_in_parallel( c.log_mut().add_error(None, bun_ast::Loc::EMPTY, msg); - // PORT NOTE: Zig `inline for` over a homogeneous tuple → const array + plain for. for (name, template) in [ ("entry", entry_naming), ("chunk", chunk_naming), @@ -508,7 +498,7 @@ pub fn generate_chunks_in_parallel( let mut resolved: Vec = Vec::new(); resolved.extend_from_slice(normalizer[0]); resolved.extend_from_slice(normalizer[1]); - let _ = unique_key_to_path.put(ch.unique_key, resolved.into_boxed_slice()); // OOM-only Result (Zig: catch unreachable) + let _ = unique_key_to_path.put(ch.unique_key, resolved.into_boxed_slice()); // OOM-only Result } } @@ -557,14 +547,14 @@ pub fn generate_chunks_in_parallel( // Free the ModuleInfo now that it's been serialized to bytes. // It was allocated with bun.default_allocator (not the arena), // so it must be explicitly destroyed. - // PORT NOTE: in Rust, dropping the Option> frees it. + // In Rust, dropping the Option> frees it. js.module_info = None; } } // Generate metafile JSON fragments for each chunk (after paths are resolved) if c.options.metafile { - // PORT NOTE: reshaped for borrowck — `generate_chunk_json` reads all chunks + // Reshaped for borrowck — `generate_chunk_json` reads all chunks // immutably while we write one chunk's `metafile_chunk_json`; index split. for i in 0..chunks.len() { let json = @@ -605,8 +595,8 @@ pub fn generate_chunks_in_parallel( let bundler: &mut BundleV2 = unsafe { &mut *LinkerContext::bundle_v2_ptr(std::ptr::from_mut::(c)) }; let mut static_route_visitor = StaticRouteVisitor { - // SAFETY: Zig stores `c: *LinkerContext` (raw). Launder via raw ptr so this - // long-lived shared borrow doesn't conflict with `c.log_disjoint()` inside + // SAFETY: launder via raw ptr so this long-lived + // shared borrow doesn't conflict with `c.log_disjoint()` inside // the chunk loop below. `c` outlives `static_route_visitor`. c: unsafe { bun_ptr::detach_lifetime_ref::(c) }, cache: bun_collections::ArrayHashMap::default(), @@ -618,16 +608,15 @@ pub fn generate_chunks_in_parallel( // Closing tag escaping (>>` frees via `Drop` (global mimalloc); if `allocatorForSize` - // returns a distinct arena for large buffers, matched-arena dealloc must be - // restored here. + // Buffers are freed via `Drop` (global mimalloc); if + // `Chunk::allocator_for_size` ever becomes size-dependent, matched-arena + // dealloc must be restored here. let mut standalone_chunk_contents: Option>>> = None; if is_standalone { let mut scc: Vec>> = vec![None; chunks.len()]; - // PORT NOTE: `IntermediateOutput.code_standalone` reads `&Chunk` / + // `IntermediateOutput.code_standalone` reads `&Chunk` / // `&[Chunk]` (chunk is `&chunks[ci]`). Take `intermediate_output` out // by value so the only `&mut` is disjoint from those shared borrows. for ci in 0..chunks.len() { @@ -675,7 +664,7 @@ pub fn generate_chunks_in_parallel( )?; } else { // In-memory build (also used for standalone mode) - // PORT NOTE: `code()` / `code_standalone()` read `chunk` (= `&chunks[i]`) + // `code()` / `code_standalone()` read `chunk` (= `&chunks[i]`) // and the full `&[Chunk]` slice simultaneously. Iterate by index so both // can be safe shared reborrows of `chunks`; the only per-chunk mutation // is the `intermediate_output` take/restore, done via `chunks[i]`. @@ -810,9 +799,7 @@ pub fn generate_chunks_in_parallel( + a.len() + b.len() + b"\n".len(); - // TODO(port): Zig uses Chunk.IntermediateOutput.allocatorForSize(total_len) let mut buf: Vec = Vec::with_capacity(total_len); - // PERF(port): was appendSliceAssumeCapacity buf.extend_from_slice(&code_result.buffer); buf.extend_from_slice(source_map_start); buf.extend_from_slice(a); @@ -849,10 +836,8 @@ pub fn generate_chunks_in_parallel( let source_map_start = b"//# sourceMappingURL=data:application/json;base64,"; let total_len = code_result.buffer.len() + source_map_start.len() + encode_len + 1; - // TODO(port): Zig uses Chunk.IntermediateOutput.allocatorForSize(total_len) let mut buf: Vec = Vec::with_capacity(total_len); - // PERF(port): was appendSliceAssumeCapacity buf.extend_from_slice(&code_result.buffer); buf.extend_from_slice(source_map_start); @@ -970,7 +955,7 @@ pub fn generate_chunks_in_parallel( })); } else { // an error - // logger OOM-only (Zig: catch unreachable) + // logger OOM-only // Split-borrow — `static_route_visitor.c` holds a // detached `&LinkerContext`; `log_disjoint` returns the // disjoint `Transpiler.log` backref so no `&mut c` is @@ -1107,8 +1092,7 @@ pub fn generate_chunks_in_parallel( None }, referenced_css_chunks: match &chunk.content { - // Zig: `@ptrCast(dupe(u32, js.css_chunks))` — `output_file::Index` - // is `#[repr(transparent)]` over u32. + // `output_file::Index` is `#[repr(transparent)]` over u32. crate::chunk::Content::Javascript(js) => js .css_chunks .iter() @@ -1164,13 +1148,10 @@ pub fn generate_chunks_in_parallel( // Deinit dropped items to free their heap allocations (paths, buffers). let mut result = output_files.take(); let mut write_idx: usize = 0; - // PORT NOTE: reshaped for borrowck — Zig iterates by pointer and assigns into items[write_idx]. let len = result.len(); for i in 0..len { if result[i].loader == Loader::Html { result.swap(write_idx, i); - // PORT NOTE: Zig copies item then leaves original; using swap keeps semantics - // (the slot at `i` will be truncated/dropped below). write_idx += 1; } // else: item at `i` will be dropped by truncate below (impl Drop handles deinit) @@ -1184,9 +1165,6 @@ pub fn generate_chunks_in_parallel( pub use crate::ThreadPool; -// TODO(port): narrow error set use crate::EntryPoint; use crate::options::SourceMapOption; use crate::output_file::BakeExtra; - -// ported from: src/bundler/linker_context/generateChunksInParallel.zig diff --git a/src/bundler/linker_context/generateCodeForFileInChunkJS.rs b/src/bundler/linker_context/generateCodeForFileInChunkJS.rs index 72dcd1714bb..5970140bb64 100644 --- a/src/bundler/linker_context/generateCodeForFileInChunkJS.rs +++ b/src/bundler/linker_context/generateCodeForFileInChunkJS.rs @@ -20,9 +20,6 @@ use bun_js_parser::lexer as js_lexer; use super::convert_stmts_for_chunk::convert_stmts_for_chunk; use super::convert_stmts_for_chunk_for_dev_server::convert_stmts_for_chunk_for_dev_server; -// PORT NOTE: MultiArrayList column access — Zig `list.items(.field)` is mapped here as -// `list.items_field()` method calls (codegen'd accessors on the SoA wrappers). - #[allow(clippy::too_many_arguments)] pub fn generate_code_for_file_in_chunk_js<'r, 'src>( c: &mut LinkerContext, @@ -40,9 +37,9 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( ) -> js_printer::PrintResult { let source_index = part_range.source_index.get() as usize; - // PORT NOTE: reshaped for borrowck — grab raw pointers to the SoA columns up front so + // Grab raw pointers to the SoA columns up front so // subsequent `&mut c` borrows (convert_stmts_for_chunk, print_code_for_file_in_chunk_js) - // don't conflict. Matches Zig which slices once at the top. + // don't conflict. // SAFETY: the underlying MultiArrayList storage is not resized for the duration of this // function (linking has already sized everything). let parts: *mut [Part] = { @@ -61,7 +58,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // referencing everything by array makes the code a lot more annoying :( // - // PORT NOTE: `MultiArrayList::get` returns `ManuallyDrop` — the + // `MultiArrayList::get` returns `ManuallyDrop` — the // storage retains ownership of every Drop field (`parts`, `symbols`, // `named_imports`, …). The local `flags` mutation below is intentional and // stays scoped to this read view. @@ -74,7 +71,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( if c.options.output_format == OutputFormat::InternalBakeDev { 'brk: { if part_range.source_index.is_runtime() { - // PERF(port): was @branchHint(.cold) debug_assert!(c.dev_server.is_none()); break 'brk; // this is from `bun build --format=internal_bake_dev` } @@ -96,7 +92,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( let all_stmts_len = main_stmts_len + stmts.outside_wrapper_prefix.len() + 1; stmts.all_stmts.reserve(all_stmts_len); - // PERF(port): was appendSliceAssumeCapacity stmts .all_stmts .extend_from_slice(stmts.inside_wrapper_prefix.stmts.as_slice()); @@ -104,10 +99,10 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( .all_stmts .extend_from_slice(stmts.inside_wrapper_suffix.as_slice()); - // PORT NOTE: reshaped for borrowck — capture pointer/len now, re-slice after pushes. + // Capture pointer/len now, re-slice after pushes (borrowck). // SAFETY: `inner` aliases the first `main_stmts_len` elements of `all_stmts`; // subsequent pushes only append past this range and capacity was reserved above - // so no reallocation occurs. Matches Zig which slices then continues appending. + // so no reallocation occurs. let inner = bun_ast::StoreSlice::new_mut( &mut stmts.all_stmts.as_mut_slice()[0..main_stmts_len], ); @@ -148,7 +143,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( }); } - // PERF(port): was temp_arena.dupe — `G::Arg` is not Copy in Rust + // `G::Arg` is not `Copy`; duplicate the args element-wise. let dup_args: &mut [G::Arg] = { let mut v = bun_alloc::ArenaVec::with_capacity_in( clousure_args.const_slice().len(), @@ -160,7 +155,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( v.into_bump_slice_mut() }; - // PERF(port): was appendAssumeCapacity stmts.all_stmts.push(Stmt::allocate_expr( temp_arena, Expr::init( @@ -177,7 +171,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( bun_ast::Loc::EMPTY, ), )); - // PERF(port): was appendSliceAssumeCapacity stmts .all_stmts .extend_from_slice(stmts.outside_wrapper_prefix.as_slice()); @@ -187,8 +180,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // TODO: there is a weird edge case where the pretty path is not computed // it does not reproduce when debugging. let source_ref = c.get_source(source_index as u32); - // PORT NOTE: reshaped for borrowck — Zig copies the `Source` by value, - // mutates `.path`, and passes `&source`. `bun_ast::Source` is not `Clone` + // `bun_ast::Source` is not `Clone` // (its `Cow` fields would deep-copy `Owned` data); instead, build a // borrowed-field shadow only when the path needs fixing. let source_storage: bun_ast::Source; @@ -309,7 +301,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( match flags.wrap { WrapKind::Esm => { - // PORT NOTE: reshaped for borrowck — append_slice borrows `stmts` mutably while + // Borrowck: `append_slice` borrows `stmts` mutably while // also reading from a sibling field. let suffix = core::mem::take(&mut stmts.inside_wrapper_suffix); stmts.append_slice(StmtListWhich::OutsideWrapperPrefix, suffix.as_slice()); @@ -399,9 +391,8 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // Be careful: the top-level value in a JSON file is not necessarily an object if let ExprData::EObject(e_object) = default_expr.data { - // PORT NOTE: Zig `properties.clone(temp_arena)` is a memcpy into the - // temp arena. `G::Property` is not `Clone` (it embeds a `Vec`), so - // mirror the Zig bitwise copy directly. JSON object properties carry no + // `G::Property` is not `Clone` (it embeds a `Vec`), so + // copy the properties bitwise. JSON object properties carry no // owned heap data (`ts_decorators` is always empty, `class_static_block` // is `None`), so the duplicated bits do not alias any allocation. let src_len = e_object.properties.len(); @@ -519,7 +510,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( stmts .all_stmts .reserve(stmts.inside_wrapper_prefix.stmts.len() + stmts.inside_wrapper_suffix.len()); - // PERF(port): was appendSliceAssumeCapacity stmts .all_stmts .extend_from_slice(stmts.inside_wrapper_prefix.stmts.as_slice()); @@ -557,7 +547,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( .flags .intersects(AstFlags::USES_MODULE_REF | AstFlags::USES_EXPORTS_REF) { - // PERF(port): was appendAssumeCapacity args.push(G::Arg { binding: Binding::alloc( temp_arena, @@ -570,7 +559,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( }); if ast.flags.contains(AstFlags::USES_MODULE_REF) { - // PERF(port): was appendAssumeCapacity args.push(G::Arg { binding: Binding::alloc( temp_arena, @@ -692,7 +680,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // Hoist all top-level "var" and "function" declarations out of the closure { let mut end: usize = 0; - // PORT NOTE: reshaped for borrowck — iterate by index since we mutate + // Iterate by index since we mutate // `inner_stmts[end]` and call `stmts.append(...)` inside the loop. 'hoist: for i in 0..stmts.all_stmts.len() { let stmt = stmts.all_stmts[i]; @@ -751,10 +739,9 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( continue 'hoist; } StmtData::SClass(mut class) => 'stmt: { - // PORT NOTE: `class` is `StoreRef` — an arena-owned pointer. + // `class` is `StoreRef` — an arena-owned pointer. // `&mut class.class` (via DerefMut) yields a `&mut G::Class` into arena - // memory, so wrapping it in a StoreRef for `EClass` is sound and matches - // Zig's `&class.class`. + // memory, so wrapping it in a StoreRef for `EClass` is sound. if class.class.can_be_moved() { stmts.append(StmtListWhich::OutsideWrapperPrefix, stmt); continue 'hoist; @@ -1056,8 +1043,6 @@ impl DeclCollector { if name.is_empty() { return; } - // Zig: `catch return` — silently drop on alloc failure. With std Vec - // push aborts on OOM, so there is nothing to catch. self.decls.push(DeclInfo { name: name.to_vec().into_boxed_slice(), kind, @@ -1073,7 +1058,7 @@ fn merge_adjacent_local_stmts(stmts: &mut Vec, _arena: &Bump) { let mut did_merge_with_previous_local = false; let mut end: usize = 1; - // PORT NOTE: reshaped for borrowck — iterate by index because we read `stmts[i]` + // Iterate by index because we read `stmts[i]` // and write `stmts[end - 1]` / `stmts[end]` in the same loop body. for i in 1..stmts.len() { let stmt = stmts[i]; @@ -1092,7 +1077,6 @@ fn merge_adjacent_local_stmts(stmts: &mut Vec, _arena: &Bump) { let mut clone = Vec::::init_capacity(before.decls.len() + after.decls.len()); - // PERF(port): was appendSliceAssumeCapacity clone.append_slice_assume_capacity(before.decls.slice()); clone.append_slice_assume_capacity(after.decls.slice()); // we must clone instead of overwrite in-place incase the same S.Local is used across threads @@ -1122,10 +1106,8 @@ fn merge_adjacent_local_stmts(stmts: &mut Vec, _arena: &Bump) { stmts.truncate(end); } -// Type aliases / re-imports for readability of match arms (mirrors Zig naming). +// Type aliases / re-imports for readability of match arms. use bun_ast::LocalKind; use bun_ast::binding::Data as BindingData; use bun_ast::expr::Data as ExprData; use bun_ast::stmt::Data as StmtData; - -// ported from: src/bundler/linker_context/generateCodeForFileInChunkJS.zig diff --git a/src/bundler/linker_context/generateCodeForLazyExport.rs b/src/bundler/linker_context/generateCodeForLazyExport.rs index cb3d546178d..6311bc6aade 100644 --- a/src/bundler/linker_context/generateCodeForLazyExport.rs +++ b/src/bundler/linker_context/generateCodeForLazyExport.rs @@ -37,7 +37,7 @@ pub fn generate_code_for_lazy_export( source_index: IndexInt, ) -> Result<(), AllocError> { let exports_kind = this.graph.ast.items_exports_kind()[source_index as usize]; - // PORT NOTE: reshaped for borrowck — take `parts` as a raw pointer *before* the + // Take `parts` as a raw pointer *before* the // long-lived immutable `items_css()` borrow below; re-borrowed again later as needed. let parts: *mut [Part] = this.graph.ast.items_parts_mut()[source_index as usize].as_mut_slice(); // SAFETY: parse_graph backref; raw deref because `all_sources` is held @@ -100,7 +100,6 @@ pub fn generate_code_for_lazy_export( struct Visitor<'a> { inner_visited: &'a mut BitSet, - // Zig: `std.AutoArrayHashMap(Ref, void)` → `ArrayHashMap` per collections map. composes_visited: &'a mut ArrayHashMap, parts: &'a mut Vec, all_import_records: &'a [bun_ast::import_record::List<'a>], @@ -111,7 +110,6 @@ pub fn generate_code_for_lazy_export( source_index: IndexInt, log: &'a mut Log, loc: Loc, - // PERF(port): was `std.mem.Allocator` (arena) — bundler is an AST crate; thread `&'bump Bump`. arena: &'a Arena, } @@ -132,7 +130,6 @@ pub fn generate_code_for_lazy_export( } self.visit_composes(ast, ref_, idx); - // PERF(port): was assume-OOM `catch |err| bun.handleOom(err)`; Vec::push aborts on OOM. self.parts.push(E::TemplatePart { value: Expr::init( E::NameOfSymbol { @@ -170,7 +167,6 @@ pub fn generate_code_for_lazy_export( .unwrap() .loc; - // PORT NOTE: was `catch |err| bun.handleOom(err)` — crash on OOM. self.log.add_range_error_fmt_with_note( Some(&self.all_sources[idx as usize]), bun_ast::Range { loc: compose_loc, ..Default::default() }, @@ -306,9 +302,8 @@ pub fn generate_code_for_lazy_export( } } - // PORT NOTE: Zig left `parts: undefined` and rebound per-iteration; Rust - // forbids uninit refs, so the Visitor is constructed inside the loop with - // a fresh `parts` borrow each time (reshaped for borrowck). + // The Visitor is constructed inside the loop with a fresh `parts` + // borrow each time (reshaped for borrowck). let all_symbols = this.graph.ast.items_symbols(); // SAFETY: `LinkerContext::arena()` returns a stable `&Arena` valid for the // link pass; detach via raw-pointer round-trip so it doesn't hold a `&self` @@ -319,7 +314,6 @@ pub fn generate_code_for_lazy_export( let ref_ = entry.ref_; debug_assert!(ref_.inner_index() < symbols.len() as u32); - // PERF(port): was arena-backed ArrayList (no deinit; `.items` moved into E.Template). let mut template_parts: Vec = Vec::new(); let mut value = Expr::init( E::NameOfSymbol { @@ -355,8 +349,7 @@ pub fn generate_code_for_lazy_export( tail_loc: stmt.loc, tail: E::TemplateContents::Cooked(E::String::init(b"")), }); - // PORT NOTE: Zig used an arena-backed ArrayList and moved `.items` - // into `E.Template`; mirror that by moving into the linker arena + // Move the parts into the linker arena // (freed when the linker arena drops). let parts_slice = bun_ast::StoreSlice::new_mut(arena.alloc_slice_fill_iter(template_parts)); @@ -430,16 +423,14 @@ pub fn generate_code_for_lazy_export( _ => { // Otherwise, generate ES6 export statements. These are added as additional // parts so they can be tree shaken individually. - // PORT NOTE: Zig `part.stmts.len = 0` truncates the slice. part.stmts = bun_ast::StoreSlice::EMPTY; if let ExprData::EObject(e_object) = &expr.data { for property in e_object.properties.slice() { let _: &G::Property = property; - // PORT NOTE: `Expr`/`ExprData`/`StoreRef<_>` are `Copy`. Copy `key` out so + // `Expr`/`ExprData`/`StoreRef<_>` are `Copy`. Copy `key` out so // `key_str: StoreRef` is a mutable local — `slice()` resolves - // the rope in-place via `DerefMut` into the arena slot (matches Zig's - // `property.key.?.data.e_string.slice(...)` which takes `*String`). + // the rope in-place via `DerefMut` into the arena slot. let Some(key) = property.key else { continue }; let ExprData::EString(mut key_str) = key.data else { continue; @@ -476,7 +467,6 @@ pub fn generate_code_for_lazy_export( // happened yet). So we need to wait until after tree shaking happens. let generated = this.generate_named_export_in_file(source_index, module_ref, name, name)?; - // PERF(port): was `this.arena().alloc(Stmt, 1)` (arena). let new_stmts: &mut [Stmt] = alloc.alloc_slice_fill_iter(core::iter::once(Stmt::alloc( S::Local { @@ -493,7 +483,7 @@ pub fn generate_code_for_lazy_export( }, key.loc, ))); - // PORT NOTE: `parts.ptr[generated[1]]` — re-borrow `parts` here for borrowck. + // Re-borrow `parts` here for borrowck. let parts = this.graph.ast.items_parts_mut()[source_index as usize].as_mut_slice(); parts[generated.1 as usize].stmts = bun_ast::StoreSlice::new_mut(new_stmts); @@ -501,7 +491,6 @@ pub fn generate_code_for_lazy_export( } { - // PERF(port): was `std.fmt.allocPrint` into arena; building into Vec then arena-dupe. let mut name_buf: Vec = Vec::new(); write!( &mut name_buf, @@ -542,5 +531,3 @@ pub fn generate_code_for_lazy_export( pub use crate::DeferredBatchTask; pub use crate::ParseTask; pub use crate::ThreadPool; - -// ported from: src/bundler/linker_context/generateCodeForLazyExport.zig diff --git a/src/bundler/linker_context/generateCompileResultForCssChunk.rs b/src/bundler/linker_context/generateCompileResultForCssChunk.rs index aa6ae59cf0d..72fe9ec8708 100644 --- a/src/bundler/linker_context/generateCompileResultForCssChunk.rs +++ b/src/bundler/linker_context/generateCompileResultForCssChunk.rs @@ -77,14 +77,12 @@ fn generate_compile_result_for_css_chunk_impl( // borrow via `BackRef::get` is fine. The heap is pinned for the worker's // lifetime; see `Worker::arena`. let arena = worker.arena.get(); - // PERF(port): was arena bulk-free (worker.temporary_arena.reset(.retain_capacity)). let _arena_reset = scopeguard::guard(&mut worker.temporary_arena, |arena| { // temporary_arena is initialized in Worker::create before any task runs. if let Some(a) = arena.as_mut() { a.reset(); } }); - // TODO(port): worker.arena threading — css crate is an AST crate and may want &'bump Bump let mut allocating_writer: Vec = Vec::new(); let Content::Css(css_content) = &chunk.content else { @@ -230,7 +228,7 @@ fn generate_compile_result_for_css_chunk_impl( if !output.is_empty() { // CONCURRENCY: key set is frozen before parallel codegen; take a // shared `&AtomicUsize` so concurrent workers updating the same - // source counter never alias a `&mut` (Zig: @atomicRmw .Add .monotonic). + // source counter never alias a `&mut`. if let Some(bytes) = chunk.files_with_parts_in_chunk.get(&idx.get()) { let _ = bytes.fetch_add(output.len(), Ordering::Relaxed); } @@ -247,5 +245,3 @@ fn generate_compile_result_for_css_chunk_impl( pub use crate::DeferredBatchTask; pub use crate::ParseTask; pub use crate::ThreadPool; - -// ported from: src/bundler/linker_context/generateCompileResultForCssChunk.zig diff --git a/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs b/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs index e8fb88000af..dcd297081a5 100644 --- a/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs +++ b/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs @@ -240,7 +240,6 @@ impl<'a> HTMLLoader<'a> { } self.added_head_tags = true; - // PERF(port): was stack-fallback (std.heap.stackFallback(256)) let slices = self.get_head_tags(); for slice in slices.as_slice() { end_tag.before(slice, true)?; @@ -255,7 +254,6 @@ impl<'a> HTMLLoader<'a> { } self.added_body_script = true; - // PERF(port): was stack-fallback (std.heap.stackFallback(256)) // SAFETY: `self.chunks` raw `*mut [Chunk]` valid for the link step; sole live `&mut`. let chunks = unsafe { &mut *self.chunks }; if let Some(js_chunk) = self.chunk.get_js_chunk_for_html(chunks) { @@ -272,7 +270,6 @@ impl<'a> HTMLLoader<'a> { } fn get_head_tags(&self) -> Vec> { - // PERF(port): was stack-fallback arena; now heap Vec let mut array: Vec> = Vec::with_capacity(2); // `self.chunk` is a `BackRef` (safe `Deref`). let chunk: &Chunk = &self.chunk; @@ -401,10 +398,11 @@ fn generate_compile_result_for_html_chunk_impl<'a>( let import_records = c.graph.ast.items_import_records(); let source_index = chunk.entry_point.source_index(); - // HTML bundles for dev server must be allocated to it, as it must outlive - // the bundle task. See `DevServer.RouteBundle.HTML.bundled_html_text` - // TODO(port): Zig used `dev.arena()` vs `worker.arena` to control output ownership. - // In Rust with global mimalloc this distinction collapses; verify DevServer ownership. + // HTML bundles for the dev server must outlive the bundle task (see + // `DevServer.RouteBundle.HTML.bundled_html_text`). The output is built in a + // plain `Vec` and returned as an owned `Box<[u8]>` inside + // `CompileResult::Html`, so it lives as long as whoever holds the + // `CompileResult` — no arena-lifetime distinction needed. // `c.log` is now `*mut Log` (raw backref); copy directly. The HTMLLoader.log // field is currently dead_code, so no write actually occurs through this @@ -462,8 +460,7 @@ fn generate_compile_result_for_html_chunk_impl<'a>( } else { 'brk: { if !html_loader.added_head_tags || !html_loader.added_body_script { - // PERF(port): @branchHint(.cold) — this is if the document is missing all head, body, and html elements. - // PERF(port): was stack-fallback (std.heap.stackFallback(256)) + // Cold path: the document is missing all of the head, body, and html elements. if !html_loader.added_head_tags { let slices = html_loader.get_head_tags(); for slice in slices.as_slice() { @@ -502,5 +499,3 @@ fn generate_compile_result_for_html_chunk_impl<'a>( } pub use crate::{DeferredBatchTask, ParseTask, ThreadPool}; - -// ported from: src/bundler/linker_context/generateCompileResultForHtmlChunk.zig diff --git a/src/bundler/linker_context/generateCompileResultForJSChunk.rs b/src/bundler/linker_context/generateCompileResultForJSChunk.rs index f33d4844344..4d5f83b3db9 100644 --- a/src/bundler/linker_context/generateCompileResultForJSChunk.rs +++ b/src/bundler/linker_context/generateCompileResultForJSChunk.rs @@ -34,7 +34,9 @@ pub unsafe fn generate_compile_result_for_js_chunk(task: *mut ThreadPoolLib::Tas let (part_range, c_ptr, chunk_ptr, mut worker) = unsafe { crate::linker_context_mod::pending_part_range_prologue(task) }; - // TODO(port): Environment.show_crash_trace — exact cfg key TBD; using feature = "show_crash_trace" + // Wired as a cargo feature through bun_runtime → bun_bundler → + // bun_crash_handler. No build profile enables the feature by + // default — it must be opted into explicitly. #[cfg(feature = "show_crash_trace")] let _crash_guard = { // `part_range.ctx.{c,chunk}` are `ParentRef`/`BackRef` — safe shared @@ -52,7 +54,7 @@ pub unsafe fn generate_compile_result_for_js_chunk(task: *mut ThreadPoolLib::Tas [part_range.part_range.source_index.get() as usize] .path; if bun_core::debug_flags::has_print_breakpoint(&path.pretty, &path.text) { - // @breakpoint() in Zig — no stable Rust equivalent; left as no-op + // No stable breakpoint intrinsic; left as a no-op. } } @@ -62,7 +64,8 @@ pub unsafe fn generate_compile_result_for_js_chunk(task: *mut ThreadPoolLib::Tas // borrows below are scoped to the impl call so they do not overlap the // raw slot write that follows. (Peer tasks still hold their own `&mut` // views into the same `LinkerContext`/`Chunk` for read-only printer use — - // see TODO(ub-audit) on `unsafe impl Sync for Chunk`.) + // see the renamer caveat / SAFETY note on `unsafe impl Sync for + // Chunk` in Chunk.rs.) let c_mut: &mut LinkerContext = unsafe { &mut *c_ptr }; // SAFETY: same mutable-provenance / disjoint-write contract as `c_ptr` above. let chunk_mut: &mut Chunk = unsafe { &mut *chunk_ptr }; @@ -89,13 +92,11 @@ fn generate_compile_result_for_js_chunk_impl( let _trace = bun_core::perf::trace("Bundler.generateCodeForFileInChunkJS"); // `defer trace.end()` → handled by Drop on _trace - // Client and server bundles for Bake must be globally allocated, as they - // must outlive the bundle task. - // TODO(port): runtime arena selection (dev_server vs default) — - // `DevServerHandle` does not yet expose an arena handle, and - // `BufferWriter::init()` / `DeclCollector.decls` use the global arena - // in the Rust port. Once `dispatch::DevServerHandle::arena()` exists, - // thread it here so dev-server bundles outlive the worker arena. + // Client and server bundles for Bake must outlive the bundle task. + // `BufferWriter::init()` output is allocated from the global heap and + // `DeclCollector.decls` from the worker heap (`worker.arena`, alive until + // bundle teardown) — both outlive the task's CompileResult consumption, + // so a per-dev-server arena would only be a perf optimization. let _ = c.dev_server; // temporary_arena / stmt_list are initialized in Worker::create before any task runs. @@ -104,14 +105,13 @@ fn generate_compile_result_for_js_chunk_impl( .as_mut() .expect("Worker.temporary_arena set in create()"); let mut buffer_writer = js_printer::BufferWriter::init(); - // Zig: `defer _ = arena.reset(.retain_capacity)` on a `std.heap.ArenaAllocator` - // (O(1) bump rewind, chunks retained). `temporary_arena` is a `MimallocArena` + // `temporary_arena` is a `MimallocArena` // here because `temp_arena` flows into `Stmt::allocate`/`Expr::allocate`/ // `Binding::alloc`/`ArenaVec`, all of which take `&MimallocArena` concretely; // a plain `reset()` would be `mi_heap_destroy + mi_heap_new` *per part_range* - // (perf-probe: 46× for one elysia build). Use `reset_retain_with_limit` — the - // codebase's mapping for Zig's `.retain_*` modes (see `ModuleLoader`'s - // `transpile_source_code_arena`): keep the heap warm across part_ranges and + // (perf-probe: 46× for one elysia build). Use `reset_retain_with_limit` + // (see `ModuleLoader`'s `transpile_source_code_arena`): + // keep the heap warm across part_ranges and // only pay the destroy+new round-trip once accumulated scratch exceeds the // limit. 8 MiB matches the module-arena precedent and comfortably covers a // worker's full part_range set for typical bundles, so this is ~one @@ -151,9 +151,8 @@ fn generate_compile_result_for_js_chunk_impl( let collect_decls = c.options.generate_bytecode_cache && c.options.output_format == OutputFormat::Esm && c.options.compile; - // PORT NOTE: Zig threaded `arena` (dev_server or default) into - // DeclCollector; the Rust DeclCollector wants `*const Arena`. Use the - // worker heap for now (see TODO above re: dev_server arena). + // DeclCollector wants `*const Arena` and uses the worker heap (see + // the dev-server allocation note above). let mut dc = DeclCollector { arena: worker.arena.as_ptr(), ..Default::default() @@ -164,8 +163,7 @@ fn generate_compile_result_for_js_chunk_impl( // a direct shared borrow is fine. Heap is pinned; see `Worker::arena`. let worker_alloc = worker.arena.get(); // SAFETY: split borrow of `chunk` — `generate_code_for_file_in_chunk_js` never - // touches `chunk.renamer` through its `chunk` parameter (Zig passes the renamer - // union by value alongside `*Chunk`); take a raw-ptr view so borrowck doesn't + // touches `chunk.renamer` through its `chunk` parameter; take a raw-ptr view so borrowck doesn't // see two overlapping `&mut chunk` borrows. let renamer_ptr: *mut crate::bun_renamer::ChunkRenamer = core::ptr::addr_of_mut!(chunk.renamer); let result = generate_code_for_file_in_chunk_js( @@ -218,5 +216,3 @@ fn generate_compile_result_for_js_chunk_impl( pub use crate::DeferredBatchTask::DeferredBatchTask; pub use crate::ParseTask; - -// ported from: src/bundler/linker_context/generateCompileResultForJSChunk.zig diff --git a/src/bundler/linker_context/postProcessCSSChunk.rs b/src/bundler/linker_context/postProcessCSSChunk.rs index 39867549c37..a774121cbc3 100644 --- a/src/bundler/linker_context/postProcessCSSChunk.rs +++ b/src/bundler/linker_context/postProcessCSSChunk.rs @@ -14,10 +14,8 @@ pub fn post_process_css_chunk( worker: &mut thread_pool::Worker, chunk: &mut Chunk, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let c = ctx.c(); - // TODO(port): worker.arena is a per-worker arena — thread `&'bump Bump`. - // PORT NOTE: avoid FRU `..Default::default()` — StringJoiner impls Drop (E0509). + // Avoid FRU `..Default::default()` — StringJoiner impls Drop (E0509). let mut j = StringJoiner::default(); j.watcher = Watcher { input: chunk.unique_key, @@ -99,7 +97,7 @@ pub fn post_process_css_chunk( } // Save the offset to the start of the stored JavaScript - // PORT NOTE: Zig `j.push(.., bun.default_allocator)` — code() borrows from + // code() borrows from // compile_results which outlives the joiner; treat as static (no copy/free). j.push_static(compile_result.code()); @@ -110,7 +108,7 @@ pub fn post_process_css_chunk( // SAFETY: bitwise alias of `chunk.compile_results_for_chunk` // (read-only and outlives this fn); see `postProcessJSChunk.rs`. source_map_chunk: unsafe { source_map_chunk.alias() }, - // Zig reads `.value` payload directly — guaranteed `Value` here + // Guaranteed `Value` here // because `source_maps != None` implies `line_offset` was // initialised to `Value(_)` above. generated_offset: match line_offset { @@ -174,5 +172,3 @@ pub fn post_process_css_chunk( Ok(()) } - -// ported from: src/bundler/linker_context/postProcessCSSChunk.zig diff --git a/src/bundler/linker_context/postProcessHTMLChunk.rs b/src/bundler/linker_context/postProcessHTMLChunk.rs index 8841bcf5742..e2166e46663 100644 --- a/src/bundler/linker_context/postProcessHTMLChunk.rs +++ b/src/bundler/linker_context/postProcessHTMLChunk.rs @@ -9,7 +9,8 @@ pub fn post_process_html_chunk( worker: &mut thread_pool::Worker, chunk: &mut Chunk, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set — Zig `!void` but body has zero `try` sites (inferred-empty) + // The body has no fallible sites; the Result signature matches the other + // `post_process_*_chunk` callees dispatched from `generate_chunk`. // This is where we split output into pieces let c = ctx.c(); // E0509: StringJoiner has Drop, so FRU `..Default::default()` is illegal — assign field instead. @@ -22,8 +23,8 @@ pub fn post_process_html_chunk( let compile_results = &chunk.compile_results_for_chunk; for compile_result in compile_results.iter() { - // PORT NOTE: Zig `j.push(.., bun.default_allocator)` — code() borrows from - // chunk.compile_results_for_chunk which outlives `j.done()`; arena arg dropped. + // code() borrows from + // chunk.compile_results_for_chunk which outlives `j.done()`. j.push_static(compile_result.code()); } @@ -41,13 +42,11 @@ pub fn post_process_html_chunk( alloc, &mut j, ctx.chunks.len() as u32, // @truncate - )); // Zig: `catch |err| bun.handleOom(err)` + )); - // PORT NOTE: reshaped for borrowck (compute hash before assigning into chunk) + // Reshaped for borrowck (compute hash before assigning into chunk) let isolated_hash = c.generate_isolated_hash(chunk, alloc); chunk.isolated_hash = isolated_hash; Ok(()) } - -// ported from: src/bundler/linker_context/postProcessHTMLChunk.zig diff --git a/src/bundler/linker_context/postProcessJSChunk.rs b/src/bundler/linker_context/postProcessJSChunk.rs index d4d278f6e3f..3413df73a69 100644 --- a/src/bundler/linker_context/postProcessJSChunk.rs +++ b/src/bundler/linker_context/postProcessJSChunk.rs @@ -27,9 +27,8 @@ use bun_sourcemap as SourceMap; use crate::IndexInt; -/// Move the printed code out of a `PrintResult`. Mirrors Zig -/// `j.push(result.code, worker.allocator)` where the joiner takes ownership of -/// the slice — the Rust `PrintResultSuccess.code` is a `Box<[u8]>` that would +/// Move the printed code out of a `PrintResult` so the joiner takes ownership +/// of the slice — `PrintResultSuccess.code` is a `Box<[u8]>` that would /// otherwise drop at end of `post_process_js_chunk` and leave the deferred /// `IntermediateOutput::Joiner` path holding freed memory. fn print_result_take_code(r: &mut PrintResult) -> Box<[u8]> { @@ -46,7 +45,6 @@ pub fn post_process_js_chunk( chunk: &mut Chunk, chunk_index: usize, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let _trace = perf::trace("Bundler.postProcessJSChunk"); let _ = chunk_index; @@ -63,11 +61,12 @@ pub fn post_process_js_chunk( // embedded `Vec`/`Vec` buffers leak from the global heap. let _ast_alloc_heap = js_ast::StoreAstAllocHeap::new(); - // TODO(port): `defer chunk.renamer.deinit(bun.default_allocator)` — Zig explicitly - // tears down the renamer at end of scope. In Rust this should be handled by Drop on - // the renamer field, or an explicit `chunk.renamer.take()` at fn exit. Verify. + // The renamer is + // not used after this function, so it is reset to `None` before returning + // (success path below). `ChunkRenamer`'s variants free their owned storage + // via Drop; the `symbols` view inside is `ManuallyDrop` (non-owning), so + // graph storage is never freed. - // PERF(port): was arena bulk-free — profile if hot. let arena = Arena::new(); // Also generate the cross-chunk binding code @@ -103,8 +102,7 @@ pub fn post_process_js_chunk( let loader = c.parse_graph().input_files.items_loader()[chunk.entry_point.source_index() as usize]; let is_typescript = loader.is_type_script(); - // Zig: ModuleInfo.create(bun.default_allocator, ...) returns heap-allocated *ModuleInfo, - // later stored on chunk.content.javascript.module_info — OWNED → Box. + // Stored on chunk.content.javascript.module_info — owned `Box`. let mut module_info: Option> = if generate_module_info { Some(ModuleInfo::create(is_typescript)) } else { @@ -115,8 +113,7 @@ pub fn post_process_js_chunk( let worker_arena: &Arena = worker.arena(); { - // PORT NOTE: Zig builds one `print_options` and passes it by-value twice. - // Rust `Options` is not `Copy` (holds `&mut ModuleInfo`), and a closure + // `Options` is not `Copy` (holds `&mut ModuleInfo`), and a closure // taking `&mut ModuleInfo` can't express "output lifetime = input // lifetime" — so build a base with `module_info: None` and override it // via FRU at each call site. @@ -139,9 +136,7 @@ pub fn post_process_js_chunk( let mut cross_chunk_import_records: Vec = Vec::with_capacity(chunk.cross_chunk_imports.len() as usize); - // PERF(port): was initCapacity catch unreachable for import_record in chunk.cross_chunk_imports.slice() { - // PERF(port): was appendAssumeCapacity cross_chunk_import_records.push(ImportRecord { kind: import_record.import_kind, // `ctx.chunks` is a `BackRef<[Chunk]>` (safe `Deref`); chunk_index is @@ -150,7 +145,7 @@ pub fn post_process_js_chunk( ctx.chunks[import_record.chunk_index as usize].unique_key, ), range: bun_ast::Range::NONE, - // Remaining fields take their Zig defaults (no Default impl): + // Remaining fields (`ImportRecord` has no `Default` impl): tag: ImportRecordTag::None, loader: None, source_index: Index::INVALID, @@ -160,7 +155,7 @@ pub fn post_process_js_chunk( }); } - // PORT NOTE: `MultiArrayList::get` returns `ManuallyDrop` — + // `MultiArrayList::get` returns `ManuallyDrop` — // the storage retains ownership of every Drop field (`named_imports`, // `parts`, `top_level_symbols_to_parts`, …), so the gathered struct // must NOT run Drop. `to_ast` consumes by value, so unwrap, convert, @@ -324,7 +319,7 @@ pub fn post_process_js_chunk( continue; } // Skip barrel-optimized-away imports — marked is_unused by - // barrel_imports.zig. Never resolved (source_index invalid), + // `barrel_imports`. Never resolved (source_index invalid), // and removed by convertStmtsForChunk. Not in emitted code. if record.flags.contains(ImportRecordFlags::IS_UNUSED) { continue; @@ -523,7 +518,6 @@ pub fn post_process_js_chunk( let is_bun = c.graph.ast.items_target()[chunk.entry_point.source_index() as usize].is_bun(); if is_bun { if c.options.generate_bytecode_cache && output_format == options::OutputFormat::Cjs { - // Zig `++` literal concat → single byte literal (concat! yields &str, not &[u8]) const INPUT: &[u8] = b"// @bun @bytecode @bun-cjs\n(function(exports, require, module, __filename, __dirname) {"; j.push_static(INPUT); @@ -602,7 +596,6 @@ pub fn post_process_js_chunk( } { - // PORT NOTE: Zig `j.push(code, worker.allocator)` transferred ownership; // `cross_chunk_prefix` is a local that drops at fn exit, but the joiner // may be stashed on `chunk.intermediate_output` and consumed later // (`IntermediateOutput::Joiner` path). Move the Box into the joiner. @@ -753,10 +746,9 @@ pub fn post_process_js_chunk( } { - // PORT NOTE: `entry_point_tail` is a local `CompileResult` whose `code` - // is a `Box<[u8]>`; Zig `j.push(tail_code, worker.allocator)` handed - // ownership to the joiner. Move it so the deferred-joiner path doesn't - // read freed memory after this fn returns. + // `entry_point_tail` is a local `CompileResult` whose `code` + // is a `Box<[u8]>`. Move it into the joiner so the deferred-joiner + // path doesn't read freed memory after this fn returns. let tail_code = entry_point_tail.into_code(); if !tail_code.is_empty() { // Stick the entry point tail at the end of the file. Deliberately don't @@ -768,7 +760,7 @@ pub fn post_process_js_chunk( // Put the cross-chunk suffix inside the IIFE { - // PORT NOTE: see cross_chunk_prefix above — move ownership into joiner. + // See cross_chunk_prefix above — move ownership into joiner. let code = print_result_take_code(&mut cross_chunk_suffix); if !code.is_empty() { if newline_before_comment { @@ -801,9 +793,7 @@ pub fn post_process_js_chunk( [chunk.entry_point.source_index() as usize] .path; let mut buf = MutableString::init_empty(); - // PERF(port): worker.arena is an arena in Zig let _ = js_printer::quote_for_json(input.pretty, &mut buf, true); // fmt::Result into Vec is infallible - // bun.handleOom dropped — Rust aborts on OOM let quoted = buf.take_slice(); line_offset.advance("ed); j.push_owned(quoted.into_boxed_slice()); @@ -880,6 +870,10 @@ pub fn post_process_js_chunk( )?; } + // Free the renamer now that printing is done; nothing reads it after + // post-processing. + chunk.renamer = Default::default(); + Ok(()) } @@ -914,7 +908,7 @@ fn add_binding_vars_to_module_info( } } -// PORT NOTE: `js_printer::print` ties bump/Options/import_records/renamer to a +// `js_printer::print` ties bump/Options/import_records/renamer to a // single `'a`, and `Renamer<'r, 'src>` is invariant in `'src` — so the caller's // renamer lifetime fixes `'a`. All by-ref params that flow into `print` must // share that lifetime. @@ -923,17 +917,14 @@ pub fn generate_entry_point_tail_js<'a>( to_common_js_ref: Ref, to_esm_ref: Ref, source_index: IndexInt, - // bundler is an AST crate: std.mem.Allocator param → &'bump Bump (Arena) - // TODO(port): thread &'bump Bump from worker.arena end-to-end. arena: &'a Arena, temp_arena: &Arena, mut r: js_printer::renamer::Renamer<'a, 'a>, mut module_info: Option<&'a mut ModuleInfo>, ) -> CompileResult { let flags: crate::js_meta::Flags = c.graph.meta.items_flags()[source_index as usize]; - // PERF(port): was arena-backed ArrayList(Stmt) — profile if hot. let mut stmts: Vec = Vec::new(); - // PORT NOTE: `MultiArrayList::get` returns `ManuallyDrop`; the + // `MultiArrayList::get` returns `ManuallyDrop`; the // storage retains ownership of every Drop field, so neither this // `BundledAst` nor the `ast_view: Ast` derived from it below may run Drop. let ast = c.graph.ast.get(source_index as usize); @@ -1024,7 +1015,6 @@ pub fn generate_entry_point_tail_js<'a>( // entry point is a CommonJS-style module, since that would generate an ES6 // export statement that's not top-level. Instead, we will export the CommonJS // exports as a default export later on. - // PERF(port): was arena-backed ArrayList(ClauseItem) — profile if hot. let mut items: Vec = Vec::new(); let cjs_export_copies = &c.graph.meta.items_cjs_export_copies()[source_index as usize]; @@ -1032,9 +1022,9 @@ pub fn generate_entry_point_tail_js<'a>( let mut had_default_export = false; for (i, alias) in sorted_and_filtered_export_aliases.iter().enumerate() { - // PORT NOTE: Zig `resolved_exports.get(alias).?` returns a by-value - // copy of `ExportData`; only `.data` (an `ImportTracker`, `Copy`) is - // read/mutated below, so copy that field instead of the whole struct. + // Only `.data` (an `ImportTracker`, `Copy`) is + // read/mutated below, so copy that field instead of + // the whole `ExportData`. let mut resolved_export_data = resolved_exports.get(alias).unwrap().data; @@ -1165,9 +1155,8 @@ pub fn generate_entry_point_tail_js<'a>( } } - // PORT NOTE: arena-owned `*mut [ClauseItem]` — move the - // collected Vec into the linker arena (Zig used - // `c.arena().alloc`). The arena slice is also iterated + // arena-owned `*mut [ClauseItem]` — move the + // collected Vec into the linker arena. The arena slice is also iterated // below for the synthetic-default-export path. let items: &mut [bun_ast::ClauseItem] = arena.alloc_slice_fill_iter(items); stmts.push(Stmt::alloc( @@ -1180,10 +1169,8 @@ pub fn generate_entry_point_tail_js<'a>( if flags.needs_synthetic_default_export && !had_default_export { let mut properties = G::PropertyList::init_capacity(items.len()); - // PERF(port): was initCapacity catch unreachable let getter_fn_body: &mut [Stmt] = arena.alloc_slice_fill_default(items.len()); - // TODO(port): arena.alloc(Stmt, n) — needs arena slice alloc API let mut remain_getter_fn_body = &mut getter_fn_body[..]; for export_item in items.iter() { let (fn_body, rest) = remain_getter_fn_body.split_at_mut(1); @@ -1203,7 +1190,6 @@ pub fn generate_entry_point_tail_js<'a>( }, bun_ast::Loc::EMPTY, ); - // PERF(port): was appendAssumeCapacity VecExt::append( &mut properties, G::Property { @@ -1325,7 +1311,7 @@ pub fn generate_entry_point_tail_js<'a>( // Add generated local declarations from entry point tail to module_info. // This captures vars like `var export_foo = cjs.foo` for CJS export copies. - // PORT NOTE: reshaped for borrowck — reborrow via as_deref_mut so module_info + // Reshaped for borrowck — reborrow via as_deref_mut so module_info // remains usable for print_options below. if let Some(mi) = module_info.as_mut() { let mi: &mut ModuleInfo = &mut **mi; @@ -1408,5 +1394,3 @@ pub fn generate_entry_point_tail_js<'a>( decls: Box::default(), } } - -// ported from: src/bundler/linker_context/postProcessJSChunk.zig diff --git a/src/bundler/linker_context/prepareCssAstsForChunk.rs b/src/bundler/linker_context/prepareCssAstsForChunk.rs index c473a298841..cbb77689781 100644 --- a/src/bundler/linker_context/prepareCssAstsForChunk.rs +++ b/src/bundler/linker_context/prepareCssAstsForChunk.rs @@ -20,8 +20,7 @@ use bun_resolver::DataURL; use crate::chunk::{Content, CssImportOrderKind}; -// PORT NOTE: Zig stores `*Chunk` / `*LinkerContext` (freely-aliasing mutable -// pointers). We mirror that with raw pointers rather than `&mut` / `&` so that +// Raw pointers rather than `&mut` / `&` so that // (a) the container_of `container_of` recovery of `*mut BundleV2` from // `linker` retains write provenance over the whole bundle, and (b) multiple // tasks may hold pointers to the same `LinkerContext` concurrently without @@ -56,8 +55,7 @@ unsafe impl Send for PrepareCssAstTask {} pub unsafe fn prepare_css_asts_for_chunk(task: *mut ThreadPoolLib::Task) { // SAFETY: `task` points to `PrepareCssAstTask.task` (intrusive thread-pool // node); the thread pool hands us exclusive access for the callback's - // duration. We only read the two raw-pointer fields, matching Zig's - // `*const PrepareCssAstTask`. + // duration. We only read the two raw-pointer fields. let prepare_css_asts: &PrepareCssAstTask = unsafe { &*bun_core::from_field_ptr!(PrepareCssAstTask, task, task) }; let linker: *mut LinkerContext = prepare_css_asts.linker; @@ -91,9 +89,7 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & // Remove duplicate rules across files. This must be done in serial, not // in parallel, and must be done from the last rule to the first rule. { - // PORT NOTE: Zig accesses `chunk.content.css.{imports_in_chunk_in_order,asts}` - // through the union field at each use site while also holding `entry` as a raw - // pointer into `imports_in_chunk_in_order`. In Rust, every `chunk.content.css.*` + // Every `chunk.content.css.*` access // re-enters the `Content` enum and re-borrows `chunk.content` as a whole, which // would alias the live `&mut entry`. Destructure the variant once so borrowck // can split the disjoint `CssChunk` struct fields (`imports_in_chunk_in_order` @@ -105,16 +101,14 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & while i != 0 { i -= 1; let entry = css_chunk.imports_in_chunk_in_order.mut_(i); - // PORT NOTE: reshaped for borrowck — match on entry.kind while also touching + // Reshaped for borrowck — match on entry.kind while also touching // entry.conditions / entry.condition_import_records relies on disjoint field borrows. match &mut entry.kind { CssImportOrderKind::Layers(layers) => { let inner = layers.inner(); let len = inner.len(); let rules = if len > 0 { - // PORT NOTE: Zig `SmallList(LayerName,1).fromBabyListNoDeinit(layers.inner().*)` - // is a bitwise Vec→SmallList header transfer. In Rust the - // `Chunk::Layers` payload is the lifetime-erased shadow + // The `Chunk::Layers` payload is the lifetime-erased shadow // `bun_css::LayerName { v: Vec> }`, // not the real `css_parser::LayerName { v: SmallList<&'static [u8],1> }`, // so the layouts differ. Rebuild the real list element-by-element; @@ -133,7 +127,7 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & shadow.v.slice().iter().map(|seg| { // `seg` borrows arena-owned bytes that outlive this // stylesheet; route through `StoreStr` for the lifetime - // erasure (see layer.rs TODO(port)). + // erasure (see the corresponding note in layer.rs). bun_ast::StoreStr::new(seg).slice() }), ), @@ -162,13 +156,11 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & css_chunk.asts[i] = ast; } CssImportOrderKind::ExternalPath(p) => { - // PORT NOTE: Zig keeps `conditions: ?*ImportConditions` as a raw - // pointer to index 0 while the `while j != 1` loop reads - // `entry.conditions.len` / `.at(j)`. Taking `&mut` at index 0 here - // would exclusively borrow the whole `entry.conditions` Vec for - // the duration, aliasing those reads. The pointer is not actually - // dereferenced until after the loop (.zig:119), so defer acquiring - // the index-0 borrow until `actual_conditions` is built below. + // Taking `&mut` at index 0 here would exclusively borrow + // the whole `entry.conditions` Vec while the `while j != 1` + // loop below still reads `entry.conditions.len` / `.at(j)`. + // The borrow is not actually needed until after the loop, so + // defer acquiring it until `actual_conditions` is built below. let had_conditions = entry.conditions.len() > 0; if had_conditions { entry.condition_import_records.push(ImportRecord { @@ -195,15 +187,14 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & while j != 1 { j -= 1; - // PORT NOTE: Zig has no destructors, so when `ast_import` falls - // out of scope the bitwise-duplicated `ImportConditions` inside - // it (see `ptr::read` below) is simply abandoned. In Rust, - // dropping `ast_import` would run `Drop` on that aliased + // `ast_import` holds a bitwise-duplicated `ImportConditions` + // (see `ptr::read` below); + // dropping it would run `Drop` on that aliased // `ImportConditions` — freeing Global-backed buffers // (`MediaList.media_queries: Vec`, `SupportsCondition::{Box,Vec}`, // `LayerName.v: SmallList`) that are still owned by // `entry.conditions[j]`, i.e. a double-free / UAF. Wrap in - // `ManuallyDrop` to mirror Zig's leak-on-scope-exit; the rule + // `ManuallyDrop` so the duplicate is abandoned instead; the rule // slab itself is arena-owned so it is reclaimed on arena reset. let ast_import = core::mem::ManuallyDrop::new(BundlerStyleSheet { options: ParserOptions::default(None), @@ -218,7 +209,7 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & loc: Location::dummy(), ..Default::default() }; - // SAFETY: Zig `entry.conditions.at(j).*` — shallow struct + // SAFETY: shallow struct // copy. The duplicate is never dropped (`ManuallyDrop` // above), so the aliased heap stays singly-owned by // `entry.conditions[j]`. @@ -283,9 +274,8 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & b"text/css", strings::trim(print_result.code.as_slice(), b" \n\r\t"), ); - // PORT NOTE: Zig allocated into the worker arena (`arena`). // `encode_string_as_shortest_data_url` returns a heap `Vec`; - // copy it into the worker bump so ownership matches Zig (freed + // copy it into the worker bump (freed // at bundle teardown via arena reset). SAFETY: arena outlives // the chunk, so the `'bump → 'static` launder is sound — same // contract as every other CSS slice in this file. @@ -296,9 +286,8 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & } let mut empty_conditions = ImportConditions::default(); - // Index 0 is disjoint from every `at(j)` (j>=1) read above; only - // now do we materialize the exclusive borrow that Zig's raw pointer - // held the whole time. + // Index 0 is disjoint from every `at(j)` (j>=1) read above; + // only now do we materialize the exclusive borrow. let actual_conditions: &mut ImportConditions = if had_conditions { entry.conditions.mut_(0) } else { @@ -323,7 +312,12 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & p.pretty, entry.condition_import_records.len() as u32, ); - // SAFETY: Zig `actual_conditions.*` — shallow struct copy. + // SAFETY: shallow struct copy. The duplicate lives in an + // `ImportRule` inside the `arena_rule_list_one` slab assigned + // to `css_chunk.asts[i]`, whose elements never run `Drop` + // (`CssChunk::Drop` frees the slab via `set_len(0)`), so + // `entry.conditions[0]` / `empty_conditions` remain the sole + // owners of the interior heap. *import_rule.conditions_mut() = unsafe { core::ptr::read(actual_conditions) }; arena_rule_list_one(bump, BundlerCssRule::Import(import_rule)) @@ -348,7 +342,7 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & [source_index.get() as usize] .as_deref() .expect("css ast present"); - // SAFETY: Zig `original_stylesheet.*` — bitwise shallow copy of the + // SAFETY: bitwise shallow copy of the // stylesheet header. All interior allocations are arena-owned and never // freed via this view, so the duplicated `Vec`/`Vec` headers are // sound for read-only / reslice use below. @@ -405,20 +399,27 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & ); for rule in &original_rules[0..prefix_end] { if matches!(rule, BundlerCssRule::LayerStatement(_)) { - // SAFETY: Zig by-value copy of arena-backed rule. + // SAFETY: bitwise duplicate of a rule. The copy goes into + // an `arena_rule_list` slab installed in `css_chunk.asts[i]`, + // whose elements never run `Drop` (see `arena_rule_list` / + // `CssChunk::Drop`), so the rule's interior heap stays + // singly-owned by the original. new_rules.push(unsafe { core::ptr::read(rule) }); } } for rule in &original_rules[prefix_end..] { - // SAFETY: Zig by-value copy of arena-backed rule. + // SAFETY: bitwise duplicate of a rule. The copy goes into + // an `arena_rule_list` slab installed in `css_chunk.asts[i]`, + // whose elements never run `Drop` (see `arena_rule_list` / + // `CssChunk::Drop`), so the rule's interior heap stays + // singly-owned by the original. new_rules.push(unsafe { core::ptr::read(rule) }); } // `ast.rules` is the shallow-copied header aliasing the // source stylesheet's arena buffer (see `ptr::read` above). // Dropping it would `drop_in_place` the aliased rules and - // free the shared backing array. Leak the header (Zig - // semantics: bitwise overwrite) before installing the - // freshly-allocated list. + // free the shared backing array. Leak the header + // before installing the freshly-allocated list. let _ = core::mem::ManuallyDrop::new(core::mem::replace( &mut ast.rules, arena_rule_list(new_rules), @@ -478,7 +479,7 @@ fn wrap_rules_with_conditions( // Generate "@layer" wrappers. Note that empty "@layer" rules still have // a side effect (they set the layer order) so they cannot be removed. if let Some(l) = &item.layer { - // SAFETY: Zig `const layer = l.v;` — by-value `?LayerName` copy. The + // SAFETY: by-value `?LayerName` copy. The // `SmallList<&'static [u8],1>` payload is arena-backed and never // freed via this view, so the bitwise duplicate is sound (same as // every other `ptr::read` shallow-copy in this file). @@ -493,8 +494,7 @@ fn wrap_rules_with_conditions( // `ast.rules.v` may be the shallow-copied / offset-resliced // header aliasing the source stylesheet's buffer (see the // `ptr::read` / `Vec::from_raw_parts` above) — dropping it - // would free into another allocation. Zig's `= .{}` is a - // bitwise overwrite; mirror that by leaking the header. + // would free into another allocation; leak the header instead. let _ = core::mem::ManuallyDrop::new(core::mem::take(&mut ast.rules.v)); do_block_rule = false; } @@ -559,5 +559,3 @@ fn wrap_rules_with_conditions( pub use crate::DeferredBatchTask; pub use crate::ParseTask; pub use crate::ThreadPool; - -// ported from: src/bundler/linker_context/prepareCssAstsForChunk.zig diff --git a/src/bundler/linker_context/renameSymbolsInChunk.rs b/src/bundler/linker_context/renameSymbolsInChunk.rs index 1d14bde68bc..450e26c382d 100644 --- a/src/bundler/linker_context/renameSymbolsInChunk.rs +++ b/src/bundler/linker_context/renameSymbolsInChunk.rs @@ -15,11 +15,6 @@ use crate::{Chunk, LinkerContext, StableRef, WrapKind}; /// TODO: investigate if we need to parallelize this function /// esbuild does parallelize it. -// TODO(port): narrow error set -// TODO(port): bundler is an AST crate (PORTING.md §Allocators) — verify whether caller passes -// an arena vs default_allocator for the dropped `arena: std.mem.Allocator` param; if arena, -// thread `bump: &'bump Bump` and switch working Vecs to bun_alloc::ArenaVec<'bump, T>. -// // CONCURRENCY: called from `LinkerContext::generate_js_renamer` (`each_ptr` // callback) — runs on worker threads, one task per chunk. Writes go to // `chunk.renamer` (per-chunk disjoint) plus per-`source_index` rows of @@ -27,9 +22,9 @@ use crate::{Chunk, LinkerContext, StableRef, WrapKind}; // symbol scope assignment). `files_in_order` is the chunk's own file list; // without code-splitting, files are partitioned across chunks so per-row // writes are disjoint. With code-splitting a `source_index` may appear in -// multiple chunks — the Zig original has the same overlap; the writes are +// multiple chunks; the writes are // idempotent (`declared_symbols` flag set, scope-member sort) so the race is -// benign there but is still a Stacked Borrows hazard here. Mitigation: never +// benign but is still a Stacked Borrows hazard. Mitigation: never // materialize `&mut LinkerContext` (would assert whole-context exclusivity // across N tasks); take `*mut LinkerContext` raw, deref to `&LinkerContext` // for reads, and access SoA columns via `split_raw()` root-provenance @@ -105,8 +100,7 @@ pub unsafe fn rename_symbols_in_chunk( ) }; - // PORT NOTE: `symbol::Map` is not `Clone`/`Copy`; Zig passed the struct - // (slice header) by value. Build a non-owning shallow view via + // `symbol::Map` is not `Clone`/`Copy`. Build a non-owning shallow view via // `from_bump_slice` so the renamer's `Map` does not free graph storage on // drop. // SAFETY: `c.graph.symbols` outlives the returned `ChunkRenamer` (both are @@ -152,7 +146,6 @@ pub unsafe fn rename_symbols_in_chunk( count += item.len() as u32; } - // PERF(port): Zig pre-set len and filled via slice writes; using push() here let mut list: Vec = Vec::with_capacity(count as usize); let stable_source_indices = c.graph.stable_source_indices.slice(); for item in imports_from_other_chunks { @@ -260,7 +253,7 @@ pub unsafe fn rename_symbols_in_chunk( top_level_symbols.clear(); for stable_ref in &sorted_imports_from_other_chunks { - // PORT NOTE: `StableRef` is `repr(packed)`; copy the field to avoid an unaligned ref. + // `StableRef` is `repr(packed)`; copy the field to avoid an unaligned ref. let ref_ = { stable_ref.r#ref }; minify_renamer.accumulate_symbol_use_count( &mut top_level_symbols, @@ -281,16 +274,15 @@ pub unsafe fn rename_symbols_in_chunk( let mut r = NumberRenamer::init(make_symbols_view(symbols), &reserved_names)?; for stable_ref in &sorted_imports_from_other_chunks { - // PORT NOTE: `StableRef` is `repr(packed)`; copy the field to avoid an unaligned ref. + // `StableRef` is `repr(packed)`; copy the field to avoid an unaligned ref. r.add_top_level_symbol(stable_ref.r#ref); } - // PORT NOTE: Zig used `r.temp_arena` for this list; arena param dropped let mut sorted: Vec = Vec::new(); for &source_index in files_in_order { let wrap = all_flags[source_index as usize].wrap; - // PORT NOTE: need `&mut [Part]` for `add_top_level_declared_symbols`. + // Need `&mut [Part]` for `add_top_level_declared_symbols`. let parts: &mut [Part] = all_parts[source_index as usize].as_mut_slice(); match wrap { @@ -366,7 +358,7 @@ pub unsafe fn rename_symbols_in_chunk( } } } - // PORT NOTE: reshaped for borrowck — `&mut r.root` while `r` is the + // Reshaped for borrowck — `&mut r.root` while `r` is the // `&mut self` receiver. Take a raw pointer; `assign_names_*` does // not touch `self.root` through `self`. let root: *mut renamer::NumberScope = core::ptr::addr_of_mut!(r.root); @@ -418,7 +410,6 @@ pub unsafe fn rename_symbols_in_chunk( &mut sorted, ); } - // Zig: `@TypeOf(r.number_scope_pool.hive.used).initEmpty()`. r.number_scope_pool.hive.used = bun_collections::hive_array::HiveBitSet::init_empty(); } } @@ -429,5 +420,3 @@ pub unsafe fn rename_symbols_in_chunk( pub use crate::DeferredBatchTask; pub use crate::ParseTask; pub use crate::ThreadPool; - -// ported from: src/bundler/linker_context/renameSymbolsInChunk.zig diff --git a/src/bundler/linker_context/scanImportsAndExports.rs b/src/bundler/linker_context/scanImportsAndExports.rs index ab95027391f..c8fe0b91033 100644 --- a/src/bundler/linker_context/scanImportsAndExports.rs +++ b/src/bundler/linker_context/scanImportsAndExports.rs @@ -1,12 +1,10 @@ -//! Port of `src/bundler/linker_context/scanImportsAndExports.zig`. -// -// PORT NOTE: the Zig body takes ~20 simultaneous mutable column slices -// (`this.graph.ast.items(.field)`) and freely interleaves them with +// This pass works over ~20 simultaneous mutable column slices +// (`this.graph.ast.items(.field)`) freely interleaved with // `&mut LinkerContext` method calls. Rust's borrowck forbids both holding // overlapping `&mut [T]` columns from the same `MultiArrayList` and holding // any `&mut` column across a `&mut self` call into `this.graph`. The columns // are physically disjoint (SoA layout) and the underlying `MultiArrayList` -// never reallocates inside this function, so this port caches the column +// never reallocates inside this function, so we cache the column // base pointers once via `Slice::items_raw` and dereferences them at each // use site through `*mut [T]`. This is the documented escape hatch in // `bun_collections::multi_array_list::Slice::items_raw`. @@ -84,9 +82,8 @@ pub fn scan_imports_and_exports( let _outer_trace = perf::trace("Bundler.scanImportsAndExports"); let output_format = this.options.output_format; - // PORT NOTE: `reachable_files` is borrowed out of `this.graph` while the + // `reachable_files` is borrowed out of `this.graph` while the // body also calls `&mut this.graph` methods. Snapshot the indices. - // PERF(port): was zero-copy slice; profile. let mut reachable: Vec = this.graph.reachable_files.slice().to_vec(); // ── cache SoA column base pointers ──────────────────────────────────── @@ -115,8 +112,8 @@ pub fn scan_imports_and_exports( let module_refs: *mut [Ref] = ast.module_ref; let wrapper_refs: *mut [Ref] = ast.wrapper_ref; let parts_list: *mut [PartList] = ast.parts; - // Zig: `[]?*bun.css.BundlerStyleSheet` — element is a *mutable* nullable - // pointer (matches `BundledAst.css: Option<*mut BundlerStyleSheet>`). + // Element is a *mutable* nullable + // pointer (`BundledAst.css: Option<*mut BundlerStyleSheet>`). let css_asts: *mut [CssCol] = ast.css; let input_files: *mut [Source] = input.source; @@ -131,9 +128,6 @@ pub fn scan_imports_and_exports( let cjs_export_copies: *mut [js_meta::CjsExportCopies] = meta.cjs_export_copies; let entry_point_part_indices: *mut [Index] = meta.entry_point_part_index; - // PORT NOTE: Zig copies `symbols` to a local and `defer`-writes it back. - // In Rust `this.graph.symbols` is the same storage, so no copy-back needed. - { // Step 1: Figure out what modules must be CommonJS for source_index_ in &reachable { @@ -317,8 +311,6 @@ pub fn scan_imports_and_exports( output_format, } }; - // PORT NOTE: `defer dependency_wrapper.export_star_map.deinit()` → Drop handles it. - for source_index_ in &reachable { let source_index = source_index_.get(); let id = source_index as usize; @@ -363,9 +355,6 @@ pub fn scan_imports_and_exports( { let mut export_star_ctx: Option = None; let _trace = perf::trace("Bundler.ResolveExportStarStatements"); - // PORT NOTE: `defer { if (export_star_ctx) |*export_ctx| export_ctx.source_index_stack.deinit(); }` - // → Drop on `export_star_ctx` handles freeing `source_index_stack: Vec`. - for source_index_ in &reachable { let source_index = source_index_.get(); let id = source_index as usize; @@ -491,11 +480,9 @@ pub fn scan_imports_and_exports( // imported using an import star statement. // Note: `do` will wait for all to finish before moving forward // - // PORT NOTE: Zig dispatched via `worker_pool.each(arena, this, - // doStep5, reachable_files)` (parallel fan-out, blocks until done). // `do_step5` only touches distinct SoA rows per `source_index` (the - // columns are pre-sized and never reallocate during this step), - // matching the Zig invariant. We pass `*mut LinkerContext` through a + // columns are pre-sized and never reallocate during this step). + // We pass `*mut LinkerContext` through a // `Sync` wrapper; the callee derefs it to `&LinkerContext` (shared) // for reads and writes per-row cells via raw `split_raw()` pointers — // mirroring `GenerateChunkCtx` (`generate_js_renamer` likewise never @@ -528,11 +515,10 @@ pub fn scan_imports_and_exports( ); } - // Zig calls `takeAstOwnership` here because `doStep5` appends to - // `part.dependencies`/`declared_symbols` with the worker allocator. - // In the Rust port those are global-allocator `Vec`s (thread-safe to - // grow) and `do_step_5` never pushes to the arena-backed `PartList`/ - // import-record columns, so no transfer is needed. + // No post-fan-out ownership transfer is needed: `do_step5` only grows + // global-allocator `Vec`s (`part.dependencies`, `declared_symbols`), + // which are thread-safe to grow per-row, and never pushes to the + // arena-backed `PartList`/import-record columns. } if FeatureFlags::HELP_CATCH_MEMORY_ISSUES { @@ -607,19 +593,18 @@ pub fn scan_imports_and_exports( }; // Allocate the identifier-name buffer from the linker arena so it is - // reclaimed when the link pass ends (Zig: `this.arena().alloc(u8, ...)`). + // reclaimed when the link pass ends. // The slices handed out below are stored in `Symbol.original_name: *const [u8]`, // which is arena-lifetime by construction. let string_buffer: &mut [u8] = this .graph .arena() .alloc_slice_fill_default::(string_buffer_len); - // PORT NOTE: `StringBuilder::drop` reconstructs a `Box<[u8]>` from + // `StringBuilder::drop` reconstructs a `Box<[u8]>` from // `ptr`/`cap` and frees it via the global arena. Here the // backing buffer is arena-owned (bumpalo), so dropping would hand // mimalloc a pointer it never allocated. Wrap in `ManuallyDrop` — - // the arena reclaims the storage on reset, matching Zig's implicit - // no-destructor semantics. + // the arena reclaims the storage on reset. let mut builder = core::mem::ManuallyDrop::new(bun_core::StringBuilder { len: 0, cap: string_buffer.len(), @@ -701,8 +686,8 @@ pub fn scan_imports_and_exports( } } - // PORT NOTE: Zig `defer bun.assert(builder.len == builder.cap)` — - // moved to end-of-scope assert (no early returns inside this block). + // End-of-scope assert; relies on there being no + // early returns inside this block. debug_assert!(builder.len == builder.cap); // Include the "__export" symbol from the runtime if it was used in the @@ -727,7 +712,7 @@ pub fn scan_imports_and_exports( { let imports_to_bind = &col_ref!(imports_to_bind_list)[id]; debug_assert_eq!(imports_to_bind.keys().len(), imports_to_bind.values().len()); - // PORT NOTE: reshaped for borrowck — iterate by index so we can + // Iterate by index so we can // re-borrow `parts` after each `top_level_symbol_to_parts` call. for itb_i in 0..imports_to_bind.keys().len() { let r#ref: Ref = col_ref!(imports_to_bind_list)[id].keys()[itb_i]; @@ -820,11 +805,10 @@ pub fn scan_imports_and_exports( this.top_level_symbols_to_parts(target_source_index.get(), target_ref); dependencies.reserve(top_to_parts.len()); for part_index in top_to_parts { - // PERF(port): was appendAssumeCapacity dependencies.push(Dependency { - // PORT NOTE: `crate::Index` ↔ `bun_ast::Index` are both - // `#[repr(transparent)] u32` newtypes ported from the - // same Zig `ast.Index`; bridge by `.value` until B-3 + // `crate::Index` ↔ `bun_ast::Index` are both + // `#[repr(transparent)] u32` newtypes; + // bridge by `.value` until B-3 // collapses them to a single re-export. source_index: bun_ast::Index(target_source_index.get()), part_index: *part_index, @@ -836,7 +820,6 @@ pub fn scan_imports_and_exports( // Ensure "exports" is included if the current output format needs it if force_include_exports { - // PERF(port): was appendAssumeCapacity dependencies.push(Dependency { source_index: bun_ast::Index::source(source_index as usize), part_index: bun_ast::NAMESPACE_EXPORT_PART_INDEX, @@ -845,7 +828,6 @@ pub fn scan_imports_and_exports( // Include the wrapper if present if add_wrapper { - // PERF(port): was appendAssumeCapacity dependencies.push(Dependency { source_index: bun_ast::Index::source(source_index as usize), part_index: col_ref!(wrapper_part_indices)[id].get(), @@ -861,8 +843,6 @@ pub fn scan_imports_and_exports( ..Default::default() }, )?; - // PORT NOTE: `catch |err| bun.handleOom(err)` dropped — `?` propagates OOM. - col!(entry_point_part_indices)[id] = Index::part(entry_point_part_index); // Pull in the "__toCommonJS" symbol if we need it due to being an entry point @@ -892,7 +872,7 @@ pub fn scan_imports_and_exports( let mut runtime_require_uses: u32 = 0; // Imports of wrapped files must depend on the wrapper - // PORT NOTE: iterate by index so each iteration re-borrows + // Iterate by index so each iteration re-borrows // `import_records` (the body calls `&mut this.graph` methods). let import_record_indices_len = col_ref!(parts_list)[id].as_slice()[part_index] .import_record_indices @@ -909,7 +889,7 @@ pub fn scan_imports_and_exports( let other_id = rec_source_index.value() as usize; // Don't follow external imports (this includes import() expressions) - // PORT NOTE: short-circuit — `is_external_dynamic_import` indexes by + // Short-circuit: `is_external_dynamic_import` indexes by // `record.source_index`, so it must only run when that index is valid. let is_external_dyn = rec_source_index.is_valid() && { let record = &col_ref!(import_records_list)[id].as_slice() @@ -1182,7 +1162,7 @@ fn should_call_runtime_require(format: options::Format) -> bool { } // ────────────────────────────────────────────────────────────────────────── -// DependencyWrapper — port of the inner Zig struct. +// DependencyWrapper // ────────────────────────────────────────────────────────────────────────── struct DependencyWrapper<'a> { flags: &'a mut [js_meta::Flags], @@ -1268,7 +1248,7 @@ impl DependencyWrapper<'_> { } // ────────────────────────────────────────────────────────────────────────── -// ExportStarContext — port of the inner Zig struct. Holds raw column ptrs. +// ExportStarContext — holds raw column ptrs. // ────────────────────────────────────────────────────────────────────────── struct ExportStarContext<'a> { import_records_list: *mut [ImportRecordList<'a>], @@ -1320,9 +1300,8 @@ impl<'a> ExportStarContext<'a> { continue; } - // PORT NOTE: reshaped for borrowck — collect (alias, name) pairs so the + // Collect (alias, name) pairs so the // loop body can mutably borrow `resolved_exports` / `imports_to_bind`. - // PERF(port): was zero-copy `iter()` over StringArrayHashMap; profile. let exports_len = col_ref!(self.named_exports)[other_id].keys().len(); 'next_export: for ne_i in 0..exports_len { // `BackRef<[u8]>` — points into the `named_exports` key @@ -1375,7 +1354,6 @@ impl<'a> ExportStarContext<'a> { }, ) .expect("oom"); - // PORT NOTE: `catch |err| bun.handleOom(err)` dropped — aborts on OOM. } else if gop.value_ptr.data.source_index.get() != other_source_index { // Two different re-exports colliding makes it potentially ambiguous gop.value_ptr @@ -1388,7 +1366,6 @@ impl<'a> ExportStarContext<'a> { }, ..Default::default() }); - // PORT NOTE: `catch |err| bun.handleOom(err)` dropped — aborts on OOM. } } @@ -1396,8 +1373,7 @@ impl<'a> ExportStarContext<'a> { self.add_exports(resolved_exports, target_id, other_source_index); } - // PORT NOTE: Zig `defer this.source_index_stack.shrinkRetainingCapacity(stack_end_pos - 1)` - // — inlined at scope end (no early returns after the push). + // Scope-end truncation (no early returns after the push). self.source_index_stack.truncate(stack_end_pos - 1); } } @@ -1413,8 +1389,8 @@ mod __css_validation { use bun_ast::Log; use bun_collections::{ArrayHashMap, StringArrayHashMap}; - // Zig: `?*bun.css.BundlerStyleSheet` — keep the column element as a raw - // `*mut` (matches `BundledAst.css`), so we never launder a `&T` into `&mut T`. + // Keep the column element as a raw + // `*mut` (`BundledAst.css`), so we never launder a `&T` into `&mut T`. use crate::bundled_ast::CssCol; /// `ArrayHashAdapter` so `LocalScope` (`ArrayHashMap, LocalEntry>`) @@ -1539,8 +1515,6 @@ mod __css_validation { log: &'a mut Log, } - // PORT NOTE: `pub fn deinit` → Drop on `visited` / `properties` handles cleanup. - impl<'a, 'bump> Visitor<'a, 'bump> { fn add_property_or_warn( &mut self, @@ -1619,8 +1593,6 @@ mod __css_validation { ), ..Default::default() }); - // PORT NOTE: nested `catch |err| bun.handleOom(err)` chain dropped — aborts on OOM. - // Don't warn more than once entry.value_ptr.source_index = Index::INVALID.get(); } @@ -1725,7 +1697,6 @@ mod __css_validation { } } - // PERF(port): was stack-fallback arena (1024 bytes) — profile. // SAFETY: parse_graph backref valid for link step. Read-only. let input = this.parse_graph().input_files.split_raw(); let mut visitor = Visitor { @@ -1745,5 +1716,3 @@ mod __css_validation { } } } - -// ported from: src/bundler/linker_context/scanImportsAndExports.zig diff --git a/src/bundler/linker_context/writeOutputFilesToDisk.rs b/src/bundler/linker_context/writeOutputFilesToDisk.rs index 75aefd5033f..9123b66ff47 100644 --- a/src/bundler/linker_context/writeOutputFilesToDisk.rs +++ b/src/bundler/linker_context/writeOutputFilesToDisk.rs @@ -25,7 +25,7 @@ use bun_sys::{ write_file_with_path_buffer, }; -/// Zig: `bun.bytecode_extension` (".jsc"). Mirror of `src/bun.zig:bytecode_extension`. +/// Bytecode output file extension (also defined in `generateChunksInParallel.rs`). const BYTECODE_EXTENSION: &str = ".jsc"; pub fn write_output_files_to_disk( @@ -66,9 +66,8 @@ pub fn write_output_files_to_disk( } }; // Optimization: when writing to disk, we can re-use the memory - // PERF(port): MaxHeapAllocator reuses the largest allocation between - // iterations. Verify bun_alloc::MaxHeapAllocator semantics - // match (init/reset/deinit). DynAlloc is currently `()` so the arena + // between iterations: MaxHeapAllocator retains the largest allocation. + // DynAlloc is currently `()` so the arena // handles below are placeholders; allocation routes through global mimalloc. let mut max_heap_allocator = MaxHeapAllocator::init(); let mut _max_heap_allocator_source_map = MaxHeapAllocator::init(); @@ -79,8 +78,8 @@ pub fn write_output_files_to_disk( let bv2: &mut BundleV2 = unsafe { &mut *LinkerContext::bundle_v2_ptr(std::ptr::from_mut::(c)) }; - // PORT NOTE: Zig passes `chunk` (an element of `chunks`) and `chunks` - // together into `code()`/`code_standalone()`. The callee now takes + // `code()`/`code_standalone()` take both `chunk` (an element of `chunks`) + // and `chunks` as // `&Chunk` / `&[Chunk]` (read-only), so iterate by index and reborrow // shared; the only per-chunk mutation is the `intermediate_output` // take/restore done via `chunks[i]`. @@ -115,7 +114,7 @@ pub fn write_output_files_to_disk( } let _trace2 = bun_core::perf::trace("Bundler.writeChunkToDisk"); - // PERF(port): Zig `defer max_heap_allocator.reset()` — reset the reusable + // Reset the reusable // buffer after each chunk. `MaxHeapAllocator::scope()` returns an RAII // guard that resets on drop and derefs to the arena, so when // `code_allocator` is wired up it can borrow through `_code_allocator`. @@ -151,7 +150,7 @@ pub fn write_output_files_to_disk( &resolver_opts.public_path }; - // PORT NOTE: take `intermediate_output` by value so its `&mut self` is + // Take `intermediate_output` by value so its `&mut self` is // disjoint from the `&chunks[i]` / `&[Chunk]` reads below. let mut intermediate_output = core::mem::take(&mut chunks[chunk_index_in_chunks_list].intermediate_output); @@ -235,15 +234,11 @@ pub fn write_output_files_to_disk( + a.len() + b.len() + b"\n".len(); - // PERF(port): Zig used Chunk.IntermediateOutput.allocatorForSize(total_len) - // to pick a size-appropriate arena. Using Vec (global mimalloc) here. let mut buf: Vec = Vec::with_capacity(total_len); - // PERF(port): was appendSliceAssumeCapacity buf.extend_from_slice(&code_result.buffer); buf.extend_from_slice(source_map_start); buf.extend_from_slice(a); buf.extend_from_slice(b); - // PERF(port): was appendAssumeCapacity buf.push(b'\n'); code_result.buffer = buf.into_boxed_slice(); } @@ -298,11 +293,8 @@ pub fn write_output_files_to_disk( let source_map_start = b"//# sourceMappingURL=data:application/json;base64,"; let total_len = code_result.buffer.len() + source_map_start.len() + encode_len + 1; - // PERF(port): Zig used `code_with_inline_source_map_allocator` (MaxHeapAllocator) - // for this Vec to reuse across iterations. let mut buf: Vec = Vec::with_capacity(total_len); - // PERF(port): was appendSliceAssumeCapacity buf.extend_from_slice(&code_result.buffer); buf.extend_from_slice(source_map_start); @@ -310,7 +302,6 @@ pub fn write_output_files_to_disk( buf.resize(old_len + encode_len, 0); let _ = bun_base64::encode(&mut buf[old_len..], &output_source_map); - // PERF(port): was appendAssumeCapacity buf.push(b'\n'); code_result.buffer = buf.into_boxed_slice(); } @@ -502,19 +493,20 @@ pub fn write_output_files_to_disk( } }), entry_point_index: if output_kind == options::OutputKind::EntryPoint { - // TODO(port): `bake_types::Framework` is missing - // `server_components`; once it lands, restore the - // `if c.framework.is_some_and(|fw| fw.server_components.is_some()) { 3 } else { 1 }` - // branch. - let offset: u32 = 1; + // Server-components builds insert 2 extra synthetic sources + // before user entry points, so the source-index offset is 3. + let offset: u32 = if c.framework.is_some_and(|fw| fw.server_components.is_some()) { + 3 + } else { + 1 + }; Some(chunk.entry_point.source_index() - offset) } else { None }, referenced_css_chunks: match &chunk.content { Content::Javascript(js) => { - // Zig: `@ptrCast(dupe(u32, js.css_chunks))` — `Index` is - // `#[repr(transparent)]` over u32. + // `Index` is `#[repr(transparent)]` over u32. js.css_chunks .iter() .map(|&i| OutputFileIndex::init(i)) @@ -545,7 +537,7 @@ pub fn write_output_files_to_disk( } { - // PORT NOTE: reshaped for borrowck — compute len before mut borrow, + // Reshaped for borrowck — compute len before mut borrow, // bump `total_insertions`, then take the slice. let additional_start = output_files.additional_output_files_start as usize; let additional_len = output_files.output_files.len() - additional_start; @@ -615,5 +607,3 @@ pub fn write_output_files_to_disk( } pub use crate::{DeferredBatchTask, ParseTask, ThreadPool}; - -// ported from: src/bundler/linker_context/writeOutputFilesToDisk.zig diff --git a/src/bundler/options.rs b/src/bundler/options.rs index fa53adadea3..6dc188fc637 100644 --- a/src/bundler/options.rs +++ b/src/bundler/options.rs @@ -1,6 +1,7 @@ //! This file is mostly the API schema but with all the options normalized. //! Normalization is necessary because most fields in the API schema are optional +use bun_analytics as analytics; use bun_collections::{MultiArrayList, StringArrayHashMap, StringHashMap}; use bun_core::strings; use bun_core::{Global, Output}; @@ -10,26 +11,8 @@ use bun_options_types::schema::api; use bun_resolver::fs as Fs; use bun_resolver::fs::PathResolverExt as _; use bun_resolver::package_json::{MacroMap as MacroRemap, PackageJSON}; -use std::borrow::Cow; -// TODO(b2-blocked): bun_analytics — Cargo.toml does not yet list the dep -// (adding it triggers upstream rebuilds with in-progress breakage). The -// `analytics::features::*` counters are pure telemetry side effects; the -// increment call-sites below are ``-gated until the dep is wired -// so the no-op is explicit (PORTING.md §Forbidden patterns: silent no-ops). - -mod analytics { - #[allow(non_upper_case_globals)] - pub(super) mod features { - use core::sync::atomic::AtomicUsize; - // Zig: `analytics.Features.{define,loaders,macros,external} += n`. - // Real statics live in `bun_analytics::features::*` (AtomicUsize). - pub(crate) static define: AtomicUsize = AtomicUsize::new(0); - pub(crate) static loaders: AtomicUsize = AtomicUsize::new(0); - pub(crate) static macros: AtomicUsize = AtomicUsize::new(0); - pub(crate) static external: AtomicUsize = AtomicUsize::new(0); - } -} use enum_map::EnumMap; +use std::borrow::Cow; pub use crate::defines; pub use defines::Define; @@ -39,18 +22,16 @@ pub use defines::Define; use crate::defines::{DefineDataExt as _, DefineExt as _}; pub use bun_options_types::global_cache::GlobalCache; -// ── B-2 type aliases for incomplete lower-tier surfaces ── -// TODO(b2-blocked): bun_resolver::package_json::ESModule::ConditionsMap — module -// path doesn't expose this yet; local alias matches Zig `StringArrayHashMap(void)`. -pub type ConditionsMap = StringArrayHashMap<()>; +// Canonical alias lives in the resolver. +pub use bun_resolver::package_json::ConditionsMap; // TODO(b2-blocked): bun_sys::Dir — directory handle. Mapped to Fd for now // (matches `bun.FD.fromStdDir` pattern). pub type Dir = bun_sys::Fd; -/// `Loader.HashTable` (Zig nested type alias). Unified with the canonical +/// Unified with the canonical /// `bun_ast::LoaderHashTable` so the resolver and /// bundler share one nominal map type (PORTING.md crate-tier rule). pub(crate) use bun_ast::LoaderHashTable; -/// `Loader.Map` (Zig nested type alias). +/// Per-[`Loader`] static byte-string map (e.g. the stdin synthetic file names). pub type LoaderEnumMap = EnumMap; /// `bun.http.MimeType` lives in `bun_http_types` (lower tier), not `bun_http`. @@ -60,7 +41,7 @@ mod bun_http { /// `bun.StringSet` (re-exported for `BundleOptions.bundler_feature_flags`). pub use bun_collections::StringSet; -/// `options.zig:Framework.ClientCssInJs` — TYPE_ONLY moved to top of module so +/// TYPE_ONLY moved to top of module so /// `entry_points.rs` (and the inline `options` mod) can resolve it before the /// gated `Framework` impl block below. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -89,9 +70,8 @@ pub fn validate_path( return Box::default(); } // TODO: switch to getFdPath()-based implementation - // PORT NOTE: Zig used `std.fs.path.resolve(arena, &.{cwd, rel_path})`; // `join_abs_string` resolves `.`/`..` against `cwd` into a threadlocal - // buffer which is then boxed (matches the arena.dupe in the Zig path). + // buffer which is then boxed. let _ = path_kind; let out = bun_paths::resolve_path::join_abs_string::(cwd, &[rel_path]); @@ -110,9 +90,6 @@ pub fn validate_path( Box::from(out) } -// PORT NOTE: options.zig `stringHashMapFromArrays` — inline the construction -// (see definesFromTransformOptions / loadersFromTransformOptions below). - // `AllowUnresolved` is defined canonically in // `bun_js_parser::options` (lower tier) because the parser is the consumer // (`P::should_allow_unresolved_dynamic_specifier`). Re-export here so @@ -128,7 +105,7 @@ pub use bun_js_parser::options::AllowUnresolved; // plain `.clone()`. pub use bun_resolver::options::{ExternalModules, WildcardPattern}; -/// `options.zig` `ExternalModules.isNodeBuiltin`. Free fn (not an inherent +/// Free fn (not an inherent /// method) because `ExternalModules` is now a foreign type and Rust forbids /// inherent impls across crates (E0116). pub fn is_node_builtin(str: &[u8]) -> bool { @@ -151,7 +128,7 @@ fn default_wildcard_patterns() -> Vec { .collect() } -/// `options.zig` `ExternalModules.init`. Free fn for the same orphan-rule +/// Free fn for the same orphan-rule /// reason as [`is_node_builtin`]; stays at bundler tier because it needs /// `Fs`/`logger`/`NODE_BUILTIN_PATTERNS`. pub fn init_external_modules( @@ -178,13 +155,7 @@ pub fn init_external_modules( result.node_modules.insert(pattern).expect("unreachable"); } } - Target::Bun => { - // // TODO: fix this stupid copy - // result.node_modules.hash_map.ensureTotalCapacity(BunNodeBuiltinPatternsCompat.len) catch unreachable; - // for (BunNodeBuiltinPatternsCompat) |pattern| { - // result.node_modules.insert(pattern) catch unreachable; - // } - } + Target::Bun => {} _ => {} } @@ -193,7 +164,6 @@ pub fn init_external_modules( } let mut patterns: Vec = Vec::with_capacity(DEFAULT_WILDCARD_PATTERNS.len()); - // PERF(port): was appendSliceAssumeCapacity patterns.extend(default_wildcard_patterns()); for external in externals { @@ -251,7 +221,7 @@ pub use bun_options_types::bundle_enums::ModuleType; pub static MODULE_TYPE_LIST: phf::Map<&'static [u8], ModuleType> = ModuleType::LIST; // Re-export of `bun_ast::Target`. -// Spec options.zig:379 has exactly ONE `Target`; re-export the canonical enum so +// There is exactly ONE `Target`; re-export the canonical enum so // `BundleOptions.target`, `js_printer::Options.target`, the resolver, and css // targets all share one nominal type (kills the `to_bundle_enums_target` shim). pub(crate) use bun_ast::Target; @@ -310,8 +280,8 @@ const DEFAULT_MAIN_FIELDS_BUN: &[&[u8]] = &[ /// `bun_bundler::options` so `use bun_bundler::options::TargetExt;` makes /// `.bake_graph()` etc. available on the single canonical type. pub trait TargetExt: Copy { - // pub const fromJS — deleted: see PORTING.md "*_jsc alias" rule. - // TODO(port): move to *_jsc — bun_bundler_jsc::options_jsc::target_from_js + // `fromJS` lives in `bun_bundler_jsc::options_jsc::target_from_js` + // (PORTING.md "*_jsc alias" rule). fn bake_graph(self) -> crate::bake_types::Graph; fn out_extensions(self) -> StringHashMap<&'static [u8]>; @@ -344,7 +314,6 @@ pub trait TargetExt: Copy { impl TargetExt for Target { fn bake_graph(self) -> crate::bake_types::Graph { - // TODO(b0): bake::Graph arrives from move-in (TYPE_ONLY → bundler) match self { Target::Browser => crate::bake_types::Graph::Client, Target::BakeServerComponentsSsr => crate::bake_types::Graph::Ssr, @@ -383,7 +352,7 @@ pub use bun_options_types::Format; pub use bun_options_types::WindowsOptions; // Re-export of `bun_ast::Loader`. -// Spec options.zig:568 has exactly ONE `Loader`; re-export so the bundler's +// There is exactly ONE `Loader`; re-export so the bundler's // `BundleOptions.loaders` and the resolver's `Path::loader()` operate on the // same nominal type. pub(crate) use bun_ast::Loader; @@ -399,11 +368,10 @@ pub trait LoaderExt: Copy { fn to_mime_type(self, paths: &[&[u8]]) -> bun_http_types::MimeType::MimeType; fn from_mime_type(mime_type: bun_http::MimeType) -> Loader; - // PORT NOTE: `pub type Map` hoisted to module-level `LoaderEnumMap`. + // `pub type Map` hoisted to module-level `LoaderEnumMap`. fn stdin_name_map() -> LoaderEnumMap { - let mut map: LoaderEnumMap = EnumMap::from_array([b"" as &[u8]; 21]); - // TODO(port): EnumMap::from_array length must match variant count. + let mut map: LoaderEnumMap = EnumMap::from_fn(|_| b"" as &[u8]); map[Loader::Jsx] = b"input.jsx"; map[Loader::Js] = b"input.js"; map[Loader::Ts] = b"input.ts"; @@ -423,10 +391,10 @@ pub trait LoaderExt: Copy { map } - // pub const fromJS — deleted: see PORTING.md "*_jsc alias" rule. - // TODO(port): move to *_jsc — bun_bundler_jsc::options_jsc::loader_from_js + // `fromJS` lives in `bun_bundler_jsc::options_jsc::loader_from_js` + // (PORTING.md "*_jsc alias" rule). - // PORT NOTE: `is_type_script` / `is_java_script_like*` spelling-aliases + // `is_type_script` / `is_java_script_like*` spelling-aliases // moved to inherent `impl Loader` in `bun_options_types::bundle_enums` so // cross-crate callers (bun_jsc / bun_runtime) resolve them without a trait // import. @@ -558,8 +526,6 @@ pub struct LoaderResult<'a> { pub package_json: Option<&'a PackageJSON>, } -// TODO(b2-blocked): bun_paths::path_literal! + Fs::Path::loader + strings::eql_long -// arity — body touches VmLoaderCtx vtable which is real but the helper APIs are not. pub fn get_loader_and_virtual_source<'a>( specifier_str: &'a [u8], jsc_vm: &'a VmLoaderCtx, @@ -578,9 +544,8 @@ pub fn get_loader_and_virtual_source<'a>( if let Some(eval_source) = jsc_vm.eval_source() { // SAFETY: eval_source outlives jsc_vm let eval_source: &'a bun_ast::Source = unsafe { &*eval_source }; - // Spec: `bun.pathLiteral("/[eval]")` — the eval/stdin entry path is built - // via `bun.pathLiteral` (cli.zig / run_command.zig / bun.js.zig), which - // rewrites `/` → `\` on Windows, so the suffix uses the platform separator. + // The eval/stdin entry path uses the platform path separator + // (`/` becomes `\` on Windows), so the suffix is per-platform. const EVAL_SUFFIX: &[u8] = if cfg!(windows) { b"\\[eval]" } else { @@ -620,8 +585,7 @@ pub fn get_loader_and_virtual_source<'a>( if !jsc_vm.blob_needs_read_file(blob) { // SAFETY: `path.text` aliases jsc_vm-owned storage (blob filename // or normalized specifier), which outlives the `virtual_source` - // returned to the caller — matches Zig `getLoaderAndVirtualSource` - // where `Fs.Path` and `logger.Source.path` share one type. + // returned to the caller. let static_text: &'static [u8] = bun_ast::StoreStr::new(path.text).slice(); *virtual_source_to_use = Some(bun_ast::Source { path: bun_paths::fs::Path::init(static_text), @@ -699,9 +663,9 @@ const DEFAULT_LOADERS_POSIX: &[(&[u8], Loader)] = &[ #[cfg(all(windows, test))] const DEFAULT_LOADERS_WIN32_EXTRA: &[(&[u8], Loader)] = &[(b".sh", Loader::Bunsh)]; -/// File-extension → default [`Loader`] map (options.zig `defaultLoaders`). +/// File-extension → default [`Loader`] map. /// -/// PERF(port): was `phf::Map<&[u8], Loader>`. phf hashes the full key (SipHash +/// PERF: deliberately not a `phf::Map<&[u8], Loader>`. phf hashes the full key (SipHash /// over up to 9 bytes) + probes a displacement table + does a final memcmp on /// every lookup. With only 22 keys bucketing into 5 distinct lengths /// (3/4/5/6/9, all `.`-prefixed), a length-gated `match` is cheaper: one @@ -774,7 +738,7 @@ impl DefaultLoaders { #[test] fn default_loaders_match_table() { // Guard against drift between the length-gated match and the canonical - // tuple list above (Zig source of truth). + // tuple list above. for (ext, loader) in DEFAULT_LOADERS_POSIX { assert_eq!(DEFAULT_LOADERS.get(ext), Some(loader), "ext {:?}", ext); } @@ -824,7 +788,6 @@ impl ESMConditions { require_condition_map.reserve(defaults.len() + 2 + addon_extra + conditions.len()); style_condition_map.reserve(defaults.len() + 2 + conditions.len()); - // PERF(port): was assume_capacity import_condition_map.insert(b"import".as_slice(), ()); require_condition_map.insert(b"require".as_slice(), ()); style_condition_map.insert(b"style".as_slice(), ()); @@ -863,8 +826,7 @@ impl ESMConditions { }) } - pub fn clone(&self) -> Result { - // TODO(port): narrow error set + pub fn clone(&self) -> Result { let default = self.default.clone()?; let import = self.import.clone()?; let require = self.require.clone()?; @@ -885,7 +847,6 @@ impl ESMConditions { self.style.reserve(conditions.len()); for condition in conditions { - // PERF(port): was assume_capacity self.default.insert(*condition, ()); self.import.insert(*condition, ()); self.require.insert(*condition, ()); @@ -928,7 +889,7 @@ pub use default_user_defines as DefaultUserDefines; pub fn defines_from_transform_options( log: &mut bun_ast::Log, - // PERF(port): borrowed, not owned — the caller (`load_defines`) holds + // PERF: borrowed, not owned — the caller (`load_defines`) holds // `transform_options` behind an `Arc`, so taking the `StringMap` by value // forced a full deep clone of the `--define` map *every* VM init even though // each value gets cloned again below on insert. Reading it through `&` keeps @@ -948,11 +909,9 @@ pub fn defines_from_transform_options( None => (&[], &[]), }; - // PORT NOTE: Zig stringHashMapFromArrays — inlined as concrete RawDefines build (over-reserves +4). let mut user_defines: defines::RawDefines = defines::RawDefines::default(); user_defines.reserve(input_keys.len() + 4); for (i, key) in input_keys.iter().enumerate() { - // PERF(port): was assume_capacity user_defines.insert(key.as_ref(), input_values[i].clone()); } @@ -979,7 +938,7 @@ pub fn defines_from_transform_options( break 'load_env; } - // PORT NOTE: flatten `api::StringMap` into parallel borrowed slices. + // flatten `api::StringMap` into parallel borrowed slices. // `api::DotEnvBehavior` is the same type as `DotEnv::DotEnvBehavior` // (re-export), so no conversion needed. let api_defaults = framework.to_api().defaults; @@ -1121,7 +1080,7 @@ impl ResolveFileExtensions { } /// Convert a static `&[&[u8]]` default into an owned `Box<[Box<[u8]>]>`. -/// PERF(port): the Zig kept these as borrowed `[]const string`; we own them so +/// We own them so /// user-provided lists (e.g. `transform.extension_order`) can be stored without /// `Box::leak` (PORTING.md §Forbidden patterns). #[inline] @@ -1174,13 +1133,12 @@ pub fn loaders_from_transform_options( let mut loaders = StringArrayHashMap::::default(); loaders.reserve(u32::try_from(total_capacity).expect("int cast") as usize); for (i, ext) in input_loaders.extensions.iter().enumerate() { - // PERF(port): was assume_capacity loaders.insert(ext, loader_values[i]); } - // PORT NOTE: Zig `getOrPutValue` → contains+insert; `Loader` is not `Default` + // contains+insert (only when absent); `Loader` is not `Default` // so the `V: Default`-gated `StringArrayHashMap::get_or_put_value` does not - // apply. Semantics are identical (insert only when absent). + // apply. for ext in DEFAULT_LOADER_EXT { if !loaders.contains(*ext) { loaders.insert(*ext, *DEFAULT_LOADERS.get(*ext).unwrap()); @@ -1206,8 +1164,6 @@ pub fn loaders_from_transform_options( Ok(loaders) } -// PORT NOTE: `Dir` alias hoisted to top of file (= bun_sys::Fd). - #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum SourceMapOption { #[default] @@ -1241,7 +1197,7 @@ impl SourceMapOption { } } -// PORT NOTE: hoisted from `impl SourceMapOption` — Rust forbids `static` in inherent impls. +// hoisted from `impl SourceMapOption` — Rust forbids `static` in inherent impls. pub static SOURCE_MAP_OPTION_MAP: phf::Map<&'static [u8], SourceMapOption> = phf::phf_map! { b"none" => SourceMapOption::None, b"inline" => SourceMapOption::Inline, @@ -1271,7 +1227,7 @@ impl PackagesOption { } } -// PORT NOTE: hoisted from `impl PackagesOption` — Rust forbids `static` in inherent impls. +// hoisted from `impl PackagesOption` — Rust forbids `static` in inherent impls. pub static PACKAGES_OPTION_MAP: phf::Map<&'static [u8], PackagesOption> = phf::phf_map! { b"external" => PackagesOption::External, b"bundle" => PackagesOption::Bundle, @@ -1287,10 +1243,8 @@ pub struct BundleOptions<'a> { /// Set of enabled feature flags for dead-code elimination via `import { feature } from "bun:bundle"`. /// Initialized once from the CLI --feature flags. /// - /// Zig: `*const bun.StringSet = &Runtime.Features.empty_bundler_feature_flags`. /// `None` ≡ the static empty set; `Some` is the owned `Box` returned by - /// `Runtime::Features::init_bundler_feature_flags` (freed on Drop, matching - /// options.zig:1888-1892 which frees iff distinct from the static empty set). + /// `Runtime::Features::init_bundler_feature_flags` (freed on Drop). pub bundler_feature_flags: Option>, pub loaders: LoaderHashTable, pub resolve_dir: Cow<'static, [u8]>, @@ -1306,9 +1260,8 @@ pub struct BundleOptions<'a> { pub hot_module_reloading: bool, pub react_fast_refresh: bool, pub inject: Option]>>, - // TODO(port): lifetime — `bun_url::URL<'a>` borrows its input string. Zig - // stored it borrowing `transform_options.origin` (sibling field). Using the - // owned variant so the struct is self-contained. + // `bun_url::URL<'a>` borrows its input string; the owned variant keeps the + // struct self-contained. pub origin: bun_url::OwnedURL, pub output_dir_handle: Option