diff --git a/src/ast/ast_memory_allocator.rs b/src/ast/ast_memory_allocator.rs index 322e5921eb76..8dbe6c3aebc0 100644 --- a/src/ast/ast_memory_allocator.rs +++ b/src/ast/ast_memory_allocator.rs @@ -278,7 +278,7 @@ impl ASTMemoryAllocator { // The AstAlloc state follows the arena's retain-or-recycle // decision: callers like `--define` hold `StoreRef`s across a // retained reset, so only clear it when the heap is recycled. - if !self.arena.reset_retain_with_limit(limit) { + if self.arena.reset_retain_with_limit(limit) == bun_alloc::ResetOutcome::Recycled { self.reset_ast_state(); } self.arena_dirty = false; diff --git a/src/ast/e.rs b/src/ast/e.rs index 32c61c186f30..9c38318a74a1 100644 --- a/src/ast/e.rs +++ b/src/ast/e.rs @@ -498,16 +498,19 @@ impl Default for ImportIdentifier { Self { ref_: Ref::NONE } } } +bun_core::bool_enum!(pub WasOriginallyIdentifier); + impl ImportIdentifier { #[inline] - pub const fn new(ref_: Ref, was_originally_identifier: bool) -> Self { + pub const fn new(ref_: Ref, was_originally_identifier: WasOriginallyIdentifier) -> Self { // Strip any incoming user bits (the caller may pass an // `E::Identifier.ref_` carrying its own flags in bits 1/2) before // applying ours, so foreign flags can't leak into this node. Self { - ref_: ref_ - .without_user_bits() - .with_user_bit(0, was_originally_identifier), + ref_: ref_.without_user_bits().with_user_bit( + 0, + matches!(was_originally_identifier, WasOriginallyIdentifier::Yes), + ), } } @@ -1123,6 +1126,8 @@ impl JsonTape { } } +bun_core::bool_enum!(pub IsSingleLine); + /// `Data::EObjectJSON`: a `(first, count)` span of the document's property-row tape. #[repr(C)] pub struct ObjectJSON { @@ -1158,7 +1163,7 @@ impl ObjectJSON { tape: core::ptr::NonNull, first: u32, count: u32, - is_single_line: bool, + is_single_line: IsSingleLine, close_brace_loc: crate::Loc, ) -> Self { ObjectJSON { @@ -1166,7 +1171,7 @@ impl ObjectJSON { first, count, close_brace_loc, - is_single_line, + is_single_line: is_single_line == IsSingleLine::Yes, } } @@ -1230,7 +1235,7 @@ impl ArrayJSON { tape: core::ptr::NonNull, first: u32, count: u32, - is_single_line: bool, + is_single_line: IsSingleLine, close_bracket_loc: crate::Loc, ) -> Self { ArrayJSON { @@ -1238,7 +1243,7 @@ impl ArrayJSON { first, count, close_bracket_loc, - is_single_line, + is_single_line: is_single_line == IsSingleLine::Yes, } } @@ -1810,7 +1815,11 @@ impl EString { // PERF: transcodes to a heap Vec then copies into the bump // arena — profile. // `fail_if_invalid = false` means the only possible error is `OutOfMemory`. - let utf16 = bun_core::handle_oom(strings::to_utf16_alloc_for_real(utf8, false, false)); + let utf16 = bun_core::handle_oom(strings::to_utf16_alloc_for_real( + utf8, + strings::FailIfInvalid::No, + strings::Sentinel::No, + )); let arena_slice: &mut [u16] = bump.alloc_slice_copy(&utf16); Self::init_utf16(arena_slice) } @@ -1860,7 +1869,7 @@ impl EString { pub fn eql_bytes(&self, other: &[u8]) -> bool { if self.is_utf8() { - strings::eql_long(&self.data, other, true) + strings::eql_long(&self.data, other, strings::CheckLen::Yes) } else { strings::utf16_eql_string(self.slice16(), other) } @@ -1885,7 +1894,11 @@ impl EString { let mut i = 0usize; let mut next: Option<&EString> = Some(self); while let Some(cur) = next { - if !strings::eql_long(&cur.data, &value[i..i + cur.data.len()], false) { + if !strings::eql_long( + &cur.data, + &value[i..i + cur.data.len()], + strings::CheckLen::No, + ) { return false; } i += cur.data.len(); @@ -1985,7 +1998,7 @@ impl EString { pub fn eql_string(&self, other: &EString) -> bool { if self.is_utf8() { if other.is_utf8() { - strings::eql_long(&self.data, &other.data, true) + strings::eql_long(&self.data, &other.data, strings::CheckLen::Yes) } else { strings::utf16_eql_string(other.slice16(), &self.data) } @@ -2543,13 +2556,15 @@ mod json_tape_tests { let kb = tape.get().alloc_str(b"b"); let (first, count) = tape.get().append_props(&[prop(kb, JsonValue::Null)], &[]); // SAFETY: the tape's own pointer, as `Parser` passes it. - let inner = unsafe { ObjectJSON::new(tape.ptr(), first, count, true, Loc::EMPTY) }; + let inner = + unsafe { ObjectJSON::new(tape.ptr(), first, count, IsSingleLine::Yes, Loc::EMPTY) }; // The parse continues: another string, then the outer object's rows. let ka = tape.get().alloc_str(b"a"); let (first, count) = tape.get().append_props(&[prop(ka, JsonValue::Null)], &[]); // SAFETY: as above. - let outer = unsafe { ObjectJSON::new(tape.ptr(), first, count, true, Loc::EMPTY) }; + let outer = + unsafe { ObjectJSON::new(tape.ptr(), first, count, IsSingleLine::Yes, Loc::EMPTY) }; // Post-parse reads, after every one of those writes. assert_eq!(inner.properties().len(), 1); @@ -2566,11 +2581,13 @@ mod json_tape_tests { let (first, count) = tape.get().append_items(&[JsonValue::Null], &[]); // SAFETY: the tape's own pointer, as `Parser` passes it. - let inner = unsafe { ArrayJSON::new(tape.ptr(), first, count, true, Loc::EMPTY) }; + let inner = + unsafe { ArrayJSON::new(tape.ptr(), first, count, IsSingleLine::Yes, Loc::EMPTY) }; let (first, count) = tape.get().append_items(&[JsonValue::Boolean(true)], &[]); // SAFETY: as above. - let outer = unsafe { ArrayJSON::new(tape.ptr(), first, count, true, Loc::EMPTY) }; + let outer = + unsafe { ArrayJSON::new(tape.ptr(), first, count, IsSingleLine::Yes, Loc::EMPTY) }; assert_eq!(inner.items().len(), 1); assert!(matches!(inner.items()[0], JsonValue::Null)); @@ -2583,9 +2600,9 @@ mod json_tape_tests { fn zero_count_spans_do_not_read_the_tape() { let tape = TapeOwner::new(); // SAFETY: the tape's own pointer. - let o = unsafe { ObjectJSON::new(tape.ptr(), 0, 0, true, Loc::EMPTY) }; + let o = unsafe { ObjectJSON::new(tape.ptr(), 0, 0, IsSingleLine::Yes, Loc::EMPTY) }; // SAFETY: as above. - let a = unsafe { ArrayJSON::new(tape.ptr(), 0, 0, true, Loc::EMPTY) }; + let a = unsafe { ArrayJSON::new(tape.ptr(), 0, 0, IsSingleLine::Yes, Loc::EMPTY) }; assert!(o.properties().is_empty()); assert_eq!(o.value_locs(), Some(&[][..])); assert!(a.items().is_empty()); @@ -2624,13 +2641,15 @@ mod json_tape_tests { let kb = tape(tape_ptr).alloc_str(b"b"); let (first, count) = tape(tape_ptr).append_props(&[prop(kb, JsonValue::Null)], &[]); // SAFETY: the tape's own pointer. - let inner = unsafe { ObjectJSON::new(tape_ptr, first, count, true, Loc::EMPTY) }; + let inner = + unsafe { ObjectJSON::new(tape_ptr, first, count, IsSingleLine::Yes, Loc::EMPTY) }; // The parse keeps appending after the node is built. let ka = tape(tape_ptr).alloc_str(b"a"); let (first, count) = tape(tape_ptr).append_props(&[prop(ka, JsonValue::Null)], &[]); // SAFETY: as above. - let outer = unsafe { ObjectJSON::new(tape_ptr, first, count, true, Loc::EMPTY) }; + let outer = + unsafe { ObjectJSON::new(tape_ptr, first, count, IsSingleLine::Yes, Loc::EMPTY) }; assert_eq!(inner.properties()[0].key.slice(), b"b"); assert_eq!(outer.properties()[0].key.slice(), b"a"); diff --git a/src/ast/lib.rs b/src/ast/lib.rs index d7e9b3b51088..b5174c0cd444 100644 --- a/src/ast/lib.rs +++ b/src/ast/lib.rs @@ -148,6 +148,8 @@ pub enum RefTag { Symbol = 3, } +bun_core::bool_enum!(pub IsSourceContentsSlice); + /// Packed-u64 symbol reference: `{inner_index: u28, user: u3, tag: u2, source_index: u31}`. /// /// LSB-first packing for the `tag`/`source_index` fields, with 3 bits stolen @@ -273,11 +275,14 @@ impl Ref { i == Self::INNER_MASK as u32 // maxInt(u31) } - pub fn init(inner_index: u32, source_index: u32, is_source_contents_slice: bool) -> Ref { - let tag = if is_source_contents_slice { - RefTag::SourceContentsSlice - } else { - RefTag::AllocatedName + pub fn init( + inner_index: u32, + source_index: u32, + is_source_contents_slice: IsSourceContentsSlice, + ) -> Ref { + let tag = match is_source_contents_slice { + IsSourceContentsSlice::Yes => RefTag::SourceContentsSlice, + IsSourceContentsSlice::No => RefTag::AllocatedName, }; Self::pack(inner_index, tag, source_index) } @@ -1350,6 +1355,13 @@ impl Range { // Log // ─────────────────────────────────────────────────────────────────────────── +bun_core::bool_enum!( + /// Whether the source contents backing the messages are recycled (so line + /// text must be deep-copied when cloning into another `Log`). + pub Recycled +); +bun_core::bool_enum!(pub Redact); + pub struct Log { pub warnings: u32, pub errors: u32, @@ -1536,19 +1548,19 @@ impl Log { }, text, Box::default(), - false, + Redact::No, ) } // `to_js`/`to_js_aggregate_error`/`to_js_array` live in `bun_logger_jsc`. - pub fn clone_to_with_recycled(&mut self, other: &mut Log, recycled: bool) { + pub fn clone_to_with_recycled(&mut self, other: &mut Log, recycled: Recycled) { let dest_start = other.msgs.len(); other.msgs.extend(self.msgs.iter().map(Msg::clone)); other.warnings += self.warnings; other.errors += self.errors; - if recycled { + if recycled == Recycled::Yes { let mut string_builder = StringBuilder; let mut notes_count: usize = 0; for msg in &self.msgs { @@ -1571,7 +1583,7 @@ impl Log { } } - pub fn append_to_with_recycled(&mut self, other: &mut Log, recycled: bool) { + pub fn append_to_with_recycled(&mut self, other: &mut Log, recycled: Recycled) { self.clone_to_with_recycled(other, recycled); self.msgs.clear(); self.msgs.shrink_to_fit(); @@ -1582,7 +1594,7 @@ impl Log { } pub fn append_to_maybe_recycled(&mut self, other: &mut Log, source: &Source) { - self.append_to_with_recycled(other, source.contents_is_recycled) + self.append_to_with_recycled(other, Recycled::from_bool(source.contents_is_recycled)) } // TODO: remove `deinit` because it does not de-initialize the log; it clears it @@ -1614,7 +1626,7 @@ impl Log { r: Range, text: Cow<'static, [u8]>, notes: Box<[Data]>, - redact_sensitive_information: bool, + redact_sensitive_information: Redact, ) { match kind { Kind::Err => self.errors += 1, @@ -1628,7 +1640,7 @@ impl Log { kind, data, notes, - redact_sensitive_information, + redact_sensitive_information: redact_sensitive_information == Redact::Yes, ..Default::default() }) } @@ -1742,7 +1754,7 @@ impl Log { args: fmt::Arguments<'_>, ) { let text = alloc_print(args); - self.add_formatted_msg(Kind::Err, source, r, text, Box::default(), false) + self.add_formatted_msg(Kind::Err, source, r, text, Box::default(), Redact::No) } #[inline] @@ -1754,7 +1766,7 @@ impl Log { args: fmt::Arguments<'_>, ) { let text = alloc_print(args); - self.add_formatted_msg(Kind::Err, source, r, text, notes, false) + self.add_formatted_msg(Kind::Err, source, r, text, notes, Redact::No) } #[inline] @@ -1774,7 +1786,7 @@ impl Log { }, text, Box::default(), - false, + Redact::No, ) } @@ -1791,7 +1803,7 @@ impl Log { }, text, Box::default(), - opts.redact_sensitive_information, + Redact::from_bool(opts.redact_sensitive_information), ) } @@ -1854,7 +1866,7 @@ impl Log { }, text, Box::default(), - false, + Redact::No, ) } @@ -1917,7 +1929,7 @@ impl Log { return; } let text = alloc_print(args); - self.add_formatted_msg(Kind::Warn, source, r, text, Box::default(), false) + self.add_formatted_msg(Kind::Warn, source, r, text, Box::default(), Redact::No) } #[cold] @@ -1955,7 +1967,7 @@ impl Log { args: fmt::Arguments<'_>, ) { let text = alloc_print(args); - self.add_formatted_msg(Kind::Warn, source, r, text, notes, false) + self.add_formatted_msg(Kind::Warn, source, r, text, notes, Redact::No) } #[cold] diff --git a/src/ast/op.rs b/src/ast/op.rs index 2912432e5c62..eece0013cce0 100644 --- a/src/ast/op.rs +++ b/src/ast/op.rs @@ -252,12 +252,14 @@ pub struct Op { pub is_keyword: bool, } +bun_core::bool_enum!(pub IsKeyword); + impl Op { - pub const fn init(text: &'static [u8], level: Level, is_keyword: bool) -> Op { + pub const fn init(text: &'static [u8], level: Level, is_keyword: IsKeyword) -> Op { Op { text, level, - is_keyword, + is_keyword: matches!(is_keyword, IsKeyword::Yes), } } } @@ -277,73 +279,74 @@ impl Table { // Built at const-eval time so it lives in `.rodata` with zero init code on the // startup path. pub static TABLE: Table = Table({ - const NIL: Op = Op::init(b"", Level::Lowest, false); + const NIL: Op = Op::init(b"", Level::Lowest, IsKeyword::No); let mut t = [NIL; ::LENGTH]; // Prefix - t[Code::UnPos as usize] = Op::init(b"+", Level::Prefix, false); - t[Code::UnNeg as usize] = Op::init(b"-", Level::Prefix, false); - t[Code::UnCpl as usize] = Op::init(b"~", Level::Prefix, false); - t[Code::UnNot as usize] = Op::init(b"!", Level::Prefix, false); - t[Code::UnVoid as usize] = Op::init(b"void", Level::Prefix, true); - t[Code::UnTypeof as usize] = Op::init(b"typeof", Level::Prefix, true); - t[Code::UnDelete as usize] = Op::init(b"delete", Level::Prefix, true); + t[Code::UnPos as usize] = Op::init(b"+", Level::Prefix, IsKeyword::No); + t[Code::UnNeg as usize] = Op::init(b"-", Level::Prefix, IsKeyword::No); + t[Code::UnCpl as usize] = Op::init(b"~", Level::Prefix, IsKeyword::No); + t[Code::UnNot as usize] = Op::init(b"!", Level::Prefix, IsKeyword::No); + t[Code::UnVoid as usize] = Op::init(b"void", Level::Prefix, IsKeyword::Yes); + t[Code::UnTypeof as usize] = Op::init(b"typeof", Level::Prefix, IsKeyword::Yes); + t[Code::UnDelete as usize] = Op::init(b"delete", Level::Prefix, IsKeyword::Yes); // Prefix update - t[Code::UnPreDec as usize] = Op::init(b"--", Level::Prefix, false); - t[Code::UnPreInc as usize] = Op::init(b"++", Level::Prefix, false); + t[Code::UnPreDec as usize] = Op::init(b"--", Level::Prefix, IsKeyword::No); + t[Code::UnPreInc as usize] = Op::init(b"++", Level::Prefix, IsKeyword::No); // Postfix update - t[Code::UnPostDec as usize] = Op::init(b"--", Level::Postfix, false); - t[Code::UnPostInc as usize] = Op::init(b"++", Level::Postfix, false); + t[Code::UnPostDec as usize] = Op::init(b"--", Level::Postfix, IsKeyword::No); + t[Code::UnPostInc as usize] = Op::init(b"++", Level::Postfix, IsKeyword::No); // Left-associative - t[Code::BinAdd as usize] = Op::init(b"+", Level::Add, false); - t[Code::BinSub as usize] = Op::init(b"-", Level::Add, false); - t[Code::BinMul as usize] = Op::init(b"*", Level::Multiply, false); - t[Code::BinDiv as usize] = Op::init(b"/", Level::Multiply, false); - t[Code::BinRem as usize] = Op::init(b"%", Level::Multiply, false); - t[Code::BinPow as usize] = Op::init(b"**", Level::Exponentiation, false); - t[Code::BinLt as usize] = Op::init(b"<", Level::Compare, false); - t[Code::BinLe as usize] = Op::init(b"<=", Level::Compare, false); - t[Code::BinGt as usize] = Op::init(b">", Level::Compare, false); - t[Code::BinGe as usize] = Op::init(b">=", Level::Compare, false); - t[Code::BinIn as usize] = Op::init(b"in", Level::Compare, true); - t[Code::BinInstanceof as usize] = Op::init(b"instanceof", Level::Compare, true); - t[Code::BinShl as usize] = Op::init(b"<<", Level::Shift, false); - t[Code::BinShr as usize] = Op::init(b">>", Level::Shift, false); - t[Code::BinUShr as usize] = Op::init(b">>>", Level::Shift, false); - t[Code::BinLooseEq as usize] = Op::init(b"==", Level::Equals, false); - t[Code::BinLooseNe as usize] = Op::init(b"!=", Level::Equals, false); - t[Code::BinStrictEq as usize] = Op::init(b"===", Level::Equals, false); - t[Code::BinStrictNe as usize] = Op::init(b"!==", Level::Equals, false); - t[Code::BinNullishCoalescing as usize] = Op::init(b"??", Level::NullishCoalescing, false); - t[Code::BinLogicalOr as usize] = Op::init(b"||", Level::LogicalOr, false); - t[Code::BinLogicalAnd as usize] = Op::init(b"&&", Level::LogicalAnd, false); - t[Code::BinBitwiseOr as usize] = Op::init(b"|", Level::BitwiseOr, false); - t[Code::BinBitwiseAnd as usize] = Op::init(b"&", Level::BitwiseAnd, false); - t[Code::BinBitwiseXor as usize] = Op::init(b"^", Level::BitwiseXor, false); + t[Code::BinAdd as usize] = Op::init(b"+", Level::Add, IsKeyword::No); + t[Code::BinSub as usize] = Op::init(b"-", Level::Add, IsKeyword::No); + t[Code::BinMul as usize] = Op::init(b"*", Level::Multiply, IsKeyword::No); + t[Code::BinDiv as usize] = Op::init(b"/", Level::Multiply, IsKeyword::No); + t[Code::BinRem as usize] = Op::init(b"%", Level::Multiply, IsKeyword::No); + t[Code::BinPow as usize] = Op::init(b"**", Level::Exponentiation, IsKeyword::No); + t[Code::BinLt as usize] = Op::init(b"<", Level::Compare, IsKeyword::No); + t[Code::BinLe as usize] = Op::init(b"<=", Level::Compare, IsKeyword::No); + t[Code::BinGt as usize] = Op::init(b">", Level::Compare, IsKeyword::No); + t[Code::BinGe as usize] = Op::init(b">=", Level::Compare, IsKeyword::No); + t[Code::BinIn as usize] = Op::init(b"in", Level::Compare, IsKeyword::Yes); + t[Code::BinInstanceof as usize] = Op::init(b"instanceof", Level::Compare, IsKeyword::Yes); + t[Code::BinShl as usize] = Op::init(b"<<", Level::Shift, IsKeyword::No); + t[Code::BinShr as usize] = Op::init(b">>", Level::Shift, IsKeyword::No); + t[Code::BinUShr as usize] = Op::init(b">>>", Level::Shift, IsKeyword::No); + t[Code::BinLooseEq as usize] = Op::init(b"==", Level::Equals, IsKeyword::No); + t[Code::BinLooseNe as usize] = Op::init(b"!=", Level::Equals, IsKeyword::No); + t[Code::BinStrictEq as usize] = Op::init(b"===", Level::Equals, IsKeyword::No); + t[Code::BinStrictNe as usize] = Op::init(b"!==", Level::Equals, IsKeyword::No); + t[Code::BinNullishCoalescing as usize] = + Op::init(b"??", Level::NullishCoalescing, IsKeyword::No); + t[Code::BinLogicalOr as usize] = Op::init(b"||", Level::LogicalOr, IsKeyword::No); + t[Code::BinLogicalAnd as usize] = Op::init(b"&&", Level::LogicalAnd, IsKeyword::No); + t[Code::BinBitwiseOr as usize] = Op::init(b"|", Level::BitwiseOr, IsKeyword::No); + t[Code::BinBitwiseAnd as usize] = Op::init(b"&", Level::BitwiseAnd, IsKeyword::No); + t[Code::BinBitwiseXor as usize] = Op::init(b"^", Level::BitwiseXor, IsKeyword::No); // Non-associative - t[Code::BinComma as usize] = Op::init(b",", Level::Comma, false); + t[Code::BinComma as usize] = Op::init(b",", Level::Comma, IsKeyword::No); // Right-associative - t[Code::BinAssign as usize] = Op::init(b"=", Level::Assign, false); - t[Code::BinAddAssign as usize] = Op::init(b"+=", Level::Assign, false); - t[Code::BinSubAssign as usize] = Op::init(b"-=", Level::Assign, false); - t[Code::BinMulAssign as usize] = Op::init(b"*=", Level::Assign, false); - t[Code::BinDivAssign as usize] = Op::init(b"/=", Level::Assign, false); - t[Code::BinRemAssign as usize] = Op::init(b"%=", Level::Assign, false); - t[Code::BinPowAssign as usize] = Op::init(b"**=", Level::Assign, false); - t[Code::BinShlAssign as usize] = Op::init(b"<<=", Level::Assign, false); - t[Code::BinShrAssign as usize] = Op::init(b">>=", Level::Assign, false); - t[Code::BinUShrAssign as usize] = Op::init(b">>>=", Level::Assign, false); - t[Code::BinBitwiseOrAssign as usize] = Op::init(b"|=", Level::Assign, false); - t[Code::BinBitwiseAndAssign as usize] = Op::init(b"&=", Level::Assign, false); - t[Code::BinBitwiseXorAssign as usize] = Op::init(b"^=", Level::Assign, false); - t[Code::BinNullishCoalescingAssign as usize] = Op::init(b"??=", Level::Assign, false); - t[Code::BinLogicalOrAssign as usize] = Op::init(b"||=", Level::Assign, false); - t[Code::BinLogicalAndAssign as usize] = Op::init(b"&&=", Level::Assign, false); + t[Code::BinAssign as usize] = Op::init(b"=", Level::Assign, IsKeyword::No); + t[Code::BinAddAssign as usize] = Op::init(b"+=", Level::Assign, IsKeyword::No); + t[Code::BinSubAssign as usize] = Op::init(b"-=", Level::Assign, IsKeyword::No); + t[Code::BinMulAssign as usize] = Op::init(b"*=", Level::Assign, IsKeyword::No); + t[Code::BinDivAssign as usize] = Op::init(b"/=", Level::Assign, IsKeyword::No); + t[Code::BinRemAssign as usize] = Op::init(b"%=", Level::Assign, IsKeyword::No); + t[Code::BinPowAssign as usize] = Op::init(b"**=", Level::Assign, IsKeyword::No); + t[Code::BinShlAssign as usize] = Op::init(b"<<=", Level::Assign, IsKeyword::No); + t[Code::BinShrAssign as usize] = Op::init(b">>=", Level::Assign, IsKeyword::No); + t[Code::BinUShrAssign as usize] = Op::init(b">>>=", Level::Assign, IsKeyword::No); + t[Code::BinBitwiseOrAssign as usize] = Op::init(b"|=", Level::Assign, IsKeyword::No); + t[Code::BinBitwiseAndAssign as usize] = Op::init(b"&=", Level::Assign, IsKeyword::No); + t[Code::BinBitwiseXorAssign as usize] = Op::init(b"^=", Level::Assign, IsKeyword::No); + t[Code::BinNullishCoalescingAssign as usize] = Op::init(b"??=", Level::Assign, IsKeyword::No); + t[Code::BinLogicalOrAssign as usize] = Op::init(b"||=", Level::Assign, IsKeyword::No); + t[Code::BinLogicalAndAssign as usize] = Op::init(b"&&=", Level::Assign, IsKeyword::No); t }); diff --git a/src/base64/lib.rs b/src/base64/lib.rs index 627be555a6f1..20d783289ad4 100644 --- a/src/base64/lib.rs +++ b/src/base64/lib.rs @@ -21,7 +21,7 @@ static MIXED_DECODER: zig_base64::Base64DecoderWithIgnore = { }; pub fn decode(destination: &mut [u8], source: &[u8]) -> SIMDUTFResult { - let result = simdutf::base64::decode(source, destination, false); + let result = simdutf::base64::decode(source, destination, Alphabet::Standard); if !result.is_successful() { // The input does not follow the WHATWG forgiving-base64 specification @@ -53,6 +53,8 @@ pub const fn decode_lenient_len(source_len: usize) -> usize { source_len.div_ceil(4) * 3 } +pub use simdutf::base64::Alphabet; + /// Decode base64 the way Node.js `Buffer.from(str, "base64" | "base64url")` /// and `buf.write(str, "base64" | "base64url")` do: both the standard and the /// URL-safe alphabets are accepted, whitespace and any other non-alphabet @@ -64,7 +66,7 @@ pub const fn decode_lenient_len(source_len: usize) -> usize { /// decoded with simdutf's `base64_default_or_url_accept_garbage` mode. /// /// Returns the number of bytes written to `destination`. -pub fn decode_lenient(destination: &mut [u8], source: &[u8], is_urlsafe: bool) -> usize { +pub fn decode_lenient(destination: &mut [u8], source: &[u8], is_urlsafe: Alphabet) -> usize { // Fast path: the common case is strictly valid base64 for the requested // alphabet (possibly with whitespace and padding), which simdutf decodes // with its fastest kernel. This is the same first attempt Node.js makes. @@ -126,7 +128,7 @@ pub fn encode_alloc(source: &[u8]) -> Vec { } fn simdutf_encode_len_url_safe(source_len: usize) -> usize { - simdutf::base64::encode_len(source_len, true) + simdutf::base64::encode_len(source_len, Alphabet::UrlSafe) } /// Encode with the following differences from regular `encode` function: @@ -136,7 +138,7 @@ fn simdutf_encode_len_url_safe(source_len: usize) -> usize { /// /// See the documentation for simdutf's `binary_to_base64` function for more details (simdutf_impl.h). pub fn encode_url_safe(dest: &mut [u8], source: &[u8]) -> usize { - simdutf::base64::encode(source, dest, true) + simdutf::base64::encode(source, dest, Alphabet::UrlSafe) } /// `encode_url_safe` into a freshly-allocated `Vec` sized exactly via diff --git a/src/boringssl/lib.rs b/src/boringssl/lib.rs index 57c5e2ec84c6..b44d955dbbcc 100644 --- a/src/boringssl/lib.rs +++ b/src/boringssl/lib.rs @@ -190,7 +190,7 @@ fn unfqdn(name: &[u8]) -> &[u8] { #[inline] fn eq_nocase(a: &[u8], b: &[u8]) -> bool { - strings::eql_case_insensitive_ascii(a, b, true) + strings::eql_case_insensitive_ascii(a, b, strings::CheckLen::Yes) } /// Wildcard interpretation for [`match_hostname`]'s left-most label. @@ -461,15 +461,20 @@ pub mod host_check { pub const NEVER_CHECK_SUBJECT: u32 = 0x20; } +bun_core::bool_enum!(Subdomains { + MultiLabel, + SingleLabel +}); + /// OpenSSL: a `host` starting with `.` matches any certificate name that ends /// with that suffix; `SINGLE_LABEL_SUBDOMAINS` restricts the stripped prefix to /// a single label. -fn match_dot_subdomain(pattern: &[u8], host: &[u8], single_label: bool) -> bool { +fn match_dot_subdomain(pattern: &[u8], host: &[u8], single_label: Subdomains) -> bool { if pattern.len() < host.len() || strings::index_of_char(pattern, 0).is_some() { return false; } let skip = &pattern[..pattern.len() - host.len()]; - if single_label && strings::index_of_char(skip, b'.').is_some() { + if single_label == Subdomains::SingleLabel && strings::index_of_char(skip, b'.').is_some() { return false; } eq_nocase(&pattern[skip.len()..], host) @@ -539,7 +544,7 @@ pub unsafe extern "C" fn Bun__X509__checkHost( strip_trailing_dot: false, }; let dot_host = host.len() > 1 && host[0] == b'.'; - let single_label = flags & host_check::SINGLE_LABEL_SUBDOMAINS != 0; + let single_label = Subdomains::from_bool(flags & host_check::SINGLE_LABEL_SUBDOMAINS != 0); let matches = |name: &[u8]| { if dot_host { diff --git a/src/brotli/lib.rs b/src/brotli/lib.rs index 58e34a6e1a89..d94415638b05 100644 --- a/src/brotli/lib.rs +++ b/src/brotli/lib.rs @@ -42,6 +42,7 @@ impl Default for DecoderOptions { } } +pub use bun_core::compress::Chunk; use bun_core::compress::State as ReaderState; // ────────────────────────────────────────────────────────────────────────── @@ -103,12 +104,12 @@ impl StreamingDecoder { /// Consume all of `input`, appending decompressed bytes to `out` /// (growing in 4096-byte steps). Returns `ShortRead` when more input is - /// required and `is_done` is false. + /// required and `is_done` is [`Chunk::More`]. pub fn decompress( &mut self, input: &[u8], out: &mut Vec, - is_done: bool, + is_done: Chunk, ) -> crate::Result<()> { if matches!(self.state, ReaderState::End | ReaderState::Error) { return Ok(()); @@ -164,7 +165,7 @@ impl StreamingDecoder { } c::BrotliDecoderResult::needs_more_input => { self.state = ReaderState::Inflating; - if is_done { + if is_done == Chunk::Last { self.state = ReaderState::Error; return Err(crate::Error::BrotliDecompressionError); } diff --git a/src/bun_alloc/MimallocArena.rs b/src/bun_alloc/MimallocArena.rs index 699c11163438..635569fb1a61 100644 --- a/src/bun_alloc/MimallocArena.rs +++ b/src/bun_alloc/MimallocArena.rs @@ -50,6 +50,13 @@ fn debug_thread_stamp() -> u64 { ID.with(|id| *id) } +/// Result of [`MimallocArena::reset_retain_with_limit`]. +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum ResetOutcome { + Recycled, + Retained, +} + /// A mimalloc heap. Owns a `mi_heap_t`; all allocations are bulk-freed on /// `Drop` via `mi_heap_destroy`. /// @@ -212,8 +219,8 @@ impl MimallocArena { /// 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. + /// `mi_heap_new`). Returns [`ResetOutcome::Retained`] when the heap was + /// retained, [`ResetOutcome::Recycled`] when it was recycled. /// /// **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 @@ -252,7 +259,7 @@ impl MimallocArena { /// `mi_heap_destroy`" knob; raising it trades steady-state RSS for fewer /// destroys per transpile batch. #[inline] - pub fn reset_retain_with_limit(&mut self, limit: usize) -> bool { + pub fn reset_retain_with_limit(&mut self, limit: usize) -> ResetOutcome { // `borrowing_default()` arenas (`!owns`) wrap `mi_heap_main()`, whose // footprint is the whole process — they always fall through to // `reset()`, which debug-asserts `owns` (recycling the main heap is a @@ -267,10 +274,10 @@ impl MimallocArena { #[cfg(debug_assertions)] self.owning_thread .store(debug_thread_stamp(), Ordering::Relaxed); - return true; + return ResetOutcome::Retained; } self.reset(); - false + ResetOutcome::Recycled } /// `bumpalo::Bump::allocated_bytes` parity — total bytes currently in use diff --git a/src/bun_alloc/lib.rs b/src/bun_alloc/lib.rs index d99683419c75..f2fe9ce6fe61 100644 --- a/src/bun_alloc/lib.rs +++ b/src/bun_alloc/lib.rs @@ -154,7 +154,7 @@ impl StdAllocator { // (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. -pub use mimalloc_arena::MimallocArena; +pub use mimalloc_arena::{MimallocArena, ResetOutcome}; pub type Arena = MimallocArena; mod baby_vec; pub use baby_vec::BabyVec; diff --git a/src/bun_core/Global.rs b/src/bun_core/Global.rs index 56a912aec7f0..5d2d463eb510 100644 --- a/src/bun_core/Global.rs +++ b/src/bun_core/Global.rs @@ -809,11 +809,13 @@ pub struct AllocatorConfiguration { pub long_running: bool, } +crate::bool_enum!(pub Force); + #[inline] -pub fn mimalloc_cleanup(force: bool) { +pub fn mimalloc_cleanup(force: Force) { if USE_MIMALLOC { // `mi_collect` is declared `safe fn` in `bun_mimalloc_sys` (no preconditions). - bun_alloc::mimalloc::mi_collect(force); + bun_alloc::mimalloc::mi_collect(force == Force::Yes); } } // Versions are now handled by build-generated header (bun_dependency_versions.h) diff --git a/src/bun_core/fmt.rs b/src/bun_core/fmt.rs index 381ba04fa6c8..b1fa7693c128 100644 --- a/src/bun_core/fmt.rs +++ b/src/bun_core/fmt.rs @@ -5,6 +5,7 @@ use core::fmt::{self, Display, Formatter, Write as _}; use core::ptr::NonNull; use crate::output as Output; +use crate::string::printer::AsciiOnly; // `strings` is the canonical `crate::strings` (lib.rs); `js_printer`/`js_lexer` // are defined locally below (move-in subset) and re-exported at the crate root. use crate::strings; @@ -37,6 +38,7 @@ pub mod js_lexer { pub mod js_printer { use super::strings::Encoding; + use crate::string::printer::{AsciiOnly, Json}; use core::fmt; /// Minimal escape set for fmt.rs quoting. /// bun_js_printer overrides with the full (ctrl-char, \u escape, encoding-aware) impl. @@ -52,7 +54,7 @@ pub mod js_printer { input: &[u8], f: &mut impl fmt::Write, quote: u8, - ascii_only: bool, + ascii_only: AsciiOnly, enc: Encoding, ) -> fmt::Result { // Writes the escaped body WITHOUT surrounding quotes. Delegate to the @@ -62,7 +64,12 @@ pub mod js_printer { // 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, + input, + &mut buf, + quote, + ascii_only, + Json::Yes, + enc, ) .map_err(|_| fmt::Error)?; f.write_str(&String::from_utf8_lossy(&buf)) @@ -358,8 +365,11 @@ impl Display for IntegrityFormatter { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { const BUF_LEN: usize = SHA512_DIGEST.div_ceil(3) * 4; let mut buf = [0u8; BUF_LEN]; - let count = - bun_simdutf_sys::simdutf::base64::encode(&self.bytes[..SHA512_DIGEST], &mut buf, false); + let count = bun_simdutf_sys::simdutf::base64::encode( + &self.bytes[..SHA512_DIGEST], + &mut buf, + bun_simdutf_sys::simdutf::base64::Alphabet::Standard, + ); let encoded = &buf[..count]; if SHORT { write!( @@ -413,7 +423,13 @@ impl Display for JSONFormatterUTF8<'_> { if self.opts.quote { js_printer::write_json_string(self.input, f, strings::Encoding::Utf8) } else { - js_printer::write_pre_quoted_string(self.input, f, b'"', false, strings::Encoding::Utf8) + js_printer::write_pre_quoted_string( + self.input, + f, + b'"', + AsciiOnly::No, + strings::Encoding::Utf8, + ) } } } diff --git a/src/bun_core/lib.rs b/src/bun_core/lib.rs index f53e3a38fcd4..5ce6067811cd 100644 --- a/src/bun_core/lib.rs +++ b/src/bun_core/lib.rs @@ -35,9 +35,51 @@ pub mod compress { End, Error, } + + crate::bool_enum!( + /// Whether the input handed to a streaming decompressor is the last + /// chunk. With `More`, running out of input yields `ShortRead`; with + /// `Last`, it is a truncated-stream error. + pub Chunk { More, Last } + ); } pub mod heap; +/// Declares a two-variant `Copy` enum to use in place of a `bool` flag, so +/// call sites read `f(x, CheckLen::Yes)` instead of `f(x, true)`. +/// +/// ```ignore +/// bun_core::bool_enum!(pub CheckLen); // CheckLen::{No, Yes} +/// bun_core::bool_enum!(pub LinkKind { Link, Image }); // false-variant first +/// ``` +/// +/// The false-like variant is always discriminant 0 and the `Default`, as with +/// `bool`. `from_bool` exists for flags computed at runtime (parsed options, +/// JS values, FFI); literal call sites should name the variant. There is +/// deliberately no `Into`. +#[macro_export] +macro_rules! bool_enum { + ($(#[$m:meta])* $vis:vis $Name:ident) => { + $crate::bool_enum!($(#[$m])* $vis $Name { No, Yes }); + }; + ($(#[$m:meta])* $vis:vis $Name:ident { $False:ident, $True:ident $(,)? }) => { + $(#[$m])* + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)] + $vis enum $Name { + #[default] + $False, + $True, + } + impl $Name { + #[inline] + #[allow(dead_code)] + pub const fn from_bool(b: bool) -> Self { + if b { Self::$True } else { Self::$False } + } + } + }; +} + pub mod debug; pub mod env; #[cfg(windows)] @@ -55,8 +97,8 @@ pub use ::bstr::{BStr, BString, ByteSlice}; pub use string::string_joiner::StringJoiner; pub use string::{ HashedString, MutableString, NodeEncoding, OwnedString, OwnedStringCell, - SliceWithUnderlyingString, SmolStr, String, StringBuilder, WTFStringImpl, WTFStringImplExt, - WTFStringImplStruct, ZigString, ZigStringSlice, + SliceWithUnderlyingString, SmolStr, String, StringBuilder, WTFEncoding, WTFStringImpl, + WTFStringImplExt, WTFStringImplStruct, ZigString, ZigStringSlice, }; pub use string::{ STRING_ALLOCATION_LIMIT, ZigStringGithubActionFormatter, cheap_prefix_normalizer, @@ -1213,6 +1255,8 @@ pub use crate::string::immutable::{ /// duplicates an `immutable` scanner; when both layers need the same helper, /// the single implementation lives here and `immutable` re-exports it. pub(crate) mod strings_impl { + pub use crate::string::immutable::CheckLen; + // ─── UTF-16 surrogate-pair encoding (ICU U16_LEAD / U16_TRAIL) ───────────── // Defined here in // bun_core (not bun_string) so the WTF-8 fallback transcoder below and any @@ -1319,8 +1363,8 @@ pub(crate) mod strings_impl { /// 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 { - if check_len { + pub fn eql_case_insensitive_ascii(a: &[u8], b: &[u8], check_len: CheckLen) -> bool { + if check_len == CheckLen::Yes { if a.len() != b.len() { return false; } @@ -1362,7 +1406,11 @@ pub(crate) mod strings_impl { } let mut start = 0usize; while start + needle.len() <= haystack.len() { - if eql_case_insensitive_ascii(&haystack[start..start + needle.len()], needle, false) { + if eql_case_insensitive_ascii( + &haystack[start..start + needle.len()], + needle, + CheckLen::No, + ) { return true; } start += 1; @@ -1947,7 +1995,7 @@ pub(crate) mod strings_impl { #[inline] pub fn eql_case_insensitive_ascii_check_length(a: &[u8], b: &[u8]) -> bool { - eql_case_insensitive_ascii(a, b, true) + eql_case_insensitive_ascii(a, b, CheckLen::Yes) } /// Haystacks are 6-12 @@ -1957,7 +2005,7 @@ pub(crate) mod strings_impl { pub fn eql_any_case_insensitive_ascii(needle: &[u8], haystack: &[&[u8]]) -> bool { haystack .iter() - .any(|h| eql_case_insensitive_ascii(needle, h, true)) + .any(|h| eql_case_insensitive_ascii(needle, h, CheckLen::Yes)) } pub(crate) fn starts_with_uuid(s: &[u8]) -> bool { diff --git a/src/bun_core/output.rs b/src/bun_core/output.rs index e0f1aa363edc..d1f83aace4cd 100644 --- a/src/bun_core/output.rs +++ b/src/bun_core/output.rs @@ -183,7 +183,7 @@ pub fn argv() -> impl Iterator { #[inline] pub fn debug_warn(payload: impl PrettyFmtInput) { if crate::env::IS_DEBUG { - let buf = payload.into_pretty_buf(enable_ansi_colors_stderr()); + let buf = payload.into_pretty_buf(AnsiColors::from_bool(enable_ansi_colors_stderr())); pretty_errorln!("debug warn: {}", buf); flush(); } @@ -192,7 +192,7 @@ pub fn debug_warn(payload: impl PrettyFmtInput) { /// `bun.Output.warn` — yellow `warn:` prefix to stderr. #[inline] pub fn warn(payload: impl PrettyFmtInput) { - let buf = payload.into_pretty_buf(enable_ansi_colors_stderr()); + let buf = payload.into_pretty_buf(AnsiColors::from_bool(enable_ansi_colors_stderr())); pretty_errorln!("warn: {}", buf); } @@ -202,7 +202,7 @@ pub fn warn(payload: impl PrettyFmtInput) { /// form: `crate::pretty_errorln!`. #[inline] pub fn pretty_errorln(payload: impl PrettyFmtInput) { - let buf = payload.into_pretty_buf(enable_ansi_colors_stderr()); + let buf = payload.into_pretty_buf(AnsiColors::from_bool(enable_ansi_colors_stderr())); write_bytes(Destination::Stderr, &buf); if buf.0.last() != Some(&b'\n') { write_bytes(Destination::Stderr, b"\n"); @@ -1446,8 +1446,12 @@ impl ScopedLogger { flag[pfx.len()..].copy_from_slice(tag); let flag_slice = flag.as_slice(); for arg in argv() { - if strings::eql_case_insensitive_ascii(arg, flag_slice, true) - || strings::eql_case_insensitive_ascii(arg, b"--debug-all", true) + if strings::eql_case_insensitive_ascii(arg, flag_slice, strings::CheckLen::Yes) + || strings::eql_case_insensitive_ascii( + arg, + b"--debug-all", + strings::CheckLen::Yes, + ) { self.really_disable.store(false, Ordering::Relaxed); break; @@ -1674,7 +1678,7 @@ pub use bun_output_tags::{ansi, ansi_b}; /// keeps it next to the call site (and the body is two calls — cheap). #[inline(always)] pub fn pretty(payload: impl PrettyFmtInput) { - let buf = payload.into_pretty_buf(enable_ansi_colors_stdout()); + let buf = payload.into_pretty_buf(AnsiColors::from_bool(enable_ansi_colors_stdout())); write_bytes(Destination::Stdout, &buf); } @@ -1685,7 +1689,7 @@ pub fn pretty(payload: impl PrettyFmtInput) { /// `inline(always)` for the same .text-layout reason as [`pretty`]. #[inline(always)] pub fn prettyln(payload: impl PrettyFmtInput) { - let buf = payload.into_pretty_buf(enable_ansi_colors_stdout()); + let buf = payload.into_pretty_buf(AnsiColors::from_bool(enable_ansi_colors_stdout())); write_bytes(Destination::Stdout, &buf); if buf.0.last() != Some(&b'\n') { write_bytes(Destination::Stdout, b"\n"); @@ -1704,28 +1708,33 @@ pub fn prettyln(payload: impl PrettyFmtInput) { /// tested against it). pub use bun_core_macros::pretty_fmt; +crate::bool_enum!( + /// Whether `` markers are rewritten to ANSI escapes or stripped. + pub AnsiColors { Disabled, Enabled } +); + /// Input accepted by [`pretty_fmt`]: either a `&str`/`&[u8]` template or a /// pre-formatted `&fmt::Arguments<'_>` (which is first rendered to a string /// then ``-rewritten — used by `Custom Inspect`-style call sites that /// build the template via `format_args!`). pub trait PrettyFmtInput { - fn into_pretty_buf(self, is_enabled: bool) -> PrettyBuf; + fn into_pretty_buf(self, is_enabled: AnsiColors) -> PrettyBuf; } impl PrettyFmtInput for &str { #[inline] - fn into_pretty_buf(self, is_enabled: bool) -> PrettyBuf { + fn into_pretty_buf(self, is_enabled: AnsiColors) -> PrettyBuf { PrettyBuf(pretty_fmt_runtime(self.as_bytes(), is_enabled)) } } impl PrettyFmtInput for &[u8] { #[inline] - fn into_pretty_buf(self, is_enabled: bool) -> PrettyBuf { + fn into_pretty_buf(self, is_enabled: AnsiColors) -> PrettyBuf { PrettyBuf(pretty_fmt_runtime(self, is_enabled)) } } impl PrettyFmtInput for &fmt::Arguments<'_> { #[inline] - fn into_pretty_buf(self, is_enabled: bool) -> PrettyBuf { + fn into_pretty_buf(self, is_enabled: AnsiColors) -> PrettyBuf { // Render the `Arguments` first, then rewrite `` markers. let rendered = std::format!("{}", self); PrettyBuf(pretty_fmt_runtime(rendered.as_bytes(), is_enabled)) @@ -1733,7 +1742,7 @@ impl PrettyFmtInput for &fmt::Arguments<'_> { } impl PrettyFmtInput for fmt::Arguments<'_> { #[inline] - fn into_pretty_buf(self, is_enabled: bool) -> PrettyBuf { + fn into_pretty_buf(self, is_enabled: AnsiColors) -> PrettyBuf { (&self).into_pretty_buf(is_enabled) } } @@ -1746,13 +1755,13 @@ impl PrettyFmtInput for fmt::Arguments<'_> { /// [`pretty_fmt_rt`]. #[inline] pub fn pretty_fmt(input: impl PrettyFmtInput) -> PrettyBuf { - input.into_pretty_buf(ENABLE_ANSI_COLORS) + input.into_pretty_buf(AnsiColors::from_bool(ENABLE_ANSI_COLORS)) } /// Runtime-bool form of [`pretty_fmt`] for call sites that don't have a /// const-generic colour flag (crash handler, dynamic templates). #[inline] -pub fn pretty_fmt_rt(input: impl PrettyFmtInput, is_enabled: bool) -> PrettyBuf { +pub fn pretty_fmt_rt(input: impl PrettyFmtInput, is_enabled: AnsiColors) -> PrettyBuf { input.into_pretty_buf(is_enabled) } @@ -1951,13 +1960,13 @@ impl fmt::Display for TemplateDisplay<'_, A> { /// Runtime `` → ANSI rewrite *with* a positional-argument tuple /// substituted at each `{}` / `{s}` / `{d}` placeholder. Returns a `Display` -/// impl so callers can `write!(w, "{}", pretty_fmt_args(fmt, true, (a, b)))`. +/// impl so callers can `write!(w, "{}", pretty_fmt_args(fmt, AnsiColors::Enabled, (a, b)))`. /// /// Port of `Output.prettyFmt` + `print` fused for the dynamic-template case /// (crash_handler builds the template at runtime). pub(crate) fn pretty_fmt_args( fmt: &str, - is_enabled: bool, + is_enabled: AnsiColors, args: A, ) -> TemplateDisplay<'static, A> { TemplateDisplay { @@ -1973,7 +1982,7 @@ pub(crate) fn pretty_fmt_args( /// vs `bun_core_macros::rewrite` because the two intentionally diverge in the /// `{` 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 { +pub fn pretty_fmt_runtime(fmt: &[u8], is_enabled: AnsiColors) -> Vec { let mut out = Vec::with_capacity(fmt.len() * 4); let mut i = 0usize; while i < fmt.len() { @@ -2027,7 +2036,7 @@ pub fn pretty_fmt_runtime(fmt: &[u8], is_enabled: bool) -> Vec { break 'picker ""; } }; - if is_enabled { + if is_enabled == AnsiColors::Enabled { out.extend_from_slice(if is_reset { RESET.as_bytes() } else { @@ -2419,7 +2428,11 @@ pub fn err(error_name: impl ErrName, fmt: &str, args: impl FmtTuple) { // `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); + let body = pretty_fmt_args( + fmt, + AnsiColors::from_bool(enable_ansi_colors_stderr()), + args, + ); if let Some(e) = error_name.as_sys_err_info() { // MOVE_DOWN: bun_sys::coreutils_error_map → bun_core (move-in pass). if let Some(label) = crate::coreutils_error_map::get(e.errno) { @@ -2464,7 +2477,11 @@ pub fn err_generic(fmt: &str, args: impl FmtTuple) { let fmt = fmt.strip_suffix('\n').unwrap_or(fmt); pretty_errorln!( "error: {}", - pretty_fmt_args(fmt, enable_ansi_colors_stderr(), args), + pretty_fmt_args( + fmt, + AnsiColors::from_bool(enable_ansi_colors_stderr()), + args + ), ); } @@ -2900,7 +2917,7 @@ mod pretty_fmt_tests { //! Parity checks between the `pretty_fmt!` proc-macro (compile-time) and //! `pretty_fmt_runtime`. Guards against the //! macro leaking raw ``/`` markup into help text. - use super::{RESET, pretty_fmt_runtime}; + use super::{AnsiColors, RESET, pretty_fmt_runtime}; use bun_core_macros::pretty_fmt; #[test] @@ -2959,12 +2976,16 @@ mod pretty_fmt_tests { fn pretty_fmt_args_preserves_multibyte_utf8() { let s = format!( "{}", - super::pretty_fmt_args(" {s} → {s}\n", false, ("a", "b")) + super::pretty_fmt_args( + " {s} → {s}\n", + AnsiColors::Disabled, + ("a", "b") + ) ); assert_eq!(s, "↑ a → b\n"); let s = format!( "{}", - super::pretty_fmt_args(" {s} →", true, ("a",)) + super::pretty_fmt_args(" {s} →", AnsiColors::Enabled, ("a",)) ); assert_eq!(s, "\x1b[36m↑\x1b[0m a →\x1b[0m"); } @@ -2997,13 +3018,13 @@ mod pretty_fmt_tests { ($s:literal) => {{ assert_eq!( pretty_fmt!($s, true).as_bytes(), - pretty_fmt_runtime($s.as_bytes(), true).as_slice(), + pretty_fmt_runtime($s.as_bytes(), AnsiColors::Enabled).as_slice(), "enabled mismatch for {:?}", $s, ); assert_eq!( pretty_fmt!($s, false).as_bytes(), - pretty_fmt_runtime($s.as_bytes(), false).as_slice(), + pretty_fmt_runtime($s.as_bytes(), AnsiColors::Disabled).as_slice(), "disabled mismatch for {:?}", $s, ); diff --git a/src/bun_core/string/immutable.rs b/src/bun_core/string/immutable.rs index c3df7ef4c337..55722abc0488 100644 --- a/src/bun_core/string/immutable.rs +++ b/src/bun_core/string/immutable.rs @@ -5,6 +5,7 @@ use core::cmp::Ordering; use crate::BoundedArray; use crate::CrateError as Error; +use crate::string::printer::{AsciiOnly, Json}; use bun_alloc::AllocError; use bun_highway as highway; use bun_simdutf_sys::simdutf; @@ -49,6 +50,7 @@ pub use unicode_draft::{ /// lives in C++ (`src/jsc/bindings/stringWidth.cpp`); this module is the FFI /// surface for the remaining Rust callers. pub use visible_impl::visible; +pub use visible_impl::visible::width::exclude_ansi_colors::AmbiguousWidth; /// `unicode` surface needed by `immutable.rs` itself (CodepointIterator + /// WTF-8 decode). Full transcoding suite lives in `unicode_draft`. @@ -494,7 +496,7 @@ pub(crate) use crate::strings_impl::{ pub fn index_equal_any(in_: &[&[u8]], target: &[u8]) -> Option { for (i, str) in in_.iter().enumerate() { - if eql_long(str, target, true) { + if eql_long(str, target, CheckLen::Yes) { return Some(i); } } @@ -939,7 +941,7 @@ pub fn starts_with(self_: &[u8], str: &[u8]) -> bool { if str.len() > self_.len() { return false; } - eql_long(&self_[0..str.len()], str, false) + eql_long(&self_[0..str.len()], str, CheckLen::No) } /// Transliterated from: @@ -976,7 +978,7 @@ pub fn is_utf8_char_boundary(c: u8) -> bool { pub fn starts_with_case_insensitive_ascii(self_: &[u8], prefix: &[u8]) -> bool { self_.len() >= prefix.len() - && eql_case_insensitive_ascii(&self_[0..prefix.len()], prefix, false) + && eql_case_insensitive_ascii(&self_[0..prefix.len()], prefix, CheckLen::No) } pub use crate::strings_impl::{ @@ -1052,7 +1054,7 @@ pub fn eql(self_: &[u8], other: &[u8]) -> bool { if self_.len() != other.len() { return false; } - eql_long(self_, other, false) + eql_long(self_, other, CheckLen::No) } pub fn eql_comptime(self_: &[u8], alt: &'static [u8]) -> bool { @@ -1143,7 +1145,7 @@ pub(crate) fn eql_comptime_check_len_with_type bool { - eql_case_insensitive_ascii(a, b, false) + eql_case_insensitive_ascii(a, b, CheckLen::No) } pub use crate::strings_impl::{ @@ -1154,13 +1156,13 @@ pub use crate::strings_impl::{ /// 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) + eql_case_insensitive_ascii(a, b, CheckLen::Yes) } // The libc `strncasecmp`-backed implementation lives in tier-0 // `crate::strings_impl` (so `contains_case_insensitive_ascii` and friends can // reach it). `check_len` is a runtime 3rd arg because that's the dominant -// call shape across the tree (`eql_case_insensitive_ascii(a, b, true)`); +// call shape across the tree (`eql_case_insensitive_ascii(a, b, CheckLen::Yes)`); // callers wanting the length-agnostic forms have the `_check_length` / // `_ignore_length` wrappers above. pub use crate::strings_impl::{contains_case_insensitive_ascii, eql_case_insensitive_ascii}; @@ -1207,13 +1209,15 @@ pub fn has_prefix_case_insensitive(str: &[u8], prefix: &[u8]) -> bool { has_prefix_case_insensitive_t(str, prefix) } +crate::bool_enum!(pub CheckLen); + // 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)`). +// 3rd arg to match the dominant call shape (`eql_long(a, b, CheckLen::Yes)`). #[inline] -pub fn eql_long(a_str: &[u8], b_str: &[u8], check_len: bool) -> bool { +pub fn eql_long(a_str: &[u8], b_str: &[u8], check_len: CheckLen) -> bool { let len = b_str.len(); - if check_len { + if check_len == CheckLen::Yes { if len == 0 { return a_str.is_empty(); } @@ -1975,7 +1979,7 @@ pub const UNICODE_REPLACEMENT: u32 = 0xFFFD; pub fn left_has_any_in_right(to_check: &[&[u8]], against: &[&[u8]]) -> bool { for check in to_check { for item in against { - if eql_long(check, item, true) { + if eql_long(check, item, CheckLen::Yes) { return true; } } @@ -2056,8 +2060,8 @@ pub fn must_escape_yaml_string(contents: &[u8]) -> bool { #[derive(Copy, Clone)] pub struct QuoteEscapeFormatFlags { pub quote_char: u8, - pub ascii_only: bool, - pub json: bool, + pub ascii_only: AsciiOnly, + pub json: Json, pub str_encoding: Encoding, } @@ -2065,8 +2069,8 @@ impl Default for QuoteEscapeFormatFlags { fn default() -> Self { Self { quote_char: b'"', - ascii_only: false, - json: false, + ascii_only: AsciiOnly::No, + json: Json::No, str_encoding: Encoding::Utf8, } } @@ -2093,9 +2097,9 @@ impl core::fmt::Display for QuoteEscapeFormat<'_> { self.data, &mut buf, self.flags.quote_char, - // Hardcoded `false` regardless of + // Hardcoded `AsciiOnly::No` regardless of // `flags.ascii_only`; the field is dead in QuoteEscapeFormat. - false, + AsciiOnly::No, self.flags.json, self.flags.str_encoding, ) @@ -2356,12 +2360,13 @@ pub fn utf16_eql_string(text: &[u16], str: &[u8]) -> bool { /// returning `None`). pub fn to_utf16_alloc_for_real( bytes: &[u8], - fail_if_invalid: bool, - sentinel: bool, + fail_if_invalid: FailIfInvalid, + sentinel: Sentinel, ) -> Result, ToUTF16Error> { if let Some(v) = to_utf16_alloc(bytes, fail_if_invalid, sentinel)? { return Ok(v); } + let sentinel = sentinel == Sentinel::Yes; // All-ASCII path: widen each byte. let mut out: Vec = Vec::new(); out.try_reserve_exact(bytes.len() + sentinel as usize) @@ -2539,7 +2544,7 @@ pub fn to_utf8_list_with_type(mut list: Vec, utf16: &[u16]) -> Result for crate::CrateError { } } +crate::bool_enum!(pub FailIfInvalid); +crate::bool_enum!(pub Sentinel); + /// `strings.toUTF16Alloc` — convert UTF-8 → UTF-16LE **iff** `bytes` contains /// 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 @@ -2562,12 +2570,14 @@ impl From for crate::CrateError { /// includes a trailing 0 u16. pub fn to_utf16_alloc( bytes: &[u8], - fail_if_invalid: bool, - sentinel: bool, + fail_if_invalid: FailIfInvalid, + sentinel: Sentinel, ) -> Result>, ToUTF16Error> { let Some(_first) = first_non_ascii(bytes) else { return Ok(None); }; + let fail_if_invalid = fail_if_invalid == FailIfInvalid::Yes; + let sentinel = sentinel == Sentinel::Yes; let out_length = simdutf::length::utf16::from::utf8(bytes); let cap = out_length + if sentinel { 1 } else { 0 }; @@ -2717,8 +2727,16 @@ mod tests { fn strings_reexport_wrappers_terminate() { assert_eq!(super::first_non_ascii(b"abc"), None); assert_eq!(super::first_non_ascii(b"ab\xC3"), Some(2)); - assert!(super::eql_case_insensitive_ascii(b"A", b"a", true)); - assert!(!super::eql_case_insensitive_ascii(b"Ab", b"a", true)); + assert!(super::eql_case_insensitive_ascii( + b"A", + b"a", + super::CheckLen::Yes + )); + assert!(!super::eql_case_insensitive_ascii( + b"Ab", + b"a", + super::CheckLen::Yes + )); } #[test] diff --git a/src/bun_core/string/immutable/visible.rs b/src/bun_core/string/immutable/visible.rs index acab202f34b9..8950280d51d3 100644 --- a/src/bun_core/string/immutable/visible.rs +++ b/src/bun_core/string/immutable/visible.rs @@ -9,6 +9,8 @@ pub mod visible { pub mod width { pub mod exclude_ansi_colors { + crate::bool_enum!(pub AmbiguousWidth { Narrow, Wide }); + unsafe extern "C" { fn Bun__visibleWidthExcludeANSI_latin1( ptr: *const u8, @@ -30,13 +32,13 @@ pub mod visible { /// Visible terminal width of Latin-1 bytes, treating ANSI escape /// sequences as zero-width. - pub(crate) fn latin1(input: &[u8], ambiguous_as_wide: bool) -> usize { + pub(crate) fn latin1(input: &[u8], ambiguous_as_wide: AmbiguousWidth) -> usize { // SAFETY: `input` is a live slice for the duration of the call. unsafe { Bun__visibleWidthExcludeANSI_latin1( input.as_ptr(), input.len(), - ambiguous_as_wide, + ambiguous_as_wide == AmbiguousWidth::Wide, ) } } @@ -50,13 +52,13 @@ pub mod visible { /// Visible terminal width of a UTF-16 string, treating ANSI escape /// sequences as zero-width. - pub(crate) fn utf16(input: &[u16], ambiguous_as_wide: bool) -> usize { + pub(crate) fn utf16(input: &[u16], ambiguous_as_wide: AmbiguousWidth) -> usize { // SAFETY: `input` is a live slice for the duration of the call. unsafe { Bun__visibleWidthExcludeANSI_utf16( input.as_ptr(), input.len(), - ambiguous_as_wide, + ambiguous_as_wide == AmbiguousWidth::Wide, ) } } diff --git a/src/bun_core/string/mod.rs b/src/bun_core/string/mod.rs index 2e7a65b21b08..9b4e490f1e76 100644 --- a/src/bun_core/string/mod.rs +++ b/src/bun_core/string/mod.rs @@ -95,6 +95,11 @@ unsafe extern "C" { fn BunString__createExternalGloballyAllocatedUTF16(bytes: *mut u16, len: usize) -> String; } +crate::bool_enum!( + /// Character width of the buffer handed to `create_external*`. + pub WTFEncoding { Utf16, Latin1 } +); + /// `ctx` is the pointer passed into `create_external`; `buffer` is the /// `[*]u8`/`[*]u16` storage; `len` is the character count. /// @@ -282,7 +287,7 @@ impl String { /// the const-assert below to keep the C-ABI cast sound. pub fn create_external( bytes: &[u8], - is_latin1: bool, + encoding: WTFEncoding, ctx: Ctx, callback: ExternalStringImplFreeFunction, ) -> Self { @@ -328,7 +333,7 @@ impl String { BunString__createExternal( bytes.as_ptr(), bytes.len(), - is_latin1, + encoding == WTFEncoding::Latin1, ctx_erased, cb_erased, ) @@ -349,11 +354,17 @@ 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. - pub fn create_static_external(bytes: &[u8], is_latin1: bool) -> Self { + pub fn create_static_external(bytes: &[u8], encoding: WTFEncoding) -> Self { debug_assert!(!bytes.is_empty()); // SAFETY: bytes describes a valid slice; C++ side stores ptr/len // without copying and never frees it. - unsafe { BunString__createStaticExternal(bytes.as_ptr(), bytes.len(), is_latin1) } + unsafe { + BunString__createStaticExternal( + bytes.as_ptr(), + bytes.len(), + encoding == WTFEncoding::Latin1, + ) + } } /// `bun.String.createFormat` — formats `args` into a temporary buffer and /// copies the result into a fresh WTF-backed string. @@ -851,7 +862,10 @@ impl String { /// `bun.String.visibleWidthExcludeANSIColors` — terminal column width of /// `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 { + pub fn visible_width_exclude_ansi_colors( + &self, + ambiguous_as_wide: strings::AmbiguousWidth, + ) -> usize { use crate::strings::visible::width::exclude_ansi_colors as w; if self.is_utf16() { return w::utf16(self.utf16(), ambiguous_as_wide); @@ -2140,8 +2154,12 @@ pub mod printer { /// `MutableString`, and any other `crate::io::Write` sink. pub use crate::io::Write as PrinterWriter; + crate::bool_enum!(pub AsciiOnly); + + crate::bool_enum!(pub Json); + #[inline] - fn can_print_without_escape(c: i32, ascii_only: bool) -> bool { + fn can_print_without_escape(c: i32, ascii_only: AsciiOnly) -> bool { if c <= LAST_ASCII as i32 { c >= FIRST_ASCII as i32 && c != b'\\' as i32 @@ -2150,7 +2168,7 @@ pub mod printer { && c != b'`' as i32 && c != b'$' as i32 } else { - !ascii_only + ascii_only == AsciiOnly::No && c != 0xFEFF && c != 0x2028 && c != 0x2029 @@ -2165,10 +2183,11 @@ pub mod printer { text_in: &[u8], writer: &mut W, quote_char: u8, - ascii_only: bool, - json: bool, + ascii_only: AsciiOnly, + json: Json, encoding: StrEncoding, ) -> crate::CrateResult<()> { + let json = json == Json::Yes; debug_assert!(!json || quote_char == b'"'); // utf16 view over the same bytes (only used when encoding == Utf16). // Callers pass 2-byte-aligned even-length input for Utf16; `cast_slice` @@ -2309,11 +2328,11 @@ pub mod printer { pub fn quote_for_json( text: &[u8], bytes: &mut MutableString, - ascii_only: bool, + ascii_only: AsciiOnly, ) -> crate::CrateResult<()> { // 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)?; + write_pre_quoted_string(text, bytes, b'"', ascii_only, Json::Yes, StrEncoding::Utf8)?; bytes.append_char(b'"').expect("unreachable"); Ok(()) } diff --git a/src/bun_core/util.rs b/src/bun_core/util.rs index 3908c9d40229..b99943640e6a 100644 --- a/src/bun_core/util.rs +++ b/src/bun_core/util.rs @@ -3605,7 +3605,7 @@ pub mod base64 { /// `bun.base64.encode` — standard alphabet, padded. Returns bytes written. pub fn encode(dest: &mut [u8], source: &[u8]) -> usize { debug_assert!(dest.len() >= encode_len(source)); - simdutf::base64::encode(source, dest, false) + simdutf::base64::encode(source, dest, simdutf::base64::Alphabet::Standard) } /// Encoded output size for a standard-base64 input of `source_len` bytes. @@ -4383,10 +4383,14 @@ pub fn maybe_handle_panic_during_process_reload() { } } -/// Port of `bun.reloadProcess`. `may_return == true` → returns on failure; `false` → panics. +crate::bool_enum!(pub ClearTerminal); +crate::bool_enum!(pub MayReturn); + +/// Port of `bun.reloadProcess`. `may_return == MayReturn::Yes` → returns on failure; `No` → panics. /// `on_before_reload_process_posix` clears CLOEXEC on stdio/IPC and resets caught signal /// dispositions on all POSIX; the close_range sweep is Linux/BSD only. -pub fn reload_process(clear_terminal: bool, may_return: bool) { +pub fn reload_process(clear_terminal: ClearTerminal, may_return: MayReturn) { + let may_return = may_return == MayReturn::Yes; // Exactly one thread may perform the reload: the JS thread and the watcher's grace-window // fallback can both reach here, and concurrent execve prep crashes on musl. The swap elects a // winner; a loser parks or returns. A thread that already owns the reload may re-enter. @@ -4404,7 +4408,7 @@ pub fn reload_process(clear_terminal: bool, may_return: bool) { } RELOAD_IN_PROGRESS_ON_CURRENT_THREAD.with(|c| c.set(true)); - if clear_terminal { + if clear_terminal == ClearTerminal::Yes { crate::output::flush(); crate::output::disable_buffering(); crate::output::reset_terminal_all(); diff --git a/src/bundler/AstBuilder.rs b/src/bundler/AstBuilder.rs index 72b55ea7b72a..0187c0881487 100644 --- a/src/bundler/AstBuilder.rs +++ b/src/bundler/AstBuilder.rs @@ -48,10 +48,12 @@ pub(crate) struct AstBuilder<'a, 'bump> { pub(crate) module_ref: Ref, pub(crate) declared_symbols: DeclaredSymbolList, /// When set, codegen is altered - pub(crate) hot_reloading: bool, + pub(crate) hot_reloading: HotReloading, pub(crate) hmr_api_ref: Ref, } +bun_core::bool_enum!(pub(crate) HotReloading); + // stub fields for ImportScanner duck typing // // The scanner transform is open-coded in `to_bundled_ast` for the stmt shapes @@ -61,7 +63,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { pub(crate) fn init( bump: &'bump Bump, source: &'a Source, - hot_reloading: bool, + hot_reloading: HotReloading, ) -> Result { let scope: *mut Scope = bump.alloc(Scope { kind: ScopeKind::Entry, @@ -180,7 +182,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { { let import_id: &[u8] = *import_id; // must be given '[N][]const u8' let ref_ = self.new_symbol(SymbolKind::Import, import_id)?; - if self.hot_reloading { + if self.hot_reloading == HotReloading::Yes { self.get_symbol(ref_).namespace_alias = Some(bun_alloc::ast_box(G::NamespaceAlias { namespace_ref, @@ -317,7 +319,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { // builder goes out of scope (UAF in the printer). Copy the `Copy` // `Stmt`s/`*mut Scope`s into the bump arena so the returned // `BundledAst` owns them with parser-arena lifetime. - if self.hot_reloading { + if self.hot_reloading == HotReloading::Yes { // get a estimate on how many statements there are going to be let prealloc_count = self.stmts.len() + 2; let mut hmr_stmts: Vec = Vec::with_capacity(prealloc_count); diff --git a/src/bundler/BundleThread.rs b/src/bundler/BundleThread.rs index cef2a0487354..0f24b0a973cf 100644 --- a/src/bundler/BundleThread.rs +++ b/src/bundler/BundleThread.rs @@ -303,7 +303,10 @@ 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 + // logger OOM-only + let _ = unsafe { + (*(*transpiler_ptr).log).append_to_with_recycled(&mut out_log, bun_ast::Recycled::Yes) + }; completion.set_log(out_log); if run.is_ok() { diff --git a/src/bundler/HTMLScanner.rs b/src/bundler/HTMLScanner.rs index 88cdf4323b10..afa81cbda5b4 100644 --- a/src/bundler/HTMLScanner.rs +++ b/src/bundler/HTMLScanner.rs @@ -1,4 +1,5 @@ use core::marker::PhantomData; +use core::ops::ControlFlow; use std::borrow::Cow; use crate::Error; @@ -134,13 +135,13 @@ pub(crate) trait HTMLProcessorHandler { // 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 Element<'_, '_>) -> bool { + fn on_body_tag(&mut self, _element: &mut Element<'_, '_>) -> ControlFlow<()> { unreachable!() } - fn on_head_tag(&mut self, _element: &mut Element<'_, '_>) -> bool { + fn on_head_tag(&mut self, _element: &mut Element<'_, '_>) -> ControlFlow<()> { unreachable!() } - fn on_html_tag(&mut self, _element: &mut Element<'_, '_>) -> bool { + fn on_html_tag(&mut self, _element: &mut Element<'_, '_>) -> ControlFlow<()> { unreachable!() } } @@ -317,7 +318,7 @@ impl _ => (*this_ptr).on_html_tag(element), } }; - if stop { + if stop.is_break() { // The exact text lol-html's C API attached to a // LOL_HTML_STOP directive (c-api/rewriter_builder.rs). Err("The rewriter has been stopped.".into()) diff --git a/src/bundler/LinkerContext.rs b/src/bundler/LinkerContext.rs index bc47073ee17d..30adb2b58c27 100644 --- a/src/bundler/LinkerContext.rs +++ b/src/bundler/LinkerContext.rs @@ -365,6 +365,8 @@ use crate::bundled_ast::Flags as AstFlags; use crate::generic_path_with_pretty_initialized; type DeclaredSymbolList = bun_ast::DeclaredSymbolList; +bun_core::bool_enum!(pub(crate) CanHaveShifts); + impl<'a> LinkerContext<'a> { pub(crate) fn arena(&self) -> &Bump { // LinkerGraph owns (a backref to) the bundle arena; see `LinkerGraph::arena`. @@ -1036,7 +1038,7 @@ impl<'a> LinkerContext<'a> { _worker: &mut crate::thread_pool::Worker, results: &MultiArrayList, chunk_abs_dir: &[u8], - can_have_shifts: bool, + can_have_shifts: CanHaveShifts, ) -> Result { let _trace = bun::perf::trace("Bundler.generateSourceMapForChunk"); @@ -1076,7 +1078,7 @@ impl<'a> LinkerContext<'a> { }; let mut quote_buf = MutableString::init(pretty.len() + 2)?; - js_printer::quote_for_json(pretty, &mut quote_buf, false)?; + js_printer::quote_for_json(pretty, &mut quote_buf, js_printer::AsciiOnly::No)?; // `to_default_owned` moves the buffer into the joiner // (joiner owns it until `done`). j.push_owned(quote_buf.to_default_owned()); @@ -1104,7 +1106,7 @@ impl<'a> LinkerContext<'a> { let mut quote_buf = MutableString::init(pretty.len() + ", ".len() + 2)?; quote_buf.append_assume_capacity(b", "); - js_printer::quote_for_json(pretty, &mut quote_buf, false)?; + js_printer::quote_for_json(pretty, &mut quote_buf, js_printer::AsciiOnly::No)?; j.push_owned(quote_buf.to_default_owned()); } } @@ -1192,7 +1194,7 @@ impl<'a> LinkerContext<'a> { debug_assert!(done[0] == b'{'); let mut pieces = SourceMapPieces::init(); - if can_have_shifts { + if can_have_shifts == CanHaveShifts::Yes { pieces.prefix.extend_from_slice(&done[0..mapping_start]); pieces .mappings @@ -1543,7 +1545,11 @@ impl SourceMapData { ); buf.push(b'"'); js_printer::write_pre_quoted_string_inner::<_, { js_printer::Encoding::Utf8 }>( - contents, &mut buf, b'"', false, true, + contents, + &mut buf, + b'"', + js_printer::AsciiOnly::No, + js_printer::Json::Yes, ) .expect("OOM"); buf.push(b'"'); @@ -2309,8 +2315,9 @@ impl<'a> LinkerContext<'a> { pub(crate) fn require_or_import_meta_for_source( &mut self, source_index: crate::IndexInt, - was_unwrapped_require: bool, + was_unwrapped_require: js_printer::WasUnwrappedRequire, ) -> js_printer::RequireOrImportMeta { + let was_unwrapped_require = was_unwrapped_require == js_printer::WasUnwrappedRequire::Yes; let flags = self.graph.meta.items_flags()[source_index as usize]; js_printer::RequireOrImportMeta { exports_ref: if flags.wrap == WrapKind::Esm @@ -2565,7 +2572,7 @@ impl<'a> js_printer::RequireOrImportMetaSource for LinkerContext<'a> { fn require_or_import_meta_for_source( &mut self, id: u32, - was_unwrapped_require: bool, + was_unwrapped_require: js_printer::WasUnwrappedRequire, ) -> js_printer::RequireOrImportMeta { LinkerContext::require_or_import_meta_for_source(self, id, was_unwrapped_require) } diff --git a/src/bundler/OutputFile.rs b/src/bundler/OutputFile.rs index 995b3096d36a..e7a63800794d 100644 --- a/src/bundler/OutputFile.rs +++ b/src/bundler/OutputFile.rs @@ -168,7 +168,7 @@ impl Value { // latin1 = true. BunString::create_external::<*mut c_void>( bytes, - true, + bun_core::WTFEncoding::Latin1, core::ptr::null_mut::(), noop, ) diff --git a/src/bundler/ParseTask.rs b/src/bundler/ParseTask.rs index a4b7cc2acbaf..c3bc9e4b21d5 100644 --- a/src/bundler/ParseTask.rs +++ b/src/bundler/ParseTask.rs @@ -10,7 +10,7 @@ use core::sync::atomic::{AtomicU32, Ordering}; use crate::Error as AnyError; use bun_alloc::Arena as Bump; // bumpalo::Bump re-export use bun_ast::ImportRecord; -use bun_ast::{Loc, Location, Log, Msg, Source}; +use bun_ast::{Loc, Location, Log, Msg, Recycled, Source}; use bun_collections::VecExt; use bun_core::strings; use bun_core::{self, FeatureFlags, declare_scope, scoped_log}; @@ -31,6 +31,7 @@ use crate::bun_css; use crate::bun_fs as Fs; use crate::bun_node_fallbacks as NodeFallbackModules; use crate::bundle_v2::{self as bundler, BundleV2}; +use crate::bundled_ast::ForceInline; use crate::cache::{Entry as CacheEntry, ExternalFreeFunction}; use crate::html_scanner::HTMLScanner; use crate::options::{self, Loader}; @@ -786,8 +787,8 @@ pub mod parse_worker { // below); reshape as a closure so every `?` exits through one // post-amble that flushes `temp_log`. let result = (|| -> core::result::Result, AnyError> { - let root: Expr = - bun_parsers::toml::TOML::parse(source, &mut temp_log, bump, false)?; + use bun_parsers::toml::{RedactLogs, TOML}; + let root: Expr = TOML::parse(source, &mut temp_log, bump, RedactLogs::No)?; Ok(JSAst::init( js_parser::new_lazy_export_ast( bump, @@ -801,7 +802,7 @@ pub mod parse_worker { .ok_or(AnyError::ParserError)?, )) })(); - let _ = temp_log.clone_to_with_recycled(log, true); + let _ = temp_log.clone_to_with_recycled(log, Recycled::Yes); return result; } Loader::Yaml => { @@ -827,7 +828,7 @@ pub mod parse_worker { .ok_or(AnyError::ParserError)?, )) })(); - let _ = temp_log.clone_to_with_recycled(log, true); + let _ = temp_log.clone_to_with_recycled(log, Recycled::Yes); return result; } Loader::Json5 => { @@ -849,7 +850,7 @@ pub mod parse_worker { .ok_or(AnyError::ParserError)?, )) })(); - let _ = temp_log.clone_to_with_recycled(log, true); + let _ = temp_log.clone_to_with_recycled(log, Recycled::Yes); return result; } Loader::Xml => { @@ -880,7 +881,7 @@ pub mod parse_worker { .ok_or(AnyError::ParserError)?, )) })(); - let _ = temp_log.clone_to_with_recycled(log, true); + let _ = temp_log.clone_to_with_recycled(log, Recycled::Yes); return result; } Loader::Text => { @@ -908,7 +909,7 @@ pub mod parse_worker { source, Some(b"text/plain"), None, - topts.compile_mode.is_standalone_html(), + ForceInline::from_bool(topts.compile_mode.is_standalone_html()), ); return Ok(ast); } @@ -949,7 +950,7 @@ pub mod parse_worker { source, Some(b"text/html"), None, - topts.compile_mode.is_standalone_html(), + ForceInline::from_bool(topts.compile_mode.is_standalone_html()), ); return Ok(ast); } @@ -1368,7 +1369,7 @@ pub mod parse_worker { source, None, Some(unique_key), - topts.compile_mode.is_standalone_html(), + ForceInline::from_bool(topts.compile_mode.is_standalone_html()), ); return Ok(ast); } @@ -1467,7 +1468,7 @@ pub mod parse_worker { fs_ref, file_path.text, contents_dir, - false, + _resolver::fs::UseSharedBuffer::No, contents_file.unwrap_valid(), read_arena, ) { diff --git a/src/bundler/ServerComponentParseTask.rs b/src/bundler/ServerComponentParseTask.rs index b2f10b316297..decffba758fc 100644 --- a/src/bundler/ServerComponentParseTask.rs +++ b/src/bundler/ServerComponentParseTask.rs @@ -16,7 +16,7 @@ use bun_ast::{B, Binding, E, G, S, Stmt, symbol}; use bun_ast::{ExprNodeList, LocRef, StmtOrExpr, UseDirective}; use bun_ast::{ImportKind, ImportRecordFlags}; -use crate::AstBuilder::AstBuilder; +use crate::AstBuilder::{AstBuilder, HotReloading}; use crate::JSAst; use crate::Worker; use crate::bundle_v2::BundleV2; @@ -174,7 +174,11 @@ fn task_callback( // 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)?; + let mut ab = AstBuilder::init( + bump, + &source, + HotReloading::from_bool(ctx.transpiler().options.hot_module_reloading), + )?; match &task.data { Data::ClientReferenceProxy(data) => generate_client_reference_proxy(ctx, data, &mut ab)?, diff --git a/src/bundler/ThreadPool.rs b/src/bundler/ThreadPool.rs index 02315b0e9e4a..961a1783d81e 100644 --- a/src/bundler/ThreadPool.rs +++ b/src/bundler/ThreadPool.rs @@ -159,6 +159,8 @@ mod io_thread_pool { } } +bun_core::bool_enum!(IsInsideThreadPool); + impl ThreadPool { /// Inherent associated type so call sites that wrote /// `ThreadPool::Worker::get(ctx)` @@ -286,7 +288,7 @@ impl ThreadPool { unsafe fn schedule_with_options( &self, parse_task: *mut ParseTask, - is_inside_thread_pool: bool, + is_inside_thread_pool: IsInsideThreadPool, ) { // SAFETY: caller contract; each read ends at the statement. let needs_source = unsafe { @@ -322,7 +324,7 @@ impl ThreadPool { } let schedule_fn: fn(&ThreadPoolLib::ThreadPool, ThreadPoolLib::Batch) = - if is_inside_thread_pool { + if is_inside_thread_pool == IsInsideThreadPool::Yes { ThreadPoolLib::ThreadPool::schedule_inside_thread_pool } else { ThreadPoolLib::ThreadPool::schedule @@ -363,12 +365,12 @@ impl ThreadPool { pub(crate) fn schedule(&self, parse_task: *mut ParseTask) { // SAFETY: callers pass a live, exclusively-owned ParseTask (heap- or // arena-allocated raw pointer); see call sites in bundle_v2.rs. - unsafe { self.schedule_with_options(parse_task, false) }; + unsafe { self.schedule_with_options(parse_task, IsInsideThreadPool::No) }; } pub(crate) fn schedule_inside_thread_pool(&self, parse_task: *mut ParseTask) { // SAFETY: see `schedule`. - unsafe { self.schedule_with_options(parse_task, true) }; + unsafe { self.schedule_with_options(parse_task, IsInsideThreadPool::Yes) }; } // returns `&'static mut` — the `Worker` is `heap::alloc`'d diff --git a/src/bundler/bundle_v2.rs b/src/bundler/bundle_v2.rs index 24999fd82b68..73a8214ee5ec 100644 --- a/src/bundler/bundle_v2.rs +++ b/src/bundler/bundle_v2.rs @@ -26,8 +26,8 @@ pub use bv2_impl::{DevServerInput, DevServerOutput, ImportTrackerIterator, Impor // `bundle_v2::Foo` rather than naming the implementation submodule. use self::bake_types as bake; pub use bv2_impl::{ - BuildResult, BundleV2Result, CompletionStruct, DependenciesScanner, DependenciesScannerResult, - OnDependenciesAnalyze, singleton, + BuildResult, BundleV2Result, CliWatchFlag, CompletionStruct, DependenciesScanner, + DependenciesScannerResult, OnDependenciesAnalyze, singleton, }; pub use crate::DeferredBatchTask::DeferredBatchTask; @@ -759,6 +759,8 @@ pub mod bv2_impl { should_continue_running: *mut i32, ) -> i32; } + bun_core::bool_enum!(pub(crate) IsOnLoad { OnResolve, OnLoad }); + bun_core::bool_enum!(pub(crate) IsServerSide); impl Plugin { /// `Plugin.drainDeferred` — resolve every onLoad /// `.defer()` promise. The @@ -801,7 +803,7 @@ pub mod bv2_impl { pub(crate) fn has_any_matches( &self, path: &crate::bun_fs::Path, - is_on_load: bool, + is_on_load: IsOnLoad, ) -> bool { let mut namespace_string = if path.is_file() { BunString::empty() @@ -813,7 +815,7 @@ pub mod bv2_impl { self, &mut namespace_string, &mut path_string, - is_on_load, + is_on_load == IsOnLoad::OnLoad, ) } @@ -823,7 +825,7 @@ pub mod bv2_impl { namespace: &[u8], context: *mut core::ffi::c_void, default_loader: Loader, - is_server_side: bool, + is_server_side: IsServerSide, ) { let _tracer = bun_core::perf::trace("JSBundler.matchOnLoad"); let mut namespace_string = if namespace.is_empty() { @@ -838,7 +840,7 @@ pub mod bv2_impl { &mut path_string, context, default_loader as u8, - is_server_side, + is_server_side == IsServerSide::Yes, ); } @@ -1279,7 +1281,9 @@ 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 is_server_side = IsServerSide::from_bool( + self.bake_graph() != crate::bake_types::Graph::Client, + ); let default_loader = self.default_loader; // reshaped for borrowck — capture the erased self // pointer before borrowing fields immutably for the FFI call. @@ -1320,7 +1324,7 @@ pub mod bv2_impl { use bun_sourcemap as SourceMap; - use crate::AstBuilder::AstBuilder; + use crate::AstBuilder::{AstBuilder, HotReloading}; use crate::DeferredBatchTask::DeferredBatchTask; use crate::Graph::Graph; use crate::LinkerContext; @@ -1602,8 +1606,11 @@ pub mod bv2_impl { .iter() .map(|s| s.as_bytes().to_vec().into_boxed_slice()) .collect(); - ct.options.conditions = - options::ESMConditions::init(Target::Browser.default_conditions(), false, &[])?; + ct.options.conditions = options::ESMConditions::init( + Target::Browser.default_conditions(), + options::AllowAddons::No, + &[], + )?; // We need to make sure it has [hash] in the names so we don't get conflicts. if this_compile { @@ -1691,15 +1698,17 @@ pub mod bv2_impl { pub(crate) stack: Vec, } + bun_core::bool_enum!(pub WasDynamicImport); + #[derive(Copy, Clone)] pub enum ReachFrame { Enter { source_index: Index, - was_dynamic_import: bool, + was_dynamic_import: WasDynamicImport, }, Leave { source_index: Index, - was_dynamic_import: bool, + was_dynamic_import: WasDynamicImport, }, } @@ -1718,7 +1727,7 @@ pub mod bv2_impl { pub(crate) fn visit( &mut self, source_index: Index, - was_dynamic_import: bool, + was_dynamic_import: WasDynamicImport, ) { debug_assert!(self.stack.is_empty()); self.stack.push(ReachFrame::Enter { @@ -1734,7 +1743,7 @@ pub mod bv2_impl { } => { // Each file must come after its dependencies self.reachable.push(source_index); - if CHECK_DYNAMIC_IMPORTS && was_dynamic_import { + if CHECK_DYNAMIC_IMPORTS && was_dynamic_import == WasDynamicImport::Yes { self.dynamic_import_entry_points .put(source_index.get(), ()) .expect("unreachable"); @@ -1752,7 +1761,7 @@ pub mod bv2_impl { } if self.visited.is_set(source_index.get() as usize) { - if CHECK_DYNAMIC_IMPORTS && was_dynamic_import { + if CHECK_DYNAMIC_IMPORTS && was_dynamic_import == WasDynamicImport::Yes { self.dynamic_import_entry_points .put(source_index.get(), ()) .expect("unreachable"); @@ -1773,13 +1782,13 @@ pub mod bv2_impl { source_index: Index::init( self.scb_list.list.items_reference_source_index()[scb_index], ), - was_dynamic_import: false, + was_dynamic_import: WasDynamicImport::No, }); self.stack.push(ReachFrame::Enter { source_index: Index::init( self.scb_list.list.items_ssr_source_index()[scb_index], ), - was_dynamic_import: false, + was_dynamic_import: WasDynamicImport::No, }); } } @@ -1852,7 +1861,9 @@ pub mod bv2_impl { let kind_is_dynamic = import_record.kind == ImportKind::Dynamic; self.stack.push(ReachFrame::Enter { source_index: next_source, - was_dynamic_import: CHECK_DYNAMIC_IMPORTS && kind_is_dynamic, + was_dynamic_import: WasDynamicImport::from_bool( + CHECK_DYNAMIC_IMPORTS && kind_is_dynamic, + ), }); } } @@ -1904,6 +1915,8 @@ pub mod bv2_impl { } } + bun_core::bool_enum!(pub CliWatchFlag); + impl<'a> BundleV2<'a> { pub(crate) fn find_reachable_files(&mut self) -> Result, Error> { // RAII guard — `Ctx` ends the span on Drop. @@ -1966,15 +1979,15 @@ pub mod bv2_impl { // If we don't include the runtime, __toESM or __toCommonJS will not get // imported and weird things will happen - visitor.visit::(Index::RUNTIME, false); + visitor.visit::(Index::RUNTIME, WasDynamicImport::No); if self.transpiler.options.code_splitting { for entry_point in self.graph.entry_points.iter().copied() { - visitor.visit::(entry_point, false); + visitor.visit::(entry_point, WasDynamicImport::No); } } else { for entry_point in self.graph.entry_points.iter().copied() { - visitor.visit::(entry_point, false); + visitor.visit::(entry_point, WasDynamicImport::No); } } @@ -2550,7 +2563,8 @@ pub mod bv2_impl { out_source_index = Some(Index::init(idx)); if let Some(secondary) = &resolve_result.path_pair.secondary { - if !secondary.is_disabled && !strings::eql_long(secondary.text, path.text, true) + if !secondary.is_disabled + && !strings::eql_long(secondary.text, path.text, strings::CheckLen::Yes) { self.graph.input_files.items_secondary_path_mut()[idx as usize] = bun_alloc::AstAlloc::vec_from_slice(secondary.text); @@ -2798,7 +2812,7 @@ pub mod bv2_impl { bake_options: Option>, _alloc: &bun_alloc::Arena, event_loop: EventLoop, - cli_watch_flag: bool, + cli_watch_flag: CliWatchFlag, // Raw `NonNull` (not `&mut`): the JS-API path threads `WorkPool::get()` // (a `&'static` from `OnceLock`, concurrently read by workers) through // here into `ThreadPool::init`, which stores it as `*mut`. Creating a @@ -2956,7 +2970,7 @@ pub mod bv2_impl { // 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 (the caller // drops the box on every error path until `generate_from_cli` leaks it). - if cli_watch_flag { + if cli_watch_flag == CliWatchFlag::Yes { // CYCLEBREAK GENUINE: hot_reloader is T6; runtime constructs the // `dispatch::WatcherHandle` (erased owner + `&'static WatcherVTable`) // via this extern hook and writes `bun_watcher`. @@ -3332,7 +3346,7 @@ pub mod bv2_impl { let alloc: &'static bun_alloc::Arena = unsafe { bun_ptr::detach_lifetime_ref::(self.arena()) }; - let hmr = self.transpiler.options.hot_module_reloading; + let hmr = HotReloading::from_bool(self.transpiler.options.hot_module_reloading); let mut server = AstBuilder::init(alloc, &bake::SERVER_VIRTUAL_SOURCE, hmr)?; let mut client = AstBuilder::init(alloc, &bake::CLIENT_VIRTUAL_SOURCE, hmr)?; @@ -3873,7 +3887,7 @@ pub mod bv2_impl { transpiler: &'a mut Transpiler<'a>, alloc: &'a bun_alloc::Arena, event_loop: EventLoop, - enable_reloading: bool, + enable_reloading: CliWatchFlag, reachable_files_count: &mut usize, minify_duration: &mut u64, source_code_size: &mut u64, @@ -4000,7 +4014,7 @@ pub mod bv2_impl { // reloader's `ctx`) and dereferences it in `on_file_update` after this // 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 { + if enable_reloading == CliWatchFlag::Yes { let _ = Box::into_raw(this); } else { this.deinit_without_freeing_arena(); @@ -4027,7 +4041,15 @@ pub mod bv2_impl { event_loop: EventLoop, entry_points: &[&[u8]], ) -> Result>, Error> { - let mut this = BundleV2::init(transpiler, None, alloc, event_loop, false, None, alloc)?; + let mut this = BundleV2::init( + transpiler, + None, + alloc, + event_loop, + CliWatchFlag::No, + None, + alloc, + )?; this.unique_key = generate_unique_key(); if this.transpiler.log().has_errors() { @@ -4062,7 +4084,7 @@ pub mod bv2_impl { Some(bake_options), alloc, event_loop, - false, + CliWatchFlag::No, None, alloc, )?; @@ -4261,7 +4283,9 @@ pub mod bv2_impl { template .print( &mut v, - !self.transpiler.options.compile_mode.is_executable(), + options::SanitizeParentDirs::from_bool( + !self.transpiler.options.compile_mode.is_executable(), + ), ) .expect("oom"); v.into_boxed_slice() @@ -5606,7 +5630,7 @@ pub mod bv2_impl { import_record.path.text, import_record.path.namespace, ); - if plugins.has_any_matches(&match_path, false) { + if plugins.has_any_matches(&match_path, jsc_api::JSBundler::IsOnLoad::OnResolve) { // This is where onResolve plugins are enqueued bun_core::scoped_log!( Bundle, @@ -5652,7 +5676,7 @@ pub mod bv2_impl { if let Some(plugins) = self.plugins_ref() { let mut temp_path = Fs::Path::init(entry_point); temp_path.namespace = b"file"; - if plugins.has_any_matches(&temp_path, false) { + if plugins.has_any_matches(&temp_path, jsc_api::JSBundler::IsOnLoad::OnResolve) { bun_core::scoped_log!( Bundle, "Entry point '{}' plugin match", @@ -5728,7 +5752,7 @@ pub mod bv2_impl { parse: &mut ParseTask, ) -> bool { if let Some(plugins) = self.plugins_ref() { - if plugins.has_any_matches(&parse.path, true) { + if plugins.has_any_matches(&parse.path, jsc_api::JSBundler::IsOnLoad::OnLoad) { // This is where onLoad plugins are enqueued bun_core::scoped_log!( Bundle, @@ -6414,7 +6438,7 @@ pub mod bv2_impl { && !strings::eql_long( resolve_result.path_pair.primary.text, import_record.path.text, - true, + strings::CheckLen::Yes, ) { import_record.path = path_as_static(&resolve_result.path_pair.primary); @@ -6581,7 +6605,7 @@ pub mod bv2_impl { if let Some(secondary) = &resolve_result.path_pair.secondary { if !secondary.is_disabled && !core::ptr::eq(secondary, path) - && !strings::eql_long(secondary.text, path.text, true) + && !strings::eql_long(secondary.text, path.text, strings::CheckLen::Yes) { resolve_task.secondary_path_for_commonjs_interop = Some(*secondary); } @@ -7013,7 +7037,7 @@ pub mod bv2_impl { // SAFETY: `transpiler.log` is a live BACKREF set in BundleV2::init. result .log - .clone_to_with_recycled(this.transpiler.log_mut(), true); + .clone_to_with_recycled(this.transpiler.log_mut(), bun_ast::Recycled::Yes); this.has_any_top_level_await_modules = this.has_any_top_level_await_modules || !result.ast.top_level_await_keyword.is_empty(); @@ -7312,8 +7336,10 @@ pub mod bv2_impl { .expect("oom"); } else if !err.log.msgs.is_empty() { // SAFETY: `transpiler.log` is a live BACKREF set in BundleV2::init. - err.log - .clone_to_with_recycled(this.transpiler.log_mut(), true); + err.log.clone_to_with_recycled( + this.transpiler.log_mut(), + bun_ast::Recycled::Yes, + ); } else { let step_name = match err.step { crate::parse_task::Step::Pending => "pending", diff --git a/src/bundler/bundled_ast.rs b/src/bundler/bundled_ast.rs index f661a7706948..2a9d62eaedf8 100644 --- a/src/bundler/bundled_ast.rs +++ b/src/bundler/bundled_ast.rs @@ -149,6 +149,8 @@ bitflags::bitflags! { } } +bun_core::bool_enum!(pub(crate) ForceInline); + impl<'arena> BundledAst<'arena> { // The three `ArenaVec` fields prevent `const fn` here, but spell out the // defaults directly instead of round-tripping through `Ast::empty_in` + @@ -335,7 +337,7 @@ impl<'arena> BundledAst<'arena> { source: &bun_ast::Source, mime_type_: Option<&[u8]>, unique_key: Option<&[u8]>, - force_inline: bool, + force_inline: ForceInline, ) { { // `by_extension` returns an owned MimeType whose `.value` is a Cow; bind it @@ -352,8 +354,9 @@ impl<'arena> BundledAst<'arena> { let contents: &[u8] = &source.contents; // TODO: make this configurable const COPY_THRESHOLD: usize = 128 * 1024; // 128kb - let should_copy = - !force_inline && contents.len() >= COPY_THRESHOLD && unique_key.is_some(); + let should_copy = force_inline == ForceInline::No + && contents.len() >= COPY_THRESHOLD + && unique_key.is_some(); if should_copy { return; } diff --git a/src/bundler/defines.rs b/src/bundler/defines.rs index 8897c2649eb2..05ba228e1e08 100644 --- a/src/bundler/defines.rs +++ b/src/bundler/defines.rs @@ -20,11 +20,15 @@ use crate::defines_table::{ // moved down to `bun_js_parser::defines_table`, so `for_identifier` reads it // directly with no cross-crate hook. // ══════════════════════════════════════════════════════════════════════════ +use bun_js_parser::defines::CanBeRemovedIfUnused; pub use bun_js_parser::defines::{ - Define, DefineData, DotDefine, Flags, IdentifierDefine, Options, RawDefines, UserDefines, - UserDefinesArray, are_parts_equal, + Define, DefineData, DotDefine, Flags, IdentifierDefine, MethodCallMustBeReplacedWithUndefined, + Options, RawDefines, UserDefines, UserDefinesArray, Valueless, are_parts_equal, }; +bun_core::bool_enum!(pub DropDebugger); +bun_core::bool_enum!(pub OmitUnusedGlobalCalls); + /// Alias for `Options` so `options.rs` can write `DefineData::init(DefineDataInit { .. })`. pub(crate) type DefineDataInit<'a> = Options<'a>; /// Alias for `ExprData` so `options.rs` can write `DefineValue::EUndefined(..)`. @@ -127,8 +131,8 @@ pub trait DefineExt: Sized { fn init( user_defines: Option, string_defines: Option, - drop_debugger: bool, - omit_unused_global_calls: bool, + drop_debugger: DropDebugger, + omit_unused_global_calls: OmitUnusedGlobalCalls, ) -> Result, bun_alloc::AllocError>; } @@ -160,13 +164,13 @@ impl DefineExt for Define { fn init( _user_defines: Option, string_defines: Option, - drop_debugger: bool, - omit_unused_global_calls: bool, + drop_debugger: DropDebugger, + omit_unused_global_calls: OmitUnusedGlobalCalls, ) -> Result, bun_alloc::AllocError> { let mut define = Box::new(Define { identifiers: StringHashMap::default(), dots: StringHashMap::default(), - drop_debugger, + drop_debugger: drop_debugger == DropDebugger::Yes, }); define.dots.reserve(124); @@ -189,7 +193,7 @@ impl DefineExt for Define { ..Default::default() }); - if omit_unused_global_calls { + if omit_unused_global_calls == OmitUnusedGlobalCalls::Yes { for global in global_no_side_effect_function_calls_safe_for_to_string.iter() { define.insert_global(global, &to_string_safe)?; } @@ -259,8 +263,8 @@ pub trait DefineDataExt: Sized { fn parse( key: &[u8], value_str: &[u8], - value_is_undefined: bool, - method_call_must_be_replaced_with_undefined_: bool, + value_is_undefined: Valueless, + method_call_must_be_replaced_with_undefined_: MethodCallMustBeReplacedWithUndefined, log: &mut bun_ast::Log, bump: &bun_alloc::Arena, ) -> Result; @@ -269,8 +273,8 @@ pub trait DefineDataExt: Sized { user_defines: &mut UserDefines, key: &[u8], value_str: &[u8], - value_is_undefined: bool, - method_call_must_be_replaced_with_undefined_: bool, + value_is_undefined: Valueless, + method_call_must_be_replaced_with_undefined_: MethodCallMustBeReplacedWithUndefined, log: &mut bun_ast::Log, bump: &bun_alloc::Arena, ) -> Result<(), crate::Error>; @@ -288,8 +292,8 @@ impl DefineDataExt for DefineData { user_defines: &mut UserDefines, key: &[u8], value_str: &[u8], - value_is_undefined: bool, - method_call_must_be_replaced_with_undefined_: bool, + value_is_undefined: Valueless, + method_call_must_be_replaced_with_undefined_: MethodCallMustBeReplacedWithUndefined, log: &mut bun_ast::Log, bump: &bun_alloc::Arena, ) -> Result<(), crate::Error> { @@ -310,8 +314,8 @@ impl DefineDataExt for DefineData { fn parse( key: &[u8], value_str: &[u8], - value_is_undefined: bool, - method_call_must_be_replaced_with_undefined_: bool, + value_is_undefined: Valueless, + method_call_must_be_replaced_with_undefined_: MethodCallMustBeReplacedWithUndefined, log: &mut bun_ast::Log, bump: &bun_alloc::Arena, ) -> Result { @@ -356,7 +360,7 @@ impl DefineDataExt for DefineData { if is_ident { // Special-case undefined. it's not an identifier here // https://github.com/evanw/esbuild/issues/1407 - let value = if value_is_undefined || value_str == b"undefined" { + let value = if value_is_undefined == Valueless::Yes || value_str == b"undefined" { ExprData::EUndefined(bun_ast::E::Undefined) } else { ExprData::EIdentifier( @@ -376,8 +380,9 @@ impl DefineDataExt for DefineData { None }, flags: Flags::new( - /* valueless: */ value_is_undefined, - /* can_be_removed_if_unused: */ true, + /* valueless: */ + value_is_undefined, + /* can_be_removed_if_unused: */ CanBeRemovedIfUnused::Yes, /* call_can_be_unwrapped_if_unused: */ bun_ast::E::CallUnwrap::Never, /* method_call_must_be_replaced_with_undefined: */ method_call_must_be_replaced_with_undefined_, @@ -404,8 +409,10 @@ impl DefineDataExt for DefineData { None }, flags: Flags::new( - /* valueless: */ value_is_undefined, - /* can_be_removed_if_unused: */ can_be_removed_if_unused, + /* valueless: */ + value_is_undefined, + /* can_be_removed_if_unused: */ + CanBeRemovedIfUnused::from_bool(can_be_removed_if_unused), /* call_can_be_unwrapped_if_unused: */ bun_ast::E::CallUnwrap::Never, /* method_call_must_be_replaced_with_undefined: */ method_call_must_be_replaced_with_undefined_, @@ -450,8 +457,10 @@ impl DefineDataExt for DefineData { None }, flags: Flags::new( - /* valueless: */ value_is_undefined, - /* can_be_removed_if_unused: */ can_be_removed_if_unused, + /* valueless: */ + value_is_undefined, + /* can_be_removed_if_unused: */ + CanBeRemovedIfUnused::from_bool(can_be_removed_if_unused), /* call_can_be_unwrapped_if_unused: */ bun_ast::E::CallUnwrap::Never, /* method_call_must_be_replaced_with_undefined: */ method_call_must_be_replaced_with_undefined_, @@ -472,8 +481,8 @@ impl DefineDataExt for DefineData { &mut user_defines, key, value, - false, - false, + Valueless::No, + MethodCallMustBeReplacedWithUndefined::No, log, bump, )?; @@ -485,8 +494,8 @@ impl DefineDataExt for DefineData { &mut user_defines, drop_item, b"", - true, - true, + Valueless::Yes, + MethodCallMustBeReplacedWithUndefined::Yes, log, bump, )?; diff --git a/src/bundler/entry_points.rs b/src/bundler/entry_points.rs index 5d342c0e4788..8eab63869206 100644 --- a/src/bundler/entry_points.rs +++ b/src/bundler/entry_points.rs @@ -36,10 +36,12 @@ pub struct ServerEntryPoint { // `deinit` only freed `contents` and reset flags; with `Box<[u8]>` this is the // auto-generated `Drop`, so no explicit impl is needed. +bun_core::bool_enum!(pub IsHotReloadEnabled); + impl ServerEntryPoint { pub fn generate( entry: &mut ServerEntryPoint, - is_hot_reload_enabled: bool, + is_hot_reload_enabled: IsHotReloadEnabled, path_to_use: &[u8], ) -> crate::Result<()> { // Use the global arena so this buffer's lifetime is decoupled @@ -47,7 +49,7 @@ impl ServerEntryPoint { // slice is read later from `getHardcodedModule` which outlives any // per-transpile arena. let code: Vec = 'brk: { - if is_hot_reload_enabled { + if is_hot_reload_enabled == IsHotReloadEnabled::Yes { let mut v: Vec = Vec::new(); write!( &mut v, diff --git a/src/bundler/linker_context/computeChunks.rs b/src/bundler/linker_context/computeChunks.rs index 362414a8cb4b..c306f66253aa 100644 --- a/src/bundler/linker_context/computeChunks.rs +++ b/src/bundler/linker_context/computeChunks.rs @@ -19,13 +19,19 @@ use super::find_all_imported_parts_in_js_order::find_all_imported_parts_in_js_or use super::find_imported_css_files_in_js_order::find_imported_css_files_in_js_order; use super::find_imported_files_in_css_order::find_imported_files_in_css_order; +bun_core::bool_enum!(HasHtmlChunk); +bun_core::bool_enum!(IsBrowserChunkFromServerBuild); + #[inline(always)] -fn make_flags(has_html_chunk: bool, is_browser_chunk_from_server_build: bool) -> chunk::Flags { +fn make_flags( + has_html_chunk: HasHtmlChunk, + is_browser_chunk_from_server_build: IsBrowserChunkFromServerBuild, +) -> chunk::Flags { let mut f = chunk::Flags::empty(); - if has_html_chunk { + if has_html_chunk == HasHtmlChunk::Yes { f |= chunk::Flags::HAS_HTML_CHUNK; } - if is_browser_chunk_from_server_build { + if is_browser_chunk_from_server_build == IsBrowserChunkFromServerBuild::Yes { f |= chunk::Flags::IS_BROWSER_CHUNK_FROM_SERVER_BUILD; } f @@ -132,9 +138,11 @@ pub(crate) fn compute_chunks( content: chunk::Content::Html, output_source_map: SourceMapPieces::init(), flags: make_flags( - false, - could_be_browser_target_from_server_build - && ast_targets[source_index as usize] == Target::Browser, + HasHtmlChunk::No, + IsBrowserChunkFromServerBuild::from_bool( + could_be_browser_target_from_server_build + && ast_targets[source_index as usize] == Target::Browser, + ), ), ..Default::default() }; @@ -179,9 +187,11 @@ pub(crate) fn compute_chunks( }), output_source_map: SourceMapPieces::init(), flags: make_flags( - has_html_chunk, - could_be_browser_target_from_server_build - && ast_targets[source_index as usize] == Target::Browser, + HasHtmlChunk::from_bool(has_html_chunk), + IsBrowserChunkFromServerBuild::from_bool( + could_be_browser_target_from_server_build + && ast_targets[source_index as usize] == Target::Browser, + ), ), ..Default::default() }; @@ -201,9 +211,11 @@ pub(crate) fn compute_chunks( content: chunk::Content::Javascript(chunk::JavaScriptChunk::default()), output_source_map: SourceMapPieces::init(), flags: make_flags( - has_html_chunk, - could_be_browser_target_from_server_build - && ast_targets[source_index as usize] == Target::Browser, + HasHtmlChunk::from_bool(has_html_chunk), + IsBrowserChunkFromServerBuild::from_bool( + could_be_browser_target_from_server_build + && ast_targets[source_index as usize] == Target::Browser, + ), ), ..Default::default() }; @@ -271,9 +283,11 @@ pub(crate) fn compute_chunks( files_with_parts_in_chunk: css_files_with_parts_in_chunk, output_source_map: SourceMapPieces::init(), flags: make_flags( - has_html_chunk, - could_be_browser_target_from_server_build - && ast_targets[source_index as usize] == Target::Browser, + HasHtmlChunk::from_bool(has_html_chunk), + IsBrowserChunkFromServerBuild::from_bool( + could_be_browser_target_from_server_build + && ast_targets[source_index as usize] == Target::Browser, + ), ), ..Default::default() }; @@ -315,7 +329,12 @@ pub(crate) fn compute_chunks( chunk::JavaScriptChunk::default(), ), output_source_map: SourceMapPieces::init(), - flags: make_flags(false, is_browser_chunk_from_server_build), + flags: make_flags( + HasHtmlChunk::No, + IsBrowserChunkFromServerBuild::from_bool( + is_browser_chunk_from_server_build, + ), + ), ..Default::default() }; } else if could_be_browser_target_from_server_build diff --git a/src/bundler/linker_context/generateChunksInParallel.rs b/src/bundler/linker_context/generateChunksInParallel.rs index 81f52306020d..520dcb55dae1 100644 --- a/src/bundler/linker_context/generateChunksInParallel.rs +++ b/src/bundler/linker_context/generateChunksInParallel.rs @@ -391,7 +391,10 @@ pub(crate) fn generate_chunks_in_parallel( // runtime bunfs references to out-of-root entrypoints resolve. chunk .template - .print(&mut rel_path, !c.options.compile_mode.is_executable()) + .print( + &mut rel_path, + options::SanitizeParentDirs::from_bool(!c.options.compile_mode.is_executable()), + ) .expect("write to Vec"); path::resolve_path::platform_to_posix_in_place::(&mut rel_path); diff --git a/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs b/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs index e858c718a358..80df43a1d0f9 100644 --- a/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs +++ b/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs @@ -1,4 +1,5 @@ use crate::mal_prelude::*; +use core::ops::ControlFlow; use bstr::BStr; @@ -202,15 +203,15 @@ impl<'a> HTMLProcessorHandler for HTMLLoader<'a> { } } - fn on_head_tag(&mut self, element: &mut Element<'_, '_>) -> bool { + fn on_head_tag(&mut self, element: &mut Element<'_, '_>) -> ControlFlow<()> { self.register_end_tag_handler(element, Self::end_head_tag_handler) } - fn on_html_tag(&mut self, element: &mut Element<'_, '_>) -> bool { + fn on_html_tag(&mut self, element: &mut Element<'_, '_>) -> ControlFlow<()> { self.register_end_tag_handler(element, Self::end_html_tag_handler) } - fn on_body_tag(&mut self, element: &mut Element<'_, '_>) -> bool { + fn on_body_tag(&mut self, element: &mut Element<'_, '_>) -> ControlFlow<()> { self.register_end_tag_handler(element, Self::end_body_tag_handler) } } @@ -222,22 +223,22 @@ type EndTagHandlerFn = fn(*mut (), &mut EndTag<'_>) -> HandlerResult; impl<'a> HTMLLoader<'a> { /// Arranges for `handler(self, end_tag)` to run when `element`'s end tag - /// is reached. Returns `true` (stop the rewriter) if `element` cannot + /// is reached. Returns `Break` (stop the rewriter) if `element` cannot /// have an end tag. fn register_end_tag_handler( &mut self, element: &mut Element<'_, '_>, handler: EndTagHandlerFn, - ) -> bool { + ) -> ControlFlow<()> { let Some(handlers) = element.end_tag_handlers() else { - return true; + return ControlFlow::Break(()); }; // `self` points at the `HTMLLoader` that `HTMLProcessor::run` holds // for the whole rewriting pass, so the erased pointer is still live // when lol-html invokes `handler` at the end tag. let opaque_this = std::ptr::from_mut::(self).cast::<()>(); handlers.push(Box::new(move |end| handler(opaque_this, end))); - false + ControlFlow::Continue(()) } /// This is called for head, body, and html; whichever ends up coming first. diff --git a/src/bundler/linker_context/postProcessCSSChunk.rs b/src/bundler/linker_context/postProcessCSSChunk.rs index 0cd4822a0cf7..579b9bab5718 100644 --- a/src/bundler/linker_context/postProcessCSSChunk.rs +++ b/src/bundler/linker_context/postProcessCSSChunk.rs @@ -4,7 +4,7 @@ use bun_core::string_joiner::{StringJoiner, Watcher}; use bun_sourcemap::{LineColumnOffset, LineColumnOffsetOptional}; use crate::chunk::IntermediateOutput; -use crate::linker_context_mod::{GenerateChunkCtx, LinkerOptionsMode}; +use crate::linker_context_mod::{CanHaveShifts, GenerateChunkCtx, LinkerOptionsMode}; use crate::thread_pool; use crate::{Chunk, CompileResultForSourceMap, Index, options}; @@ -155,7 +155,10 @@ pub(crate) fn post_process_css_chunk( // chunk.flags.is_executable = is_executable; if c.options.source_maps != options::SourceMapOption::None { - let can_have_shifts = matches!(chunk.intermediate_output, IntermediateOutput::Pieces(_)); + let can_have_shifts = CanHaveShifts::from_bool(matches!( + chunk.intermediate_output, + IntermediateOutput::Pieces(_) + )); // Copy the `ParentRef` out (not `c.resolver()`) so `output_dir` // borrows the local, not `c`, avoiding the split-borrow with // `c.generate_source_map_for_chunk(&mut self, …)` below. diff --git a/src/bundler/linker_context/postProcessJSChunk.rs b/src/bundler/linker_context/postProcessJSChunk.rs index f503f3254274..f8fb2466adc7 100644 --- a/src/bundler/linker_context/postProcessJSChunk.rs +++ b/src/bundler/linker_context/postProcessJSChunk.rs @@ -1,7 +1,7 @@ use crate::LinkerContext; use crate::analyze_transpiled_module::ModuleInfo; use crate::bundle_v2::bake_types::{HmrRuntimeSide, get_hmr_runtime}; -use crate::linker_context_mod::{GenerateChunkCtx, LinkerOptionsMode}; +use crate::linker_context_mod::{CanHaveShifts, GenerateChunkCtx, LinkerOptionsMode}; use crate::mal_prelude::*; use crate::options; use crate::options_impl::TargetExt as _; @@ -627,7 +627,8 @@ pub(crate) fn post_process_js_chunk( [chunk.entry_point.source_index() as usize] .path; let mut buf = MutableString::init_empty(); - let _ = js_printer::quote_for_json(input.pretty, &mut buf, true); // fmt::Result into Vec is infallible + let _ = + js_printer::quote_for_json(input.pretty, &mut buf, js_printer::AsciiOnly::Yes); // fmt::Result into Vec is infallible let quoted = buf.take_slice(); line_offset.advance("ed); j.push_owned(quoted.into_boxed_slice()); @@ -687,10 +688,10 @@ pub(crate) fn post_process_js_chunk( .set(crate::chunk::Flags::IS_EXECUTABLE, is_executable); if c.options.source_maps != options::SourceMapOption::None { - let can_have_shifts = matches!( + let can_have_shifts = CanHaveShifts::from_bool(matches!( chunk.intermediate_output, crate::chunk::IntermediateOutput::Pieces(_) - ); + )); // Copy the `ParentRef` out (not `c.resolver()`) so the arg borrows the // local, not `c`, avoiding the split-borrow with // `c.generate_source_map_for_chunk(&mut self, …)`. diff --git a/src/bundler/options.rs b/src/bundler/options.rs index 52f350c1bb71..628c499f5334 100644 --- a/src/bundler/options.rs +++ b/src/bundler/options.rs @@ -555,7 +555,7 @@ pub fn get_loader_and_virtual_source<'a>( } } - let is_main = strings::eql_long(specifier, jsc_vm.main(), true); + let is_main = strings::eql_long(specifier, jsc_vm.main(), strings::CheckLen::Yes); let dir = path.name().dir; // NOTE: we cannot trust `path.isFile()` since it's not always correct @@ -721,12 +721,15 @@ pub struct ESMConditions { pub(crate) style: ConditionsMap, } +bun_core::bool_enum!(pub AllowAddons); + impl ESMConditions { pub fn init( defaults: &[&[u8]], - allow_addons: bool, + allow_addons: AllowAddons, conditions: &[&[u8]], ) -> Result { + let allow_addons = allow_addons == AllowAddons::Yes; let mut default_condition_amp = ConditionsMap::default(); let mut import_condition_map = ConditionsMap::default(); @@ -840,7 +843,7 @@ pub(crate) fn defines_from_transform_options( framework_env: Option<&Env>, node_env: Option<&[u8]>, drop: &[&[u8]], - omit_unused_global_calls: bool, + omit_unused_global_calls: defines::OmitUnusedGlobalCalls, bump: &bun_alloc::Arena, ) -> Result, crate::Error> { let (input_keys, input_values): (&[Box<[u8]>], &[Box<[u8]>]) = match maybe_input_define { @@ -944,7 +947,8 @@ pub(crate) fn defines_from_transform_options( let resolved_defines = defines::DefineData::from_input(&user_defines, drop, log, bump)?; - let drop_debugger = drop.iter().any(|item| *item == b"debugger"); + let drop_debugger = + defines::DropDebugger::from_bool(drop.iter().any(|item| *item == b"debugger")); Ok(defines::Define::init( Some(resolved_defines), @@ -1595,7 +1599,9 @@ impl<'a> BundleOptions<'a> { // `&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, + defines::OmitUnusedGlobalCalls::from_bool( + self.dead_code_elimination && self.minify_syntax, + ), arena, )?; self.defines_loaded = true; @@ -1785,7 +1791,7 @@ impl<'a> BundleOptions<'a> { // 3. user conditions opts.conditions = ESMConditions::init( Target::default_conditions_map()[opts.target], - transform.allow_addons.unwrap_or(true), + AllowAddons::from_bool(transform.allow_addons.unwrap_or(true)), &transform .conditions .iter() @@ -2081,6 +2087,8 @@ pub fn find_unterminated_placeholder(template: &[u8]) -> Option<(usize, &[u8])> None } +bun_core::bool_enum!(pub(crate) SanitizeParentDirs); + // Shared body for PathTemplate::print / PathTemplateConst::print (D064). // Writes raw path bytes via a byte-writer free fn (not `core::fmt::Display`). fn path_template_print( @@ -2091,7 +2099,7 @@ fn path_template_print( ext: &[u8], hash: Option, target: &[u8], - sanitize_parent_dirs: bool, + sanitize_parent_dirs: SanitizeParentDirs, ) -> bun_io::Result<()> { let mut remain: &[u8] = data; while let Some(j) = strings::index_of_char(remain, b'[') { @@ -2136,7 +2144,7 @@ fn path_template_print( PlaceholderField::Dir => { if dir.is_empty() { writer.write_all(b".")?; - } else if sanitize_parent_dirs { + } else if sanitize_parent_dirs == SanitizeParentDirs::Yes { // Rewrite `..` segments so `[dir]` can't escape outdir for an // out-of-root source. `--compile` skips this: bunfs entries keep // `..` so runtime references to them resolve. @@ -2215,7 +2223,17 @@ fn write_sanitized_parent_dirs_rewrites_every_dotdot_segment() { fn path_template_print_tolerates_malformed_brackets() { fn run(template: &[u8]) -> Vec { let mut out = Vec::new(); - path_template_print(&mut out, template, b"D", b"N", b"E", Some(0), b"T", false).unwrap(); + path_template_print( + &mut out, + template, + b"D", + b"N", + b"E", + Some(0), + b"T", + SanitizeParentDirs::No, + ) + .unwrap(); out } // Unterminated known placeholder: used to slice one past the end. @@ -2331,7 +2349,7 @@ impl PathTemplate { pub(crate) fn print( &self, writer: &mut W, - sanitize_parent_dirs: bool, + sanitize_parent_dirs: SanitizeParentDirs, ) -> bun_io::Result<()> { path_template_print( writer, @@ -2400,7 +2418,7 @@ impl PathTemplateConst { pub(crate) fn print( &self, writer: &mut W, - sanitize_parent_dirs: bool, + sanitize_parent_dirs: SanitizeParentDirs, ) -> bun_io::Result<()> { path_template_print( writer, @@ -2418,7 +2436,8 @@ impl PathTemplateConst { impl core::fmt::Display for PathTemplateConst { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let mut buf = Vec::::new(); - self.print(&mut buf, true).map_err(|_| core::fmt::Error)?; + self.print(&mut buf, SanitizeParentDirs::Yes) + .map_err(|_| core::fmt::Error)?; write!(f, "{}", bstr::BStr::new(&buf)) } } @@ -2426,7 +2445,8 @@ impl core::fmt::Display for PathTemplateConst { impl core::fmt::Display for PathTemplate { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let mut buf = Vec::::new(); - self.print(&mut buf, true).map_err(|_| core::fmt::Error)?; + self.print(&mut buf, SanitizeParentDirs::Yes) + .map_err(|_| core::fmt::Error)?; write!(f, "{}", bstr::BStr::new(&buf)) } } diff --git a/src/bundler/transpiler.rs b/src/bundler/transpiler.rs index b34de7f8e6c2..1905834f1c7d 100644 --- a/src/bundler/transpiler.rs +++ b/src/bundler/transpiler.rs @@ -546,7 +546,9 @@ impl<'a> Transpiler<'a> { self.options.env.prefix = Box::from(b"BUN_".as_slice()); } - self.run_env_loader(self.options.env.disable_default_env_files)?; + self.run_env_loader(dot_env::SkipDefaultEnv::from_bool( + self.options.env.disable_default_env_files, + ))?; let env_loader = self.env_mut(); let mut is_production = env_loader.is_production(); @@ -673,10 +675,13 @@ fn merge_tsconfig_jsx_into(tsconfig: &TSConfigJSON, out: &mut crate::options_imp *out = tsconfig.merge_jsx(core::mem::take(out)); } +bun_core::bool_enum!(pub AutoJsx); +bun_core::bool_enum!(KeepJsonAndTomlAsOneStatement); + impl<'a> Transpiler<'a> { /// 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) { + pub fn configure_linker_with_auto_jsx(&mut self, auto_jsx: AutoJsx) { // `Linker::init` dropped its `arena` arg (linker.rs:172 // — global mimalloc). `crate::linker::Linker` stores raw pointers // so `&mut self.options` etc. coerce directly. Self-reference is @@ -697,7 +702,7 @@ impl<'a> Transpiler<'a> { self.fs, ); - if auto_jsx { + if auto_jsx == AutoJsx::Yes { // Most of the time, this will already be cached let top_level_dir = self.fs().top_level_dir; if let Ok(Some(root_dir)) = self.resolver.read_dir_info(top_level_dir) { @@ -719,12 +724,15 @@ impl<'a> Transpiler<'a> { /// [`Self::configure_linker_with_auto_jsx`] with `auto_jsx = true`. #[inline] pub fn configure_linker(&mut self) { - self.configure_linker_with_auto_jsx(true); + self.configure_linker_with_auto_jsx(AutoJsx::Yes); } /// Load `.env` files into the env loader according to /// `options.env.behavior`. - pub fn run_env_loader(&mut self, skip_default_env: bool) -> crate::Result<()> { + pub fn run_env_loader( + &mut self, + skip_default_env: dot_env::SkipDefaultEnv, + ) -> crate::Result<()> { 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. @@ -1445,7 +1453,7 @@ impl<'a> Transpiler<'a> { self.fs_mut(), path.text, dirname_fd, - USE_SHARED_BUFFER, + Fs::UseSharedBuffer::from_bool(USE_SHARED_BUFFER), file_descriptor, if USE_SHARED_BUFFER { None } else { Some(arena) }, ) { @@ -1788,7 +1796,9 @@ impl<'a> Transpiler<'a> { source_backing, arena, log, - this_parse.keep_json_and_toml_as_one_statement, + KeepJsonAndTomlAsOneStatement::from_bool( + this_parse.keep_json_and_toml_as_one_statement, + ), ); } options::Loader::Text => { @@ -1838,8 +1848,10 @@ fn parse_data_loader<'a>( source_backing: resolver::cache::Contents, arena: &'a Arena, log: &mut bun_ast::Log, - keep_json_and_toml_as_one_statement: bool, + keep_json_and_toml_as_one_statement: KeepJsonAndTomlAsOneStatement, ) -> Option> { + let keep_json_and_toml_as_one_statement = + keep_json_and_toml_as_one_statement == KeepJsonAndTomlAsOneStatement::Yes; // `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 @@ -1859,10 +1871,13 @@ fn parse_data_loader<'a>( Err(_) => return None, } } - options::Loader::Toml => match bun_parsers::toml::TOML::parse(source, log, arena, false) { - Ok(e) => e, - Err(_) => return None, - }, + options::Loader::Toml => { + use bun_parsers::toml::{RedactLogs, TOML}; + match TOML::parse(source, log, arena, RedactLogs::No) { + Ok(e) => e, + Err(_) => return None, + } + } options::Loader::Yaml => match bun_parsers::yaml::YAML::parse( source, log, @@ -2001,7 +2016,8 @@ fn parse_data_loader<'a>( ..Default::default() }; - let ref_ = bun_ast::Ref::init(count as u32, 0, false); + let ref_ = + bun_ast::Ref::init(count as u32, 0, bun_ast::IsSourceContentsSlice::No); decls[count] = bun_ast::G::Decl { binding: bun_ast::Binding::alloc( arena, @@ -2993,7 +3009,7 @@ impl<'a> Transpiler<'a> { self.fs_mut(), file_path_text, dirname_fd, - false, + Fs::UseSharedBuffer::No, None, None, ) { diff --git a/src/bundler_jsc/analyze_jsc.rs b/src/bundler_jsc/analyze_jsc.rs index 838efaa6c09f..72b86e82ffed 100644 --- a/src/bundler_jsc/analyze_jsc.rs +++ b/src/bundler_jsc/analyze_jsc.rs @@ -120,8 +120,8 @@ extern "C" fn zig__ModuleInfoDeserialized__toJSModuleRecord( // 0 = ModulePhase::Evaluation, 1 = ModulePhase::Defer. Reject anything // else — the buffer may have come from an on-disk cache. let phase_defer = match reqp { - 0 => false, - 1 => true, + 0 => ModulePhase::Evaluation, + 1 => ModulePhase::Defer, _ => return core::ptr::null_mut(), }; match reqv { @@ -400,6 +400,8 @@ impl JSModuleRecord { } } +bun_core::bool_enum!(ModulePhase { Evaluation, Defer }); + // Thin method shims over the raw `*mut JSModuleRecord` returned by `create`. // These take `*mut Self` raw-ptr receivers to avoid materializing `&mut` aliases. trait JSModuleRecordExt { @@ -434,32 +436,32 @@ trait JSModuleRecordExt { self, ia: *mut IdentifierArray, module_name: StringID, - phase_defer: bool, + phase_defer: ModulePhase, ); fn add_requested_module_java_script( self, ia: *mut IdentifierArray, module_name: StringID, - phase_defer: bool, + phase_defer: ModulePhase, ); fn add_requested_module_web_assembly( self, ia: *mut IdentifierArray, module_name: StringID, - phase_defer: bool, + phase_defer: ModulePhase, ); fn add_requested_module_json( self, ia: *mut IdentifierArray, module_name: StringID, - phase_defer: bool, + phase_defer: ModulePhase, ); fn add_requested_module_host_defined( self, ia: *mut IdentifierArray, module_name: StringID, host_defined_import_type: StringID, - phase_defer: bool, + phase_defer: ModulePhase, ); fn add_import_entry_single( self, @@ -562,7 +564,7 @@ impl JSModuleRecordExt for *mut JSModuleRecord { self, ia: *mut IdentifierArray, module_name: StringID, - phase_defer: bool, + phase_defer: ModulePhase, ) { // SAFETY: `self` is the non-null record from `JSModuleRecord::create`; `ia` is kept alive by the caller's scopeguard. unsafe { @@ -570,7 +572,7 @@ impl JSModuleRecordExt for *mut JSModuleRecord { self, ia, module_name, - phase_defer, + phase_defer == ModulePhase::Defer, ) } } @@ -579,11 +581,16 @@ impl JSModuleRecordExt for *mut JSModuleRecord { self, ia: *mut IdentifierArray, module_name: StringID, - phase_defer: bool, + phase_defer: ModulePhase, ) { // SAFETY: `self` is the non-null record from `JSModuleRecord::create`; `ia` is kept alive by the caller's scopeguard. unsafe { - JSC_JSModuleRecord__addRequestedModuleJavaScript(self, ia, module_name, phase_defer) + JSC_JSModuleRecord__addRequestedModuleJavaScript( + self, + ia, + module_name, + phase_defer == ModulePhase::Defer, + ) } } #[inline] @@ -591,11 +598,16 @@ impl JSModuleRecordExt for *mut JSModuleRecord { self, ia: *mut IdentifierArray, module_name: StringID, - phase_defer: bool, + phase_defer: ModulePhase, ) { // SAFETY: `self` is the non-null record from `JSModuleRecord::create`; `ia` is kept alive by the caller's scopeguard. unsafe { - JSC_JSModuleRecord__addRequestedModuleWebAssembly(self, ia, module_name, phase_defer) + JSC_JSModuleRecord__addRequestedModuleWebAssembly( + self, + ia, + module_name, + phase_defer == ModulePhase::Defer, + ) } } #[inline] @@ -603,10 +615,17 @@ impl JSModuleRecordExt for *mut JSModuleRecord { self, ia: *mut IdentifierArray, module_name: StringID, - phase_defer: bool, + phase_defer: ModulePhase, ) { // SAFETY: `self` is the non-null record from `JSModuleRecord::create`; `ia` is kept alive by the caller's scopeguard. - unsafe { JSC_JSModuleRecord__addRequestedModuleJSON(self, ia, module_name, phase_defer) } + unsafe { + JSC_JSModuleRecord__addRequestedModuleJSON( + self, + ia, + module_name, + phase_defer == ModulePhase::Defer, + ) + } } #[inline] fn add_requested_module_host_defined( @@ -614,7 +633,7 @@ impl JSModuleRecordExt for *mut JSModuleRecord { ia: *mut IdentifierArray, module_name: StringID, host_defined_import_type: StringID, - phase_defer: bool, + phase_defer: ModulePhase, ) { // SAFETY: `self` is the non-null record from `JSModuleRecord::create`; `ia` is kept alive by the caller's scopeguard. unsafe { @@ -623,7 +642,7 @@ impl JSModuleRecordExt for *mut JSModuleRecord { ia, module_name, host_defined_import_type, - phase_defer, + phase_defer == ModulePhase::Defer, ) } } diff --git a/src/bunfig/arguments.rs b/src/bunfig/arguments.rs index d0a388d3ef7c..eeb027344a9e 100644 --- a/src/bunfig/arguments.rs +++ b/src/bunfig/arguments.rs @@ -35,9 +35,11 @@ fn get_home_config_path(buf: &mut PathBuffer) -> Option<&ZStr> { None } +bun_core::bool_enum!(pub AutoLoaded); + fn load_bunfig( cmd: CommandTag, - auto_loaded: bool, + auto_loaded: AutoLoaded, config_path: &ZStr, ctx: Context<'_>, ) -> Result<(), crate::Error> { @@ -45,7 +47,7 @@ fn load_bunfig( match bun_ast::to_source(config_path, bun_ast::ToSourceOptions { convert_bom: true }) { Ok(s) => s, Err(err) => { - if auto_loaded { + if auto_loaded == AutoLoaded::Yes { return Ok(()); } bun_core::pretty_errorln!( @@ -88,14 +90,14 @@ fn load_global_bunfig(cmd: CommandTag, ctx: Context<'_>) -> Result<(), crate::Er let mut config_buf = PathBuffer::uninit(); if let Some(path) = get_home_config_path(&mut config_buf) { - load_bunfig(cmd, true, path, ctx)?; + load_bunfig(cmd, AutoLoaded::Yes, path, ctx)?; } Ok(()) } pub fn load_config_path( cmd: CommandTag, - auto_loaded: bool, + auto_loaded: AutoLoaded, config_path: &ZStr, ctx: Context<'_>, ) -> Result<(), crate::Error> { @@ -105,7 +107,7 @@ pub fn load_config_path( // 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 { + if auto_loaded == AutoLoaded::Yes { return Ok(()); } @@ -157,7 +159,7 @@ pub fn load_config( ctx.has_loaded_global_config = true; if let Some(path) = get_home_config_path(&mut config_buf) { - if let Err(err) = load_config_path(cmd, true, path, ctx) { + if let Err(err) = load_config_path(cmd, AutoLoaded::Yes, path, ctx) { report_bunfig_load_failure(ctx.log, err); } } @@ -166,7 +168,7 @@ pub fn load_config( let mut config_path_: &[u8] = user_config_path_.unwrap_or(b""); - let mut auto_loaded: bool = false; + let mut auto_loaded = AutoLoaded::No; if config_path_.is_empty() && (user_config_path_.is_some() || ALWAYS_LOADS_CONFIG[cmd] @@ -181,7 +183,7 @@ pub fn load_config( ))) { config_path_ = b"bunfig.toml"; - auto_loaded = true; + auto_loaded = AutoLoaded::Yes; } if config_path_.is_empty() { diff --git a/src/bunfig/bunfig.rs b/src/bunfig/bunfig.rs index 34044429cc60..413aba20e3fb 100644 --- a/src/bunfig/bunfig.rs +++ b/src/bunfig/bunfig.rs @@ -14,7 +14,7 @@ use core::sync::atomic::Ordering; use bun_alloc::Arena as Bump; use bun_ast::{E, Expr, ExprTag, expr::Data as ExprData}; use bun_parsers::json as json_parser; -use bun_parsers::toml::TOML; +use bun_parsers::toml::{RedactLogs, TOML}; use bun_install_types::NodeLinker::FromExprError; use bun_options_types::LoaderExt as _; @@ -1123,7 +1123,7 @@ impl Bunfig { let is_toml = ext.len() > 1 && &ext[1..] == b"toml"; let expr = if is_toml { - match TOML::parse(source, log, &bump, true) { + match TOML::parse(source, log, &bump, RedactLogs::Yes) { Ok(e) => e, Err(e) => { if log.errors + log.warnings == log_count { diff --git a/src/bunfig/lib.rs b/src/bunfig/lib.rs index 4786aad2517d..0396bb661a79 100644 --- a/src/bunfig/lib.rs +++ b/src/bunfig/lib.rs @@ -12,5 +12,5 @@ pub mod arguments; pub mod bunfig; pub mod error; -pub use arguments::{load_config, load_config_path, load_config_with_cmd_args}; +pub use arguments::{AutoLoaded, load_config, load_config_path, load_config_with_cmd_args}; pub use error::{Error, Result}; diff --git a/src/clap/lib.rs b/src/clap/lib.rs index 9e1f9bc8d545..ed011ecab6d4 100644 --- a/src/clap/lib.rs +++ b/src/clap/lib.rs @@ -407,7 +407,10 @@ fn get_help_simple(param: &Param) -> &'static [u8] { #[allow(clippy::disallowed_methods)] // template is a runtime help-string parameter fn pretty_help_desc(param: &Param) -> std::borrow::Cow<'static, [u8]> { if Output::enable_ansi_colors_stdout() { - std::borrow::Cow::Owned(bun_core::output::pretty_fmt_runtime(param.id.msg, true)) + std::borrow::Cow::Owned(bun_core::output::pretty_fmt_runtime( + param.id.msg, + bun_core::output::AnsiColors::Enabled, + )) } else { std::borrow::Cow::Borrowed(param.id.msg_plain) } diff --git a/src/collections/lib.rs b/src/collections/lib.rs index 82774f7bccf6..4bbd0afbb124 100644 --- a/src/collections/lib.rs +++ b/src/collections/lib.rs @@ -157,7 +157,7 @@ pub use array_hash_map::{ pub use hashbrown; pub mod string_map; -pub use string_map::StringMap; +pub use string_map::{DupeKeys, StringMap}; // Re-export from bun_ptr so callers can name it as `bun_collections::TaggedPtrUnion` // (PORTING.md groups it under Collections; the impl lives in src/ptr/). diff --git a/src/collections/string_map.rs b/src/collections/string_map.rs index 64ec506190ba..30f57a534f1b 100644 --- a/src/collections/string_map.rs +++ b/src/collections/string_map.rs @@ -5,19 +5,21 @@ use bun_alloc::AllocError; use crate::array_hash_map::StringArrayHashMap; +bun_core::bool_enum!(pub DupeKeys); + pub struct StringMap { pub(crate) map: StringArrayHashMap>, - pub dupe_keys: bool, + pub dupe_keys: DupeKeys, } impl Default for StringMap { fn default() -> Self { - Self::init(false) + Self::init(DupeKeys::No) } } impl StringMap { - pub fn init(dupe_keys: bool) -> Self { + pub fn init(dupe_keys: DupeKeys) -> Self { Self { map: StringArrayHashMap::default(), dupe_keys, diff --git a/src/crash_handler/lib.rs b/src/crash_handler/lib.rs index a5cb8c8966ea..7b84fa565f2a 100644 --- a/src/crash_handler/lib.rs +++ b/src/crash_handler/lib.rs @@ -1209,7 +1209,7 @@ mod draft { ); Output::flush(); - bun_core::reload_process(false, true); + bun_core::reload_process(bun_core::ClearTerminal::No, bun_core::MayReturn::Yes); } } t @ (1 | 2) => { diff --git a/src/css/css_modules.rs b/src/css/css_modules.rs index 314c1e569e9e..954dbb30c720 100644 --- a/src/css/css_modules.rs +++ b/src/css/css_modules.rs @@ -48,7 +48,7 @@ impl<'a> CssModule<'a> { hashes.push(hash( bump, format_args!("{}", bstr::BStr::new(source)), - matches!(config.pattern.segments.at(0), Segment::Hash), + AtStart::from_bool(matches!(config.pattern.segments.at(0), Segment::Hash)), )); } break 'hashes hashes; @@ -158,7 +158,7 @@ impl<'a> CssModule<'a> { bstr::BStr::new(name), bstr::BStr::new(key) ), - false, + AtStart::No, ); // Build `--{the_hash}` as a bump Vec — a plain concat @@ -288,6 +288,8 @@ impl Default for Pattern { } } +bun_core::bool_enum!(pub ReplaceDots); + impl Pattern { /// Write the substituted pattern to a destination. pub(crate) fn write( @@ -295,26 +297,26 @@ impl Pattern { hash_: &[u8], path: &[u8], local: &[u8], - mut writefn: impl FnMut(&[u8], /* replace_dots: */ bool), + mut writefn: impl FnMut(&[u8], ReplaceDots), ) { for segment in self.segments.slice() { match segment { Segment::Literal(s) => { - writefn(s, false); + writefn(s, ReplaceDots::No); } Segment::Name => { let stem = bun_paths::stem(path); if bun_core::index_of(stem, b".").is_some() { - writefn(stem, true); + writefn(stem, ReplaceDots::Yes); } else { - writefn(stem, false); + writefn(stem, ReplaceDots::No); } } Segment::Local => { - writefn(local, false); + writefn(local, ReplaceDots::No); } Segment::Hash => { - writefn(hash_, false); + writefn(hash_, ReplaceDots::No); } } } @@ -329,21 +331,26 @@ impl Pattern { local: &[u8], ) -> &'a [u8] { let mut res: BumpVec<'a, u8> = BumpVec::new_in(bump); - self.write(hash_, path, local, |slice: &[u8], replace_dots: bool| { - res.extend_from_slice(prefix); - if replace_dots { - let start = res.len(); - res.extend_from_slice(slice); - let end = res.len(); - for c in &mut res[start..end] { - if *c == b'.' { - *c = b'-'; + self.write( + hash_, + path, + local, + |slice: &[u8], replace_dots: ReplaceDots| { + res.extend_from_slice(prefix); + if replace_dots == ReplaceDots::Yes { + let start = res.len(); + res.extend_from_slice(slice); + let end = res.len(); + for c in &mut res[start..end] { + if *c == b'.' { + *c = b'-'; + } } + return; } - return; - } - res.extend_from_slice(slice); - }); + res.extend_from_slice(slice); + }, + ); res.into_bump_slice() } @@ -356,20 +363,25 @@ impl Pattern { local: &[u8], ) -> &'a [u8] { let mut res = res_; - self.write(hash_, path, local, |slice: &[u8], replace_dots: bool| { - if replace_dots { - let start = res.len(); - res.extend_from_slice(slice); - let end = res.len(); - for c in &mut res[start..end] { - if *c == b'.' { - *c = b'-'; + self.write( + hash_, + path, + local, + |slice: &[u8], replace_dots: ReplaceDots| { + if replace_dots == ReplaceDots::Yes { + let start = res.len(); + res.extend_from_slice(slice); + let end = res.len(); + for c in &mut res[start..end] { + if *c == b'.' { + *c = b'-'; + } } + return; } - return; - } - res.extend_from_slice(slice); - }); + res.extend_from_slice(slice); + }, + ); res.into_bump_slice() } @@ -422,12 +434,14 @@ pub enum CssModuleReference<'a> { }, } +bun_core::bool_enum!(pub(crate) AtStart); + /// LAYERING: canonical implementation lives in `bun_base64::wyhash_url_safe` /// (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. #[inline] -pub(crate) fn hash<'a>(bump: &'a Bump, args: Arguments<'_>, at_start: bool) -> &'a [u8] { - bun_base64::wyhash_url_safe(bump, args, at_start) +pub(crate) fn hash<'a>(bump: &'a Bump, args: Arguments<'_>, at_start: AtStart) -> &'a [u8] { + bun_base64::wyhash_url_safe(bump, args, at_start == AtStart::Yes) } diff --git a/src/css/css_parser.rs b/src/css/css_parser.rs index b2cfade3f66e..c2c5634efdf7 100644 --- a/src/css/css_parser.rs +++ b/src/css/css_parser.rs @@ -36,7 +36,10 @@ pub use crate::error::{ pub use crate::generics::{self as generic, implement_deep_clone, implement_hash}; pub use crate::logical::{self, PropertyCategory}; pub use crate::prefixes; -pub use crate::printer::{self as css_printer, ImportInfo, Printer, PrinterOptions}; +pub use crate::printer::{ + self as css_printer, HandleCssModule, ImportInfo, IsDeclaration, Printer, PrinterOptions, + WsBefore, +}; pub use crate::small_list::SmallList; pub use crate::targets::{self, Features, Targets}; @@ -62,7 +65,7 @@ pub use crate::properties::{ }; pub use crate::rules::custom_media::CustomMediaRule as CustomMedia; pub use crate::rules::{ - self as css_rules, CssRule, CssRuleList, Location, MinifyContext, StyleContext, + self as css_rules, CssRule, CssRuleList, Location, MinifyContext, ParentIsUnused, StyleContext, import::{ImportConditions, ImportRule}, layer::{LayerBlockRule, LayerName, LayerStatementRule}, namespace::NamespaceRule, @@ -393,7 +396,7 @@ fn parse_custom_at_rule_without_block( start: &ParserState, options: &ParserOptions, at_rule_parser: &mut T, - is_nested: bool, + is_nested: IsNested, ) -> Maybe, ()> { match T::rule_without_block(at_rule_parser, prelude, start, options, is_nested) { Ok(v) => Ok(CssRule::Custom(v)), @@ -407,7 +410,7 @@ fn parse_custom_at_rule_body( start: &ParserState, options: &ParserOptions, at_rule_parser: &mut T, - is_nested: bool, + is_nested: IsNested, ) -> CssResult { match T::parse_block(at_rule_parser, prelude, start, input, options, is_nested) { Ok(vv) => Ok(vv), @@ -631,6 +634,8 @@ impl DefaultAtRule { } } +bun_core::bool_enum!(pub IsNested); + /// Same as `AtRuleParser` but modified to provide parser options. /// Also added: `on_import_rule` to handle `@import` rules. pub trait CustomAtRuleParser { @@ -649,7 +654,7 @@ pub trait CustomAtRuleParser { prelude: Self::Prelude, start: &ParserState, options: &ParserOptions, - is_nested: bool, + is_nested: IsNested, ) -> Maybe; fn parse_block( @@ -658,7 +663,7 @@ pub trait CustomAtRuleParser { start: &ParserState, input: &mut Parser, options: &ParserOptions, - is_nested: bool, + is_nested: IsNested, ) -> CssResult; fn on_import_rule(this: &mut Self, import_rule: &mut ImportRule, start: u32, end: u32); @@ -718,7 +723,7 @@ impl CustomAtRuleParser for DefaultAtRuleParser { _: &ParserState, input: &mut Parser, _: &ParserOptions, - _: bool, + _: IsNested, ) -> CssResult { Err(input.new_error(BasicParseErrorKind::at_rule_body_invalid)) } @@ -728,7 +733,7 @@ impl CustomAtRuleParser for DefaultAtRuleParser { _: (), _: &ParserState, _: &ParserOptions, - _: bool, + _: IsNested, ) -> Maybe { Err(()) } @@ -779,7 +784,7 @@ impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { _: &ParserState, input: &mut Parser, _: &ParserOptions, - _: bool, + _: IsNested, ) -> CssResult { Err(input.new_error(BasicParseErrorKind::at_rule_body_invalid)) } @@ -789,7 +794,7 @@ impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { _prelude: (), _: &ParserState, _: &ParserOptions, - _: bool, + _: IsNested, ) -> Maybe { Err(()) } @@ -1145,7 +1150,7 @@ where self.any_rule_so_far = true; if first_stylesheet_rule - && strings::eql_case_insensitive_ascii(name, b"charset", true) + && strings::eql_case_insensitive_ascii(name, b"charset", strings::CheckLen::Yes) { let delimiters = Delimiters::SEMICOLON | Delimiters::CLOSE_CURLY_BRACKET; let _ = self @@ -1433,12 +1438,15 @@ mod rule_parsers { // ── NestedRuleParser behavior (struct hoisted above) ───────────────────────── + bun_core::bool_enum!(pub(crate) IsStyleRule); + impl<'a, T: CustomAtRuleParser> NestedRuleParser<'a, T> { pub(crate) fn parse_nested( &mut self, input: &mut Parser, - is_style_rule: bool, + is_style_rule: IsStyleRule, ) -> CssResult<(DeclarationBlock<'static>, CssRuleList)> { + let is_style_rule = is_style_rule == IsStyleRule::Yes; // TODO: think about memory management in error cases let mut rules = CssRuleList::::default(); let composes_state = if self.is_in_style_rule @@ -1530,7 +1538,7 @@ mod rule_parsers { // Declarations can be immediately within @media and @supports blocks // that are nested within a parent style rule. These act the same way // as if they were nested within a `& { ... }` block. - let (declarations, mut rules) = self.parse_nested(input, false)?; + let (declarations, mut rules) = self.parse_nested(input, IsStyleRule::No)?; if declarations.len() > 0 { rules.v.insert( @@ -1896,7 +1904,7 @@ mod rule_parsers { Ok(()) } AtRulePrelude::Nest(selectors) => { - let (declarations, rules) = this.parse_nested(input, true)?; + let (declarations, rules) = this.parse_nested(input, IsStyleRule::Yes)?; this.rules .v .push(CssRule::Nesting(css_rules::nesting::NestingRule { @@ -1928,7 +1936,7 @@ mod rule_parsers { start, this.options, this.at_rule_parser, - this.is_in_style_rule, + IsNested::from_bool(this.is_in_style_rule), )?)); Ok(()) } @@ -1970,7 +1978,7 @@ mod rule_parsers { start, this.options, this.at_rule_parser, - this.is_in_style_rule, + IsNested::from_bool(this.is_in_style_rule), )?; this.rules.v.push(rule); Ok(()) @@ -2072,7 +2080,7 @@ mod rule_parsers { } } let location = input.position(); - let (declarations, rules) = this.parse_nested(input, true)?; + let (declarations, rules) = this.parse_nested(input, IsStyleRule::Yes)?; // We parsed a style rule with the `composes` property. Track which // properties it used so we can validate it later. @@ -2427,7 +2435,11 @@ mod stylesheet_impl { selector_expansion_total: 0, }; - if self.rules.minify(&mut minify_ctx, false).is_err() { + if self + .rules + .minify(&mut minify_ctx, ParentIsUnused::No) + .is_err() + { // Rule-level minify signals failure with the unit `MinifyErr` // and records the diagnostic out-of-band on the context. debug_assert!(minify_ctx.err.is_some()); @@ -6080,7 +6092,7 @@ pub mod to_css { for (idx, val) in this.iter().enumerate() { val.to_css(dest)?; if idx < len - 1 { - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; } } Ok(()) diff --git a/src/css/declaration.rs b/src/css/declaration.rs index 9ced7a46cd18..96bf9fedd13c 100644 --- a/src/css/declaration.rs +++ b/src/css/declaration.rs @@ -3,6 +3,7 @@ use bun_alloc::Arena as Bump; use bun_alloc::ArenaVecExt as _; use css::{CssResult as Result, PrintErr, Printer}; +use crate::css_properties::Important; use crate::css_properties::align::AlignHandler; use crate::css_properties::background::BackgroundHandler; use crate::css_properties::border::BorderHandler; @@ -70,10 +71,10 @@ impl<'bump> DeclarationBlock<'bump> { decls: &mut DeclarationList<'bump>, ctx: &mut css::PropertyHandlerContext, hndlr: &mut DeclarationHandler<'bump>, - important: bool, + important: Important, ) { for prop in decls.iter_mut() { - ctx.is_important = important; + ctx.is_important = important == Important::Yes; let handled = hndlr.handle_property(prop, ctx); @@ -92,9 +93,9 @@ impl<'bump> DeclarationBlock<'bump> { &mut self.important_declarations, context, important_handler, - true, + Important::Yes, ); - handle(&mut self.declarations, context, handler, false); + handle(&mut self.declarations, context, handler, Important::No); handler.finalize(context); important_handler.finalize(context); @@ -120,7 +121,7 @@ impl<'bump> DeclarationBlock<'bump> { let mut i: usize = 0; for decl in self.declarations.iter() { - decl.to_css(dest, false)?; + decl.to_css(dest, Important::No)?; if i != length - 1 { dest.write_char(b';')?; dest.whitespace()?; @@ -128,7 +129,7 @@ impl<'bump> DeclarationBlock<'bump> { i += 1; } for decl in self.important_declarations.iter() { - decl.to_css(dest, true)?; + decl.to_css(dest, Important::Yes)?; if i != length - 1 { dest.write_char(b';')?; dest.whitespace()?; diff --git a/src/css/dependencies.rs b/src/css/dependencies.rs index f0682860c504..0d3fc1226dc3 100644 --- a/src/css/dependencies.rs +++ b/src/css/dependencies.rs @@ -1,6 +1,7 @@ //! CSS dependency tracking — `@import` and `url()` references collected during printing. use crate::SourceLocation; +use crate::css_modules::AtStart; /// Options for `analyze_dependencies` in `PrinterOptions`. pub struct DependencyOptions { @@ -54,7 +55,7 @@ impl ImportDependency { bstr::BStr::new(filename), bstr::BStr::new(rule.url) ), - false, + AtStart::No, ); ImportDependency { @@ -81,7 +82,7 @@ impl UrlDependency { let placeholder = crate::css_modules::hash( bump, format_args!("{}_{}", bstr::BStr::new(filename), bstr::BStr::new(theurl)), - false, + AtStart::No, ); UrlDependency { placeholder: std::ptr::from_ref::<[u8]>(placeholder), diff --git a/src/css/lib.rs b/src/css/lib.rs index 771ffc9a3999..d830cb22c1d8 100644 --- a/src/css/lib.rs +++ b/src/css/lib.rs @@ -187,7 +187,9 @@ pub use css_parser::{ DefaultAtRule, LocalsResultsMap, MinifyOptions, Parser, ParserFlags, ParserInput, ParserOptions, StyleAttribute, StyleSheet, StylesheetExtra, ToCssResult, }; -pub use printer::{ImportInfo, Printer, PrinterOptions, PseudoClasses}; +pub use printer::{ + HandleCssModule, ImportInfo, IsDeclaration, Printer, PrinterOptions, PseudoClasses, WsBefore, +}; /// Dependent crates name this `ImportRecordHandler`; the surviving type is /// `printer::ImportInfo`, exposed under both names. pub type ImportRecordHandler<'a> = printer::ImportInfo<'a>; diff --git a/src/css/media_query.rs b/src/css/media_query.rs index 24c9e39d3fe8..93b1e593014b 100644 --- a/src/css/media_query.rs +++ b/src/css/media_query.rs @@ -1,9 +1,9 @@ //! CSS [media queries](https://drafts.csswg.org/mediaqueries/). use crate as css; -use crate::css_properties::custom::EnvironmentVariable; +use crate::css_properties::custom::{EnvironmentVariable, IsCustomProperty}; use crate::css_values::ident::{DashedIdent, Ident}; -use crate::{Parser, PrintErr, Printer, Result}; +use crate::{Parser, PrintErr, Printer, Result, WsBefore}; use bun_alloc::ArenaPtr; // Strings here borrow parser input/arena memory but the structs carry no lifetime @@ -887,7 +887,7 @@ impl QueryFeature { } QueryFeature::Plain { name, value } => { name.to_css(dest)?; - dest.delim(b':', false)?; + dest.delim(b':', WsBefore::No)?; value.to_css(dest)?; } QueryFeature::Range { @@ -1034,14 +1034,14 @@ impl MediaFeatureComparison { // 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), + MediaFeatureComparison::Equal => dest.delim(b'-', WsBefore::Yes), + MediaFeatureComparison::GreaterThan => dest.delim(b'>', WsBefore::Yes), MediaFeatureComparison::GreaterThanEqual => { dest.whitespace()?; dest.write_str(">=")?; dest.whitespace() } - MediaFeatureComparison::LessThan => dest.delim(b'<', true), + MediaFeatureComparison::LessThan => dest.delim(b'<', WsBefore::Yes), MediaFeatureComparison::LessThanEqual => { dest.whitespace()?; dest.write_str("<=")?; @@ -1077,7 +1077,7 @@ impl MediaFeatureValue { MediaFeatureValue::Resolution(res) => res.to_css(dest), MediaFeatureValue::Ratio(ratio) => ratio.to_css(dest), MediaFeatureValue::Ident(id) => id.to_css(dest), - MediaFeatureValue::Env(env) => env.to_css(dest, false), + MediaFeatureValue::Env(env) => env.to_css(dest, IsCustomProperty::No), } } @@ -1220,7 +1220,7 @@ fn write_min_max( name.to_css(dest)?; } - dest.delim(b':', false)?; + dest.delim(b':', WsBefore::No)?; // `MediaFeatureValue: Clone`, so clone-by-value before adjusting. let adjusted: Option = match operator { @@ -1645,7 +1645,7 @@ impl QueryFeature { fn parse_name_first(input: &mut Parser, options: &css::ParserOptions) -> Result { let (name, legacy_op) = MediaFeatureName::::parse(input)?; - let operator = match input.try_parse(|i| consume_operation_or_colon(i, true)) { + let operator = match input.try_parse(|i| consume_operation_or_colon(i, AllowColon::Yes)) { Ok(operator) => operator, Err(_) => return Ok(QueryFeature::Boolean { name }), }; @@ -1696,7 +1696,7 @@ impl QueryFeature { // Now we can parse the first value. let value = MediaFeatureValue::parse(input, name.value_type(), options)?; - let operator = consume_operation_or_colon(input, false)?; + let operator = consume_operation_or_colon(input, AllowColon::No)?; // Skip over the feature name again. { @@ -1708,7 +1708,9 @@ impl QueryFeature { return Err(input.new_custom_error(css::ParserError::invalid_media_query)); } - if let Ok(end_operator_) = input.try_parse(|i| consume_operation_or_colon(i, false)) { + if let Ok(end_operator_) = + input.try_parse(|i| consume_operation_or_colon(i, AllowColon::No)) + { let start_operator = operator.unwrap(); let end_operator = end_operator_.unwrap(); // Start and end operators must be matching. @@ -1755,20 +1757,22 @@ impl QueryFeature { } } +bun_core::bool_enum!(AllowColon); + /// Consumes an operation or a colon, or returns an error. /// /// Returns `Ok(None)` /// when a colon was consumed (and `allow_colon`); `Ok(Some(op))` for `<`/`>`/`=`. fn consume_operation_or_colon( input: &mut Parser, - allow_colon: bool, + allow_colon: AllowColon, ) -> Result> { let location = input.current_source_location(); let first_delim: u32 = { let loc = input.current_source_location(); let next_token = input.next()?.clone(); match next_token { - css::Token::Colon if allow_colon => return Ok(None), + css::Token::Colon if allow_colon == AllowColon::Yes => return Ok(None), css::Token::Delim(oper) => oper, _ => return Err(loc.new_unexpected_token_error(next_token)), } diff --git a/src/css/printer.rs b/src/css/printer.rs index c37427280041..9f6adddd93bb 100644 --- a/src/css/printer.rs +++ b/src/css/printer.rs @@ -5,6 +5,7 @@ use bun_alloc::ArenaVec as BumpVec; use bun_ast::ImportRecord; use bun_core::strings; +use crate::css_modules::ReplaceDots; use crate::css_parser as css; use crate::values as css_values; @@ -102,6 +103,10 @@ impl<'a> ImportInfo<'a> { } } +bun_core::bool_enum!(pub WsBefore); +bun_core::bool_enum!(pub HandleCssModule); +bun_core::bool_enum!(pub IsDeclaration); + /// A `Printer` represents a destination to output serialized CSS, as used in /// the [ToCss](super::traits::ToCss) trait. It can wrap any destination that /// implements [std::fmt::Write](std::fmt::Write), such as a [String](String). @@ -519,9 +524,9 @@ impl<'a> Printer<'a> { pub(crate) fn write_ident_or_ref( &mut self, ident: css_values::ident::IdentOrRef, - handle_css_module: bool, + handle_css_module: HandleCssModule, ) -> PrintResult<()> { - if !handle_css_module { + if handle_css_module == HandleCssModule::No { if let Some(identifier) = ident.as_ident() { return self.serialize_identifier(identifier.v()); } else { @@ -548,9 +553,9 @@ impl<'a> Printer<'a> { pub(crate) fn write_ident( &mut self, ident: &'a [u8], - handle_css_module: bool, + handle_css_module: HandleCssModule, ) -> PrintResult<()> { - if handle_css_module { + if handle_css_module == HandleCssModule::Yes { if self.css_module.is_some() { // Copy the `'a`-lifetime references out of `css_module` up front so // the closure can hold the sole `&mut self`. @@ -568,14 +573,16 @@ impl<'a> Printer<'a> { let mut first = true; let mut err: Option = None; - config - .pattern - .write(hash, source, ident, |s1: &[u8], replace_dots: bool| { + config.pattern.write( + hash, + source, + ident, + |s1: &[u8], replace_dots: ReplaceDots| { if err.is_some() { return; } // PERF: stack fallback? - let s: &[u8] = if !replace_dots { + let s: &[u8] = if replace_dots == ReplaceDots::No { s1 } else { Printer::replace_dots(arena, s1) @@ -590,7 +597,8 @@ impl<'a> Printer<'a> { if r.is_err() { err = Some(PrintErr::CSSPrintError); } - }); + }, + ); if let Some(e) = err { return Err(e); } @@ -610,7 +618,7 @@ impl<'a> Printer<'a> { pub(crate) fn write_dashed_ident( &mut self, ident: &DashedIdent, - is_declaration: bool, + is_declaration: IsDeclaration, ) -> PrintResult<()> { self.write_str(b"--")?; @@ -642,11 +650,11 @@ impl<'a> Printer<'a> { hash, source, &ident_v[2..], - |s1: &[u8], replace_dots: bool| { + |s1: &[u8], replace_dots: ReplaceDots| { if err.is_some() { return; } - let s: &[u8] = if !replace_dots { + let s: &[u8] = if replace_dots == ReplaceDots::No { s1 } else { Printer::replace_dots(arena, s1) @@ -661,7 +669,7 @@ impl<'a> Printer<'a> { return Err(e); } - if is_declaration { + if is_declaration == IsDeclaration::Yes { let src_idx = self.loc.source_index; self.css_module .as_mut() @@ -700,8 +708,8 @@ impl<'a> Printer<'a> { /// Writes a delimiter character, followed by whitespace (depending on the `minify` option). /// If `ws_before` is true, then whitespace is also written before the delimiter. - pub(crate) fn delim(&mut self, delim_: u8, ws_before: bool) -> PrintResult<()> { - if ws_before { + pub(crate) fn delim(&mut self, delim_: u8, ws_before: WsBefore) -> PrintResult<()> { + if ws_before == WsBefore::Yes { self.whitespace()?; } self.write_char(delim_)?; @@ -781,7 +789,7 @@ impl<'a> Printer<'a> { I: IntoIterator, F: FnMut(&mut Self, I::Item) -> PrintResult<()>, { - self.write_separated(iter, |d| d.delim(b',', false), f) + self.write_separated(iter, |d| d.delim(b',', WsBefore::No), f) } pub(crate) fn with_context( diff --git a/src/css/properties/animation.rs b/src/css/properties/animation.rs index dfe3f6d08df8..6ce7fb70907d 100644 --- a/src/css/properties/animation.rs +++ b/src/css/properties/animation.rs @@ -5,7 +5,7 @@ use crate::css_values::ident::{CustomIdent, DashedIdent, is_reserved_custom_iden use crate::css_values::number::{CSSNumber, CSSNumberFns}; use crate::css_values::time::Time; use crate::generics::CssEql; -use crate::{Parser, PrintErr, Printer}; +use crate::{HandleCssModule, Parser, PrintErr, Printer}; use bun_core::strings; /// A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property. @@ -178,7 +178,9 @@ impl Animation { } if self.iteration_count != AnimationIterationCount::default() - || name_str.is_some_and(|n| strings::eql_case_insensitive_ascii(n, b"infinite", true)) + || name_str.is_some_and(|n| { + strings::eql_case_insensitive_ascii(n, b"infinite", strings::CheckLen::Yes) + }) { space!(); self.iteration_count.to_css(dest)?; @@ -202,7 +204,7 @@ impl Animation { if self.fill_mode != AnimationFillMode::default() || name_str.is_some_and(|n| { - !strings::eql_case_insensitive_ascii(n, b"none", true) + !strings::eql_case_insensitive_ascii(n, b"none", strings::CheckLen::Yes) && css::parse_utility::parse_string::( dest.arena, n, @@ -347,7 +349,10 @@ impl AnimationName { css_module.get_reference(arena, name, source_index); } } - return s.to_css_with_options(dest, css_module_animation_enabled); + return s.to_css_with_options( + dest, + HandleCssModule::from_bool(css_module_animation_enabled), + ); } AnimationName::String(s) => { // SAFETY: arena-owned slice valid for 'bump. @@ -368,7 +373,10 @@ impl AnimationName { return dest.serialize_string(name); } - return dest.write_ident(name, css_module_animation_enabled); + return dest.write_ident( + name, + HandleCssModule::from_bool(css_module_animation_enabled), + ); } } } diff --git a/src/css/properties/background.rs b/src/css/properties/background.rs index 71fd71f72bcf..887b78a29f20 100644 --- a/src/css/properties/background.rs +++ b/src/css/properties/background.rs @@ -1,5 +1,6 @@ #![warn(unused_must_use)] use crate as css; +use crate::WsBefore; use crate::css_values::color::ColorFallbackKind; use crate::css_values::color::CssColor; use crate::css_values::image::Image; @@ -156,7 +157,7 @@ impl Background { position.to_css(dest)?; if !self.size.eql(&BackgroundSize::default()) { - dest.delim(b'/', true)?; + dest.delim(b'/', WsBefore::Yes)?; self.size.to_css(dest)?; } diff --git a/src/css/properties/border_image.rs b/src/css/properties/border_image.rs index ea12e03c1d37..622dac483e3c 100644 --- a/src/css/properties/border_image.rs +++ b/src/css/properties/border_image.rs @@ -5,6 +5,7 @@ use crate::Result; use css::PrintErr; use css::Printer; use css::SmallList; +use css::WsBefore; use crate::generics::IsCompatible as _; use css::VendorPrefix; @@ -169,14 +170,14 @@ impl BorderImage { dest.write_str(" ")?; slice.to_css(dest)?; if has_width || has_outset { - dest.delim(b'/', true)?; + dest.delim(b'/', WsBefore::Yes)?; } if has_width { width.to_css(dest)?; } if has_outset { - dest.delim(b'/', true)?; + dest.delim(b'/', WsBefore::Yes)?; outset.to_css(dest)?; } } diff --git a/src/css/properties/border_radius.rs b/src/css/properties/border_radius.rs index b8df92a8e0f1..c393eb9ced4b 100644 --- a/src/css/properties/border_radius.rs +++ b/src/css/properties/border_radius.rs @@ -5,6 +5,7 @@ use crate::Printer; use crate::PropertyCategory; use crate::PropertyHandlerContext; use crate::VendorPrefix; +use crate::WsBefore; use crate::css_properties::{Property, PropertyId, PropertyIdTag}; use crate::css_values::length::LengthPercentage; use crate::css_values::rect::Rect; @@ -128,7 +129,7 @@ impl BorderRadius { write_rect(wt, wr, wb, wl, dest)?; if !(wt == ht && wr == hr && wb == hb && wl == hl) { - dest.delim(b'/', true)?; + dest.delim(b'/', WsBefore::Yes)?; write_rect(ht, hr, hb, hl, dest)?; } Ok(()) diff --git a/src/css/properties/custom.rs b/src/css/properties/custom.rs index 61aeedf3497a..98685a5fb324 100644 --- a/src/css/properties/custom.rs +++ b/src/css/properties/custom.rs @@ -11,7 +11,7 @@ use crate::PrintResult; use crate::Token; use crate::css_parser::{self, Delimiters, EnumProperty, Parser, ParserOptions, ParserState}; use crate::error::{BasicParseErrorKind, ParseError, ParserError, ParserErrorKind}; -use crate::printer::Printer; +use crate::printer::{HandleCssModule, IsDeclaration, Printer, WsBefore}; use crate::values as css_values; use css_values::angle::Angle; @@ -156,7 +156,10 @@ mod ext { }; // SAFETY: arena-owned slice valid for the printer's `'a` lifetime. let v: &'static [u8] = unsafe { crate::arena_str(this.v) }; - dest.write_ident(v, css_module_custom_idents_enabled) + dest.write_ident( + v, + HandleCssModule::from_bool(css_module_custom_idents_enabled), + ) } } @@ -296,10 +299,16 @@ pub struct TokenList { pub(crate) v: Vec, } +bun_core::bool_enum!(pub IsCustomProperty); + impl TokenList { // deinit(): body only freed owned `Vec` fields — handled by `Drop` on `Vec`. - pub fn to_css(&self, dest: &mut Printer, is_custom_property: bool) -> PrintResult<()> { + pub fn to_css( + &self, + dest: &mut Printer, + is_custom_property: IsCustomProperty, + ) -> PrintResult<()> { if !dest.minify && self.v.len() == 1 && self.v[0].is_whitespace() { return Ok(()); } @@ -317,7 +326,7 @@ impl TokenList { } TokenOrValue::Url(url) => { if dest.dependencies.is_some() - && is_custom_property + && is_custom_property == IsCustomProperty::Yes && !url.is_absolute(dest.get_import_records()?) { let pretty = std::ptr::from_ref::<[u8]>( @@ -364,7 +373,7 @@ impl TokenList { has_whitespace = false; } TokenOrValue::DashedIdent(v) => { - dest.write_dashed_ident(v, true)?; + dest.write_dashed_ident(v, IsDeclaration::Yes)?; has_whitespace = false; } TokenOrValue::AnimationName(v) => { @@ -382,7 +391,7 @@ impl TokenList { let ws_before = !has_whitespace && (*d == b'/' as u32 || *d == b'*' as u32); debug_assert!(*d <= 0x7F); - dest.delim(*d as u8, ws_before)?; + dest.delim(*d as u8, WsBefore::from_bool(ws_before))?; // `delim()` emits no surrounding whitespace when minifying, so // consecutive `/` and `*` delims would be printed adjacently and // form a `/*` or `*/` comment delimiter. Emit a real space when @@ -406,7 +415,7 @@ impl TokenList { has_whitespace = true; } Token::Comma => { - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; has_whitespace = true; } Token::CloseParen | Token::CloseSquare | Token::CloseCurly => { @@ -886,7 +895,7 @@ impl UnresolvedColor { // deinit(): body only freed owned `TokenList` fields — handled by `Drop`. - fn to_css(&self, dest: &mut Printer, is_custom_property: bool) -> PrintResult<()> { + fn to_css(&self, dest: &mut Printer, is_custom_property: IsCustomProperty) -> PrintResult<()> { fn conv(c: f32) -> i32 { css_values::color::clamp_unit_f32(c) as i32 } @@ -899,11 +908,11 @@ impl UnresolvedColor { { dest.write_str("rgba(")?; css_parser::to_css::integer(conv(*r), dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; css_parser::to_css::integer(conv(*g), dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; css_parser::to_css::integer(conv(*b), dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; alpha.to_css(dest, is_custom_property)?; dest.write_char(b')')?; return Ok(()); @@ -915,7 +924,7 @@ impl UnresolvedColor { css_parser::to_css::integer(conv(*g), dest)?; dest.write_char(b' ')?; css_parser::to_css::integer(conv(*b), dest)?; - dest.delim(b'/', true)?; + dest.delim(b'/', WsBefore::Yes)?; alpha.to_css(dest, is_custom_property)?; dest.write_char(b')') } @@ -926,11 +935,11 @@ impl UnresolvedColor { { dest.write_str("hsla(")?; CSSNumberFns::to_css(*h, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; Percentage { v: *s }.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; Percentage { v: *l }.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; alpha.to_css(dest, is_custom_property)?; dest.write_char(b')')?; return Ok(()); @@ -942,26 +951,26 @@ impl UnresolvedColor { Percentage { v: *s }.to_css(dest)?; dest.write_char(b' ')?; Percentage { v: *l }.to_css(dest)?; - dest.delim(b'/', true)?; + dest.delim(b'/', WsBefore::Yes)?; alpha.to_css(dest, is_custom_property)?; dest.write_char(b')') } UnresolvedColor::LightDark { light, dark } => { if !dest.targets.is_compatible(css::compat::Feature::LightDark) { dest.write_str("var(--buncss-light")?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; light.to_css(dest, is_custom_property)?; dest.write_char(b')')?; dest.whitespace()?; dest.write_str("var(--buncss-dark")?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; dark.to_css(dest, is_custom_property)?; return dest.write_char(b')'); } dest.write_str("light-dark(")?; light.to_css(dest, is_custom_property)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; dark.to_css(dest, is_custom_property)?; dest.write_char(b')') } @@ -975,15 +984,15 @@ impl UnresolvedColor { depth: usize, ) -> Result { use css_values::color::{ - ComponentParser, HSL, RgbComponents, SRGB, parse_hsl_hwb_components, - parse_rgb_components, + AllowNone, AllowsLegacy, ComponentParser, HSL, LegacySyntax, RgbComponents, SRGB, + parse_hsl_hwb_components, parse_rgb_components, }; - let mut parser = ComponentParser::new(false); + let mut parser = ComponentParser::new(AllowNone::No); crate::match_ignore_ascii_case! { f, { b"rgb" => return input.parse_nested_block(|input2| { parser.parse_relative::(input2, |i, p| { let RgbComponents { r, g, b, is_legacy } = parse_rgb_components(i, p)?; - if is_legacy { + if is_legacy == LegacySyntax::Yes { return Err(i.new_custom_error(ParserError::invalid_value)); } i.expect_delim(b'/')?; @@ -993,8 +1002,8 @@ impl UnresolvedColor { }), b"hsl" => return input.parse_nested_block(|input2| { parser.parse_relative::(input2, |i, p| { - let (h, s, l, is_legacy) = parse_hsl_hwb_components::(i, p, false)?; - if is_legacy { + let (h, s, l, is_legacy) = parse_hsl_hwb_components::(i, p, AllowsLegacy::No)?; + if is_legacy == LegacySyntax::Yes { return Err(i.new_custom_error(ParserError::invalid_value)); } i.expect_delim(b'/')?; @@ -1080,11 +1089,11 @@ impl Variable { Ok(Variable { name, fallback }) } - fn to_css(&self, dest: &mut Printer, is_custom_property: bool) -> PrintResult<()> { + fn to_css(&self, dest: &mut Printer, is_custom_property: IsCustomProperty) -> PrintResult<()> { dest.write_str("var(")?; ext::dashed_ident_ref_to_css(&self.name, dest)?; if let Some(fallback) = &self.fallback { - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; fallback.to_css(dest, is_custom_property)?; } dest.write_char(b')') @@ -1151,7 +1160,11 @@ impl EnvironmentVariable { }) } - pub(crate) fn to_css(&self, dest: &mut Printer, is_custom_property: bool) -> PrintResult<()> { + pub(crate) fn to_css( + &self, + dest: &mut Printer, + is_custom_property: IsCustomProperty, + ) -> PrintResult<()> { dest.write_str("env(")?; self.name.to_css(dest)?; @@ -1161,7 +1174,7 @@ impl EnvironmentVariable { } if let Some(fallback) = &self.fallback { - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; fallback.to_css(dest, is_custom_property)?; } @@ -1302,7 +1315,7 @@ pub struct Function { impl Function { // deinit(): body only freed owned `TokenList` field — handled by `Drop`. - fn to_css(&self, dest: &mut Printer, is_custom_property: bool) -> PrintResult<()> { + fn to_css(&self, dest: &mut Printer, is_custom_property: IsCustomProperty) -> PrintResult<()> { IdentFns::to_css(&self.name, dest)?; dest.write_char(b'(')?; self.arguments.to_css(dest, is_custom_property)?; @@ -1582,7 +1595,7 @@ impl CustomPropertyName { CustomPropertyName::Custom(custom) => { // DashedIdent.toCss → dest.writeDashedIdent(ident, true), // which applies CSS-Modules dashed-ident renaming. - dest.write_dashed_ident(custom, true) + dest.write_dashed_ident(custom, IsDeclaration::Yes) } CustomPropertyName::Unknown(unknown) => { // SAFETY: arena-owned slice valid for printer lifetime. diff --git a/src/css/properties/display.rs b/src/css/properties/display.rs index 25eb70aad4f6..d436e22cf8eb 100644 --- a/src/css/properties/display.rs +++ b/src/css/properties/display.rs @@ -132,22 +132,22 @@ impl DisplayPair { // 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; - let inside = if eq(ident, b"inline-block", true) { + use bun_core::{CheckLen, eql_case_insensitive_ascii as eq}; + let inside = if eq(ident, b"inline-block", CheckLen::Yes) { DisplayInside::FlowRoot - } else if eq(ident, b"inline-table", true) { + } else if eq(ident, b"inline-table", CheckLen::Yes) { DisplayInside::Table - } else if eq(ident, b"inline-flex", true) { + } else if eq(ident, b"inline-flex", CheckLen::Yes) { DisplayInside::Flex(VendorPrefix::NONE) - } else if eq(ident, b"-webkit-inline-flex", true) { + } else if eq(ident, b"-webkit-inline-flex", CheckLen::Yes) { DisplayInside::Flex(VendorPrefix::WEBKIT) - } else if eq(ident, b"-ms-inline-flexbox", true) { + } else if eq(ident, b"-ms-inline-flexbox", CheckLen::Yes) { DisplayInside::Flex(VendorPrefix::MS) - } else if eq(ident, b"-webkit-inline-box", true) { + } else if eq(ident, b"-webkit-inline-box", CheckLen::Yes) { DisplayInside::Box(VendorPrefix::WEBKIT) - } else if eq(ident, b"-moz-inline-box", true) { + } else if eq(ident, b"-moz-inline-box", CheckLen::Yes) { DisplayInside::Box(VendorPrefix::MOZ) - } else if eq(ident, b"inline-grid", true) { + } else if eq(ident, b"inline-grid", CheckLen::Yes) { DisplayInside::Grid } else { return Err(location.new_unexpected_token_error(css::Token::Ident(ident))); @@ -242,26 +242,26 @@ impl DisplayInside { let ident = input.expect_ident_cloned()?; // 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) { + use bun_core::{CheckLen, eql_case_insensitive_ascii as eq}; + Ok(if eq(ident, b"flow", CheckLen::Yes) { DisplayInside::Flow - } else if eq(ident, b"flow-root", true) { + } else if eq(ident, b"flow-root", CheckLen::Yes) { DisplayInside::FlowRoot - } else if eq(ident, b"table", true) { + } else if eq(ident, b"table", CheckLen::Yes) { DisplayInside::Table - } else if eq(ident, b"flex", true) { + } else if eq(ident, b"flex", CheckLen::Yes) { DisplayInside::Flex(VendorPrefix::NONE) - } else if eq(ident, b"-webkit-flex", true) { + } else if eq(ident, b"-webkit-flex", CheckLen::Yes) { DisplayInside::Flex(VendorPrefix::WEBKIT) - } else if eq(ident, b"-ms-flexbox", true) { + } else if eq(ident, b"-ms-flexbox", CheckLen::Yes) { DisplayInside::Flex(VendorPrefix::MS) - } else if eq(ident, b"-webkit-box", true) { + } else if eq(ident, b"-webkit-box", CheckLen::Yes) { DisplayInside::Box(VendorPrefix::WEBKIT) - } else if eq(ident, b"-moz-box", true) { + } else if eq(ident, b"-moz-box", CheckLen::Yes) { DisplayInside::Box(VendorPrefix::MOZ) - } else if eq(ident, b"grid", true) { + } else if eq(ident, b"grid", CheckLen::Yes) { DisplayInside::Grid - } else if eq(ident, b"ruby", true) { + } else if eq(ident, b"ruby", CheckLen::Yes) { DisplayInside::Ruby } else { return Err(location.new_unexpected_token_error(css::Token::Ident(ident))); diff --git a/src/css/properties/font.rs b/src/css/properties/font.rs index 1f97fadfa022..cac5a314d2bc 100644 --- a/src/css/properties/font.rs +++ b/src/css/properties/font.rs @@ -14,7 +14,7 @@ use crate::PrintResult; use crate::compat::Feature; use crate::css_parser as css; use crate::error::ParserError; -use crate::printer::Printer; +use crate::printer::{Printer, WsBefore}; use bun_alloc::ArenaVecExt as _; use crate::values as css_values; @@ -799,7 +799,7 @@ impl Font { self.size.to_css(dest)?; if self.line_height != LineHeight::default() { - dest.delim(b'/', true)?; + dest.delim(b'/', WsBefore::Yes)?; self.line_height.to_css(dest)?; } @@ -809,7 +809,7 @@ impl Font { for (idx, val) in self.family.slice_const().iter().enumerate() { val.to_css(dest)?; if idx < len - 1 { - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; } } Ok(()) @@ -983,9 +983,11 @@ impl FontHandler { if !self.flushed_properties.contains(FontProperty::FONT_FAMILY) { family = compatible_font_family( family, - !context - .targets - .should_compile_same(Feature::FontFamilySystemUi), + SystemUiSupported::from_bool( + !context + .targets + .should_compile_same(Feature::FontFamilySystemUi), + ), ); } @@ -1100,13 +1102,15 @@ const DEFAULT_SYSTEM_FONTS: &[&[u8]] = &[ b"Helvetica Neue", ]; +bun_core::bool_enum!(SystemUiSupported); + #[inline] fn compatible_font_family( _family: Option>, - is_supported: bool, + is_supported: SystemUiSupported, ) -> Option> { let mut family = _family; - if is_supported { + if is_supported == SystemUiSupported::Yes { return family; } diff --git a/src/css/properties/margin_padding.rs b/src/css/properties/margin_padding.rs index 2f39e9894116..8b27ebf47c1f 100644 --- a/src/css/properties/margin_padding.rs +++ b/src/css/properties/margin_padding.rs @@ -856,7 +856,7 @@ impl SizeHandler { &mut block_start, &mut block_end, LogicalSidePair::Block, - logical_supported, + LogicalSupported::from_bool(logical_supported), dest, context, ); @@ -886,7 +886,7 @@ impl SizeHandler { &mut inline_start, &mut inline_end, LogicalSidePair::Inline, - logical_supported, + LogicalSupported::from_bool(logical_supported), dest, context, ); @@ -987,12 +987,12 @@ impl SizeHandler { start: &mut Option, end: &mut Option, pair: LogicalSidePair, - logical_supported: bool, + logical_supported: LogicalSupported, dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) { // _ = this; // autofix - let shorthand_supported = logical_supported + let shorthand_supported = logical_supported == LogicalSupported::Yes && match S::SHORTHAND_FEATURE { Some(f) => !context.should_compile_logical(f), None => true, @@ -1072,6 +1072,8 @@ enum LogicalSidePair { Inline, } +bun_core::bool_enum!(LogicalSupported); + // ────────────────────────────────────────────────────────────────────────── // Spec instantiations // ────────────────────────────────────────────────────────────────────────── diff --git a/src/css/properties/masking.rs b/src/css/properties/masking.rs index 5672a93631d7..29b7d89d2fbc 100644 --- a/src/css/properties/masking.rs +++ b/src/css/properties/masking.rs @@ -2,6 +2,7 @@ use crate as css; use crate::PrintErr; use crate::Printer; +use crate::WsBefore; use crate::css_values::image::Image; use crate::css_values::length::LengthOrNumber; @@ -227,7 +228,7 @@ impl Mask { self.position.to_css(dest)?; if self.size != BackgroundSize::default() { - dest.delim(b'/', true)?; + dest.delim(b'/', WsBefore::Yes)?; self.size.to_css(dest)?; } } diff --git a/src/css/properties/mod.rs b/src/css/properties/mod.rs index eb7f69d20443..4fa975a0fb53 100644 --- a/src/css/properties/mod.rs +++ b/src/css/properties/mod.rs @@ -140,6 +140,7 @@ mod properties_impl; pub use self::custom::CustomPropertyName; pub use self::properties_generated::{Property, PropertyId, PropertyIdTag}; +pub use self::properties_impl::property_mixin::Important; /// A [CSS-wide keyword](https://drafts.csswg.org/css-cascade-5/#defaulting-keywords). // The `DefineEnumProperty` derive emits `EnumProperty` + diff --git a/src/css/properties/properties_generated.rs b/src/css/properties/properties_generated.rs index 56e539c6ece6..1f0dab6c9547 100644 --- a/src/css/properties/properties_generated.rs +++ b/src/css/properties/properties_generated.rs @@ -13,8 +13,9 @@ use crate::prefixes::Feature as PrefixFeature; use crate::targets::Targets; use super::CSSWideKeyword; -use super::custom::{CustomProperty, CustomPropertyName, UnparsedProperty}; +use super::custom::{CustomProperty, CustomPropertyName, IsCustomProperty, UnparsedProperty}; use super::properties_impl; +use super::properties_impl::property_mixin::Important; // Leaf property modules. use super::align; @@ -3914,10 +3915,11 @@ impl Property { Property::MaskBoxImageRepeat(v) => css::generic::to_css(&v.0, dest), Property::ColorScheme(v) => css::generic::to_css(v, dest), Property::All(v) => css::generic::to_css(v, dest), - Property::Unparsed(u) => u.value.to_css(dest, false), - Property::Custom(c) => c - .value - .to_css(dest, matches!(c.name, CustomPropertyName::Custom(..))), + Property::Unparsed(u) => u.value.to_css(dest, IsCustomProperty::No), + Property::Custom(c) => c.value.to_css( + dest, + IsCustomProperty::from_bool(matches!(c.name, CustomPropertyName::Custom(..))), + ), } } @@ -6168,7 +6170,11 @@ impl Property { UnparsedProperty::parse(property_id, input, options).map(Property::Unparsed) } - pub fn to_css(&self, dest: &mut css::Printer, important: bool) -> Result<(), css::PrintErr> { + pub fn to_css( + &self, + dest: &mut css::Printer, + important: Important, + ) -> Result<(), css::PrintErr> { properties_impl::property_mixin::to_css(self, dest, important) } diff --git a/src/css/properties/properties_impl.rs b/src/css/properties/properties_impl.rs index 64768b418f7e..ea3afcb9e9c6 100644 --- a/src/css/properties/properties_impl.rs +++ b/src/css/properties/properties_impl.rs @@ -3,6 +3,7 @@ use crate as css; use css::PrintErr; use css::Printer; use css::VendorPrefix; +use css::WsBefore; use css::css_properties::CustomPropertyName; use css::css_properties::{Property, PropertyId, PropertyIdTag}; @@ -83,15 +84,18 @@ pub(super) mod property_id_mixin { pub(super) mod property_mixin { use super::*; + bun_core::bool_enum!(pub Important); + /// Serializes the CSS property, with an optional `!important` flag. pub(crate) fn to_css( this: &Property, dest: &mut Printer, - important: bool, + important: Important, ) -> Result<(), PrintErr> { + let important = important == Important::Yes; if let Property::Custom(custom) = this { custom.name.to_css(dest)?; - dest.delim(b':', false)?; + dest.delim(b':', WsBefore::No)?; this.value_to_css(dest)?; if important { dest.whitespace()?; @@ -110,7 +114,7 @@ pub(super) mod property_mixin { |d, p| { p.to_css(d)?; d.write_str(name)?; - d.delim(b':', false)?; + d.delim(b':', WsBefore::No)?; this.value_to_css(d)?; if important { d.whitespace()?; diff --git a/src/css/properties/size.rs b/src/css/properties/size.rs index 86975feaa582..f9de5c011f08 100644 --- a/src/css/properties/size.rs +++ b/src/css/properties/size.rs @@ -31,9 +31,13 @@ impl BoxSizing { pub(crate) fn parse(input: &mut css::Parser) -> css::Result { let location = input.current_source_location(); let ident = input.expect_ident_cloned()?; - if bun_core::eql_case_insensitive_ascii(ident, b"content-box", true) { + if bun_core::eql_case_insensitive_ascii(ident, b"content-box", bun_core::CheckLen::Yes) { Ok(BoxSizing::ContentBox) - } else if bun_core::eql_case_insensitive_ascii(ident, b"border-box", true) { + } else if bun_core::eql_case_insensitive_ascii( + ident, + b"border-box", + bun_core::CheckLen::Yes, + ) { Ok(BoxSizing::BorderBox) } else { Err(location.new_unexpected_token_error(css::Token::Ident(ident))) @@ -74,7 +78,7 @@ pub enum Size { macro_rules! size_ident_match { ($ident:expr, { $($lit:literal => $val:expr,)+ } else $err:expr) => {{ let __ident: &[u8] = $ident; - $(if bun_core::eql_case_insensitive_ascii(__ident, $lit, true) { + $(if bun_core::eql_case_insensitive_ascii(__ident, $lit, bun_core::CheckLen::Yes) { Ok($val) } else)+ { $err } }}; diff --git a/src/css/properties/transform.rs b/src/css/properties/transform.rs index c5b95f8d9578..4f1c44b3e2ae 100644 --- a/src/css/properties/transform.rs +++ b/src/css/properties/transform.rs @@ -9,7 +9,8 @@ use crate::css_values::number::CSSNumberFns; use crate::css_values::percentage::NumberOrPercentage; use crate::prefixes; use crate::{ - DeclarationList, Parser, PrintErr, Printer, PropertyHandlerContext, Result, Token, VendorPrefix, + DeclarationList, Parser, PrintErr, Printer, PropertyHandlerContext, Result, Token, + VendorPrefix, WsBefore, }; /// A value for the [transform](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#propdef-transform) property. @@ -324,7 +325,7 @@ impl Transform { dest.write_str("translate(")?; x.to_css(dest)?; if !y.is_zero() { - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; y.to_css(dest)?; } } @@ -362,14 +363,14 @@ impl Transform { } else if dest.minify && z.is_zero() { dest.write_str("translate(")?; x.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; y.to_css(dest)?; } else { dest.write_str("translate3d(")?; x.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; y.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; z.to_css(dest)?; } dest.write_char(b')')?; @@ -387,7 +388,7 @@ impl Transform { dest.write_str("scale(")?; CSSNumberFns::to_css(x, dest)?; if y != x { - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(y, dest)?; } } @@ -431,14 +432,14 @@ impl Transform { } else if dest.minify && z == 1.0 { dest.write_str("scale(")?; CSSNumberFns::to_css(x, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(y, dest)?; } else { dest.write_str("scale3d(")?; CSSNumberFns::to_css(x, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(y, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(z, dest)?; } dest.write_char(b')')?; @@ -476,11 +477,11 @@ impl Transform { } else { dest.write_str("rotate3d(")?; CSSNumberFns::to_css(*x, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(*y, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(*z, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; angle.to_css_with_unitless_zero(dest)?; } dest.write_char(b')')?; @@ -493,7 +494,7 @@ impl Transform { dest.write_str("skew(")?; x.to_css(dest)?; if !y.is_zero() { - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; y.to_css_with_unitless_zero(dest)?; } } @@ -517,50 +518,50 @@ impl Transform { Transform::Matrix(m) => { dest.write_str("matrix(")?; CSSNumberFns::to_css(m.a, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.b, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.c, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.d, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.e, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.f, dest)?; dest.write_char(b')')?; } Transform::Matrix3d(m) => { dest.write_str("matrix3d(")?; CSSNumberFns::to_css(m.m11, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m12, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m13, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m14, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m21, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m22, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m23, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m24, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m31, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m32, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m33, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m34, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m41, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m42, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m43, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(m.m44, dest)?; dest.write_char(b')')?; } diff --git a/src/css/rules/container.rs b/src/css/rules/container.rs index 169da574f0bc..521cb638777f 100644 --- a/src/css/rules/container.rs +++ b/src/css/rules/container.rs @@ -2,7 +2,7 @@ use crate as css; use crate::css_rules::{CssRuleList, Location}; use crate::css_values::ident::CustomIdent; use crate::media_query::{self, MediaFeatureType, Operator, QueryCondition, QueryFeature, ToCss}; -use crate::properties::Property; +use crate::properties::{Important, Property}; use crate::{PrintErr, Printer}; /// A [``](https://drafts.csswg.org/css-contain-3/#typedef-container-name). @@ -141,7 +141,7 @@ impl QueryCondition for StyleQuery { } } fn feature_to_css(f: &Property, dest: &mut Printer) -> core::result::Result<(), PrintErr> { - f.to_css(dest, false) + f.to_css(dest, Important::No) } fn parse_feature(input: &mut css::Parser) -> css::Result { diff --git a/src/css/rules/font_face.rs b/src/css/rules/font_face.rs index d1d8957fde8a..c91fc49d4c83 100644 --- a/src/css/rules/font_face.rs +++ b/src/css/rules/font_face.rs @@ -1,10 +1,11 @@ use crate as css; +use crate::css_properties::custom::IsCustomProperty; use crate::css_rules::Location; use crate::css_values::angle::Angle; use crate::css_values::size::Size2D; use crate::css_values::url::Url; use crate::generics::DeepClone as _; -use crate::{PrintErr, Printer}; +use crate::{PrintErr, Printer, WsBefore}; use super::ArrayList; @@ -37,7 +38,7 @@ impl FontFaceProperty { macro_rules! write_property_single { ($d:expr, $prop:expr, $value:expr) => {{ $d.write_str($prop)?; - $d.delim(b':', false)?; + $d.delim(b':', WsBefore::No)?; $value.to_css($d) }}; } @@ -45,13 +46,13 @@ impl FontFaceProperty { macro_rules! write_property_multi { ($d:expr, $prop:expr, $value:expr) => {{ $d.write_str($prop)?; - $d.delim(b':', false)?; + $d.delim(b':', WsBefore::No)?; let slice = $value; let len = slice.len(); for (idx, val) in slice.iter().enumerate() { val.to_css($d)?; if idx < len - 1 { - $d.delim(b',', false)?; + $d.delim(b',', WsBefore::No)?; } } Ok(()) @@ -75,8 +76,8 @@ impl FontFaceProperty { } FontFaceProperty::Custom(custom) => { custom.name.to_css(dest)?; - dest.delim(b':', false)?; - custom.value.to_css(dest, true) + dest.delim(b':', WsBefore::No)?; + custom.value.to_css(dest, IsCustomProperty::Yes) } } } diff --git a/src/css/rules/font_palette_values.rs b/src/css/rules/font_palette_values.rs index 9f80077d982f..6cd6bcd2bc40 100644 --- a/src/css/rules/font_palette_values.rs +++ b/src/css/rules/font_palette_values.rs @@ -1,9 +1,10 @@ use crate as css; +use crate::css_properties::custom::IsCustomProperty; use crate::css_rules::Location; use crate::css_values::color::CssColor; use crate::css_values::ident::DashedIdent; use crate::generics::DeepClone as _; -use crate::{PrintErr, Printer}; +use crate::{PrintErr, Printer, WsBefore}; use super::ArrayList; @@ -93,23 +94,23 @@ impl FontPaletteValuesProperty { match self { FontPaletteValuesProperty::FontFamily(f) => { dest.write_str("font-family")?; - dest.delim(b':', false)?; + dest.delim(b':', WsBefore::No)?; f.to_css(dest) } FontPaletteValuesProperty::BasePalette(b) => { dest.write_str("base-palette")?; - dest.delim(b':', false)?; + dest.delim(b':', WsBefore::No)?; b.to_css(dest) } FontPaletteValuesProperty::OverrideColors(o) => { dest.write_str("override-colors")?; - dest.delim(b':', false)?; + dest.delim(b':', WsBefore::No)?; css::to_css::from_list(o.as_slice(), dest) } FontPaletteValuesProperty::Custom(custom) => { custom.name.to_css(dest)?; - dest.delim(b':', false)?; - custom.value.to_css(dest, true) + dest.delim(b':', WsBefore::No)?; + custom.value.to_css(dest, IsCustomProperty::Yes) } } } diff --git a/src/css/rules/keyframes.rs b/src/css/rules/keyframes.rs index b78cdd58e32a..ea105d9adab7 100644 --- a/src/css/rules/keyframes.rs +++ b/src/css/rules/keyframes.rs @@ -4,7 +4,7 @@ use crate as css; use crate::css_rules::Location; use crate::css_values::ident::{CustomIdent, is_reserved_custom_ident}; use crate::css_values::percentage::Percentage; -use crate::{DeclarationBlock, PrintErr, Printer, VendorPrefix}; +use crate::{DeclarationBlock, HandleCssModule, PrintErr, Printer, VendorPrefix}; use super::ArrayList; @@ -55,15 +55,15 @@ impl KeyframesName { fn write_ident<'a>( dest: &mut Printer<'a>, v: &'a [u8], - handle_css_module: bool, + handle_css_module: HandleCssModule, ) -> core::result::Result<(), PrintErr> { dest.write_ident(v, handle_css_module) } let css_module_animation_enabled = if let Some(css_module) = &dest.css_module { - css_module.config.animation + HandleCssModule::from_bool(css_module.config.animation) } else { - false + HandleCssModule::No }; match self { diff --git a/src/css/rules/mod.rs b/src/css/rules/mod.rs index 6cd0b547bad4..c1fe9464dab9 100644 --- a/src/css/rules/mod.rs +++ b/src/css/rules/mod.rs @@ -1,8 +1,9 @@ use crate as css; +use crate::properties::Important; use css::PrintErr; -use css::Printer; use css::error::MinifyErr; +use css::{HandleCssModule, Printer}; // PERF: heap-backed shim. // TODO(refactor): thread `'bump` and replace this with `crate::generics::ArrayList<'bump, T>` @@ -320,7 +321,7 @@ fn decl_block_to_css( let mut i: usize = 0; for decl in decls.declarations.iter() { dest.newline()?; - decl.to_css(dest, false)?; + decl.to_css(dest, Important::No)?; if i != length - 1 || !dest.minify { dest.write_char(b';')?; } @@ -328,7 +329,7 @@ fn decl_block_to_css( } for decl in decls.important_declarations.iter() { dest.newline()?; - decl.to_css(dest, true)?; + decl.to_css(dest, Important::Yes)?; if i != length - 1 || !dest.minify { dest.write_char(b';')?; } @@ -368,7 +369,7 @@ fn custom_ident_to_css( .css_module .as_ref() .is_some_and(|m| m.config.custom_idents); - dest.write_ident(v, enabled) + dest.write_ident(v, HandleCssModule::from_bool(enabled)) } /// Port of `DashedIdentFns.toCss` → `Printer.writeDashedIdent`. The @@ -384,6 +385,8 @@ fn dashed_ident_to_css( dest.serialize_name(&v[2..]) } +bun_core::bool_enum!(pub ParentIsUnused); + /// Recurse into the nested list and report whether the rule should be /// dropped. NOTE: `never_matches()` is a *drop condition*, not merely an /// optimization — omitting it diverges output (e.g. `@media not all @@ -392,7 +395,7 @@ impl media::MediaRule { pub(crate) fn minify( &mut self, context: &mut MinifyContext<'_, '_>, - parent_is_unused: bool, + parent_is_unused: ParentIsUnused, ) -> Result where R: for<'b> css::generics::DeepClone<'b>, @@ -488,7 +491,7 @@ impl CssRuleList { pub(crate) fn minify( &mut self, context: &mut MinifyContext<'_, '_>, - parent_is_unused: bool, + parent_is_unused: ParentIsUnused, ) -> Result<(), MinifyErr> where R: for<'b> css::generics::DeepClone<'b>, @@ -651,7 +654,7 @@ fn minify_style_arm css::generics::DeepClone<'b>>( style_rules: &mut StyleRuleKeyMap, merge_state: &mut StyleRuleMergeState, context: &mut MinifyContext<'_, '_>, - parent_is_unused: bool, + parent_is_unused: ParentIsUnused, ) -> Result<(), MinifyErr> { use css::SmallList; use css::selector::{self, Component, Selector, SelectorList}; @@ -659,7 +662,7 @@ fn minify_style_arm css::generics::DeepClone<'b>>( unreachable!() }; - if parent_is_unused || sty.minify(context, parent_is_unused)? { + if parent_is_unused == ParentIsUnused::Yes || sty.minify(context, parent_is_unused)? { return Ok(()); } diff --git a/src/css/rules/page.rs b/src/css/rules/page.rs index 27a830dfef65..f01a79a9396b 100644 --- a/src/css/rules/page.rs +++ b/src/css/rules/page.rs @@ -1,5 +1,6 @@ use crate as css; use crate::css_rules::Location; +use crate::properties::Important; use crate::{DeclarationBlock, PrintErr, Printer}; use super::ArrayList; @@ -142,9 +143,12 @@ impl PageRule { let len = self.declarations.len() + self.rules.len(); // 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), + let decls_groups: [(&[crate::css_parser::Property], Important); 2] = [ + (self.declarations.declarations.as_slice(), Important::No), + ( + self.declarations.important_declarations.as_slice(), + Important::Yes, + ), ]; for (decls, important) in decls_groups { for decl in decls { diff --git a/src/css/rules/scope.rs b/src/css/rules/scope.rs index a3a42cd75336..047f577c1f78 100644 --- a/src/css/rules/scope.rs +++ b/src/css/rules/scope.rs @@ -20,7 +20,7 @@ pub struct ScopeRule { impl ScopeRule { pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - use crate::selectors::selector::serialize::serialize_selector_list; + use crate::selectors::selector::serialize::{IsRelative, serialize_selector_list}; // #[cfg(feature = "sourcemap")] // dest.add_mapping(self.loc); @@ -36,7 +36,7 @@ impl ScopeRule { // 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)?; + serialize_selector_list(scope_start.v.slice(), dest, ctx, IsRelative::No)?; dest.write_char(b')')?; dest.whitespace()?; } @@ -55,12 +55,12 @@ impl ScopeRule { scope_end, |scope_end: &SelectorList, d: &mut Printer| -> Result<(), PrintErr> { let ctx = d.ctx; - serialize_selector_list(scope_end.v.slice(), d, ctx, false) + serialize_selector_list(scope_end.v.slice(), d, ctx, IsRelative::No) }, )?; } else { let ctx = dest.ctx; - return serialize_selector_list(scope_end.v.slice(), dest, ctx, false); + return serialize_selector_list(scope_end.v.slice(), dest, ctx, IsRelative::No); } dest.write_char(b')')?; dest.whitespace()?; diff --git a/src/css/rules/style.rs b/src/css/rules/style.rs index bfc3551a87da..3823ee7cd9a7 100644 --- a/src/css/rules/style.rs +++ b/src/css/rules/style.rs @@ -1,7 +1,8 @@ use crate as css; -use crate::css_rules::{CssRule, CssRuleList, Location, MinifyContext}; +use crate::css_rules::{CssRule, CssRuleList, Location, MinifyContext, ParentIsUnused}; use crate::declaration::DeclarationBlock; use crate::error::MinifyErr; +use crate::properties::Important; use crate::selectors::selector; use crate::{PrintErr, Printer, VendorPrefix}; @@ -91,10 +92,12 @@ impl StyleRule { /// (`selectors/selector.rs`) and `MAX_SELECTOR_EXPANSION` (`rules/mod.rs`). const MAX_PREFIX_EXPANSION_BYTES: usize = 64 << 20; +bun_core::bool_enum!(IsFinalPrefixPass); + impl StyleRule { pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { if self.vendor_prefix.is_empty() { - self.to_css_base(dest, true)?; + self.to_css_base(dest, IsFinalPrefixPass::Yes)?; } else { let mut first_rule = true; let mut emitted_first_pass = false; @@ -129,7 +132,10 @@ impl StyleRule { } else { 0 }; - self.to_css_base(dest, remaining_prefixes.is_empty())?; + self.to_css_base( + dest, + IsFinalPrefixPass::from_bool(remaining_prefixes.is_empty()), + )?; if is_duplicate_pass { let emitted = dest.bytes_written().saturating_sub(bytes_before); dest.prefix_expansion_bytes = @@ -157,7 +163,11 @@ impl StyleRule { Ok(()) } - fn to_css_base(&self, dest: &mut Printer, is_final_prefix_pass: bool) -> Result<(), PrintErr> { + fn to_css_base( + &self, + dest: &mut Printer, + is_final_prefix_pass: IsFinalPrefixPass, + ) -> Result<(), PrintErr> { use css::error::PrinterErrorKind; use css::properties::Property; @@ -183,7 +193,7 @@ impl StyleRule { self.selectors.v.slice(), dest, ctx, - false, + selector::serialize::IsRelative::No, )?; dest.whitespace()?; dest.write_char(b'{')?; @@ -192,9 +202,12 @@ impl StyleRule { let mut i: usize = 0; // 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), + let decls_groups: [(&[Property], Important); 2] = [ + (self.declarations.declarations.as_slice(), Important::No), + ( + self.declarations.important_declarations.as_slice(), + Important::Yes, + ), ]; for (decls, important) in decls_groups { for decl in decls { @@ -284,7 +297,7 @@ impl StyleRule { // they would be duplicated once per ancestor prefix, which grows // exponentially with nesting depth. let saved_skip = dest.skip_prefixed_nested_rules; - let skip_prefixed_nested = saved_skip || !is_final_prefix_pass; + let skip_prefixed_nested = saved_skip || is_final_prefix_pass == IsFinalPrefixPass::No; // Whether any nested rule is emitted in this pass; if not, don't // write the separator between the declarations and the nested // rules (nothing would follow it). @@ -313,7 +326,7 @@ impl StyleRule { pub(crate) fn minify( &mut self, context: &mut MinifyContext<'_, '_>, - parent_is_unused: bool, + parent_is_unused: ParentIsUnused, ) -> Result where R: for<'b> css::generics::DeepClone<'b>, @@ -368,7 +381,7 @@ impl StyleRule { context.handler_context.context = DeclarationContext::None; if self.rules.v.len() > 0 { - self.minify_nested_rules(context, unused)?; + self.minify_nested_rules(context, ParentIsUnused::from_bool(unused))?; if unused && self.rules.v.len() == 0 { return Ok(true); } @@ -412,7 +425,7 @@ impl StyleRule { pub(crate) fn minify_nested_rules( &mut self, context: &mut MinifyContext<'_, '_>, - parent_is_unused: bool, + parent_is_unused: ParentIsUnused, ) -> Result<(), MinifyErr> where R: for<'b> css::generics::DeepClone<'b>, diff --git a/src/css/rules/supports.rs b/src/css/rules/supports.rs index e4e658649cc2..b8329aeedc9a 100644 --- a/src/css/rules/supports.rs +++ b/src/css/rules/supports.rs @@ -1,8 +1,8 @@ use crate as css; -use crate::css_rules::{CssRuleList, Location, MinifyContext}; +use crate::css_rules::{CssRuleList, Location, MinifyContext, ParentIsUnused}; use crate::error::MinifyErr; use crate::properties::PropertyId; -use crate::{PrintErr, Printer}; +use crate::{PrintErr, Printer, WsBefore}; use bun_alloc::ArenaPtr; /// A [``](https://drafts.csswg.org/css-conditional-3/#typedef-supports-condition), @@ -210,7 +210,7 @@ impl SupportsCondition { |d| d.write_str(b") or ("), |d, _flag| { d.serialize_name(name)?; - d.delim(b':', false)?; + d.delim(b':', WsBefore::No)?; // Raw parser-input slice: may span newlines. d.write_bytes(value) }, @@ -419,7 +419,7 @@ impl SupportsRule { pub(crate) fn minify( &mut self, context: &mut MinifyContext, - parent_is_unused: bool, + parent_is_unused: ParentIsUnused, ) -> core::result::Result<(), MinifyErr> where R: for<'b> crate::generics::DeepClone<'b>, diff --git a/src/css/rules/unknown.rs b/src/css/rules/unknown.rs index 47e0e050a5ec..1896c3676454 100644 --- a/src/css/rules/unknown.rs +++ b/src/css/rules/unknown.rs @@ -1,5 +1,5 @@ use crate::css_rules::Location; -use crate::properties::custom::TokenList; +use crate::properties::custom::{IsCustomProperty, TokenList}; use crate::{PrintErr, Printer}; /// An unknown at-rule, stored as raw tokens. @@ -25,13 +25,13 @@ impl UnknownAtRule { if !self.prelude.v.is_empty() { dest.write_char(b' ')?; - self.prelude.to_css(dest, false)?; + self.prelude.to_css(dest, IsCustomProperty::No)?; } if let Some(block) = &self.block { dest.block(|d| { d.newline()?; - block.to_css(d, false) + block.to_css(d, IsCustomProperty::No) }) } else { dest.write_char(b';') diff --git a/src/css/selectors/parser.rs b/src/css/selectors/parser.rs index 144ffcded494..45a48d88233e 100644 --- a/src/css/selectors/parser.rs +++ b/src/css/selectors/parser.rs @@ -2482,11 +2482,14 @@ pub struct NthSelectorData { pub(crate) b: i32, } +bun_core::bool_enum!(pub(crate) OfType); +bun_core::bool_enum!(pub(crate) IsFunction); + impl NthSelectorData { /// Returns selector data for :only-{child,of-type} - pub(crate) fn only(of_type: bool) -> NthSelectorData { + pub(crate) fn only(of_type: OfType) -> NthSelectorData { NthSelectorData { - ty: if of_type { + ty: if of_type == OfType::Yes { NthType::OnlyOfType } else { NthType::OnlyChild @@ -2498,9 +2501,9 @@ impl NthSelectorData { } /// Returns selector data for :first-{child,of-type} - pub(crate) fn first(of_type: bool) -> NthSelectorData { + pub(crate) fn first(of_type: OfType) -> NthSelectorData { NthSelectorData { - ty: if of_type { + ty: if of_type == OfType::Yes { NthType::OfType } else { NthType::Child @@ -2512,9 +2515,9 @@ impl NthSelectorData { } /// Returns selector data for :last-{child,of-type} - pub(crate) fn last(of_type: bool) -> NthSelectorData { + pub(crate) fn last(of_type: OfType) -> NthSelectorData { NthSelectorData { - ty: if of_type { + ty: if of_type == OfType::Yes { NthType::LastOfType } else { NthType::LastChild @@ -2528,8 +2531,9 @@ impl NthSelectorData { pub(crate) fn write_start( &self, dest: &mut Printer, - is_function: bool, + is_function: IsFunction, ) -> Result<(), PrintErr> { + let is_function = is_function == IsFunction::Yes; dest.write_str(match self.ty { NthType::Child => { if is_function { @@ -3154,7 +3158,7 @@ pub(crate) fn parse_type_selector( state: SelectorParsingState, sink: &mut SelectorBuilder, ) -> CResult { - let result = match parse_qualified_name::(parser, input, false) { + let result = match parse_qualified_name::(parser, input, InAttrSelector::No) { Ok(v) => v, Err(e) => { if matches!( @@ -3425,7 +3429,7 @@ pub(crate) fn parse_attribute_selector( let (namespace, local_name): (Option>, Str) = 'brk: { input.skip_whitespace(); - let qname = parse_qualified_name::(parser, input, true)?; + let qname = parse_qualified_name::(parser, input, InAttrSelector::Yes)?; match qname { OptionalQName::None(t) => { return Err(input.new_custom_error( @@ -3680,18 +3684,18 @@ pub(crate) fn parse_simple_pseudo_class( if state.allows_tree_structural_pseudo_classes() { crate::match_ignore_ascii_case! { name, { - b"first-child" => return Ok(GenericComponent::Nth(NthSelectorData::first(false))), - b"last-child" => return Ok(GenericComponent::Nth(NthSelectorData::last(false))), - b"only-child" => return Ok(GenericComponent::Nth(NthSelectorData::only(false))), + b"first-child" => return Ok(GenericComponent::Nth(NthSelectorData::first(OfType::No))), + b"last-child" => return Ok(GenericComponent::Nth(NthSelectorData::last(OfType::No))), + b"only-child" => return Ok(GenericComponent::Nth(NthSelectorData::only(OfType::No))), b"root" => return Ok(GenericComponent::Root), b"empty" => return Ok(GenericComponent::Empty), b"scope" => return Ok(GenericComponent::Scope), b"host" => if parser.parse_host() { return Ok(GenericComponent::Host(None)); }, - b"first-of-type" => return Ok(GenericComponent::Nth(NthSelectorData::first(true))), - b"last-of-type" => return Ok(GenericComponent::Nth(NthSelectorData::last(true))), - b"only-of-type" => return Ok(GenericComponent::Nth(NthSelectorData::only(true))), + b"first-of-type" => return Ok(GenericComponent::Nth(NthSelectorData::first(OfType::Yes))), + b"last-of-type" => return Ok(GenericComponent::Nth(NthSelectorData::last(OfType::Yes))), + b"only-of-type" => return Ok(GenericComponent::Nth(NthSelectorData::only(OfType::Yes))), _ => {}, } } } @@ -3700,7 +3704,7 @@ pub(crate) fn parse_simple_pseudo_class( // https://w3c.github.io/csswg-drafts/css-view-transitions-1/#pseudo-root if state.contains(SelectorParsingState::AFTER_VIEW_TRANSITION) { if strings::eql_case_insensitive_ascii_check_length(name, b"only-child") { - return Ok(GenericComponent::Nth(NthSelectorData::only(false))); + return Ok(GenericComponent::Nth(NthSelectorData::only(OfType::No))); } } @@ -3889,6 +3893,8 @@ pub(crate) enum QNamePrefix { ExplicitNamespace(Impl::NamespacePrefix, Impl::NamespaceUrl), // `prefix|foo` } +bun_core::bool_enum!(pub(crate) InAttrSelector); + /// * `Err(())`: Invalid selector, abort /// * `Ok(None(token))`: Not a simple selector, could be something else. `input` was not consumed, /// but the token is still returned. @@ -3896,7 +3902,7 @@ pub(crate) enum QNamePrefix { pub(crate) fn parse_qualified_name( parser: &mut SelectorParser, input: &mut CssParser, - in_attr_selector: bool, + in_attr_selector: InAttrSelector, ) -> CResult> { let start = input.state(); @@ -3936,7 +3942,7 @@ pub(crate) fn parse_qualified_name( ); } else { input.reset(&after_ident); - if in_attr_selector { + if in_attr_selector == InAttrSelector::Yes { return Ok(OptionalQName::Some( QNamePrefix::ImplicitNoNamespace, Some(value), @@ -3964,7 +3970,7 @@ pub(crate) fn parse_qualified_name( // Reshaped for borrowck — clone token before reset. let result_cloned = result.cloned(); input.reset(&after_star); - if in_attr_selector { + if in_attr_selector == InAttrSelector::Yes { let t = result_cloned?; return Err(after_star .source_location() @@ -4005,7 +4011,7 @@ fn parse_qualified_name_default_namespace_helper( fn parse_qualified_name_eplicit_namespace_helper( input: &mut CssParser, namespace: QNamePrefix, - in_attr_selector: bool, + in_attr_selector: InAttrSelector, ) -> CResult> { let location = input.current_source_location(); let t = input.next_including_whitespace()?.clone(); @@ -4013,12 +4019,12 @@ fn parse_qualified_name_eplicit_namespace_helper( Token::Ident(local_name) => return Ok(OptionalQName::Some(namespace, Some(*local_name))), // `*` is only a valid local name outside of attribute selectors; // `[ns|*]` must fall through to the `InvalidQualNameInAttr` error below. - Token::Delim(c) if *c == b'*' as u32 && !in_attr_selector => { + Token::Delim(c) if *c == b'*' as u32 && in_attr_selector == InAttrSelector::No => { return Ok(OptionalQName::Some(namespace, None)); } _ => {} } - if in_attr_selector { + if in_attr_selector == InAttrSelector::Yes { let e = SelectorParseErrorKind::InvalidQualNameInAttr(t); return Err(location.new_custom_error(e)); } diff --git a/src/css/selectors/selector.rs b/src/css/selectors/selector.rs index df3e053cde40..71729036ed2a 100644 --- a/src/css/selectors/selector.rs +++ b/src/css/selectors/selector.rs @@ -1,7 +1,9 @@ use crate::css_parser as css; use crate::css_parser::compat::Feature; use crate::css_parser::targets::Targets; -use crate::css_parser::{PrintErr, Printer, StyleContext, VendorPrefix}; +use crate::css_parser::{ + HandleCssModule, ParentIsUnused, PrintErr, Printer, StyleContext, VendorPrefix, WsBefore, +}; use crate::{CSSStringFns, IdentFns}; type SymbolList = Vec; @@ -14,6 +16,7 @@ pub use css::PrintErr as _PrintErr; pub use css::Printer as _Printer; // re-export alias parity pub(crate) use parser::Component; +use parser::IsFunction; pub use parser::PseudoClass; pub use parser::PseudoElement; pub use parser::Selector; @@ -501,7 +504,7 @@ pub(crate) fn is_unused( selectors: &[parser::Selector], unused_symbols: &ArrayHashMap, ()>, symbols: &SymbolList, - parent_is_unused: bool, + parent_is_unused: ParentIsUnused, ) -> bool { if unused_symbols.len() == 0 { return false; @@ -520,7 +523,7 @@ fn is_selector_unused( selector: &parser::Selector, unused_symbols: &ArrayHashMap, ()>, symbols: &SymbolList, - parent_is_unused: bool, + parent_is_unused: ParentIsUnused, ) -> bool { for component in selector.components.iter() { match component { @@ -557,7 +560,7 @@ fn is_selector_unused( } } Component::Nesting => { - if parent_is_unused { + if parent_is_unused == ParentIsUnused::Yes { return true; } } @@ -575,11 +578,13 @@ fn is_selector_unused( pub(crate) mod serialize { use super::*; + bun_core::bool_enum!(pub(crate) IsRelative); + pub(crate) fn serialize_selector_list( list: &[parser::Selector], dest: &mut Printer, context: Option<&StyleContext>, - is_relative: bool, + is_relative: IsRelative, ) -> Result<(), PrintErr> { dest.write_comma_separated(list, |d, sel| { serialize_selector(sel, d, context, is_relative) @@ -590,9 +595,9 @@ pub(crate) mod serialize { selector: &parser::Selector, dest: &mut Printer, context: Option<&StyleContext>, - is_relative_: bool, + is_relative_: IsRelative, ) -> Result<(), PrintErr> { - let mut is_relative = is_relative_; + let mut is_relative = is_relative_ == IsRelative::Yes; #[cfg(debug_assertions)] { @@ -715,7 +720,7 @@ pub(crate) mod serialize { } if swap_nesting { - serialize_nesting(dest, context, false)?; + serialize_nesting(dest, context, First::No)?; } // Skip step 2, which is an "otherwise". @@ -764,7 +769,7 @@ pub(crate) mod serialize { } else if has_leading_nesting && should_compile_nesting { // Nesting selector may serialize differently if it is leading, due to type selectors. i += 1; - serialize_nesting(dest, context, true)?; + serialize_nesting(dest, context, First::Yes)?; } if i < compound.len() { @@ -866,7 +871,12 @@ pub(crate) mod serialize { Component::Is(selectors) => { // If there's only one simple selector, serialize it directly. if should_unwrap_is(selectors) { - return serialize_selector(&selectors[0], dest, context, false); + return serialize_selector( + &selectors[0], + dest, + context, + IsRelative::No, + ); } let vp = dest.vendor_prefix; @@ -903,13 +913,13 @@ pub(crate) mod serialize { }, dest, context, - false, + IsRelative::No, )?; return dest.write_str(b")"); } Component::Has(list) => { dest.write_str(b":has(")?; - serialize_selector_list(list, dest, context, true)?; + serialize_selector_list(list, dest, context, IsRelative::Yes)?; return dest.write_str(b")"); } Component::NonTsPseudoClass(pseudo) => { @@ -919,22 +929,28 @@ pub(crate) mod serialize { return serialize_pseudo_element(pseudo, dest, context); } Component::Nesting => { - return serialize_nesting(dest, context, false); + return serialize_nesting(dest, context, First::No); } Component::Class(class) => { dest.write_char(b'.')?; - return dest.write_ident_or_ref(*class, dest.css_module.is_some()); + return dest.write_ident_or_ref( + *class, + HandleCssModule::from_bool(dest.css_module.is_some()), + ); } Component::Id(id) => { dest.write_char(b'#')?; - return dest.write_ident_or_ref(*id, dest.css_module.is_some()); + return dest.write_ident_or_ref( + *id, + HandleCssModule::from_bool(dest.css_module.is_some()), + ); } Component::Host(selector) => { dest.write_str(b":host")?; if let Some(sel) = selector { dest.write_char(b'(')?; let ctx = dest.ctx; - serialize_selector(sel, dest, ctx, false)?; + serialize_selector(sel, dest, ctx, IsRelative::No)?; dest.write_char(b')')?; } return Ok(()); @@ -942,7 +958,7 @@ pub(crate) mod serialize { Component::Slotted(selector) => { dest.write_str(b"::slotted(")?; let ctx = dest.ctx; - serialize_selector(selector, dest, ctx, false)?; + serialize_selector(selector, dest, ctx, IsRelative::No)?; dest.write_char(b')')?; } // Component::Nth(nth_data) => { @@ -964,10 +980,10 @@ pub(crate) mod serialize { dest: &mut Printer, ) -> Result<(), PrintErr> { match combinator { - parser::Combinator::Child => dest.delim(b'>', true)?, + parser::Combinator::Child => dest.delim(b'>', WsBefore::Yes)?, parser::Combinator::Descendant => dest.write_str(b" ")?, - parser::Combinator::NextSibling => dest.delim(b'+', true)?, - parser::Combinator::LaterSibling => dest.delim(b'~', true)?, + parser::Combinator::NextSibling => dest.delim(b'+', WsBefore::Yes)?, + parser::Combinator::LaterSibling => dest.delim(b'~', WsBefore::Yes)?, parser::Combinator::Deep => dest.write_str(b" /deep/ ")?, parser::Combinator::DeepDescendant => { dest.whitespace()?; @@ -1117,10 +1133,12 @@ pub(crate) mod serialize { // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-autofill PseudoClass::Autofill(prefix) => write_prefixed(dest, *prefix, b"autofill")?, - PseudoClass::Local { selector } => serialize_selector(selector, dest, context, false)?, + PseudoClass::Local { selector } => { + serialize_selector(selector, dest, context, IsRelative::No)? + } PseudoClass::Global { selector } => { let css_module = dest.css_module.take(); - serialize_selector(selector, dest, context, false)?; + serialize_selector(selector, dest, context, IsRelative::No)?; dest.css_module = css_module; } @@ -1221,12 +1239,12 @@ pub(crate) mod serialize { PseudoElement::CueRegion => dest.write_str(b"::cue-region")?, PseudoElement::CueFunction { selector } => { dest.write_str(b"::cue(")?; - serialize_selector(selector, dest, context, false)?; + serialize_selector(selector, dest, context, IsRelative::No)?; dest.write_char(b')')?; } PseudoElement::CueRegionFunction { selector } => { dest.write_str(b"::cue-region(")?; - serialize_selector(selector, dest, context, false)?; + serialize_selector(selector, dest, context, IsRelative::No)?; dest.write_char(b')')?; } PseudoElement::Placeholder(prefix) => { @@ -1310,10 +1328,12 @@ pub(crate) mod serialize { /// bound. const MAX_NESTING_EXPANSIONS: u32 = 65_536; + bun_core::bool_enum!(First); + fn serialize_nesting( dest: &mut Printer, context: Option<&StyleContext>, - first: bool, + first: First, ) -> Result<(), PrintErr> { if let Some(ctx) = context { dest.nesting_expansions += 1; @@ -1328,14 +1348,14 @@ pub(crate) mod serialize { // Type selectors are only allowed at the start of a compound selector, // so use :is() if that is not the case. if ctx.selectors.v.len() == 1 - && (first + && (first == First::Yes || (!has_type_selector(ctx.selectors.v.at(0)) && is_simple(ctx.selectors.v.at(0)))) { - serialize_selector(ctx.selectors.v.at(0), dest, ctx.parent, false)?; + serialize_selector(ctx.selectors.v.at(0), dest, ctx.parent, IsRelative::No)?; } else { dest.write_str(b":is(")?; - serialize_selector_list(ctx.selectors.v.slice(), dest, ctx.parent, false)?; + serialize_selector_list(ctx.selectors.v.slice(), dest, ctx.parent, IsRelative::No)?; dest.write_char(b')')?; } } else { @@ -1525,11 +1545,11 @@ pub(crate) mod tocss_servo { } Component::Id(s) => { dest.write_char(b'#')?; - dest.write_ident_or_ref(*s, dest.css_module.is_some())?; + dest.write_ident_or_ref(*s, HandleCssModule::from_bool(dest.css_module.is_some()))?; } Component::Class(s) => { dest.write_char(b'.')?; - dest.write_ident_or_ref(*s, dest.css_module.is_some())?; + dest.write_ident_or_ref(*s, HandleCssModule::from_bool(dest.css_module.is_some()))?; } Component::LocalName(local_name) => { local_name.to_css(dest)?; @@ -1595,7 +1615,7 @@ pub(crate) mod tocss_servo { } } Component::Nth(nth_data) => { - nth_data.write_start(dest, nth_data.is_function_())?; + nth_data.write_start(dest, IsFunction::from_bool(nth_data.is_function_()))?; if nth_data.is_function_() { nth_data.write_affine(dest)?; dest.write_char(b')')?; @@ -1603,7 +1623,7 @@ pub(crate) mod tocss_servo { } Component::NthOf(nth_of_data) => { let nth_data = nth_of_data.nth_data(); - nth_data.write_start(dest, true)?; + nth_data.write_start(dest, IsFunction::Yes)?; // A selector must be a function to hold An+B notation debug_assert!(nth_data.is_function); nth_data.write_affine(dest)?; diff --git a/src/css/targets.rs b/src/css/targets.rs index 164159d18feb..9278331a2003 100644 --- a/src/css/targets.rs +++ b/src/css/targets.rs @@ -38,7 +38,7 @@ impl Targets { if val.is_empty() { return None; } - if strings::eql_case_insensitive_ascii(val, b"null", true) { + if strings::eql_case_insensitive_ascii(val, b"null", strings::CheckLen::Yes) { return None; } diff --git a/src/css/values/angle.rs b/src/css/values/angle.rs index 21f37e3fdaa8..48366f3a0710 100644 --- a/src/css/values/angle.rs +++ b/src/css/values/angle.rs @@ -30,14 +30,16 @@ pub enum Angle { Turn(CSSNumber) = TAG_TURN, } +bun_core::bool_enum!(AllowUnitlessZero); + impl Angle { // ~toCssImpl pub(crate) fn parse(input: &mut Parser) -> Result { - Angle::parse_internal(input, false) + Angle::parse_internal(input, AllowUnitlessZero::No) } - fn parse_internal(input: &mut Parser, allow_unitless_zero: bool) -> Result { + fn parse_internal(input: &mut Parser, allow_unitless_zero: AllowUnitlessZero) -> Result { if let Ok(calc_value) = input.try_parse(Calc::::parse) { if let Calc::Value(value) = calc_value { return Ok(*value); @@ -61,7 +63,7 @@ impl Angle { }}; } Token::Number(num) => { - if num.value == 0.0 && allow_unitless_zero { + if num.value == 0.0 && allow_unitless_zero == AllowUnitlessZero::Yes { return Ok(Angle::zero()); } } @@ -71,7 +73,7 @@ impl Angle { } pub(crate) fn parse_with_unitless_zero(input: &mut Parser) -> Result { - Angle::parse_internal(input, true) + Angle::parse_internal(input, AllowUnitlessZero::Yes) } pub(crate) fn to_css(self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { diff --git a/src/css/values/calc.rs b/src/css/values/calc.rs index a429d0d7381e..1c1f6ebbf56c 100644 --- a/src/css/values/calc.rs +++ b/src/css/values/calc.rs @@ -1,5 +1,5 @@ use crate::css_parser as css; -use crate::css_parser::{CssResult, PrintErr, Printer}; +use crate::css_parser::{CssResult, PrintErr, Printer, WsBefore}; use crate::values::angle::Angle; use crate::values::length::{Length, LengthValue}; use crate::values::number::{CSSNumber, CSSNumberFns}; @@ -480,12 +480,24 @@ impl Calc { parse_ident, ) }), - CalcUnit::Sin => Self::parse_trig(input, TrigFnKind::Sin, false, ctx, parse_ident), - CalcUnit::Cos => Self::parse_trig(input, TrigFnKind::Cos, false, ctx, parse_ident), - CalcUnit::Tan => Self::parse_trig(input, TrigFnKind::Tan, false, ctx, parse_ident), - CalcUnit::Asin => Self::parse_trig(input, TrigFnKind::Asin, true, ctx, parse_ident), - CalcUnit::Acos => Self::parse_trig(input, TrigFnKind::Acos, true, ctx, parse_ident), - CalcUnit::Atan => Self::parse_trig(input, TrigFnKind::Atan, true, ctx, parse_ident), + CalcUnit::Sin => { + Self::parse_trig(input, TrigFnKind::Sin, ToAngle::No, ctx, parse_ident) + } + CalcUnit::Cos => { + Self::parse_trig(input, TrigFnKind::Cos, ToAngle::No, ctx, parse_ident) + } + CalcUnit::Tan => { + Self::parse_trig(input, TrigFnKind::Tan, ToAngle::No, ctx, parse_ident) + } + CalcUnit::Asin => { + Self::parse_trig(input, TrigFnKind::Asin, ToAngle::Yes, ctx, parse_ident) + } + CalcUnit::Acos => { + Self::parse_trig(input, TrigFnKind::Acos, ToAngle::Yes, ctx, parse_ident) + } + CalcUnit::Atan => { + Self::parse_trig(input, TrigFnKind::Atan, ToAngle::Yes, ctx, parse_ident) + } CalcUnit::Atan2 => input.parse_nested_block(|i| { let res = Self::parse_atan2(i, ctx, parse_ident)?; if let Some(v) = V::try_from_angle(res) { @@ -737,7 +749,7 @@ impl Calc { pub(crate) fn parse_trig( input: &mut css::Parser, trig_fn_kind: TrigFnKind, - to_angle: bool, + to_angle: ToAngle, ctx: C, parse_ident: impl Fn(C, &[u8]) -> Option + Copy, ) -> CssResult { @@ -771,7 +783,7 @@ impl Calc { let rad: f32 = 'rad: { match &v { Calc::Value(angle) => { - if !to_angle { + if to_angle == ToAngle::No { break 'rad trig_fn(angle.to_radians()); } } @@ -781,7 +793,7 @@ impl Calc { return Err(i.new_custom_error(css::ParserError::invalid_value)); }; - if to_angle && !rad.is_nan() { + if to_angle == ToAngle::Yes && !rad.is_nan() { if let Some(val) = V::try_from_angle(Angle::Rad(rad)) { return Ok(Calc::Value(Box::new(val))); } @@ -1018,11 +1030,11 @@ impl Calc { if num.abs() < 1.0 { let div = 1.0 / num; calc.to_css(dest)?; - dest.delim(b'/', true)?; + dest.delim(b'/', WsBefore::Yes)?; CSSNumberFns::to_css(div, dest)?; } else { CSSNumberFns::to_css(num, dest)?; - dest.delim(b'*', true)?; + dest.delim(b'*', WsBefore::Yes)?; calc.to_css(dest)?; } Ok(()) @@ -1167,6 +1179,8 @@ pub enum TrigFnKind { Atan, } +bun_core::bool_enum!(pub(crate) ToAngle); + /// A CSS math function. /// /// Math functions may be used in most properties and values that accept numeric @@ -1395,9 +1409,9 @@ impl MathFunction { MathFunction::Clamp { min, center, max } => { dest.write_str("clamp(")?; min.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; center.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; max.to_css(dest)?; dest.write_char(b')') } @@ -1409,24 +1423,24 @@ impl MathFunction { dest.write_str("round(")?; if *strategy != RoundingStrategy::default() { strategy.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; } value.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; interval.to_css(dest)?; dest.write_char(b')') } MathFunction::Rem { dividend, divisor } => { dest.write_str("rem(")?; dividend.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; divisor.to_css(dest)?; dest.write_char(b')') } MathFunction::Mod { dividend, divisor } => { dest.write_str("mod(")?; dividend.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; divisor.to_css(dest)?; dest.write_char(b')') } diff --git a/src/css/values/color.rs b/src/css/values/color.rs index 0bb221f293cc..03580b4d8884 100644 --- a/src/css/values/color.rs +++ b/src/css/values/color.rs @@ -7,7 +7,7 @@ use crate::PrintErr; use crate::compat::Feature; use crate::css_parser as css; use crate::css_parser::CssResult; -use crate::printer::Printer; +use crate::printer::{Printer, WsBefore}; use crate::targets; use crate::values::angle::Angle; use crate::values::calc::Calc; @@ -463,11 +463,11 @@ impl CssColor { return dest.write_str("transparent"); } else { dest.write_fmt(format_args!("rgba({}", color.red))?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; dest.write_fmt(format_args!("{}", color.green))?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; dest.write_fmt(format_args!("{}", color.blue))?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; // Try first with two decimal places, then with three. let mut rounded_alpha = (color.alpha_f32() * 100.0).round() / 100.0; @@ -510,19 +510,19 @@ impl CssColor { CssColor::LightDark { light, dark } => { if !dest.targets.is_compatible(Feature::LightDark) { dest.write_str("var(--buncss-light")?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; light.to_css(dest)?; dest.write_char(b')')?; dest.whitespace()?; dest.write_str("var(--buncss-dark")?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; dark.to_css(dest)?; return dest.write_char(b')'); } dest.write_str("light-dark(")?; light.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; dark.to_css(dest)?; dest.write_char(b')') } @@ -1124,7 +1124,7 @@ pub(crate) fn parse_color_function( function: &'static [u8], input: &mut css::Parser, ) -> CssResult { - let mut parser = ComponentParser::new(true); + let mut parser = ComponentParser::new(AllowNone::Yes); crate::match_ignore_ascii_case! { function, { b"lab" => parse_lab::(input, &mut parser, |l, a, b, alpha| { @@ -1140,7 +1140,7 @@ pub(crate) fn parse_color_function( LABColor::Oklch(OKLCH { l, c, h, alpha }) }), b"color" => parse_predefined(input, &mut parser), - b"hsl" | b"hsla" => parse_hsl_hwb::(input, &mut parser, true, |h, s, l, a| { + b"hsl" | b"hsla" => parse_hsl_hwb::(input, &mut parser, AllowsLegacy::Yes, |h, s, l, a| { let hsl = HSL { h, s, l, alpha: a }; if !h.is_nan() && !s.is_nan() && !l.is_nan() && !a.is_nan() { CssColor::Rgba(RGBA::from(hsl)) @@ -1148,7 +1148,7 @@ pub(crate) fn parse_color_function( CssColor::Float(Box::new(FloatColor::Hsl(hsl))) } }), - b"hwb" => parse_hsl_hwb::(input, &mut parser, false, |h, w, b, a| { + b"hwb" => parse_hsl_hwb::(input, &mut parser, AllowsLegacy::No, |h, w, b, a| { let hwb = HWB { h, w, b, alpha: a }; if !h.is_nan() && !w.is_nan() && !b.is_nan() && !a.is_nan() { CssColor::Rgba(RGBA::from(hwb)) @@ -1174,6 +1174,8 @@ pub(crate) fn parse_color_function( }} } +bun_core::bool_enum!(pub(crate) LegacySyntax); + /// The channels of an `rgb()` / `rgba()` call, up to but not including the /// alpha: 0..=255 in the legacy comma syntax, otherwise 0..=1 (NaN for `none`). pub(crate) struct RgbComponents { @@ -1181,7 +1183,7 @@ pub(crate) struct RgbComponents { pub(crate) g: f32, pub(crate) b: f32, /// `rgb(0, 0, 0, 0.5)`: the alpha that follows is comma-separated too. - pub(crate) is_legacy: bool, + pub(crate) is_legacy: LegacySyntax, } pub(crate) fn parse_rgb_components( @@ -1242,7 +1244,7 @@ pub(crate) fn parse_rgb_components( r, g, b, - is_legacy: is_legacy_syntax, + is_legacy: LegacySyntax::from_bool(is_legacy_syntax), }) } @@ -1371,6 +1373,8 @@ pub(crate) fn parse_lch + From + }) } +bun_core::bool_enum!(pub(crate) AllowsLegacy); + /// Parses the hsl() and hwb() functions. /// The results of this function are stored as floating point if there are any `none` components. /// https://drafts.csswg.org/css-color-4/#the-hsl-notation @@ -1379,13 +1383,13 @@ pub(crate) fn parse_hsl_hwb< >( input: &mut css::Parser, parser: &mut ComponentParser, - allows_legacy: bool, + allows_legacy: AllowsLegacy, func: fn(f32, f32, f32, f32) -> CssColor, ) -> CssResult { input.parse_nested_block(|i| { parser.parse_relative::(i, |i, p| { let (h, a, b, is_legacy) = parse_hsl_hwb_components::(i, p, allows_legacy)?; - let alpha = if is_legacy { + let alpha = if is_legacy == LegacySyntax::Yes { parse_legacy_alpha(i, p)? } else { parse_alpha(i, p)? @@ -1399,11 +1403,11 @@ pub(crate) fn parse_hsl_hwb< pub(crate) fn parse_hsl_hwb_components( input: &mut css::Parser, parser: &mut ComponentParser, - allows_legacy: bool, -) -> CssResult<(f32, f32, f32, bool)> { + allows_legacy: AllowsLegacy, +) -> CssResult<(f32, f32, f32, LegacySyntax)> { let _ = core::marker::PhantomData::; // autofix let h = parse_angle_or_number(input, parser)?; - let is_legacy_syntax = allows_legacy + let is_legacy_syntax = allows_legacy == AllowsLegacy::Yes && parser.from.is_none() && !h.is_nan() && input.try_parse(|i| i.expect_comma()).is_ok(); @@ -1420,7 +1424,7 @@ pub(crate) fn parse_hsl_hwb_components( return Err(input.new_custom_error(css::ParserError::invalid_value)); } - Ok((h, a, b, is_legacy_syntax)) + Ok((h, a, b, LegacySyntax::from_bool(is_legacy_syntax))) } pub(crate) fn parse_angle_or_number( @@ -1439,14 +1443,14 @@ fn parse_rgb(input: &mut css::Parser, parser: &mut ComponentParser) -> CssResult input.parse_nested_block(|i| { parser.parse_relative::(i, |i, p| { let RgbComponents { r, g, b, is_legacy } = parse_rgb_components(i, p)?; - let alpha = if is_legacy { + let alpha = if is_legacy == LegacySyntax::Yes { parse_legacy_alpha(i, p)? } else { parse_alpha(i, p)? }; if !r.is_nan() && !g.is_nan() && !b.is_nan() && !alpha.is_nan() { - if is_legacy { + if is_legacy == LegacySyntax::Yes { return Ok(CssColor::Rgba(RGBA::new(r as u8, g as u8, b as u8, alpha))); } @@ -1881,13 +1885,15 @@ define_colorspace! { // ComponentParser // ────────────────────────────────────────────────────────────────────────── +bun_core::bool_enum!(pub(crate) AllowNone); + pub(crate) struct ComponentParser { - pub(crate) allow_none: bool, + pub(crate) allow_none: AllowNone, pub(crate) from: Option, } impl ComponentParser { - pub(crate) fn new(allow_none: bool) -> ComponentParser { + pub(crate) fn new(allow_none: AllowNone) -> ComponentParser { ComponentParser { allow_none, from: None, @@ -1960,7 +1966,7 @@ impl ComponentParser { Ok(NumberOrPercentage::Percentage { unit_value: value.v, }) - } else if self.allow_none { + } else if self.allow_none == AllowNone::Yes { input.expect_ident_matching(b"none")?; Ok(NumberOrPercentage::Number { value: f32::NAN }) } else { @@ -1986,7 +1992,7 @@ impl ComponentParser { }) } else if let Ok(value) = input.try_parse(CSSNumberFns::parse) { Ok(css::color::AngleOrNumber::Number { value }) - } else if self.allow_none { + } else if self.allow_none == AllowNone::Yes { input.expect_ident_matching(b"none")?; Ok(css::color::AngleOrNumber::Number { value: f32::NAN }) } else { @@ -2004,7 +2010,7 @@ impl ComponentParser { if let Ok(val) = input.try_parse(Percentage::parse) { Ok(val.v) - } else if self.allow_none { + } else if self.allow_none == AllowNone::Yes { input.expect_ident_matching(b"none")?; Ok(f32::NAN) } else { @@ -2021,7 +2027,7 @@ impl ComponentParser { if let Ok(val) = input.try_parse(CSSNumberFns::parse) { Ok(val) - } else if self.allow_none { + } else if self.allow_none == AllowNone::Yes { input.expect_ident_matching(b"none")?; Ok(f32::NAN) } else { @@ -2633,7 +2639,7 @@ pub(crate) fn write_components( dest.write_char(b' ')?; write_component(c, dest)?; if alpha.is_nan() || (alpha - 1.0).abs() > f32::EPSILON { - dest.delim(b'/', true)?; + dest.delim(b'/', WsBefore::Yes)?; write_component(alpha, dest)?; } dest.write_char(b')') @@ -2664,7 +2670,7 @@ pub(crate) fn write_predefined( write_component(c, dest)?; if alpha.is_nan() || (alpha - 1.0).abs() > f32::EPSILON { - dest.delim(b'/', true)?; + dest.delim(b'/', WsBefore::Yes)?; write_component(alpha, dest)?; } diff --git a/src/css/values/easing.rs b/src/css/values/easing.rs index 23b7d4788447..2772829d0af7 100644 --- a/src/css/values/easing.rs +++ b/src/css/values/easing.rs @@ -1,5 +1,5 @@ use crate::css_parser as css; -use crate::css_parser::{CssResult as Result, PrintErr, Printer, Token}; +use crate::css_parser::{CssResult as Result, PrintErr, Printer, Token, WsBefore}; use crate::values::number::{CSSInteger, CSSIntegerFns, CSSNumber, CSSNumberFns}; /// A CSS [easing function](https://www.w3.org/TR/css-easing-1/#easing-functions). @@ -181,7 +181,7 @@ impl EasingFunction { return dest.write_str("step-end"); } dest.write_fmt(format_args!("steps({}", steps.count))?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; steps.position.to_css(dest)?; dest.write_char(b')') } diff --git a/src/css/values/gradient.rs b/src/css/values/gradient.rs index 1eac150d2cc7..542cd19775f0 100644 --- a/src/css/values/gradient.rs +++ b/src/css/values/gradient.rs @@ -9,7 +9,7 @@ use crate::values::percentage::{DimensionPercentage, NumberOrPercentage, Percent use crate::values::position::{ HorizontalPositionKeyword, Position, PositionComponent, VerticalPositionKeyword, }; -use crate::{PrintErr, Printer, VendorPrefix}; +use crate::{PrintErr, Printer, VendorPrefix, WsBefore}; use bun_alloc::Arena; // `'bump` arena threading dropped for now: `BumpVec<'bump,_>` → @@ -160,7 +160,10 @@ impl Gradient { match self { Gradient::Linear(linear) | Gradient::RepeatingLinear(linear) => { - linear.to_css(dest, linear.vendor_prefix != VendorPrefix::NONE)?; + linear.to_css( + dest, + IsPrefixed::from_bool(linear.vendor_prefix != VendorPrefix::NONE), + )?; } Gradient::Radial(radial) | Gradient::RepeatingRadial(radial) => { radial.to_css(dest)?; @@ -311,11 +314,16 @@ pub struct LinearGradient { pub(crate) items: Vec>, } +bun_core::bool_enum!(pub(crate) IsPrefixed); + impl LinearGradient { fn parse(input: &mut css::Parser, vendor_prefix: VendorPrefix) -> Result { - let direction: LineDirection = if let Ok(dir) = - input.try_parse(|i| LineDirection::parse(i, vendor_prefix != VendorPrefix::NONE)) - { + let direction: LineDirection = if let Ok(dir) = input.try_parse(|i| { + LineDirection::parse( + i, + IsPrefixed::from_bool(vendor_prefix != VendorPrefix::NONE), + ) + }) { input.expect_comma()?; dir } else { @@ -329,7 +337,11 @@ impl LinearGradient { }) } - fn to_css(&self, dest: &mut Printer, is_prefixed: bool) -> core::result::Result<(), PrintErr> { + fn to_css( + &self, + dest: &mut Printer, + is_prefixed: IsPrefixed, + ) -> core::result::Result<(), PrintErr> { let angle: f32 = match &self.direction { LineDirection::Vertical(v) => match v { VerticalPositionKeyword::Bottom => 180.0, @@ -410,7 +422,7 @@ impl LinearGradient { && self.direction != LineDirection::Angle(Angle::Deg(180.0)) { self.direction.to_css(dest, is_prefixed)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; } if serialize_items::(&self.items, dest).is_err() { @@ -499,7 +511,7 @@ impl RadialGradient { if self.shape != EndingShape::default() { self.shape.to_css(dest)?; if self.position.is_center() { - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; } else { dest.write_char(b' ')?; } @@ -508,7 +520,7 @@ impl RadialGradient { if !self.position.is_center() { dest.write_str(b"at ")?; self.position.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; } serialize_items::(&self.items, dest) @@ -595,7 +607,7 @@ impl ConicGradient { self.angle.to_css(dest)?; if self.position.is_center() { - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; } else { dest.write_char(b' ')?; } @@ -604,7 +616,7 @@ impl ConicGradient { if !self.position.is_center() { dest.write_str(b"at ")?; self.position.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; } serialize_items::(&self.items, dest) @@ -754,28 +766,28 @@ impl WebKitGradient { match self { WebKitGradient::Linear(linear) => { dest.write_str(b"linear")?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; linear.from.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; linear.to.to_css(dest)?; for stop in linear.stops.iter() { - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; stop.to_css(dest)?; } Ok(()) } WebKitGradient::Radial(radial) => { dest.write_str(b"radial")?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; radial.from.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(radial.r0, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; radial.to.to_css(dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; CSSNumberFns::to_css(radial.r1, dest)?; for stop in radial.stops.iter() { - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; stop.to_css(dest)?; } Ok(()) @@ -932,14 +944,14 @@ pub enum LineDirection { } impl LineDirection { - fn parse(input: &mut css::Parser, is_prefixed: bool) -> Result { + fn parse(input: &mut css::Parser, is_prefixed: IsPrefixed) -> Result { // Spec allows unitless zero angles for gradients. // https://w3c.github.io/csswg-drafts/css-images-3/#linear-gradient-syntax if let Ok(angle) = input.try_parse(Angle::parse_with_unitless_zero) { return Ok(LineDirection::Angle(angle)); } - if !is_prefixed { + if is_prefixed == IsPrefixed::No { input.expect_ident_matching(b"to")?; } @@ -963,7 +975,11 @@ impl LineDirection { Ok(LineDirection::Vertical(y)) } - fn to_css(&self, dest: &mut Printer, is_prefixed: bool) -> core::result::Result<(), PrintErr> { + fn to_css( + &self, + dest: &mut Printer, + is_prefixed: IsPrefixed, + ) -> core::result::Result<(), PrintErr> { match self { LineDirection::Angle(angle) => angle.to_css(dest), LineDirection::Horizontal(k) => { @@ -973,7 +989,7 @@ impl LineDirection { HorizontalPositionKeyword::Right => &b"90deg"[..], }) } else { - if !is_prefixed { + if is_prefixed == IsPrefixed::No { dest.write_str(b"to ")?; } k.to_css(dest) @@ -986,14 +1002,14 @@ impl LineDirection { VerticalPositionKeyword::Bottom => &b"180deg"[..], }) } else { - if !is_prefixed { + if is_prefixed == IsPrefixed::No { dest.write_str(b"to ")?; } k.to_css(dest) } } LineDirection::Corner(c) => { - if !is_prefixed { + if is_prefixed == IsPrefixed::No { dest.write_str(b"to ")?; } c.vertical.to_css(dest)?; @@ -1219,7 +1235,7 @@ impl WebKitColorStop { } else { dest.write_str(b"color-stop(")?; CSSNumberFns::to_css(self.position, dest)?; - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; self.color.to_css(dest)?; } dest.write_char(b')') @@ -1516,7 +1532,7 @@ fn serialize_items( if first { first = false; } else { - dest.delim(b',', false)?; + dest.delim(b',', WsBefore::No)?; } item.to_css(dest)?; last = Some(item); diff --git a/src/css/values/ident.rs b/src/css/values/ident.rs index 3cd4b213261b..b9eda4f5b886 100644 --- a/src/css/values/ident.rs +++ b/src/css/values/ident.rs @@ -1,6 +1,8 @@ use crate::SmallList; use crate::css_parser as css; -use crate::css_parser::{CssResult, Parser, PrintErr, Printer, Token}; +use crate::css_parser::{ + CssResult, HandleCssModule, IsDeclaration, Parser, PrintErr, Printer, Token, +}; use bun_ast::Ref; use bun_core::strings; @@ -169,7 +171,7 @@ impl DashedIdentReference { return dest.serialize_name(name); } } - dest.write_dashed_ident(&self.ident, false) + dest.write_dashed_ident(&self.ident, IsDeclaration::No) } } @@ -196,7 +198,7 @@ impl DashedIdent { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - dest.write_dashed_ident(self, true) + dest.write_dashed_ident(self, IsDeclaration::Yes) } } @@ -263,6 +265,8 @@ pub(crate) fn debug_ident<'a>(_raw: &'a [u8], _arena: &'a bun_alloc::Arena) -> D } } +bun_core::bool_enum!(IdentOrRefKind { Ident, Ref }); + impl IdentOrRef { #[inline] fn ptrbits(self) -> u64 { @@ -280,9 +284,9 @@ impl IdentOrRef { } #[inline] - fn pack(ptrbits: u64, ref_bit: bool, len: u64) -> Self { + fn pack(ptrbits: u64, ref_bit: IdentOrRefKind, len: u64) -> Self { let mut v: u128 = (ptrbits as u128) & PTRBITS_MASK; - if ref_bit { + if ref_bit == IdentOrRefKind::Ref { v |= REF_BIT; } v |= (len as u128) << 64; @@ -294,13 +298,13 @@ impl IdentOrRef { let (ptr, len) = (s.as_ptr() as usize as u64, s.len() as u64); // narrowing usize→u63 is checked in debug debug_assert!(ptr & (1u64 << 63) == 0); - Self::pack(ptr, false, len) + Self::pack(ptr, IdentOrRefKind::Ident, len) } pub(crate) fn from_ref(r: Ref, debug_ident: DebugIdent<'_>) -> Self { let len: u64 = r.to_raw_bits(); #[cfg(not(debug_assertions))] - let this = Self::pack(0, true, len); + let this = Self::pack(0, IdentOrRefKind::Ref, len); #[cfg(debug_assertions)] let this = { @@ -309,7 +313,7 @@ impl IdentOrRef { let heap_ptr: &mut *const [u8] = bump.alloc(std::ptr::from_ref::<[u8]>(slice)); let addr = std::ptr::from_mut::<*const [u8]>(heap_ptr) as usize as u64; debug_assert!(addr & (1u64 << 63) == 0); - Self::pack(addr, true, len) + Self::pack(addr, IdentOrRefKind::Ref, len) }; #[cfg(not(debug_assertions))] { @@ -441,16 +445,16 @@ impl CustomIdent { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - Self::to_css_with_options(self, dest, true) + Self::to_css_with_options(self, dest, HandleCssModule::Yes) } /// Write the custom ident to CSS. pub(crate) fn to_css_with_options( &self, dest: &mut Printer, - enabled_css_modules: bool, + enabled_css_modules: HandleCssModule, ) -> Result<(), PrintErr> { - let css_module_custom_idents_enabled = enabled_css_modules + let css_module_custom_idents_enabled = enabled_css_modules == HandleCssModule::Yes && if let Some(css_module) = &dest.css_module { css_module.config.custom_idents } else { @@ -459,7 +463,10 @@ impl CustomIdent { // SAFETY: arena-owned slice valid for the printer's `'a` lifetime // (`arena_str` yields an unbounded borrow, which coerces to `'a`). let v = unsafe { crate::arena_str(self.v) }; - dest.write_ident(v, css_module_custom_idents_enabled) + dest.write_ident( + v, + HandleCssModule::from_bool(css_module_custom_idents_enabled), + ) } } diff --git a/src/css/values/image.rs b/src/css/values/image.rs index 91d7c8c9572f..88def8b264a8 100644 --- a/src/css/values/image.rs +++ b/src/css/values/image.rs @@ -3,7 +3,7 @@ use crate::css_parser::CssResult as Result; use crate::dependencies::UrlDependency; use crate::generics::DeepClone as _; use crate::values::color::ColorFallbackKind; -use crate::values::gradient::Gradient; +use crate::values::gradient::{Gradient, IsPrefixed}; use crate::values::resolution::Resolution; use crate::values::url::Url; use crate::{PrintErr, VendorPrefix}; @@ -313,7 +313,7 @@ impl ImageSet { fn to_css(&self, dest: &mut css::Printer) -> core::result::Result<(), PrintErr> { self.vendor_prefix.to_css(dest)?; dest.write_str("image-set(")?; - let prefixed = self.vendor_prefix != VendorPrefix::NONE; + let prefixed = IsPrefixed::from_bool(self.vendor_prefix != VendorPrefix::NONE); dest.write_comma_separated(self.options.iter(), |d, opt| opt.to_css(d, prefixed))?; dest.write_char(b')') } @@ -421,9 +421,9 @@ impl ImageSetOption { fn to_css( &self, dest: &mut css::Printer, - is_prefixed: bool, + is_prefixed: IsPrefixed, ) -> core::result::Result<(), PrintErr> { - if matches!(self.image, Image::Url(_)) && !is_prefixed { + if matches!(self.image, Image::Url(_)) && is_prefixed == IsPrefixed::No { let Image::Url(url) = &self.image else { unreachable!() }; diff --git a/src/css/values/ratio.rs b/src/css/values/ratio.rs index 7dbc5e734843..bef08fcab7b5 100644 --- a/src/css/values/ratio.rs +++ b/src/css/values/ratio.rs @@ -1,4 +1,4 @@ -use crate::css_parser::{CssResult as Result, Parser, PrintErr, Printer}; +use crate::css_parser::{CssResult as Result, Parser, PrintErr, Printer, WsBefore}; use crate::values::number::{CSSNumber, CSSNumberFns}; /// A CSS [``](https://www.w3.org/TR/css-values-4/#ratios) value, @@ -38,7 +38,7 @@ impl Ratio { pub(crate) fn to_css(self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { CSSNumberFns::to_css(self.numerator, dest)?; if self.denominator != 1.0 { - dest.delim(b'/', true)?; + dest.delim(b'/', WsBefore::Yes)?; CSSNumberFns::to_css(self.denominator, dest)?; } Ok(()) diff --git a/src/css/values/syntax.rs b/src/css/values/syntax.rs index d12b585f5308..766d578e6467 100644 --- a/src/css/values/syntax.rs +++ b/src/css/values/syntax.rs @@ -1,6 +1,8 @@ use crate::css_parser as css; -use crate::css_parser::{CssResult, ParserError, ParserOptions, PrintErr, Printer, Token}; -use crate::properties::custom::TokenList; +use crate::css_parser::{ + CssResult, ParserError, ParserOptions, PrintErr, Printer, Token, WsBefore, +}; +use crate::properties::custom::{IsCustomProperty, TokenList}; use crate::properties::transform::{Transform, TransformList}; use crate::values::angle::Angle; use crate::values::color::CssColor; @@ -45,7 +47,7 @@ impl SyntaxString { SyntaxString::Components(components) => { dest.write_separated( components.iter(), - |d| d.delim(b'|', true), + |d| d.delim(b'|', WsBefore::Yes), |d, c| c.to_css(d), )?; } @@ -465,13 +467,13 @@ impl ParsedComponent { ParsedComponent::Repeated(r) => dest.write_separated( r.components.iter(), |d| match r.multiplier { - Multiplier::Comma => d.delim(b',', false), + Multiplier::Comma => d.delim(b',', WsBefore::No), Multiplier::Space => d.write_char(b' '), Multiplier::None => unreachable!(), }, |d, c| c.to_css(d), ), - ParsedComponent::TokenList(t) => t.to_css(dest, false), + ParsedComponent::TokenList(t) => t.to_css(dest, IsCustomProperty::No), } } diff --git a/src/dotenv/env_loader.rs b/src/dotenv/env_loader.rs index 57b4464602a8..5e19e0520989 100644 --- a/src/dotenv/env_loader.rs +++ b/src/dotenv/env_loader.rs @@ -162,6 +162,16 @@ static NODE_PATH_TO_USE_SET_ONCE: bun_core::RwLock>> = bun_core // PORTING.md §Concurrency: OnceLock — set once from CLI flag, read many. pub static HAS_NO_CLEAR_SCREEN_CLI_FLAG: OnceLock = OnceLock::new(); +bun_core::bool_enum!( + /// Which proxy variable pair to consult: `https_proxy` or `http_proxy`. + pub HttpScheme { Https, Http } +); + +bun_core::bool_enum!( + /// Skip the implicit `.env*` files (only explicit `--env-file`s are loaded). + pub SkipDefaultEnv +); + impl Loader { /// Shared "empty-ish" predicate for proxy env vars: an unset/empty value, /// or a literal empty-quote pair left over from shell `export FOO=""` / @@ -323,7 +333,11 @@ impl Loader { } pub fn get_http_proxy_for(&self, url: &URL<'_>) -> Option> { - self.get_http_proxy(url.is_http(), Some(url.hostname), Some(url.host)) + self.get_http_proxy( + HttpScheme::from_bool(url.is_http()), + Some(url.hostname), + Some(url.host), + ) } pub fn has_http_proxy(&self) -> bool { @@ -338,14 +352,14 @@ impl Loader { /// `host` is the host with port if present (e.g., "localhost:3000") pub fn get_http_proxy( &self, - is_http: bool, + is_http: HttpScheme, hostname: Option<&[u8]>, host: Option<&[u8]>, ) -> Option> { // TODO: When Web Worker support is added, make sure to intern these strings let mut http_proxy: Option> = None; - let proxy = if is_http { + let proxy = if is_http == HttpScheme::Http { self.get_lower_then_upper(b"http_proxy", b"HTTP_PROXY") } else { self.get_lower_then_upper(b"https_proxy", b"HTTPS_PROXY") @@ -418,7 +432,11 @@ impl Loader { if has_port { // Entry has a port, do exact match against host:port if let Some(h) = host { - if strings::eql_case_insensitive_ascii(h, no_proxy_entry, true) { + if strings::eql_case_insensitive_ascii( + h, + no_proxy_entry, + strings::CheckLen::Yes, + ) { return true; } } @@ -426,7 +444,11 @@ impl Loader { // Entry is hostname/IPv6 only, match exact or dot-boundary suffix (case-insensitive) let entry_len = no_proxy_entry.len(); if hn.len() == entry_len { - if strings::eql_case_insensitive_ascii(hn, no_proxy_entry, true) { + if strings::eql_case_insensitive_ascii( + hn, + no_proxy_entry, + strings::CheckLen::Yes, + ) { return true; } } else if hn.len() > entry_len @@ -434,7 +456,7 @@ impl Loader { && strings::eql_case_insensitive_ascii( &hn[hn.len() - entry_len..], no_proxy_entry, - true, + strings::CheckLen::Yes, ) { return true; @@ -635,7 +657,7 @@ impl Loader { dir: &D, env_files: &[&[u8]], suffix: DotEnvFileSuffix, - skip_default_env: bool, + skip_default_env: SkipDefaultEnv, ) -> crate::Result<()> { // `suffix` is a runtime arg (avoids unstable adt_const_params; cold path). let start = bun_core::time::nano_timestamp(); @@ -653,7 +675,7 @@ impl Loader { // // See https://github.com/oven-sh/bun/issues/9635#issuecomment-2021350123 // for more details on how this edge case works. - if !skip_default_env { + if skip_default_env == SkipDefaultEnv::No { self.load_default_files(suffix, dir, &mut value_buffer)?; } } @@ -924,6 +946,11 @@ struct Parser<'a> { // there (NBSP is C2 A0), and trimming it byte-wise corrupts multi-byte sequences. const WHITESPACE_CHARS: &[u8] = b"\t\x0B\x0C \n\r"; +bun_core::bool_enum!( + /// Whether `$`-expansion produced anything different from the input. + Expanded { Unchanged, Changed } +); + impl<'a> Parser<'a> { fn skip_line(&mut self) { if let Some(i) = strings::index_of_any(&self.src[self.pos..], b"\n\r") { @@ -1094,7 +1121,7 @@ impl<'a> Parser<'a> { return Ok(None); } self.value_buffer.clear(); - if !Self::expand_into(map, value, self.value_buffer, 0) { + if Self::expand_into(map, value, self.value_buffer, 0) == Expanded::Unchanged { return Ok(None); } Ok(Some(self.value_buffer.as_slice())) @@ -1104,7 +1131,7 @@ impl<'a> Parser<'a> { /// `${...}` locates its matching `}` by depth (`${` opens, `}` closes, /// `\x` skipped); malformed forms fall through as literal text. The `:-` /// default clause is expanded recursively. - fn expand_into(map: &Map, value: &[u8], out: &mut Vec, depth: u8) -> bool { + fn expand_into(map: &Map, value: &[u8], out: &mut Vec, depth: u8) -> Expanded { #[inline] fn is_ident(b: u8) -> bool { matches!(b, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_') @@ -1199,7 +1226,7 @@ impl<'a> Parser<'a> { out.push(b'$'); pos += 1; } - changed + Expanded::from_bool(changed) } fn parse( diff --git a/src/dotenv/lib.rs b/src/dotenv/lib.rs index 8a60edc0ec4f..9f588020a73e 100644 --- a/src/dotenv/lib.rs +++ b/src/dotenv/lib.rs @@ -7,8 +7,8 @@ pub use error::{Error, Result}; pub use env_loader::{ DirEntryKeys, DirEntryProbe, DotEnvBehavior, DotEnvFileSuffix, HAS_NO_CLEAR_SCREEN_CLI_FLAG, - HashTable, HashTableValue, INSTANCE, Loader, Map, NullDelimitedEnvMap, S3Credentials, - StdEnvMapWrapper, instance, set_instance, + HashTable, HashTableValue, HttpScheme, INSTANCE, Loader, Map, NullDelimitedEnvMap, + S3Credentials, SkipDefaultEnv, StdEnvMapWrapper, instance, set_instance, }; /// `dotenv::map::{HashTable, Entry}` namespace expected by `install_jsc::ini_jsc` et al. diff --git a/src/glob/GlobWalker.rs b/src/glob/GlobWalker.rs index ef28a0d13532..ef9ca95082c7 100644 --- a/src/glob/GlobWalker.rs +++ b/src/glob/GlobWalker.rs @@ -236,6 +236,12 @@ type IgnoreFilterFn = fn(&[u8]) -> bool; /// component count works. pub type ComponentSet = AutoBitSet; +bun_core::bool_enum!(pub Dot); +bun_core::bool_enum!(pub Absolute); +bun_core::bool_enum!(pub FollowSymlinks); +bun_core::bool_enum!(pub ErrorOnBrokenSymlinks); +bun_core::bool_enum!(pub OnlyFiles); + pub struct GlobWalker { // PERF: per-walk allocations (paths, workbuf, matchedPaths) use the // global allocator; an arena could bulk-free them — profile if hot. @@ -248,13 +254,13 @@ pub struct GlobWalker { pub(crate) pattern_components: Vec, pub matched_paths: MatchedMap, - pub(crate) dot: bool, - pub(crate) absolute: bool, + pub(crate) dot: Dot, + pub(crate) absolute: Absolute, pub(crate) cwd: Box<[u8]>, - pub(crate) follow_symlinks: bool, - pub(crate) error_on_broken_symlinks: bool, - pub(crate) only_files: bool, + pub(crate) follow_symlinks: FollowSymlinks, + pub(crate) error_on_broken_symlinks: ErrorOnBrokenSymlinks, + pub(crate) only_files: OnlyFiles, pub(crate) path_buf: Box, // iteration state @@ -558,7 +564,7 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { let mut dir_path_buf = Box::new(PathBuffer::uninit()); let mut dir_path_len: usize = 'dir_path: { if ROOT { - if !self.walker.absolute { + if self.walker.absolute == Absolute::No { dir_path_buf[0] = 0; break 'dir_path 0; } @@ -684,9 +690,9 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { }; self.close_disallowing_cwd(fd); let mode = stat_result.st_mode as u32; - let matches = (S::ISDIR(mode) && !self.walker.only_files) + let matches = (S::ISDIR(mode) && self.walker.only_files == OnlyFiles::No) || S::ISREG(mode) - || !self.walker.only_files; + || self.walker.only_files == OnlyFiles::No; if matches { if let Some(path) = self .walker @@ -889,13 +895,15 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { if err.get_errno() == E::ENOTDIR { break 'brk None; } - if self.walker.error_on_broken_symlinks { + if self.walker.error_on_broken_symlinks + == ErrorOnBrokenSymlinks::Yes + { return Ok(Err(self.walker.handle_sys_err_with_path( &err, symlink_full_path_z, ))); } - if !self.walker.only_files + if self.walker.only_files == OnlyFiles::No && self.walker.eval_file(&active, entry_name) { match self.walker.prepare_matched_path_symlink( @@ -971,7 +979,7 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { self.close_disallowing_cwd(dir_fd); } - if add_dir && !self.walker.only_files { + if add_dir && self.walker.only_files == OnlyFiles::No { match self .walker .prepare_matched_path_symlink(symlink_full_path_z.as_bytes())? @@ -1060,7 +1068,7 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { ); } } - if add_dir && !self.walker.only_files { + if add_dir && self.walker.only_files == OnlyFiles::No { match self.walker.prepare_matched_path(entry_name, dir_dir_path)? { Some(prepared_path) => { return Ok(Ok(Some(prepared_path))); @@ -1076,6 +1084,7 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { // followSymlinks option governs wildcard traversal, // not explicitly-spelled path segments. let follow_active: Option = if self.walker.follow_symlinks + == FollowSymlinks::Yes { self.walker .eval_impl(&active, entry_name) @@ -1094,7 +1103,7 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { continue; } - if self.walker.only_files { + if self.walker.only_files == OnlyFiles::Yes { continue; } @@ -1154,7 +1163,7 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { None, ); } - if add_dir && !self.walker.only_files { + if add_dir && self.walker.only_files == OnlyFiles::No { match self .walker .prepare_matched_path(entry_name, dir_dir_path)? @@ -1168,7 +1177,7 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { } bun_sys::FileKind::SymLink => { let follow_active: Option = - if self.walker.follow_symlinks { + if self.walker.follow_symlinks == FollowSymlinks::Yes { Some(active.clone().expect("OOM")) } else { let subset = self @@ -1182,7 +1191,7 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { entry_name, follow_active, )?; - } else if !self.walker.only_files { + } else if self.walker.only_files == OnlyFiles::No { if self.walker.eval_file(&active, entry_name) { match self .walker @@ -1383,11 +1392,11 @@ impl GlobWalker { // Note: out-param constructor reshaped to return Self. pub fn init( pattern: &[u8], - dot: bool, - absolute: bool, - follow_symlinks: bool, - error_on_broken_symlinks: bool, - only_files: bool, + dot: Dot, + absolute: Absolute, + follow_symlinks: FollowSymlinks, + error_on_broken_symlinks: ErrorOnBrokenSymlinks, + only_files: OnlyFiles, ignore_filter_fn: Option, ) -> Result, Error> { // `bun_paths::fs::FileSystem` (singleton holds only the cwd string; the @@ -1430,11 +1439,11 @@ impl GlobWalker { pub fn init_with_cwd( pattern: &[u8], cwd: &[u8], - dot: bool, - absolute: bool, - follow_symlinks: bool, - error_on_broken_symlinks: bool, - only_files: bool, + dot: Dot, + absolute: Absolute, + follow_symlinks: FollowSymlinks, + error_on_broken_symlinks: ErrorOnBrokenSymlinks, + only_files: OnlyFiles, ignore_filter_fn: Option, ) -> Result, Error> { log!("initWithCwd(cwd={})", bstr::BStr::new(cwd)); @@ -1666,7 +1675,7 @@ impl GlobWalker { return None; } - let hidden = !self.dot && Self::starts_with_dot(entry_name); + let hidden = self.dot == Dot::No && Self::starts_with_dot(entry_name); // Handle double wildcard `**`, this could possibly // propagate the `**` to the directory's children @@ -1762,7 +1771,7 @@ impl GlobWalker { log!("matchPatternImpl: {}", bstr::BStr::new(filepath)); // A pattern segment that itself starts with a literal `.` opts into // matching dotfiles for that segment, regardless of the `dot` flag. - if !self.dot + if self.dot == Dot::No && Self::starts_with_dot(filepath) && !Self::starts_with_dot(pattern_component.pattern_slice(&self.pattern)) { @@ -1807,7 +1816,7 @@ impl GlobWalker { let mut child = self.make_set(); let comps = &self.pattern_components; let len: u32 = u32::try_from(comps.len()).expect("int cast"); - let hidden = !self.dot && Self::starts_with_dot(entry_name); + let hidden = self.dot == Dot::No && Self::starts_with_dot(entry_name); let mut it = active.iterator::(); while let Some(i) = it.next() { let idx: u32 = u32::try_from(i).expect("int cast"); @@ -1956,7 +1965,7 @@ impl GlobWalker { #[inline] fn join(&self, subdir_parts: &[&[u8]]) -> Result, AllocError> { - if !self.absolute { + if self.absolute == Absolute::No { // If relative paths enabled, stdlib join is preferred over // ResolvePath.joinBuf because it doesn't try to normalize the path return Ok(bun_paths::join_sep_maybe_z::(subdir_parts)); diff --git a/src/glob/lib.rs b/src/glob/lib.rs index 5aa5fbdacdfe..1dc2dc0ffc3f 100644 --- a/src/glob/lib.rs +++ b/src/glob/lib.rs @@ -7,7 +7,7 @@ pub mod matcher; // `match` is a Rust keyword; re-export with raw identifier. pub use crate::glob_walker as walk; pub use crate::matcher::{MatchResult, r#match}; -pub use walk::GlobWalker; +pub use walk::{Absolute, Dot, ErrorOnBrokenSymlinks, FollowSymlinks, GlobWalker, OnlyFiles}; // `ignore_filter_fn` is a runtime fn-pointer field supplied at `init()` rather than a type // parameter (const-generic fn ptrs are unstable). diff --git a/src/highway/lib.rs b/src/highway/lib.rs index 95eb419fd7f6..80a5be819662 100644 --- a/src/highway/lib.rs +++ b/src/highway/lib.rs @@ -679,10 +679,23 @@ pub fn decode_hex_u16(src: &[u16], dst: &mut [u8]) -> usize { written } +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum SkipMask { + No, + Yes, +} + +impl SkipMask { + #[inline] + pub const fn from_bool(b: bool) -> Self { + if b { Self::Yes } else { Self::No } + } +} + /// Apply a WebSocket mask to data using SIMD acceleration -/// If skip_mask is true, data is copied without masking +/// With `SkipMask::Yes`, data is copied without masking #[inline(always)] -pub fn fill_with_skip_mask(mask: [u8; 4], output: &mut [u8], input: &[u8], skip_mask: bool) { +pub fn fill_with_skip_mask(mask: [u8; 4], output: &mut [u8], input: &[u8], skip_mask: SkipMask) { if input.is_empty() { return; } @@ -695,7 +708,7 @@ pub fn fill_with_skip_mask(mask: [u8; 4], output: &mut [u8], input: &[u8], skip_ output.as_mut_ptr(), input.as_ptr(), input.len(), - skip_mask, + skip_mask == SkipMask::Yes, ); } } @@ -707,7 +720,7 @@ pub fn fill_with_skip_mask(mask: [u8; 4], output: &mut [u8], input: &[u8], skip_ /// reads-before-writes per lane (it's `dst[i] = src[i] ^ mask[i&3]`), so /// feeding it `src == dst` is sound. #[inline(always)] -pub fn fill_with_skip_mask_inplace(mask: [u8; 4], buf: &mut [u8], skip_mask: bool) { +pub fn fill_with_skip_mask_inplace(mask: [u8; 4], buf: &mut [u8], skip_mask: SkipMask) { if buf.is_empty() { return; } @@ -722,7 +735,7 @@ pub fn fill_with_skip_mask_inplace(mask: [u8; 4], buf: &mut [u8], skip_mask: boo buf.as_mut_ptr(), buf.as_ptr(), buf.len(), - skip_mask, + skip_mask == SkipMask::Yes, ); } } diff --git a/src/http/AsyncHTTP.rs b/src/http/AsyncHTTP.rs index 970c61bbe591..7f2e909db877 100644 --- a/src/http/AsyncHTTP.rs +++ b/src/http/AsyncHTTP.rs @@ -88,7 +88,7 @@ const fn noop_callback() -> HTTPClientResultCallback { /// /// # Safety /// `href` must have been allocated via the global allocator as a `Box<[u8]>` -/// and ownership ceded to this module via `is_url_owned = true`. +/// and ownership ceded to this module via `UrlOwnership::Owned`. #[inline] unsafe fn free_owned_href(href: &'static [u8]) { if !href.is_empty() { @@ -322,7 +322,7 @@ struct Preconnect { // (late-init); `None` is never observed after `preconnect()` populates it. async_http: Option>, url: URL<'static>, - is_url_owned: bool, + is_url_owned: UrlOwnership, } impl Preconnect { @@ -335,7 +335,7 @@ impl Preconnect { .as_mut() .expect("Preconnect.async_http set in preconnect()") .clear_data(); - if (*this).is_url_owned { + if (*this).is_url_owned == UrlOwnership::Owned { // SAFETY: `is_url_owned` is the caller's promise that `url.href` // is a global-allocator `Box<[u8]>` we now own. free_owned_href((*this).url.href); @@ -347,9 +347,11 @@ impl Preconnect { } } -pub fn preconnect(url: URL<'static>, is_url_owned: bool) { +bun_core::bool_enum!(pub UrlOwnership { Borrowed, Owned }); + +pub fn preconnect(url: URL<'static>, is_url_owned: UrlOwnership) { if !FeatureFlags::IS_FETCH_PRECONNECT_SUPPORTED { - if is_url_owned { + if is_url_owned == UrlOwnership::Owned { // SAFETY: `is_url_owned` is the caller's promise that `url.href` is a // global-allocator `Box<[u8]>` we now own. unsafe { free_owned_href(url.href) }; diff --git a/src/http/Decompressor.rs b/src/http/Decompressor.rs index aca7133da552..b4ed95f705ce 100644 --- a/src/http/Decompressor.rs +++ b/src/http/Decompressor.rs @@ -1,4 +1,5 @@ use bun_core::MutableString; +use bun_core::compress::Chunk; use bun_http_types::Encoding::Encoding; // The streaming decoders below own only their C-side state and take @@ -57,7 +58,7 @@ impl Decompressor { encoding: Encoding, buffer: &[u8], body_out_str: &mut MutableString, - is_done: bool, + is_done: Chunk, ) -> crate::Result<()> { if !encoding.is_compressed() { return Ok(()); diff --git a/src/http/H2Client.rs b/src/http/H2Client.rs index a946e78c8fd4..d4da44058c82 100644 --- a/src/http/H2Client.rs +++ b/src/http/H2Client.rs @@ -62,6 +62,7 @@ pub mod stream; pub use client_session::ClientSession; pub(crate) use client_session::SessionPtr; +pub use encode::EndStream; pub use pending_connect::PendingConnect; pub use stream::Stream; @@ -121,7 +122,7 @@ mod bridge { pub(crate) fn h2_handle_response_body( &mut self, buf: &[u8], - is_only_buffer: bool, + is_only_buffer: crate::OnlyBuffer, ) -> crate::Result { self.handle_response_body(buf, is_only_buffer) } diff --git a/src/http/HTTPContext.rs b/src/http/HTTPContext.rs index 508956c59d3a..79ab4ca627a3 100644 --- a/src/http/HTTPContext.rs +++ b/src/http/HTTPContext.rs @@ -6,7 +6,8 @@ use crate::Error; use crate::http_thread::InitOpts as HTTPThreadInitOpts; use crate::ssl_config::{self, SSLConfig}; use crate::{ - self as http, AlpnOffer, HTTPCertError, HTTPClient, InitError, get_cert_error_from_no, h2, + self as http, AllowProxyUrl, AlpnOffer, HTTPCertError, HTTPClient, InitError, + get_cert_error_from_no, h2, }; use bun_boringssl::ssl_ctx_setup; use bun_boringssl_sys::SSL_CTX; @@ -760,7 +761,11 @@ impl HTTPContext { if socket.target_port != target_port { continue; } - if !strings::eql_long(&socket.target_hostname, target_hostname, true) { + if !strings::eql_long( + &socket.target_hostname, + target_hostname, + strings::CheckLen::Yes, + ) { continue; } // A tunnel established with reject_unauthorized=false never @@ -793,7 +798,7 @@ impl HTTPContext { if strings::eql_long( &socket.hostname_buf[..socket.hostname_len as usize], hostname, - true, + strings::CheckLen::Yes, ) { let http_socket = socket.http_socket; @@ -862,7 +867,7 @@ impl HTTPContext { .cast::>(), ) .ptr(), - false, // dont allow half-open sockets + uws::AllowHalfOpen::No, )?; client.allow_retry = false; Ok(Some(socket)) @@ -1047,7 +1052,7 @@ impl HTTPContext { .cast::>(), ) .ptr(), - false, + uws::AllowHalfOpen::No, )?; client.allow_retry = false; if SSL { @@ -1184,7 +1189,7 @@ impl Handler { .expect("TLS socket has native handle after handshake") .cast::() }; - if !client.check_server_identity::(socket, ssl, true) { + if !client.check_server_identity::(socket, ssl, AllowProxyUrl::Yes) { // checkServerIdentity already called closeAndFail() → fail() // → result callback, which may have destroyed the // AsyncHTTP that embeds `client`. Socket is terminated diff --git a/src/http/HeaderValueIterator.rs b/src/http/HeaderValueIterator.rs index aa32d87aa4dd..4d38bc65df0d 100644 --- a/src/http/HeaderValueIterator.rs +++ b/src/http/HeaderValueIterator.rs @@ -41,15 +41,17 @@ pub fn upgrade_header_is_not_h2(value: &[u8]) -> bool { .any(|token| !strings::eql_any_case_insensitive_ascii(token, &[b"h2", b"h2c"])) } -/// `Some(false)` if any token is `close` (wins), `Some(true)` if `keep-alive`, else `None`. -pub fn connection_header_keep_alive(value: &[u8]) -> Option { +bun_core::bool_enum!(pub ConnectionHeader { Close, KeepAlive }); + +/// `Some(Close)` if any token is `close` (wins), `Some(KeepAlive)` if `keep-alive`, else `None`. +pub fn connection_header_keep_alive(value: &[u8]) -> Option { let mut keep_alive = None; for token in HeaderValueIterator::init(value) { if strings::eql_case_insensitive_ascii_check_length(token, b"close") { - return Some(false); + return Some(ConnectionHeader::Close); } if strings::eql_case_insensitive_ascii_check_length(token, b"keep-alive") { - keep_alive = Some(true); + keep_alive = Some(ConnectionHeader::KeepAlive); } } keep_alive diff --git a/src/http/InternalState.rs b/src/http/InternalState.rs index 2bef8005cbb6..206ecab630ec 100644 --- a/src/http/InternalState.rs +++ b/src/http/InternalState.rs @@ -1,6 +1,7 @@ use crate::Error; use bun_core::MutableString; use bun_core::Output; +use bun_core::compress::Chunk; use crate::{CertificateInfo, Decompressor, Encoding, HTTPRequestBody, HTTPResponseMetadata}; @@ -231,7 +232,7 @@ impl<'a> InternalState<'a> { } /// Mark the body complete and drive `process_body_buffer` one last time - /// with `is_final_chunk = true` so a compressed stream that never reached + /// with `Chunk::Last` so a compressed stream that never reached /// stream-end is rejected. Call from every site that flips /// `received_last_chunk` on an end-of-body signal that arrives with no /// accompanying body bytes (h1 FIN, proxy-tunnel close, h2/h3 END_STREAM). @@ -239,13 +240,13 @@ impl<'a> InternalState<'a> { pub(crate) fn finalize_body_on_eof(&mut self) -> Result<(), Error> { self.flags.received_last_chunk = true; let buffer_snap = core::mem::take(&mut self.get_body_buffer().list); - self.process_body_buffer(buffer_snap, true).map(drop) + self.process_body_buffer(buffer_snap, Chunk::Last).map(drop) } pub(crate) fn decompress_bytes( &mut self, buffer: &[u8], - is_final_chunk: bool, + is_final_chunk: Chunk, ) -> Result<(), Error> { // A response that declared a Content-Encoding but sent zero body bytes // (e.g. an empty chunked gzip response) has nothing to decompress. @@ -266,7 +267,7 @@ impl<'a> InternalState<'a> { 'libdeflate: { use bun_libdeflate_sys::libdeflate as bun_libdeflate; - if !(is_final_chunk + if !(is_final_chunk == Chunk::Last && !self.flags.is_libdeflate_fast_path_disabled && self.encoding.can_use_lib_deflate() && self.is_done()) @@ -367,7 +368,7 @@ impl<'a> InternalState<'a> { self.encoding, buffer, &mut self.decoded_body, - is_done, + Chunk::from_bool(is_done), ) { if is_done || err != crate::Error::ShortRead { bun_core::pretty_errorln!( @@ -392,7 +393,7 @@ impl<'a> InternalState<'a> { pub(crate) fn process_body_buffer( &mut self, mut buffer: Vec, - is_final_chunk: bool, + is_final_chunk: Chunk, ) -> Result { if self.flags.is_redirect_pending { // Caller moved the bytes out of the body buffer; put them back so the diff --git a/src/http/ProxyTunnel.rs b/src/http/ProxyTunnel.rs index 43f8394b3d92..33b593b7ff6a 100644 --- a/src/http/ProxyTunnel.rs +++ b/src/http/ProxyTunnel.rs @@ -10,8 +10,10 @@ use crate::http_cert_error::HTTPCertError; use crate::http_context::HTTPSocket; use crate::internal_state::{HTTPStage, Stage}; use crate::ssl_config::SSLConfig; -use crate::ssl_wrapper::{Handlers as SSLWrapperHandlers, InitError, SSLWrapper, WriteDataError}; -use crate::{AlpnOffer, HTTPClient}; +use crate::ssl_wrapper::{ + FastShutdown, Handlers as SSLWrapperHandlers, InitError, SSLWrapper, WriteDataError, +}; +use crate::{AllowProxyUrl, AlpnOffer, HTTPClient, OnlyBuffer}; bun_core::declare_scope!(http_proxy_tunnel, visible); @@ -284,7 +286,7 @@ fn on_data(ctx: *mut HTTPClient, decoded_data: &[u8]) { if decoded_data.is_empty() { return; } - let report_progress = match this.handle_response_body(decoded_data, false) { + let report_progress = match this.handle_response_body(decoded_data, OnlyBuffer::No) { Ok(v) => v, Err(err) => { // `this` is dead (NLL); reenter via raw ptr so on_close's @@ -391,7 +393,7 @@ fn on_handshake( let ssl = unsafe { &mut *ssl_ptr.as_ptr() }; match ProxyTunnel::socket_of(proxy_nn) { &Socket::Ssl(socket) => { - if !this.check_server_identity::(socket, ssl, false) { + if !this.check_server_identity::(socket, ssl, AllowProxyUrl::No) { scoped_log!( http_proxy_tunnel, "ProxyTunnel onHandshake checkServerIdentity failed" @@ -404,7 +406,7 @@ fn on_handshake( } } &Socket::Tcp(socket) => { - if !this.check_server_identity::(socket, ssl, false) { + if !this.check_server_identity::(socket, ssl, AllowProxyUrl::No) { scoped_log!( http_proxy_tunnel, "ProxyTunnel onHandshake checkServerIdentity failed" @@ -588,7 +590,7 @@ impl ProxyTunnel { let custom_options = ssl_options.as_usockets_for_client_verification(); let wrapper = match ProxyTunnelWrapper::init_from_options( &custom_options, - true, + uws::TlsRole::Client, SSLWrapperHandlers { on_open, on_data, @@ -656,7 +658,7 @@ impl ProxyTunnel { // across the reentrant call. if let Some(wrapper) = ProxyTunnel::wrapper_ref(this.as_ptr()) { // fast shutdown the connection - let _ = wrapper.shutdown(true); + let _ = wrapper.shutdown(FastShutdown::Yes); } } @@ -665,7 +667,7 @@ impl ProxyTunnel { // tunnel and the caller's `&mut` borrows are NLL-dead before this call. if let Some(wrapper) = unsafe { &*addr_of!((*this.as_ptr()).wrapper) } { // fast shutdown the connection - let _ = wrapper.shutdown(true); + let _ = wrapper.shutdown(FastShutdown::Yes); } } diff --git a/src/http/h2_client/ClientSession.rs b/src/http/h2_client/ClientSession.rs index 580af0a9510c..b39ad08d05c5 100644 --- a/src/http/h2_client/ClientSession.rs +++ b/src/http/h2_client/ClientSession.rs @@ -18,7 +18,9 @@ use crate::internal_state::HTTPStage; use crate::lshpack; use crate::signals; use crate::ssl_config; -use crate::{HTTPClient, HTTPVerboseLevel, HeaderResult, NewHTTPContext, Protocol}; +use crate::{ + HTTPClient, HTTPVerboseLevel, HeaderResult, IntoShared, NewHTTPContext, OnlyBuffer, Protocol, +}; /// HTTP/2 only ever runs over TLS in this client (ALPN "h2"). pub type Socket = HTTPSocket; @@ -402,7 +404,7 @@ impl ClientSession { self.port == port && mine == ssl_config && self.host_header_hash == host_header_hash - && strings::eql_long(&self.hostname, hostname, true) + && strings::eql_long(&self.hostname, hostname, strings::CheckLen::Yes) } fn adopt_client(&mut self, client: &mut HTTPClient) { @@ -546,7 +548,7 @@ impl ClientSession { self.rearm_timeout(); // DATA-frame encoding may yield mid-body — compress into the Vec so the // cursor stays valid across event-loop ticks. - if let Err(e) = client.compress_body_for_send(false) { + if let Err(e) = client.compress_body_for_send(IntoShared::No) { self.remove_stream(stream); client.h2 = None; client.fail(e); @@ -582,9 +584,9 @@ impl ClientSession { Protocol::Http2, &request, client.url.href, - !client.flags.reject_unauthorized, + bun_picohttp::IgnoreInsecure::from_bool(!client.flags.reject_unauthorized), client.state.request_body.slice(), - client.verbose == HTTPVerboseLevel::Curl, + client.verbose, ); } client.state.request_stage = if stream_ref.local_closed() { @@ -1188,7 +1190,7 @@ impl ClientSession { stream.client = None; client.h2 = None; } - let report = match client.h2_handle_response_body(&stream.body_buffer, false) { + let report = match client.h2_handle_response_body(&stream.body_buffer, OnlyBuffer::No) { Ok(r) => r, Err(err) => { stream.body_buffer.clear(); diff --git a/src/http/h2_client/PendingConnect.rs b/src/http/h2_client/PendingConnect.rs index a4ed14d9b639..13e7bcf96bb9 100644 --- a/src/http/h2_client/PendingConnect.rs +++ b/src/http/h2_client/PendingConnect.rs @@ -55,7 +55,7 @@ impl PendingConnect { self.port == port && self.ssl_config == ssl_config && self.host_header_hash == host_header_hash - && strings::eql_long(&self.hostname, hostname, true) + && strings::eql_long(&self.hostname, hostname, strings::CheckLen::Yes) } /// Remove `this` from `ctx.pending_h2_connects` and hand the owning diff --git a/src/http/h2_client/encode.rs b/src/http/h2_client/encode.rs index 55e93a2d2c45..4d983a060925 100644 --- a/src/http/h2_client/encode.rs +++ b/src/http/h2_client/encode.rs @@ -9,6 +9,7 @@ use crate::HTTPClient; use crate::h2_frame_parser as wire; use crate::http_request_body::HTTPRequestBody; use crate::internal_state::HTTPStage; +use crate::lshpack::NeverIndex; use bun_core::strings; use bun_picohttp as picohttp; @@ -126,9 +127,21 @@ pub(crate) fn write_request( } } - encode_header(session, &mut encoded, b":method", request.method, false)?; - encode_header(session, &mut encoded, b":scheme", b"https", false)?; - encode_header(session, &mut encoded, b":authority", authority, false)?; + encode_header( + session, + &mut encoded, + b":method", + request.method, + NeverIndex::No, + )?; + encode_header(session, &mut encoded, b":scheme", b"https", NeverIndex::No)?; + encode_header( + session, + &mut encoded, + b":authority", + authority, + NeverIndex::No, + )?; encode_header( session, &mut encoded, @@ -138,7 +151,7 @@ pub(crate) fn write_request( } else { b"/" }, - false, + NeverIndex::No, )?; for h in request.headers { @@ -154,7 +167,7 @@ pub(crate) fn write_request( heap = vec![0u8; h.name().len()]; strings::copy_lowercase_if_needed(h.name(), &mut heap) }; - let mut never_index = false; + let mut never_index = NeverIndex::No; if let Some(kind) = classify_request_header(name) { match kind { RequestHeader::Drop | RequestHeader::Host => continue, @@ -171,7 +184,7 @@ pub(crate) fn write_request( continue; } } - RequestHeader::Sensitive => never_index = true, + RequestHeader::Sensitive => never_index = NeverIndex::Yes, RequestHeader::Expect => {} } } @@ -197,7 +210,7 @@ pub(crate) fn write_request( session, stream.id, &encoded, - !has_inline_body && !is_streaming, + EndStream::from_bool(!has_inline_body && !is_streaming), ); if encoded.capacity() > 64 * 1024 { encoded = Vec::new(); @@ -212,11 +225,16 @@ pub(crate) fn write_request( Ok(()) } +bun_core::bool_enum!( + /// The END_STREAM flag on a DATA/HEADERS frame. + pub EndStream +); + pub(crate) fn write_header_block( session: &mut ClientSession, stream_id: u32, block: &[u8], - end_stream: bool, + end_stream: EndStream, ) { let max: usize = session.remote_max_frame_size as usize; let mut remaining = block; @@ -229,7 +247,7 @@ pub(crate) fn write_header_block( if last { flags |= wire::HeadersFrameFlags::END_HEADERS as u8; } - if first && end_stream { + if first && end_stream == EndStream::Yes { flags |= wire::HeadersFrameFlags::END_STREAM as u8; } session.write_frame( @@ -256,7 +274,7 @@ pub(crate) fn write_data_windowed( session: &mut ClientSession, stream: &mut Stream, data: &[u8], - end_stream: bool, + end_stream: EndStream, cap: usize, ) -> usize { let mut remaining = data; @@ -281,7 +299,7 @@ pub(crate) fn write_data_windowed( .min(session.remote_max_frame_size as usize) .min(window); let last = chunk_len == remaining.len(); - let flags: u8 = if last && end_stream { + let flags: u8 = if last && end_stream == EndStream::Yes { wire::DataFrameFlags::END_STREAM as u8 } else { 0 @@ -316,7 +334,7 @@ pub(crate) fn drain_send_body(session: &mut ClientSession, stream: &mut Stream, match &mut client.state.original_request_body { HTTPRequestBody::Bytes(_) => { let pending = stream.pending_body; - let sent = write_data_windowed(session, stream, pending.slice(), true, cap); + let sent = write_data_windowed(session, stream, pending.slice(), EndStream::Yes, cap); // pending_body[sent..] is a suffix of the original slice. stream.pending_body = bun_ptr::RawSlice::new(&pending.slice()[sent..]); if stream.pending_body.is_empty() { @@ -339,7 +357,7 @@ pub(crate) fn drain_send_body(session: &mut ClientSession, stream: &mut Stream, } // SAFETY: data_ptr[cursor..cursor+data_len] is the readable slice. let data = unsafe { bun_core::ffi::slice(data_ptr.add(cursor), data_len) }; - let sent = write_data_windowed(session, stream, data, ended, cap); + let sent = write_data_windowed(session, stream, data, EndStream::from_bool(ended), cap); // We still hold the lock from `acquire()` above; `sb` is the sole // live borrow, so reborrowing `&mut sb.buffer` is a child access. let buffer = &mut sb.buffer; @@ -398,7 +416,7 @@ fn encode_header( encoded: &mut Vec, name: &[u8], value: &[u8], - never_index: bool, + never_index: NeverIndex, ) -> crate::Result<()> { let required = encoded.len() + name.len() + value.len() + 32; encoded.reserve(required.saturating_sub(encoded.len())); diff --git a/src/http/h3_client/AltSvc.rs b/src/http/h3_client/AltSvc.rs index 208498c59e30..e2320d46fd87 100644 --- a/src/http/h3_client/AltSvc.rs +++ b/src/http/h3_client/AltSvc.rs @@ -54,7 +54,7 @@ pub(crate) fn parse(field_value: &[u8]) -> Result, ParseError> { if value.is_empty() { return Ok(None); } - if strings::eql_case_insensitive_ascii(value, b"clear", true) { + if strings::eql_case_insensitive_ascii(value, b"clear", strings::CheckLen::Yes) { return Err(ParseError::Clear); } @@ -75,7 +75,7 @@ pub(crate) fn parse(field_value: &[u8]) -> Result, ParseError> { let proto = &alternative[..eq]; // Only the final IETF "h3" ALPN token; draft `h3-NN` versions are // ignored since lsquic is built for the final spec. - if !strings::eql_case_insensitive_ascii(proto, b"h3", true) { + if !strings::eql_case_insensitive_ascii(proto, b"h3", strings::CheckLen::Yes) { continue; } @@ -108,7 +108,7 @@ pub(crate) fn parse(field_value: &[u8]) -> Result, ParseError> { continue; }; let peq = peq as usize; - if strings::eql_case_insensitive_ascii(¶m[..peq], b"ma", true) { + if strings::eql_case_insensitive_ascii(¶m[..peq], b"ma", strings::CheckLen::Yes) { result.ma = strings::parse_int::(¶m[peq + 1..], 10).unwrap_or(result.ma); } // `persist` and unknown parameters are ignored (§3.1). diff --git a/src/http/h3_client/ClientSession.rs b/src/http/h3_client/ClientSession.rs index 43a03bd0c4a2..ff1ea755632b 100644 --- a/src/http/h3_client/ClientSession.rs +++ b/src/http/h3_client/ClientSession.rs @@ -16,7 +16,7 @@ use super::stream::Stream; use crate::h3_client as H3; use crate::internal_state::HTTPStage; use crate::signals::Field as Signal; -use crate::{HTTPClient, HeaderResult, Protocol}; +use crate::{HTTPClient, HeaderResult, OnlyBuffer, Protocol}; use crate::h3_client::h3_client; @@ -44,6 +44,8 @@ pub struct ClientSession { pub(crate) pending: Vec<*mut Stream>, } +bun_core::bool_enum!(pub(crate) StreamEnded); + impl ClientSession { /// `bun.TrivialNew(@This())` — heap-allocate and return raw; pointer is /// stashed in the `quic.Socket` ext slot and the `ClientContext` registry. @@ -69,7 +71,7 @@ impl ClientSession { !self.closed && self.port == port && self.reject_unauthorized == reject_unauthorized - && strings::eql_long(&self.hostname, hostname, true) + && strings::eql_long(&self.hostname, hostname, strings::CheckLen::Yes) } /// Mutable access to the live lsquic connection handle. @@ -293,7 +295,8 @@ impl ClientSession { /// `done` = the lsquic stream is gone; deliver whatever is buffered then /// detach. Mirrors H2's `ClientSession.deliverStream` so the HTTPClient state /// machine sees the same call sequence regardless of transport. - pub(crate) fn deliver(&mut self, stream: *mut Stream, done: bool) { + pub(crate) fn deliver(&mut self, stream: *mut Stream, done: StreamEnded) { + let done = done == StreamEnded::Yes; let st = stream_mut(stream); let Some(client_ptr) = st.client else { if done { @@ -367,13 +370,14 @@ impl ClientSession { if done { client.state.flags.received_last_chunk = true; } - let report = match client.handle_response_body(st.body_buffer.as_slice(), false) { - Ok(r) => r, - Err(e) => { - st.body_buffer.clear(); - return self.fail(stream, e); - } - }; + let report = + match client.handle_response_body(st.body_buffer.as_slice(), OnlyBuffer::No) { + Ok(r) => r, + Err(e) => { + st.body_buffer.clear(); + return self.fail(stream, e); + } + }; st.body_buffer.clear(); if done { self.detach(stream); diff --git a/src/http/h3_client/callbacks.rs b/src/http/h3_client/callbacks.rs index 31dfefbd3d16..942655cc3a0e 100644 --- a/src/http/h3_client/callbacks.rs +++ b/src/http/h3_client/callbacks.rs @@ -13,7 +13,7 @@ use bstr::BStr; use bun_uws::quic; use super::client_context::ClientContext; -use super::client_session::{ClientSession, session_mut, stream_mut, stream_ref}; +use super::client_session::{ClientSession, StreamEnded, session_mut, stream_mut, stream_ref}; use super::encode; use super::stream::Stream; use crate::h2_client::dispatch::{is_malformed_response_field, is_malformed_response_value}; @@ -260,7 +260,7 @@ extern "C" fn on_stream_headers(s: *mut quic::Stream) { return; } stream.status_code = status; - session.deliver(stream, false); + session.deliver(stream, StreamEnded::No); } extern "C" fn on_stream_data(s: *mut quic::Stream, data: *const u8, len: c_uint, fin: c_int) { @@ -269,7 +269,9 @@ extern "C" fn on_stream_data(s: *mut quic::Stream, data: *const u8, len: c_uint, // SAFETY: lsquic guarantees `data` points to `len` valid bytes (or `(null,0)`). let slice = unsafe { bun_core::ffi::slice(data, len as usize) }; stream.body_buffer.extend_from_slice(slice); - stream.session_mut().deliver(stream, fin != 0); + stream + .session_mut() + .deliver(stream, StreamEnded::from_bool(fin != 0)); let Some(stream) = stream_of(s) else { return }; if fin != 0 || stream.read_paused { @@ -314,5 +316,5 @@ extern "C" fn on_stream_close(s: *mut quic::Stream) { stream.status_code, stream.headers_delivered, ); - stream.session_mut().deliver(stream, true); + stream.session_mut().deliver(stream, StreamEnded::Yes); } diff --git a/src/http/h3_client/encode.rs b/src/http/h3_client/encode.rs index 3bad80ab23d4..003b52257a8e 100644 --- a/src/http/h3_client/encode.rs +++ b/src/http/h3_client/encode.rs @@ -11,7 +11,7 @@ use super::client_session::ClientSession; use super::stream::Stream; use crate::http_request_body::HTTPRequestBody; use crate::internal_state::HTTPStage; -use crate::{HTTPClient, HTTPVerboseLevel, Protocol}; +use crate::{HTTPClient, HTTPVerboseLevel, IntoShared, Protocol}; /// Build pseudo-headers + user headers and send them on `qs`, then kick off /// body transmission. Called from the first `callbacks.on_stream_writable` @@ -36,7 +36,7 @@ pub(crate) fn write_request( let reject_unauthorized = client.flags.reject_unauthorized; // h3 body bytes flow into lsquic's send buffer asynchronously — compress // into the Vec so the cursor stays valid across event-loop ticks. - client.compress_body_for_send(false)?; + client.compress_body_for_send(IntoShared::No)?; let req_body: bun_ptr::RawSlice = client.state.request_body; let body_len = client.body_len_for_send(); let is_streaming = client.state.original_request_body.is_stream(); @@ -52,9 +52,9 @@ pub(crate) fn write_request( Protocol::Http3, &request, href, - !reject_unauthorized, + bun_picohttp::IgnoreInsecure::from_bool(!reject_unauthorized), body, - verbose == HTTPVerboseLevel::Curl, + verbose, ); } diff --git a/src/http/lib.rs b/src/http/lib.rs index 57bfb2fccb51..c2afb50d4716 100644 --- a/src/http/lib.rs +++ b/src/http/lib.rs @@ -108,7 +108,7 @@ pub enum Protocol { pub use bun_http_types::Encoding::Encoding; pub use header_value_iterator::{ - HeaderValueIterator, connection_header_keep_alive, upgrade_header_is_not_h2, + ConnectionHeader, HeaderValueIterator, connection_header_keep_alive, upgrade_header_is_not_h2, }; pub use init_error::InitError; @@ -761,13 +761,13 @@ fn no_proxy_matches(no_proxy_text: &[u8], hostname: &[u8], host: &[u8]) -> bool }; if has_port { - if strings::eql_case_insensitive_ascii(host, entry, true) { + if strings::eql_case_insensitive_ascii(host, entry, strings::CheckLen::Yes) { return true; } } else { let entry_len = entry.len(); if hostname.len() == entry_len { - if strings::eql_case_insensitive_ascii(hostname, entry, true) { + if strings::eql_case_insensitive_ascii(hostname, entry, strings::CheckLen::Yes) { return true; } } else if hostname.len() > entry_len @@ -775,7 +775,7 @@ fn no_proxy_matches(no_proxy_text: &[u8], hostname: &[u8], host: &[u8]) -> bool && strings::eql_case_insensitive_ascii( &hostname[hostname.len() - entry_len..], entry, - true, + strings::CheckLen::Yes, ) { return true; @@ -927,7 +927,7 @@ impl Drop for HTTPClient<'_> { // tunnel was created by ProxyTunnel::new (heap::alloc) and refcounted; // close_proxy_tunnel releases this client's strong ref (detach+deref // only, no shutdown). - self.close_proxy_tunnel(false); + self.close_proxy_tunnel(ShutdownTunnel::No); // The session detaches `h2` before any terminal callback, so this should // be None by the time the result callback's deinit path runs. debug_assert!(self.h2.is_none()); @@ -999,6 +999,7 @@ use bstr::BStr; use bun_boringssl as boringssl; use bun_collections::{ArrayHashMap, VecExt}; use bun_core::StringBuilder; +use bun_core::compress::Chunk; use bun_core::{FeatureFlags, Global, Output}; use bun_core::{OwnedString, String as BunString, Tag as BunStringTag, strings}; use bun_http_types::ETag::StringPointer; @@ -1232,12 +1233,14 @@ fn unregister_abort_tracker_for_socket(socket: uws::InternalSocket) { } } +bun_core::bool_enum!(pub(crate) AllowProxyUrl); + /// Returns the hostname to use for TLS SNI and certificate verification. /// Priority: tls_props.server_name > client.hostname > client.url.hostname /// The Host header value (client.hostname) may contain a port suffix which /// must be stripped because it is not part of the DNS name in certificates. -fn get_tls_hostname<'c>(client: &'c HTTPClient<'_>, allow_proxy_url: bool) -> &'c [u8] { - if allow_proxy_url { +fn get_tls_hostname<'c>(client: &'c HTTPClient<'_>, allow_proxy_url: AllowProxyUrl) -> &'c [u8] { + if allow_proxy_url == AllowProxyUrl::Yes { if let Some(proxy) = &client.http_proxy { return proxy.hostname; } @@ -1416,11 +1419,11 @@ pub(crate) fn print_request( protocol: Protocol, request: &picohttp::Request<'_>, url: &[u8], - ignore_insecure: bool, + ignore_insecure: picohttp::IgnoreInsecure, body: &[u8], - curl: bool, + verbose: HTTPVerboseLevel, ) { - if curl { + if verbose == HTTPVerboseLevel::Curl { let request_ = picohttp::Request { method: request.method, path: url, @@ -1444,8 +1447,12 @@ pub(crate) fn print_request( ); for header in request.headers { let name = header.name(); - if strings::eql_case_insensitive_ascii(name, b"authorization", true) - || strings::eql_case_insensitive_ascii(name, b"proxy-authorization", true) + if strings::eql_case_insensitive_ascii(name, b"authorization", strings::CheckLen::Yes) + || strings::eql_case_insensitive_ascii( + name, + b"proxy-authorization", + strings::CheckLen::Yes, + ) { let value = header.value(); let scheme_len = strings::index_of_char_usize(value, b' ').map_or(0, |i| i + 1); @@ -1587,6 +1594,11 @@ pub(crate) fn get_cert_error_from_no(error_no: i32) -> crate::Error { }) } +bun_core::bool_enum!(ShutdownTunnel); +bun_core::bool_enum!(ClearProxyTunneling); +bun_core::bool_enum!(pub(crate) IntoShared); +bun_core::bool_enum!(pub(crate) OnlyBuffer); + // ── HTTPClient field accessors ────────────────────────────────────────── // These helpers centralize the unsafe deref of the `Option>` // fields so the state-machine bodies stay readable. @@ -1628,9 +1640,9 @@ impl<'a> HTTPClient<'a> { /// Detach the proxy tunnel, if one is attached, and release this client's /// ref on it through the handle that holds it. #[inline] - fn close_proxy_tunnel(&mut self, shutdown: bool) { + fn close_proxy_tunnel(&mut self, shutdown: ShutdownTunnel) { if let Some(t) = self.proxy_tunnel.take() { - if shutdown { + if shutdown == ShutdownTunnel::Yes { proxy_tunnel::ProxyTunnel::shutdown(t.data); } proxy_tunnel::raw_as_mut(t.as_ptr()).detach_socket(); @@ -1639,7 +1651,7 @@ impl<'a> HTTPClient<'a> { } /// Common tail of `fail` / `fail_from_h2` / `complete_connecting_process`: /// build the result, reset request state, and dispatch the callback. - fn dispatch_result_and_reset(&mut self, clear_proxy_tunneling: bool) { + fn dispatch_result_and_reset(&mut self, clear_proxy_tunneling: ClearProxyTunneling) { let callback = self.result_callback; let result = self.to_result(); self.state.reset(); @@ -1656,7 +1668,7 @@ impl<'a> HTTPClient<'a> { self.state.request_stage = RequestStage::Fail; self.state.response_stage = ResponseStage::Fail; self.state.stage = Stage::Fail; - if clear_proxy_tunneling { + if clear_proxy_tunneling == ClearProxyTunneling::Yes { self.flags.proxy_tunneling = false; } callback.run(self.parent_async_http(), result); @@ -1690,7 +1702,7 @@ impl<'a> HTTPClient<'a> { &mut self, socket: HttpSocket, ssl: &mut boringssl::c::SSL, - allow_proxy_url: bool, + allow_proxy_url: AllowProxyUrl, ) -> bool { if self.flags.reject_unauthorized { // SAFETY: `ssl` is a live `&mut SSL` for the open TLS socket whose @@ -1709,7 +1721,8 @@ impl<'a> HTTPClient<'a> { // (Node semantics). For the HTTPS proxy's own handshake, use // the native SAN check — a pinning callback written for the // target would reject the proxy's certificate. - let is_proxy_certificate = allow_proxy_url && self.http_proxy.is_some(); + let is_proxy_certificate = + allow_proxy_url == AllowProxyUrl::Yes && self.http_proxy.is_some(); if !is_proxy_certificate && self.signals.get(signals::Field::CertErrors) { // clone the relevant data // SAFETY: x509 is a live *mut X509 borrowed from cert_chain; null out-ptr requests size-only @@ -1843,7 +1856,8 @@ impl<'a> HTTPClient<'a> { .unwrap_or(core::ptr::null_mut()); // SAFETY: ssl_ptr is a live *mut SSL for the just-opened TLS socket if !ssl_ptr.is_null() && unsafe { boringssl::c::SSL_is_init_finished(ssl_ptr) } == 0 { - let raw_hostname = get_tls_hostname(self, self.http_proxy.is_some()); + let raw_hostname = + get_tls_hostname(self, AllowProxyUrl::from_bool(self.http_proxy.is_some())); // Build a NUL-terminated SNI string only when the hostname is not an // IP literal (RFC 6066 forbids IP SNI). ALPN/SCT/OCSP must still be @@ -2087,7 +2101,7 @@ impl<'a> HTTPClient<'a> { { return; } - self.dispatch_result_and_reset(false); + self.dispatch_result_and_reset(ClearProxyTunneling::No); } } @@ -2100,7 +2114,7 @@ impl<'a> HTTPClient<'a> { self.fail(crate::Error::Aborted); return; } - self.close_proxy_tunnel(true); + self.close_proxy_tunnel(ShutdownTunnel::Yes); let in_progress = self.state.stage != Stage::Done && self.state.stage != Stage::Fail && !self.state.flags.is_redirect_pending; @@ -2257,7 +2271,8 @@ impl<'a> HTTPClient<'a> { // same pool entry as one without). if let Some(sni_raw) = &self.hostname { let sni = strip_port_from_host(sni_raw); - if !strings::eql_case_insensitive_ascii(sni, self.url.hostname, true) { + if !strings::eql_case_insensitive_ascii(sni, self.url.hostname, strings::CheckLen::Yes) + { let sni_lower: &[u8] = if sni.len() <= name_lower_buf.len() { strings::copy_lowercase(sni, &mut name_lower_buf[0..sni.len()]) } else { @@ -2293,7 +2308,11 @@ impl<'a> HTTPClient<'a> { // (order-independent) without the cancellation. combined = combined.wrapping_add(h.final_()); any = true; - if strings::eql_case_insensitive_ascii(name, b"proxy-authorization", true) { + if strings::eql_case_insensitive_ascii( + name, + b"proxy-authorization", + strings::CheckLen::Yes, + ) { user_provided_auth = true; } } @@ -2422,11 +2441,11 @@ impl<'a> HTTPClient<'a> { if will_append { override_connection_header = true; match connection_header_keep_alive(self.header_str(header_values[i])) { - Some(false) => { + Some(ConnectionHeader::Close) => { connection_close_requested = true; self.flags.disable_keepalive = true; } - Some(true) if !connection_close_requested => { + Some(ConnectionHeader::KeepAlive) if !connection_close_requested => { self.flags.disable_keepalive = false; } _ => {} @@ -2626,7 +2645,7 @@ impl<'a> HTTPClient<'a> { // original host. Close the tunnel on redirect; only pool the raw socket. if self.proxy_tunnel.is_some() { bun_core::scoped_log!(fetch, "close the tunnel"); - self.close_proxy_tunnel(true); + self.close_proxy_tunnel(ShutdownTunnel::Yes); GenHttpContext::::close_socket(socket); } else if keep_alive_possible && self.is_request_fully_sent() @@ -2677,7 +2696,7 @@ impl<'a> HTTPClient<'a> { bun_core::scoped_log!(fetch, "doRedirect state reset"); // also reset proxy to redirect self.flags.proxy_tunneling = false; - self.close_proxy_tunnel(false); + self.close_proxy_tunnel(ShutdownTunnel::No); self.flags.protocol = Protocol::Http1_1; self.reevaluate_proxy_for_redirect(); @@ -2911,7 +2930,7 @@ impl<'a> HTTPClient<'a> { /// `state.flags.body_compressed`. /// /// [`spill_compressed_body`]: Self::spill_compressed_body - pub(crate) fn compress_body_for_send(&mut self, into_shared: bool) -> crate::Result<()> { + pub(crate) fn compress_body_for_send(&mut self, into_shared: IntoShared) -> crate::Result<()> { let Some(opt) = self.compress else { return Ok(()); }; @@ -2929,7 +2948,9 @@ impl<'a> HTTPClient<'a> { let out = compress_body::compress_into(deflater, input, &opt, &mut self.compressed_request_body)?; let slice: &[u8] = match out { - compress_body::CompressOutput::Shared(n) if into_shared => &deflater.shared_buffer[..n], + compress_body::CompressOutput::Shared(n) if into_shared == IntoShared::Yes => { + &deflater.shared_buffer[..n] + } compress_body::CompressOutput::Shared(n) => { self.compressed_request_body .extend_from_slice(&deflater.shared_buffer[..n]); @@ -2988,7 +3009,7 @@ impl<'a> HTTPClient<'a> { &mut self, socket: HttpSocket, ) -> crate::Result { - self.compress_body_for_send(true)?; + self.compress_body_for_send(IntoShared::Yes)?; let mut request_body_buffer = self.get_request_body_send_buffer(); // request_body_buffer drops at scope exit (was `defer .deinit()`) @@ -3059,9 +3080,9 @@ impl<'a> HTTPClient<'a> { Protocol::Http1_1, &request, self.url.href, - !self.flags.reject_unauthorized, + picohttp::IgnoreInsecure::from_bool(!self.flags.reject_unauthorized), self.request_body(), - self.verbose == HTTPVerboseLevel::Curl, + self.verbose, ); } @@ -3476,7 +3497,7 @@ impl<'a> HTTPClient<'a> { self.set_timeout(&socket); // Proxy-tunnel writes can be partial across event-loop ticks // — compress straight into the Vec. - if let Err(e) = self.compress_body_for_send(false) { + if let Err(e) = self.compress_body_for_send(IntoShared::No) { self.close_and_fail::(e, socket); return; } @@ -3816,7 +3837,7 @@ impl<'a> HTTPClient<'a> { } if self.state.response_stage == ResponseStage::Body { - let report_progress = match self.handle_response_body(to_read, true) { + let report_progress = match self.handle_response_body(to_read, OnlyBuffer::Yes) { Ok(b) => b, Err(err) => { self.close_and_fail::(err, socket); @@ -3894,7 +3915,8 @@ impl<'a> HTTPClient<'a> { self.set_timeout(&socket); } - let report_progress = match self.handle_response_body(incoming_data, false) { + let report_progress = match self.handle_response_body(incoming_data, OnlyBuffer::No) + { Ok(b) => b, Err(err) => { self.close_and_fail::(err, socket); @@ -3946,7 +3968,7 @@ impl<'a> HTTPClient<'a> { self.flags .defer_terminal_dispatch_until_connecting_is_complete = false; if self.state.stage == Stage::Fail { - self.dispatch_result_and_reset(true); + self.dispatch_result_and_reset(ClearProxyTunneling::Yes); } else if self.flags.is_preconnect_only && self.state.stage == Stage::Done { // Deferred preconnect success (see `on_preconnect`). self.dispatch_preconnect_result(); @@ -4006,7 +4028,7 @@ impl<'a> HTTPClient<'a> { self.unregister_abort_tracker(); self.resolve_pending_h2(PendingH2Resolution::LeaderFailed); - self.close_proxy_tunnel(true); + self.close_proxy_tunnel(ShutdownTunnel::Yes); if self.state.stage != Stage::Done && self.state.stage != Stage::Fail { self.state.request_stage = RequestStage::Fail; self.state.response_stage = ResponseStage::Fail; @@ -4017,7 +4039,7 @@ impl<'a> HTTPClient<'a> { .flags .defer_terminal_dispatch_until_connecting_is_complete { - self.dispatch_result_and_reset(true); + self.dispatch_result_and_reset(ClearProxyTunneling::Yes); } } } @@ -4232,7 +4254,7 @@ impl<'a> HTTPClient<'a> { } else { if self.proxy_tunnel.is_some() { bun_core::scoped_log!(fetch, "close the tunnel"); - self.close_proxy_tunnel(true); + self.close_proxy_tunnel(ShutdownTunnel::Yes); } GenHttpContext::::close_socket(socket); } @@ -4512,7 +4534,7 @@ impl<'a> HTTPClient<'a> { pub(crate) fn handle_response_body( &mut self, incoming_data: &[u8], - is_only_buffer: bool, + is_only_buffer: OnlyBuffer, ) -> crate::Result { debug_assert!(self.state.transfer_encoding == Encoding::Identity); let content_length = self.state.content_length; @@ -4522,7 +4544,7 @@ impl<'a> HTTPClient<'a> { self.state.flags.allow_keepalive = false; } // is it exactly as much as we need? - if is_only_buffer + if is_only_buffer == OnlyBuffer::Yes && let Some(len) = content_length && incoming_data.len() >= len { @@ -4548,7 +4570,7 @@ impl<'a> HTTPClient<'a> { // we can ignore the body data in redirects if !self.state.flags.is_redirect_pending { if self.state.encoding.is_compressed() { - self.state.decompress_bytes(incoming_data, true)?; + self.state.decompress_bytes(incoming_data, Chunk::Last)?; } else { self.state .get_body_buffer() @@ -4603,7 +4625,7 @@ impl<'a> HTTPClient<'a> { let is_streaming = self.signals.get(signals::Field::ResponseBodyStreaming) || self.signals.body_receive_mode.is_some(); if is_done || is_streaming || content_length.is_none() { - let is_final_chunk = is_done; + let is_final_chunk = Chunk::from_bool(is_done); // Move the body buffer's bytes out — process_body_buffer takes `&mut self.state` // and may mutate `compressed_body` (via decompress_bytes' reset) or `decoded_body`, // so any `&` into `self.state` held across the call would be aliased UB. @@ -4697,7 +4719,7 @@ impl<'a> HTTPClient<'a> { // Move the // bytes out so no `&` into self.state aliases the `&mut self.state` call. let buffer_snap = core::mem::take(&mut self.state.get_body_buffer().list); - return self.state.process_body_buffer(buffer_snap, false); + return self.state.process_body_buffer(buffer_snap, Chunk::More); } return Ok(false); @@ -4708,7 +4730,7 @@ impl<'a> HTTPClient<'a> { // Move the // bytes out so no `&` into self.state aliases the `&mut self.state` call. let buffer_snap = core::mem::take(&mut self.state.get_body_buffer().list); - let _ = self.state.process_body_buffer(buffer_snap, true)?; + let _ = self.state.process_body_buffer(buffer_snap, Chunk::Last)?; self.report_progress(buffer_len); @@ -4775,7 +4797,7 @@ impl<'a> HTTPClient<'a> { // the bytes out so no `&` into self.state aliases the `&mut self.state` // taken by process_body_buffer (which mutates compressed_body/decoded_body). let buffer_snap = core::mem::take(&mut self.state.get_body_buffer().list); - return self.state.process_body_buffer(buffer_snap, false); + return self.state.process_body_buffer(buffer_snap, Chunk::More); } Ok(false) @@ -4897,8 +4919,8 @@ impl<'a> HTTPClient<'a> { h if h == hash_header_const(b"Connection") => { // `close` on any field line, any status, is sticky (RFC 9110 §5.3, RFC 9112 §9.6). match connection_header_keep_alive(header.value()) { - Some(false) => self.state.flags.allow_keepalive = false, - Some(true) => has_keep_alive_token = true, + Some(ConnectionHeader::Close) => self.state.flags.allow_keepalive = false, + Some(ConnectionHeader::KeepAlive) => has_keep_alive_token = true, None => {} } } @@ -5035,10 +5057,17 @@ impl<'a> HTTPClient<'a> { } else { &location[0..i] }; - let is_http = - strings::eql_case_insensitive_ascii(protocol_name, b"http", true); + let is_http = strings::eql_case_insensitive_ascii( + protocol_name, + b"http", + strings::CheckLen::Yes, + ); if is_http - || strings::eql_case_insensitive_ascii(protocol_name, b"https", true) + || strings::eql_case_insensitive_ascii( + protocol_name, + b"https", + strings::CheckLen::Yes, + ) { } else { return Err(crate::Error::UnsupportedRedirectProtocol); @@ -5090,7 +5119,7 @@ impl<'a> HTTPClient<'a> { is_same_origin = strings::eql_case_insensitive_ascii( strings::without_trailing_slash(new_url.origin), strings::without_trailing_slash(self.url.origin), - true, + strings::CheckLen::Yes, ); self.url = new_url; // connected_url still borrows from the previous hop's buffer @@ -5108,8 +5137,11 @@ impl<'a> HTTPClient<'a> { return Err(crate::Error::RedirectURLTooLong); } - let is_http = - strings::eql_case_insensitive_ascii(protocol_name, b"http", true); + let is_http = strings::eql_case_insensitive_ascii( + protocol_name, + b"http", + strings::CheckLen::Yes, + ); if is_http { string_builder.count(b"http:"); @@ -5145,7 +5177,7 @@ impl<'a> HTTPClient<'a> { is_same_origin = strings::eql_case_insensitive_ascii( strings::without_trailing_slash(new_url.origin), strings::without_trailing_slash(self.url.origin), - true, + strings::CheckLen::Yes, ); self.url = new_url; debug_assert!(self.prev_redirect.is_empty()); @@ -5173,7 +5205,7 @@ impl<'a> HTTPClient<'a> { is_same_origin = strings::eql_case_insensitive_ascii( strings::without_trailing_slash(self.url.origin), strings::without_trailing_slash(original_url.origin), - true, + strings::CheckLen::Yes, ); debug_assert!(self.prev_redirect.is_empty()); self.prev_redirect = core::mem::replace(&mut self.redirect, new_url); diff --git a/src/http/lshpack.rs b/src/http/lshpack.rs index 52f88c5ffc26..34f6cf5efd64 100644 --- a/src/http/lshpack.rs +++ b/src/http/lshpack.rs @@ -50,6 +50,11 @@ pub enum HpackError { UnableToEncode, } +bun_core::bool_enum!( + /// RFC 7541 §6.2.3 never-indexed literal (sensitive header field). + pub NeverIndex +); + impl HPACK { /// DecodeResult name and value uses a thread_local shared buffer and should be copy/cloned before the next decode/encode call pub fn decode(&mut self, src: &[u8]) -> Result { @@ -92,7 +97,7 @@ impl HPACK { &mut self, name: &[u8], value: &[u8], - never_index: bool, + never_index: NeverIndex, dst_buffer: &mut [u8], dst_buffer_offset: usize, ) -> Result { @@ -107,7 +112,7 @@ impl HPACK { name.len(), value.as_ptr(), value.len(), - never_index as c_int, + (never_index == NeverIndex::Yes) as c_int, dst_buffer.as_mut_ptr(), dst_buffer.len(), dst_buffer_offset, diff --git a/src/http/session_cache.rs b/src/http/session_cache.rs index a1ba85bfc941..4010857d1634 100644 --- a/src/http/session_cache.rs +++ b/src/http/session_cache.rs @@ -66,7 +66,7 @@ impl SessionCache { let idx = entries.iter().position(|e| { e.port == port && e.proxy_auth_hash == proxy_auth_hash - && strings::eql_long(&e.hostname, hostname, true) + && strings::eql_long(&e.hostname, hostname, strings::CheckLen::Yes) })?; entries.remove(idx).session.take() } @@ -88,7 +88,7 @@ impl SessionCache { if let Some(idx) = entries.iter().position(|e| { e.port == port && e.proxy_auth_hash == proxy_auth_hash - && strings::eql_long(&e.hostname, hostname, true) + && strings::eql_long(&e.hostname, hostname, strings::CheckLen::Yes) }) { let _ = entries.remove(idx); } else if entries.len() >= SESSION_CACHE_CAPACITY { diff --git a/src/http/ssl_config.rs b/src/http/ssl_config.rs index c6bea951364a..637af719bb24 100644 --- a/src/http/ssl_config.rs +++ b/src/http/ssl_config.rs @@ -519,7 +519,11 @@ fn cstr_bytes<'a>(p: CStrPtr) -> &'a [u8] { fn cstr_eq(a: CStrPtr, b: CStrPtr) -> bool { match (a.is_null(), b.is_null()) { (true, true) => true, - (false, false) => bun_core::strings::eql_long(cstr_bytes(a), cstr_bytes(b), true), + (false, false) => bun_core::strings::eql_long( + cstr_bytes(a), + cstr_bytes(b), + bun_core::strings::CheckLen::Yes, + ), _ => false, } } diff --git a/src/http_jsc/websocket_client.rs b/src/http_jsc/websocket_client.rs index 9e687f807adf..ed181e0d0794 100644 --- a/src/http_jsc/websocket_client.rs +++ b/src/http_jsc/websocket_client.rs @@ -20,10 +20,10 @@ use bun_http::websocket::{Opcode, WebsocketHeader}; use bun_io::KeepAlive; use bun_jsc::{self as jsc, GlobalRef, JSGlobalObject, JSValue}; use bun_ptr::{AsCtxPtr, ThisPtr}; -use bun_uws::{self as uws, NewSocketHandler, SslCtx, us_bun_verify_error_t}; +use bun_uws::{self as uws, Fin, NewSocketHandler, SslCtx, us_bun_verify_error_t}; use bun_uws_sys::us_socket_t; -use self::cpp_websocket::{CppWebSocket, CppWebSocketRef}; +use self::cpp_websocket::{CloneText, CppWebSocket, CppWebSocketRef}; use self::websocket_deflate::WebSocketDeflate; use self::websocket_proxy_tunnel::WebSocketProxyTunnel; @@ -62,6 +62,19 @@ const MAX_CLOSE_REASON: usize = MAX_CONTROL_PAYLOAD - 2; /// Outgoing control frame prefix: 2-byte header + 4-byte masking key. const CONTROL_HEADER_SIZE: usize = 6; +bun_core::bool_enum!( + /// Drop the fifo's backing allocation instead of just discarding its contents. + FreeMemory +); +bun_core::bool_enum!( + /// Flush `send_buffer` to the socket now vs. only append to it. + DoWrite +); +bun_core::bool_enum!( + /// Per-message-deflate (RSV1) payload. + Compressed +); + #[derive(bun_ptr::CellRefCounted)] #[ref_count(destroy = Self::deinit)] pub struct WebSocket { @@ -161,8 +174,8 @@ impl WebSocket { pub(crate) fn clear_data(&self) { log!("clearData"); self.unref_keep_alive(); - self.clear_receive_buffers(true); - self.clear_send_buffers(true); + self.clear_receive_buffers(FreeMemory::Yes); + self.clear_send_buffers(FreeMemory::Yes); self.control_frame_started.set(false); self.ping_len.set(0); if let Some((_, reason)) = self.close_dispatch_pending.take() { @@ -334,7 +347,7 @@ impl WebSocket { self.fail(code); } - fn clear_receive_buffers(&self, free: bool) { + fn clear_receive_buffers(&self, free: FreeMemory) { // `discard` never rewinds `head`; `reset_head_if_empty` keeps `readable_slice(0)` contiguous. { let mut receive_buffer = self.receive_buffer.borrow_mut(); @@ -343,7 +356,7 @@ impl WebSocket { receive_buffer.reset_head_if_empty(); } - if free { + if free == FreeMemory::Yes { self.receive_buffer .replace(LinearFifo::>::init()); } @@ -351,7 +364,7 @@ impl WebSocket { self.receive_body_remain.set(0); } - fn clear_send_buffers(&self, free: bool) { + fn clear_send_buffers(&self, free: FreeMemory) { // see clear_receive_buffers — discard instead of poking // private `head`/`count`. { @@ -359,7 +372,7 @@ impl WebSocket { let len = send_buffer.readable_length(); send_buffer.discard(len); } - if free { + if free == FreeMemory::Yes { self.send_buffer .replace(LinearFifo::>::init()); } @@ -404,7 +417,11 @@ impl WebSocket { // this function encodes to UTF-16 if > 127 // so we don't need to worry about latin1 non-ascii code points // we avoid trim since we wanna keep the utf8 validation intact - let utf16_bytes = match strings::to_utf16_alloc(data, true, false) { + let utf16_bytes = match strings::to_utf16_alloc( + data, + strings::FailIfInvalid::Yes, + strings::Sentinel::No, + ) { Ok(v) => v, Err(strings::ToUTF16Error::InvalidByteSequence) => { self.terminate(ErrorCode::InvalidUtf8); @@ -427,11 +444,11 @@ impl WebSocket { outstring = ZigString::from16_slice(&utf16); outstring.mark_global(); jsc::mark_binding!(); - out.did_receive_text(false, &outstring); + out.did_receive_text(CloneText::Adopt, &outstring); } else { outstring = ZigString::init(data); jsc::mark_binding!(); - out.did_receive_text(true, &outstring); + out.did_receive_text(CloneText::Clone, &outstring); } } Opcode::Binary | Opcode::Ping | Opcode::Pong => { @@ -457,7 +474,7 @@ impl WebSocket { data: &[u8], left_in_fragment: usize, kind: Opcode, - is_final: bool, + is_final: Fin, ) -> usize { debug_assert!(data.len() <= left_in_fragment); @@ -465,6 +482,7 @@ impl WebSocket { if self.receiving_compressed.get() { return self.consume_compressed(data, left_in_fragment, kind, is_final); } + let is_final = is_final == Fin::Yes; let frame_complete = data.len() == left_in_fragment; if is_final && frame_complete { @@ -475,7 +493,7 @@ impl WebSocket { return data.len(); } if data.is_empty() { - self.dispatch_buffered_message(kind, false); + self.dispatch_buffered_message(kind, Compressed::No); return 0; } } @@ -489,7 +507,7 @@ impl WebSocket { if frame_complete { self.receive_body_remain.set(0); if is_final { - self.dispatch_buffered_message(kind, false); + self.dispatch_buffered_message(kind, Compressed::No); } } data.len() @@ -500,7 +518,7 @@ impl WebSocket { data: &[u8], left_in_fragment: usize, kind: Opcode, - is_final: bool, + is_final: Fin, ) -> usize { if !data.is_empty() { bun_core::handle_oom(self.buffer_payload(data)); @@ -508,28 +526,28 @@ impl WebSocket { if data.len() == left_in_fragment { self.receive_body_remain.set(0); - if is_final { - self.dispatch_buffered_message(kind, true); + if is_final == Fin::Yes { + self.dispatch_buffered_message(kind, Compressed::Yes); } } data.len() } /// Dispatch the message accumulated in `receive_buffer`, then reset the per-message state. - fn dispatch_buffered_message(&self, kind: Opcode, compressed: bool) { + fn dispatch_buffered_message(&self, kind: Opcode, compressed: Compressed) { // Take the fifo first: `dispatch_*` can reach `clear_receive_buffers(true)` and free the readable slice. let buf = self .receive_buffer .replace(LinearFifo::>::init()); - if compressed { + if compressed == Compressed::Yes { self.dispatch_compressed_data(buf.readable_slice(0), kind); } else { self.dispatch_data(buf.readable_slice(0), kind); } // Restore the taken fifo so its capacity is kept for the next message. self.receive_buffer.replace(buf); - self.clear_receive_buffers(false); - if compressed { + self.clear_receive_buffers(FreeMemory::No); + if compressed == Compressed::Yes { self.receiving_compressed.set(false); } self.message_is_compressed.set(false); @@ -717,7 +735,7 @@ impl WebSocket { // An empty final message still dispatches ("", ArrayBuffer(0), ...). if cursor.body_remain == 0 && cursor.state == ReceiveState::NeedBody && cursor.is_final { - let _ = self.consume(b"", 0, cursor.last_data_type, true); + let _ = self.consume(b"", 0, cursor.last_data_type, Fin::Yes); cursor.state = ReceiveState::NeedHeader; self.receiving_compressed.set(false); @@ -848,7 +866,7 @@ impl WebSocket { chunk, cursor.body_remain, cursor.last_data_type, - cursor.is_final, + Fin::from_bool(cursor.is_final), ); cursor.body_remain -= consumed; @@ -913,7 +931,7 @@ impl WebSocket { }; // Buffer any data the tunnel couldn't accept if wrote < bytes.len() { - let _ = self.copy_to_send_buffer(&bytes[wrote..], false); + let _ = self.copy_to_send_buffer(&bytes[wrote..], DoWrite::No); } return true; } @@ -932,19 +950,21 @@ impl WebSocket { return false; } - let _ = self - .copy_to_send_buffer(&bytes[usize::try_from(wrote).expect("int cast")..], false); + let _ = self.copy_to_send_buffer( + &bytes[usize::try_from(wrote).expect("int cast")..], + DoWrite::No, + ); return true; } - self.copy_to_send_buffer(bytes, true) + self.copy_to_send_buffer(bytes, DoWrite::Yes) } - fn copy_to_send_buffer(&self, bytes: &[u8], do_write: bool) -> bool { + fn copy_to_send_buffer(&self, bytes: &[u8], do_write: DoWrite) -> bool { self.send_data(Copy::Raw(bytes), do_write, Opcode::Binary) } - fn send_data(&self, bytes: Copy<'_>, do_write: bool, opcode: Opcode) -> bool { + fn send_data(&self, bytes: Copy<'_>, do_write: DoWrite, opcode: Opcode) -> bool { let may_compress = self.deflate.borrow().is_some() && matches!(opcode, Opcode::Text | Opcode::Binary) && !matches!(bytes, Copy::Raw(_)); @@ -1010,7 +1030,7 @@ impl WebSocket { send_buffer.update(frame_size); } - if do_write { + if do_write == DoWrite::Yes { self.debug_assert_socket_writable(); return self.send_buffer_out(); } @@ -1018,7 +1038,7 @@ impl WebSocket { true } - fn send_data_uncompressed(&self, bytes: Copy<'_>, do_write: bool, opcode: Opcode) -> bool { + fn send_data_uncompressed(&self, bytes: Copy<'_>, do_write: DoWrite, opcode: Opcode) -> bool { let (write_len, content_byte_len) = bytes.frame_and_content_len(); debug_assert!(write_len > 0); @@ -1034,7 +1054,7 @@ impl WebSocket { send_buffer.update(write_len); } - if do_write { + if do_write == DoWrite::Yes { self.debug_assert_socket_writable(); return self.send_buffer_out(); } @@ -1287,7 +1307,7 @@ impl WebSocket { return; } - let _ = self.send_data(bytes, !self.has_backpressure(), opcode); + let _ = self.send_data(bytes, DoWrite::from_bool(!self.has_backpressure()), opcode); } // `extern "C"` entrypoint; pointers are valid by C++ contract (see SAFETY comments below). @@ -1378,7 +1398,11 @@ impl WebSocket { // the JSValue is rooted by the caller for the duration of this call. let data = unsafe { (*blob).shared_view() }; if data.is_empty() { - let _ = this.send_data(Copy::Bytes(&[]), !this.has_backpressure(), opcode); + let _ = this.send_data( + Copy::Bytes(&[]), + DoWrite::from_bool(!this.has_backpressure()), + opcode, + ); return; } @@ -1429,7 +1453,7 @@ impl WebSocket { } else { Copy::Latin1(str.slice()) }, - !this.has_backpressure(), + DoWrite::from_bool(!this.has_backpressure()), opcode, ); } @@ -2064,7 +2088,7 @@ impl Mask { fn fill(global_this: &JSGlobalObject, mask_buf: &mut [u8; 4], output: &mut [u8], input: &[u8]) { *mask_buf = Self::generate(global_this); - let skip_mask = u32::from_ne_bytes(*mask_buf) == 0; + let skip_mask = bun_highway::SkipMask::from_bool(u32::from_ne_bytes(*mask_buf) == 0); if input.is_empty() { bun_core::hint::cold(); return; @@ -2076,7 +2100,7 @@ impl Mask { /// (borrowck forbids `&mut [u8]` + `&[u8]` aliasing in `fill`). fn fill_in_place(global_this: &JSGlobalObject, mask_buf: &mut [u8; 4], buf: &mut [u8]) { *mask_buf = Self::generate(global_this); - let skip_mask = u32::from_ne_bytes(*mask_buf) == 0; + let skip_mask = bun_highway::SkipMask::from_bool(u32::from_ne_bytes(*mask_buf) == 0); if buf.is_empty() { bun_core::hint::cold(); return; @@ -2278,7 +2302,7 @@ impl Copy<'_> { content_byte_len: usize, opcode: Opcode, ) { - self.copy_with_compressed_flag(global_this, buf, content_byte_len, opcode, false); + self.copy_with_compressed_flag(global_this, buf, content_byte_len, opcode, Compressed::No); } /// Frame an already-deflated payload; `is_first_fragment` controls RSV1. @@ -2294,7 +2318,7 @@ impl Copy<'_> { buf, compressed_data.len(), opcode, - is_first_fragment, + Compressed::from_bool(is_first_fragment), ); } @@ -2304,7 +2328,7 @@ impl Copy<'_> { buf: &mut [u8], content_byte_len: usize, opcode: Opcode, - compressed: bool, + compressed: Compressed, ) { if let Copy::Raw(raw) = self { debug_assert!(buf.len() >= raw.len()); @@ -2315,7 +2339,7 @@ impl Copy<'_> { let mut header = WebsocketHeader::new(WebsocketHeader::pack_length(content_byte_len), true, opcode); - header.set_compressed(compressed); + header.set_compressed(compressed == Compressed::Yes); let mut parts = split_frame(buf, content_byte_len); diff --git a/src/http_jsc/websocket_client/CppWebSocket.rs b/src/http_jsc/websocket_client/CppWebSocket.rs index 21bc471e115b..d4ba51e6da68 100644 --- a/src/http_jsc/websocket_client/CppWebSocket.rs +++ b/src/http_jsc/websocket_client/CppWebSocket.rs @@ -82,6 +82,11 @@ unsafe extern "C" { fn WebSocket__setProtocol(websocket_context: &CppWebSocket, protocol: *mut BunString); } +bun_core::bool_enum!( + /// `Adopt`: C++ takes ownership of the (global-marked) buffer; `Clone`: C++ copies it. + pub(crate) CloneText { Adopt, Clone } +); + // Receivers are `&self` (not `&mut self`) because `CppWebSocket` is // an opaque C++ handle with no Rust-visible state; mutation happens entirely on // the C++ side. Callers hold `NonNull` and dispatch via shared @@ -135,13 +140,13 @@ impl CppWebSocket { event_loop.exit(); } - pub(crate) fn did_receive_text(&self, clone: bool, text: &ZigString) { + pub(crate) fn did_receive_text(&self, clone: CloneText, text: &ZigString) { // SAFETY: VirtualMachine::get() returns the live current-thread VM; // event_loop() yields its raw event-loop pointer (live for VM lifetime). let event_loop = VirtualMachine::get().event_loop_mut(); event_loop.enter(); // SAFETY: self is a valid C++ WebCore::WebSocket; text outlives the call. - unsafe { WebSocket__didReceiveText(self, clone, text) }; + unsafe { WebSocket__didReceiveText(self, clone == CloneText::Clone, text) }; event_loop.exit(); } diff --git a/src/http_jsc/websocket_client/WebSocketProxy.rs b/src/http_jsc/websocket_client/WebSocketProxy.rs index d67d4651588a..b1915ac053fe 100644 --- a/src/http_jsc/websocket_client/WebSocketProxy.rs +++ b/src/http_jsc/websocket_client/WebSocketProxy.rs @@ -1,6 +1,7 @@ use core::ptr::NonNull; use super::WebSocketProxyTunnel; +use super::websocket_upgrade_client::TargetTls; /// WebSocketProxy encapsulates proxy state for WebSocket connections through HTTP/HTTPS proxies. /// This struct holds only the fields needed after the initial CONNECT request. @@ -10,7 +11,7 @@ pub(crate) struct WebSocketProxy { /// Target hostname for SNI during TLS handshake target_host: Box<[u8]>, /// Whether target uses TLS (wss://) - target_is_https: bool, + target_is_https: TargetTls, /// WebSocket upgrade request to send after CONNECT succeeds websocket_request_buf: Box<[u8]>, /// TLS tunnel for wss:// through HTTP proxy @@ -23,7 +24,7 @@ impl WebSocketProxy { // params are owned (caller transfers ownership; freed in deinit) pub(crate) fn init( target_host: Box<[u8]>, - target_is_https: bool, + target_is_https: TargetTls, websocket_request_buf: Box<[u8]>, ) -> WebSocketProxy { WebSocketProxy { @@ -41,7 +42,7 @@ impl WebSocketProxy { /// Check if the target uses HTTPS (wss://) pub(crate) fn is_target_https(&self) -> bool { - self.target_is_https + self.target_is_https == TargetTls::Tls } /// Get the TLS tunnel for wss:// through HTTP proxy diff --git a/src/http_jsc/websocket_client/WebSocketProxyTunnel.rs b/src/http_jsc/websocket_client/WebSocketProxyTunnel.rs index 5d1a7a7ee83e..dff558075992 100644 --- a/src/http_jsc/websocket_client/WebSocketProxyTunnel.rs +++ b/src/http_jsc/websocket_client/WebSocketProxyTunnel.rs @@ -36,8 +36,8 @@ use core::ptr::NonNull; use bun_boringssl as boringssl; use bun_io::StreamBuffer; -use bun_uws::ssl_wrapper::{Handlers as SslHandlers, SslWrapper}; -use bun_uws::{NewSocketHandler, us_bun_verify_error_t}; +use bun_uws::ssl_wrapper::{FastShutdown, Handlers as SslHandlers, SslWrapper}; +use bun_uws::{NewSocketHandler, RejectUnauthorized, us_bun_verify_error_t}; use super::websocket_upgrade_client::{ HttpUpgradeClient, HttpsUpgradeClient, NewHttpUpgradeClient, @@ -134,7 +134,7 @@ pub struct WebSocketProxyTunnel { /// Hostname for SNI (Server Name Indication) sni_hostname: Option>, /// Whether to reject unauthorized certificates - reject_unauthorized: bool, + reject_unauthorized: RejectUnauthorized, } use bun_uws::MaybeAnySocket as SocketUnion; @@ -147,7 +147,7 @@ impl WebSocketProxyTunnel { upgrade_client: *mut NewHttpUpgradeClient, socket: NewSocketHandler, sni_hostname: &[u8], - reject_unauthorized: bool, + reject_unauthorized: RejectUnauthorized, ) -> Result, bun_alloc::AllocError> { // const-generic bool → variant selection. The pointer cast is // identity when SSL matches the alias (HttpUpgradeClient = NewHttpUpgradeClient, @@ -204,7 +204,7 @@ impl WebSocketProxyTunnel { // the `SSLConfig`-taking `init` lives in bun_runtime. let wrapper = SslWrapperType::init_from_options( &options.as_usockets(), - true, + bun_uws::TlsRole::Client, SslHandlers { // Store the Box-provenance pointer directly so callback derefs // remain valid regardless of intervening reborrows. @@ -355,7 +355,7 @@ impl WebSocketProxyTunnel { } // Check for SSL errors if we need to reject unauthorized - if reject_unauthorized { + if reject_unauthorized == RejectUnauthorized::Yes { if ssl_error.error_no != 0 { upgrade_client.terminate(ErrorCode::TlsHandshakeFailed); return; @@ -591,7 +591,7 @@ impl WebSocketProxyTunnel { let wrapper_ptr = unsafe { ptr::addr_of_mut!((*this).wrapper) }; // SAFETY: deref of field projection; `this` is live. if let Some(w) = unsafe { (*wrapper_ptr).as_ref() } { - let _ = w.shutdown(true); // Fast shutdown + let _ = w.shutdown(FastShutdown::Yes); } } diff --git a/src/http_jsc/websocket_client/WebSocketUpgradeClient.rs b/src/http_jsc/websocket_client/WebSocketUpgradeClient.rs index 2275b454fffe..f91c895030e8 100644 --- a/src/http_jsc/websocket_client/WebSocketUpgradeClient.rs +++ b/src/http_jsc/websocket_client/WebSocketUpgradeClient.rs @@ -35,7 +35,7 @@ use bun_io::KeepAlive; use bun_jsc::{JSGlobalObject, VirtualMachineRef}; use bun_picohttp as picohttp; use bun_ptr::ThisPtr; -use bun_uws::{self as uws, SocketHandler, SocketKind, SslCtx}; +use bun_uws::{self as uws, RejectUnauthorized, SocketHandler, SocketKind, SslCtx}; use super::cpp_websocket::CppWebSocket; use super::websocket_deflate as WebSocketDeflate; @@ -80,6 +80,13 @@ unsafe fn vm_loop_ctx(vm: *mut VirtualMachineRef) -> bun_io::EventLoopCtx { /// `uws.NewSocketHandler(ssl)` type Socket = SocketHandler; +bun_core::bool_enum!( + /// Whether the target URL is `wss://` (independent of the socket's `SSL` parameter: + /// an HTTPS proxy carries `ws://` targets over TLS too). + pub(crate) TargetTls { Plain, Tls } +); +bun_core::bool_enum!(pub(crate) OfferPermessageDeflate); + #[derive(Default, Clone, Copy)] pub(crate) struct DeflateNegotiationResult { pub enabled: bool, @@ -238,14 +245,14 @@ impl HTTPClient { // TLS options (full SSLConfig for complete TLS customization) ssl_config: Option>, // Whether the target URL is wss:// (separate from ssl template parameter) - target_is_secure: bool, + target_is_secure: TargetTls, // Target URL authorization (Basic auth from ws://user:pass@host) target_authorization: Option<&BunString>, // Unix domain socket path for ws+unix:// / wss+unix:// (None for TCP) unix_socket_path: Option<&BunString>, // Whether to advertise `permessage-deflate` in the upgrade request // (ws.WebSocket's `perMessageDeflate` option; true by default). - offer_permessage_deflate: bool, + offer_permessage_deflate: OfferPermessageDeflate, ) -> Option<*mut Self> { let vm_ptr = global.bun_vm_ptr(); let vm = global.bun_vm().as_mut(); @@ -276,7 +283,11 @@ impl HTTPClient { // Check if user provided a custom protocol for subprotocols validation let mut protocol_for_subprotocols: &[u8] = client_protocol_slice.slice(); for (name, value) in extra_headers.iter() { - if strings::eql_case_insensitive_ascii(name, b"sec-websocket-protocol", true) { + if strings::eql_case_insensitive_ascii( + name, + b"sec-websocket-protocol", + strings::CheckLen::Yes, + ) { protocol_for_subprotocols = value; break; } @@ -457,7 +468,7 @@ impl HTTPClient { ssl_config, secure, expected_accept: request_result.expected_accept, - offered_permessage_deflate: offer_permessage_deflate, + offered_permessage_deflate: offer_permessage_deflate == OfferPermessageDeflate::Yes, subprotocols, })); bun_core::scoped_log!(alloc, "new({}) = {:p}", Self::TYPE_NAME, client); @@ -470,7 +481,7 @@ impl HTTPClient { secure_ptr, usp.slice(), client, - false, + uws::AllowHalfOpen::No, ) { Ok(socket) => { // `client` is live (refcount >= 1) but no longer solely @@ -526,7 +537,7 @@ impl HTTPClient { display_host, c_int::from(connect_port), client, - false, + uws::AllowHalfOpen::No, ) { Ok(sock) => { // `client` is live (refcount >= 1) but no longer solely owned @@ -1121,8 +1132,10 @@ impl HTTPClient { // Get certificate verification setting // SAFETY: `this` is live; scoped read of a `Copy` field. let reject_unauthorized = match unsafe { (*this).outgoing_websocket } { - Some(ws) => CppWebSocket::opaque_ref(ws).reject_unauthorized(), - None => true, + Some(ws) => { + RejectUnauthorized::from_bool(CppWebSocket::opaque_ref(ws).reject_unauthorized()) + } + None => RejectUnauthorized::Yes, }; // Create proxy tunnel with all parameters. @@ -1543,7 +1556,11 @@ impl HTTPClient { return; } - if !strings::eql_case_insensitive_ascii(upgrade_header.value(), b"websocket", true) { + if !strings::eql_case_insensitive_ascii( + upgrade_header.value(), + b"websocket", + strings::CheckLen::Yes, + ) { // SAFETY: no `&mut Self` is live across this call. unsafe { Self::terminate(this, ErrorCode::InvalidUpgradeHeader) }; return; @@ -1930,7 +1947,11 @@ fn build_connect_request( // Skip Proxy-Authorization if user provided one (we already added it) let name = hdrs.as_str(*name_ptr); if proxy_authorization.is_some() - && strings::eql_case_insensitive_ascii(name, b"proxy-authorization", true) + && strings::eql_case_insensitive_ascii( + name, + b"proxy-authorization", + strings::CheckLen::Yes, + ) { continue; } @@ -1959,16 +1980,16 @@ struct BuildRequestResult { fn build_request_body( vm: &mut VirtualMachineRef, pathname: &[u8], - is_https: bool, + is_https: TargetTls, host: &[u8], port: u16, client_protocol: &[u8], extra_headers: &Headers8Bit<'_>, target_authorization: Option<&[u8]>, - // When false, don't advertise `permessage-deflate` (matches `ws` with - // `perMessageDeflate: false`). When true, send the default extension + // When `No`, don't advertise `permessage-deflate` (matches `ws` with + // `perMessageDeflate: false`). When `Yes`, send the default extension // offer `permessage-deflate; client_max_window_bits`. - offer_permessage_deflate: bool, + offer_permessage_deflate: OfferPermessageDeflate, ) -> Result { // Check for user overrides let mut user_host: Option<&[u8]> = None; @@ -1977,18 +1998,32 @@ fn build_request_body( let mut user_authorization = false; for (name_slice, value) in extra_headers.iter() { - if user_host.is_none() && strings::eql_case_insensitive_ascii(name_slice, b"host", true) { + if user_host.is_none() + && strings::eql_case_insensitive_ascii(name_slice, b"host", strings::CheckLen::Yes) + { user_host = Some(value); } else if user_key.is_none() - && strings::eql_case_insensitive_ascii(name_slice, b"sec-websocket-key", true) + && strings::eql_case_insensitive_ascii( + name_slice, + b"sec-websocket-key", + strings::CheckLen::Yes, + ) { user_key = Some(value); } else if user_protocol.is_none() - && strings::eql_case_insensitive_ascii(name_slice, b"sec-websocket-protocol", true) + && strings::eql_case_insensitive_ascii( + name_slice, + b"sec-websocket-protocol", + strings::CheckLen::Yes, + ) { user_protocol = Some(value); } else if !user_authorization - && strings::eql_case_insensitive_ascii(name_slice, b"authorization", true) + && strings::eql_case_insensitive_ascii( + name_slice, + b"authorization", + strings::CheckLen::Yes, + ) { user_authorization = true; } @@ -2019,7 +2054,7 @@ fn build_request_body( let protocol = user_protocol.unwrap_or(client_protocol); let host_fmt = HostFormatter { - is_https, + is_https: is_https == TargetTls::Tls, host, port: Some(port), }; @@ -2071,7 +2106,7 @@ fn build_request_body( .unwrap(); } - let extensions_line: &[u8] = if offer_permessage_deflate { + let extensions_line: &[u8] = if offer_permessage_deflate == OfferPermessageDeflate::Yes { b"Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n" } else { b"" @@ -2219,10 +2254,10 @@ macro_rules! export_http_client { proxy_header_values, proxy_header_count, ssl_config, - target_is_secure, + TargetTls::from_bool(target_is_secure), target_authorization, unix_socket_path, - offer_permessage_deflate, + OfferPermessageDeflate::from_bool(offer_permessage_deflate), ) } { Some(p) => p, diff --git a/src/http_types/ETag.rs b/src/http_types/ETag.rs index 0206898525c0..0b0b06d89841 100644 --- a/src/http_types/ETag.rs +++ b/src/http_types/ETag.rs @@ -1,9 +1,11 @@ use bun_core::strings; +bun_core::bool_enum!(Strength { Strong, Weak }); + // Borrows from the input slice; not a persistent heap struct. struct Parsed<'a> { tag: &'a [u8], - is_weak: bool, + strength: Strength, } /// Parse a single entity tag from a string, returns the tag without quotes and whether it's weak @@ -11,9 +13,9 @@ fn parse(tag_str: &[u8]) -> Parsed<'_> { let mut str = strings::trim(tag_str, b" \t"); // Check for weak indicator - let mut is_weak = false; + let mut strength = Strength::Strong; if str.starts_with(b"W/") { - is_weak = true; + strength = Strength::Weak; str = &str[2..]; // bun_string has no multi-char trim_left; inline it (trailing // whitespace was already stripped above). @@ -27,11 +29,11 @@ fn parse(tag_str: &[u8]) -> Parsed<'_> { str = &str[1..str.len() - 1]; } - Parsed { tag: str, is_weak } + Parsed { tag: str, strength } } /// Perform weak comparison between two entity tags according to RFC 9110 Section 8.8.3.2 -fn weak_match(tag1: &[u8], is_weak1: bool, tag2: &[u8], is_weak2: bool) -> bool { +fn weak_match(tag1: &[u8], is_weak1: Strength, tag2: &[u8], is_weak2: Strength) -> bool { let _ = is_weak1; let _ = is_weak2; // For weak comparison, we only compare the opaque tag values, ignoring weak indicators @@ -122,12 +124,12 @@ pub fn if_match( } let Some(etag) = etag else { return false }; let ours = parse(etag); - if ours.is_weak { + if ours.strength == Strength::Weak { return false; } for tag_str in split_entity_tags(if_match) { let parsed = parse(tag_str); - if !parsed.is_weak && parsed.tag == ours.tag { + if parsed.strength == Strength::Strong && parsed.tag == ours.tag { return true; } } @@ -152,9 +154,9 @@ pub fn if_none_match( let parsed = parse(tag_str); if weak_match( our_parsed.tag, - our_parsed.is_weak, + our_parsed.strength, parsed.tag, - parsed.is_weak, + parsed.strength, ) { return true; // Condition is false, so we should return 304 } @@ -246,7 +248,11 @@ impl Headers { let names: &[StringPointer] = entries.items_name(); let values: &[StringPointer] = entries.items_value(); for (i, name_ptr) in names.iter().enumerate() { - if strings::eql_case_insensitive_ascii(self.as_str(*name_ptr), name, true) { + if strings::eql_case_insensitive_ascii( + self.as_str(*name_ptr), + name, + strings::CheckLen::Yes, + ) { return Some(self.as_str(values[i])); } } diff --git a/src/http_types/MimeType.rs b/src/http_types/MimeType.rs index 58deff475551..151cc58b3b39 100644 --- a/src/http_types/MimeType.rs +++ b/src/http_types/MimeType.rs @@ -275,6 +275,8 @@ pub const JSON: MimeType = pub const TEXT: MimeType = MimeType::init_comptime(b"text/plain;charset=utf-8", Category::Html); pub const WASM: MimeType = MimeType::init_comptime(b"application/wasm", Category::Wasm); +bun_core::bool_enum!(pub Dupe); + impl MimeType { const fn init_comptime(str: &'static [u8], t: Category) -> MimeType { MimeType { @@ -283,7 +285,8 @@ impl MimeType { } } - pub fn init(str_: &[u8], dupe: bool, allocated: Option<&mut bool>) -> MimeType { + pub fn init(str_: &[u8], dupe: Dupe, allocated: Option<&mut bool>) -> MimeType { + let dupe = dupe == Dupe::Yes; let mut str = str_; if let Some(slash) = strings::index_of_char_usize(str, b'/') { let category_ = &str[0..slash]; @@ -440,7 +443,7 @@ pub fn by_extension_no_default(ext_without_leading_dot: &[u8]) -> Option MimeType { - MimeType::init(name, false, None) + MimeType::init(name, Dupe::No, None) } // Duplicate keys are rejected at compile time. The original table diff --git a/src/ini/lib.rs b/src/ini/lib.rs index fed27f26583d..0b56781a7c20 100644 --- a/src/ini/lib.rs +++ b/src/ini/lib.rs @@ -1634,7 +1634,7 @@ mod draft { use bun_install_types::NodeLinker::{ Behavior as PnpmBehavior, CreateMatcherError, FromExprError, Matcher as PnpmMatcherEntry, - PnpmMatcher, create_matcher, + MatcherKind, PnpmMatcher, create_matcher, }; /// `PnpmMatcher.fromExpr` operating on @@ -1680,8 +1680,8 @@ mod draft { return Err(FromExprError::InvalidRegExp); } }; - has_include = has_include || !matcher.is_exclude; - has_exclude = has_exclude || matcher.is_exclude; + has_include = has_include || matcher.kind == MatcherKind::Include; + has_exclude = has_exclude || matcher.kind == MatcherKind::Exclude; matchers.push(matcher); } ExprData::EArray(patterns) => { @@ -1705,8 +1705,8 @@ mod draft { return Err(FromExprError::InvalidRegExp); } }; - has_include = has_include || !matcher.is_exclude; - has_exclude = has_exclude || matcher.is_exclude; + has_include = has_include || matcher.kind == MatcherKind::Include; + has_exclude = has_exclude || matcher.kind == MatcherKind::Exclude; matchers.push(matcher); } else { log.add_error_opts( diff --git a/src/install/NetworkTask.rs b/src/install/NetworkTask.rs index 615177ac1646..0fbe20fc62f6 100644 --- a/src/install/NetworkTask.rs +++ b/src/install/NetworkTask.rs @@ -4,6 +4,7 @@ use core::sync::atomic::Ordering; use crate::bun_fs::{FileSystem, FilenameStore}; use bun_collections::HashMap; +use bun_core::compress::Chunk; use bun_core::{self, fmt::quote}; use bun_core::{MutableString, strings}; use bun_http::{ @@ -14,7 +15,7 @@ use bun_threading::thread_pool::Batch; use bun_url::URL; use crate::extract_tarball; -use crate::npm::{self as npm, PackageManifest}; +use crate::npm::{self as npm, ExtendedManifest, PackageManifest}; use crate::{ExtractTarball, PackageManager, PatchTask, TarballStream, Task}; // Adapter so `StringOrTinyString::init_append_if_needed` can intern overflow @@ -112,7 +113,7 @@ pub enum Callback { PackageManifest { loaded_manifest: Option, name: strings::StringOrTinyString, - is_extended_manifest: bool, + is_extended_manifest: ExtendedManifest, }, Extract(ExtractTarball), LocalTarball, @@ -254,7 +255,7 @@ impl NetworkTask { unsafe { (*this).streaming_committed = true }; // SAFETY: `stream` is the live heap-allocated // `TarballStream` owned by this task. - unsafe { TarballStream::on_chunk(stream, chunk, false, None) }; + unsafe { TarballStream::on_chunk(stream, chunk, Chunk::More, None) }; } return; } @@ -272,7 +273,7 @@ impl NetworkTask { TarballStream::on_chunk( stream, chunk, - true, + Chunk::Last, result.fail.map(crate::Error::from), ) }; @@ -435,15 +436,22 @@ impl bun_core::output::ErrName for ForManifestError { } } +bun_core::bool_enum!( + /// Whether the dependency requesting this manifest is optional; failures to + /// build the request are then swallowed instead of logged as errors. + pub IsOptional +); + impl NetworkTask { pub(crate) fn for_manifest( &mut self, name: &[u8], scope: &npm::registry::Scope, loaded_manifest: Option<&PackageManifest>, - is_optional: bool, - needs_extended: bool, + is_optional: IsOptional, + needs_extended: ExtendedManifest, ) -> Result<(), ForManifestError> { + let is_optional = is_optional == IsOptional::Yes; let pm = self.pm_mut(); // SAFETY: `pm.log` is the long-lived `*mut Log` the package manager // was constructed with. @@ -565,7 +573,9 @@ impl NetworkTask { let mut last_modified: &[u8] = b""; let mut etag: &[u8] = b""; if let Some(manifest) = loaded_manifest { - if (needs_extended && manifest.pkg.has_extended_manifest) || !needs_extended { + if (needs_extended == ExtendedManifest::Yes && manifest.pkg.has_extended_manifest) + || needs_extended == ExtendedManifest::No + { last_modified = manifest.pkg.last_modified.slice(&manifest.string_buf); etag = manifest.pkg.etag.slice(&manifest.string_buf); } @@ -582,7 +592,7 @@ impl NetworkTask { } let headers_buf: &'static [u8] = if header_builder.header_count > 0 { - let accept_header = if needs_extended { + let accept_header = if needs_extended == ExtendedManifest::Yes { ACCEPT_HEADER_VALUE_EXTENDED } else { ACCEPT_HEADER_VALUE @@ -618,7 +628,7 @@ impl NetworkTask { // SAFETY: same invariant as `last_modified` above. unsafe { bun_ptr::detach_lifetime(&*self.header_buf) } } else { - let header_buf: &'static str = if needs_extended { + let header_buf: &'static str = if needs_extended == ExtendedManifest::Yes { EXTENDED_HEADERS_BUF } else { DEFAULT_HEADERS_BUF diff --git a/src/install/PackageInstall.rs b/src/install/PackageInstall.rs index 6adf994d1ad9..840d61f7dc80 100644 --- a/src/install/PackageInstall.rs +++ b/src/install/PackageInstall.rs @@ -742,6 +742,12 @@ impl UninstallTask { // ───────────────────────────── impl PackageInstall ───────────────────────────── +bun_core::bool_enum!( + /// Skip removing an existing destination before installing (fresh + /// `node_modules`, nothing to delete). + pub SkipDelete +); + impl<'a> PackageInstall<'a> { /// fn verify_patch_hash(&mut self, patch: Patch, root_node_modules_dir: &Dir) -> bool { @@ -806,7 +812,7 @@ impl<'a> PackageInstall<'a> { strings::eql_long( repo.resolved.slice(&self.lockfile.buffers.string_bytes), &bun_tag_file.bytes, - true, + strings::CheckLen::Yes, ) } @@ -2030,13 +2036,13 @@ impl<'a> PackageInstall<'a> { pub(crate) fn install_from_link( &mut self, - skip_delete: bool, + skip_delete: SkipDelete, destination_dir: &Dir, ) -> InstallResult { let dest_path = self.destination_dir_subpath; // If this fails, we don't care. // we'll catch it the next error - if !skip_delete && dest_path.as_bytes() != b"." { + if skip_delete == SkipDelete::No && dest_path.as_bytes() != b"." { self.uninstall_before_install(destination_dir); } @@ -2295,7 +2301,7 @@ impl<'a> PackageInstall<'a> { pub(crate) fn install( &mut self, - skip_delete: bool, + skip_delete: SkipDelete, destination_dir: &Dir, method_: Method, resolution_tag: resolution::Tag, @@ -2304,7 +2310,7 @@ impl<'a> PackageInstall<'a> { // If this fails, we don't care. // we'll catch it the next error - if !skip_delete && self.destination_dir_subpath.as_bytes() != b"." { + if skip_delete == SkipDelete::No && self.destination_dir_subpath.as_bytes() != b"." { self.uninstall_before_install(destination_dir); } diff --git a/src/install/PackageInstaller.rs b/src/install/PackageInstaller.rs index 6c96867f593b..b68bde2d4491 100644 --- a/src/install/PackageInstaller.rs +++ b/src/install/PackageInstaller.rs @@ -9,13 +9,14 @@ use bun_paths::{AbsPath, AutoAbsPath, MAX_PATH_BYTES, PathBuffer, SEP, platform} use bun_semver::String; use bun_sys::{self as Syscall, Dir, Fd}; +use crate::Scope; use crate::bin_real as bin; use crate::bin_real::Bin; use crate::bun_bunfig::Arguments as Command; use crate::bun_fs::FileSystem; use crate::bun_progress::{Node as ProgressNode, Progress}; -use crate::lifecycle_script_runner::LifecycleScriptSubprocess; +use crate::lifecycle_script_runner::{Foreground, LifecycleScriptSubprocess, Optional}; // `Lockfile` here is the in-crate `crate::lockfile::Lockfile` (the // struct `PackageManager.lockfile` actually carries). `lockfile_real` is still // imported for `tree::Id` / `Tree` / `DependencySlice` / `package::*`, all of @@ -26,7 +27,7 @@ use crate::lockfile_real::package::{ }; use crate::lockfile_real::{self as lockfile, DependencySlice, Tree}; use crate::network_task::ForTarballError; -use crate::package_install::{self, PackageInstall}; +use crate::package_install::{self, PackageInstall, SkipDelete}; use crate::package_manager::{self, Options, PackageManager}; use crate::package_manager_real::progress_strings::ProgressStrings; use crate::package_manager_task as task; @@ -45,7 +46,7 @@ type Bitset = DynamicBitSet; pub struct PendingLifecycleScript { pub(crate) list: lockfile::package::scripts::List, pub(crate) tree_id: lockfile::tree::Id, - pub(crate) optional: bool, + pub(crate) optional: Optional, } pub struct PackageInstaller<'a> { @@ -67,7 +68,7 @@ pub struct PackageInstaller<'a> { pub(crate) node_modules: NodeModulesFolder, pub(crate) skip_verify_installed_version_number: bool, - pub(crate) skip_delete: bool, + pub(crate) skip_delete: SkipDelete, pub(crate) force_install: bool, pub(crate) root_node_modules_folder: Dir, pub(crate) summary: &'a mut package_install::Summary, @@ -440,6 +441,9 @@ fn print_package_version<'a>( spill } +bun_core::bool_enum!(InstallPackages); +bun_core::bool_enum!(CanDefer); + impl<'a> PackageInstaller<'a> { // ────────────────────────────────────────────────────────────────────── // BACKREF accessors @@ -502,7 +506,7 @@ impl<'a> PackageInstaller<'a> { // runtime arg rather than a const generic. fn increment_tree_install_count( &mut self, - should_install_packages: bool, + should_install_packages: InstallPackages, tree_id: lockfile::tree::Id, log_level: Options::LogLevel, ) { @@ -548,7 +552,7 @@ impl<'a> PackageInstaller<'a> { // reshaped for borrowck — pass tree_id, re-borrow tree inside. self.link_tree_bins( tree_id, - true, + CanDefer::Yes, link_target_buf.as_mut_slice(), link_dest_buf.as_mut_slice(), link_rel_buf.as_mut_slice(), @@ -556,7 +560,7 @@ impl<'a> PackageInstaller<'a> { ); } - if should_install_packages { + if should_install_packages == InstallPackages::Yes { const FORCE: bool = false; self.install_available_packages::(log_level); } @@ -568,7 +572,7 @@ impl<'a> PackageInstaller<'a> { // Takes only `tree_id` and re-borrows `&mut self.trees[tree_id]` to // satisfy borrowck. tree_id: TreeContextId, - can_defer: bool, + can_defer: CanDefer, link_target_buf: &mut [u8], link_dest_buf: &mut [u8], link_rel_buf: &mut [u8], @@ -650,7 +654,8 @@ impl<'a> PackageInstaller<'a> { }; if target_tree_id != tree_id { - if can_defer && !completed_trees.is_set(target_tree_id as usize) + if can_defer == CanDefer::Yes + && !completed_trees.is_set(target_tree_id as usize) { // Platform package's tree isn't installed // yet: link the package's own bin now and @@ -683,15 +688,15 @@ impl<'a> PackageInstaller<'a> { // globally linked packages shouls always belong to the root // tree (0). let global = if !manager.options.global || tree_id != 0 { - false + Scope::Local } else { 'global: { for request in manager.update_requests.iter() { if request.package_id == package_id { - break 'global true; + break 'global Scope::Global; } } - break 'global false; + break 'global Scope::Local; } }; @@ -810,7 +815,7 @@ impl<'a> PackageInstaller<'a> { self.link_tree_bins( tree_id as u32, - false, + CanDefer::No, link_target_buf.as_mut_slice(), link_dest_buf.as_mut_slice(), link_rel_buf.as_mut_slice(), @@ -831,7 +836,7 @@ impl<'a> PackageInstaller<'a> { // reshaped for borrowck — `package_name` is `Box<[u8]>`; // clone it for the error message since `entry.list` is moved into `spawn`. let name: Box<[u8]> = entry.list.package_name.clone(); - let output_in_foreground = false; + let output_in_foreground = Foreground::No; if let Err(err) = self.manager_mut().spawn_package_lifecycle_scripts( self.command_ctx, @@ -961,7 +966,7 @@ impl<'a> PackageInstaller<'a> { } let optional = entry.optional; - let output_in_foreground = false; + let output_in_foreground = Foreground::No; if let Err(err) = self.manager_mut().spawn_package_lifecycle_scripts( self.command_ctx, entry.list, @@ -1300,7 +1305,7 @@ impl<'a> PackageInstaller<'a> { } self.summary.fail += 1; self.increment_tree_install_count( - !IS_PENDING_PACKAGE_INSTALL, + InstallPackages::from_bool(!IS_PENDING_PACKAGE_INSTALL), self.current_tree_id, log_level, ); @@ -1506,7 +1511,7 @@ impl<'a> PackageInstaller<'a> { } self.summary.fail += 1; self.increment_tree_install_count( - !IS_PENDING_PACKAGE_INSTALL, + InstallPackages::from_bool(!IS_PENDING_PACKAGE_INSTALL), self.current_tree_id, log_level, ); @@ -1589,7 +1594,7 @@ impl<'a> PackageInstaller<'a> { panic!("Internal assertion failure: unexpected resolution tag"); } self.increment_tree_install_count( - !IS_PENDING_PACKAGE_INSTALL, + InstallPackages::from_bool(!IS_PENDING_PACKAGE_INSTALL), self.current_tree_id, log_level, ); @@ -1657,7 +1662,7 @@ impl<'a> PackageInstaller<'a> { } Err(ForTarballError::AlreadyFailed) => self .increment_tree_install_count( - !IS_PENDING_PACKAGE_INSTALL, + InstallPackages::from_bool(!IS_PENDING_PACKAGE_INSTALL), self.current_tree_id, log_level, ), @@ -1689,7 +1694,7 @@ impl<'a> PackageInstaller<'a> { } Err(ForTarballError::AlreadyFailed) => self .increment_tree_install_count( - !IS_PENDING_PACKAGE_INSTALL, + InstallPackages::from_bool(!IS_PENDING_PACKAGE_INSTALL), self.current_tree_id, log_level, ), @@ -1727,7 +1732,7 @@ impl<'a> PackageInstaller<'a> { } Err(ForTarballError::AlreadyFailed) => self .increment_tree_install_count( - !IS_PENDING_PACKAGE_INSTALL, + InstallPackages::from_bool(!IS_PENDING_PACKAGE_INSTALL), self.current_tree_id, log_level, ), @@ -1738,7 +1743,7 @@ impl<'a> PackageInstaller<'a> { panic!("unreachable, handled above"); } self.increment_tree_install_count( - !IS_PENDING_PACKAGE_INSTALL, + InstallPackages::from_bool(!IS_PENDING_PACKAGE_INSTALL), self.current_tree_id, log_level, ); @@ -1810,7 +1815,7 @@ impl<'a> PackageInstaller<'a> { } self.summary.fail += 1; self.increment_tree_install_count( - !IS_PENDING_PACKAGE_INSTALL, + InstallPackages::from_bool(!IS_PENDING_PACKAGE_INSTALL), self.current_tree_id, log_level, ); @@ -1997,7 +2002,9 @@ impl<'a> PackageInstaller<'a> { log_level, &mut folder_path, package_id, - dep_behavior.contains(crate::dependency::Behavior::OPTIONAL), + Optional::from_bool( + dep_behavior.contains(crate::dependency::Behavior::OPTIONAL), + ), resolution, ) { if is_trusted_through_update_request { @@ -2075,7 +2082,7 @@ impl<'a> PackageInstaller<'a> { } self.increment_tree_install_count( - !IS_PENDING_PACKAGE_INSTALL, + InstallPackages::from_bool(!IS_PENDING_PACKAGE_INSTALL), self.current_tree_id, log_level, ); @@ -2090,7 +2097,7 @@ impl<'a> PackageInstaller<'a> { // even if the package failed to install, we still need to increment the install // counter for this tree self.increment_tree_install_count( - !IS_PENDING_PACKAGE_INSTALL, + InstallPackages::from_bool(!IS_PENDING_PACKAGE_INSTALL), self.current_tree_id, log_level, ); @@ -2336,7 +2343,9 @@ impl<'a> PackageInstaller<'a> { log_level, &mut folder_path, package_id, - dep_behavior.contains(crate::dependency::Behavior::OPTIONAL), + Optional::from_bool( + dep_behavior.contains(crate::dependency::Behavior::OPTIONAL), + ), resolution, ) { let (trusted_name, trusted_name_hash) = @@ -2378,7 +2387,7 @@ impl<'a> PackageInstaller<'a> { // only used in the `needs_install` branch's EACCES handler). destination_dir.close(); self.increment_tree_install_count( - !IS_PENDING_PACKAGE_INSTALL, + InstallPackages::from_bool(!IS_PENDING_PACKAGE_INSTALL), self.current_tree_id, log_level, ); @@ -2391,7 +2400,7 @@ impl<'a> PackageInstaller<'a> { ) { self.summary.fail += 1; self.increment_tree_install_count( - !IS_PENDING_PACKAGE_INSTALL, + InstallPackages::from_bool(!IS_PENDING_PACKAGE_INSTALL), self.current_tree_id, log_level, ); @@ -2404,7 +2413,7 @@ impl<'a> PackageInstaller<'a> { log_level: Options::LogLevel, package_path: &mut bun_paths::AutoAbsPath, package_id: PackageID, - optional: bool, + optional: Optional, resolution: &Resolution, ) -> bool { let mut scripts: PackageScripts = diff --git a/src/install/PackageManager.rs b/src/install/PackageManager.rs index e1524675ffb9..56e02bb43e40 100644 --- a/src/install/PackageManager.rs +++ b/src/install/PackageManager.rs @@ -201,25 +201,26 @@ pub use super::package_installer::PackageInstaller; pub use self::package_manager_directories as directories; use directories::attempt_to_create_package_json_and_open; pub use directories::{ - attempt_to_create_package_json, cached_git_folder_name, cached_git_folder_name_print, - cached_git_folder_name_print_auto, cached_github_folder_name, cached_github_folder_name_print, - cached_github_folder_name_print_auto, cached_npm_package_folder_name, - cached_npm_package_folder_name_print, cached_npm_package_folder_print_basename, - cached_tarball_folder_name, cached_tarball_folder_name_print, compute_cache_dir_and_subpath, - fetch_cache_directory_path, get_cache_directory, get_cache_directory_and_abs_path, - get_temporary_directory, global_link_dir, global_link_dir_path, is_folder_in_cache, - path_for_cached_npm_path, path_for_resolution, save_lockfile, setup_global_dir, - update_lockfile_if_needed, write_yarn_lock, + HadAnyDiffs, IncludeCacheVersion, attempt_to_create_package_json, cached_git_folder_name, + cached_git_folder_name_print, cached_git_folder_name_print_auto, cached_github_folder_name, + cached_github_folder_name_print, cached_github_folder_name_print_auto, + cached_npm_package_folder_name, cached_npm_package_folder_name_print, + cached_npm_package_folder_print_basename, cached_tarball_folder_name, + cached_tarball_folder_name_print, compute_cache_dir_and_subpath, fetch_cache_directory_path, + get_cache_directory, get_cache_directory_and_abs_path, get_temporary_directory, + global_link_dir, global_link_dir_path, is_folder_in_cache, path_for_cached_npm_path, + path_for_resolution, save_lockfile, setup_global_dir, update_lockfile_if_needed, + write_yarn_lock, }; pub use self::package_manager_enqueue as enqueue; pub use enqueue::{ - create_extract_task_for_streaming, enqueue_dependency_list, enqueue_dependency_to_root, - enqueue_dependency_with_main, enqueue_dependency_with_main_and_success_fn, - enqueue_extract_npm_package, enqueue_git_checkout, enqueue_git_for_checkout, - enqueue_network_task, enqueue_package_for_download, enqueue_parse_npm_package, - enqueue_patch_task, enqueue_patch_task_pre, enqueue_tarball_for_download, - enqueue_tarball_for_reading, + InstallPeer, IsRoot, create_extract_task_for_streaming, enqueue_dependency_list, + enqueue_dependency_to_root, enqueue_dependency_with_main, + enqueue_dependency_with_main_and_success_fn, enqueue_extract_npm_package, enqueue_git_checkout, + enqueue_git_for_checkout, enqueue_network_task, enqueue_package_for_download, + enqueue_parse_npm_package, enqueue_patch_task, enqueue_patch_task_pre, + enqueue_tarball_for_download, enqueue_tarball_for_reading, }; use self::package_manager_lifecycle as lifecycle; @@ -1075,7 +1076,7 @@ fn configure_env_for_scripts_run( &mut this_transpiler_slot, env_ptr, log_level != package_manager_options::LogLevel::Silent, - false, + bun_resolver::fs::StoreFd::No, )?; // SAFETY: the install-tier `RunCommand::configure_env_for_run` shim // (lib.rs) `.write()`s the slot via `Transpiler::init` before returning @@ -1647,7 +1648,7 @@ pub fn init( debug_assert!(strings::eql_long( &original_package_json_path_buf[..this_cwd.len()], this_cwd, - true, + strings::CheckLen::Yes, )); original_package_json_path_buf.truncate(this_cwd.len()); original_package_json_path_buf.push(SEP); @@ -1797,7 +1798,11 @@ pub fn init( #[cfg(not(windows))] let maybe_workspace_path = child_path; - if strings::eql_long(maybe_workspace_path, path_, true) { + if strings::eql_long( + maybe_workspace_path, + path_, + strings::CheckLen::Yes, + ) { // Intern via the resolver's DirnameStore so the slice is // process-lifetime (`set_top_level_dir` requires `'static`). fs.set_top_level_dir(fs.dirname_store().append(parent)?); @@ -1857,7 +1862,7 @@ pub fn init( // Returns the resolver's BSSMap-owned // `*EntriesOption` slot. - let entries_option = match fs.read_directory(fs.top_level_dir(), 0, true)? { + let entries_option = match fs.read_directory(fs.top_level_dir(), 0, fs::StoreFd::Yes)? { fs::EntriesOption::Entries(e) => { // SAFETY: the BSSMap singleton owns `*e` for the process // lifetime, and `init()` runs single-threaded before any other @@ -1893,7 +1898,7 @@ pub fn init( &env_probe_keys, &[], dot_env::DotEnvFileSuffix::Production, - false, + dot_env::SkipDefaultEnv::No, )?; initialize_store(); @@ -2395,12 +2400,15 @@ fn init_with_runtime_once( // leaves `holder::RAW_PTR` null rather than pointing at an uninitialized // manager. Returns the resolver's BSSMap-owned `*EntriesOption` slot. let fs_instance = FileSystem::instance(); - let root_dir = match fs_instance.read_directory(fs_instance.top_level_dir(), 0, true)? { - // SAFETY: the BSSMap singleton owns `*e` for the process lifetime, - // and runtime init runs once on the main thread before any other access. - fs::EntriesOption::Entries(e) => unsafe { &mut *std::ptr::from_mut::(*e) }, - fs::EntriesOption::Err(e) => return Err(e.canonical_error.into()), - }; + let root_dir = + match fs_instance.read_directory(fs_instance.top_level_dir(), 0, fs::StoreFd::Yes)? { + // SAFETY: the BSSMap singleton owns `*e` for the process lifetime, + // and runtime init runs once on the main thread before any other access. + fs::EntriesOption::Entries(e) => unsafe { + &mut *std::ptr::from_mut::(*e) + }, + fs::EntriesOption::Err(e) => return Err(e.canonical_error.into()), + }; let cpu_count: u32 = u32::from(bun_core::get_thread_count()); allocate_package_manager(); diff --git a/src/install/PackageManager/PackageJSONEditor.rs b/src/install/PackageManager/PackageJSONEditor.rs index 00a19a9c55f3..742b4643893d 100644 --- a/src/install/PackageManager/PackageJSONEditor.rs +++ b/src/install/PackageManager/PackageJSONEditor.rs @@ -595,7 +595,11 @@ fn edit_update_entries( } let workspace_dep_name = workspace_dep.name.slice(string_buf); - if !strings::eql_long(workspace_dep_name, key_str, true) { + if !strings::eql_long( + workspace_dep_name, + key_str, + strings::CheckLen::Yes, + ) { continue; } @@ -808,7 +812,11 @@ pub(crate) fn edit_catalogs_after_update( .unwrap_or(&info.original_version_literal), ); - changed |= !strings::eql_long(new_literal, &info.original_version_literal, true); + changed |= !strings::eql_long( + new_literal, + &info.original_version_literal, + strings::CheckLen::Yes, + ); dep.value = Some(Expr::allocate( arena, @@ -1004,7 +1012,11 @@ pub(crate) fn edit( // `bun update ` edits the slot in place; the rebuild below re-sorts the keys. if request.package_id != INVALID_PACKAGE_ID && manager.subcommand != Subcommand::Update - && strings::eql_long(list, dependency_list, true) + && strings::eql_long( + list, + dependency_list, + strings::CheckLen::Yes, + ) && !keep_catalog_reference { replacing += 1; @@ -1416,7 +1428,7 @@ pub(crate) fn edit( if strings::eql_long( installed, &entry.original_version_literal, - true, + strings::CheckLen::Yes, ) { break 'npm arena_dup(arena, installed); } diff --git a/src/install/PackageManager/PackageManagerDirectories.rs b/src/install/PackageManager/PackageManagerDirectories.rs index 4861484c4b21..a2c5ad05dde2 100644 --- a/src/install/PackageManager/PackageManagerDirectories.rs +++ b/src/install/PackageManager/PackageManagerDirectories.rs @@ -4,6 +4,7 @@ use bun_alloc::AllocError; use crate::Error; use crate::bun_fs::FileSystem; +use crate::lockfile_real::PrintNameVersion; use crate::lockfile_real::package::PackageColumns; use crate::repository::Repository; use bun_core::ZStr; @@ -620,7 +621,7 @@ pub fn cached_npm_package_folder_name_print<'a>( let scope = this.scope_for_package_name(name); if scope.name.is_empty() && !this.options.did_override_default_scope { - let include_version_number = true; + let include_version_number = IncludeCacheVersion::Yes; return cached_npm_package_folder_print_basename( buf, name, @@ -630,7 +631,7 @@ pub fn cached_npm_package_folder_name_print<'a>( ); } - let include_version_number = false; + let include_version_number = IncludeCacheVersion::No; let spanned_len = cached_npm_package_folder_print_basename(buf, name, version, None, include_version_number) .as_bytes() @@ -691,15 +692,17 @@ pub fn cached_npm_package_folder_name( ) } +bun_core::bool_enum!(pub IncludeCacheVersion); + // TODO: normalize to alphanumeric pub fn cached_npm_package_folder_print_basename<'a>( buf: &'a mut [u8], name: &[u8], version: Semver::Version, patch_hash: Option, - include_cache_version: bool, + include_cache_version: IncludeCacheVersion, ) -> &'a ZStr { - let cache_ver = if include_cache_version { + let cache_ver = if include_cache_version == IncludeCacheVersion::Yes { Some(CacheVersion::CURRENT) } else { None @@ -1080,11 +1083,13 @@ pub fn attempt_to_create_package_json() -> Result<(), Error> { Ok(()) } +bun_core::bool_enum!(pub HadAnyDiffs); + pub fn save_lockfile( this: &mut PackageManager, load_result: &LoadResult, save_format: LockfileFormat, - had_any_diffs: bool, + had_any_diffs: HadAnyDiffs, // NOTE(dylan-conway): this and `packages_len_before_install` can most likely be deleted // now that git dependnecies don't append to lockfile during installation. lockfile_before_install: &Lockfile, @@ -1112,7 +1117,7 @@ pub fn save_lockfile( Err(err) => { // we don't care if err.get_errno() == sys::E::ENOENT { - if had_any_diffs { + if had_any_diffs == HadAnyDiffs::Yes { return Ok(false); } break 'delete; @@ -1177,7 +1182,7 @@ pub fn save_lockfile( } else { if this .lockfile - .has_meta_hash_changed(false, packages_len_before_install) + .has_meta_hash_changed(PrintNameVersion::No, packages_len_before_install) .unwrap_or(false) { Output::panic(format_args!( diff --git a/src/install/PackageManager/PackageManagerEnqueue.rs b/src/install/PackageManager/PackageManagerEnqueue.rs index db8f7c3058d3..1217cd6561c3 100644 --- a/src/install/PackageManager/PackageManagerEnqueue.rs +++ b/src/install/PackageManager/PackageManagerEnqueue.rs @@ -31,7 +31,9 @@ use crate::resolution::{ NpmVersionInfo as ResolutionNpmValue, Tag as ResolutionTag, TaggedValue as ResolutionTagged, }; use crate::{ManifestLoad, dependency}; -use bun_install::NetworkTask; +use bun_install::network_task::{IsOptional, NetworkTask}; +use bun_install::npm::ExtendedManifest; +use bun_install::package_manager_task::NormalizePath; use bun_install::{ self as install, Behavior, Dependency, DependencyID, ExtractTarball, Features, Integrity, Npm, PackageID, PackageNameHash, PatchTask, Repository, Resolution, TaskCallbackContext, @@ -71,13 +73,25 @@ const MS_PER_S: f64 = bun_core::time::MS_PER_S as f64; // ───────────────────────────────────────────────────────────────────────────── +bun_core::bool_enum!( + /// Whether peer dependencies are resolved/installed in this pass (second + /// phase) or deferred to `peer_dependencies` for later. + pub InstallPeer +); + +bun_core::bool_enum!( + /// Whether the dependency being enqueued is a root dependency + /// (`assign_root_resolution`) rather than a transitive one. + pub IsRoot +); + pub fn enqueue_dependency_with_main( this: &mut PackageManager, id: DependencyID, // This must be a *const to prevent UB dependency: &Dependency, resolution: PackageID, - install_peer: bool, + install_peer: InstallPeer, ) -> crate::Result<()> { enqueue_dependency_with_main_and_success_fn( this, @@ -87,7 +101,7 @@ pub fn enqueue_dependency_with_main( install_peer, assign_resolution, None, - false, + IsRoot::No, ) } @@ -107,7 +121,9 @@ pub fn enqueue_dependency_list( while i < end { let dependency = this.lockfile.buffers.dependencies[i as usize].clone(); let resolution = this.lockfile.buffers.resolutions[i as usize]; - if let Err(err) = enqueue_dependency_with_main(this, i, &dependency, resolution, false) { + if let Err(err) = + enqueue_dependency_with_main(this, i, &dependency, resolution, InstallPeer::No) + { let path_sep = match dependency.version.tag { dependency::version::Tag::Folder => bun_fmt::PathSep::Auto, _ => bun_fmt::PathSep::Any, @@ -414,7 +430,7 @@ pub fn enqueue_dependency_to_root( let dep_id = 'brk: { let str_buf = this.lockfile.buffers.string_bytes.as_slice(); for (id, dep) in this.lockfile.buffers.dependencies.iter().enumerate() { - if !strings::eql_long(dep.name.slice(str_buf), name, true) { + if !strings::eql_long(dep.name.slice(str_buf), name, strings::CheckLen::Yes) { continue; } if !dep.version.eql(version, str_buf, version_buf) { @@ -461,10 +477,10 @@ pub fn enqueue_dependency_to_root( dep_id, &dependency, invalid_package_id, - false, + InstallPeer::No, assign_root_resolution, Some(fail_root_resolution), - true, + IsRoot::Yes, ) { return DependencyToEnqueue::Failure(err); } @@ -493,7 +509,7 @@ pub fn enqueue_dependency_to_root( if let Err(err) = run_tasks::run_tasks::( manager, &mut (), - false, + InstallPeer::No, log_level, ) { self.err = Some(err); @@ -643,7 +659,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( // This must be a *const to prevent UB dependency: &Dependency, resolution: PackageID, - install_peer: bool, + install_peer: InstallPeer, success_fn: SuccessFn, fail_fn: Option, // The two `SuccessFn` candidates @@ -651,7 +667,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( // bodies in release builds, so Apple ld64 (which ignores `.llvm_addrsig`) // folds them and a runtime fn-pointer address comparison is unsound. Thread // an explicit flag instead. - is_root: bool, + is_root: IsRoot, ) -> crate::Result<()> { if dependency.behavior.is_optional_peer() { return Ok(()); @@ -1016,13 +1032,14 @@ pub fn enqueue_dependency_with_main_and_success_fn( ); } - if !dependency.behavior.is_peer() || install_peer { + if !dependency.behavior.is_peer() || install_peer == InstallPeer::Yes { if !this.has_created_network_task( task_id, dependency.behavior.is_required(), ) { - let needs_extended_manifest = - this.options.minimum_release_age_ms.is_some(); + let needs_extended_manifest = ExtendedManifest::from_bool( + this.options.minimum_release_age_ms.is_some(), + ); if this.options.enable.manifest_cache() { let mut expired = false; // SAFETY: `this_ptr` is the live exclusive @@ -1164,7 +1181,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( &name_str, scope, loaded_manifest.as_ref(), - dependency.behavior.is_optional(), + IsOptional::from_bool(dependency.behavior.is_optional()), needs_extended_manifest, )?; } @@ -1181,7 +1198,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( *manifest_entry_parse.value_ptr = TaskCallbackList::default(); } - let ctx = if is_root { + let ctx = if is_root == IsRoot::Yes { TaskCallbackContext::RootDependency(id) } else { TaskCallbackContext::Dependency(id) @@ -1211,7 +1228,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( let alias = this.lockfile.str_detached(&dependency.name); let url = this.lockfile.str_detached(&dep.repo); let clone_id = Task::Id::for_git_clone(url); - let ctx = if is_root { + let ctx = if is_root == IsRoot::Yes { TaskCallbackContext::RootDependency(id) } else { TaskCallbackContext::Dependency(id) @@ -1278,7 +1295,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( } if dependency.behavior.is_peer() { - if !install_peer { + if install_peer == InstallPeer::No { this.peer_dependencies.write_item(id)?; return Ok(()); } @@ -1310,7 +1327,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( entry.value_ptr.push(ctx); if dependency.behavior.is_peer() { - if !install_peer { + if install_peer == InstallPeer::No { this.peer_dependencies.write_item(id)?; return Ok(()); } @@ -1358,7 +1375,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( } } - let ctx = if is_root { + let ctx = if is_root == IsRoot::Yes { TaskCallbackContext::RootDependency(id) } else { TaskCallbackContext::Dependency(id) @@ -1378,7 +1395,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( } if dependency.behavior.is_peer() { - if !install_peer { + if install_peer == InstallPeer::No { this.peer_dependencies.write_item(id)?; return Ok(()); } @@ -1564,7 +1581,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( } } - let ctx = if is_root { + let ctx = if is_root == IsRoot::Yes { TaskCallbackContext::RootDependency(id) } else { TaskCallbackContext::Dependency(id) @@ -1582,7 +1599,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( } if dependency.behavior.is_peer() { - if !install_peer { + if install_peer == InstallPeer::No { this.peer_dependencies.write_item(id)?; return Ok(()); } @@ -1903,17 +1920,17 @@ fn enqueue_local_tarball( // other dependencies (e.g. `appendPackage` / `StringBuilder.allocate` // in `Package.fromNPM`). let mut abs_buf = PathBuffer::uninit(); - let (tarball_path, normalize): (&[u8], bool) = 'tarball_path: { + let (tarball_path, normalize): (&[u8], NormalizePath) = 'tarball_path: { let workspace_pkg_id = this .lockfile .get_workspace_pkg_if_workspace_dep(dependency_id); if workspace_pkg_id == invalid_package_id { - break 'tarball_path (path, true); + break 'tarball_path (path, NormalizePath::Yes); } let workspace_res = this.lockfile.packages.items_resolution()[workspace_pkg_id as usize]; if workspace_res.tag != ResolutionTag::Workspace { - break 'tarball_path (path, true); + break 'tarball_path (path, NormalizePath::Yes); } // Construct an absolute path to the tarball. @@ -1926,7 +1943,7 @@ fn enqueue_local_tarball( &mut abs_buf, &[workspace_path, path], ); - break 'tarball_path (joined, false); + break 'tarball_path (joined, NormalizePath::No); }; // Build the `Task` value *before* claiming a hive slot — the `.expect()`s @@ -2054,7 +2071,7 @@ fn get_or_put_resolved_package_with_find_result( behavior: Behavior, manifest: &Npm::PackageManifest, find_result: Npm::FindResult, - install_peer: bool, + install_peer: InstallPeer, success_fn: SuccessFn, ) -> crate::Result> { // reshaped for borrowck — `is_root_dependency(&self, &mut PackageManager, …)` @@ -2115,7 +2132,7 @@ fn get_or_put_resolved_package_with_find_result( // version preference, and the "peer *" hoisting test depends on it // deduping to whatever sibling pin exists rather than the manifest floor. let suppress_peer_satisfies = behavior.is_peer() - && !install_peer + && install_peer == InstallPeer::No && !(version.tag == dependency::version::Tag::Npm && version.npm().version.is_star()); if let Some(id) = this.lockfile.get_package_id( name_hash, @@ -2135,7 +2152,7 @@ fn get_or_put_resolved_package_with_find_result( is_first_time: false, task: None, })); - } else if behavior.is_peer() && !install_peer { + } else if behavior.is_peer() && install_peer == InstallPeer::No { return Ok(None); } @@ -2278,10 +2295,10 @@ fn get_or_put_resolved_package( behavior: Behavior, dependency_id: DependencyID, resolution: PackageID, - install_peer: bool, + install_peer: InstallPeer, success_fn: SuccessFn, ) -> crate::Result> { - if install_peer && behavior.is_peer() { + if install_peer == InstallPeer::Yes && behavior.is_peer() { if let Some(index) = this.lockfile.package_index.get(&name_hash) { let resolutions = this.lockfile.packages.items_resolution(); match index { @@ -2438,7 +2455,8 @@ fn get_or_put_resolved_package( // materializing `&mut *this_ptr` after `name_str`/`scope` are // derived from it would pop their borrow-stack tags under SB. let cache_ctx = this.manifest_disk_cache_ctx(); - let needs_ext = this.options.minimum_release_age_ms.is_some(); + let needs_ext = + ExtendedManifest::from_bool(this.options.minimum_release_age_ms.is_some()); let this_ptr: *mut PackageManager = this; // SAFETY: `string_bytes` is not resized between here and the // `find_result` lookup; `manifest` lives in `this.manifests` and @@ -2590,7 +2608,7 @@ fn get_or_put_resolved_package( } // `Ok(None)` in the peer pass makes the caller reload the manifest and retry. - if behavior.is_peer() && !install_peer { + if behavior.is_peer() && install_peer == InstallPeer::No { return Ok(None); } diff --git a/src/install/PackageManager/PackageManagerLifecycle.rs b/src/install/PackageManager/PackageManagerLifecycle.rs index 63730f9876a4..213700099ae8 100644 --- a/src/install/PackageManager/PackageManagerLifecycle.rs +++ b/src/install/PackageManager/PackageManagerLifecycle.rs @@ -17,9 +17,9 @@ use crate::bun_fs::FileSystem; use super::directories; use crate::lifecycle_script_runner::{ - InstallCtx, LifecycleScriptSubprocess as RealLifecycleScriptSubprocess, + Foreground, InstallCtx, LifecycleScriptSubprocess as RealLifecycleScriptSubprocess, Optional, }; -use crate::lockfile_real::package::scripts::List as ScriptsList; +use crate::lockfile_real::package::scripts::{AddNodeGypRebuildScript, List as ScriptsList}; use crate::package_manager_real::Command; use crate::resolution_real::Tag as ResolutionTag; use bun_install::lockfile::{Lockfile, Package}; @@ -309,9 +309,11 @@ impl PackageManager { // `defer top_level_dir.deinit()` — handled by Drop if root_package.scripts.has_any() { - let add_node_gyp_rebuild_script = root_package.scripts.install.is_empty() - && root_package.scripts.preinstall.is_empty() - && Syscall::exists(binding_dot_gyp_path.as_bytes()); + let add_node_gyp_rebuild_script = AddNodeGypRebuildScript::from_bool( + root_package.scripts.install.is_empty() + && root_package.scripts.preinstall.is_empty() + && Syscall::exists(binding_dot_gyp_path.as_bytes()), + ); self.root_lifecycle_scripts = root_package.scripts.create_list( &self.lockfile, @@ -329,7 +331,7 @@ impl PackageManager { &mut top_level_dir, name, ResolutionTag::Root, - true, + AddNodeGypRebuildScript::Yes, ); } } @@ -340,8 +342,8 @@ impl PackageManager { &mut self, ctx: Command::Context<'_>, list: ScriptsList, - optional: bool, - foreground: bool, + optional: Optional, + foreground: Foreground, install_ctx: Option>, ) -> Result<(), crate::Error> { let log_level = self.options.log_level; diff --git a/src/install/PackageManager/PackageManagerResolution.rs b/src/install/PackageManager/PackageManagerResolution.rs index 1f1b0b65edf0..95a142ea3d79 100644 --- a/src/install/PackageManager/PackageManagerResolution.rs +++ b/src/install/PackageManager/PackageManagerResolution.rs @@ -383,7 +383,7 @@ impl PackageManager { || strings::eql_long( failed_dep.name.slice(string_buf), failed_dep.version.literal.slice(string_buf), - true, + strings::CheckLen::Yes, ) { Output::err_generic( diff --git a/src/install/PackageManager/PopulateManifestCache.rs b/src/install/PackageManager/PopulateManifestCache.rs index 92750c35cc3c..7e6269bb4d40 100644 --- a/src/install/PackageManager/PopulateManifestCache.rs +++ b/src/install/PackageManager/PopulateManifestCache.rs @@ -4,13 +4,15 @@ use bun_core::Output; use crate::DependencyID; use crate::ManifestLoad; -use crate::NetworkTask; use crate::PackageID; use crate::Resolution; use crate::invalid_package_id; +use crate::network_task::{IsOptional, NetworkTask}; +use crate::npm::ExtendedManifest; // Import the // *module* under the `Task` name so `Task::Id` resolves as a path (matches // `runTasks.rs` / `PackageManagerEnqueue.rs`). +use super::InstallPeer; use super::PackageManager; use super::enqueue; use super::run_tasks::{self, RunTasksCallbacks}; @@ -47,7 +49,7 @@ fn start_manifest_task( manager: &mut PackageManager, pkg_name: &[u8], is_required: bool, - needs_extended_manifest: bool, + needs_extended_manifest: ExtendedManifest, ) -> Result<(), StartManifestTaskError> { let task_id = Task::Id::for_manifest(pkg_name); if run_tasks::has_created_network_task(manager, task_id, is_required) { @@ -83,7 +85,7 @@ fn start_manifest_task( pkg_name, scope.get(), None, - !is_required, + IsOptional::from_bool(!is_required), needs_extended_manifest, )?; @@ -173,7 +175,8 @@ pub fn populate_manifest_cache( let pkg_name_slice = pkg_name.slice(string_buf); // `options` is not mutated between here and the // `start_manifest_task` call — read via the BACKREF `mgr_ref`. - let needs_extended_manifest = mgr_ref.options.minimum_release_age_ms.is_some(); + let needs_extended_manifest = + ExtendedManifest::from_bool(mgr_ref.options.minimum_release_age_ms.is_some()); // `scope_for_package_name` borrows only `options` (via the // BACKREF `mgr_ref`); `manifests` is a disjoint field projected @@ -232,7 +235,9 @@ pub fn populate_manifest_cache( // `options` read via BACKREF `mgr_ref` — see provenance-root // note above. - let needs_extended_manifest = mgr_ref.options.minimum_release_age_ms.is_some(); + let needs_extended_manifest = ExtendedManifest::from_bool( + mgr_ref.options.minimum_release_age_ms.is_some(), + ); let package_name = pkg_names[pkg_id as usize].slice(string_buf); // See disjoint-field note on the `.All` arm above. let scope = @@ -273,7 +278,8 @@ pub fn populate_manifest_cache( continue; } let package_name = pkg_names[pkg_id as usize].slice(string_buf); - let needs_extended_manifest = mgr_ref.options.minimum_release_age_ms.is_some(); + let needs_extended_manifest = + ExtendedManifest::from_bool(mgr_ref.options.minimum_release_age_ms.is_some()); let scope = bun_ptr::BackRef::new(mgr_ref.options.scope_for_package_name(package_name)); // SAFETY: `manifests` is disjoint from `options`/`lockfile`; `manager_ptr` is the SRW root. @@ -326,7 +332,7 @@ pub fn populate_manifest_cache( if let Err(err) = run_tasks::run_tasks::( manager, &mut (), - true, + InstallPeer::Yes, log_level, ) { closure.err = Some(err); diff --git a/src/install/PackageManager/UpdateRequest.rs b/src/install/PackageManager/UpdateRequest.rs index b861070dbb42..0f8e28163d13 100644 --- a/src/install/PackageManager/UpdateRequest.rs +++ b/src/install/PackageManager/UpdateRequest.rs @@ -79,6 +79,12 @@ fn anchor_cli_bytes(b: Box<[u8]>) -> &'static [u8] { unsafe { &*ptr } } +bun_core::bool_enum!( + /// Whether a parse failure prints and crashes (CLI) or is recorded in `log` + /// and returned as an error. + pub Fatal +); + impl UpdateRequest { /// Borrow the backing string buffer. /// @@ -147,8 +153,15 @@ impl UpdateRequest { update_requests: &'a mut Array, subcommand: Subcommand, ) -> &'a mut [UpdateRequest] { - Self::parse_with_error(pm, log, positionals, update_requests, subcommand, true) - .unwrap_or_else(|_| Global::crash()) + Self::parse_with_error( + pm, + log, + positionals, + update_requests, + subcommand, + Fatal::Yes, + ) + .unwrap_or_else(|_| Global::crash()) } pub fn parse_with_error<'a>( mut pm: Option<&mut PackageManager>, @@ -156,7 +169,7 @@ impl UpdateRequest { positionals: &[&[u8]], update_requests: &'a mut Array, subcommand: Subcommand, - fatal: bool, + fatal: Fatal, ) -> crate::Result<&'a mut [UpdateRequest]> { // first one is always either: // add @@ -223,7 +236,7 @@ impl UpdateRequest { Some(&mut *log), pm.as_deref_mut(), ) else { - if fatal { + if fatal == Fatal::Yes { Output::err_generic( "unrecognised dependency format: {}", format_args!("{}", bstr::BStr::new(positional)), @@ -262,7 +275,7 @@ impl UpdateRequest { dependency::version::Tag::Npm => version.npm().name.eql(placeholder, input, input), _ => false, } { - if fatal { + if fatal == Fatal::Yes { Output::err_generic( "unrecognised dependency format: {}", format_args!("{}", bstr::BStr::new(positional)), diff --git a/src/install/PackageManager/WorkspacePackageJSONCache.rs b/src/install/PackageManager/WorkspacePackageJSONCache.rs index 3ef125dddb95..7a244e29797c 100644 --- a/src/install/PackageManager/WorkspacePackageJSONCache.rs +++ b/src/install/PackageManager/WorkspacePackageJSONCache.rs @@ -66,7 +66,7 @@ impl MapEntry { /// invokes this to restore the invariant `root == parse(source)`. pub(crate) fn reparse_root(&mut self, log: &mut Log) -> Result<(), Error> { let json_bump = bun_alloc::Arena::new(); - let parsed = parse_package_json(&self.source, log, &json_bump, false)?; + let parsed = parse_package_json(&self.source, log, &json_bump, GuessIndentation::No)?; self.root = bun_core::handle_oom(parsed.root.deep_clone(&json_bump)); self.json_arena = json_bump; Ok(()) @@ -75,16 +75,18 @@ impl MapEntry { pub type Map = StringHashMap; +bun_core::bool_enum!(GuessIndentation); + fn parse_package_json( source: &Source, log: &mut Log, bump: &bun_alloc::Arena, - guess_indentation: bool, + guess_indentation: GuessIndentation, ) -> Result { Ok(json::parse_package_json_utf8_with_opts( json::JSONOptions { json_warn_duplicate_keys: false, - guess_indentation, + guess_indentation: guess_indentation == GuessIndentation::Yes, ..json::PACKAGE_JSON_OPTS }, source, @@ -188,7 +190,12 @@ impl WorkspacePackageJSONCache { } let json_bump = bun_alloc::Arena::new(); - let parsed = match parse_package_json(&source, log, &json_bump, opts.guess_indentation) { + let parsed = match parse_package_json( + &source, + log, + &json_bump, + GuessIndentation::from_bool(opts.guess_indentation), + ) { Ok(p) => p, Err(err) => { return GetResult::ParseErr(err); diff --git a/src/install/PackageManager/install_with_manager.rs b/src/install/PackageManager/install_with_manager.rs index ddeecd488e28..6d311bd724c6 100644 --- a/src/install/PackageManager/install_with_manager.rs +++ b/src/install/PackageManager/install_with_manager.rs @@ -12,7 +12,8 @@ use bun_semver::String as SemverString; use crate::GetJsonResult as WorkspacePackageJsonCacheResult; use crate::Subcommand; use crate::dependency::{DependencyExt as _, Tag as DependencyVersionTag}; -use crate::lockfile::{self, Lockfile, reachable}; +use crate::lockfile::package::scripts::AddNodeGypRebuildScript; +use crate::lockfile::{self, Lockfile, PrintNameVersion, reachable}; use crate::resolution::Tag as ResolutionTag; use crate::update_transitive::{ DirectDependencies, TransitiveUpdate, enqueue_peer_rows, moved_targets_after_clean, @@ -29,6 +30,7 @@ use crate::PackageManager; use crate::config_version::ConfigVersion; use crate::hoisted_install::install_hoisted_packages; use crate::isolated_install::install_isolated_packages; +use crate::lifecycle_script_runner::{Foreground, Optional}; use crate::lockfile_real::package::Diff; use crate::lockfile_real::package::PackageColumns as _; use crate::lockfile_real::{Printer, printer as LockfilePrinter}; @@ -41,9 +43,11 @@ use bun_install_types::NodeLinker::NodeLinker; // to avoid one giant `impl PackageManager` block. use crate::package_manager_real::run_tasks::{RunTasksCallbacks, run_tasks}; use crate::package_manager_real::{ - UpdateRequest, enqueue_dependency_list, enqueue_dependency_with_main, enqueue_patch_task_pre, - save_lockfile, setup_global_dir, update_lockfile_if_needed, write_yarn_lock, + HadAnyDiffs, InstallPeer, UpdateRequest, enqueue_dependency_list, enqueue_dependency_with_main, + enqueue_patch_task_pre, save_lockfile, setup_global_dir, update_lockfile_if_needed, + write_yarn_lock, }; +use crate::{InstallRootDependencies, Scope}; use super::security_scanner; @@ -105,7 +109,7 @@ pub fn install_with_manager( manager.options.enable.set( Enable::FORCE_SAVE_LOCKFILE, manager.options.enable.force_save_lockfile() - || changed_config_version + || changed_config_version == lockfile::ConfigVersionChanged::Yes || (matches!(load_result, lockfile::LoadResult::Ok { .. }) // if migrated always save a new lockfile && (load_result.ok().migrated != lockfile::Migrated::None @@ -505,7 +509,7 @@ pub fn install_with_manager( dependency_i as u32, &dependency, invalid_package_id, - false, + InstallPeer::No, ) { add_dependency_error(manager, &dependency, err); } @@ -543,7 +547,7 @@ pub fn install_with_manager( dep_id, &dep, invalid_package_id, - false, + InstallPeer::No, ) { add_dependency_error(manager, &dep, err); } @@ -571,7 +575,7 @@ pub fn install_with_manager( dependency_i, &dependency, resolution, - false, + InstallPeer::No, ) { add_dependency_error(manager, &dependency, err); } @@ -748,7 +752,7 @@ pub fn install_with_manager( continue; } let scripts = packages.items_scripts()[pkg_i]; - let add_node_gyp = !scripts.has_any(); + let add_node_gyp = AddNodeGypRebuildScript::from_bool(!scripts.has_any()); let (first_index, _, entries) = scripts.get_script_entries(string_bytes, ResolutionTag::Workspace, add_node_gyp); @@ -790,8 +794,10 @@ pub fn install_with_manager( } else if !(manager .lockfile .has_meta_hash_changed( - PackageManager::verbose_install() - || manager.options.do_.print_meta_hash_string(), + PrintNameVersion::from_bool( + PackageManager::verbose_install() + || manager.options.do_.print_meta_hash_string(), + ), packages_len_before_install, ) .unwrap_or(false)) @@ -834,7 +840,7 @@ pub fn install_with_manager( ctx, &load_result, save_format, - had_any_diffs, + HadAnyDiffs::from_bool(had_any_diffs), lockfile_before_install, packages_len_before_install, log_level, @@ -922,7 +928,10 @@ pub fn install_with_manager( )? } else { manager.lockfile.has_meta_hash_changed( - PackageManager::verbose_install() || manager.options.do_.print_meta_hash_string(), + PrintNameVersion::from_bool( + PackageManager::verbose_install() + || manager.options.do_.print_meta_hash_string(), + ), packages_len_before_install.min(manager.lockfile.packages.len()), )? }; @@ -948,7 +957,7 @@ pub fn install_with_manager( manager, &load_result, save_format, - had_any_diffs, + HadAnyDiffs::from_bool(had_any_diffs), lockfile_before_install.get(), packages_len_before_install, log_level, @@ -966,7 +975,10 @@ pub fn install_with_manager( write_yarn_lock_with_progress(manager, log_level)?; } - if manager.options.do_.run_scripts() && install_root_dependencies && !manager.options.global { + if manager.options.do_.run_scripts() + && install_root_dependencies == InstallRootDependencies::Yes + && !manager.options.global + { run_root_lifecycle_scripts(manager, ctx, log_level)?; } @@ -975,7 +987,7 @@ pub fn install_with_manager( manager, ctx, &install_summary, - did_meta_hash_change, + MetaHashChanged::from_bool(did_meta_hash_change), requests_removed_from_lockfile, log_level, )?; @@ -1036,7 +1048,12 @@ impl // concrete `RunTasksCallbacks` impl; `extract_ctx` collapses to `()` so we // do NOT pass `this` as both receiver and ctx (would alias `&mut`). let log_level = this.options.log_level; - if let Err(err) = run_tasks::(this, &mut (), CHECK_PEERS, log_level) { + if let Err(err) = run_tasks::( + this, + &mut (), + InstallPeer::from_bool(CHECK_PEERS), + log_level, + ) { closure.err = Some(err); return true; } @@ -1111,13 +1128,15 @@ fn wait_for_peers(this: &mut PackageManager) -> crate::Result<()> { // monolithic body required: every other output section (tree, added, removed, // failures, fallback timestamp, blocked-scripts) lives in its own // `#[cold] #[inline(never)]` helper that LLVM places in `.text.unlikely`. +bun_core::bool_enum!(MetaHashChanged); + #[cold] #[inline(never)] fn print_install_summary( this: &mut PackageManager, ctx: Command::Context, install_summary: &PackageInstallSummary, - did_meta_hash_change: bool, + did_meta_hash_change: MetaHashChanged, requests_removed_from_lockfile: u32, log_level: Options::LogLevel, ) -> crate::Result<()> { @@ -1144,7 +1163,7 @@ fn print_install_summary( this.summary.remove = requests_removed_from_lockfile; } - if !did_meta_hash_change { + if did_meta_hash_change == MetaHashChanged::No { this.summary.remove = 0; this.summary.add = 0; this.summary.update = 0; @@ -1180,7 +1199,7 @@ fn print_install_summary( ); Output::print_start_end_stdout(ctx.start_time, nano_timestamp()); printed_timestamp = true; - print_blocked_packages_info(install_summary, this.options.global); + print_blocked_packages_info(install_summary, Scope::from_bool(this.options.global)); } else { bun_core::pretty!( "Done! Checked {} package{} (no changes) ", @@ -1193,7 +1212,7 @@ fn print_install_summary( ); Output::print_start_end_stdout(ctx.start_time, nano_timestamp()); printed_timestamp = true; - print_blocked_packages_info(install_summary, this.options.global); + print_blocked_packages_info(install_summary, Scope::from_bool(this.options.global)); } } @@ -1284,7 +1303,7 @@ fn print_summary_installed( if pkgs_installed == 1 { "" } else { "s" }, ); Output::print_start_end_stdout(start_time, nano_timestamp()); - print_blocked_packages_info(install_summary, this.options.global); + print_blocked_packages_info(install_summary, Scope::from_bool(this.options.global)); if this.summary.remove > 0 { bun_core::pretty!("Removed: {}\n", this.summary.remove); @@ -1327,7 +1346,7 @@ fn print_summary_removed( if this.summary.remove == 1 { "" } else { "s" }, ); Output::print_start_end_stdout(start_time, nano_timestamp()); - print_blocked_packages_info(install_summary, this.options.global); + print_blocked_packages_info(install_summary, Scope::from_bool(this.options.global)); } #[cold] @@ -1350,7 +1369,7 @@ fn print_summary_timing_fallback(start_time: i128) { #[cold] #[inline(never)] -fn print_blocked_packages_info(summary: &PackageInstallSummary, global: bool) { +fn print_blocked_packages_info(summary: &PackageInstallSummary, global: Scope) { let packages_count = summary.packages_with_blocked_scripts.len(); let mut scripts_count: usize = 0; for count in summary.packages_with_blocked_scripts.values() { @@ -1367,7 +1386,7 @@ fn print_blocked_packages_info(summary: &PackageInstallSummary, global: bool) { "\n\nBlocked {} postinstall{}. Run `bun pm {}untrusted` for details.\n", scripts_count, if scripts_count > 1 { "s" } else { "" }, - if global { "-g " } else { "" }, + if global == Scope::Global { "-g " } else { "" }, ); } else { bun_core::pretty!("\n"); @@ -1377,10 +1396,10 @@ fn print_blocked_packages_info(summary: &PackageInstallSummary, global: bool) { pub(crate) fn get_workspace_filters( manager: &mut PackageManager, original_cwd: &[u8], -) -> crate::Result<(Vec, bool)> { +) -> crate::Result<(Vec, InstallRootDependencies)> { let ids = if manager.subcommand == Subcommand::Install { if manager.options.filter_patterns.is_empty() { - return Ok((Vec::new(), true)); + return Ok((Vec::new(), InstallRootDependencies::Yes)); } WorkspaceFilter::select_workspaces( &manager.lockfile, @@ -1389,13 +1408,16 @@ pub(crate) fn get_workspace_filters( ) } else { match &manager.filtered_link_targets { - None => return Ok((Vec::new(), true)), + None => return Ok((Vec::new(), InstallRootDependencies::Yes)), Some(targets) => targets.package_ids(&manager.lockfile), } }; let filters = vec![WorkspaceFilter::from_ids(ids)]; let install_root_dependencies = WorkspaceFilter::is_selected(&filters, 0); - Ok((filters, install_root_dependencies)) + Ok(( + filters, + InstallRootDependencies::from_bool(install_root_dependencies), + )) } fn frozen_changed_section( @@ -1592,7 +1614,7 @@ fn enqueue_named_updates( dependency_i as DependencyID, &dependency, invalid_package_id, - false, + InstallPeer::No, ) { add_dependency_error(manager, &dependency, err); } @@ -2165,7 +2187,7 @@ fn save_lockfile_only( ctx: Command::Context, load_result: &lockfile::LoadResult, save_format: lockfile::Format, - had_any_diffs: bool, + had_any_diffs: HadAnyDiffs, lockfile_before_install: bun_ptr::ParentRef, packages_len_before_install: usize, log_level: Options::LogLevel, @@ -2180,7 +2202,9 @@ fn save_lockfile_only( // save the lockfile and exit. make sure metahash is generated for binary lockfile manager.lockfile.meta_hash = manager.lockfile.generate_meta_hash( - PackageManager::verbose_install() || manager.options.do_.print_meta_hash_string(), + PrintNameVersion::from_bool( + PackageManager::verbose_install() || manager.options.do_.print_meta_hash_string(), + ), packages_len_before_install, )?; @@ -2275,8 +2299,8 @@ fn run_root_lifecycle_scripts( } // root lifecycle scripts can run now that all dependencies are installed, dependency scripts // have finished, and lockfiles have been saved - let optional = false; - let output_in_foreground = true; + let optional = Optional::No; + let output_in_foreground = Foreground::Yes; // `spawn_package_lifecycle_scripts` consumes by-value; `.take()` // moves it out (`package_name` is owned by the List and drops with it). manager.spawn_package_lifecycle_scripts( diff --git a/src/install/PackageManager/processDependencyList.rs b/src/install/PackageManager/processDependencyList.rs index 7fdd1f654dac..557829f51b59 100644 --- a/src/install/PackageManager/processDependencyList.rs +++ b/src/install/PackageManager/processDependencyList.rs @@ -13,7 +13,7 @@ use crate::lockfile_real::StringBuilder; use crate::lockfile_real::package::{Package, PackageColumns, ResolverContext, Scripts}; use crate::package_manager_real::options::LogLevel; use crate::package_manager_real::{ - PackageManager, TaskCallbackList, enqueue, resolution as pm_resolution, + InstallPeer, IsRoot, PackageManager, TaskCallbackList, enqueue, resolution as pm_resolution, }; use crate::repository_real::{Repository, RepositoryExt as _}; use crate::resolution::{ResolutionType, Tag as ResolutionTag, TaggedValue}; @@ -350,7 +350,7 @@ impl PackageManager { &mut self, item: &TaskCallbackContext, any_root: Option<&Cell>, - install_peer: bool, + install_peer: InstallPeer, ) -> Result<(), crate::Error> { match *item { TaskCallbackContext::Dependency(dependency_id) => { @@ -385,7 +385,7 @@ impl PackageManager { install_peer, pm_resolution::assign_root_resolution, Some(PackageManager::fail_root_resolution), - true, + IsRoot::Yes, )?; if let Some(ptr) = any_root { let new_resolution_id = @@ -415,7 +415,7 @@ impl PackageManager { peer_dependency_id, &dependency, resolution, - true, + InstallPeer::Yes, )?; } Ok(()) @@ -426,7 +426,7 @@ impl PackageManager { dep_list: TaskCallbackList, ctx: C, on_resolve: Option, - install_peer: bool, + install_peer: InstallPeer, ) -> Result<(), crate::Error> { if !dep_list.is_empty() { let dependency_list = dep_list; diff --git a/src/install/PackageManager/runTasks.rs b/src/install/PackageManager/runTasks.rs index 05c3c87d1002..8e88f2bcd890 100644 --- a/src/install/PackageManager/runTasks.rs +++ b/src/install/PackageManager/runTasks.rs @@ -21,11 +21,11 @@ use bun_install::{ use super::{ Command, PackageInstaller, PackageManager, ProgressStrings, Subcommand, TaskCallbackList, }; -use super::{directories, enqueue}; +use super::{InstallPeer, directories, enqueue}; use crate::dependency::Behavior; use crate::isolated_install::installer as store_installer; use crate::isolated_install::store::{EntryColumns as _, NodeColumns as _}; -use crate::lifecycle_script_runner::InstallCtx; +use crate::lifecycle_script_runner::{Foreground, InstallCtx, Optional}; use crate::network_task::{Authorization, ForTarballError}; use crate::package_manifest_map::Value as ManifestEntry; use bun_core::fmt::PathSep; @@ -137,7 +137,7 @@ pub trait RunTasksCallbacks { pub fn run_tasks( manager: &mut PackageManager, extract_ctx: &mut C::Ctx, - install_peer: bool, + install_peer: InstallPeer, log_level: Options::LogLevel, ) -> crate::Result<()> { // `Cell` so the `scopeguard::defer!` below can read it via `&self` @@ -278,7 +278,7 @@ pub fn run_tasks( let node_id = installer.store.entries.items_node_id()[entry_id.get() as usize]; let dep_id = installer.store.nodes.items_dep_id()[node_id.get() as usize]; let dep = &installer.lockfile().buffers.dependencies[dep_id as usize]; - let optional = dep.behavior.contains(Behavior::OPTIONAL); + let optional = Optional::from_bool(dep.behavior.contains(Behavior::OPTIONAL)); // SAFETY: `list` is the per-entry scripts slot owned by // `store.entries.items_scripts()[entry_id]`; this Task is // its sole consumer (see Installer.rs Yield::RunScripts). @@ -294,7 +294,7 @@ pub fn run_tasks( command_ctx, list_val, optional, - false, + Foreground::No, Some(InstallCtx { entry_id, installer: installer_ptr, @@ -532,7 +532,10 @@ pub fn run_tasks( // The HTTP request was cached if let Some(mut manifest) = loaded_manifest.take() { // If we requested extended manifest but we somehow got an abbreviated one, this is a bug - debug_assert!(!is_extended_manifest || manifest.pkg.has_extended_manifest); + debug_assert!( + is_extended_manifest == crate::npm::ExtendedManifest::No + || manifest.pkg.has_extended_manifest + ); if timestamp_this_tick.is_none() { let now = u64::try_from(bun_core::time::timestamp().max(0)) @@ -1628,7 +1631,13 @@ fn do_flush_dependency_queue(this: &mut PackageManager) { while i < end { let dependency = this.lockfile.buffers.dependencies[i as usize].clone(); let resolution = this.lockfile.buffers.resolutions[i as usize]; - let _ = enqueue::enqueue_dependency_with_main(this, i, &dependency, resolution, false); + let _ = enqueue::enqueue_dependency_with_main( + this, + i, + &dependency, + resolution, + InstallPeer::No, + ); i += 1; } } @@ -1960,7 +1969,7 @@ fn process_dependency_list_for_ctx( manager: &mut PackageManager, dependency_list: TaskCallbackList, extract_ctx: &mut C::Ctx, - install_peer: bool, + install_peer: InstallPeer, ) -> crate::Result<()> { let ctx_ptr: *mut C::Ctx = extract_ctx; manager.process_dependency_list( diff --git a/src/install/PackageManager/security_scanner.rs b/src/install/PackageManager/security_scanner.rs index 9fffb25307dc..f3d83b198979 100644 --- a/src/install/PackageManager/security_scanner.rs +++ b/src/install/PackageManager/security_scanner.rs @@ -18,12 +18,13 @@ use bun_core::strings; use bun_core::{self, Output}; use bun_event_loop::EventLoopHandle; use bun_install::{ - DependencyID, PackageID, PackageManager, invalid_dependency_id, invalid_package_id, + DependencyID, InstallRootDependencies, PackageID, PackageManager, invalid_dependency_id, + invalid_package_id, }; use bun_io::Loop as AsyncLoop; #[cfg(unix)] use bun_io::pipe_reader::PosixFlags; -use bun_io::{BufferedReader, ReadState}; +use bun_io::{BufferedReader, IsPollable, ReadState}; use bun_ptr::{RefCount, RefPtr, ThreadSafeRefCount}; #[cfg(not(windows))] use bun_spawn::SpawnResultExt as _; @@ -109,7 +110,7 @@ fn do_partial_install_of_security_scanner( manager, ctx, &[], - true, + InstallRootDependencies::Yes, log_level, packages_to_install, )? @@ -118,7 +119,7 @@ fn do_partial_install_of_security_scanner( IsolatedInstall::install_isolated_packages( manager, ctx, - true, + InstallRootDependencies::Yes, &[], packages_to_install, )? @@ -227,8 +228,9 @@ pub(crate) fn perform_security_scan_after_resolution( return Ok(None); } - let scan_all = - manager.subcommand == bun_install::Subcommand::Remove || manager.update_requests.is_empty(); + let scan_all = ScanAll::from_bool( + manager.subcommand == bun_install::Subcommand::Remove || manager.update_requests.is_empty(), + ); scan_installing_scanner_if_needed( manager, security_scanner, @@ -251,7 +253,7 @@ pub fn perform_security_scan_for_all( scan_installing_scanner_if_needed( manager, security_scanner, - true, + ScanAll::Yes, &[], command_ctx, original_cwd, @@ -261,7 +263,7 @@ pub fn perform_security_scan_for_all( fn scan_installing_scanner_if_needed( manager: &mut PackageManager, security_scanner: &[u8], - scan_all: bool, + scan_all: ScanAll, seeds: &[PackageID], command_ctx: CommandContext, original_cwd: &[u8], @@ -290,7 +292,7 @@ fn scan_installing_scanner_if_needed( seeds, command_ctx, original_cwd, - true, + IsRetry::Yes, )?; match retry_result { ScanAttemptResult::Success(scan_results) => Ok(Some(scan_results)), @@ -789,10 +791,17 @@ impl<'a> JSONBuilder<'a> { // scanner-entry.d.ts is NOT included in the build (type definitions only) const SCANNER_ENTRY_SOURCE: &[u8] = include_bytes!("./scanner-entry.ts"); +bun_core::bool_enum!( + /// Scan every package in the lockfile rather than only the packages being + /// added/updated by this command. + ScanAll +); +bun_core::bool_enum!(pub(crate) IsRetry); + fn attempt_security_scan( manager: &mut PackageManager, security_scanner: &[u8], - scan_all: bool, + scan_all: ScanAll, seeds: &[PackageID], command_ctx: CommandContext, original_cwd: &[u8], @@ -804,18 +813,18 @@ fn attempt_security_scan( seeds, command_ctx, original_cwd, - false, + IsRetry::No, ) } fn attempt_security_scan_with_retry( manager: &mut PackageManager, security_scanner: &[u8], - scan_all: bool, + scan_all: ScanAll, seeds: &[PackageID], command_ctx: CommandContext, original_cwd: &[u8], - is_retry: bool, + is_retry: IsRetry, ) -> Result { if manager.options.log_level == crate::package_manager::Options::LogLevel::Verbose { bun_core::pretty_errorln!( @@ -848,7 +857,7 @@ fn attempt_security_scan_with_retry( let mut collector = PackageCollector::init(manager); - if scan_all { + if scan_all == ScanAll::Yes { collector.collect_all_packages()?; } else { collector.collect_update_packages()?; @@ -1195,7 +1204,8 @@ impl<'a> SecurityScanSubprocess<'a> { // Windows (see the `.uv_loop` projection in `loop_()`); pass through. let uv_loop = self.loop_(); // SAFETY: *pipe was just heap-allocated above and is non-null. - if let Some(e) = unsafe { (**pipe).init(uv_loop, false) }.to_error(bun_sys::Tag::pipe) { + if let Some(e) = unsafe { (**pipe).init(uv_loop, uv::Ipc::No) }.to_error(bun_sys::Tag::pipe) + { return Err(e.into()); } if let Some(e) = unsafe { (**pipe).open(fds.1.unwrap().uv()) }.to_error(bun_sys::Tag::open) @@ -1293,7 +1303,7 @@ impl<'a> SecurityScanSubprocess<'a> { // StaticPipeWriter still holds a pointer to it (child crash case). self.remaining_fds = 2; self.ipc_reader - .start(ipc_read_fd, true) + .start(ipc_read_fd, IsPollable::Yes) .map_err(|e| e.to_zig_err())?; // `to_process` consumes `SpawnResult` by value on POSIX (and @@ -1512,7 +1522,7 @@ impl<'a> SecurityScanSubprocess<'a> { security_scanner_pkg_id: Option, _command_ctx: CommandContext, // Reserved for future use _original_cwd: &[u8], // Reserved for future use - is_retry: bool, + is_retry: IsRetry, ) -> Result { // `defer { ipc_data.deinit(); stderr_data.deinit(); }` — Vec fields drop with self. @@ -1616,7 +1626,7 @@ impl<'a> SecurityScanSubprocess<'a> { ErrorCode::ModuleNotFound => { // If this is a retry after partial install, we need to handle it differently // The scanner might have been installed but the lockfile wasn't updated - if is_retry { + if is_retry == IsRetry::Yes { // Check if the scanner is an npm package name (not a file path) let is_package_name = bun_paths::is_package_path(security_scanner); diff --git a/src/install/PackageManagerTask.rs b/src/install/PackageManagerTask.rs index 18d868675bb0..93f889c2f8b1 100644 --- a/src/install/PackageManagerTask.rs +++ b/src/install/PackageManagerTask.rs @@ -605,10 +605,10 @@ impl<'a> Task<'a> { fn read_and_extract( tarball: &ExtractTarball, tarball_path: &[u8], - normalize: bool, + normalize: NormalizePath, log: &mut Log, ) -> crate::Result { - let bytes = if normalize { + let bytes = if normalize == NormalizePath::Yes { // Resolves // a user-provided relative path against `bun.fs.FileSystem.instance.top_level_dir` // (the absolute project root cached at startup — NOT the live process cwd). @@ -697,16 +697,22 @@ pub struct GitCheckoutRequest { pub(crate) env: &'static dot_env::Map, } +bun_core::bool_enum!( + /// Whether `LocalTarballRequest::tarball_path` is a user-provided path to + /// resolve against the project root (`Yes`) or already absolute (`No`). + pub NormalizePath +); + pub struct LocalTarballRequest { pub(crate) tarball: ExtractTarball, /// Path to read the tarball from. May be the same as `tarball.url` (when - /// `normalize` is true) or an absolute path joined with a workspace + /// `NormalizePath::Yes`) or an absolute path joined with a workspace /// directory. Computed on the main thread in `enqueueLocalTarball` because /// resolving it requires reading `lockfile.packages` / `string_bytes`, /// which can be reallocated concurrently by the main thread while this /// task runs on a ThreadPool worker. pub(crate) tarball_path: StringOrTinyString, - /// When true, `tarball_path` is a user-provided path resolved relative to - /// cwd. When false, it is already an absolute path. - pub(crate) normalize: bool, + /// When `NormalizePath::Yes`, `tarball_path` is a user-provided path resolved relative to + /// cwd. When `NormalizePath::No`, it is already an absolute path. + pub(crate) normalize: NormalizePath, } diff --git a/src/install/PackageManifestMap.rs b/src/install/PackageManifestMap.rs index cdd492b83b82..9beaa6e719f5 100644 --- a/src/install/PackageManifestMap.rs +++ b/src/install/PackageManifestMap.rs @@ -4,7 +4,7 @@ use bun_semver::string::Builder as StringBuilder; use bun_sys::Fd; use crate::PackageNameHash; -use crate::npm; +use crate::npm::{self, ExtendedManifest}; #[derive(Default)] pub struct PackageManifestMap { @@ -62,7 +62,7 @@ impl PackageManifestMap { scope: &npm::registry::Scope, name: &[u8], cache_behavior: CacheBehavior, - needs_extended_manifest: bool, + needs_extended_manifest: ExtendedManifest, ) -> Option<&mut npm::PackageManifest> { self.by_name_hash( ctx, @@ -90,7 +90,7 @@ impl PackageManifestMap { name: &[u8], name_hash: PackageNameHash, cache_behavior: CacheBehavior, - needs_extended_manifest: bool, + needs_extended_manifest: ExtendedManifest, ) -> Option<&mut npm::PackageManifest> { self.by_name_hash_allow_expired( ctx, @@ -126,7 +126,7 @@ impl PackageManifestMap { name: &[u8], is_expired: Option<&mut bool>, cache_behavior: CacheBehavior, - needs_extended_manifest: bool, + needs_extended_manifest: ExtendedManifest, ) -> Option<&mut npm::PackageManifest> { self.by_name_hash_allow_expired( ctx, @@ -152,7 +152,7 @@ impl PackageManifestMap { name_hash: PackageNameHash, is_expired: Option<&mut bool>, cache_behavior: CacheBehavior, - needs_extended_manifest: bool, + needs_extended_manifest: ExtendedManifest, ) -> Option<&mut npm::PackageManifest> { if cache_behavior == CacheBehavior::LoadFromMemory { let entry = self.hash_map.get_mut(&name_hash)?; @@ -183,7 +183,8 @@ impl PackageManifestMap { let demote = matches!( value_ptr, Value::Manifest(m) - if needs_extended_manifest && !m.pkg.has_extended_manifest + if needs_extended_manifest == ExtendedManifest::Yes + && !m.pkg.has_extended_manifest ); if demote { let Value::Manifest(m) = core::mem::replace(value_ptr, Value::NotFound) else { @@ -214,7 +215,9 @@ impl PackageManifestMap { .ok() .flatten() { - if needs_extended_manifest && !manifest.pkg.has_extended_manifest { + if needs_extended_manifest == ExtendedManifest::Yes + && !manifest.pkg.has_extended_manifest + { let value_ptr = vac.insert(Value::Expired(manifest)); if let Some(expiry) = is_expired { *expiry = true; diff --git a/src/install/TarballStream.rs b/src/install/TarballStream.rs index 7ae9f698469b..5d8b959fbb29 100644 --- a/src/install/TarballStream.rs +++ b/src/install/TarballStream.rs @@ -215,10 +215,10 @@ impl TarballStream { // For GitHub/URL/local tarballs we need a SHA-512 to record in the // lockfile even when there is no expected value to verify against, // matching `ExtractTarball.run`. - let compute_if_missing = matches!( + let compute_if_missing = integrity::ComputeIfMissing::from_bool(matches!( tarball.resolution.tag, ResolutionTag::Github | ResolutionTag::RemoteTarball | ResolutionTag::LocalTarball - ); + )); let npm_mode = tarball.resolution.tag != ResolutionTag::Github; let want_first_dirname = tarball.resolution.tag == ResolutionTag::Github; @@ -287,9 +287,10 @@ impl TarballStream { pub(crate) unsafe fn on_chunk( this: *mut Self, chunk: &[u8], - is_last: bool, + is_last: bun_core::compress::Chunk, err: Option, ) { + let is_last = is_last == bun_core::compress::Chunk::Last; let drain_threshold = Self::drain_threshold(); // SAFETY: see fn-level # Safety — `this` is live, raw-ptr field // projection only (no `&mut TarballStream` formed). diff --git a/src/install/audit_fix.rs b/src/install/audit_fix.rs index 505bd97a225c..9e53a79afb32 100644 --- a/src/install/audit_fix.rs +++ b/src/install/audit_fix.rs @@ -11,10 +11,10 @@ use bun_semver::{self as Semver, SlicedString}; use crate::dependency::Behavior; use crate::lockfile::Lockfile; use crate::lockfile::package::PackageColumns as _; -use crate::npm::PackageManifest; +use crate::npm::{ExtendedManifest, PackageManifest}; use crate::package_manager::Options::{Do, Enable, LogLevel}; -use crate::package_manager_real::enqueue_dependency_with_main; use crate::package_manager_real::populate_manifest_cache::{self, Packages}; +use crate::package_manager_real::{InstallPeer, enqueue_dependency_with_main}; use crate::update_transitive::{pretty_update_row, row_glyphs}; use crate::{ Dependency, DependencyID, DependencyVersionTag, ManifestLoad, PackageID, PackageManager, @@ -650,7 +650,7 @@ pub fn plan_fixes(manager: &mut PackageManager, advisories: &[Advisory]) -> crat &inst.name, Some(&mut expired), ManifestLoad::LoadFromMemoryFallbackToDisk, - min_age.is_some(), + ExtendedManifest::from_bool(min_age.is_some()), ) else { for &a in &inst.advisories { advisory_still_present.set(a); @@ -1314,5 +1314,11 @@ fn enqueue_pinned_as( }, }; manager.lockfile.buffers.resolutions[dep_id as usize] = invalid_package_id; - enqueue_dependency_with_main(manager, dep_id, &pinned, invalid_package_id, false) + enqueue_dependency_with_main( + manager, + dep_id, + &pinned, + invalid_package_id, + InstallPeer::No, + ) } diff --git a/src/install/bin.rs b/src/install/bin.rs index 7bb19bf33bbf..ab0b776185c5 100644 --- a/src/install/bin.rs +++ b/src/install/bin.rs @@ -1,7 +1,7 @@ use core::fmt; use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; -use crate::Error; +use crate::{Error, Scope}; use bun_alloc::AllocError; use bun_collections::{StringHashMap, VecExt}; use bun_core::ZStr; @@ -838,6 +838,12 @@ pub struct Linker<'a> { static UMASK: AtomicU32 = AtomicU32::new(0); static HAS_SET_UMASK: AtomicBool = AtomicBool::new(false); +bun_core::bool_enum!( + /// Whether a bin target's textual form was inconclusive and its resolved + /// parent must be checked for escaping the package directory. + ContainmentCheck { Lexical, Resolved } +); + impl<'a> Linker<'a> { pub fn ensure_umask() { // Single-winner gate: only the thread that flips false->true performs @@ -888,8 +894,8 @@ impl<'a> Linker<'a> { &mut self, abs_target: &ZStr, abs_dest: &ZStr, - global: bool, - target_needs_resolved_containment_check: bool, + global: Scope, + target_needs_resolved_containment_check: ContainmentCheck, ) { debug_assert!(path::is_absolute(abs_target.as_bytes())); debug_assert!(path::is_absolute(abs_dest.as_bytes())); @@ -911,7 +917,7 @@ impl<'a> Linker<'a> { return; } - if target_needs_resolved_containment_check { + if target_needs_resolved_containment_check == ContainmentCheck::Resolved { #[cfg(not(windows))] if self.resolved_target_parent_escapes_package_dir(abs_target) { return; @@ -1110,7 +1116,7 @@ impl<'a> Linker<'a> { target: &sys::File, abs_target: &ZStr, abs_dest: &ZStr, - global: bool, + global: Scope, ) { // `encode_into` reinterprets this byte buffer as `[u16]`. // Constructing a `&mut [u16]` from a pointer that is not 2-aligned is @@ -1147,7 +1153,9 @@ impl<'a> Linker<'a> { Ok(f) => break 'bunx_file f, Err(err) => { let err: crate::Error = err.into(); - if err != crate::Error::Sys(bun_errno::SystemErrno::ENOENT) || global { + if err != crate::Error::Sys(bun_errno::SystemErrno::ENOENT) + || global == Scope::Global + { self.err = Some(err); return; } @@ -1257,7 +1265,7 @@ impl<'a> Linker<'a> { } #[cfg(not(windows))] - fn create_symlink(&mut self, abs_target: &ZStr, abs_dest: &ZStr, global: bool) { + fn create_symlink(&mut self, abs_target: &ZStr, abs_dest: &ZStr, global: Scope) { // hoisted from `defer { if (this.err == null) chmod }` — scopeguard // cannot capture `&mut self.err` without conflicting with the body's writes, // so each return path calls `Self::chmod_on_ok` explicitly instead. @@ -1278,7 +1286,7 @@ impl<'a> Linker<'a> { // ENOENT means `.bin` hasn't been created yet. Should only happen if this isn't global if err.get_errno() == sys::Errno::ENOENT { - if global { + if global == Scope::Global { self.err = Some(err.into()); Self::chmod_on_ok(self.err, abs_target); return; @@ -1551,13 +1559,13 @@ impl<'a> Linker<'a> { /// (i.e. where the bin name should be written). // Returning an offset (rather than a slice into abs_dest_buf) avoids // overlapping &mut borrows of self. - pub(crate) fn build_destination_dir(&mut self, global: bool) -> usize { + pub(crate) fn build_destination_dir(&mut self, global: Scope) -> usize { let dest_dir_without_trailing_slash = strings::without_trailing_slash(self.node_modules_path.slice()); let buf = &mut *self.abs_dest_buf; let mut off: usize = 0; - if global { + if global == Scope::Global { let global_bin_path_without_trailing_slash = strings::without_trailing_slash(self.global_bin_path.as_bytes()); buf[off..off + global_bin_path_without_trailing_slash.len()] @@ -1581,7 +1589,7 @@ impl<'a> Linker<'a> { // target: what the symlink points to // destination: where the symlink exists on disk - pub fn link(&mut self, global: bool) { + pub fn link(&mut self, global: Scope) { let package_dir_len = self.build_target_package_dir().len(); let mut dest_off = self.build_destination_dir(global); let is_redirect = self.is_native_binlink_redirect(); @@ -1609,8 +1617,9 @@ impl<'a> Linker<'a> { if target.is_empty() || bin_target_escapes_package_dir(target) { return; } - let target_needs_resolved_containment_check = - bin_target_needs_resolved_containment_check(target); + let target_needs_resolved_containment_check = ContainmentCheck::from_bool( + bin_target_needs_resolved_containment_check(target), + ); let unscoped_package_name = Dependency::unscoped_package_name(self.package_name.slice()); @@ -1664,8 +1673,9 @@ impl<'a> Linker<'a> { { return; } - let target_needs_resolved_containment_check = - bin_target_needs_resolved_containment_check(target); + let target_needs_resolved_containment_check = ContainmentCheck::from_bool( + bin_target_needs_resolved_containment_check(target), + ); if normalized_name.len() >= self.abs_dest_buf.len().saturating_sub(dest_off) { self.err = Some(crate::Error::Sys(bun_errno::SystemErrno::ENAMETOOLONG)); return; @@ -1718,8 +1728,9 @@ impl<'a> Linker<'a> { i += 2; continue; } - let target_needs_resolved_containment_check = - bin_target_needs_resolved_containment_check(bin_target); + let target_needs_resolved_containment_check = ContainmentCheck::from_bool( + bin_target_needs_resolved_containment_check(bin_target), + ); if normalized_bin_dest.len() >= self.abs_dest_buf.len().saturating_sub(abs_dest_dir_end) { @@ -1831,7 +1842,12 @@ impl<'a> Linker<'a> { // SAFETY: abs_dest_buf[abs_dest_len] == 0 written above; see note above. let abs_dest = ZStr::from_raw(abs_dest_buf_ptr, abs_dest_len); - self.link_bin_or_create_shim(abs_target, abs_dest, global, true); + self.link_bin_or_create_shim( + abs_target, + abs_dest, + global, + ContainmentCheck::Resolved, + ); } _ => {} } @@ -1841,7 +1857,7 @@ impl<'a> Linker<'a> { } } - pub fn unlink(&mut self, global: bool) { + pub fn unlink(&mut self, global: Scope) { let package_dir_len = self.build_target_package_dir().len(); let mut dest_off = self.build_destination_dir(global); diff --git a/src/install/dependency.rs b/src/install/dependency.rs index 2f5b38f9b895..b0e20b452b06 100644 --- a/src/install/dependency.rs +++ b/src/install/dependency.rs @@ -714,7 +714,7 @@ impl VersionExt for Version { strings::eql_long( self.literal.slice(lhs_buf), rhs.literal.slice(rhs_buf), - true, + strings::CheckLen::Yes, ) || self.npm().eql(rhs.npm(), lhs_buf, rhs_buf) } Tag::Folder | Tag::DistTag => self.literal.eql(rhs.literal, lhs_buf, rhs_buf), diff --git a/src/install/extract_tarball.rs b/src/install/extract_tarball.rs index b8d3a8b091b5..92bfaca1bd49 100644 --- a/src/install/extract_tarball.rs +++ b/src/install/extract_tarball.rs @@ -570,7 +570,7 @@ impl ExtractTarball { dir_to_move, Fd::from_std_dir(cache_dir), path_to_use, - true, + bun_sys::windows::ReplaceIfExists::Yes, ) { bun_sys::Result::Err(err) => { if retries < MAX_RETRIES { diff --git a/src/install/hoisted_install.rs b/src/install/hoisted_install.rs index 81dc1dc5ee53..0695259ebba4 100644 --- a/src/install/hoisted_install.rs +++ b/src/install/hoisted_install.rs @@ -14,14 +14,15 @@ use crate::bun_fs::FileSystem; use crate::bun_progress::{Node as ProgressNode, Progress}; use crate::lockfile::tree; -use crate::{DependencyID, ExtractData, PackageID}; +use crate::{DependencyID, ExtractData, InstallRootDependencies, PackageID}; // Bring the `items_{,_mut}()` column accessors for // `MultiArrayList::Slice` into scope. use crate::PackageManager; use crate::bin_real as bin; -use crate::package_install; +use crate::package_install::{self, SkipDelete}; use crate::package_installer::{NodeModulesFolder, PackageInstaller, TreeContext}; use crate::package_manager::{self, WorkspaceFilter}; +use crate::package_manager_real::InstallPeer; use crate::package_manager_real::ProgressStrings; use crate::package_manager_real::run_tasks; use crate::package_manager_task as Task; @@ -59,7 +60,7 @@ pub(crate) fn install_hoisted_packages( this: &mut PackageManager, ctx: Command::Context, workspace_filters: &[WorkspaceFilter], - install_root_dependencies: bool, + install_root_dependencies: InstallRootDependencies, log_level: package_manager::Options::LogLevel, packages_to_install: Option<&[PackageID]>, ) -> crate::Result { @@ -215,12 +216,12 @@ pub(crate) fn install_hoisted_packages( } }; - let mut skip_delete = new_node_modules; + let mut skip_delete = SkipDelete::from_bool(new_node_modules); let mut skip_verify_installed_version_number = new_node_modules; if this.options.enable.force_install() { skip_verify_installed_version_number = true; - skip_delete = false; + skip_delete = SkipDelete::No; } let mut summary = package_install::Summary::default(); @@ -454,7 +455,7 @@ pub(crate) fn install_hoisted_packages( run_tasks::run_tasks::( this, &mut installer, - true, + InstallPeer::Yes, log_level, )?; if !this.options.do_.install_packages() { @@ -472,7 +473,7 @@ pub(crate) fn install_hoisted_packages( run_tasks::run_tasks::( this, &mut installer, - true, + InstallPeer::Yes, log_level, )?; if !this.options.do_.install_packages() { @@ -502,7 +503,7 @@ pub(crate) fn install_hoisted_packages( if let Err(err) = run_tasks::run_tasks::( manager, closure.installer, - true, + InstallPeer::Yes, log_level, ) { closure.err = Some(err); diff --git a/src/install/integrity.rs b/src/install/integrity.rs index e5ae223d335e..3486b1c4783f 100644 --- a/src/install/integrity.rs +++ b/src/install/integrity.rs @@ -207,7 +207,7 @@ impl Integrity { .expect("infallible: size matches"); // SAFETY: engine is null (default). unsafe { Crypto::SHA1::hash(bytes, ptr, core::ptr::null_mut()) }; - strings::eql_long(ptr, &sum[0..LEN], true) + strings::eql_long(ptr, &sum[0..LEN], strings::CheckLen::Yes) } Tag::SHA512 => { const LEN: usize = SHA512_DIGEST_LEN; @@ -216,7 +216,7 @@ impl Integrity { .expect("infallible: size matches"); // SAFETY: engine is null (default). unsafe { Crypto::SHA512::hash(bytes, ptr, core::ptr::null_mut()) }; - strings::eql_long(ptr, &sum[0..LEN], true) + strings::eql_long(ptr, &sum[0..LEN], strings::CheckLen::Yes) } Tag::SHA256 => { const LEN: usize = SHA256_DIGEST_LEN; @@ -225,7 +225,7 @@ impl Integrity { .expect("infallible: size matches"); // SAFETY: engine is null (default). unsafe { Crypto::SHA256::hash(bytes, ptr, core::ptr::null_mut()) }; - strings::eql_long(ptr, &sum[0..LEN], true) + strings::eql_long(ptr, &sum[0..LEN], strings::CheckLen::Yes) } Tag::SHA384 => { const LEN: usize = SHA384_DIGEST_LEN; @@ -234,7 +234,7 @@ impl Integrity { .expect("infallible: size matches"); // SAFETY: engine is null (default). unsafe { Crypto::SHA384::hash(bytes, ptr, core::ptr::null_mut()) }; - strings::eql_long(ptr, &sum[0..LEN], true) + strings::eql_long(ptr, &sum[0..LEN], strings::CheckLen::Yes) } _ => false, } @@ -345,8 +345,13 @@ pub(crate) enum Hasher { Sha512(Crypto::SHA512), } +bun_core::bool_enum!( + /// Whether to compute a SHA-512 when `expected` carries no supported hash. + pub(crate) ComputeIfMissing +); + impl Streaming { - pub(crate) fn init(expected: &Integrity, compute_if_missing: bool) -> Streaming { + pub(crate) fn init(expected: &Integrity, compute_if_missing: ComputeIfMissing) -> Streaming { Streaming { expected: *expected, hasher: match expected.tag { @@ -355,7 +360,7 @@ impl Streaming { Tag::SHA384 => Hasher::Sha384(Crypto::SHA384::init()), Tag::SHA512 => Hasher::Sha512(Crypto::SHA512::init()), _ => { - if compute_if_missing { + if compute_if_missing == ComputeIfMissing::Yes { Hasher::Sha512(Crypto::SHA512::init()) } else { Hasher::None @@ -441,7 +446,11 @@ impl Streaming { return false; } let len = self.expected.tag.digest_len(); - strings::eql_long(&computed.value[0..len], &self.expected.value[0..len], true) + strings::eql_long( + &computed.value[0..len], + &self.expected.value[0..len], + strings::CheckLen::Yes, + ) } } diff --git a/src/install/isolated_install.rs b/src/install/isolated_install.rs index 26f662acfc4c..83a88900dd65 100644 --- a/src/install/isolated_install.rs +++ b/src/install/isolated_install.rs @@ -44,12 +44,12 @@ use crate::bun_bunfig::Arguments as Command; use crate::bun_progress::{Node as ProgressNode, Progress}; use crate::lockfile::tree::is_filtered_dependency_or_workspace; use crate::lockfile::{self, Lockfile}; -use crate::package_manager::{self, PackageManager, WorkspaceFilter, run_tasks}; +use crate::package_manager::{self, InstallPeer, PackageManager, WorkspaceFilter, run_tasks}; use crate::package_manager_real::ProgressStrings; use crate::package_manager_task as Task; use crate::{ - self as install, DependencyID, PackageID, PackageInstall, PackageNameHash, Resolution, - invalid_dependency_id, invalid_package_id, + self as install, DependencyID, InstallRootDependencies, PackageID, PackageInstall, + PackageNameHash, Resolution, invalid_dependency_id, invalid_package_id, }; use store::{Entry as StoreEntry, EntryColumns as _, Node as StoreNode, NodeColumns as _}; @@ -185,7 +185,7 @@ impl<'a, 'b> Wait<'a, 'b> { if let Err(err) = run_tasks::run_tasks::( pkg_manager, self.installer, - true, + InstallPeer::Yes, log_level, ) { self.err = Some(err); @@ -224,7 +224,7 @@ pub(crate) enum Timings { pub(crate) fn build_store( manager: &PackageManager, lockfile: &Lockfile, - install_root_dependencies: bool, + install_root_dependencies: InstallRootDependencies, workspace_filters: &[WorkspaceFilter], packages_to_install: Option<&[PackageID]>, timings: Timings, @@ -1144,7 +1144,7 @@ pub(crate) fn build_store( pub(crate) fn install_isolated_packages( manager: &mut PackageManager, command_ctx: Command::Context, - install_root_dependencies: bool, + install_root_dependencies: InstallRootDependencies, workspace_filters: &[WorkspaceFilter], packages_to_install: Option<&[PackageID]>, ) -> Result { diff --git a/src/install/isolated_install/Installer.rs b/src/install/isolated_install/Installer.rs index ce346a116b37..731027be0107 100644 --- a/src/install/isolated_install/Installer.rs +++ b/src/install/isolated_install/Installer.rs @@ -1,7 +1,7 @@ use core::sync::atomic::{AtomicU8, Ordering}; use std::io::Write as _; -use bun_ast::Log; +use bun_ast::{Log, Recycled}; use bun_collections::{ArrayHashMap, DynamicBitSet, StringHashMap}; use bun_core::{Environment, Global, Output}; use bun_core::{ZStr, strings}; @@ -22,7 +22,7 @@ use crate::postinstall_optimizer::PostinstallOptimizer; use crate::resolution; use crate::{ self as install, DependencyID, Lockfile, PackageID, PackageManager, PackageNameHash, - Resolution, TaskCallbackContext, TruncatedPackageNameHash, bin, invalid_dependency_id, + Resolution, Scope, TaskCallbackContext, TruncatedPackageNameHash, bin, invalid_dependency_id, }; // Bring `items_()` column accessors into scope for // `MultiArrayList` / `Slice`. @@ -309,7 +309,7 @@ impl<'a> Installer<'a> { if let crate::patch_install::Callback::Apply(apply) = &mut patch_task.callback { if apply.logger.has_errors() { - apply.logger.clone_to_with_recycled(log, true); + apply.logger.clone_to_with_recycled(log, Recycled::Yes); } } } @@ -1845,7 +1845,7 @@ impl Task { skipped_due_to_missing_bin: false, }; - bin_linker.link(false); + bin_linker.link(Scope::Local); if target_node_modules_path.is_some() && (bin_linker.skipped_due_to_missing_bin || bin_linker.err.is_some()) @@ -1862,7 +1862,7 @@ impl Task { ); } - bin_linker.link(false); + bin_linker.link(Scope::Local); } if let Some(err) = bin_linker.err { @@ -2271,7 +2271,9 @@ impl<'a> Installer<'a> { dest.set_length(base_len); let _ = dest.append(dep_name); // OOM/capacity: fire-and-forget - if entry_node_modules_name.is_some_and(|name| strings::eql_long(dep_name, name, true)) { + if entry_node_modules_name + .is_some_and(|name| strings::eql_long(dep_name, name, strings::CheckLen::Yes)) + { // same name as the entry itself: nest one node_modules deeper to avoid the collision let _ = dest.append(b"node_modules"); // OOM/capacity: fire-and-forget let _ = dest.append(dep_name); // OOM/capacity: fire-and-forget @@ -2407,7 +2409,7 @@ impl<'a> Installer<'a> { skipped_due_to_missing_bin: false, }; - bin_linker.link(false); + bin_linker.link(Scope::Local); if target_node_modules_path.is_some() && (bin_linker.skipped_due_to_missing_bin || bin_linker.err.is_some()) @@ -2423,7 +2425,7 @@ impl<'a> Installer<'a> { ); } - bin_linker.link(false); + bin_linker.link(Scope::Local); } if let Some(err) = bin_linker.err { diff --git a/src/install/isolated_install/Symlinker.rs b/src/install/isolated_install/Symlinker.rs index 52c90735abba..bf0251a7231d 100644 --- a/src/install/isolated_install/Symlinker.rs +++ b/src/install/isolated_install/Symlinker.rs @@ -113,14 +113,21 @@ impl Symlinker { // libuv adds a trailing slash to junctions. current_link = strings::without_trailing_slash(current_link); - if strings::eql_long(current_link, self.target.slice_z().as_bytes(), true) { + if strings::eql_long( + current_link, + self.target.slice_z().as_bytes(), + strings::CheckLen::Yes, + ) { return Ok(false); } #[cfg(windows)] { - if strings::eql_long(current_link, self.fallback_junction_target.slice(), true) - { + if strings::eql_long( + current_link, + self.fallback_junction_target.slice(), + strings::CheckLen::Yes, + ) { return Ok(false); } diff --git a/src/install/lib.rs b/src/install/lib.rs index aaa1d4ba102f..42e21a7fc359 100644 --- a/src/install/lib.rs +++ b/src/install/lib.rs @@ -67,6 +67,18 @@ use core::fmt; pub mod error; pub use error::{Error, Result}; +bun_core::bool_enum!( + /// Whether an install/link operation targets the project (`node_modules/.bin`) + /// or the global bin directory. + pub Scope { Local, Global } +); + +bun_core::bool_enum!( + /// Whether the root package's own dependencies are part of this install + /// (false when `--filter` excludes the root workspace). + pub InstallRootDependencies +); + pub mod npm; #[path = "PackageManifestMap.rs"] pub mod package_manifest_map; @@ -796,7 +808,7 @@ impl RunCommand { this_transpiler: &mut ::core::mem::MaybeUninit>, env: Option<*mut bun_dotenv::Loader>, _log_errors: bool, - store_root_fd: bool, + store_root_fd: bun_resolver::fs::StoreFd, ) -> Result<*mut (), crate::Error> { use bun_core::Global; diff --git a/src/install/lifecycle_script_runner.rs b/src/install/lifecycle_script_runner.rs index 463fcea10407..2ce2b9c8a8a0 100644 --- a/src/install/lifecycle_script_runner.rs +++ b/src/install/lifecycle_script_runner.rs @@ -247,6 +247,18 @@ pub fn replace_package_manager_run( Ok(()) } +bun_core::bool_enum!( + /// Whether a lifecycle script's stdio is inherited (root package scripts) + /// or captured/buffered (dependency scripts). + pub Foreground +); + +bun_core::bool_enum!( + /// Whether the scripts belong to an optional dependency, whose failure + /// must not fail the install. + pub Optional +); + pub struct LifecycleScriptSubprocess<'a> { pub(crate) package_name: Box<[u8]>, @@ -274,8 +286,8 @@ pub struct LifecycleScriptSubprocess<'a> { pub(crate) has_incremented_alive_count: bool, - pub(crate) foreground: bool, - pub(crate) optional: bool, + pub(crate) foreground: Foreground, + pub(crate) optional: Optional, pub(crate) started_at: u64, pub(crate) ctx: Option>, @@ -540,7 +552,9 @@ impl<'a> LifecycleScriptSubprocess<'a> { let combined_script: &mut ZStr = ZStr::from_raw_mut(copy_script.as_mut_ptr(), copy_script.len() - 1); - if (*this).foreground && (*manager).options.log_level != crate::LogLevel::Silent { + if (*this).foreground == Foreground::Yes + && (*manager).options.log_level != crate::LogLevel::Silent + { Output::command(Output::CommandArgv::Single(combined_script.as_bytes())); } else if let Some(scripts_node) = (*manager).scripts_node_mut() { (*manager).set_node_name::( @@ -600,7 +614,7 @@ impl<'a> LifecycleScriptSubprocess<'a> { // `spawned.stdout/stderr` after spawn — see the `#[cfg(windows)]` // block below and `filter_run.rs` for the canonical pattern. let spawn_options = SpawnOptions { - stdin: if (*this).foreground { + stdin: if (*this).foreground == Foreground::Yes { bun_spawn::Stdio::Inherit } else { bun_spawn::Stdio::Ignore @@ -608,7 +622,9 @@ impl<'a> LifecycleScriptSubprocess<'a> { stdout: if (*manager).options.log_level == crate::LogLevel::Silent { bun_spawn::Stdio::Ignore - } else if (*manager).options.log_level.is_verbose() || (*this).foreground { + } else if (*manager).options.log_level.is_verbose() + || (*this).foreground == Foreground::Yes + { bun_spawn::Stdio::Inherit } else { #[cfg(unix)] @@ -628,7 +644,9 @@ impl<'a> LifecycleScriptSubprocess<'a> { }, stderr: if (*manager).options.log_level == crate::LogLevel::Silent { bun_spawn::Stdio::Ignore - } else if (*manager).options.log_level.is_verbose() || (*this).foreground { + } else if (*manager).options.log_level.is_verbose() + || (*this).foreground == Foreground::Yes + { bun_spawn::Stdio::Inherit } else { #[cfg(unix)] @@ -704,7 +722,7 @@ impl<'a> LifecycleScriptSubprocess<'a> { (*this).remaining_fds += 1; Self::reset_output_flags(&mut (*this).stdout, stdout); - (*this).stdout.start(stdout, true)?; + (*this).stdout.start(stdout, bun_io::IsPollable::Yes)?; if let Some(poll) = (*this).stdout.handle.get_poll() { poll.set_flag(FilePollFlag::Socket); } @@ -720,7 +738,7 @@ impl<'a> LifecycleScriptSubprocess<'a> { (*this).remaining_fds += 1; Self::reset_output_flags(&mut (*this).stderr, stderr); - (*this).stderr.start(stderr, true)?; + (*this).stderr.start(stderr, bun_io::IsPollable::Yes)?; if let Some(poll) = (*this).stderr.handle.get_poll() { poll.set_flag(FilePollFlag::Socket); } @@ -850,7 +868,7 @@ impl<'a> LifecycleScriptSubprocess<'a> { match status { Status::Exited(exit) => { if exit.code > 0 { - if self.optional { + if self.optional == Optional::Yes { if let Some(ctx) = &self.ctx { let installer = ctx.installer_mut(); installer.store.entries.items_step()[ctx.entry_id.get() as usize] @@ -874,7 +892,7 @@ impl<'a> LifecycleScriptSubprocess<'a> { Global::exit(exit.code as u32); } - if !self.foreground + if self.foreground == Foreground::No && let Some(scripts_node) = self.manager().scripts_node_mut() { // .monotonic is okay because because this value is only used by hoisted @@ -972,7 +990,9 @@ impl<'a> LifecycleScriptSubprocess<'a> { "error: {} script from \"{}\" terminated by {}", bstr::BStr::new(self.script_name()), bstr::BStr::new(&self.package_name), - signal_code.fmt(Output::enable_ansi_colors_stderr()), + signal_code.fmt(Output::AnsiColors::from_bool( + Output::enable_ansi_colors_stderr() + )), ); // `Status::signal_code()` range-checks 1..=31 (`bun_core::SignalCode` is @@ -985,7 +1005,7 @@ impl<'a> LifecycleScriptSubprocess<'a> { ); } Status::Err(err) => { - if self.optional { + if self.optional == Optional::Yes { if let Some(ctx) = &self.ctx { let installer = ctx.installer_mut(); installer.store.entries.items_step()[ctx.entry_id.get() as usize] @@ -1094,9 +1114,9 @@ impl<'a> LifecycleScriptSubprocess<'a> { list: ScriptsList, envp: bun_dotenv::NullDelimitedEnvMap, shell_bin: Option<&'a ZStr>, - optional: bool, + optional: Optional, log_level: crate::LogLevel, - foreground: bool, + foreground: Foreground, ctx: Option>, ) -> Result<(), crate::Error> { let package_name = list.package_name.clone(); diff --git a/src/install/lockfile.rs b/src/install/lockfile.rs index da5691de7ee9..ce855a8fe8df 100644 --- a/src/install/lockfile.rs +++ b/src/install/lockfile.rs @@ -35,10 +35,10 @@ use crate::resolution_real::{self as resolution, Resolution}; use crate::string_builder; use crate::update_request::UpdateRequest; use crate::{ - self as Install, DependencyID, ExternalSlice, Features, PackageID, PackageManager, - PackageNameAndVersionHash, PackageNameHash, TruncatedPackageNameHash, dependency, - dependency::Dependency, initialize_store, invalid_dependency_id, invalid_package_id, - npm as Npm, + self as Install, DependencyID, ExternalSlice, Features, InstallRootDependencies, PackageID, + PackageManager, PackageNameAndVersionHash, PackageNameHash, TruncatedPackageNameHash, + dependency, dependency::Dependency, initialize_store, invalid_dependency_id, + invalid_package_id, npm as Npm, }; use bun_install_types::NodeLinker::NodeLinker; @@ -363,6 +363,12 @@ pub enum LoadResult<'a> { Ok(LoadResultOk<'a>), } +bun_core::bool_enum!( + /// Whether the chosen `configVersion` differs from what the lockfile had + /// (and therefore needs to be saved). + pub ConfigVersionChanged +); + impl<'a> LoadResult<'a> { pub(crate) fn loaded_from_text_lockfile(&self) -> bool { match self { @@ -437,21 +443,23 @@ impl<'a> LoadResult<'a> { } /// configVersion and boolean for if the configVersion previously existed/needs to be saved to lockfile - pub(crate) fn choose_config_version(&self) -> (ConfigVersion, bool) { + pub(crate) fn choose_config_version(&self) -> (ConfigVersion, ConfigVersionChanged) { match self { - LoadResult::NotFound | LoadResult::Err(_) => (ConfigVersion::CURRENT, true), + LoadResult::NotFound | LoadResult::Err(_) => { + (ConfigVersion::CURRENT, ConfigVersionChanged::Yes) + } LoadResult::Ok(ok) => match ok.migrated { Migrated::None => { if let Some(config_version) = ok.lockfile.saved_config_version { - return (config_version, false); + return (config_version, ConfigVersionChanged::No); } // existing bun project without configVersion - (ConfigVersion::V0, true) + (ConfigVersion::V0, ConfigVersionChanged::Yes) } - Migrated::Pnpm => (ConfigVersion::V1, true), - Migrated::Npm => (ConfigVersion::V0, true), - Migrated::Yarn => (ConfigVersion::V0, true), + Migrated::Pnpm => (ConfigVersion::V1, ConfigVersionChanged::Yes), + Migrated::Npm => (ConfigVersion::V0, ConfigVersionChanged::Yes), + Migrated::Yarn => (ConfigVersion::V0, ConfigVersionChanged::Yes), }, } } @@ -1271,7 +1279,13 @@ impl<'a> Cloner<'a> { impl Lockfile { /// Re-hoists while a pass bound an optional peer late; a reload has that binding up front. pub(crate) fn resolve(&mut self, log: &mut bun_ast::Log) -> Result<(), tree::SubtreeError> { - while self.hoist::<{ tree::BuilderMethod::Resolvable }>(log, None, true, &[], None)? {} + while self.hoist::<{ tree::BuilderMethod::Resolvable }>( + log, + None, + InstallRootDependencies::Yes, + &[], + None, + )? {} Ok(()) } @@ -1279,7 +1293,7 @@ impl Lockfile { &mut self, log: &mut bun_ast::Log, manager: &mut PackageManager, - install_root_dependencies: bool, + install_root_dependencies: InstallRootDependencies, workspace_filters: &[WorkspaceFilter], packages_to_install: Option<&[PackageID]>, ) -> Result<(), tree::SubtreeError> { @@ -1300,7 +1314,7 @@ impl Lockfile { // `tree::Builder` stores these unconditionally (Option/slice), so // accept the concrete shapes for all `METHOD`s. manager: Option<&PackageManager>, - install_root_dependencies: bool, + install_root_dependencies: InstallRootDependencies, workspace_filters: &[WorkspaceFilter], packages_to_install: Option<&[PackageID]>, ) -> Result { @@ -1434,7 +1448,7 @@ impl Lockfile { pkg_name_str, pkg_name_hash, Install::ManifestLoad::LoadFromMemoryFallbackToDisk, - false, + Npm::ExtendedManifest::No, ) else { continue; }; @@ -1670,7 +1684,8 @@ impl<'a> Printer<'a> { // Erase to raw so the `entries_mutex` reborrow below doesn't conflict // with the `&mut self` borrow `read_directory` took. let entries_option: *const Fs::EntriesOption = - fs.fs.read_directory(top_level_dir, None, 0, true)?; + fs.fs + .read_directory(top_level_dir, None, 0, Fs::StoreFd::Yes)?; // Copy the listing's basenames out under `entries_mutex`; `.data` must // only be probed while the lock is held. let entries = { @@ -1692,7 +1707,7 @@ impl<'a> Printer<'a> { &entries, &[] as &[&[u8]], DotEnv::DotEnvFileSuffix::Production, - false, + DotEnv::SkipDefaultEnv::No, )?; let mut log = bun_ast::Log::init(); options.load( @@ -2667,6 +2682,12 @@ impl<'a> EqlSorter<'a> { } } +bun_core::bool_enum!( + /// Whether `generate_meta_hash` also prints the alphabetized name@version + /// listing it hashes. + pub PrintNameVersion +); + impl Lockfile { /// A placement of `r` bound past `r_loaded_package_count` was rebound after loading: a change. pub(crate) fn eql( @@ -2838,7 +2859,7 @@ impl Lockfile { pub fn has_meta_hash_changed( &mut self, - print_name_version_string: bool, + print_name_version_string: PrintNameVersion, packages_len: usize, ) -> Result { let previous_meta_hash = self.meta_hash; @@ -2846,13 +2867,13 @@ impl Lockfile { Ok(!strings::eql_long( &previous_meta_hash, &self.meta_hash, - false, + strings::CheckLen::No, )) } pub(crate) fn generate_meta_hash( &self, - print_name_version_string: bool, + print_name_version_string: PrintNameVersion, packages_len: usize, ) -> Result { if packages_len <= 1 { @@ -2959,7 +2980,7 @@ impl Lockfile { let len = string_builder.len; let alphabetized_name_version_string = &string_builder.allocated_slice()[..len]; - if print_name_version_string { + if print_name_version_string == PrintNameVersion::Yes { Output::flush(); Output::disable_buffering(); Output::writer() diff --git a/src/install/lockfile/Package.rs b/src/install/lockfile/Package.rs index fcd5b545bd4d..79f46c4a131e 100644 --- a/src/install/lockfile/Package.rs +++ b/src/install/lockfile/Package.rs @@ -2768,7 +2768,11 @@ impl Package { let num_notes = 'count: { let mut i: usize = 0; for value in workspace_names.values() { - if strings::eql_long(&value.name, &entry.name, true) { + if strings::eql_long( + &value.name, + &entry.name, + strings::CheckLen::Yes, + ) { i += 1; } } @@ -2785,7 +2789,11 @@ impl Package { if note_path.as_ptr() == path_.as_ptr() { continue; } - if strings::eql_long(&value.name, &entry.name, true) { + if strings::eql_long( + &value.name, + &entry.name, + strings::CheckLen::Yes, + ) { let note_abs_path = bun_core::ZBox::from_bytes( resolve_path::join_abs_string_z::( cwd, @@ -3176,6 +3184,12 @@ pub mod serializer { pub needs_update: bool, } + bun_core::bool_enum!( + /// Whether the on-disk package list is the v2 (`u32` semver ints) + /// layout and must be widened to `u64` while loading. + pub(crate) MigrateFromV2 + ); + // The v2-migration arm // below hard-codes `u32 → u64` (`VersionedURL.migrate()` returns ``). // The only caller (`bun.lockb.rs`) instantiates at `u64`, so bind concretely @@ -3183,7 +3197,7 @@ pub mod serializer { pub(crate) fn load( stream: &mut Stream, end: usize, - migrate_from_v2: bool, + migrate_from_v2: MigrateFromV2, ) -> crate::Result> { type SemverIntType = u64; let reader = stream.reader(); @@ -3224,7 +3238,7 @@ pub mod serializer { list.ensure_total_capacity(list_len as usize)?; let mut needs_update = false; - if migrate_from_v2 { + if migrate_from_v2 == MigrateFromV2::Yes { type OldPackageV2 = Package; let mut list_for_migrating_from_v2 = >::default(); // defer list_for_migrating_from_v2.deinit(allocator); — Drop handles it diff --git a/src/install/lockfile/Package/Scripts.rs b/src/install/lockfile/Package/Scripts.rs index a629ed57680b..bfc6f648fda7 100644 --- a/src/install/lockfile/Package/Scripts.rs +++ b/src/install/lockfile/Package/Scripts.rs @@ -31,6 +31,12 @@ pub struct Scripts { pub(crate) filled: bool, } +bun_core::bool_enum!( + /// Whether to synthesize a `node-gyp rebuild` install script because the + /// package ships a `binding.gyp` without its own install/preinstall script. + pub AddNodeGypRebuildScript +); + impl Scripts { /// (name, getter) table used by debug JSON serialization in place of /// field reflection. @@ -119,14 +125,14 @@ impl Scripts { &self, lockfile_buf: &[u8], resolution_tag: ResolutionTag, - add_node_gyp_rebuild_script: bool, + add_node_gyp_rebuild_script: AddNodeGypRebuildScript, ) -> (i8, u8, [Option>; SCRIPT_NAMES_LEN]) { let mut script_index: u8 = 0; let mut first_script_index: i8 = -1; let mut scripts: [Option>; 6] = [const { None }; 6]; let mut counter: u8 = 0; - if add_node_gyp_rebuild_script { + if add_node_gyp_rebuild_script == AddNodeGypRebuildScript::Yes { { script_index += 1; if first_script_index == -1 { @@ -204,7 +210,7 @@ impl Scripts { cwd_: &mut bun_paths::AutoAbsPath, package_name: &[u8], resolution_tag: ResolutionTag, - add_node_gyp_rebuild_script: bool, + add_node_gyp_rebuild_script: AddNodeGypRebuildScript, ) -> Option { let _ = lockfile; let (first_index, total, scripts) = @@ -296,7 +302,7 @@ impl Scripts { resolution: &Resolution, ) -> Result, crate::Error> { if self.has_any() { - let add_node_gyp_rebuild_script = + let add_node_gyp_rebuild_script = AddNodeGypRebuildScript::from_bool( if lockfile.has_trusted_dependency(folder_name, folder_name, resolution) && self.install.is_empty() && self.preinstall.is_empty() @@ -309,7 +315,8 @@ impl Scripts { bun_sys::exists(save.slice()) } else { false - }; + }, + ); return Ok(self.create_list( lockfile, @@ -375,16 +382,18 @@ impl Scripts { let mut builder = tmp.string_builder(); self.fill_from_package_json(&mut builder, log, folder_path)?; - let add_node_gyp_rebuild_script = if self.install.is_empty() && self.preinstall.is_empty() { - // `defer save.restore()` — `save()` returns an RAII guard that - // restores the path length on Drop and derefs to the path. - let mut save = folder_path.save(); - let _ = save.append(b"binding.gyp"); - - bun_sys::exists(save.slice()) - } else { - false - }; + let add_node_gyp_rebuild_script = AddNodeGypRebuildScript::from_bool( + if self.install.is_empty() && self.preinstall.is_empty() { + // `defer save.restore()` — `save()` returns an RAII guard that + // restores the path length on Drop and derefs to the path. + let mut save = folder_path.save(); + let _ = save.append(b"binding.gyp"); + + bun_sys::exists(save.slice()) + } else { + false + }, + ); Ok(self.create_list( lockfile, diff --git a/src/install/lockfile/Package/WorkspaceMap.rs b/src/install/lockfile/Package/WorkspaceMap.rs index 2f2d415ccd4d..88596a46cb35 100644 --- a/src/install/lockfile/Package/WorkspaceMap.rs +++ b/src/install/lockfile/Package/WorkspaceMap.rs @@ -259,7 +259,7 @@ impl WorkspaceMap { if strings::eql_long( resolve_path::dirname::(abs_package_json_path), root_dir, - true, + strings::CheckLen::Yes, ) { continue; } @@ -387,11 +387,11 @@ impl WorkspaceMap { let mut walker = match GlobWalker::init_with_cwd( glob_pattern, cwd, - false, - false, - false, - false, - true, + glob::Dot::No, + glob::Absolute::No, + glob::FollowSymlinks::No, + glob::ErrorOnBrokenSymlinks::No, + glob::OnlyFiles::Yes, Some(ignored_workspace_paths), )? { Ok(w) => w, diff --git a/src/install/lockfile/Tree.rs b/src/install/lockfile/Tree.rs index 7143e9260626..928828506a9d 100644 --- a/src/install/lockfile/Tree.rs +++ b/src/install/lockfile/Tree.rs @@ -10,8 +10,8 @@ use crate::lockfile::package::PackageColumns as _; use crate::lockfile::{DepSorter, DependencyIDList, DependencyIDSlice, Lockfile}; use crate::package_manager::{PackageManager, WorkspaceFilter}; use crate::{ - Dependency, DependencyID, PackageID, PackageNameHash, Resolution, invalid_dependency_id, - invalid_package_id, + Dependency, DependencyID, InstallRootDependencies, PackageID, PackageNameHash, Resolution, + invalid_dependency_id, invalid_package_id, }; // ────────────────────────────────────────────────────────────────────────── @@ -447,7 +447,7 @@ pub struct Builder<'a, const METHOD: BuilderMethod> { pub(crate) manager: Option<&'a PackageManager>, pub(crate) sort_buf: Vec, pub(crate) workspace_filters: &'a [WorkspaceFilter], - pub(crate) install_root_dependencies: bool, + pub(crate) install_root_dependencies: InstallRootDependencies, pub(crate) packages_to_install: Option<&'a [PackageID]>, } @@ -550,7 +550,7 @@ pub(crate) fn is_filtered_dependency_or_workspace( dep_id: DependencyID, parent_pkg_id: PackageID, workspace_filters: &[WorkspaceFilter], - install_root_dependencies: bool, + install_root_dependencies: InstallRootDependencies, manager: &PackageManager, lockfile: &Lockfile, resolutions: &[PackageID], @@ -616,7 +616,7 @@ pub(crate) fn is_filtered_dependency_or_workspace( } if !dep.behavior.is_workspace() { - return !install_root_dependencies; + return install_root_dependencies == InstallRootDependencies::No; } if manager.summary.pruned_workspaces.contains(&dep.name_hash) { diff --git a/src/install/lockfile/bun.lock.rs b/src/install/lockfile/bun.lock.rs index 1f25d8cc6a06..1382f4b50465 100644 --- a/src/install/lockfile/bun.lock.rs +++ b/src/install/lockfile/bun.lock.rs @@ -2427,7 +2427,7 @@ pub(crate) fn parse_into_binary_lockfile( if !strings::eql_long( path, workspace_path.slice(lockfile.buffers.string_bytes.as_slice()), - true, + strings::CheckLen::Yes, ) { continue; } @@ -2730,7 +2730,7 @@ pub(crate) fn parse_into_binary_lockfile( pkg_path, pkg_names[workspace_pkg_id as usize] .slice(lockfile.buffers.string_bytes.as_slice()), - true, + strings::CheckLen::Yes, )); } @@ -3670,7 +3670,7 @@ fn parse_append_dependencies if !strings::eql_long( path, workspace_path.slice(lockfile.buffers.string_bytes.as_slice()), - true, + strings::CheckLen::Yes, ) { continue; } diff --git a/src/install/lockfile/bun.lockb.rs b/src/install/lockfile/bun.lockb.rs index 28d719ae958a..1f5bf904ada7 100644 --- a/src/install/lockfile/bun.lockb.rs +++ b/src/install/lockfile/bun.lockb.rs @@ -431,8 +431,11 @@ pub(crate) fn load( return Err(crate::Error::LockfileIsMissingData); } - let packages_load_result = - package::serializer::load(stream, total_buffer_size as usize, migrate_from_v2)?; + let packages_load_result = package::serializer::load( + stream, + total_buffer_size as usize, + package::serializer::MigrateFromV2::from_bool(migrate_from_v2), + )?; lockfile.packages = packages_load_result.list; diff --git a/src/install/migration.rs b/src/install/migration.rs index dd5270fe8424..4c81ac379e25 100644 --- a/src/install/migration.rs +++ b/src/install/migration.rs @@ -9,7 +9,8 @@ use bun_sys::{self, Fd, File, O}; use crate::install::{self as Install, PackageManager, Subcommand}; use crate::lockfile::{ - Format as LockfileFormat, LoadResult, LoadResultErr, LoadResultOk, LoadStep, Lockfile, Migrated, + self, Format as LockfileFormat, LoadResult, LoadResultErr, LoadResultOk, LoadStep, Lockfile, + Migrated, }; use crate::lockfile_real::package::PackageColumns as _; use crate::lockfile_real::package::workspace_map::{MissingWorkspace, NamesArray, WorkspaceMap}; @@ -409,7 +410,8 @@ fn migrate_npm_lockfile<'a>( this.verify_data()?; } - this.meta_hash = this.generate_meta_hash(false, this.packages.len())?; + this.meta_hash = + this.generate_meta_hash(lockfile::PrintNameVersion::No, this.packages.len())?; Ok(LoadResult::Ok(LoadResultOk { lockfile: this, diff --git a/src/install/migration/npm_lock.rs b/src/install/migration/npm_lock.rs index 263077dfa258..1513cbdc9837 100644 --- a/src/install/migration/npm_lock.rs +++ b/src/install/migration/npm_lock.rs @@ -240,7 +240,7 @@ impl<'a> Migrator<'a> { return Ok(()); }; let pkg_name = package_name_from_path(key); - if strings::eql_long(&wksp_entry.name, pkg_name, true) { + if strings::eql_long(&wksp_entry.name, pkg_name, strings::CheckLen::Yes) { return Ok(()); } let pkg_name_hash = string_hash(pkg_name); diff --git a/src/install/npm.rs b/src/install/npm.rs index 1d1482ab76e2..846439c864df 100644 --- a/src/install/npm.rs +++ b/src/install/npm.rs @@ -544,7 +544,7 @@ pub mod registry { package_name: &[u8], loaded_manifest: Option, package_manager: &mut PackageManager, - is_extended_manifest: bool, + is_extended_manifest: ExtendedManifest, ) -> Result { match response.status_code { 400 => return Err(crate::Error::BadRequest), @@ -891,6 +891,13 @@ const _: () = { // ────────────────────────────────────────────────────────────────────────── +bun_core::bool_enum!( + /// Whether a registry manifest is (or must be) the full `application/json` + /// document rather than the abbreviated `vnd.npm.install-v1` form. The + /// extended form is needed for `time` (minimum-release-age filtering). + pub ExtendedManifest +); + #[derive(Default, Clone)] pub struct PackageManifest { pub(crate) pkg: NpmPackage, @@ -2008,7 +2015,7 @@ impl PackageManifest { last_modified: &[u8], etag: &[u8], public_max_age: u32, - is_extended_manifest: bool, + is_extended_manifest: ExtendedManifest, ) -> Result, Error> { // `bun_ast::Source::init_path_string` accepts borrowed `&[u8]` via // `IntoStr`; the Source only lives for the duration of this function, @@ -2024,7 +2031,7 @@ impl PackageManifest { Ok(j) => j, Err(_) => { let mut cloned_log = bun_ast::Log::init(); - log.clone_to_with_recycled(&mut cloned_log, true); + log.clone_to_with_recycled(&mut cloned_log, bun_ast::Recycled::Yes); *log = cloned_log; return Ok(None); } @@ -2070,7 +2077,7 @@ impl PackageManifest { // from the default registry we don't check because the registry might have a different name in the manifest. // https://github.com/oven-sh/bun/issues/4925 if scope.url_hash == *registry::DEFAULT_URL_HASH - && !strings::eql_long(expected_name, received_name, true) + && !strings::eql_long(expected_name, received_name, strings::CheckLen::Yes) { bun_core::warn!( "Package name mismatch. Expected \"{}\" but received \"{}\"", @@ -2467,7 +2474,11 @@ impl PackageManifest { cur.slice(string_builder.allocated_slice()); let second = prev_item .slice(string_builder.allocated_slice()); - if !strings::eql_long(first, second, true) { + if !strings::eql_long( + first, + second, + strings::CheckLen::Yes, + ) { Output::panic(format_args!( "Bin group is not identical: {} != {}", bstr::BStr::new(first), @@ -2494,7 +2505,11 @@ impl PackageManifest { cur.slice(string_builder.allocated_slice()); let second = prev_item .slice(string_builder.allocated_slice()); - if !strings::eql_long(first, second, true) { + if !strings::eql_long( + first, + second, + strings::CheckLen::Yes, + ) { Output::panic(format_args!( "Bin group is not identical: {} != {}", bstr::BStr::new(first), @@ -3228,7 +3243,7 @@ impl PackageManifest { }; result.bundled_deps_buf = bundled_deps_buf; result.pkg.public_max_age = public_max_age; - result.pkg.has_extended_manifest = is_extended_manifest; + result.pkg.has_extended_manifest = is_extended_manifest == ExtendedManifest::Yes; if let Some(buf) = string_builder.ptr.take() { let mut v = buf.into_vec(); diff --git a/src/install/patch_install.rs b/src/install/patch_install.rs index c5162af3567d..7e1645806df2 100644 --- a/src/install/patch_install.rs +++ b/src/install/patch_install.rs @@ -491,7 +491,7 @@ impl PatchTask { }; match pkg_install.install( - true, + SkipDelete::Yes, sys::Dir::borrow(&system_tmpdir), InstallMethod::Copyfile, resolution_tag, @@ -824,6 +824,6 @@ impl PatchTask { use crate::PreinstallState; use crate::network_task::Authorization; -use crate::package_install::{InstallResult, Method as InstallMethod}; +use crate::package_install::{InstallResult, Method as InstallMethod, SkipDelete}; use crate::package_manager::Options::LogLevel; use crate::package_manager_task::Id as TaskId; diff --git a/src/install/pnpm.rs b/src/install/pnpm.rs index e14532accf02..2d4ab79c3dcd 100644 --- a/src/install/pnpm.rs +++ b/src/install/pnpm.rs @@ -17,6 +17,7 @@ use crate::external_slice::ExternalSlice; use crate::integrity::Integrity; use crate::lockfile::{self, LoadResult, LoadResultOk, Lockfile}; use crate::npm::{self}; +use crate::package_manager::IsRoot; use crate::repository::Repository; use crate::resolution::{self, Resolution, TaggedValue}; use crate::{DependencyID, INVALID_PACKAGE_ID, PackageID, PackageManager}; @@ -839,7 +840,7 @@ pub(crate) fn migrate_pnpm_lockfile<'a>( b".", exclude_links, log, - true, + IsRoot::Yes, &importers_obj, importer_versions.value_ptr, )?; @@ -868,7 +869,11 @@ pub(crate) fn migrate_pnpm_lockfile<'a>( let value = prop.value.as_ref().expect("infallible: prop has value"); let path = as_string(key).unwrap(); - if !strings::eql_long(path, workspace_path.slice(string_bytes!(lockfile)), true) { + if !strings::eql_long( + path, + workspace_path.slice(string_bytes!(lockfile)), + strings::CheckLen::Yes, + ) { continue; } @@ -918,7 +923,7 @@ pub(crate) fn migrate_pnpm_lockfile<'a>( path, exclude_links, log, - false, + IsRoot::No, &importers_obj, importer_versions.value_ptr, )?; @@ -1021,7 +1026,7 @@ pub(crate) fn migrate_pnpm_lockfile<'a>( if strings::eql_long( workspace_path_buf.slice(), link_path_buf.slice(), - true, + strings::CheckLen::Yes, ) { continue 'next_dep; } @@ -2108,7 +2113,7 @@ fn parse_append_importer_dependencies( importer_path: &[u8], exclude_links_from_lockfile: bool, log: &mut bun_ast::Log, - is_root: bool, + is_root: IsRoot, importers_obj: &Expr, importer_versions: &mut StringArrayHashMap>, ) -> Result<(u32, u32), ParseAppendDependenciesError> { @@ -2234,13 +2239,17 @@ fn parse_append_importer_dependencies( } } - if is_root { + if is_root == IsRoot::Yes { let workspace_paths_snapshot: Vec = lockfile.workspace_paths.values().to_vec(); 'workspaces: for workspace_path in &workspace_paths_snapshot { for prop in e_object(importers_obj).properties.slice() { let key = prop.key.as_ref().expect("infallible: prop has key"); let path = as_string(key).unwrap(); - if !strings::eql_long(path, workspace_path.slice(string_bytes!(lockfile)), true) { + if !strings::eql_long( + path, + workspace_path.slice(string_bytes!(lockfile)), + strings::CheckLen::Yes, + ) { continue; } diff --git a/src/install/prune.rs b/src/install/prune.rs index 85376162d1e5..44f8972c11d7 100644 --- a/src/install/prune.rs +++ b/src/install/prune.rs @@ -19,7 +19,9 @@ use crate::lockfile_real::package::{Diff, DiffSummary, Package}; use crate::package_manager::Options::{Enable, LogLevel}; use crate::package_manager::ROOT_PACKAGE_JSON_PATH; use crate::package_manager::workspace_selection::{self, RootSelection}; -use crate::{Features, PackageID, PackageManager, ResolutionTag, invalid_package_id}; +use crate::{ + Features, InstallRootDependencies, PackageID, PackageManager, ResolutionTag, invalid_package_id, +}; const STORE_DIR: &[u8] = b"node_modules/.bun"; const ROOT_DIR: &[u8] = b"node_modules"; @@ -622,7 +624,13 @@ fn hoist_filtered(manager: &mut PackageManager) { let result = unsafe { let lf: *mut Lockfile = &raw mut *(*pm).lockfile; let log: *mut bun_ast::Log = (*pm).log; - (*lf).hoist::<{ tree::BuilderMethod::Filter }>(&mut *log, Some(&*pm), true, &[], None) + (*lf).hoist::<{ tree::BuilderMethod::Filter }>( + &mut *log, + Some(&*pm), + InstallRootDependencies::Yes, + &[], + None, + ) }; if result.is_err() { manager.crash(); @@ -1478,7 +1486,7 @@ fn build_store_with(manager: &mut PackageManager, (local, remote): StoreFeatures let store = build_store( &*manager, &manager.lockfile, - true, + InstallRootDependencies::Yes, &[], None, Timings::Quiet, @@ -1551,7 +1559,7 @@ fn direct_aliases(manager: &PackageManager, pkg_id: PackageID) -> Vec> dep_id, pkg_id, &[], - true, + InstallRootDependencies::Yes, manager, lockfile, resolutions, diff --git a/src/install/update_transitive.rs b/src/install/update_transitive.rs index d53d2adbd821..0da67b31b757 100644 --- a/src/install/update_transitive.rs +++ b/src/install/update_transitive.rs @@ -12,11 +12,11 @@ use crate::dedupe; use crate::dependency::{self, Behavior}; use crate::lockfile::package::PackageColumns as _; use crate::lockfile::{Lockfile, PackageIndexEntry}; -use crate::npm::PackageManifest; +use crate::npm::{ExtendedManifest, PackageManifest}; use crate::package_manager::Options::LogLevel; use crate::package_manager::ROOT_PACKAGE_JSON_PATH; use crate::package_manager_real::populate_manifest_cache::{self, Packages}; -use crate::package_manager_real::{PackageUpdateInfo, enqueue_dependency_with_main}; +use crate::package_manager_real::{InstallPeer, PackageUpdateInfo, enqueue_dependency_with_main}; use crate::update_scope::UpdateScope; use crate::{ DependencyID, DependencyVersionTag, GetJsonOptions, GetJsonResult, ManifestLoad, PackageID, @@ -433,7 +433,7 @@ fn reresolve(manager: &mut PackageManager, dep_id: DependencyID) -> crate::Resul let mut dep = manager.lockfile.buffers.dependencies[dep_id as usize].clone(); dep.behavior = dep.behavior.with(Behavior::PEER, false); manager.lockfile.buffers.resolutions[dep_id as usize] = invalid_package_id; - enqueue_dependency_with_main(manager, dep_id, &dep, invalid_package_id, false) + enqueue_dependency_with_main(manager, dep_id, &dep, invalid_package_id, InstallPeer::No) } /// `bun update `: the `plannable_peer_rows` naming a requested package, re-resolved after their manifests are fetched; each is appended to `moved` for `redirect_moved_edges`, and every package in `moved` is registered so the summary prints its update row. @@ -819,7 +819,7 @@ fn newest_allowed(manager: &mut PackageManager, pkg_id: PackageID) -> Option = None; @@ -1155,7 +1155,7 @@ fn plan_edges( name, Some(&mut expired), ManifestLoad::LoadFromMemoryFallbackToDisk, - min_age.is_some(), + ExtendedManifest::from_bool(min_age.is_some()), ) else { let entry = (Box::from(name), text(inst.current.fmt(buf))); if !unchecked.contains(&entry) { diff --git a/src/install/yarn.rs b/src/install/yarn.rs index cbcc3613b691..9c5bbc74563e 100644 --- a/src/install/yarn.rs +++ b/src/install/yarn.rs @@ -530,7 +530,7 @@ fn process_deps( .expect("unreachable"); if let Some(dep_entry) = yarn_lock_.find_entry_by_spec(&dep_spec) { - let dep_entry_workspace = dep_entry.workspace; + let dep_entry_workspace = IsWorkspace::from_bool(dep_entry.workspace); let dep_name_hash = string_hash(dep_name); let dep_name_str = string_buf_.append_with_hash(dep_name, dep_name_hash)?; @@ -1168,7 +1168,7 @@ pub(crate) fn migrate_yarn_lockfile<'a>( Some(&mut *manager), ) .unwrap_or_default(), - behavior: behavior_for(dep.dep_type, false), + behavior: behavior_for(dep.dep_type, IsWorkspace::No), }, ); } @@ -1644,7 +1644,7 @@ pub(crate) fn migrate_yarn_lockfile<'a>( name_hash, name: dep_name_string, version: parsed_version, - behavior: behavior_for(root_dep.dep_type, false), + behavior: behavior_for(root_dep.dep_type, IsWorkspace::No), }; this.buffers.dependencies.push(dep); @@ -1913,7 +1913,8 @@ pub(crate) fn migrate_yarn_lockfile<'a>( this.verify_data()?; } - this.meta_hash = this.generate_meta_hash(false, this.packages.len())?; + this.meta_hash = + this.generate_meta_hash(lockfile::PrintNameVersion::No, this.packages.len())?; let result = LoadResult::Ok(lockfile::LoadResultOk { lockfile: this, @@ -2000,10 +2001,12 @@ fn string_hash(s: &[u8]) -> u64 { Semver::string::Builder::string_hash(s) } +bun_core::bool_enum!(IsWorkspace); + /// The bitflags-backed `Behavior` has no named fields, so build via /// `with(FLAG, cond)` chaining. #[inline] -fn behavior_for(dep_type: DependencyType, workspace: bool) -> dependency::Behavior { +fn behavior_for(dep_type: DependencyType, workspace: IsWorkspace) -> dependency::Behavior { dependency::Behavior::default() .with( dependency::Behavior::PROD, @@ -2018,5 +2021,8 @@ fn behavior_for(dep_type: DependencyType, workspace: bool) -> dependency::Behavi dep_type == DependencyType::Development, ) .with(dependency::Behavior::PEER, dep_type == DependencyType::Peer) - .with(dependency::Behavior::WORKSPACE, workspace) + .with( + dependency::Behavior::WORKSPACE, + workspace == IsWorkspace::Yes, + ) } diff --git a/src/install_jsc/update_request_jsc.rs b/src/install_jsc/update_request_jsc.rs index bdaa149f3449..bc45ee73453c 100644 --- a/src/install_jsc/update_request_jsc.rs +++ b/src/install_jsc/update_request_jsc.rs @@ -5,7 +5,7 @@ use bun_jsc::{JSGlobalObject, JSValue, JsResult}; pub(crate) fn from_js(global: &JSGlobalObject, input: JSValue) -> JsResult { use bun_ast::Log; use bun_install::Subcommand; - use bun_install::package_manager::update_request::{self, UpdateRequest}; + use bun_install::package_manager::update_request::{self, Fatal, UpdateRequest}; // `to_slice_clone` returns `ZigStringSlice`; convert to owned `Vec` // via `.into_vec()` since there is no arena backing the slices here. @@ -45,7 +45,7 @@ pub(crate) fn from_js(global: &JSGlobalObject, input: JSValue) -> JsResult v, Err(_) => { diff --git a/src/install_types/NodeLinker.rs b/src/install_types/NodeLinker.rs index 8f31597efddb..b982c4cb0a9c 100644 --- a/src/install_types/NodeLinker.rs +++ b/src/install_types/NodeLinker.rs @@ -128,9 +128,14 @@ pub struct PnpmMatcher { pub behavior: Behavior, } +bun_core::bool_enum!( + /// A pnpm-style pattern (`foo-*`) or its negation (`!foo-*`). + pub MatcherKind { Include, Exclude } +); + pub struct Matcher { pub(crate) pattern: Pattern, - pub is_exclude: bool, + pub kind: MatcherKind, } pub enum Pattern { @@ -207,8 +212,8 @@ impl PnpmMatcher { return Err(FromExprError::InvalidRegExp); } }; - has_include = has_include || !matcher.is_exclude; - has_exclude = has_exclude || matcher.is_exclude; + has_include = has_include || matcher.kind == MatcherKind::Include; + has_exclude = has_exclude || matcher.kind == MatcherKind::Exclude; matchers.push(matcher); } ast::ExprData::EArray(patterns) => { @@ -232,8 +237,8 @@ impl PnpmMatcher { return Err(FromExprError::InvalidRegExp); } }; - has_include = has_include || !matcher.is_exclude; - has_exclude = has_exclude || matcher.is_exclude; + has_include = has_include || matcher.kind == MatcherKind::Include; + has_exclude = has_exclude || matcher.kind == MatcherKind::Exclude; matchers.push(matcher); } else { log.add_error_opts( @@ -318,11 +323,11 @@ impl PnpmMatcher { for matcher in self.matchers.iter() { match &matcher.pattern { Pattern::MatchAll => { - matches = !matcher.is_exclude; + matches = matcher.kind == MatcherKind::Include; } Pattern::Regex(regex) => { if regex.matches(&name_str) { - matches = !matcher.is_exclude; + matches = matcher.kind == MatcherKind::Include; } } } @@ -347,16 +352,16 @@ pub fn create_matcher(raw: &[u8], buf: &mut Vec) -> Result) -> Result bool { match (lhs, rhs) { (URI::Local(l), URI::Local(r)) | (URI::Remote(l), URI::Remote(r)) => { - strings::eql_long(l.slice(lhs_buf), r.slice(rhs_buf), true) + strings::eql_long(l.slice(lhs_buf), r.slice(rhs_buf), strings::CheckLen::Yes) } _ => false, } diff --git a/src/io/ParentDeathWatchdog.rs b/src/io/ParentDeathWatchdog.rs index fe05cc8c709a..79120b15ddeb 100644 --- a/src/io/ParentDeathWatchdog.rs +++ b/src/io/ParentDeathWatchdog.rs @@ -331,7 +331,7 @@ pub fn install_on_event_loop(handle: EventLoopCtx) { match unsafe { &mut *poll }.register( handle.loop_mut(), crate::file_poll::Pollable::Process, - true, + crate::OneShot::Yes, ) { bun_sys::Result::Ok(()) => { // Do not keep the event loop alive on this poll's behalf — the diff --git a/src/io/PipeReader.rs b/src/io/PipeReader.rs index 5eadb4f0fc23..a62f61fa6ae2 100644 --- a/src/io/PipeReader.rs +++ b/src/io/PipeReader.rs @@ -22,9 +22,9 @@ pub type Loop = bun_sys::windows::libuv::Loop; /// dispatch in `bun_runtime::dispatch::__bun_run_file_poll` recovers the type /// from this constant. T2 cannot name `bun_io`, so the value is mirrored. use crate::max_buf::MaxBuf; -use crate::pipes::{Chunk, FileType, PollOrFd, ReadState}; +use crate::pipes::{Chunk, FileType, IsPollable, PollOrFd, ReadState, ReceivedHup}; #[cfg(windows)] -use crate::source::Source; +use crate::source::{Source, WasCanceled}; #[cfg(windows)] use bun_sys::ReturnCodeExt as _; @@ -352,7 +352,7 @@ impl PosixBufferedReader { // Unregister the FilePoll if it's registered if let PollOrFd::Poll(poll) = &mut self.handle { if poll.is_registered() { - let _ = poll.unregister(self.vtable.loop_().cast(), false); + let _ = poll.unregister(self.vtable.loop_().cast(), crate::ForceUnregister::No); } } } @@ -543,8 +543,8 @@ impl PosixBufferedReader { } } - pub fn start(&mut self, fd: Fd, is_pollable: bool) -> sys::Result<()> { - if !is_pollable { + pub fn start(&mut self, fd: Fd, is_pollable: IsPollable) -> sys::Result<()> { + if is_pollable == IsPollable::No { self.buffer().clear(); self.flags.remove(PosixFlags::IS_DONE); self.handle.close(None, None::); @@ -566,7 +566,12 @@ impl PosixBufferedReader { sys::Result::Ok(()) } - pub fn start_file_offset(&mut self, fd: Fd, poll: bool, offset: usize) -> sys::Result<()> { + pub fn start_file_offset( + &mut self, + fd: Fd, + poll: IsPollable, + offset: usize, + ) -> sys::Result<()> { self._offset = offset; self.flags.insert(PosixFlags::USE_PREAD); self.start(fd, poll) @@ -618,13 +623,13 @@ impl PosixBufferedReader { // The read loop dispatches `on_read_chunk` and touches `*this` // afterwards, so the parent (which embeds this reader) must outlive it. let _parent = vtable.ref_parent(); - let mut received_hup = false; + let mut received_hup = ReceivedHup::No; // A used-up limit is reported without reading, so there is nothing to wait for. // SAFETY: caller contract; borrow ends at `;`. if file_type == FileType::Pipe && !unsafe { (*this).limit.reached() } { match bun_core::is_readable(fd) { bun_core::Pollable::Ready => {} - bun_core::Pollable::Hup => received_hup = true, + bun_core::Pollable::Hup => received_hup = ReceivedHup::Yes, bun_core::Pollable::NotReady => { // SAFETY: caller contract; the error dispatch may free the parent. unsafe { Self::register_poll(this) }; @@ -639,7 +644,11 @@ impl PosixBufferedReader { /// # Safety /// `this` is the live reader registered as the poll's user data; see /// [`Self::read`] for why the entry is raw. - pub unsafe fn on_poll(this: *mut PosixBufferedReader, size_hint: isize, received_hup: bool) { + pub unsafe fn on_poll( + this: *mut PosixBufferedReader, + size_hint: isize, + received_hup: ReceivedHup, + ) { // SAFETY: caller contract — `this` is live; borrows end at each `;`. let Some((fd, file_type, vtable)) = (unsafe { (*this).begin_read() }) else { return; @@ -758,10 +767,11 @@ impl PosixBufferedReader { this: *mut PosixBufferedReader, file_type: FileType, fd: Fd, - mut received_hup: bool, + received_hup: ReceivedHup, ) { // SAFETY: caller contract — `this` is live. let vtable = unsafe { (*this).vtable }; + let mut received_hup = received_hup == ReceivedHup::Yes; let streaming = vtable.is_streaming_enabled(); let mut scratch = vtable.event_loop().claim_pipe_read_scratch(); loop { @@ -1291,7 +1301,7 @@ impl WindowsBufferedReader { unsafe { (*this.cast::()).close() }; } - pub fn start(&mut self, fd: Fd, _: bool) -> sys::Result<()> { + pub fn start(&mut self, fd: Fd, _: IsPollable) -> sys::Result<()> { debug_assert!(self.source.is_none()); // Use the event loop from the parent, not the global one // This is critical for spawnSync to use its isolated loop @@ -1304,7 +1314,12 @@ impl WindowsBufferedReader { self.start_with_current_pipe() } - pub fn start_file_offset(&mut self, fd: Fd, poll: bool, offset: usize) -> sys::Result<()> { + pub fn start_file_offset( + &mut self, + fd: Fd, + poll: IsPollable, + offset: usize, + ) -> sys::Result<()> { self._offset = offset; self.flags.insert(WindowsFlags::USE_PREAD); self.start(fd, poll) @@ -1419,7 +1434,7 @@ impl WindowsBufferedReader { ); // ALWAYS complete the read first (cleans up fs_t, updates state) - file.complete(was_canceled); + file.complete(WasCanceled::from_bool(was_canceled)); if parent_ptr.is_null() { if file.state != crate::source::FileState::Closing { @@ -1553,7 +1568,7 @@ impl WindowsBufferedReader { .to_error(sys::Tag::write) { // SAFETY: see above. - unsafe { (*file_raw).complete(false) }; + unsafe { (*file_raw).complete(WasCanceled::No) }; this.flags.remove(WindowsFlags::HAS_INFLIGHT_READ); this.flags.insert(WindowsFlags::IS_PAUSED); // we should inform the error if we are unable to keep reading @@ -1633,7 +1648,7 @@ impl WindowsBufferedReader { .to_error(sys::Tag::write) { // SAFETY: see above. - unsafe { (*file_raw).complete(false) }; + unsafe { (*file_raw).complete(WasCanceled::No) }; self.flags.remove(WindowsFlags::HAS_INFLIGHT_READ); return sys::Result::Err(err); } diff --git a/src/io/PipeWriter.rs b/src/io/PipeWriter.rs index 074c67937994..464bc3c2975f 100644 --- a/src/io/PipeWriter.rs +++ b/src/io/PipeWriter.rs @@ -16,9 +16,9 @@ use bun_sys::{self as sys, Fd}; use crate::{EventLoopHandle, FilePollFlag, FilePollKind, FilePollRef, Owner, PollTag}; -use crate::pipes::{FileType, PollOrFd}; +use crate::pipes::{CloseFd, FileType, IsPollable, PollOrFd, ReceivedHup}; #[cfg(windows)] -use crate::source::Source; +use crate::source::{Source, WasCanceled}; bun_core::define_scoped_log!(log, PipeWriter, hidden); @@ -119,11 +119,11 @@ pub trait PosixPipeWriter { WriteResult::Wrote(offset) } - fn on_poll(&mut self, size_hint: isize, received_hup: bool) { + fn on_poll(&mut self, size_hint: isize, received_hup: ReceivedHup) { // reshaped for borrowck — capture buffer.len() before further &mut self calls. let buffer_len = self.get_buffer().len(); log!("onPoll({})", buffer_len); - if buffer_len == 0 && !received_hup { + if buffer_len == 0 && received_hup == ReceivedHup::No { let self_addr = std::ptr::from_ref(self).cast::<()>() as usize; log!( "PosixPipeWriter(0x{:x}) handle={}", @@ -180,7 +180,7 @@ pub trait PosixPipeWriter { /// and parents rely on no `on_write` arriving after `on_error`). An error /// is always `Err`: `try_write` reports a short write as `Pending`, never /// as `Wrote`, so an error here means nothing was written this round. - fn drain_buffered_data(&self, max_write_size: usize, received_hup: bool) -> WriteResult { + fn drain_buffered_data(&self, max_write_size: usize, received_hup: ReceivedHup) -> WriteResult { let _ = received_hup; // autofix let buf_len = self.get_buffer().len(); @@ -482,7 +482,7 @@ impl PosixBufferedWriter { Some(parent.cast()), // SAFETY: parent was set via set_parent with a *mut Parent. Some(|ctx: *mut c_void| unsafe { Parent::on_close(ctx.cast::()) }), - self.close_fd, + CloseFd::from_bool(self.close_fd), ); } } @@ -518,10 +518,10 @@ impl PosixBufferedWriter { /// On POSIX a `MovableIfWindowsFd` never transfers ownership, so callers /// pass the plain `Fd` (via `MovableIfWindowsFd::get_posix()` when needed). - pub fn start(&mut self, rawfd: Fd, pollable: bool) -> sys::Result<()> { + pub fn start(&mut self, rawfd: Fd, pollable: IsPollable) -> sys::Result<()> { let fd = rawfd; - self.pollable = pollable; - if !pollable { + self.pollable = pollable == IsPollable::Yes; + if pollable == IsPollable::No { debug_assert!(!matches!(self.handle, PollOrFd::Poll(_))); self.handle = PollOrFd::Fd(fd); return sys::Result::Ok(()); @@ -958,9 +958,9 @@ impl PosixStreamingWriter { let received_hup = 'brk: { if let Some(poll) = self.get_poll() { - break 'brk poll.has_flag(FilePollFlag::Hup); + break 'brk ReceivedHup::from_bool(poll.has_flag(FilePollFlag::Hup)); } - false + ReceivedHup::No }; let rc = self.drain_buffered_data(usize::MAX, received_hup); @@ -1034,8 +1034,8 @@ impl PosixStreamingWriter { ); } - pub fn start(&mut self, fd: Fd, is_pollable: bool) -> sys::Result<()> { - if !is_pollable { + pub fn start(&mut self, fd: Fd, is_pollable: IsPollable) -> sys::Result<()> { + if is_pollable == IsPollable::No { self.close(); self.handle = PollOrFd::Fd(fd); return sys::Result::Ok(()); @@ -1242,7 +1242,7 @@ pub trait BaseWindowsPipeWriter: Sized { self.start_with_current_pipe() } - fn start_sync(&mut self, fd: Fd, _pollable: bool) -> sys::Result<()> { + fn start_sync(&mut self, fd: Fd, _pollable: IsPollable) -> sys::Result<()> { debug_assert!(self.source().is_none()); let mut source = Source::SyncFile(Source::open_file(fd)); source.set_data(core::ptr::from_mut(self).cast::()); @@ -1263,7 +1263,7 @@ pub trait BaseWindowsPipeWriter: Sized { } // TODO: MovableIfWindowsFd overload — add a separate start_movable(). - fn start(&mut self, rawfd: Fd, _pollable: bool) -> sys::Result<()> { + fn start(&mut self, rawfd: Fd, _pollable: IsPollable) -> sys::Result<()> { let fd = rawfd; debug_assert!(self.source().is_none()); // Use the event loop from the parent, not the global one @@ -1590,7 +1590,7 @@ impl WindowsBufferedWriter { // ALWAYS complete first — the boxed `source::File` outlives this // callback (detach()/close() gates free). - file.complete(was_canceled); + file.complete(WasCanceled::from_bool(was_canceled)); // If detached, file may be closing (owned fd) or just stopped (non-owned fd). // The deref to balance write()'s ref was already done in close(). @@ -1694,7 +1694,7 @@ impl WindowsBufferedWriter { } .to_error(sys::Tag::write) { - file.complete(false); + file.complete(WasCanceled::No); self.close(); self.parent_on_error(err); } else { @@ -2167,7 +2167,7 @@ impl WindowsStreamingWriter { // ALWAYS complete first — the boxed `source::File` outlives this // callback (detach()/close() gates free). - file.complete(was_canceled); + file.complete(WasCanceled::from_bool(was_canceled)); // If detached, file may be closing (owned fd) or just stopped (non-owned fd). // The deref to balance processSend's ref was already done in close(). @@ -2317,7 +2317,7 @@ impl WindowsStreamingWriter { } .to_error(sys::Tag::write) { - file.complete(false); + file.complete(WasCanceled::No); Self::r(this).last_write_result = WriteResult::Err(err.clone()); Self::r_on_error(this, err); core::hint::black_box(this); diff --git a/src/io/lib.rs b/src/io/lib.rs index 62e083df2b0c..1764e7c0a2d5 100644 --- a/src/io/lib.rs +++ b/src/io/lib.rs @@ -440,6 +440,7 @@ pub use windows_event_loop::Store; pub use posix_event_loop::Flags as PollFlag; /// Mirrors poll kind enum used by process.rs. pub use posix_event_loop::Flags as PollKind; +pub use posix_event_loop::{ForceUnregister, OneShot}; /// `file_poll` module — real one lives in {posix,windows}_event_loop.rs. pub mod file_poll { @@ -481,7 +482,7 @@ pub mod write; pub use write::{AsFmt, DiscardingWriter, FixedBufferStream, FmtAdapter, IntLe, Result, Write}; pub use max_buf as MaxBuf; -pub use pipes::{Chunk, FileType, ReadState}; +pub use pipes::{Chunk, CloseFd, FileType, IsPollable, ReadState, ReceivedHup}; // `BufferedReader` parent callback dispatch. Each variant's `link_impl_*!` (in // `bun_runtime`/`bun_install`) forwards to that type's `BufferedReaderParent` @@ -664,7 +665,7 @@ pub use source::Source; pub use pipe_reader::{BufferedReader, BufferedReaderParent, PosixFlags}; -pub use open_for_writing_mod::{open_for_writing, open_for_writing_impl}; +pub use open_for_writing_mod::{ForceSync, open_for_writing, open_for_writing_impl}; // ════════════════════════════════════════════════════════════════════════════ @@ -991,7 +992,7 @@ impl IoRequestLoop { Flags::PollReadable, readable.tag, watcher_fd, - true, + OneShot::Yes, readable.fd, ) { Err(err) => { @@ -1005,7 +1006,7 @@ impl IoRequestLoop { Flags::PollWritable, writable.tag, watcher_fd, - true, + OneShot::Yes, writable.fd, ) { Err(err) => { @@ -1701,14 +1702,14 @@ impl Poll { flag: Flags, tag: PollableTag, watcher_fd: Fd, - one_shot: bool, + one_shot: OneShot, fd: Fd, ) -> sys::Result<()> { log!("register: {:?} ({})", flag as u8, fd); debug_assert!(fd != Fd::INVALID); - if one_shot { + if one_shot == OneShot::Yes { self.flags.insert(Flags::OneShot); } @@ -1857,7 +1858,11 @@ impl FilePollRef { unsafe { &mut *loop_ } } #[inline] - pub(crate) fn unregister(self, loop_: *mut bun_uws_sys::Loop, force: bool) -> sys::Result<()> { + pub(crate) fn unregister( + self, + loop_: *mut bun_uws_sys::Loop, + force: ForceUnregister, + ) -> sys::Result<()> { let loop_ = Self::uws_loop_mut(loop_); #[cfg(not(windows))] { diff --git a/src/io/openForWriting.rs b/src/io/openForWriting.rs index ac898e686bea..f0dfb6d28856 100644 --- a/src/io/openForWriting.rs +++ b/src/io/openForWriting.rs @@ -49,6 +49,8 @@ impl OpenForWritingInput for &ZStr { } } +bun_core::bool_enum!(pub ForceSync); + pub fn open_for_writing( dir: Fd, input_path: &P, @@ -56,7 +58,7 @@ pub fn open_for_writing( mode: Mode, pollable: &mut bool, is_socket: &mut bool, - force_sync: bool, + force_sync: ForceSync, out_nonblocking: &mut bool, ctx: C, on_force_sync_or_isa_tty: fn(C), @@ -88,7 +90,7 @@ pub fn open_for_writing_impl( mode: Mode, pollable: &mut bool, is_socket: &mut bool, - force_sync: bool, + force_sync: ForceSync, out_nonblocking: &mut bool, ctx: C, on_force_sync_or_isa_tty: fn(C), @@ -136,13 +138,13 @@ where *is_socket = bun_sys::S::ISSOCK(stat.st_mode as Mode); - if force_sync || isatty { + if force_sync == ForceSync::Yes || isatty { // Prevents interleaved or dropped stdout/stderr output for terminals. // As noted in the following reference, local TTYs tend to be quite fast and // this behavior has become expected due historical functionality on OS X, // even though it was originally intended to change in v1.0.2 (Libuv 1.2.1). // Ref: https://github.com/nodejs/node/pull/1771#issuecomment-119351671 - let _ = bun_sys::update_nonblocking(fd, false); + let _ = bun_sys::update_nonblocking(fd, bun_sys::IoMode::Blocking); is_nonblocking = false; // this.force_sync = true; // this.writer.force_sync = true; @@ -175,7 +177,7 @@ where { *pollable = (bun_sys::windows::GetFileType(fd.native()) & bun_sys::windows::FILE_TYPE_PIPE) != 0 - && !force_sync; + && force_sync == ForceSync::No; return Ok(fd); } } diff --git a/src/io/pipes.rs b/src/io/pipes.rs index e4e12423c942..3150ead330bb 100644 --- a/src/io/pipes.rs +++ b/src/io/pipes.rs @@ -8,6 +8,10 @@ use bun_sys::FdExt; use crate::FilePollFlag; use crate::{FilePollRef, Owner}; +bun_core::bool_enum!(pub IsPollable); +bun_core::bool_enum!(pub ReceivedHup); +bun_core::bool_enum!(pub CloseFd); + pub enum PollOrFd { Poll(FilePollRef), Fd(Fd), @@ -55,7 +59,7 @@ impl PollOrFd { &mut self, ctx: Option<*mut c_void>, on_close_fn: Option, - close_fd: bool, + close_fd: CloseFd, ) where F: FnOnce(*mut c_void), { @@ -104,10 +108,10 @@ impl PollOrFd { } #[cfg(not(windows))] { - if close_async && close_fd { + if close_async && close_fd == CloseFd::Yes { crate::closer::Closer::close(fd, ()); } else { - if close_fd { + if close_fd == CloseFd::Yes { let _ = fd.close_allowing_bad_file_descriptor(None); } } @@ -126,7 +130,7 @@ impl PollOrFd { where F: FnOnce(*mut c_void), { - self.close_impl(ctx, on_close_fn, true); + self.close_impl(ctx, on_close_fn, CloseFd::Yes); } } diff --git a/src/io/posix_event_loop.rs b/src/io/posix_event_loop.rs index cf509017697d..2a7a0d46e67c 100644 --- a/src/io/posix_event_loop.rs +++ b/src/io/posix_event_loop.rs @@ -280,6 +280,8 @@ pub enum AllocatorType { Mini, } +bun_core::bool_enum!(pub ForceUnregister); + // `FilePoll`/`Store` here are POSIX-specific (kqueue/epoll registration, // generation_number, allocator_type). On Windows the variants live in // `windows_event_loop`; the shared `EventLoopCtxVTable` above names @@ -388,15 +390,15 @@ impl FilePoll { // put back via `Store::put`; Drop would be wrong here. pub fn deinit(&mut self) { let ctx = get_vm_ctx(self.allocator_type); - self.deinit_possibly_defer(ctx, false); + self.deinit_possibly_defer(ctx, ForceUnregister::No); } pub(crate) fn deinit_force_unregister(&mut self) { let ctx = get_vm_ctx(self.allocator_type); - self.deinit_possibly_defer(ctx, true); + self.deinit_possibly_defer(ctx, ForceUnregister::Yes); } - fn deinit_possibly_defer(&mut self, vm: EventLoopCtx, force_unregister: bool) { + fn deinit_possibly_defer(&mut self, vm: EventLoopCtx, force_unregister: ForceUnregister) { // `loop_mut()` is the crate-private nonnull-asref accessor (single // deref in `EventLoopCtx`); the `&mut Loop` is consumed by `unregister` // and dropped before any `&mut Store` is materialised. @@ -420,7 +422,7 @@ impl FilePoll { } pub fn deinit_with_vm(&mut self, vm: EventLoopCtx) { - self.deinit_possibly_defer(vm, false); + self.deinit_possibly_defer(vm, ForceUnregister::No); } pub fn is_registered(&self) -> bool { @@ -554,11 +556,16 @@ impl FilePoll { poll } - pub fn register(&mut self, loop_: &mut Loop, flag: Flags, one_shot: bool) -> sys::Result<()> { + pub fn register( + &mut self, + loop_: &mut Loop, + flag: Flags, + one_shot: OneShot, + ) -> sys::Result<()> { self.register_with_fd( loop_, flag, - if one_shot { + if one_shot == OneShot::Yes { OneShotFlag::OneShot } else { OneShotFlag::None @@ -870,7 +877,11 @@ impl FilePoll { sys::Result::Ok(()) } - pub fn unregister(&mut self, loop_: &mut Loop, force_unregister: bool) -> sys::Result<()> { + pub fn unregister( + &mut self, + loop_: &mut Loop, + force_unregister: ForceUnregister, + ) -> sys::Result<()> { self.unregister_with_fd(loop_, self.fd, force_unregister) } @@ -878,7 +889,7 @@ impl FilePoll { &mut self, loop_: &mut Loop, fd: Fd, - force_unregister: bool, + force_unregister: ForceUnregister, ) -> sys::Result<()> { // Note: compute the syscall result first, then unconditionally // deactivate. Avoids a raw-pointer scopeguard. @@ -913,7 +924,7 @@ impl FilePoll { &mut self, loop_: &mut Loop, fd: Fd, - force_unregister: bool, + force_unregister: ForceUnregister, ) -> sys::Result<()> { debug_assert!(fd.native() >= 0 && fd != INVALID_FD); @@ -950,7 +961,7 @@ impl FilePoll { return sys::Result::Ok(()); }; - if self.flags.contains(Flags::NeedsRearm) && !force_unregister { + if self.flags.contains(Flags::NeedsRearm) && force_unregister == ForceUnregister::No { syslog!( "unregister: {} ({}) skipped due to needs_rearm", <&'static str>::from(flag), @@ -1520,6 +1531,8 @@ static TIMEOUT: bun_sys::posix::timespec = bun_sys::posix::timespec { tv_nsec: 0, }; +bun_core::bool_enum!(pub OneShot); + #[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq)] pub enum OneShotFlag { diff --git a/src/io/source.rs b/src/io/source.rs index bb230336c72b..817a11f67426 100644 --- a/src/io/source.rs +++ b/src/io/source.rs @@ -15,6 +15,8 @@ bun_core::declare_scope!(PipeSource, hidden); pub type Pipe = uv::Pipe; pub type Tty = uv::uv_tty_t; +bun_core::bool_enum!(pub(crate) WasCanceled); + pub enum Source { Pipe(Box), /// `BackRef` not `Box`: the stdin tty (fd 0) lives in static storage @@ -190,9 +192,9 @@ impl File { /// Mark the operation as complete and clean up. /// Must be called first in the callback before processing data. - pub(crate) fn complete(&mut self, was_canceled: bool) { + pub(crate) fn complete(&mut self, was_canceled: WasCanceled) { debug_assert!(self.state == FileState::Operating || self.state == FileState::Canceling); - if was_canceled { + if was_canceled == WasCanceled::Yes { debug_assert!(self.state == FileState::Canceling); } @@ -356,7 +358,7 @@ impl Source { bun_core::scoped_log!(PipeSource, "openPipe (fd = {})", fd); let mut pipe: Box = Box::new(bun_core::ffi::zeroed::()); // we should never init using IPC here - if let Some(err) = pipe.init(loop_, false).to_error(bun_sys::Tag::pipe) { + if let Some(err) = pipe.init(loop_, uv::Ipc::No).to_error(bun_sys::Tag::pipe) { drop(pipe); return bun_sys::Result::Err(err); } diff --git a/src/js_parser/fold.rs b/src/js_parser/fold.rs index 911e1e76de30..fb6e8b363039 100644 --- a/src/js_parser/fold.rs +++ b/src/js_parser/fold.rs @@ -2,7 +2,7 @@ use bun_collections::VecExt; use bun_core::feature_flags as FeatureFlags; -use crate::p::P; +use crate::p::{Inverted, P}; use crate::parser::{self as js_parser, IdentifierOpts, RelocateVars, RelocateVarsMode}; use bun_ast::ast_result::CommonJSNamedExport; use bun_ast::{self as js_ast, Binding, E, Expr, Flags, G, LocRef, S}; @@ -494,7 +494,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } js_ast::ExprData::EImportMeta(_) => { if name == b"main" { - return Some(p.value_for_import_meta_main(false, target.loc)); + return Some(p.value_for_import_meta_main(Inverted::No, target.loc)); } if name == b"hot" { diff --git a/src/js_parser/lexer.rs b/src/js_parser/lexer.rs index de866ea8ce77..44eda6424745 100644 --- a/src/js_parser/lexer.rs +++ b/src/js_parser/lexer.rs @@ -298,6 +298,8 @@ pub struct LexerType< pub(crate) all_comments: Vec, } +bun_core::bool_enum!(ForPragma); + // Note: Rust macros must emit complete items; the macro now wraps the // entire `impl { ... }` block instead of just the header. macro_rules! lexer_impl_header { @@ -1821,7 +1823,7 @@ lexer_impl_header! { return Ok(()); } } - self.scan_comment_text(false); + self.scan_comment_text(ForPragma::No); continue; } 0x2A => { @@ -1841,7 +1843,7 @@ lexer_impl_header! { return Ok(()); } } - self.scan_comment_text(true); + self.scan_comment_text(ForPragma::Yes); continue; } _ => { @@ -2193,7 +2195,7 @@ lexer_impl_header! { } } - fn scan_comment_text(&mut self, for_pragma: bool) { + fn scan_comment_text(&mut self, for_pragma: ForPragma) { let text = &self.contents[self.start..self.end]; let has_legal_annotation = text.len() > 2 && text[2] == b'!'; let is_multiline_comment = text.len() > 1 && text[1] == b'*'; @@ -2257,7 +2259,7 @@ lexer_impl_header! { return; } - if !for_pragma { + if for_pragma == ForPragma::No { return; } @@ -2272,7 +2274,7 @@ lexer_impl_header! { let offset = self.scan_pragma( self.start + i + (text.len() - rest.len()), chunk, - false, + AllowNewline::No, ); rest = &rest[ @@ -2406,7 +2408,7 @@ lexer_impl_header! { // Detach via `StoreStr` (arena-owned, lives for parse). let chunk = js_ast::StoreStr::new(self.remaining()); let offset = - self.scan_pragma(pragma_trigger_pos, chunk.slice(), true); + self.scan_pragma(pragma_trigger_pos, chunk.slice(), AllowNewline::Yes); if offset > 0 { // Pragma found (e.g., __PURE__). @@ -2450,7 +2452,7 @@ lexer_impl_header! { &mut self, offset_for_errors: usize, chunk: &[u8], - allow_newline: bool, + allow_newline: AllowNewline, ) -> usize { if !self.has_pure_comment_before { if strings::has_prefix_with_word_boundary(chunk, b"__PURE__") { @@ -3845,6 +3847,8 @@ pub(crate) fn latin1_identifier_continue_length_scalar(name: &[u8]) -> usize { name.len() } +bun_core::bool_enum!(pub(crate) AllowNewline); + pub struct PragmaArg; impl PragmaArg { @@ -3902,7 +3906,7 @@ impl PragmaArg { offset_: usize, pragma: &[u8], text_: &[u8], - allow_newline: bool, + allow_newline: AllowNewline, ) -> Option { let mut text = &text_[pragma.len()..]; let mut iter = CodepointIterator::init(text); @@ -3929,7 +3933,9 @@ impl PragmaArg { let _ = iter.next(&mut cursor); let mut i: usize = 0; - while !is_whitespace(cursor.c) && (!allow_newline || !Self::is_newline(cursor.c)) { + while !is_whitespace(cursor.c) + && (allow_newline == AllowNewline::No || !Self::is_newline(cursor.c)) + { i += cursor.width as usize; if i >= text.len() { break; diff --git a/src/js_parser/lib.rs b/src/js_parser/lib.rs index 6668dce79446..aff5ac17f872 100644 --- a/src/js_parser/lib.rs +++ b/src/js_parser/lib.rs @@ -289,6 +289,10 @@ pub mod defines { pub data: DefineData, } + bun_core::bool_enum!(pub Valueless); + bun_core::bool_enum!(pub CanBeRemovedIfUnused); + bun_core::bool_enum!(pub MethodCallMustBeReplacedWithUndefined); + /// Bit-packed flags (LSB-first): 3 padding bits, `valueless`, /// `can_be_removed_if_unused`, `call_can_be_unwrapped_if_unused` /// (`E.CallUnwrap`, 2 bits), `method_call_must_be_replaced_with_undefined`. @@ -346,17 +350,18 @@ pub mod defines { | ((v as u8) << Self::METHOD_CALL_UNDEF_SHIFT); } pub fn new( - valueless: bool, - can_be_removed_if_unused: bool, + valueless: Valueless, + can_be_removed_if_unused: CanBeRemovedIfUnused, call_can_be_unwrapped_if_unused: E::CallUnwrap, - method_call_must_be_replaced_with_undefined: bool, + method_call_must_be_replaced_with_undefined: MethodCallMustBeReplacedWithUndefined, ) -> Self { let mut f = Flags(0); - f.set_valueless(valueless); - f.set_can_be_removed_if_unused(can_be_removed_if_unused); + f.set_valueless(valueless == Valueless::Yes); + f.set_can_be_removed_if_unused(can_be_removed_if_unused == CanBeRemovedIfUnused::Yes); f.set_call_can_be_unwrapped_if_unused(call_can_be_unwrapped_if_unused); f.set_method_call_must_be_replaced_with_undefined( - method_call_must_be_replaced_with_undefined, + method_call_must_be_replaced_with_undefined + == MethodCallMustBeReplacedWithUndefined::Yes, ); f } @@ -421,10 +426,12 @@ pub mod defines { DefineData { value: options.value, flags: Flags::new( - options.valueless, - options.can_be_removed_if_unused, + Valueless::from_bool(options.valueless), + CanBeRemovedIfUnused::from_bool(options.can_be_removed_if_unused), options.call_can_be_unwrapped_if_unused, - options.method_call_must_be_replaced_with_undefined, + MethodCallMustBeReplacedWithUndefined::from_bool( + options.method_call_must_be_replaced_with_undefined, + ), ), original_name: options.original_name.map(Box::<[u8]>::from), } @@ -482,12 +489,16 @@ pub mod defines { value: b.value, flags: Flags::new( // TODO: investigate if this is correct. This is what it was before. - a.method_call_must_be_replaced_with_undefined() - || b.method_call_must_be_replaced_with_undefined(), - a.can_be_removed_if_unused(), + Valueless::from_bool( + a.method_call_must_be_replaced_with_undefined() + || b.method_call_must_be_replaced_with_undefined(), + ), + CanBeRemovedIfUnused::from_bool(a.can_be_removed_if_unused()), a.call_can_be_unwrapped_if_unused(), - a.method_call_must_be_replaced_with_undefined() - || b.method_call_must_be_replaced_with_undefined(), + MethodCallMustBeReplacedWithUndefined::from_bool( + a.method_call_must_be_replaced_with_undefined() + || b.method_call_must_be_replaced_with_undefined(), + ), ), original_name: b.original_name, } diff --git a/src/js_parser/lower/lower_decorators.rs b/src/js_parser/lower/lower_decorators.rs index 77c2620dac5e..d5629e88b151 100644 --- a/src/js_parser/lower/lower_decorators.rs +++ b/src/js_parser/lower/lower_decorators.rs @@ -139,6 +139,8 @@ fn can_be_class_binding_name(name: &[u8]) -> bool { && !is_eval_or_arguments(name) } +bun_core::bool_enum!(ClassKind { Stmt, Expr }); + // ── impl P ─────────────────────────────────────────────────────────────────── impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_ONLY> { @@ -1078,7 +1080,14 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O js_ast::StmtData::SClass(c) => c, _ => unreachable!(), }; - self.lower_impl(&mut s_class.class, stmt.loc, None, false, Some(stmt), out); + self.lower_impl( + &mut s_class.class, + stmt.loc, + None, + ClassKind::Stmt, + Some(stmt), + out, + ); } pub(crate) fn lower_standard_decorators_expr( @@ -1089,7 +1098,14 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O ) -> Expr { let bump = self.arena; let mut out = BumpVec::::new_in(bump); - self.lower_impl(class, loc, name_from_context, true, None, &mut out); + self.lower_impl( + class, + loc, + name_from_context, + ClassKind::Expr, + None, + &mut out, + ); if out.is_empty() { return self.new_expr(E::Missing {}, loc); } @@ -1107,11 +1123,12 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O class: &mut G::Class, loc: bun_ast::Loc, name_from_context: Option<&'a [u8]>, - is_expr: bool, + is_expr: ClassKind, original_stmt: Option, out: &mut BumpVec<'a, Stmt>, ) { let p = self; + let is_expr = is_expr == ClassKind::Expr; let bump = p.arena; // Receiver-capture temporaries created by `rewrite_private_accesses_in_expr` diff --git a/src/js_parser/lower/lower_esm_exports_hmr.rs b/src/js_parser/lower/lower_esm_exports_hmr.rs index 1299e8b030ba..99b92461c1df 100644 --- a/src/js_parser/lower/lower_esm_exports_hmr.rs +++ b/src/js_parser/lower/lower_esm_exports_hmr.rs @@ -34,6 +34,8 @@ struct DeduplicatedImportResult { pub import_record_index: u32, } +bun_core::bool_enum!(IsLiveBindingSource); + impl<'a> ConvertESMExportsForHmr<'a> { // Note: takes the concrete `P<'p, TS, SCAN>`; `AstBuilder` instead // open-codes the equivalent transform (see bundler/AstBuilder.rs). @@ -283,14 +285,26 @@ impl<'a> ConvertESMExportsForHmr<'a> { st.func.flags.remove(bun_ast::flags::Function::IsExport); - self.visit_ref_to_export(p, st.func.name.unwrap().ref_, None, stmt.loc, false)?; + self.visit_ref_to_export( + p, + st.func.name.unwrap().ref_, + None, + stmt.loc, + IsLiveBindingSource::No, + )?; break 'stmt stmt; } js_ast::StmtData::SExportClause(st) => { for item in st.items.iter() { let ref_ = item.name.ref_; - self.visit_ref_to_export(p, ref_, Some(item.alias), item.name.loc, false)?; + self.visit_ref_to_export( + p, + ref_, + Some(item.alias), + item.name.loc, + IsLiveBindingSource::No, + )?; } return Ok(()); // do not emit a statement here @@ -325,7 +339,7 @@ impl<'a> ConvertESMExportsForHmr<'a> { ref_, Some(item.alias), item.name.loc, - !self.is_in_node_modules, // live binding when this may be replaced + IsLiveBindingSource::from_bool(!self.is_in_node_modules), // live binding when this may be replaced )?; // imports and export statements have their alias + @@ -520,7 +534,7 @@ impl<'a> ConvertESMExportsForHmr<'a> { match binding.data { B::B::BMissing(_) => {} B::B::BIdentifier(id) => { - self.visit_ref_to_export(p, id.r#ref, None, binding.loc, false)?; + self.visit_ref_to_export(p, id.r#ref, None, binding.loc, IsLiveBindingSource::No)?; } B::B::BArray(array) => { for item in array.items.iter() { @@ -542,7 +556,7 @@ impl<'a> ConvertESMExportsForHmr<'a> { ref_: Ref, export_symbol_name: Option, loc: bun_ast::Loc, - is_live_binding_source: bool, + is_live_binding_source: IsLiveBindingSource, ) -> Result<(), AllocError> { let (kind, has_been_assigned_to, original_name) = { let symbol = &p.symbols[ref_.inner_index() as usize]; @@ -563,7 +577,7 @@ impl<'a> ConvertESMExportsForHmr<'a> { } else { Expr::init_identifier(ref_, loc) }; - if is_live_binding_source + if is_live_binding_source == IsLiveBindingSource::Yes || (kind == js_ast::symbol::Kind::Import && !self.is_in_node_modules) || has_been_assigned_to { diff --git a/src/js_parser/p.rs b/src/js_parser/p.rs index 6db4b4882930..98ec0b3753ee 100644 --- a/src/js_parser/p.rs +++ b/src/js_parser/p.rs @@ -191,6 +191,16 @@ pub enum ReactRefreshExportKind { Default, } +bun_core::bool_enum!(pub(crate) IsInternal); +bun_core::bool_enum!(pub(crate) IsSpread); +bun_core::bool_enum!(pub(crate) WasOriginallyBareImport); +bun_core::bool_enum!(pub(crate) IsVar); +bun_core::bool_enum!(pub(crate) IsEnumScope); +bun_core::bool_enum!(pub(crate) Inverted); +bun_core::bool_enum!(IsYesBranch); +bun_core::bool_enum!(pub(crate) AllValuesArePure); +bun_core::bool_enum!(pub(crate) ShouldHoistFns); + // ───────────────────────────────────────────────────────────────────────────── // P — the parser struct. // `'a` covers borrowed init() params (log/define/source) AND the arena (`bump`). @@ -1588,14 +1598,20 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } } - return self.new_expr(E::ImportIdentifier::new(ident.ref_, true), loc); + return self.new_expr( + E::ImportIdentifier::new(ident.ref_, E::WasOriginallyIdentifier::Yes), + loc, + ); } } // Substitute an EImportIdentifier now if this is an import item if self.is_import_item.contains_key(&ref_) { return self.new_expr( - E::ImportIdentifier::new(ref_, opts.was_originally_identifier()), + E::ImportIdentifier::new( + ref_, + E::WasOriginallyIdentifier::from_bool(opts.was_originally_identifier()), + ), loc, ); } @@ -1784,7 +1800,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O symbols: &Sym, additional_stmt: Option, prefix: &'static [u8], - is_internal: bool, + is_internal: IsInternal, tag: bun_ast::PartTag, ) -> Result<(), crate::Error> where @@ -1796,6 +1812,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let import_record_i = self.add_import_record_by_range(ImportKind::Stmt, bun_ast::Range::NONE, import_path); { + let is_internal = is_internal == IsInternal::Yes; let import_record = &mut self.import_records.items_mut()[import_record_i as usize]; if is_internal { import_record.path.namespace = b"runtime"; @@ -3406,7 +3423,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let res = self.convert_expr_to_binding_and_initializer( &mut item, invalid_loc, - is_spread, + IsSpread::from_bool(is_spread), ); items.push(bun_ast::ArrayBinding { @@ -3466,8 +3483,11 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O continue; } let value = item.value.as_mut().unwrap(); - let tup = - self.convert_expr_to_binding_and_initializer(value, invalid_loc, false); + let tup = self.convert_expr_to_binding_and_initializer( + value, + invalid_loc, + IsSpread::No, + ); let initializer = tup.expr.or(item.initializer); let is_spread = item.kind == js_ast::g::PropertyKind::Spread || item.flags.contains(Flags::Property::IsSpread); @@ -3512,7 +3532,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O &mut self, _expr: &mut ExprNodeIndex, invalid_log: &mut LocList, - is_spread: bool, + is_spread: IsSpread, ) -> ExprBindingTuple { let mut initializer: Option = None; // `Expr` is `Copy`; read it by value so the `EBinary` arm can switch @@ -3529,7 +3549,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let bind = self.convert_expr_to_binding(expr, invalid_log); if let Some(initial) = initializer { let equals_range = self.source.range_of_operator_before(initial.loc, b"="); - if is_spread { + if is_spread == IsSpread::Yes { self.log().add_range_error( Some(self.source), equals_range, @@ -3613,7 +3633,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O stmt_: S::Import, path: ParsedPath<'a>, loc: bun_ast::Loc, - was_originally_bare_import: bool, + was_originally_bare_import: WasOriginallyBareImport, ) -> Result { let is_macro = Self::ALLOW_MACROS && (path.is_macro || crate::Macro::is_macro_path(path.text)); @@ -3741,7 +3761,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O .flags .set( bun_ast::ImportRecordFlags::WAS_ORIGINALLY_BARE_IMPORT, - was_originally_bare_import, + was_originally_bare_import == WasOriginallyBareImport::Yes, ); self.import_records.items_mut()[stmt.import_record_index as usize] .flags @@ -4174,13 +4194,15 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O &mut self, decls: &[G::Decl], loop_type: &'static str, - is_var: bool, + is_var: IsVar, ) -> Result<(), crate::Error> { match decls.len() { 0 => {} 1 => { if let Some(value) = &decls[0].value { - if is_var && matches!(decls[0].binding.data, js_ast::b::B::BIdentifier(_)) { + if is_var == IsVar::Yes + && matches!(decls[0].binding.data, js_ast::b::B::BIdentifier(_)) + { // This is a weird special case. Initializers are allowed in "var" // statements with identifier bindings. return Ok(()); @@ -4261,8 +4283,9 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O &mut self, name: &[u8], is_export: bool, - is_enum_scope: bool, + is_enum_scope: IsEnumScope, ) -> js_ast::StoreRef { + let is_enum_scope = is_enum_scope == IsEnumScope::Yes; let map: Option> = 'brk: { // Merge with a sibling namespace from the same scope if let Some(existing_member) = self.current_scope().members.get(name) { @@ -4725,7 +4748,11 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let inner_index = self.allocated_names.len(); debug_assert!(inner_index <= u32::MAX as usize); self.allocated_names.push(name); - Ref::init(inner_index as u32, self.source.index.0, false) + Ref::init( + inner_index as u32, + self.source.index.0, + js_ast::IsSourceContentsSlice::No, + ) } } @@ -5360,7 +5387,12 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } #[inline] - pub(crate) fn value_for_import_meta_main(&mut self, inverted: bool, loc: bun_ast::Loc) -> Expr { + pub(crate) fn value_for_import_meta_main( + &mut self, + inverted: Inverted, + loc: bun_ast::Loc, + ) -> Expr { + let inverted = inverted == Inverted::Yes; if let Some(known) = self.options.import_meta_main_value { return Expr { loc, @@ -5543,10 +5575,16 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } js_ast::ExprData::EIf(ex) => { return self.expr_can_be_removed_if_unused_without_dce_check(&ex.test) - && (self.is_side_effect_free_unbound_identifier_ref(ex.yes, ex.test, true) - || self.expr_can_be_removed_if_unused_without_dce_check(&ex.yes)) - && (self.is_side_effect_free_unbound_identifier_ref(ex.no, ex.test, false) - || self.expr_can_be_removed_if_unused_without_dce_check(&ex.no)); + && (self.is_side_effect_free_unbound_identifier_ref( + ex.yes, + ex.test, + IsYesBranch::Yes, + ) || self.expr_can_be_removed_if_unused_without_dce_check(&ex.yes)) + && (self.is_side_effect_free_unbound_identifier_ref( + ex.no, + ex.test, + IsYesBranch::No, + ) || self.expr_can_be_removed_if_unused_without_dce_check(&ex.no)); } js_ast::ExprData::EArray(ex) => { for item in ex.items.slice() { @@ -5654,16 +5692,20 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Special-case "||" to make sure "typeof x === 'undefined' || x" can be removed js_ast::op::Code::BinLogicalOr => { return self.expr_can_be_removed_if_unused_without_dce_check(&ex.left) - && (self - .is_side_effect_free_unbound_identifier_ref(ex.right, ex.left, false) - || self.expr_can_be_removed_if_unused_without_dce_check(&ex.right)); + && (self.is_side_effect_free_unbound_identifier_ref( + ex.right, + ex.left, + IsYesBranch::No, + ) || self.expr_can_be_removed_if_unused_without_dce_check(&ex.right)); } // Special-case "&&" to make sure "typeof x !== 'undefined' && x" can be removed js_ast::op::Code::BinLogicalAnd => { return self.expr_can_be_removed_if_unused_without_dce_check(&ex.left) - && (self - .is_side_effect_free_unbound_identifier_ref(ex.right, ex.left, true) - || self.expr_can_be_removed_if_unused_without_dce_check(&ex.right)); + && (self.is_side_effect_free_unbound_identifier_ref( + ex.right, + ex.left, + IsYesBranch::Yes, + ) || self.expr_can_be_removed_if_unused_without_dce_check(&ex.right)); } // For "==" and "!=", pretend the operator was actually "===" or "!==". If // we know that we can convert it to "==" or "!=", then we can consider the @@ -5719,7 +5761,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O &mut self, value: Expr, guard_condition: Expr, - is_yes_branch_: bool, + is_yes_branch_: IsYesBranch, ) -> bool { let js_ast::ExprData::EIdentifier(id) = value.data else { return false; @@ -5730,7 +5772,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let js_ast::ExprData::EBinary(binary) = guard_condition.data else { return false; }; - let mut is_yes_branch = is_yes_branch_; + let mut is_yes_branch = is_yes_branch_ == IsYesBranch::Yes; match binary.op { js_ast::op::Code::BinStrictEq @@ -6100,7 +6142,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O original_name_ref: Ref, arg_ref: Ref, stmts_inside_closure: &'a mut [Stmt], - all_values_are_pure: bool, + all_values_are_pure: AllValuesArePure, ) -> Result<(), crate::Error> { let mut name_ref = original_name_ref; @@ -6256,7 +6298,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let closure = self.s( S::SExpr { value: call, - does_not_affect_tree_shaking: all_values_are_pure, + does_not_affect_tree_shaking: all_values_are_pure == AllValuesArePure::Yes, }, stmt_loc, ); @@ -6302,7 +6344,10 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O pub(crate) fn runtime_identifier(&mut self, loc: bun_ast::Loc, name: &'static [u8]) -> Expr { let ref_ = self.runtime_identifier_ref(name); self.record_usage(ref_); - self.new_expr(E::ImportIdentifier::new(ref_, false), loc) + self.new_expr( + E::ImportIdentifier::new(ref_, E::WasOriginallyIdentifier::No), + loc, + ) } pub(crate) fn call_runtime( @@ -7922,7 +7967,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O hashbang: &'a [u8], ) -> Result>, crate::Error> { use crate::lower::lower_esm_exports_hmr::ConvertESMExportsForHmr; - use crate::scan::scan_imports::ImportScanner; + use crate::scan::scan_imports::{ImportScanner, WillTransformToCommonJs}; let arena = self.arena; @@ -7971,7 +8016,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let _ = ImportScanner::scan::( self, part.stmts.slice_mut(), - wrap_mode != WrapMode::None, + WillTransformToCommonJs::from_bool(wrap_mode != WrapMode::None), Some(&mut hmr_transform_ctx), )?; } @@ -7981,7 +8026,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let _ = ImportScanner::scan::( self, last_stmts.slice_mut(), - wrap_mode != WrapMode::None, + WillTransformToCommonJs::from_bool(wrap_mode != WrapMode::None), Some(&mut hmr_transform_ctx), )?; } @@ -8017,7 +8062,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let result = match ImportScanner::scan::( self, part.stmts.slice_mut(), - wrap_mode != WrapMode::None, + WillTransformToCommonJs::from_bool(wrap_mode != WrapMode::None), None, ) { Ok(r) => r, @@ -8958,7 +9003,7 @@ impl LowerUsingDeclarationsContext { &mut self, p: &mut P<'a, T, S_>, stmts: &'a mut [Stmt], - should_hoist_fns: bool, + should_hoist_fns: ShouldHoistFns, ) -> ListManaged<'a, Stmt> { let mut result = BumpVec::new_in(p.arena); let mut exports = BumpVec::::new_in(p.arena); @@ -9001,7 +9046,7 @@ impl LowerUsingDeclarationsContext { continue; } js_ast::StmtData::SFunction(_) => { - if should_hoist_fns { + if should_hoist_fns == ShouldHoistFns::Yes { // Hoist function declarations for cross-file ESM references result.push(stmt); continue; diff --git a/src/js_parser/parse/mod.rs b/src/js_parser/parse/mod.rs index 7b3519a9ba8a..2ca137dce77a 100644 --- a/src/js_parser/parse/mod.rs +++ b/src/js_parser/parse/mod.rs @@ -20,10 +20,10 @@ use bun_core::strings; use bun_ast::LexerLog as _; use crate::lexer::T; -use crate::p::P; +use crate::p::{IsSpread, P}; use crate::parser::{ AwaitOrYield, DeferredArrowArgErrors, DeferredErrors, ExprListLoc, ExprOrLetStmt, - FnOrArrowDataParse, LexicalDecl, LocList, ParenExprOpts, ParseBindingOptions, + FnOrArrowDataParse, IsAsync, LexicalDecl, LocList, ParenExprOpts, ParseBindingOptions, ParseClassOptions, ParseStatementOptions, ParsedPath, PropertyOpts, SkipTypeParameterResult, StmtList, TypeParameterFlag, }; @@ -33,6 +33,8 @@ use bun_ast::op::Level; use bun_ast::{ArrayBinding, StrictModeKind}; use bun_ast::{B, Binding, E, Expr, ExprNodeIndex, ExprNodeList, Flags, G, LocRef, S, Stmt}; +bun_core::bool_enum!(pub(crate) IncludeRaw); + // File-split mixin: Round-C lowered `const JSX: JSXTransformType` → `J: JsxT`, // so this is a direct `impl P` block. @@ -280,7 +282,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O pub(crate) fn parse_template_parts( &mut self, - include_raw: bool, + include_raw: IncludeRaw, ) -> Result<(bun_ast::StoreSlice, bun_ast::Loc), Error> { let p = self; let mut parts = BumpVec::::with_capacity_in(1, p.arena); @@ -297,7 +299,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O tail_loc = p.lexer.loc(); p.lexer.rescan_close_brace_as_template_token()?; - let tail: E::TemplateContents = if !include_raw { + let tail: E::TemplateContents = if include_raw == IncludeRaw::No { E::TemplateContents::Cooked(p.lexer.to_e_string()?) } else { E::TemplateContents::Raw(p.lexer.raw_template_contents().into()) @@ -517,9 +519,9 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // First, try converting the expressions to bindings for i in 0..items.len() { - let mut is_spread = false; + let mut is_spread = IsSpread::No; if let js_ast::expr::Data::ESpread(v) = &items[i].data { - is_spread = true; + is_spread = IsSpread::Yes; let inner = v.value; items[i] = inner; } @@ -1566,7 +1568,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let p = self; // "async function() {}" if !p.lexer.has_newline_before && p.lexer.token == T::TFunction { - return p.parse_fn_expr(async_range.loc, true); + return p.parse_fn_expr(async_range.loc, IsAsync::Yes); } // Check the precedence level to avoid parsing an arrow function in diff --git a/src/js_parser/parse/parse_entry.rs b/src/js_parser/parse/parse_entry.rs index 7fb8ce6548e2..86154b62afb3 100644 --- a/src/js_parser/parse/parse_entry.rs +++ b/src/js_parser/parse/parse_entry.rs @@ -13,7 +13,7 @@ use bun_ast::import_record::{Flags as ImportRecordFlags, ImportRecord}; use crate::defines::Define; use crate::lexer as js_lexer; -use crate::p::P; +use crate::p::{IsInternal, P}; use crate::parser::{ Jest, ParseStatementOptions, RuntimeFeatures, RuntimeImports, ScanPassResult, StatementScope, WrapMode, @@ -340,6 +340,8 @@ impl<'a> Parser<'a> { // append_part, to_ast, …}` (gated in P.rs); the full ported body is preserved // per-method-gated in the impl block below and replaces this stub once that // surface lands. +bun_core::bool_enum!(HasHashbang); + impl<'a> Parser<'a> { #[cfg_attr(not(target_arch = "wasm32"), allow(unused_mut))] pub fn parse(mut self) -> Result, Error> { @@ -785,7 +787,10 @@ impl<'a> Parser<'a> { // Detect a leading "// @bun" pragma if p.options.features.dont_bundle_twice { - if let Some(pragma) = Self::has_bun_pragma(&source.contents, !hashbang.is_empty()) { + if let Some(pragma) = Self::has_bun_pragma( + &source.contents, + HasHashbang::from_bool(!hashbang.is_empty()), + ) { return Ok(crate::Result::AlreadyBundled(pragma)); } } @@ -878,7 +883,9 @@ impl<'a> Parser<'a> { .then(|| "ssr".to_owned()), ..Default::default() }; - let opt_out = bun_react_compiler::has_module_scope_opt_out(stmts); + let opt_out = bun_react_compiler::ModuleScopeOptOut::from_bool( + bun_react_compiler::has_module_scope_opt_out(stmts), + ); let import_bindings = bun_react_compiler::collect_import_bindings( stmts, p.import_records.items(), @@ -2042,7 +2049,7 @@ impl<'a> Parser<'a> { &symbols, None, b"import_", - true, + IsInternal::Yes, js_ast::PartTag::Runtime, ) .expect("unreachable"); @@ -2075,7 +2082,7 @@ impl<'a> Parser<'a> { &jsx_imports, None, b"", - false, + IsInternal::No, js_ast::PartTag::JsxImport, ) .expect("unreachable"); @@ -2090,7 +2097,7 @@ impl<'a> Parser<'a> { &jsx_imports, None, b"", - false, + IsInternal::No, js_ast::PartTag::JsxImport, ) .expect("unreachable"); @@ -2249,7 +2256,7 @@ impl<'a> Parser<'a> { // because `_parse` consumes `self` by value and destructures it before this // call site; the source contents are passed explicitly. // called from gated `_parse` body above - fn has_bun_pragma(contents: &[u8], has_hashbang: bool) -> Option { + fn has_bun_pragma(contents: &[u8], has_hashbang: HasHashbang) -> Option { const BUN_PRAGMA: &[u8] = b"// @bun"; let end = contents.len(); @@ -2261,7 +2268,7 @@ impl<'a> Parser<'a> { // const myCode = 1; // ``` let mut cursor: usize = 0; - if has_hashbang { + if has_hashbang == HasHashbang::Yes { while contents[cursor] != b'\n' { cursor += 1; if cursor >= end { diff --git a/src/js_parser/parse/parse_fn.rs b/src/js_parser/parse/parse_fn.rs index 084b395075b0..0774334254aa 100644 --- a/src/js_parser/parse/parse_fn.rs +++ b/src/js_parser/parse/parse_fn.rs @@ -5,8 +5,8 @@ use crate::js_lexer; use crate::js_lexer::T; use crate::p::P; use crate::parser::{ - ARGUMENTS_STR as arguments_str, AwaitOrYield, FnOrArrowDataParse, FunctionKind, LexicalDecl, - ParseStatementOptions, TypeParameterFlag, + ARGUMENTS_STR as arguments_str, AwaitOrYield, FnOrArrowDataParse, FunctionKind, IsAsync, + LexicalDecl, ParseStatementOptions, TypeParameterFlag, }; use bun_ast as js_ast; use bun_ast::op::Level; @@ -418,9 +418,10 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O pub(crate) fn parse_fn_expr( &mut self, loc: bun_ast::Loc, - is_async: bool, + is_async: IsAsync, ) -> Result { let p = self; + let is_async = is_async == IsAsync::Yes; p.lexer.next()?; let is_generator = p.lexer.token == T::TAsterisk; if is_generator { diff --git a/src/js_parser/parse/parse_prefix.rs b/src/js_parser/parse/parse_prefix.rs index bc5d24b92166..1ec0c9e530dc 100644 --- a/src/js_parser/parse/parse_prefix.rs +++ b/src/js_parser/parse/parse_prefix.rs @@ -4,9 +4,11 @@ use bun_collections::VecExt; use crate::lexer::T; use crate::p::P; +use crate::parse::IncludeRaw; use crate::parser::{ - AsyncPrefixExpression, AwaitOrYield, DeferredErrors, FnOrArrowDataParse, ParenExprOpts, - ParseClassOptions, PropertyOpts, SkipTypeParameterResult, TypeParameterFlag, prefill, + AsyncPrefixExpression, AwaitOrYield, DeferredErrors, FnOrArrowDataParse, IsAsync, + ParenExprOpts, ParseClassOptions, PropertyOpts, SkipTypeParameterResult, TypeParameterFlag, + prefill, }; use bun_ast::e::UnaryFlags; use bun_ast::expr::EFlags; @@ -282,7 +284,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let loc = p.lexer.loc(); let head = p.lexer.to_e_string()?; - let (parts, _tail_loc) = p.parse_template_parts(false)?; + let (parts, _tail_loc) = p.parse_template_parts(IncludeRaw::No)?; // Check if TemplateLiteral is unsupported. We don't care for this product.` // if () @@ -529,7 +531,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O #[inline] fn pfx_t_function(p: &mut Self) -> PResult { let loc = p.lexer.loc(); - p.parse_fn_expr(loc, false) + p.parse_fn_expr(loc, IsAsync::No) } fn pfx_t_class(p: &mut Self) -> PResult { diff --git a/src/js_parser/parse/parse_stmt.rs b/src/js_parser/parse/parse_stmt.rs index 67fb248ab74f..a1d2bc0f91a0 100644 --- a/src/js_parser/parse/parse_stmt.rs +++ b/src/js_parser/parse/parse_stmt.rs @@ -4,7 +4,7 @@ use bun_collections::VecExt; use bun_core; use crate::lexer as js_lexer; -use crate::p::P; +use crate::p::{IsVar, P, WasOriginallyBareImport}; use bun_ast as js_ast; use js_ast::op::Level; @@ -546,11 +546,11 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // arena outlives this fn, so the lifetime-erased view remains valid. let mut decls_ptr: bun_ast::StoreSlice = bun_ast::StoreSlice::EMPTY; let init_loc = p.lexer.loc(); - let mut is_var = false; + let mut is_var = IsVar::No; match p.lexer.token { // for (var ) T::TVar => { - is_var = true; + is_var = IsVar::Yes; p.lexer.next()?; let mut stmt_opts = ParseStatementOptions::default(); let decls = @@ -652,7 +652,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } } - p.forbid_initializers(decls_ptr.slice(), "of", false)?; + p.forbid_initializers(decls_ptr.slice(), "of", IsVar::No)?; p.lexer.next()?; let value = p.parse_expr(Level::Comma)?; p.lexer.expect(T::TCloseParen)?; @@ -1535,7 +1535,12 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let path = p.parse_path()?; p.lexer.expect_or_insert_semicolon()?; - return p.process_import_statement(stmt, path, loc, false); + return p.process_import_statement( + stmt, + path, + loc, + WasOriginallyBareImport::No, + ); } if Self::IS_TYPESCRIPT_ENABLED { @@ -1644,7 +1649,12 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let path = p.parse_path()?; p.lexer.expect_or_insert_semicolon()?; - p.process_import_statement(stmt, path, loc, was_originally_bare_import) + p.process_import_statement( + stmt, + path, + loc, + WasOriginallyBareImport::from_bool(was_originally_bare_import), + ) } /// Out-of-line tail for the (uncommon) `label: stmt` form reached from diff --git a/src/js_parser/parse/parse_suffix.rs b/src/js_parser/parse/parse_suffix.rs index 36825b386133..207dfeda5b2c 100644 --- a/src/js_parser/parse/parse_suffix.rs +++ b/src/js_parser/parse/parse_suffix.rs @@ -4,6 +4,7 @@ use crate::Error; use crate::lexer::T; use crate::p::P; +use crate::parse::IncludeRaw; use crate::parser::DeferredErrors; use crate::scan::scan_side_effects::SideEffects; use bun_ast::expr::EFlags; @@ -311,7 +312,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } // p.markSyntaxFeature(compat.TemplateLiteral, p.lexer.Range()); let head = E::Str::new(p.lexer.raw_template_contents()); - let (parts, _tail_loc) = p.parse_template_parts(true)?; + let (parts, _tail_loc) = p.parse_template_parts(IncludeRaw::Yes)?; let tag = *left; let loc = left.loc; *left = p.new_expr( diff --git a/src/js_parser/parse/parse_typescript.rs b/src/js_parser/parse/parse_typescript.rs index 62bc1c41df2d..3e04b27663e2 100644 --- a/src/js_parser/parse/parse_typescript.rs +++ b/src/js_parser/parse/parse_typescript.rs @@ -3,7 +3,7 @@ use bun_collections::VecExt; use crate::Error; use crate::lexer::{self as js_lexer, T}; -use crate::p::P; +use crate::p::{IsEnumScope, P}; use crate::parser::{FnOrArrowDataParse, ParseStatementOptions, Ref, ScopeOrder, StatementScope}; use bun_alloc::{ArenaVec as BumpVec, ArenaVecExt as _}; use bun_ast::expr::EFlags; @@ -215,7 +215,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Generate the namespace object // Arena-owned `StoreRef`. let mut ts_namespace: js_ast::StoreRef = - p.get_or_create_exported_namespace_members(name_text, opts.is_export, false); + p.get_or_create_exported_namespace_members(name_text, opts.is_export, IsEnumScope::No); let mut exported_members: js_ast::StoreRef = ts_namespace.exported_members; let ns_member_data = TSNamespaceMemberData::Namespace(exported_members); @@ -588,7 +588,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Generate the namespace object let mut arg_ref: Ref = Ref::NONE; let mut ts_namespace: js_ast::StoreRef = - p.get_or_create_exported_namespace_members(name_text, opts.is_export, true); + p.get_or_create_exported_namespace_members(name_text, opts.is_export, IsEnumScope::Yes); let mut exported_members: js_ast::StoreRef = ts_namespace.exported_members; diff --git a/src/js_parser/parser.rs b/src/js_parser/parser.rs index 8be190092052..44bd8ab02044 100644 --- a/src/js_parser/parser.rs +++ b/src/js_parser/parser.rs @@ -860,6 +860,8 @@ pub enum FunctionKind { Expr, } +bun_core::bool_enum!(pub(crate) IsAsync); + #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq)] pub(crate) enum AsyncPrefixExpression { diff --git a/src/js_parser/react_compiler_host.rs b/src/js_parser/react_compiler_host.rs index 161ba27d2fc0..04587639ca26 100644 --- a/src/js_parser/react_compiler_host.rs +++ b/src/js_parser/react_compiler_host.rs @@ -97,12 +97,11 @@ impl<'a, const TS: bool, const SCAN_ONLY: bool> bun_react_compiler::Host ref_ } - fn runtime_sentinel(&mut self, early: bool) -> js_ast::Ref { + fn runtime_sentinel(&mut self, early: bun_react_compiler::RuntimeSentinel) -> js_ast::Ref { let p = &mut *self.p; - let name: &'static [u8] = if early { - b"__EARLY_RETURN_SENTINEL" - } else { - b"__MEMO_CACHE_SENTINEL" + let name: &'static [u8] = match early { + bun_react_compiler::RuntimeSentinel::EarlyReturn => b"__EARLY_RETURN_SENTINEL", + bun_react_compiler::RuntimeSentinel::MemoCache => b"__MEMO_CACHE_SENTINEL", }; p.runtime_identifier_ref(name) } diff --git a/src/js_parser/repl_transforms.rs b/src/js_parser/repl_transforms.rs index 06fcfb00fd57..c7d0db93cc79 100644 --- a/src/js_parser/repl_transforms.rs +++ b/src/js_parser/repl_transforms.rs @@ -15,6 +15,7 @@ use bun_ast::stmt::Data as StmtData; use bun_ast::{B, Binding, E, Expr, ExprNodeList, G, S, Stmt}; use crate::p::P; +use crate::parser::IsAsync; impl<'a, const TS: bool, const SCAN: bool> P<'a, TS, SCAN> { /// Apply REPL-mode transforms to the AST. @@ -80,17 +81,22 @@ impl<'a, const TS: bool, const SCAN: bool> P<'a, TS, SCAN> { } // Apply transform with is_async based on presence of top-level await - self.repl_transform_with_hoisting(parts, all_stmts, bump, has_top_level_await) + self.repl_transform_with_hoisting( + parts, + all_stmts, + bump, + IsAsync::from_bool(has_top_level_await), + ) } /// Transform code with hoisting and IIFE wrapper - /// `is_async`: true for async IIFE (when top-level await present), false for sync IIFE + /// `IsAsync::Yes` for async IIFE (when top-level await present), `IsAsync::No` for sync IIFE fn repl_transform_with_hoisting<'bump>( &mut self, parts: &mut BumpVec<'bump, js_ast::Part>, all_stmts: &[Stmt], bump: &'bump Bump, - is_async: bool, + is_async: IsAsync, ) -> Result<(), bun_alloc::AllocError> { if all_stmts.is_empty() { return Ok(()); @@ -495,7 +501,7 @@ impl<'a, const TS: bool, const SCAN: bool> P<'a, TS, SCAN> { loc: bun_ast::Loc::EMPTY, stmts: bun_ast::StoreSlice::new_mut(inner_slice), }, - is_async, + is_async: is_async == IsAsync::Yes, ..Default::default() }, bun_ast::Loc::EMPTY, diff --git a/src/js_parser/scan/scan_imports.rs b/src/js_parser/scan/scan_imports.rs index b34a49c67754..4e58afd3ba7d 100644 --- a/src/js_parser/scan/scan_imports.rs +++ b/src/js_parser/scan/scan_imports.rs @@ -25,6 +25,8 @@ fn raw_str(s: &'static [u8]) -> js_ast::StoreStr { js_ast::StoreStr::new(s) } +bun_core::bool_enum!(pub(crate) WillTransformToCommonJs); + impl<'a> ImportScanner<'a> { // Only the parser P is handled here — the bundler scans imports via its own // path and does not go through this function. @@ -36,7 +38,7 @@ impl<'a> ImportScanner<'a> { >( p: &mut P<'p, TYPESCRIPT, SCAN_ONLY>, stmts: &'a mut [Stmt], - will_transform_to_common_js: bool, + will_transform_to_common_js: WillTransformToCommonJs, // Const generics can't gate a param type on a const, so use Option and // debug-assert presence matches the const. mut hot_module_reloading_context: Option<&mut ConvertESMExportsForHmr>, @@ -687,7 +689,9 @@ impl<'a> ImportScanner<'a> { // exports.default = // But only if it's anonymous // This monomorphization is the parser `P` only (see fn-level TODO). - if !HOT_MODULE_RELOADING_TRANSFORMATIONS && will_transform_to_common_js { + if !HOT_MODULE_RELOADING_TRANSFORMATIONS + && will_transform_to_common_js == WillTransformToCommonJs::Yes + { let expr = core::mem::take(&mut st.value).to_expr(); // Arena allocation that persists in the AST. let export_default_args = p.arena.alloc_slice_fill_default::(2); diff --git a/src/js_parser/visit/mod.rs b/src/js_parser/visit/mod.rs index 685d5e1ef7a5..4c16b33eb662 100644 --- a/src/js_parser/visit/mod.rs +++ b/src/js_parser/visit/mod.rs @@ -7,7 +7,7 @@ pub(crate) mod visit_expr; pub(crate) mod visit_stmt; use crate::lexer as js_lexer; -use crate::p::{LowerUsingDeclarationsContext, P}; +use crate::p::{LowerUsingDeclarationsContext, P, ShouldHoistFns}; use crate::parser::{ ExprIn, FnOnlyDataVisit, FnOrArrowDataVisit, ImportItemForNamespaceMap, PrependTempRefsOpts, Ref, RelocateVarsMode, ScopeOrder, StmtsKind, StrictModeFeature, StringVoidMap, VisitArgsOpts, @@ -37,6 +37,8 @@ use core::ptr::NonNull; // In the AST crate, ListManaged is arena-backed. type ListManaged<'bump, T> = BumpVec<'bump, T>; +bun_core::bool_enum!(pub(crate) IsInOrOf); + impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_ONLY> { // Thin alias of `current_scope_mut()` kept for local readability. #[inline(always)] @@ -562,10 +564,10 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } } - pub(crate) fn visit_for_loop_init(&mut self, stmt: Stmt, is_in_or_of: bool) -> Stmt { + pub(crate) fn visit_for_loop_init(&mut self, stmt: Stmt, is_in_or_of: IsInOrOf) -> Stmt { match stmt.data { StmtData::SExpr(mut st) => { - let assign_target = if is_in_or_of { + let assign_target = if is_in_or_of == IsInOrOf::Yes { AssignTarget::Replace } else { AssignTarget::None @@ -1562,7 +1564,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let raw = core::mem::replace(stmts, ListManaged::new_in(arena)).into_bump_slice_mut(); // SAFETY: current_scope is a valid arena ptr for the parse. let parent_is_none = p.current_scope().parent.is_none(); - *stmts = ctx.finalize(p, raw, parent_is_none); + *stmts = ctx.finalize(p, raw, ShouldHoistFns::from_bool(parent_is_none)); } #[cfg(debug_assertions)] diff --git a/src/js_parser/visit/visit_binary.rs b/src/js_parser/visit/visit_binary.rs index e86ba8da5398..053485081849 100644 --- a/src/js_parser/visit/visit_binary.rs +++ b/src/js_parser/visit/visit_binary.rs @@ -2,7 +2,7 @@ use bun_collections::VecExt; use core::cmp::Ordering; -use crate::p::P; +use crate::p::{Inverted, P}; use crate::parser::{ExprIn, float_to_int32, prefill}; use crate::scan::scan_side_effects::SideEffects; use bun_ast::fold_string_addition::{FoldStringAdditionKind, fold_string_addition}; @@ -239,7 +239,7 @@ impl BinaryExpressionVisitor { Equality::RequireMainAndModule => { p.ignore_usage_of_runtime_require(); p.ignore_usage(p.module_ref); - return p.value_for_import_meta_main(false, v.loc); + return p.value_for_import_meta_main(Inverted::No, v.loc); } Equality::Equal => return p.new_expr(E::Boolean { value: true }, v.loc), Equality::NotEqual => return p.new_expr(E::Boolean { value: false }, v.loc), @@ -269,7 +269,7 @@ impl BinaryExpressionVisitor { Equality::RequireMainAndModule => { p.ignore_usage(p.module_ref); p.ignore_usage_of_runtime_require(); - return p.value_for_import_meta_main(false, v.loc); + return p.value_for_import_meta_main(Inverted::No, v.loc); } Equality::Equal => return p.new_expr(E::Boolean { value: true }, v.loc), Equality::NotEqual => return p.new_expr(E::Boolean { value: false }, v.loc), @@ -292,7 +292,7 @@ impl BinaryExpressionVisitor { Equality::RequireMainAndModule => { p.ignore_usage(p.module_ref); p.ignore_usage_of_runtime_require(); - return p.value_for_import_meta_main(true, v.loc); + return p.value_for_import_meta_main(Inverted::Yes, v.loc); } Equality::Equal => return p.new_expr(E::Boolean { value: false }, v.loc), Equality::NotEqual => return p.new_expr(E::Boolean { value: true }, v.loc), @@ -319,7 +319,7 @@ impl BinaryExpressionVisitor { Equality::RequireMainAndModule => { p.ignore_usage(p.module_ref); p.ignore_usage_of_runtime_require(); - return p.value_for_import_meta_main(true, v.loc); + return p.value_for_import_meta_main(Inverted::Yes, v.loc); } Equality::Equal => return p.new_expr(E::Boolean { value: false }, v.loc), Equality::NotEqual => return p.new_expr(E::Boolean { value: true }, v.loc), diff --git a/src/js_parser/visit/visit_stmt.rs b/src/js_parser/visit/visit_stmt.rs index dfce8bec1923..663cfe8b6c7d 100644 --- a/src/js_parser/visit/visit_stmt.rs +++ b/src/js_parser/visit/visit_stmt.rs @@ -1,11 +1,12 @@ #![warn(unused_must_use)] use crate::Error; use crate::lexer as js_lexer; -use crate::p::{P, ReactRefreshExportKind}; +use crate::p::{AllValuesArePure, P, ReactRefreshExportKind, ShouldHoistFns}; use crate::parser::{ PrependTempRefsOpts, ReactRefresh, Ref, RelocateVarsMode, SideEffects, StmtsKind, statement_cares_about_scope, }; +use crate::visit::IsInOrOf; use bun_alloc::{ArenaVec as BumpVec, ArenaVecExt as _}; use bun_ast::flags; use bun_ast::stmt::Data as StmtData; @@ -1830,7 +1831,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O .expect("unreachable"); if let Some(initst) = data.init { - data.init = Some(p.visit_for_loop_init(initst, false)); + data.init = Some(p.visit_for_loop_init(initst, IsInOrOf::No)); } if let Some(mut test) = data.test { @@ -1881,7 +1882,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O { p.push_scope_for_visit_pass(js_ast::scope::Kind::Block, stmt.loc) .expect("unreachable"); - let _ = p.visit_for_loop_init(data.init, true); + let _ = p.visit_for_loop_init(data.init, IsInOrOf::Yes); p.visit_expr(&mut data.value); data.body = p.visit_loop_body(data.body); @@ -1928,7 +1929,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O ) -> Result<(), Error> { p.push_scope_for_visit_pass(js_ast::scope::Kind::Block, stmt.loc) .expect("unreachable"); - let _ = p.visit_for_loop_init(data.init, true); + let _ = p.visit_for_loop_init(data.init, IsInOrOf::Yes); p.visit_expr(&mut data.value); data.body = p.visit_loop_body(data.body); @@ -2001,8 +2002,10 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let visited_stmts = ctx.finalize( p, stmts_slice, - p.will_wrap_module_in_try_catch_for_using - && p.current_scope().parent.is_none(), + ShouldHoistFns::from_bool( + p.will_wrap_module_in_try_catch_for_using + && p.current_scope().parent.is_none(), + ), ); if let StmtData::SBlock(mut b) = data.body.data { b.stmts = list_to_stmts(visited_stmts); @@ -2120,7 +2123,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O ctx.scan_stmts(p, cases[i].body.slice_mut()); } let switch_stmt = p.arena.alloc_slice_copy(&[*stmt]); - stmts.extend_from_slice(&ctx.finalize(p, switch_stmt, false)); + stmts.extend_from_slice(&ctx.finalize(p, switch_stmt, ShouldHoistFns::No)); } p.pop_scope(); @@ -2177,7 +2180,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let mut value_exprs: BumpVec<'a, Expr> = BumpVec::with_capacity_in(values.len(), p.arena); - let mut all_values_are_pure = true; + let mut all_values_are_pure = AllValuesArePure::Yes; // ts_namespace is set for the enum scope (push_scope_for_visit_pass populated it // during the parse pass); exported_members is an arena-backed `StoreRef`. @@ -2235,7 +2238,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } if !p.expr_can_be_removed_if_unused(&visited) { - all_values_are_pure = false; + all_values_are_pure = AllValuesArePure::No; } } } @@ -2386,7 +2389,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O data.name.ref_, data.arg, prepend_list.into_bump_slice_mut(), - false, + AllValuesArePure::No, )?; Ok(()) } diff --git a/src/js_parser_jsc/Macro.rs b/src/js_parser_jsc/Macro.rs index 279cb1d6ccab..53b25cda6afe 100644 --- a/src/js_parser_jsc/Macro.rs +++ b/src/js_parser_jsc/Macro.rs @@ -22,7 +22,8 @@ use bun_resolver::package_json::{ use crate::expr_jsc::ExprJsc; use bun_jsc::js_property_iterator::JSPropertyIteratorOptions; use bun_jsc::virtual_machine::{ - InitOptions as VirtualMachineInitOptions, MacroModeGuard, VirtualMachine, runtime_hooks, + InitOptions as VirtualMachineInitOptions, IsRejection, MacroModeGuard, VirtualMachine, + runtime_hooks, }; use bun_jsc::{ self as jsc, ConsoleObject, JSArrayIterator, JSGlobalObject, JSPropertyIterator, JSValue, @@ -427,7 +428,7 @@ impl Macro { // CLI-path macro VM uses the caller's log sink and env loader. // JSC needs to be initialized if building from CLI - jsc::initialize(false); + jsc::initialize(jsc::EvalMode::No); let _vm = VirtualMachine::init(VirtualMachineInitOptions { log: Some(NonNull::from(&mut *log)), @@ -626,8 +627,9 @@ impl<'a> Run<'a> { match tag { T::Error => { // SAFETY: `vm()` is the per-thread VM; uniquely accessed here. - let _ = - unsafe { (*self.macro_.vm()).uncaught_exception(self.global, value, false) }; + let _ = unsafe { + (*self.macro_.vm()).uncaught_exception(self.global, value, IsRejection::No) + }; return Ok(self.caller); } T::Undefined => { @@ -663,7 +665,11 @@ impl<'a> Run<'a> { { // SAFETY: `vm()` is the per-thread VM; uniquely accessed here. let _ = unsafe { - (*self.macro_.vm()).uncaught_exception(self.global, value, false) + (*self.macro_.vm()).uncaught_exception( + self.global, + value, + IsRejection::No, + ) }; return Err(MacroError::MacroFailed); } @@ -1093,7 +1099,7 @@ fn expr_from_blob( if is_text_like { let mut output = bun_core::MutableString::init_empty(); - bun_core::quote_for_json(bytes, &mut output, true)?; + bun_core::quote_for_json(bytes, &mut output, bun_core::printer::AsciiOnly::Yes)?; let owned = output.to_owned_slice(); // strip the surrounding quotes; copy into the bump arena so the // `E.String` data outlives `owned`. diff --git a/src/js_printer/lib.rs b/src/js_printer/lib.rs index a465407cf3f4..34ed8dfad537 100644 --- a/src/js_printer/lib.rs +++ b/src/js_printer/lib.rs @@ -693,6 +693,7 @@ pub mod analyze_transpiled_module { pub type RuntimeTranspilerCacheRef = core::ptr::NonNull; use bun_core::fmt::hex2_upper; // remaining `\xHH` site below +pub use bun_core::printer::{AsciiOnly, Json}; use bun_core::printer::{ FIRST_ASCII, FIRST_HIGH_SURROGATE, LAST_ASCII, LAST_LOW_SURROGATE, bmp_escape, surrogate_pair_escape, @@ -707,7 +708,7 @@ const ASCII_ONLY_ALWAYS_ON_UNLESS_MINIFYING: bool = true; // single monomorphization instead of one per (ascii_only × quote_char × …) combo — // see the comment on `write_pre_quoted_string`. #[inline] -pub(crate) fn can_print_without_escape(c: i32, ascii_only: bool) -> bool { +pub(crate) fn can_print_without_escape(c: i32, ascii_only: AsciiOnly) -> bool { if c <= LAST_ASCII as i32 { c >= FIRST_ASCII as i32 && c != i32::from(b'\\') @@ -716,7 +717,7 @@ pub(crate) fn can_print_without_escape(c: i32, ascii_only: bool) -> bool { && c != i32::from(b'`') && c != i32::from(b'$') } else { - !ascii_only + ascii_only == AsciiOnly::No && c != 0xFEFF && c != 0x2028 && c != 0x2029 @@ -727,7 +728,9 @@ pub(crate) fn can_print_without_escape(c: i32, ascii_only: bool) -> bool { const INDENTATION_SPACE_BUF: [u8; 128] = [b' '; 128]; const INDENTATION_TAB_BUF: [u8; 128] = [b'\t'; 128]; -pub(crate) fn best_quote_char_for_string(str: &[T], allow_backtick: bool) -> u8 +bun_core::bool_enum!(pub(crate) AllowBacktick); + +pub(crate) fn best_quote_char_for_string(str: &[T], allow_backtick: AllowBacktick) -> u8 where T: Copy + Into, { @@ -759,7 +762,7 @@ where i += 1; } - if allow_backtick && backtick_cost < single_cost.min(double_cost) { + if allow_backtick == AllowBacktick::Yes && backtick_cost < single_cost.min(double_cost) { return b'`'; } if single_cost < double_cost { @@ -846,7 +849,13 @@ pub fn write_pre_quoted_string< where W: Write + ?Sized, { - write_pre_quoted_string_inner::(text_in, writer, QUOTE_CHAR, ASCII_ONLY, JSON) + write_pre_quoted_string_inner::( + text_in, + writer, + QUOTE_CHAR, + AsciiOnly::from_bool(ASCII_ONLY), + Json::from_bool(JSON), + ) } /// `quote_char` / `ascii_only` / `json` are runtime args (were `const`): the @@ -859,12 +868,13 @@ pub fn write_pre_quoted_string_inner( text_in: &[u8], writer: &mut W, quote_char: u8, - ascii_only: bool, - json: bool, + ascii_only: AsciiOnly, + json: Json, ) -> crate::Result<()> where W: Write + ?Sized, { + let json = json == Json::Yes; debug_assert!( !(json && quote_char != b'"'), "for json, quote_char must be '\"'" @@ -1051,7 +1061,7 @@ where pub fn quote_for_json( text: &[u8], bytes: &mut MutableString, - ascii_only: bool, + ascii_only: AsciiOnly, ) -> crate::Result<()> { // `ascii_only` is threaded at runtime so // the heavy escaper isn't monomorphized per ascii_only/quote-char combo. @@ -1065,7 +1075,13 @@ pub fn quote_for_json( // source. The writer still grows on demand if this under-shoots. bytes.grow_if_needed(text.len() + (text.len() >> 3) + 8)?; bytes.append_char(b'"')?; - write_pre_quoted_string_inner::<_, { Encoding::Utf8 }>(text, bytes, b'"', ascii_only, true)?; + write_pre_quoted_string_inner::<_, { Encoding::Utf8 }>( + text, + bytes, + b'"', + ascii_only, + Json::Yes, + )?; bytes.append_char(b'"').expect("unreachable"); Ok(()) } @@ -1075,7 +1091,7 @@ pub fn write_json_string( writer: &mut W, ) -> crate::Result<()> { writer.write_all(b"\"")?; - write_pre_quoted_string_inner::<_, ENCODING>(input, writer, b'"', false, true)?; + write_pre_quoted_string_inner::<_, ENCODING>(input, writer, b'"', AsciiOnly::No, Json::Yes)?; writer.write_all(b"\"")?; Ok(()) } @@ -1197,7 +1213,7 @@ impl<'a> Options<'a> { pub(crate) fn require_or_import_meta_for_source( &self, id: u32, - was_unwrapped_require: bool, + was_unwrapped_require: WasUnwrappedRequire, ) -> RequireOrImportMeta { if self .require_or_import_meta_for_source_callback @@ -1250,11 +1266,16 @@ impl<'a> Default for Options<'a> { use bun_ast::{Indentation, IndentationCharacter}; +bun_core::bool_enum!(pub IsExport); + +bun_core::bool_enum!(pub(crate) HasRestArg); +bun_core::bool_enum!(pub(crate) IsArrow); + // `is_export` gates whether printing a binding also records an export entry // in ModuleInfo; dead-code elimination drops it when MAY_HAVE_MODULE_INFO is false. #[derive(Clone, Copy, Default)] pub struct TopLevelAndIsExport { - pub is_export: bool, + pub is_export: IsExport, } /// Downstream-compat: `print_json` callers pass this. Only the fields any caller actually sets are surfaced @@ -1284,17 +1305,19 @@ pub struct RequireOrImportMeta { pub was_unwrapped_require: bool, } +bun_core::bool_enum!(pub WasUnwrappedRequire); + // Clone/Copy: bitwise OK — `ctx` is a non-owning opaque backref the caller // keeps alive for the print pass; `callback` is POD. #[derive(Clone, Copy)] pub struct RequireOrImportMetaCallback { pub(crate) ctx: Option>, - pub(crate) callback: fn(*mut (), u32, bool) -> RequireOrImportMeta, + pub(crate) callback: fn(*mut (), u32, WasUnwrappedRequire) -> RequireOrImportMeta, } impl Default for RequireOrImportMetaCallback { fn default() -> Self { - fn noop(_: *mut (), _: u32, _: bool) -> RequireOrImportMeta { + fn noop(_: *mut (), _: u32, _: WasUnwrappedRequire) -> RequireOrImportMeta { RequireOrImportMeta::default() } Self { @@ -1310,12 +1333,16 @@ pub trait RequireOrImportMetaSource { fn require_or_import_meta_for_source( &mut self, id: u32, - was_unwrapped_require: bool, + was_unwrapped_require: WasUnwrappedRequire, ) -> RequireOrImportMeta; } impl RequireOrImportMetaCallback { - pub(crate) fn call(&self, id: u32, was_unwrapped_require: bool) -> RequireOrImportMeta { + pub(crate) fn call( + &self, + id: u32, + was_unwrapped_require: WasUnwrappedRequire, + ) -> RequireOrImportMeta { (self.callback)(self.ctx.unwrap().as_ptr(), id, was_unwrapped_require) } @@ -1323,7 +1350,7 @@ impl RequireOrImportMetaCallback { fn thunk( p: *mut (), id: u32, - was_unwrapped_require: bool, + was_unwrapped_require: WasUnwrappedRequire, ) -> RequireOrImportMeta { // SAFETY: `p` was constructed from `&mut T` in `init` below; caller guarantees // `ctx` outlives this `RequireOrImportMetaCallback`, so the cast-back @@ -1822,7 +1849,7 @@ pub(crate) mod __gated_printer { match statement { None => self.print_require_or_import_expr( import.import_record_index, - false, + WasUnwrappedRequire::No, &[], Expr::EMPTY, Level::Lowest, @@ -1843,7 +1870,7 @@ pub(crate) mod __gated_printer { self.print_equals(); self.print_require_or_import_expr( import.import_record_index, - false, + WasUnwrappedRequire::No, &[], Expr::EMPTY, Level::Lowest, @@ -1893,7 +1920,7 @@ pub(crate) mod __gated_printer { match statement { None => self.print_require_or_import_expr( import.import_record_index, - false, + WasUnwrappedRequire::No, &[], Expr::EMPTY, Level::Lowest, @@ -2218,7 +2245,7 @@ pub(crate) mod __gated_printer { self.print_space_before_identifier(); self.print_identifier(alias); } else { - self.print_string_literal_utf8(alias, false); + self.print_string_literal_utf8(alias, AllowBacktick::No); } } @@ -2226,9 +2253,9 @@ pub(crate) mod __gated_printer { &mut self, open_paren_loc: Option, args: &[G::Arg], - has_rest_arg: bool, + has_rest_arg: HasRestArg, // is_arrow can be used for minifying later - _is_arrow: bool, + _is_arrow: IsArrow, ) { let wrap = true; @@ -2245,7 +2272,7 @@ pub(crate) mod __gated_printer { self.print_space(); } - if has_rest_arg && i + 1 == args.len() { + if has_rest_arg == HasRestArg::Yes && i + 1 == args.len() { self.print(b"..."); } @@ -2266,8 +2293,8 @@ pub(crate) mod __gated_printer { self.print_fn_args( Some(func.open_parens_loc), slice_of(func.args), - func.flags.contains(G::FnFlags::HasRestArg), - false, + HasRestArg::from_bool(func.flags.contains(G::FnFlags::HasRestArg)), + IsArrow::No, ); self.print_space(); self.print_block(func.body.loc, slice_of(func.body.stmts), None); @@ -2318,7 +2345,10 @@ pub(crate) mod __gated_printer { self.print(b"}"); } - pub(crate) fn best_quote_char_for_e_string(str: &E::String, allow_backtick: bool) -> u8 { + pub(crate) fn best_quote_char_for_e_string( + str: &E::String, + allow_backtick: AllowBacktick, + ) -> u8 { if IS_JSON { return b'"'; } @@ -2370,8 +2400,8 @@ pub(crate) mod __gated_printer { text, &mut writer, quote, - ASCII_ONLY, - false, + AsciiOnly::from_bool(ASCII_ONLY), + Json::No, ); } @@ -2383,8 +2413,8 @@ pub(crate) mod __gated_printer { slice, &mut writer, quote, - ASCII_ONLY, - false, + AsciiOnly::from_bool(ASCII_ONLY), + Json::No, ); } @@ -2431,7 +2461,7 @@ pub(crate) mod __gated_printer { #[inline(never)] pub(crate) fn print_require_error(&mut self, text: &[u8]) { self.print(b"(()=>{throw new Error(\"Cannot require module \"+"); - self.print_string_literal_utf8(text, false); + self.print_string_literal_utf8(text, AllowBacktick::No); self.print(b");})()"); } @@ -2455,7 +2485,7 @@ pub(crate) mod __gated_printer { pub(crate) fn print_require_or_import_expr( &mut self, import_record_index: u32, - was_unwrapped_require: bool, + was_unwrapped_require: WasUnwrappedRequire, leading_interior_comments: &[G::Comment], import_options: Expr, level_: Level, @@ -2566,7 +2596,7 @@ pub(crate) mod __gated_printer { self.print_symbol(self.options.hmr_ref); self.print(b".require("); let path = &input_files[record.source_index.get() as usize].path; - self.print_string_literal_utf8(path.pretty, false); + self.print_string_literal_utf8(path.pretty, AllowBacktick::No); self.print(b")"); } else if !meta.was_unwrapped_require { // Call the wrapper @@ -2661,7 +2691,7 @@ pub(crate) mod __gated_printer { self.print(b".require("); } let path = &record.path; - self.print_string_literal_utf8(path.pretty, false); + self.print_string_literal_utf8(path.pretty, AllowBacktick::No); self.print(b")"); if wrap { self.print(b")"); @@ -2708,7 +2738,7 @@ pub(crate) mod __gated_printer { self.print_symbol(self.options.hmr_ref); self.print(b".dynamicImport("); let path = &record.path; - self.print_string_literal_utf8(path.pretty, false); + self.print_string_literal_utf8(path.pretty, AllowBacktick::No); } if !import_options.is_missing() { @@ -2744,7 +2774,7 @@ pub(crate) mod __gated_printer { pub(crate) fn print_string_literal_e_string( &mut self, str: &E::String, - allow_backtick: bool, + allow_backtick: AllowBacktick, ) { let quote = Self::best_quote_char_for_e_string(str, allow_backtick); self.print(quote); @@ -2752,7 +2782,11 @@ pub(crate) mod __gated_printer { self.print(quote); } - pub(crate) fn print_string_literal_utf8(&mut self, str: &[u8], allow_backtick: bool) { + pub(crate) fn print_string_literal_utf8( + &mut self, + str: &[u8], + allow_backtick: AllowBacktick, + ) { // WTF-8 = UTF-8 plus surrogate code points (U+D800..U+DFFF as // `ED A0 80`..`ED BF BF`), so validate UTF-8 shape minus the // surrogate exclusion. @@ -3105,7 +3139,7 @@ pub(crate) mod __gated_printer { ); self.print_string_literal_utf8( self.import_record(*index as usize).path.pretty, - true, + AllowBacktick::Yes, ); } }, @@ -3157,7 +3191,7 @@ pub(crate) mod __gated_printer { self.print(key); } else { self.print(b"["); - self.print_string_literal_utf8(key, false); + self.print_string_literal_utf8(key, AllowBacktick::No); self.print(b"]"); } } else { @@ -3315,7 +3349,7 @@ pub(crate) mod __gated_printer { ExprData::ERequireString(e) => { self.print_require_or_import_expr( e.import_record_index, - e.unwrapped_id.is_some(), + WasUnwrappedRequire::from_bool(e.unwrapped_id.is_some()), &[], Expr::EMPTY, level, @@ -3343,7 +3377,7 @@ pub(crate) mod __gated_printer { self.print(b"("); self.print_string_literal_utf8( self.import_record(e.import_record_index as usize).path.text, - true, + AllowBacktick::Yes, ); self.print(b")"); @@ -3383,7 +3417,7 @@ pub(crate) mod __gated_printer { } else { self.print_require_or_import_expr( e.import_record_index, - false, + WasUnwrappedRequire::No, &[], // e.leading_interior_comments, e.options, level, @@ -3435,7 +3469,7 @@ pub(crate) mod __gated_printer { } else { self.print(b"["); } - self.print_string_literal_utf8(&e.name, false); + self.print_string_literal_utf8(&e.name, AllowBacktick::No); self.print(b"]"); } @@ -3534,8 +3568,8 @@ pub(crate) mod __gated_printer { self.print_fn_args( if e.is_async { None } else { Some(expr.loc) }, &e.args, - e.has_rest_arg, - true, + HasRestArg::from_bool(e.has_rest_arg), + IsArrow::Yes, ); self.print_whitespacer(ws!(b" => ")); @@ -3773,7 +3807,7 @@ pub(crate) mod __gated_printer { return; } - self.print_string_literal_e_string(&e, true); + self.print_string_literal_e_string(&e, AllowBacktick::Yes); } ExprData::ETemplate(e) => { // Print from a thread-local shallow copy of the template @@ -4076,7 +4110,7 @@ pub(crate) mod __gated_printer { } else { self.print(b"["); // TODO: addSourceMappingForName - self.print_string_literal_utf8(alias, false); + self.print_string_literal_utf8(alias, AllowBacktick::No); self.print(b"]"); } @@ -4332,7 +4366,7 @@ pub(crate) mod __gated_printer { self.print_identifier(namespace.alias.slice()); } else { self.print(b"["); - self.print_string_literal_utf8(namespace.alias.slice(), false); + self.print_string_literal_utf8(namespace.alias.slice(), AllowBacktick::No); self.print(b"]"); } } @@ -4419,7 +4453,7 @@ pub(crate) mod __gated_printer { self.print_indent(); } let key = E::String::init(prop.key.slice()); - self.print_string_literal_e_string(&key, false); + self.print_string_literal_e_string(&key, AllowBacktick::No); self.print(b":"); self.print_space(); self.print_json_value(&prop.value); @@ -4480,7 +4514,7 @@ pub(crate) mod __gated_printer { E::JsonValue::Number(n) => self.print_number(n.value(), Level::Lowest), E::JsonValue::String(s) => { let s = E::String::init(s.slice()); - self.print_string_literal_e_string(&s, false); + self.print_string_literal_e_string(&s, AllowBacktick::No); } E::JsonValue::Object(o) => self.print_object_json(o.get()), E::JsonValue::Array(a) => self.print_array_json(a.get()), @@ -4661,7 +4695,7 @@ pub(crate) mod __gated_printer { self.print_identifier(key_str.slice8()); } else { allow_shorthand = false; - self.print_string_literal_e_string(&key_str, false); + self.print_string_literal_e_string(&key_str, AllowBacktick::No); } // Use a shorthand property if the names are the same @@ -4739,7 +4773,7 @@ pub(crate) mod __gated_printer { } } } else { - let c = best_quote_char_for_string(key_str.slice16(), false); + let c = best_quote_char_for_string(key_str.slice16(), AllowBacktick::No); self.print(c); self.print_string_characters_utf16(key_str.slice16(), c); self.print(c); @@ -4809,7 +4843,7 @@ pub(crate) mod __gated_printer { self.print_space_before_identifier(); self.add_source_mapping(binding.loc); self.print_symbol(b.r#ref); - if Self::MAY_HAVE_MODULE_INFO && tlm.is_export { + if Self::MAY_HAVE_MODULE_INFO && tlm.is_export == IsExport::Yes { // reshaped for borrowck — fetch name before borrowing module_info. let local_name = self.name_for_symbol(b.r#ref); if let Some(mi) = self.module_info() { @@ -4925,7 +4959,7 @@ pub(crate) mod __gated_printer { == self.name_for_symbol(id.r#ref) { if Self::MAY_HAVE_MODULE_INFO - && tlm.is_export + && tlm.is_export == IsExport::Yes { if let Some(mi) = self.module_info() { let name_id = mi.str(str.slice8()); @@ -4941,7 +4975,10 @@ pub(crate) mod __gated_printer { } } } else { - self.print_string_literal_utf8(str.slice8(), false); + self.print_string_literal_utf8( + str.slice8(), + AllowBacktick::No, + ); } } else if self.can_print_identifier_utf16(str.slice16()) { self.print_space_before_identifier(); @@ -4957,7 +4994,9 @@ pub(crate) mod __gated_printer { str.slice16(), self.name_for_symbol(id.r#ref), ) { - if Self::MAY_HAVE_MODULE_INFO && tlm.is_export { + if Self::MAY_HAVE_MODULE_INFO + && tlm.is_export == IsExport::Yes + { // reshaped for borrowck — bump access first. let str8 = str.slice(self.bump); if let Some(mi) = self.module_info() { @@ -5463,17 +5502,18 @@ pub(crate) mod __gated_printer { self.print_indent(); self.print_space_before_identifier(); self.add_source_mapping(stmt.loc); + let is_export = IsExport::from_bool(s.is_export); match s.kind { S::Kind::KConst => { - self.print_decl_stmt(s.is_export, b"const", s.decls.slice()) + self.print_decl_stmt(is_export, b"const", s.decls.slice()) } - S::Kind::KLet => self.print_decl_stmt(s.is_export, b"let", s.decls.slice()), - S::Kind::KVar => self.print_decl_stmt(s.is_export, b"var", s.decls.slice()), + S::Kind::KLet => self.print_decl_stmt(is_export, b"let", s.decls.slice()), + S::Kind::KVar => self.print_decl_stmt(is_export, b"var", s.decls.slice()), S::Kind::KUsing => { - self.print_decl_stmt(s.is_export, b"using", s.decls.slice()) + self.print_decl_stmt(is_export, b"using", s.decls.slice()) } S::Kind::KAwaitUsing => { - self.print_decl_stmt(s.is_export, b"await using", s.decls.slice()) + self.print_decl_stmt(is_export, b"await using", s.decls.slice()) } } } @@ -6072,7 +6112,7 @@ pub(crate) mod __gated_printer { self.print_indent(); self.print_space_before_identifier(); self.add_source_mapping(stmt.loc); - self.print_string_literal_utf8(s.value.slice(), false); + self.print_string_literal_utf8(s.value.slice(), AllowBacktick::No); self.print_semicolon_after_statement(); } StmtData::SBreak(s) => { @@ -6139,7 +6179,7 @@ pub(crate) mod __gated_printer { unreachable!(); } - let quote = best_quote_char_for_string(import_record.path.text, false); + let quote = best_quote_char_for_string(import_record.path.text, AllowBacktick::No); if import_record .flags .contains(ImportRecordFlags::PRINT_NAMESPACE_IN_PATH) @@ -6376,11 +6416,11 @@ pub(crate) mod __gated_printer { pub(crate) fn print_decl_stmt( &mut self, - is_export: bool, + is_export: IsExport, keyword: &'static [u8], decls: &[G::Decl], ) { - if is_export { + if is_export == IsExport::Yes { self.print(b"export "); } let tlm: TopLevelAndIsExport = if Self::MAY_HAVE_MODULE_INFO { @@ -6608,7 +6648,7 @@ pub(crate) mod __gated_printer { self.indent(); self.print_indent(); - self.print_string_literal_utf8(source.path.pretty, false); + self.print_string_literal_utf8(source.path.pretty, AllowBacktick::No); let stmts = slice_of(part.stmts); let func = &stmts[0] @@ -6636,8 +6676,8 @@ pub(crate) mod __gated_printer { self.print_fn_args( Some(func.open_parens_loc), slice_of(func.args), - func.flags.contains(G::FnFlags::HasRestArg), - false, + HasRestArg::from_bool(func.flags.contains(G::FnFlags::HasRestArg)), + IsArrow::No, ); self.print_space(); self.print(b"{\n"); @@ -6672,7 +6712,7 @@ pub(crate) mod __gated_printer { self.print_indent(); let import = stmt.data.s_import().unwrap(); let record = self.import_record(import.import_record_index as usize); - self.print_string_literal_utf8(record.path.pretty, false); + self.print_string_literal_utf8(record.path.pretty, AllowBacktick::No); let item_count = u32::from(import.default_name.is_some()) + u32::try_from(slice_of(import.items).len()).expect("int cast"); @@ -6690,7 +6730,10 @@ pub(crate) mod __gated_printer { } for item in slice_of(import.items).iter() { self.print(b" "); - self.print_string_literal_utf8(item.alias.slice(), false); + self.print_string_literal_utf8( + item.alias.slice(), + AllowBacktick::No, + ); self.print(b","); } } @@ -6714,7 +6757,7 @@ pub(crate) mod __gated_printer { self.print(b" "); } len += key.len(); - self.print_string_literal_utf8(key, false); + self.print_string_literal_utf8(key, AllowBacktick::No); self.print(b","); } self.unindent(); @@ -6734,7 +6777,7 @@ pub(crate) mod __gated_printer { had_any_stars = true; self.print_newline(); self.print_indent(); - self.print_string_literal_utf8(record.path.pretty, false); + self.print_string_literal_utf8(record.path.pretty, AllowBacktick::No); self.print(b","); } self.unindent(); @@ -6751,8 +6794,8 @@ pub(crate) mod __gated_printer { self.print_fn_args( Some(func.open_parens_loc), slice_of(func.args), - func.flags.contains(G::FnFlags::HasRestArg), - false, + HasRestArg::from_bool(func.flags.contains(G::FnFlags::HasRestArg)), + IsArrow::No, ); self.print(b" => {\n"); self.indent(); @@ -7324,7 +7367,9 @@ pub(crate) fn get_source_map_builder<'a, const IS_BUN_PLATFORM: bool>( let mut builder = SourceMap::chunk::Builder { source_map: SourceMap::chunk::SourceMapFormat::init( // opts.source_map_allocator orelse opts.allocator — allocator dropped - IS_BUN_PLATFORM && generate_source_map == GenerateSourceMap::Lazy, + SourceMap::chunk::PrependCount::from_bool( + IS_BUN_PLATFORM && generate_source_map == GenerateSourceMap::Lazy, + ), ), cover_lines_without_mappings: true, approximate_input_line_count: tree.approximate_newline_count, diff --git a/src/js_printer/renamer.rs b/src/js_printer/renamer.rs index aebe444e72b9..36ee2255c0b5 100644 --- a/src/js_printer/renamer.rs +++ b/src/js_printer/renamer.rs @@ -10,7 +10,7 @@ use bun_ast::lexer_tables::{ }; use bun_ast::symbol; use bun_ast::symbol::SlotNamespace; -use bun_ast::{Ref, Symbol}; +use bun_ast::{IsSourceContentsSlice, Ref, Symbol}; use bun_collections::hive_array::Fallback as HiveArrayFallback; use bun_collections::{HashMap, StringHashMap, VecExt}; use bun_core::Output; @@ -614,7 +614,10 @@ impl NumberRenamer { sorted.sort_unstable(); for &inner_index in sorted.iter() { - self.assign_name(s, Ref::init(inner_index, source_index, false)); + self.assign_name( + s, + Ref::init(inner_index, source_index, IsSourceContentsSlice::No), + ); } } @@ -943,7 +946,8 @@ impl NumberScope { // `is_simple_ascii_identifier` is ASCII-restricted. The hot ASCII path // skips the byte compare via `!normalized`; the rare non-ASCII path // falls back to it. - if !collided && (!normalized || strings::eql_long(name, input_name, true)) { + if !collided && (!normalized || strings::eql_long(name, input_name, strings::CheckLen::Yes)) + { // `input_name` is `Symbol::original_name.slice()` — an AST-arena // slice that outlives the renamer (see [`NameKey`] doc). No copy. let prev = self diff --git a/src/jsc/AsyncModule.rs b/src/jsc/AsyncModule.rs index 1c9d5c721cd3..0273f1aef392 100644 --- a/src/jsc/AsyncModule.rs +++ b/src/jsc/AsyncModule.rs @@ -266,7 +266,7 @@ unsafe extern "C" { use core::sync::atomic::Ordering; use std::io::Write as _; -use bun_install::package_manager::run_tasks; +use bun_install::package_manager::{InstallPeer, run_tasks}; use bun_install::{self as install, LogLevel, PackageID}; use crate::event_loop::{ConcurrentTaskItem, Task}; @@ -417,13 +417,18 @@ impl Queue { if bun_core::output::enable_ansi_colors_stderr() { pm.start_progress_bar_if_none(); - run_tasks::run_tasks::>(pm, self, true, LogLevel::Default) - .expect("unreachable"); + run_tasks::run_tasks::>( + pm, + self, + InstallPeer::Yes, + LogLevel::Default, + ) + .expect("unreachable"); } else { run_tasks::run_tasks::>( pm, self, - true, + InstallPeer::Yes, LogLevel::DefaultNoProgress, ) .expect("unreachable"); diff --git a/src/jsc/BunCPUProfiler.rs b/src/jsc/BunCPUProfiler.rs index f854f1aecca7..cb71463b9ba0 100644 --- a/src/jsc/BunCPUProfiler.rs +++ b/src/jsc/BunCPUProfiler.rs @@ -86,21 +86,23 @@ pub(crate) fn stop_and_write_profile( // Write JSON format if requested and not empty if config.json_format && !json_string.is_empty() { - write_profile_to_file(&json_string, config, false)?; + write_profile_to_file(&json_string, config, CpuProfileFormat::Json)?; } // Write text format if requested and not empty if config.md_format && !text_string.is_empty() { - write_profile_to_file(&text_string, config, true)?; + write_profile_to_file(&text_string, config, CpuProfileFormat::Md)?; } Ok(()) } +bun_core::bool_enum!(CpuProfileFormat { Json, Md }); + fn write_profile_to_file( profile_string: &BunString, config: &CPUProfilerConfig, - is_md_format: bool, + is_md_format: CpuProfileFormat, ) -> Result<(), ProfilerError> { let profile_slice = profile_string.to_utf8(); // (defer profile_slice.deinit() — handled by Drop on Utf8Slice) @@ -152,7 +154,7 @@ fn write_profile_to_file( fn build_output_path( path: &mut AutoAbsPathChecked, config: &CPUProfilerConfig, - is_md_format: bool, + is_md_format: CpuProfileFormat, ) -> Result<(), ProfilerError> { // Generate filename let mut filename_buf = PathBuffer::uninit(); @@ -164,7 +166,10 @@ fn build_output_path( 'blk: { if has_both_formats { // Custom name with both formats - append extension based on format - let ext: &[u8] = if is_md_format { b".md" } else { b".cpuprofile" }; + let ext: &[u8] = match is_md_format { + CpuProfileFormat::Md => b".md", + CpuProfileFormat::Json => b".cpuprofile", + }; let mut cursor = std::io::Cursor::new(&mut filename_buf[..]); cursor .write_all(config.name) @@ -194,9 +199,12 @@ fn build_output_path( fn generate_default_filename( buf: &mut PathBuffer, - md_format: bool, + md_format: CpuProfileFormat, ) -> Result<&[u8], ProfilerError> { - let extension: &str = if md_format { ".md" } else { ".cpuprofile" }; + let extension: &str = match md_format { + CpuProfileFormat::Md => ".md", + CpuProfileFormat::Json => ".cpuprofile", + }; let mut cursor = std::io::Cursor::new(&mut buf[..]); write_diagnostic_filename(&mut cursor, "CPU", extension) .map_err(|_| ProfilerError::FilenameTooLong)?; diff --git a/src/jsc/CachedBytecode.rs b/src/jsc/CachedBytecode.rs index c9b2056d4eb0..1309fcf040b4 100644 --- a/src/jsc/CachedBytecode.rs +++ b/src/jsc/CachedBytecode.rs @@ -135,7 +135,7 @@ pub(crate) fn __bun_jsc_generate_cached_bytecode( source_provider_url: &mut BunString, ) -> Option> { crate::virtual_machine::IS_BUNDLER_THREAD_FOR_BYTECODE_CACHE.set(true); - crate::initialize(false); + crate::initialize(crate::EvalMode::No); let (bytes, handle) = CachedBytecode::generate(format, source, source_provider_url)?; let owned = Box::<[u8]>::from(bytes); // `handle` was just produced by C++ and is valid until deref; diff --git a/src/jsc/ConsoleObject.rs b/src/jsc/ConsoleObject.rs index a9270cf88662..f20106332bbb 100644 --- a/src/jsc/ConsoleObject.rs +++ b/src/jsc/ConsoleObject.rs @@ -6,11 +6,14 @@ use crate::{ComptimeStringMapExt as _, ZigStringJsc as _}; use core::cell::{Cell, RefCell}; use core::ffi::c_void; +use core::ops::ControlFlow; use crate as jsc; use crate::virtual_machine::VirtualMachine; use crate::{EventType, JSGlobalObject, JSPromise, JSValue, JsResult, ZigString}; use bun_collections::HashMap; +use bun_core::output::AnsiColors; +use bun_core::strings::AmbiguousWidth; use bun_core::{Output, StackCheck}; use bun_core::{OwnedString, String as BunString, strings}; @@ -611,7 +614,8 @@ enum RowKey { impl RowKey { fn str(name: &BunString) -> Self { Self::Str { - width: u32::try_from(name.visible_width_exclude_ansi_colors(false)).expect("int cast"), + width: u32::try_from(name.visible_width_exclude_ansi_colors(AmbiguousWidth::Narrow)) + .expect("int cast"), text: name.to_utf8(), } } @@ -1016,8 +1020,11 @@ impl<'a> TablePrinter<'a> { for col in columns.iter_mut() { // also update the col width with the length of the column name itself col.width = col.width.max( - u32::try_from(col.name.visible_width_exclude_ansi_colors(false)) - .expect("int cast"), + u32::try_from( + col.name + .visible_width_exclude_ansi_colors(AmbiguousWidth::Narrow), + ) + .expect("int cast"), ); } @@ -1039,7 +1046,9 @@ impl<'a> TablePrinter<'a> { if i > 0 { let _ = writer.write_all("│".as_bytes()); } - let len = col.name.visible_width_exclude_ansi_colors(false); + let len = col + .name + .visible_width_exclude_ansi_colors(AmbiguousWidth::Narrow); let needed = (col.width as usize).saturating_sub(len); let _ = writer.splat_byte_all(b' ', 1); if ENABLE_ANSI_COLORS { @@ -1170,7 +1179,7 @@ pub fn write_trace(writer: &mut dyn bun_io::Write, global: &JSGlobalObject) { None, &mut need_to_clear, &mut source_code_slice, - false, + crate::virtual_machine::AllowSourceCodePreview::No, ); holder.need_to_clear_parser_arena_on_deinit = need_to_clear; @@ -1178,7 +1187,7 @@ pub fn write_trace(writer: &mut dyn bun_io::Write, global: &JSGlobalObject) { let _ = VirtualMachine::print_stack_trace( adapter.interface(), &holder.zig_exception().stack, - Output::enable_ansi_colors_stderr(), + AnsiColors::from_bool(Output::enable_ansi_colors_stderr()), ); // `ZigStringSlice` frees on `Drop`. @@ -1235,13 +1244,14 @@ pub enum Colon { pub struct ErrorDisplayLevelFormatter { pub name: BunString, pub(crate) level: ErrorDisplayLevel, - pub(crate) enable_colors: bool, + pub(crate) enable_colors: AnsiColors, pub(crate) colon: Colon, } impl core::fmt::Display for ErrorDisplayLevelFormatter { fn fmt(&self, writer: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - if self.enable_colors { + let enable_colors = self.enable_colors == AnsiColors::Enabled; + if enable_colors { match self.level { ErrorDisplayLevel::Normal => writer.write_str(pfmt!("", true))?, ErrorDisplayLevel::Warn => writer.write_str(pfmt!("", true))?, @@ -1258,13 +1268,13 @@ impl core::fmt::Display for ErrorDisplayLevelFormatter { } if self.colon == Colon::ExcludeColon { - if self.enable_colors { + if enable_colors { writer.write_str(pfmt!("", true))?; } return Ok(()); } - if self.enable_colors { + if enable_colors { writer.write_str(pfmt!(": ", true))?; } else { writer.write_str(": ")?; @@ -1277,7 +1287,7 @@ impl ErrorDisplayLevel { pub(crate) fn formatter( self, error_name: BunString, - enable_colors: bool, + enable_colors: AnsiColors, colon: Colon, ) -> ErrorDisplayLevelFormatter { ErrorDisplayLevelFormatter { @@ -3274,7 +3284,7 @@ pub mod formatter { /// carry a 32-byte redzone, and the 512-deep `Bun.inspect` test /// cannot afford them in the per-level `print_as` frame. /// - /// Returns `Ok(true)` to continue into the tag dispatch. + /// Returns `Ok(Continue)` to continue into the tag dispatch. #[inline(never)] fn print_as_prelude( &mut self, @@ -3282,15 +3292,15 @@ pub mod formatter { value: JSValue, can_circ: bool, remove_before_recurse: &mut bool, - ) -> JsResult { + ) -> JsResult> { if self.failed { - return Ok(false); + return Ok(ControlFlow::Break(())); } if self.global_this.has_exception() { return Err(jsc::JsError::Thrown); } if !can_circ { - return Ok(true); + return Ok(ControlFlow::Continue(())); } if !self.stack_check.is_safe_to_recurse() { @@ -3298,7 +3308,7 @@ pub mod formatter { if self.can_throw_stack_overflow { return Err(self.global_this.throw_stack_overflow()); } - return Ok(false); + return Ok(ControlFlow::Break(())); } if self.map_node.is_none() { @@ -3318,10 +3328,10 @@ pub mod formatter { { self.failed = true; } - return Ok(false); + return Ok(ControlFlow::Break(())); } *remove_before_recurse = true; - Ok(true) + Ok(ControlFlow::Continue(())) } #[inline(never)] @@ -3336,12 +3346,15 @@ pub mod formatter { // `[Circular]` due to the value already being present in the map. let mut remove_before_recurse = false; - if !self.print_as_prelude::( - writer_, - value, - format.can_have_circular_references(), - &mut remove_before_recurse, - )? { + if self + .print_as_prelude::( + writer_, + value, + format.can_have_circular_references(), + &mut remove_before_recurse, + )? + .is_break() + { return Ok(()); } @@ -3935,7 +3948,15 @@ pub mod formatter { let mut adapter = DynWriteAdapter::new(&mut *writer_); // SAFETY: per-thread VM. let vm = VirtualMachine::get().as_mut(); - vm.print_errorlike_object(value, None, None, self, adapter.interface(), C, false); + vm.print_errorlike_object( + value, + None, + None, + self, + adapter.interface(), + AnsiColors::from_bool(C), + crate::virtual_machine::AllowSideEffects::No, + ); Ok(()) } diff --git a/src/jsc/Debugger.rs b/src/jsc/Debugger.rs index c3ea7428c972..03677ef0e486 100644 --- a/src/jsc/Debugger.rs +++ b/src/jsc/Debugger.rs @@ -666,7 +666,12 @@ impl AsyncTaskTracker { if self.id == 0 { return; } - did_schedule_async_call(global_object, AsyncCallType::EventListener, self.id, true); + did_schedule_async_call( + global_object, + AsyncCallType::EventListener, + self.id, + SingleShot::Yes, + ); } pub fn did_cancel(self, global_object: &JSGlobalObject) { @@ -739,14 +744,16 @@ unsafe extern "C" { safe fn Debugger__willDispatchAsyncCall(global: &JSGlobalObject, call: AsyncCallType, id: u64); } +bun_core::bool_enum!(pub SingleShot); + pub fn did_schedule_async_call( global_object: &JSGlobalObject, call: AsyncCallType, id: u64, - single_shot: bool, + single_shot: SingleShot, ) { jsc::mark_binding(); - Debugger__didScheduleAsyncCall(global_object, call, id, single_shot); + Debugger__didScheduleAsyncCall(global_object, call, id, single_shot == SingleShot::Yes); } pub fn did_cancel_async_call(global_object: &JSGlobalObject, call: AsyncCallType, id: u64) { jsc::mark_binding(); diff --git a/src/jsc/JSGlobalObject.rs b/src/jsc/JSGlobalObject.rs index 804bdabdaa80..0aad3c861506 100644 --- a/src/jsc/JSGlobalObject.rs +++ b/src/jsc/JSGlobalObject.rs @@ -81,6 +81,8 @@ impl core::fmt::Debug for GlobalRef { } } +bun_core::bool_enum!(DateTimeZone { Utc, Local }); + impl JSGlobalObject { /// Alias of the macro-provided [`as_mut_ptr`](Self::as_mut_ptr) kept for /// call-site readability where mutation is not the intent. @@ -131,7 +133,7 @@ impl JSGlobalObject { minute: i32, second: i32, millisecond: i32, - local: bool, + local: DateTimeZone, ) -> JsResult { crate::mark_binding(); crate::cpp::Bun__gregorianDateTimeToMS( @@ -143,7 +145,7 @@ impl JSGlobalObject { minute, second, millisecond, - local, + local == DateTimeZone::Local, ) } @@ -165,7 +167,7 @@ impl JSGlobalObject { minute, second, millisecond, - false, + DateTimeZone::Utc, ) } @@ -187,11 +189,11 @@ impl JSGlobalObject { minute, second, millisecond, - true, + DateTimeZone::Local, ) } - fn ms_to_gregorian_date_time_impl(&self, ms: f64, local: bool) -> GregorianDateTime { + fn ms_to_gregorian_date_time_impl(&self, ms: f64, local: DateTimeZone) -> GregorianDateTime { crate::mark_binding(); let mut dt = GregorianDateTime::default(); // SAFETY: FFI — &self is a valid JSGlobalObject*; out-param pointers are to live @@ -200,7 +202,7 @@ impl JSGlobalObject { crate::cpp::raw::Bun__msToGregorianDateTime( self.as_ptr(), ms, - local, + local == DateTimeZone::Local, &raw mut dt.year, &raw mut dt.month, &raw mut dt.day, @@ -214,11 +216,11 @@ impl JSGlobalObject { } pub fn ms_to_gregorian_date_time_utc(&self, ms: f64) -> GregorianDateTime { - self.ms_to_gregorian_date_time_impl(ms, false) + self.ms_to_gregorian_date_time_impl(ms, DateTimeZone::Utc) } pub fn ms_to_gregorian_date_time(&self, ms: f64) -> GregorianDateTime { - self.ms_to_gregorian_date_time_impl(ms, true) + self.ms_to_gregorian_date_time_impl(ms, DateTimeZone::Local) } pub fn ms_to_gregorian_date_time_in_zone(&self, ms: f64, tz_id: u32) -> GregorianDateTime { diff --git a/src/jsc/NodeCompileCache.rs b/src/jsc/NodeCompileCache.rs index 38d23c3147a0..a47eb3641856 100644 --- a/src/jsc/NodeCompileCache.rs +++ b/src/jsc/NodeCompileCache.rs @@ -47,7 +47,7 @@ unsafe impl Send for CacheState {} struct Entry { /// `path.text` of the module (absolute file path). filename: Box<[u8]>, - is_cjs: bool, + is_cjs: ModuleFormat, code_hash: [u8; HASH_SIZE], code_size: u32, /// Post-transpile text; `None` when the module never transpiled @@ -166,10 +166,12 @@ fn errno_tail(e: &sys::Error) -> String { } } +bun_core::bool_enum!(pub ModuleFormat { Esm, Cjs }); + /// Human-readable module name for logs: plain path for CommonJS, `file://` /// URL for ESM — matching Node's output. -fn display_name(filename: &[u8], is_cjs: bool) -> String { - if is_cjs { +fn display_name(filename: &[u8], is_cjs: ModuleFormat) -> String { + if is_cjs == ModuleFormat::Cjs { filename.as_bstr().to_string() } else if cfg!(windows) { let mut bytes = Vec::with_capacity(filename.len() + 8); @@ -186,8 +188,11 @@ fn display_name(filename: &[u8], is_cjs: bool) -> String { } } -fn type_name(is_cjs: bool) -> &'static str { - if is_cjs { "CommonJS" } else { "ESM" } +fn type_name(is_cjs: ModuleFormat) -> &'static str { + match is_cjs { + ModuleFormat::Cjs => "CommonJS", + ModuleFormat::Esm => "ESM", + } } // ────────────────────────────────────────────────────────────────────────── @@ -203,8 +208,8 @@ fn sha256(bytes: &[u8]) -> [u8; HASH_SIZE] { /// First 8 digest bytes of `SHA256(type byte || filename)`: the in-memory map /// key and the on-disk entry name (16 hex chars). -fn cache_key(filename: &[u8], is_cjs: bool) -> u64 { - let type_byte: [u8; 1] = [is_cjs as u8]; +fn cache_key(filename: &[u8], is_cjs: ModuleFormat) -> u64 { + let type_byte: [u8; 1] = [(is_cjs == ModuleFormat::Cjs) as u8]; let mut ctx = core::mem::MaybeUninit::::uninit(); let mut out = [0u8; HASH_SIZE]; // SAFETY: `SHA256_Init` fully initializes the context; updates/final only @@ -226,7 +231,7 @@ fn hex(digest: &[u8; HASH_SIZE]) -> String { /// Portable mode keys on the path relative to the cache dir (Node parity). /// Falls back to absolute keys when no relative form exists (e.g. different /// Windows drives, where `relative` returns `to` unchanged — Node parity). -fn key_for(state: &CacheState, filename: &[u8], is_cjs: bool) -> u64 { +fn key_for(state: &CacheState, filename: &[u8], is_cjs: ModuleFormat) -> u64 { if state.portable { // Thread-local scratch result: consumed before any other resolve call. let rel = bun_paths::resolve_path::relative(&state.dir, filename); @@ -542,7 +547,7 @@ pub fn get_dir() -> Option> { /// Module-fetch hook: register/refresh the entry for `filename`; returns the /// validated bytecode blob when the on-disk cache matches `code` (post- /// transpile text). The pointer stays valid for the process (entry map owns it). -pub fn fetch(filename: &[u8], is_cjs: bool, code: &[u8]) -> Option<(*mut u8, usize)> { +pub fn fetch(filename: &[u8], is_cjs: ModuleFormat, code: &[u8]) -> Option<(*mut u8, usize)> { if !is_enabled() || filename.is_empty() || !bun_paths::is_absolute(filename) { return None; } @@ -602,7 +607,7 @@ pub fn fetch(filename: &[u8], is_cjs: bool, code: &[u8]) -> Option<(*mut u8, usi /// Parse-failure hook: mirrors Node registering an entry before compilation. /// The entry stays "not initialized" so exit-time persist logs the skip line /// (and the cache directory exists with zero entries — Node parity). -pub fn note_parse_failure(filename: &[u8], is_cjs: bool) { +pub fn note_parse_failure(filename: &[u8], is_cjs: ModuleFormat) { if !is_enabled() || filename.is_empty() || !bun_paths::is_absolute(filename) { return; } @@ -919,7 +924,7 @@ struct PersistJob { format: Format, code: Box<[u8]>, filename: Box<[u8]>, - is_cjs: bool, + is_cjs: ModuleFormat, code_size: u32, code_hash: [u8; HASH_SIZE], } @@ -955,10 +960,9 @@ fn collect_persist_jobs(state: &mut CacheState) -> Vec { }; jobs.push(PersistJob { key, - format: if entry.is_cjs { - Format::Cjs - } else { - Format::Esm + format: match entry.is_cjs { + ModuleFormat::Cjs => Format::Cjs, + ModuleFormat::Esm => Format::Esm, }, code, filename: entry.filename.clone(), diff --git a/src/jsc/RegularExpression.rs b/src/jsc/RegularExpression.rs index 992abc697b66..49e0a9b3c718 100644 --- a/src/jsc/RegularExpression.rs +++ b/src/jsc/RegularExpression.rs @@ -92,7 +92,7 @@ impl RegularExpression { #[unsafe(no_mangle)] fn __bun_regex_compile(pattern: BunString) -> Option> { // Initialize JSC before first compile (idempotent). - crate::initialize(false); + crate::initialize(crate::EvalMode::No); match RegularExpression::init(pattern, Flags::None) { Ok(r) => core::ptr::NonNull::new(r.cast()), Err(_) => None, diff --git a/src/jsc/RuntimeTranspilerStore.rs b/src/jsc/RuntimeTranspilerStore.rs index 640ad4635a6c..7b70ccfd1af9 100644 --- a/src/jsc/RuntimeTranspilerStore.rs +++ b/src/jsc/RuntimeTranspilerStore.rs @@ -708,7 +708,7 @@ impl TranspilerJob { // SAFETY: dst/src point at locals that outlive this guard; no aliases at drop. unsafe { *dst = bun_ast::Log::init(); - (*src).clone_to_with_recycled(&mut *dst, true); + (*src).clone_to_with_recycled(&mut *dst, bun_ast::Recycled::Yes); } }, ); @@ -829,7 +829,7 @@ impl TranspilerJob { let (vm_main, vm_main_hash) = unsafe { ((*vm).main(), (*vm).main_hash) }; let is_main = vm_main.len() == path.text.len() && vm_main_hash == hash - && strings::eql_long(vm_main, path.text, false); + && strings::eql_long(vm_main, path.text, strings::CheckLen::No); let module_type: ModuleType = match this_tag { ResolvedSourceTag::PackageJsonTypeCommonjs => ModuleType::Cjs, diff --git a/src/jsc/Task.rs b/src/jsc/Task.rs index b2d60c0a9415..65d446b21313 100644 --- a/src/jsc/Task.rs +++ b/src/jsc/Task.rs @@ -49,7 +49,9 @@ pub fn report_error_or_terminate(global: &JSGlobalObject, proof: JsError) -> Res return Err(Stopped); } let vm = global.bun_vm(); - let _ = vm.as_mut().uncaught_exception(global, ex, false); + let _ = vm + .as_mut() + .uncaught_exception(global, ex, crate::virtual_machine::IsRejection::No); if vm.is_shutting_down() { return Ok(()); } diff --git a/src/jsc/VM.rs b/src/jsc/VM.rs index 3081278db388..17ca0558e99f 100644 --- a/src/jsc/VM.rs +++ b/src/jsc/VM.rs @@ -44,6 +44,8 @@ bun_opaque::opaque_ffi! { pub struct VM; } +bun_core::bool_enum!(pub GcMode { Async, Sync }); + impl VM { // Note: `JSC__VM__create` was removed from bindings.cpp (Bun creates // its VM via `Zig::GlobalObject::create` → `WebWorker__createVM` instead). @@ -90,8 +92,8 @@ impl VM { JSC__VM__shrinkFootprint(self) } - pub fn run_gc(&self, sync: bool) -> usize { - JSC__VM__runGC(self, sync) + pub fn run_gc(&self, sync: GcMode) -> usize { + JSC__VM__runGC(self, sync == GcMode::Sync) } pub(crate) fn heap_size(&self) -> usize { diff --git a/src/jsc/VirtualMachine.rs b/src/jsc/VirtualMachine.rs index fc224cc8f479..8a089c69b25f 100644 --- a/src/jsc/VirtualMachine.rs +++ b/src/jsc/VirtualMachine.rs @@ -8,6 +8,7 @@ use core::ffi::{c_int, c_void}; use core::ptr::NonNull; use bun_bundler::Transpiler; +use bun_bundler::transpiler::AutoJsx; use bun_io as Async; use bun_uws as uws; @@ -22,6 +23,8 @@ use crate::{ ZigException, }; +use bun_core::output::AnsiColors; + pub use crate::process_auto_killer as ProcessAutoKiller; // ────────────────────────────────────────────────────────────────────────── @@ -1265,11 +1268,11 @@ impl VirtualMachine { } #[cold] - pub fn garbage_collect(&self, sync: bool) -> usize { - bun_core::Global::mimalloc_cleanup(false); + pub fn garbage_collect(&self, sync: crate::GcMode) -> usize { + bun_core::Global::mimalloc_cleanup(bun_core::Force::No); let vm = self.global().vm(); - if sync { - return vm.run_gc(true); + if sync == crate::GcMode::Sync { + return vm.run_gc(crate::GcMode::Sync); } vm.collect_async(); vm.heap_size() @@ -1278,7 +1281,9 @@ impl VirtualMachine { #[inline] pub fn auto_garbage_collect(&self) { if self.aggressive_garbage_collection != GCLevel::None { - let _ = self.garbage_collect(self.aggressive_garbage_collection == GCLevel::Aggressive); + let _ = self.garbage_collect(crate::GcMode::from_bool( + self.aggressive_garbage_collection == GCLevel::Aggressive, + )); } } @@ -1493,15 +1498,25 @@ impl VirtualMachine { pub fn set_is_main_thread_vm(value: bool) { IS_MAIN_THREAD_VM.set(value); } +} + +bun_core::bool_enum!(pub BlockUntilConnected); +bun_core::bool_enum!(pub IsRejection); +impl VirtualMachine { /// The body lives in `bun_runtime` (it constructs `bun.api.Debugger`), so /// dispatch through [`RuntimeHooks::ensure_debugger`] like /// [`reload_entry_point`] does. No-op when hooks aren't installed (pure /// `bun_jsc` unit tests). - pub fn ensure_debugger(&mut self, block_until_connected: bool) -> crate::CrateResult<()> { + pub fn ensure_debugger( + &mut self, + block_until_connected: BlockUntilConnected, + ) -> crate::CrateResult<()> { if let Some(hooks) = runtime_hooks() { // SAFETY: hook contract — `self` is the live per-thread VM. - unsafe { (hooks.ensure_debugger)(self, block_until_connected) }; + unsafe { + (hooks.ensure_debugger)(self, block_until_connected == BlockUntilConnected::Yes) + }; } Ok(()) } @@ -1518,7 +1533,7 @@ impl VirtualMachine { &mut self, global_object: &JSGlobalObject, err: JSValue, - is_rejection: bool, + is_rejection: IsRejection, ) -> bool { if self.is_shutting_down() { return true; @@ -1552,7 +1567,11 @@ impl VirtualMachine { let handled = Bun__handleUncaughtException( global_object, err.to_error().unwrap_or(err), - if is_rejection { 1 } else { 0 }, + if is_rejection == IsRejection::Yes { + 1 + } else { + 0 + }, ) > 0; if !handled { // `beforeExit` has already been dispatched, so the run is winding @@ -2683,7 +2702,7 @@ impl VirtualMachine { self.overridden_main.deinit(); let hooks = runtime_hooks(); - let _ = self.ensure_debugger(true); + let _ = self.ensure_debugger(BlockUntilConnected::Yes); // Node.js `--trace-*` and `--stack-trace-limit` flags need // `internal/process/pre_execution` to run before any user code. @@ -3360,7 +3379,7 @@ pub fn collect_macro_vm_garbage() { } debug_assert!(!vm_ref.is_main_thread); debug_assert_eq!(vm_ref.macro_guard_depth, 0); - vm_ref.jsc_vm().run_gc(true); + vm_ref.jsc_vm().run_gc(crate::GcMode::Sync); } fn normalize_source(source: &[u8]) -> &[u8] { @@ -3654,7 +3673,7 @@ impl VirtualMachine { if let Err(e) = r { let exc = global_object.take_exception(e); // `exc` is already the exception's value; report it directly. - let _ = this.uncaught_exception(global_object, exc, false); + let _ = this.uncaught_exception(global_object, exc, IsRejection::No); } }; @@ -3691,7 +3710,7 @@ impl VirtualMachine { Mode::Strict => { let wrapped = wrap_unhandled_rejection_error_for_uncaught_exception(global_object, reason); - let _ = self.uncaught_exception(global_object, wrapped, true); + let _ = self.uncaught_exception(global_object, wrapped, IsRejection::Yes); let handled = handle_unhandled(); if !handled { emit_warning(self); @@ -3706,7 +3725,7 @@ impl VirtualMachine { } let wrapped = wrap_unhandled_rejection_error_for_uncaught_exception(global_object, reason); - if self.uncaught_exception(global_object, wrapped, true) { + if self.uncaught_exception(global_object, wrapped, IsRejection::Yes) { drain(self); return; } @@ -3834,7 +3853,10 @@ impl VirtualMachine { // node's child does via AtExit(FlushCompileCache) on every restart. crate::node_compile_cache::persist_now(); bun_core::Output::flush(); - bun_core::reload_process(should_clear_terminal, false); + bun_core::reload_process( + bun_core::ClearTerminal::from_bool(should_clear_terminal), + bun_core::MayReturn::No, + ); } if let Some(p) = self.pending_internal_promise { @@ -3959,8 +3981,10 @@ impl VirtualMachine { let vm_ref = unsafe { &mut *vm }; vm_ref.transpiler.resolver.standalone_module_graph = Some(graph); // Avoid reading from tsconfig.json & package.json when in standalone mode - vm_ref.transpiler.configure_linker_with_auto_jsx(false); - vm_ref.transpiler.resolver.store_fd = false; + vm_ref + .transpiler + .configure_linker_with_auto_jsx(AutoJsx::No); + vm_ref.transpiler.resolver.store_fd = bun_resolver::fs::StoreFd::No; IS_SMOL_MODE.store(opts.smol, core::sync::atomic::Ordering::Relaxed); Ok(vm) } @@ -4006,11 +4030,13 @@ impl VirtualMachine { vm_ref.transpiler.resolver.standalone_module_graph = opts.graph; vm_ref.hot_reload = worker.hot_reload(); vm_ref.initial_script_execution_context_identifier = worker.execution_context_id() as i32; - vm_ref.transpiler.resolver.store_fd = opts.store_fd; + vm_ref.transpiler.resolver.store_fd = bun_resolver::fs::StoreFd::from_bool(opts.store_fd); if opts.graph.is_none() { vm_ref.transpiler.configure_linker(); } else { - vm_ref.transpiler.configure_linker_with_auto_jsx(false); + vm_ref + .transpiler + .configure_linker_with_auto_jsx(AutoJsx::No); } Ok(vm) } @@ -4158,7 +4184,7 @@ impl VirtualMachine { // `ref_` as ctx. let s = bun_core::String::create_external::<*mut RefString>( unsafe { bun_core::ffi::slice(ptr, len) }, - true, + bun_core::WTFEncoding::Latin1, ref_, free_ref_string, ); @@ -4793,6 +4819,12 @@ impl VirtualMachine { } self.has_terminated = true; } +} + +bun_core::bool_enum!(pub AllowSideEffects); +bun_core::bool_enum!(pub AllowSourceCodePreview); + +impl VirtualMachine { /// Note: takes the concrete /// `bun_core::io::Writer` since every call site passes /// `Output.errorWriterBuffered()`. @@ -4801,7 +4833,7 @@ impl VirtualMachine { exception: &Exception, exception_list: Option<&mut ExceptionList>, writer: &mut bun_core::io::Writer, - allow_side_effects: bool, + allow_side_effects: AllowSideEffects, ) { let mut formatter = crate::console_object::Formatter::new(self.global()); let colors = bun_core::Output::enable_ansi_colors_stderr(); @@ -4811,7 +4843,7 @@ impl VirtualMachine { exception_list, &mut formatter, writer, - colors, + AnsiColors::from_bool(colors), allow_side_effects, ); // `defer formatter.deinit()` → Drop. @@ -4848,7 +4880,7 @@ impl VirtualMachine { self.event_loop_mut().ensure_waker(); - let _ = self.ensure_debugger(true); + let _ = self.ensure_debugger(BlockUntilConnected::Yes); if !self.transpiler.options.disable_transpilation { if let Some(hooks) = runtime_hooks() { @@ -5155,8 +5187,8 @@ impl VirtualMachine { exception_list: Option<&mut ExceptionList>, formatter: &mut crate::console_object::Formatter, writer: &mut bun_core::io::Writer, - allow_ansi_color: bool, - allow_side_effects: bool, + allow_ansi_color: AnsiColors, + allow_side_effects: AllowSideEffects, ) { // Note: the post-print stack/exception_list block is handled at the // tail instead of via a drop guard (the body has no early-`?` returns @@ -5173,8 +5205,8 @@ impl VirtualMachine { formatter: *mut crate::console_object::Formatter<'a>, writer: *mut bun_core::io::Writer, exception_list: *mut ExceptionList, - allow_ansi_color: bool, - allow_side_effects: bool, + allow_ansi_color: AnsiColors, + allow_side_effects: AllowSideEffects, } extern "C" fn agg_iter( _vm: *mut crate::VM, @@ -5264,12 +5296,12 @@ impl VirtualMachine { exception_list: Option<&mut ExceptionList>, formatter: &mut crate::console_object::Formatter, writer: &mut bun_core::io::Writer, - allow_ansi_color: bool, - allow_side_effects: bool, + allow_ansi_color: AnsiColors, + allow_side_effects: AllowSideEffects, ) -> bool { macro_rules! write_msg { ($msg:expr, $w:expr, $color:expr) => { - if $color { + if $color == AnsiColors::Enabled { let _ = $msg.write_format::(&mut bun_io::AsFmt::new($w)); } else { let _ = $msg.write_format::(&mut bun_io::AsFmt::new($w)); @@ -5351,7 +5383,7 @@ impl VirtualMachine { exception: &Exception, ) -> JSValue { let jsc_vm = global_object.bun_vm().as_mut(); - let _ = jsc_vm.uncaught_exception(global_object, exception.value(), false); + let _ = jsc_vm.uncaught_exception(global_object, exception.value(), IsRejection::No); JSValue::UNDEFINED } @@ -5359,7 +5391,7 @@ impl VirtualMachine { pub(crate) fn print_stack_trace( writer: &mut bun_core::io::Writer, trace: &crate::ZigStackTrace, - allow_ansi_colors: bool, + allow_ansi_colors: AnsiColors, ) -> crate::CrateResult<()> { use crate::zig_stack_frame::LineColumn; let stack = trace.frames(); @@ -5388,7 +5420,7 @@ impl VirtualMachine { let has_name = { use core::fmt::Write as _; let mut probe = String::new(); - let _ = write!(probe, "{}", frame.name_formatter(false)); + let _ = write!(probe, "{}", frame.name_formatter(AnsiColors::Disabled)); !probe.is_empty() }; @@ -5396,7 +5428,7 @@ impl VirtualMachine { // dispatches on the runtime `allow_ansi_colors` flag. macro_rules! pretty_write { ($fmt:literal $(, $arg:expr)* $(,)?) => { - if allow_ansi_colors { + if allow_ansi_colors == AnsiColors::Enabled { write!(writer, bun_core::pretty_fmt!($fmt, true) $(, $arg)*) } else { write!(writer, bun_core::pretty_fmt!($fmt, false) $(, $arg)*) @@ -5498,7 +5530,7 @@ impl VirtualMachine { exception_list: Option<&mut ExceptionList>, must_reset_parser_arena_later: &mut bool, source_code_slice: &mut Option, - allow_source_code_preview: bool, + allow_source_code_preview: AllowSourceCodePreview, ) { // `global()` returns `&'static`, so the borrow detaches from `&self` // and survives the `&mut self` reborrows below. @@ -5508,7 +5540,7 @@ impl VirtualMachine { // and read the *current* value at scope-exit without a raw-ptr deref, // while the body freely `.set()`s it. let enable_source_code_preview = Cell::new( - allow_source_code_preview + allow_source_code_preview == AllowSourceCodePreview::Yes && !(bun_core::env_var::feature_flag::BUN_DISABLE_SOURCE_CODE_PREVIEW::get() .unwrap_or(false) || bun_core::env_var::feature_flag::BUN_DISABLE_TRANSPILED_SOURCE_CODE_PREVIEW::get() @@ -5848,8 +5880,8 @@ impl VirtualMachine { zig_exception: &mut ZigException, formatter: Option<&mut crate::console_object::Formatter>, writer: &mut bun_core::io::Writer, - allow_side_effects: bool, - allow_ansi_color: bool, + allow_side_effects: AllowSideEffects, + allow_ansi_color: AnsiColors, ) -> crate::CrateResult<()> { let mut default_formatter = crate::console_object::Formatter::new(self.global()); let f = formatter.unwrap_or(&mut default_formatter); @@ -5873,8 +5905,8 @@ impl VirtualMachine { exception_list: Option<&mut ExceptionList>, formatter: &mut crate::console_object::Formatter, writer: &mut bun_core::io::Writer, - allow_ansi_color: bool, - allow_side_effects: bool, + allow_ansi_color: AnsiColors, + allow_side_effects: AllowSideEffects, ) -> crate::CrateResult<()> { // Note: stack-safety guard for the Error recursion path. // `print_error_instance_body` dispatches on runtime bools, so it @@ -5928,7 +5960,9 @@ impl VirtualMachine { exception_list, &mut exception_holder.need_to_clear_parser_arena_on_deinit, &mut source_code_slice, - formatter.error_display_level != crate::console_object::ErrorDisplayLevel::Warn, + AllowSourceCodePreview::from_bool( + formatter.error_display_level != crate::console_object::ErrorDisplayLevel::Warn, + ), ); error_instance.ensure_still_alive(); @@ -5967,8 +6001,8 @@ impl VirtualMachine { exception_list: Option<&mut ExceptionList>, formatter: &mut crate::console_object::Formatter, writer: &mut bun_core::io::Writer, - allow_ansi_color: bool, - allow_side_effects: bool, + allow_ansi_color: AnsiColors, + allow_side_effects: AllowSideEffects, ) -> crate::CrateResult<()> { use crate::JSType; use crate::console_object::formatter::TagOptions; @@ -5995,7 +6029,7 @@ impl VirtualMachine { prev: prev_had_errors, }; - if allow_side_effects { + if allow_side_effects == AllowSideEffects::Yes { if let Some(debugger) = self.debugger.as_deref_mut() { debugger.lifecycle_reporter_agent.report_error(exception); } @@ -6020,7 +6054,8 @@ impl VirtualMachine { } } let _defer_gh = DeferGhAnnotation { - run: allow_side_effects && bun_core::Output::is_github_action(), + run: allow_side_effects == AllowSideEffects::Yes + && bun_core::Output::is_github_action(), exception: bun_ptr::BackRef::new(&*exception), }; @@ -6028,7 +6063,7 @@ impl VirtualMachine { // `allow_ansi_color` bool through a local wrapper. macro_rules! pretty_write { ($w:expr, $fmt:literal $(, $arg:expr)* $(,)?) => { - if allow_ansi_color { + if allow_ansi_color == AnsiColors::Enabled { write!($w, bun_core::pretty_fmt!($fmt, true) $(, $arg)*) } else { write!($w, bun_core::pretty_fmt!($fmt, false) $(, $arg)*) @@ -6079,12 +6114,12 @@ impl VirtualMachine { let hl = bun_core::fmt::fmt_javascript( clamped, bun_core::fmt::HighlighterOptions { - enable_colors: allow_ansi_color, + enable_colors: allow_ansi_color == AnsiColors::Enabled, ..Default::default() }, ); if clamped.len() != trimmed.len() { - if allow_ansi_color { + if allow_ansi_color == AnsiColors::Enabled { pretty_write!( writer, "{} | {} | ... truncated \n", @@ -6185,12 +6220,12 @@ impl VirtualMachine { let hl = bun_core::fmt::fmt_javascript( clamped, bun_core::fmt::HighlighterOptions { - enable_colors: allow_ansi_color, + enable_colors: allow_ansi_color == AnsiColors::Enabled, ..Default::default() }, ); if clamped.len() != trimmed.len() { - if allow_ansi_color { + if allow_ansi_color == AnsiColors::Enabled { pretty_write!( writer, "- | {} | ... truncated \n", @@ -6222,12 +6257,12 @@ impl VirtualMachine { let hl = bun_core::fmt::fmt_javascript( clamped, bun_core::fmt::HighlighterOptions { - enable_colors: allow_ansi_color, + enable_colors: allow_ansi_color == AnsiColors::Enabled, ..Default::default() }, ); if clamped.len() != trimmed.len() { - if allow_ansi_color { + if allow_ansi_color == AnsiColors::Enabled { pretty_write!( writer, "{} | {} | ... truncated \n\n", @@ -6378,7 +6413,7 @@ impl VirtualMachine { splat_space(writer, pad_left as u64)?; pretty_write!(writer, " {}: ", field)?; - if allow_side_effects && global_ref.has_exception() { + if allow_side_effects == AllowSideEffects::Yes && global_ref.has_exception() { global_ref.clear_exception(); } @@ -6387,13 +6422,13 @@ impl VirtualMachine { global_ref, TagOptions::DISABLE_INSPECT_CUSTOM | TagOptions::HIDE_GLOBAL, )?; - let _ = if allow_ansi_color { + let _ = if allow_ansi_color == AnsiColors::Enabled { formatter.format::(tag, writer, value, global_ref) } else { formatter.format::(tag, writer, value, global_ref) }; - if allow_side_effects { + if allow_side_effects == AllowSideEffects::Yes { if global_ref.has_exception() { global_ref.clear_exception(); } @@ -6438,7 +6473,7 @@ impl VirtualMachine { TagOptions::DISABLE_INSPECT_CUSTOM | TagOptions::HIDE_GLOBAL, )?; if !matches!(tag.tag, TagPayload::NativeCode) { - let _ = if allow_ansi_color { + let _ = if allow_ansi_color == AnsiColors::Enabled { formatter.format::(tag, writer, error_instance, global_ref) } else { formatter.format::(tag, writer, error_instance, global_ref) @@ -6497,13 +6532,13 @@ impl VirtualMachine { is_browser_error: bool, optional_code: Option<&[u8]>, writer: &mut bun_core::io::Writer, - allow_ansi_color: bool, + allow_ansi_color: AnsiColors, error_display_level: crate::console_object::ErrorDisplayLevel, ) -> crate::CrateResult<()> { use crate::console_object::Colon; macro_rules! pretty_write { ($fmt:literal $(, $arg:expr)* $(,)?) => { - if allow_ansi_color { + if allow_ansi_color == AnsiColors::Enabled { write!(writer, bun_core::pretty_fmt!($fmt, true) $(, $arg)*) } else { write!(writer, bun_core::pretty_fmt!($fmt, false) $(, $arg)*) @@ -6533,7 +6568,7 @@ impl VirtualMachine { && bun_core::strings::eql_long( &msg_chars[..code.len()], code, - false, + bun_core::strings::CheckLen::No, ) && msg_chars[code.len()] == b':' && msg_chars[code.len() + 1] == b' ' @@ -6674,12 +6709,17 @@ impl VirtualMachine { let (name_str, loc_str) = { use core::fmt::Write as _; let mut name_str = String::new(); - let _ = write!(name_str, "{}", frame.name_formatter(false)); + let _ = write!(name_str, "{}", frame.name_formatter(AnsiColors::Disabled)); let mut loc_str = String::new(); let _ = write!( loc_str, "{}", - frame.source_url_formatter(file, origin, LineColumn::Include, false) + frame.source_url_formatter( + file, + origin, + LineColumn::Include, + AnsiColors::Disabled + ) ); (name_str, loc_str) }; diff --git a/src/jsc/ZigStackFrame.rs b/src/jsc/ZigStackFrame.rs index 4de668b7810d..ae3931704947 100644 --- a/src/jsc/ZigStackFrame.rs +++ b/src/jsc/ZigStackFrame.rs @@ -5,6 +5,7 @@ use bstr::BStr; use bun_core::Output; use bun_core::String as BunString; +use bun_core::output::AnsiColors; use bun_paths::strings; use bun_url::URL as ZigURL; @@ -52,7 +53,12 @@ impl ZigStackFrame { write!( &mut file, "{}", - self.source_url_formatter(root_path, origin, LineColumn::Exclude, false) + self.source_url_formatter( + root_path, + origin, + LineColumn::Exclude, + AnsiColors::Disabled + ) ) .expect("Vec write is infallible"); } @@ -75,7 +81,7 @@ impl ZigStackFrame { jsc_stack_frame_index: -1, }; - pub fn name_formatter(&self, enable_color: bool) -> NameFormatter { + pub fn name_formatter(&self, enable_color: AnsiColors) -> NameFormatter { NameFormatter { function_name: self.function_name, code_type: self.code_type, @@ -89,7 +95,7 @@ impl ZigStackFrame { root_path: &'a [u8], origin: Option<&'a ZigURL<'a>>, line_column: LineColumn, - enable_color: bool, + enable_color: AnsiColors, ) -> SourceURLFormatter<'a> { SourceURLFormatter { source_url: self.source_url, @@ -113,7 +119,7 @@ pub(crate) enum LineColumn { pub struct SourceURLFormatter<'a> { pub(crate) source_url: BunString, pub(crate) position: ZigStackFramePosition, - pub(crate) enable_color: bool, + pub(crate) enable_color: AnsiColors, pub(crate) origin: Option<&'a ZigURL<'a>>, pub(crate) line_column: LineColumn, pub(crate) remapped: bool, @@ -122,9 +128,10 @@ pub struct SourceURLFormatter<'a> { impl<'a> fmt::Display for SourceURLFormatter<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let enable_color = self.enable_color == AnsiColors::Enabled; // `Output::pretty_fmt!` expands to a `&'static str` literal (substituting ``/``/ // etc. for ANSI sequences at compile time), so it is usable as a `write!` format string. - if self.enable_color { + if enable_color { f.write_str(Output::pretty_fmt!("", true))?; } @@ -147,7 +154,7 @@ impl<'a> fmt::Display for SourceURLFormatter<'a> { } write!(f, "{}", BStr::new(source_slice))?; } else { - if self.enable_color { + if enable_color { let not_root = if cfg!(windows) { self.root_path.len() > b"C:\\".len() } else { @@ -175,20 +182,20 @@ impl<'a> fmt::Display for SourceURLFormatter<'a> { && !source_slice.is_empty() && (self.position.line.is_valid() || self.position.column.is_valid()) { - if self.enable_color { + if enable_color { f.write_str(Output::pretty_fmt!(":", true))?; } else { f.write_str(":")?; } } - if self.enable_color { + if enable_color { f.write_str(Output::pretty_fmt!("", true))?; } if self.line_column == LineColumn::Include { if self.position.line.is_valid() && self.position.column.is_valid() { - if self.enable_color { + if enable_color { write!( f, Output::pretty_fmt!("{}:{}", true), @@ -204,7 +211,7 @@ impl<'a> fmt::Display for SourceURLFormatter<'a> { )?; } } else if self.position.line.is_valid() { - if self.enable_color { + if enable_color { write!( f, Output::pretty_fmt!("{}", true), @@ -223,17 +230,18 @@ impl<'a> fmt::Display for SourceURLFormatter<'a> { pub struct NameFormatter { pub(crate) function_name: BunString, pub(crate) code_type: ZigStackFrameCode, - pub(crate) enable_color: bool, + pub(crate) enable_color: AnsiColors, pub(crate) is_async: bool, } impl fmt::Display for NameFormatter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let name = &self.function_name; + let enable_color = self.enable_color == AnsiColors::Enabled; match self.code_type { ZigStackFrameCode::EVAL => { - if self.enable_color { + if enable_color { f.write_str(concat!( Output::pretty_fmt!("", true), "eval", @@ -243,7 +251,7 @@ impl fmt::Display for NameFormatter { f.write_str("eval")?; } if !name.is_empty() { - if self.enable_color { + if enable_color { write!(f, Output::pretty_fmt!(" {}", true), name)?; } else { write!(f, " {}", name)?; @@ -252,7 +260,7 @@ impl fmt::Display for NameFormatter { } ZigStackFrameCode::FUNCTION => { if !name.is_empty() { - if self.enable_color { + if enable_color { if self.is_async { write!(f, Output::pretty_fmt!("async {}", true), name,)?; } else { @@ -266,7 +274,7 @@ impl fmt::Display for NameFormatter { } } } else { - if self.enable_color { + if enable_color { if self.is_async { f.write_str(concat!( Output::pretty_fmt!("", true), diff --git a/src/jsc/btjs.rs b/src/jsc/btjs.rs index 068f1d64d877..3a5d9dac5b50 100644 --- a/src/jsc/btjs.rs +++ b/src/jsc/btjs.rs @@ -261,7 +261,7 @@ fn print_source_at_address( &symbol_info.compile_unit_name, tty_config, print_line_from_file_any_os, - do_llint, + DoLlint::from_bool(do_llint), )?; if let Some(frame) = frame { let desc = frame.describe_frame(); @@ -293,10 +293,12 @@ fn print_unknown_source( module_name.as_deref().unwrap_or(b"???"), tty_config, print_line_from_file_any_os, - false, + DoLlint::No, ) } +bun_core::bool_enum!(DoLlint); + #[cfg(debug_assertions)] fn print_line_info( out_stream: &mut Vec, @@ -307,7 +309,7 @@ fn print_line_info( tty_config: tty::Config, // impl-Trait so it monomorphizes, not a runtime fn pointer. print_line_from_file: impl Fn(&mut Vec, &SourceLocation) -> Result<(), Error>, - do_llint: bool, + do_llint: DoLlint, ) -> Result<(), Error> { if !cfg!(debug_assertions) { unreachable!(); @@ -324,12 +326,12 @@ fn print_line_info( sl.line, sl.column )?; - } else if !do_llint { + } else if do_llint == DoLlint::No { out_stream.extend_from_slice(b"???:?:?"); } tty_config.set_color(out_stream, Color::Reset)?; - if !do_llint || source_location.is_some() { + if do_llint == DoLlint::No || source_location.is_some() { out_stream.extend_from_slice(b": "); } tty_config.set_color(out_stream, Color::Dim)?; diff --git a/src/jsc/bun_string_jsc.rs b/src/jsc/bun_string_jsc.rs index 7edd84e60e58..3359e19e21dd 100644 --- a/src/jsc/bun_string_jsc.rs +++ b/src/jsc/bun_string_jsc.rs @@ -142,20 +142,22 @@ pub(crate) fn slice_with_underlying_string_to_js( this: &mut SliceWithUnderlyingString, global_object: &JSGlobalObject, ) -> JsResult { - slice_with_underlying_string_to_js_with_options(this, global_object, false) + slice_with_underlying_string_to_js_with_options(this, global_object, Transfer::No) } pub(crate) fn slice_with_underlying_string_transfer_to_js( this: &mut SliceWithUnderlyingString, global_object: &JSGlobalObject, ) -> JsResult { - slice_with_underlying_string_to_js_with_options(this, global_object, true) + slice_with_underlying_string_to_js_with_options(this, global_object, Transfer::Yes) } +bun_core::bool_enum!(Transfer); + fn slice_with_underlying_string_to_js_with_options( this: &mut SliceWithUnderlyingString, global_object: &JSGlobalObject, - transfer: bool, + transfer: Transfer, ) -> JsResult { if (this.underlying.tag() == Tag::Dead || this.underlying.tag() == Tag::Empty) && this.utf8.length() > 0 @@ -169,8 +171,12 @@ fn slice_with_underlying_string_to_js_with_options( // `ZigStringSlice` encodes ownership in the variant: // `Owned`/`WTF` ⇒ allocated, `Static` ⇒ borrowed. if this.utf8.is_allocated() { - if let Some(utf16) = strings::to_utf16_alloc(this.utf8.slice(), false, false) - .map_err(|_| global_object.throw_out_of_memory())? + if let Some(utf16) = strings::to_utf16_alloc( + this.utf8.slice(), + strings::FailIfInvalid::No, + strings::Sentinel::No, + ) + .map_err(|_| global_object.throw_out_of_memory())? { // Drop the now-unused utf8 allocation. this.utf8 = ZigStringSlice::default(); @@ -200,13 +206,13 @@ fn slice_with_underlying_string_to_js_with_options( } let result = create_utf8_for_js(global_object, this.utf8.slice()); - if transfer { + if transfer == Transfer::Yes { this.utf8 = ZigStringSlice::default(); } return result; } - if transfer { + if transfer == Transfer::Yes { this.utf8 = ZigStringSlice::default(); transfer_to_js(&mut this.underlying, global_object) } else { @@ -292,7 +298,11 @@ pub mod unicode_testing_apis { }; let bytes = array_buffer.byte_slice(); - let result = match strings::to_utf16_alloc_for_real(bytes, false, true) { + let result = match strings::to_utf16_alloc_for_real( + bytes, + strings::FailIfInvalid::No, + strings::Sentinel::Yes, + ) { Ok(r) => r, Err(err) => { return Err(global_this.throw(format_args!("{err:?} toUTF16AllocForReal failed"))); diff --git a/src/jsc/event_loop.rs b/src/jsc/event_loop.rs index 29da984079e3..16ca159e08ac 100644 --- a/src/jsc/event_loop.rs +++ b/src/jsc/event_loop.rs @@ -266,6 +266,8 @@ impl Drop for EventLoopEnterGuard { } } +bun_core::bool_enum!(pub AllowDrainMicrotask); + impl EventLoop { /// Before your code enters JavaScript at the top of the event loop, call /// `loop.enter()`. If running a single callback, prefer `runCallback` instead. @@ -312,13 +314,16 @@ impl EventLoop { pub fn exit_maybe_drain_microtasks( &mut self, - allow_drain_microtask: bool, + allow_drain_microtask: AllowDrainMicrotask, ) -> Result<(), Stopped> { let count = self.entered_event_loop_count; bun_core::scoped_log!(EventLoop, "exit() = {}", count - 1); let inside_deferred = self.vm_ref().is_inside_deferred_task_queue.get(); - let result = if allow_drain_microtask && count == 1 && !inside_deferred { + let result = if allow_drain_microtask == AllowDrainMicrotask::Yes + && count == 1 + && !inside_deferred + { self.drain_microtasks() } else { Ok(()) diff --git a/src/jsc/hot_reloader.rs b/src/jsc/hot_reloader.rs index b94eb1050993..eb185f6b6060 100644 --- a/src/jsc/hot_reloader.rs +++ b/src/jsc/hot_reloader.rs @@ -62,14 +62,18 @@ impl ImportWatcher { } #[inline] - pub fn add_file_by_path_slow(&mut self, file_path: &[u8], loader: bun_ast::Loader) -> bool { + pub fn add_file_by_path_slow( + &mut self, + file_path: &[u8], + loader: bun_ast::Loader, + ) -> bun_sys::Result<()> { // Note: bun_watcher::Loader is an opaque newtype over u8; // wrap the bun_ast::Loader discriminant. match self { ImportWatcher::Hot(w) | ImportWatcher::Watch(w) => { w.add_file_by_path_slow(file_path, bun_watcher::Loader(loader as u8)) } - ImportWatcher::None => true, + ImportWatcher::None => Ok(()), } } @@ -614,8 +618,10 @@ where Output::flush(); flush_changed_paths_for_reload(); bun_core::reload_process( - CLEAR_SCREEN.load(core::sync::atomic::Ordering::Relaxed), - false, + bun_core::ClearTerminal::from_bool( + CLEAR_SCREEN.load(core::sync::atomic::Ordering::Relaxed), + ), + bun_core::MayReturn::No, ); unreachable!(); } @@ -679,8 +685,10 @@ fn arm_watch_reload_grace_timer() { crate::node_compile_cache::persist_now(); Output::flush(); bun_core::reload_process( - CLEAR_SCREEN.load(core::sync::atomic::Ordering::Relaxed), - false, + bun_core::ClearTerminal::from_bool( + CLEAR_SCREEN.load(core::sync::atomic::Ordering::Relaxed), + ), + bun_core::MayReturn::No, ); unreachable!(); }; diff --git a/src/jsc/lib.rs b/src/jsc/lib.rs index 605f0c9e9e16..e6d666fd3962 100644 --- a/src/jsc/lib.rs +++ b/src/jsc/lib.rs @@ -486,13 +486,16 @@ pub mod jsc_scheduler; #[path = "ProcessAutoKiller.rs"] pub mod process_auto_killer; +bun_core::bool_enum!(pub EvalMode); +bun_core::bool_enum!(pub ShortLivedGlobals); + /// Binding for JSCInitialize in ZigGlobalObject.cpp -pub fn initialize(eval_mode: bool) { - initialize_with(eval_mode, false); +pub fn initialize(eval_mode: EvalMode) { + initialize_with(eval_mode, ShortLivedGlobals::No); } /// `short_lived_globals`: `bun test --isolate`/`--parallel`, where each file gets a fresh global and per-global JIT code is discarded with it. -pub fn initialize_with(eval_mode: bool, short_lived_globals: bool) { +pub fn initialize_with(eval_mode: EvalMode, short_lived_globals: ShortLivedGlobals) { // The counter lives in `bun_core` so this crate doesn't depend on // `bun_analytics`. bun_core::analytics::Features::jsc_inc(); @@ -509,9 +512,9 @@ pub fn initialize_with(eval_mode: bool, short_lived_globals: bool) { env.as_ptr(), env.len(), on_jsc_invalid_env_var, - eval_mode, + eval_mode == EvalMode::Yes, one_shot, - short_lived_globals, + short_lived_globals == ShortLivedGlobals::Yes, ) }; } @@ -775,7 +778,7 @@ pub use abort_signal::{AbortSignal, AbortSignalRef}; // type (and likewise for `JSGlobalObject`). Both structs carry `UnsafeCell` // so `&T → *mut T` for FFI is sound under Stacked Borrows. pub use self::js_global_object::{GlobalRef, JSGlobalObject}; -pub use self::vm::VM; +pub use self::vm::{GcMode, VM}; /// Options for `JSGlobalObject::validate_integer_range` / `validate_bigint_range`. /// min/max are `i128` so every diff --git a/src/jsc/uuid.rs b/src/jsc/uuid.rs index 16870cc0a48b..50e6aa61b139 100644 --- a/src/jsc/uuid.rs +++ b/src/jsc/uuid.rs @@ -258,13 +258,13 @@ pub mod namespaces { ]; pub fn get(namespace: &[u8]) -> Option<&'static [u8; 16]> { - if strings::eql_case_insensitive_ascii(namespace, b"dns", true) { + if strings::eql_case_insensitive_ascii(namespace, b"dns", strings::CheckLen::Yes) { Some(DNS) - } else if strings::eql_case_insensitive_ascii(namespace, b"url", true) { + } else if strings::eql_case_insensitive_ascii(namespace, b"url", strings::CheckLen::Yes) { Some(URL) - } else if strings::eql_case_insensitive_ascii(namespace, b"oid", true) { + } else if strings::eql_case_insensitive_ascii(namespace, b"oid", strings::CheckLen::Yes) { Some(OID) - } else if strings::eql_case_insensitive_ascii(namespace, b"x500", true) { + } else if strings::eql_case_insensitive_ascii(namespace, b"x500", strings::CheckLen::Yes) { Some(X500) } else { None diff --git a/src/jsc/virtual_machine_exports.rs b/src/jsc/virtual_machine_exports.rs index b44f9f4758fc..91336e77cb5d 100644 --- a/src/jsc/virtual_machine_exports.rs +++ b/src/jsc/virtual_machine_exports.rs @@ -76,10 +76,11 @@ pub fn report_unhandled_error(global: &JSGlobalObject, value: JSValue) { crate::mark_binding!(); if !value.is_termination_exception() { - let _ = global - .bun_vm() - .as_mut() - .uncaught_exception(global, value, false); + let _ = global.bun_vm().as_mut().uncaught_exception( + global, + value, + crate::virtual_machine::IsRejection::No, + ); } } diff --git a/src/jsc/web_worker.rs b/src/jsc/web_worker.rs index 114ad0091bff..fb8135b08389 100644 --- a/src/jsc/web_worker.rs +++ b/src/jsc/web_worker.rs @@ -344,7 +344,7 @@ impl WebWorker { // its own thread; the worker never dereferences `parent`. // SAFETY: `parent` is the calling thread's live VM. let parent_ref = unsafe { &*parent }; - let store_fd = parent_ref.transpiler.resolver.store_fd; + let store_fd = parent_ref.transpiler.resolver.store_fd == bun_resolver::fs::StoreFd::Yes; let mut transform_options = (*parent_ref.transpiler.options.transform_options).clone(); if !inherit_exec_argv { let hooks = runtime_hooks().expect("RuntimeHooks not installed"); @@ -885,7 +885,7 @@ impl WebWorker { let handled = vm.as_mut().uncaught_exception( vm.global(), (*promise).result(vm.jsc_vm()), - is_rejection, + crate::virtual_machine::IsRejection::from_bool(is_rejection), ); if handled { EntryOutcome::Continue @@ -922,7 +922,7 @@ impl WebWorker { vm.global().vm().release_weak_refs(); // `Arena = bumpalo::Bump` has no collect; global mimalloc // handles reclamation. - let _ = vm.global().vm().run_gc(false); + let _ = vm.global().vm().run_gc(crate::GcMode::Async); } // Always do a first tick so we call CppTask without delay after diff --git a/src/jsc/webcore_types.rs b/src/jsc/webcore_types.rs index fa74140c563b..14b526c4feb6 100644 --- a/src/jsc/webcore_types.rs +++ b/src/jsc/webcore_types.rs @@ -211,6 +211,8 @@ const _: () = { } }; +bun_core::bool_enum!(pub IncludeContentType); + impl Blob { /// Heap-promote and mark as /// heap-allocated so `deinit` knows to free the heap box. @@ -350,7 +352,7 @@ impl Blob { /// New view onto the same store, +1 ref. #[inline] pub fn dupe(&self) -> Blob { - self.dupe_with_content_type(false) + self.dupe_with_content_type(IncludeContentType::No) } /// Alias for [`Self::dupe`]. @@ -364,7 +366,7 @@ impl Blob { /// `content_type` so freeing one side does not dangle the other (the old /// borrow path was removed because it dropped user-supplied parameters /// like multipart boundaries on a static-mime miss). - pub fn dupe_with_content_type(&self, _include_content_type: bool) -> Blob { + pub fn dupe_with_content_type(&self, _include_content_type: IncludeContentType) -> Blob { // `Option::clone` bumps the intrusive `Store::ref_count`. Blob { reported_estimated_size: Cell::new(self.reported_estimated_size.get()), diff --git a/src/libuv_sys/libuv.rs b/src/libuv_sys/libuv.rs index 17f19a46b62b..266a5a84a74e 100644 --- a/src/libuv_sys/libuv.rs +++ b/src/libuv_sys/libuv.rs @@ -1172,6 +1172,8 @@ pub struct Pipe { pipe: pipe_u, } +bun_core::bool_enum!(pub Ipc); + impl Pipe { #[inline] pub fn ipc_remote_pid(&self) -> DWORD { @@ -1182,9 +1184,9 @@ impl Pipe { /// in higher tiers map to `bun_sys::Result` themselves so this crate stays /// free of `bun_sys`. #[inline] - pub fn init(&mut self, loop_: *mut Loop, ipc: bool) -> ReturnCode { + pub fn init(&mut self, loop_: *mut Loop, ipc: Ipc) -> ReturnCode { // SAFETY: `self` is a valid `uv_pipe_t`-sized allocation. - let rc = unsafe { uv_pipe_init(loop_, self, if ipc { 1 } else { 0 }) }; + let rc = unsafe { uv_pipe_init(loop_, self, if ipc == Ipc::Yes { 1 } else { 0 }) }; if rc.0 == 0 { open_handles::add_pipe(self); } diff --git a/src/md/ansi_renderer.rs b/src/md/ansi_renderer.rs index 6fbd68486007..0d1061934476 100644 --- a/src/md/ansi_renderer.rs +++ b/src/md/ansi_renderer.rs @@ -2394,7 +2394,7 @@ fn is_js_lang(lang: &[u8]) -> bool { b"cts", ]; for n in NAMES { - if strings::eql_case_insensitive_ascii(lang, n, true) { + if strings::eql_case_insensitive_ascii(lang, n, strings::CheckLen::Yes) { return true; } } @@ -2468,7 +2468,7 @@ pub fn detect_kitty_graphics() -> bool { // TERM=dumb is the standard opt-out for any ESC handling — bail // before any env match or probe runs. if let Some(term) = bun_core::getenv_z(bun_core::zstr!("TERM")) { - if strings::eql_case_insensitive_ascii(term, b"dumb", true) { + if strings::eql_case_insensitive_ascii(term, b"dumb", strings::CheckLen::Yes) { return false; } } @@ -2488,10 +2488,10 @@ pub fn detect_kitty_graphics() -> bool { } } if let Some(tp) = bun_core::getenv_z(bun_core::zstr!("TERM_PROGRAM")) { - if strings::eql_case_insensitive_ascii(tp, b"wezterm", true) { + if strings::eql_case_insensitive_ascii(tp, b"wezterm", strings::CheckLen::Yes) { return true; } - if strings::eql_case_insensitive_ascii(tp, b"ghostty", true) { + if strings::eql_case_insensitive_ascii(tp, b"ghostty", strings::CheckLen::Yes) { return true; } } diff --git a/src/md/autolinks.rs b/src/md/autolinks.rs index 37d6aad62b82..64571916b76e 100644 --- a/src/md/autolinks.rs +++ b/src/md/autolinks.rs @@ -28,7 +28,7 @@ pub(crate) fn is_emph_boundary_resolved( if al.beg > 0 { let prev = content[al.beg - 1]; if prev == b'*' || prev == b'_' || prev == b'~' { - if !check_left_boundary(content, al.beg, false) { + if !check_left_boundary(content, al.beg, AllowEmph::No) { // Left boundary failed strict check, emphasis char caused the relaxed match. // Verify it's actually resolved. let mut found_resolved = false; @@ -51,7 +51,7 @@ pub(crate) fn is_emph_boundary_resolved( if al.end < content.len() { let next = content[al.end]; if next == b'*' || next == b'_' || next == b'~' { - if !check_right_boundary(content, al.end, false) { + if !check_right_boundary(content, al.end, AllowEmph::No) { let mut found_resolved = false; for d in resolved { if d.pos <= al.end @@ -158,23 +158,25 @@ fn is_in_set(c: u8, set: &[u8]) -> bool { false } +bun_core::bool_enum!(pub(crate) AllowEmph); + /// Check left boundary for permissive autolinks. -/// When `allow_emph` is true, emphasis delimiters (*_~) are also valid boundaries. -fn check_left_boundary(content: &[u8], pos: usize, allow_emph: bool) -> bool { +/// With `AllowEmph::Yes`, emphasis delimiters (*_~) are also valid boundaries. +fn check_left_boundary(content: &[u8], pos: usize, allow_emph: AllowEmph) -> bool { if pos == 0 { return true; } match content[pos - 1] { b' ' | b'\t' | b'\n' | b'\r' | 0x0B | 0x0C => true, b'(' | b'{' | b'[' => true, - b'*' | b'_' | b'~' => allow_emph, + b'*' | b'_' | b'~' => allow_emph == AllowEmph::Yes, _ => false, } } /// Check right boundary for permissive autolinks. -/// When `allow_emph` is true, emphasis delimiters (*_~) are also valid boundaries. -fn check_right_boundary(content: &[u8], pos: usize, allow_emph: bool) -> bool { +/// With `AllowEmph::Yes`, emphasis delimiters (*_~) are also valid boundaries. +fn check_right_boundary(content: &[u8], pos: usize, allow_emph: AllowEmph) -> bool { if pos >= content.len() { return true; } @@ -182,7 +184,7 @@ fn check_right_boundary(content: &[u8], pos: usize, allow_emph: bool) -> bool { b' ' | b'\t' | b'\n' | b'\r' | 0x0B | 0x0C => true, b')' | b'}' | b']' | b'<' => true, b'.' | b'!' | b'?' | b',' | b';' | b'&' => true, - b'*' | b'_' | b'~' => allow_emph, + b'*' | b'_' | b'~' => allow_emph == AllowEmph::Yes, _ => false, } } @@ -197,7 +199,7 @@ struct Scheme { pub(crate) fn find_permissive_autolink( content: &[u8], pos: usize, - allow_emph: bool, + allow_emph: AllowEmph, ) -> AutolinkResult { if pos >= content.len() { return None; diff --git a/src/md/containers.rs b/src/md/containers.rs index 6c500200a125..50769b1e8882 100644 --- a/src/md/containers.rs +++ b/src/md/containers.rs @@ -3,6 +3,7 @@ use core::mem::{align_of, size_of}; use bun_alloc::AllocError; use crate::autolinks::is_list_bullet; +use crate::inlines::TrimTrailing; use crate::parser::{self, BlockHeader, Parser}; use crate::types::{self, BlockType, Container, VerbatimLine}; @@ -273,9 +274,9 @@ impl Parser<'_> { BlockType::Code => self.process_code_block(&block_lines, data, flags)?, BlockType::Html => self.process_html_block(&block_lines)?, BlockType::Table => self.process_table_block(&block_lines, data)?, - BlockType::P => self.process_leaf_block(&block_lines, true)?, - BlockType::H => self.process_leaf_block(&block_lines, true)?, - _ => self.process_leaf_block(&block_lines, false)?, + BlockType::P => self.process_leaf_block(&block_lines, TrimTrailing::Yes)?, + BlockType::H => self.process_leaf_block(&block_lines, TrimTrailing::Yes)?, + _ => self.process_leaf_block(&block_lines, TrimTrailing::No)?, } if !is_in_tight_list || block_type != BlockType::P { self.leave_block(block_type, data)?; diff --git a/src/md/inlines.rs b/src/md/inlines.rs index 92f9cd303c25..719aba959437 100644 --- a/src/md/inlines.rs +++ b/src/md/inlines.rs @@ -1,6 +1,6 @@ -use crate::autolinks::{find_permissive_autolink, is_emph_boundary_resolved}; +use crate::autolinks::{AllowEmph, find_permissive_autolink, is_emph_boundary_resolved}; use crate::helpers; -use crate::links::{BracketMatches, LabelLeave}; +use crate::links::{BracketMatches, LabelLeave, LinkKind}; use crate::parser::{self, Parser}; use crate::types::{SpanType, TextType, VerbatimLine}; @@ -123,6 +123,8 @@ impl HtmlScanMemo { } } +bun_core::bool_enum!(pub(crate) TrimTrailing); + impl Parser<'_> { /// Merge all lines into buffer with \n between them (unmodified), /// then process inlines on the merged text. Hard/soft breaks are detected @@ -130,7 +132,7 @@ impl Parser<'_> { pub(crate) fn process_leaf_block( &mut self, block_lines: &[VerbatimLine], - trim_trailing: bool, + trim_trailing: TrimTrailing, ) -> Result<(), parser::Error> { if block_lines.is_empty() { return Ok(()); @@ -152,7 +154,7 @@ impl Parser<'_> { // For headings, trim trailing whitespace let mut merged_len = self.buffer.len(); - if trim_trailing { + if trim_trailing == TrimTrailing::Yes { while merged_len > 0 && (self.buffer[merged_len - 1] == b' ' || self.buffer[merged_len - 1] == b'\t') { @@ -441,7 +443,9 @@ impl Parser<'_> { if i > text_start { self.emit_text(TextType::Normal, &content[text_start..i])?; } - if let Some(parse) = self.process_link(content, i, false, &brackets, base)? { + if let Some(parse) = + self.process_link(content, i, LinkKind::Link, &brackets, base)? + { enter_label!(parse); } else { self.emit_text(TextType::Normal, b"[")?; @@ -456,7 +460,9 @@ impl Parser<'_> { if i > text_start { self.emit_text(TextType::Normal, &content[text_start..i])?; } - if let Some(parse) = self.process_link(content, i + 1, true, &brackets, base)? { + if let Some(parse) = + self.process_link(content, i + 1, LinkKind::Image, &brackets, base)? + { enter_label!(parse); } else { self.emit_text(TextType::Normal, b"!")?; @@ -476,9 +482,9 @@ impl Parser<'_> { || (c == b'.' && self.flags.permissive_www_autolinks)) { // First try with strict boundaries, then with relaxed (emphasis-aware) - let mut al = find_permissive_autolink(content, i, false); + let mut al = find_permissive_autolink(content, i, AllowEmph::No); if al.is_none() { - al = find_permissive_autolink(content, i, true); + al = find_permissive_autolink(content, i, AllowEmph::Yes); if let Some(a) = al { if !is_emph_boundary_resolved(content, a, &resolved) { al = None; diff --git a/src/md/links.rs b/src/md/links.rs index 85943eb5f095..a4fa75a58201 100644 --- a/src/md/links.rs +++ b/src/md/links.rs @@ -28,6 +28,8 @@ pub(crate) struct BracketLinkMatch { pub(crate) link_end: usize, } +bun_core::bool_enum!(pub(crate) LinkKind { Link, Image }); + /// A successfully parsed link/image/wikilink whose opening span has been /// emitted. The caller renders `content[label_start..label_end]` as inline /// content, performs `leave`, and resumes at `link_end`. Returning this @@ -309,12 +311,12 @@ impl Parser<'_> { &mut self, dest: &[u8], title: &[u8], - is_image: bool, + is_image: LinkKind, ) -> Result { if self.image_nesting_level > 0 { // Inside image alt text: emit only text, no HTML tags Ok(LabelLeave::AltText) - } else if is_image { + } else if is_image == LinkKind::Image { self.renderer.enter_span( Span::Img, SpanAttrs { @@ -343,7 +345,7 @@ impl Parser<'_> { &mut self, content: &[u8], start: usize, - is_image: bool, + is_image: LinkKind, brackets: &BracketMatches, base: usize, ) -> Result, parser::Error> { @@ -490,7 +492,7 @@ impl Parser<'_> { let dest = &content[dest_start..dest_end]; // Link nesting prohibition: links cannot contain other links (CommonMark §6.7) - if !is_image + if is_image == LinkKind::Link && has_inner_bracket && self.label_contains_link(label, brackets, base + start + 1) { @@ -539,7 +541,7 @@ impl Parser<'_> { let dest: Box<[u8]> = Box::from(&ref_def.dest[..]); let title: Box<[u8]> = Box::from(&ref_def.title[..]); // Link nesting prohibition - if !is_image + if is_image == LinkKind::Link && has_inner_bracket && self.label_contains_link(label, brackets, base + start + 1) { @@ -574,7 +576,7 @@ impl Parser<'_> { let dest: Box<[u8]> = Box::from(&ref_def.dest[..]); let title: Box<[u8]> = Box::from(&ref_def.title[..]); // Link nesting prohibition - if !is_image + if is_image == LinkKind::Link && has_inner_bracket && self.label_contains_link(label, brackets, base + start + 1) { diff --git a/src/md/render_blocks.rs b/src/md/render_blocks.rs index 19061a52fb0a..c3e7a05ffa4d 100644 --- a/src/md/render_blocks.rs +++ b/src/md/render_blocks.rs @@ -2,6 +2,8 @@ use super::helpers; use super::parser::{Error as ParserError, Parser}; use super::types::{self, BlockType, JsResult, TextType, VerbatimLine}; +bun_core::bool_enum!(pub(crate) TableRowKind { Body, Header }); + impl Parser<'_> { pub(crate) fn enter_block( &mut self, @@ -80,7 +82,7 @@ impl Parser<'_> { // First line is header, second is underline, rest are body self.enter_block(BlockType::Thead, 0, 0)?; self.enter_block(BlockType::Tr, 0, 0)?; - self.process_table_row(block_lines[0], true, col_count)?; + self.process_table_row(block_lines[0], TableRowKind::Header, col_count)?; self.leave_block(BlockType::Tr, 0)?; self.leave_block(BlockType::Thead, 0)?; @@ -88,7 +90,7 @@ impl Parser<'_> { self.enter_block(BlockType::Tbody, 0, 0)?; for vline in &block_lines[2..] { self.enter_block(BlockType::Tr, 0, 0)?; - self.process_table_row(*vline, false, col_count)?; + self.process_table_row(*vline, TableRowKind::Body, col_count)?; self.leave_block(BlockType::Tr, 0)?; } self.leave_block(BlockType::Tbody, 0)?; @@ -99,9 +101,10 @@ impl Parser<'_> { pub(crate) fn process_table_row( &mut self, vline: VerbatimLine, - is_header: bool, + is_header: TableRowKind, col_count: u32, ) -> Result<(), ParserError> { + let is_header = is_header == TableRowKind::Header; let row_text = &self.text[vline.beg as usize..vline.end as usize]; let mut start: usize = 0; let mut cell_index: u32 = 0; diff --git a/src/options_types/global_cache.rs b/src/options_types/global_cache.rs index 997fc8099f48..177ecf7c2686 100644 --- a/src/options_types/global_cache.rs +++ b/src/options_types/global_cache.rs @@ -19,6 +19,8 @@ bun_core::comptime_string_map! { }; } +bun_core::bool_enum!(pub HasNodeModulesFolder); + impl GlobalCache { /// The map type is a zero-sized handle, so this is the same map as the /// module-level `MAP` static. @@ -28,11 +30,11 @@ impl GlobalCache { self == GlobalCache::force } - pub fn can_use(self, has_a_node_modules_folder: bool) -> bool { + pub fn can_use(self, has_a_node_modules_folder: HasNodeModulesFolder) -> bool { // When there is a node_modules folder, we default to false // When there is NOT a node_modules folder, we default to true // That is the difference between these two branches. - if has_a_node_modules_folder { + if has_a_node_modules_folder == HasNodeModulesFolder::Yes { match self { GlobalCache::fallback | GlobalCache::allow_install | GlobalCache::force => true, GlobalCache::read_only | GlobalCache::disable | GlobalCache::auto => false, diff --git a/src/parsers/json.rs b/src/parsers/json.rs index 8a6fbb4fcc5e..9540c4211240 100644 --- a/src/parsers/json.rs +++ b/src/parsers/json.rs @@ -91,11 +91,16 @@ struct ParseOutput { indentation: Indentation, } +bun_core::bool_enum!( + /// Whether anything after the root value (other than trivia) is an error. + TrailingData { Allow, Reject } +); + fn parse_impl( source: &bun_ast::Source, log: &mut bun_ast::Log, opts: JSONOptions, - check_len: bool, + check_len: TrailingData, ) -> crate::Result { parse_impl_in(source, log, opts, check_len, E::TapeAlloc::Global) } @@ -104,7 +109,7 @@ fn parse_impl_in( source: &bun_ast::Source, log: &mut bun_ast::Log, opts: JSONOptions, - check_len: bool, + check_len: TrailingData, tape_alloc: E::TapeAlloc, ) -> crate::Result { let contents: &[u8] = &source.contents; @@ -192,12 +197,12 @@ fn run_stage2<'s>( log: &mut bun_ast::Log, sidx: &mut StructuralIndex<'s>, opts: JSONOptions, - check_len: bool, + check_len: TrailingData, tape_alloc: E::TapeAlloc, ) -> crate::Result { let mut parser = Parser::new(source, log, sidx, opts, tape_alloc); let root = parser.parse_value()?; - if check_len && !parser.at_trailing_end() { + if check_len == TrailingData::Reject && !parser.at_trailing_end() { return Err(parser.unexpected_here()); } let tape = parser.take_tape(); @@ -328,7 +333,14 @@ pub fn parse_utf8_impl( if source.contents.is_empty() { return Ok(empty_object_expr()); } - Ok(parse_classic(source, log, bump, JSON_OPTS, CHECK_LEN)?.root) + Ok(parse_classic( + source, + log, + bump, + JSON_OPTS, + TrailingData::from_bool(CHECK_LEN), + )? + .root) } fn parse_classic( @@ -336,14 +348,19 @@ fn parse_classic( log: &mut bun_ast::Log, bump: &Bump, opts: JSONOptions, - check_len: bool, + check_len: TrailingData, ) -> crate::Result { let opts = JSONOptions { record_value_locs: true, ..opts }; let mut out = parse_impl(source, log, opts, check_len)?; - out.root = match materialize_impl(&out.root, source, bump, opts.was_originally_macro) { + out.root = match materialize_impl( + &out.root, + source, + bump, + WasOriginallyMacro::from_bool(opts.was_originally_macro), + ) { Ok(root) => root, Err(e) => { log.add_error_fmt_opts( @@ -427,7 +444,15 @@ fn parse_to_rows( let root = Expr::init( // SAFETY: the tape's own pointer; `ParsedJson` keeps it alive, and // an empty span never dereferences it anyway. - unsafe { E::ObjectJSON::new(tape.root_ptr(), 0, 0, true, bun_ast::Loc::EMPTY) }, + unsafe { + E::ObjectJSON::new( + tape.root_ptr(), + 0, + 0, + E::IsSingleLine::Yes, + bun_ast::Loc::EMPTY, + ) + }, bun_ast::Loc { start: 0 }, ); return Ok(ParsedJson { @@ -435,7 +460,7 @@ fn parse_to_rows( tape: Some(tape), }); } - let out = parse_impl(source, log, opts, false)?; + let out = parse_impl(source, log, opts, TrailingData::Allow)?; Ok(ParsedJson { root: out.root, tape: out.tape, @@ -454,11 +479,19 @@ fn parse_to_rows_in( return Ok(Expr::init( // SAFETY: the arena-allocated tape's own pointer; it lives until the // arena resets, and an empty span never dereferences it anyway. - unsafe { E::ObjectJSON::new(tape.root_ptr(), 0, 0, true, bun_ast::Loc::EMPTY) }, + unsafe { + E::ObjectJSON::new( + tape.root_ptr(), + 0, + 0, + E::IsSingleLine::Yes, + bun_ast::Loc::EMPTY, + ) + }, bun_ast::Loc { start: 0 }, )); } - Ok(parse_impl_in(source, log, opts, false, tape_alloc)?.root) + Ok(parse_impl_in(source, log, opts, TrailingData::Allow, tape_alloc)?.root) } /// Parse package.json (comments & trailing commas allowed) into the classic `E::Object` AST. @@ -470,7 +503,7 @@ pub fn parse_package_json_utf8( if source.contents.is_empty() { return Ok(empty_object_expr()); } - Ok(parse_classic(source, log, bump, PACKAGE_JSON_OPTS, false)?.root) + Ok(parse_classic(source, log, bump, PACKAGE_JSON_OPTS, TrailingData::Allow)?.root) } #[derive(Default)] @@ -492,7 +525,7 @@ pub fn parse_package_json_utf8_with_opts( ..Default::default() }); } - let out = parse_classic(source, log, bump, opts, false)?; + let out = parse_classic(source, log, bump, opts, TrailingData::Allow)?; Ok(JsonResult { root: out.root, indentation: out.indentation, @@ -507,7 +540,7 @@ pub fn parse_for_macro( if source.contents.is_empty() { return Ok(empty_object_expr()); } - Ok(parse_classic(source, log, bump, MACRO_JSON_OPTS, false)?.root) + Ok(parse_classic(source, log, bump, MACRO_JSON_OPTS, TrailingData::Allow)?.root) } /// `tsconfig.json` / `.jsonc` (comments, trailing commas) into the classic `E::Object` AST. @@ -520,7 +553,7 @@ pub fn parse_ts_config( if source.contents.is_empty() { return Ok(empty_object_expr()); } - Ok(parse_classic(source, log, bump, TSCONFIG_OPTS, false)?.root) + Ok(parse_classic(source, log, bump, TSCONFIG_OPTS, TrailingData::Allow)?.root) } /// `.env` / `--define` values: JSON, keywords, or an implicitly-quoted string. @@ -545,15 +578,17 @@ pub fn parse_env_json( } let rewritten: &[u8] = bump.alloc_slice_copy(&unescaped); let rw_source = bun_ast::Source::init_path_string("", rewritten); - return Ok(parse_classic(&rw_source, log, bump, DOTENV_JSON_OPTS, false)?.root); + return Ok( + parse_classic(&rw_source, log, bump, DOTENV_JSON_OPTS, TrailingData::Allow)?.root, + ); } match contents[0] { b'{' | b'[' | b'0'..=b'9' | b'"' | b'\'' => { - Ok(parse_classic(source, log, bump, DOTENV_JSON_OPTS, false)?.root) + Ok(parse_classic(source, log, bump, DOTENV_JSON_OPTS, TrailingData::Allow)?.root) } b'-' | b'.' if leads_a_number(contents) => { - Ok(parse_classic(source, log, bump, DOTENV_JSON_OPTS, false)?.root) + Ok(parse_classic(source, log, bump, DOTENV_JSON_OPTS, TrailingData::Allow)?.root) } _ => { let word_len = contents @@ -913,7 +948,7 @@ pub fn materialize( log: &mut bun_ast::Log, bump: &Bump, ) -> crate::Result { - materialize_impl(root, source, bump, false).inspect_err(|_| { + materialize_impl(root, source, bump, WasOriginallyMacro::No).inspect_err(|_| { log.add_error_fmt_opts( format_args!("Document is too deeply nested"), bun_ast::AddErrorOptions { @@ -925,11 +960,13 @@ pub fn materialize( }) } +bun_core::bool_enum!(WasOriginallyMacro); + fn materialize_impl( root: &Expr, source: &bun_ast::Source, bump: &Bump, - was_originally_macro: bool, + was_originally_macro: WasOriginallyMacro, ) -> crate::Result { let m = Materializer { contents: &source.contents, @@ -948,7 +985,7 @@ fn materialize_impl( struct Materializer<'a> { contents: &'a [u8], bump: &'a Bump, - was_originally_macro: bool, + was_originally_macro: WasOriginallyMacro, stack_check: bun_core::StackCheck, overflowed: core::cell::Cell, } @@ -998,7 +1035,7 @@ impl Materializer<'_> { E::Object { properties, is_single_line: o.is_single_line, - was_originally_macro: self.was_originally_macro, + was_originally_macro: self.was_originally_macro == WasOriginallyMacro::Yes, close_brace_loc: o.close_brace_loc, ..Default::default() } @@ -1032,7 +1069,7 @@ impl Materializer<'_> { E::Array { items, is_single_line: a.is_single_line, - was_originally_macro: self.was_originally_macro, + was_originally_macro: self.was_originally_macro == WasOriginallyMacro::Yes, close_bracket_loc: a.close_bracket_loc, ..Default::default() } diff --git a/src/parsers/json5.rs b/src/parsers/json5.rs index af47b87f7258..a2f71cc307ee 100644 --- a/src/parsers/json5.rs +++ b/src/parsers/json5.rs @@ -32,6 +32,8 @@ struct Token<'a> { data: TokenData<'a>, } +bun_core::bool_enum!(Sign { Positive, Negative }); + enum TokenData<'a> { Eof, // Structural @@ -350,14 +352,14 @@ impl<'a> JSON5Parser<'a> { start: i32::try_from(self.pos).expect("int cast"), }; self.pos += 1; - break 'next TokenData::Number(self.scan_signed_value(false)?); + break 'next TokenData::Number(self.scan_signed_value(Sign::Positive)?); } b'-' => { self.token.loc = Loc { start: i32::try_from(self.pos).expect("int cast"), }; self.pos += 1; - break 'next TokenData::Number(self.scan_signed_value(true)?); + break 'next TokenData::Number(self.scan_signed_value(Sign::Negative)?); } // Strings b'"' | b'\'' => { @@ -474,7 +476,8 @@ impl<'a> JSON5Parser<'a> { true } - fn scan_signed_value(&mut self, is_negative: bool) -> Result { + fn scan_signed_value(&mut self, is_negative: Sign) -> Result { + let is_negative = is_negative == Sign::Negative; match self.peek() { b'0'..=b'9' | b'.' => { let n = self.scan_number()?; diff --git a/src/parsers/json_index.rs b/src/parsers/json_index.rs index a4487909af52..39ea05bdae1e 100644 --- a/src/parsers/json_index.rs +++ b/src/parsers/json_index.rs @@ -43,34 +43,39 @@ pub struct StructuralIndex<'c> { src_off: usize, kernel_state: [u64; 3], - use_scalar: bool, + producer: Producer, s_i: usize, s_prev_scalar: bool, s_pending_escape: bool, s_skip: usize, } +bun_core::bool_enum!( + /// Which indexer produces the structural index: the Highway SIMD kernel or the scalar fallback. + pub(crate) Producer { Simd, Scalar } +); + impl<'c> StructuralIndex<'c> { pub fn new(contents: &'c [u8]) -> Self { - Self::with_producer(contents, !bun_core::env::IS_NATIVE) + Self::with_producer(contents, Producer::from_bool(!bun_core::env::IS_NATIVE)) } - fn with_producer(contents: &'c [u8], use_scalar: bool) -> Self { + fn with_producer(contents: &'c [u8], producer: Producer) -> Self { if contents.len() > i32::MAX as usize { - let mut idx = Self::empty(contents, use_scalar); + let mut idx = Self::empty(contents, producer); idx.index_error = Some(IndexError::DocumentTooLarge); idx.done = true; return idx; } let win_cap = contents.len().min(REFILL_INPUT) + 66 + SENTINELS + LOOKBEHIND; let dirty_words = (contents.len().div_ceil(64)).div_ceil(64) + 1; - let mut idx = Self::empty(contents, use_scalar); + let mut idx = Self::empty(contents, producer); idx.win = Vec::with_capacity(win_cap); idx.dirty = vec![0; dirty_words]; idx } - fn empty(contents: &'c [u8], use_scalar: bool) -> Self { + fn empty(contents: &'c [u8], producer: Producer) -> Self { StructuralIndex { contents, win: Vec::new(), @@ -82,7 +87,7 @@ impl<'c> StructuralIndex<'c> { done: false, src_off: 0, kernel_state: [0; 3], - use_scalar, + producer, s_i: 0, s_prev_scalar: false, s_pending_escape: false, @@ -120,7 +125,7 @@ impl<'c> StructuralIndex<'c> { fn refill_once(&mut self) { let len = self.contents.len(); - if bun_core::env::IS_NATIVE && !self.use_scalar { + if bun_core::env::IS_NATIVE && self.producer == Producer::Simd { if self.src_off >= len { return self.finish(); } @@ -136,7 +141,7 @@ impl<'c> StructuralIndex<'c> { &mut self.kernel_state, ); if chunk_flags & FLAG_ODDITY != 0 { - self.use_scalar = true; + self.producer = Producer::Scalar; self.s_skip = self.base + self.win.len(); self.dirty.fill(0); return; @@ -348,7 +353,7 @@ mod tests { if simd.index_error.is_some() { return None; } - let mut scalar = StructuralIndex::with_producer(contents, true); + let mut scalar = StructuralIndex::with_producer(contents, Producer::Scalar); let (ci, cf) = collect(&mut scalar); if scalar.index_error.is_some() { return None; @@ -425,13 +430,13 @@ mod tests { doc.push_str(&tail); let mut streamed = StructuralIndex::new(doc.as_bytes()); let (si, sf) = collect(&mut streamed); - let mut scalar = StructuralIndex::with_producer(doc.as_bytes(), true); + let mut scalar = StructuralIndex::with_producer(doc.as_bytes(), Producer::Scalar); let (ci, cf) = collect(&mut scalar); assert_eq!(si, ci, "prefix {prefix:?} oddity {oddity:?}"); assert_eq!(sf, cf); let mut clean = StructuralIndex::new(strict.as_bytes()); collect(&mut clean); - assert!(!clean.use_scalar || !bun_core::env::IS_NATIVE); + assert!(clean.producer == Producer::Simd || !bun_core::env::IS_NATIVE); } } } diff --git a/src/parsers/json_stage2.rs b/src/parsers/json_stage2.rs index 29b15e5dcaaf..ff7b2fe533f5 100644 --- a/src/parsers/json_stage2.rs +++ b/src/parsers/json_stage2.rs @@ -85,6 +85,11 @@ pub(crate) fn is_exotic_whitespace(cp: CodePoint) -> bool { || strings::is_unicode_space_separator(cp as u32) } +bun_core::bool_enum!( + /// A `0`-prefixed number without a radix letter (`017`, `089`). + LegacyOctal +); + impl<'a, 's, 'i> Parser<'a, 's, 'i> { pub(crate) fn new( source: &'s Source, @@ -702,7 +707,15 @@ impl<'a, 's, 'i> Parser<'a, 's, 'i> { Ok(Expr::init( // SAFETY: `tape_ptr` is the tape allocation's own pointer, and the // tape outlives the AST (`take_tape` hands it to the caller). - unsafe { E::ArrayJSON::new(self.tape_ptr(), first, count, is_single_line, close_loc) }, + unsafe { + E::ArrayJSON::new( + self.tape_ptr(), + first, + count, + E::IsSingleLine::from_bool(is_single_line), + close_loc, + ) + }, loc, )) } @@ -831,7 +844,15 @@ impl<'a, 's, 'i> Parser<'a, 's, 'i> { let (first, count) = self.push_props_block(mark); Ok(Expr::init( // SAFETY: see `parse_array`. - unsafe { E::ObjectJSON::new(self.tape_ptr(), first, count, is_single_line, close_loc) }, + unsafe { + E::ObjectJSON::new( + self.tape_ptr(), + first, + count, + E::IsSingleLine::from_bool(is_single_line), + close_loc, + ) + }, loc, )) } @@ -957,13 +978,13 @@ impl<'a, 's, 'i> Parser<'a, 's, 'i> { } if first == b'0' && n > 1 { - let (radix, prefix_len, legacy_octal): (u32, usize, bool) = match t[1] { - b'b' | b'B' => (2, 2, false), - b'o' | b'O' => (8, 2, false), - b'x' | b'X' => (16, 2, false), - b'0'..=b'7' | b'_' => (8, 1, true), - b'8' | b'9' => (10, 1, true), - _ => (0, 0, false), + let (radix, prefix_len, legacy_octal): (u32, usize, LegacyOctal) = match t[1] { + b'b' | b'B' => (2, 2, LegacyOctal::No), + b'o' | b'O' => (8, 2, LegacyOctal::No), + b'x' | b'X' => (16, 2, LegacyOctal::No), + b'0'..=b'7' | b'_' => (8, 1, LegacyOctal::Yes), + b'8' | b'9' => (10, 1, LegacyOctal::Yes), + _ => (0, 0, LegacyOctal::No), }; if radix != 0 { return self.parse_radix_number(t, pos, radix, prefix_len, legacy_octal); @@ -1063,8 +1084,9 @@ impl<'a, 's, 'i> Parser<'a, 's, 'i> { pos: usize, radix: u32, prefix_len: usize, - legacy_octal: bool, + legacy_octal: LegacyOctal, ) -> PResult<(f64, usize)> { + let legacy_octal = legacy_octal == LegacyOctal::Yes; let n = t.len(); let mut i = prefix_len; let mut value: f64 = 0.0; diff --git a/src/parsers/toml.rs b/src/parsers/toml.rs index fef4851980f3..edb838d192fa 100644 --- a/src/parsers/toml.rs +++ b/src/parsers/toml.rs @@ -86,12 +86,17 @@ struct KeySeg<'a> { // Each scan mode returns its own narrow token type: a token that is illegal // at a grammar position cannot be produced there. +bun_core::bool_enum!( + /// `[table]` or `[[array-of-tables]]`. + HeaderKind { Table, ArrayOfTables } +); + /// What begins a top-level expression. enum LineStart<'a> { Eof, /// `[` (`aot` for `[[`) at `pos`. TableOpen { - aot: bool, + aot: HeaderKind, pos: usize, }, Key(KeySeg<'a>), @@ -155,12 +160,17 @@ enum ArrayItem<'a> { pub struct TOML; +bun_core::bool_enum!( + /// Omit key text and source bytes from diagnostics (for files that may hold secrets). + pub RedactLogs +); + impl TOML { pub fn parse<'a>( source: &'a Source, log: &mut Log, bump: &'a Bump, - redact_logs: bool, + redact_logs: RedactLogs, ) -> crate::Result { source.check_parseable_len(log, "TOML document")?; let mut parser = Parser { @@ -225,6 +235,11 @@ fn loc_of(pos: usize) -> Loc { // ── scanner ───────────────────────────────────────────────────────────────── +bun_core::bool_enum!( + /// `"""` / `'''` strings versus their single-line forms. + Multiline +); + /// Owns the byte cursor. The only component that reads source bytes; every /// public method scans one token (or one fixed construct) for one grammar /// position and skips exactly the leading trivia that position allows. @@ -234,7 +249,7 @@ struct Scanner<'a, 'log> { bump: &'a Bump, source: &'a Source, log: &'log mut Log, - redact: bool, + redact: RedactLogs, } impl<'a, 'log> Scanner<'a, 'log> { @@ -251,7 +266,7 @@ impl<'a, 'log> Scanner<'a, 'log> { source: Some(self.source), loc: loc_of(pos), len: 0, - redact_sensitive_information: self.redact, + redact_sensitive_information: self.redact == RedactLogs::Yes, }, ); PErr::Syntax @@ -265,7 +280,7 @@ impl<'a, 'log> Scanner<'a, 'log> { key: &[u8], after: &'static str, ) -> PErr { - if self.redact { + if self.redact == RedactLogs::Yes { self.err_fmt(pos, format_args!("{}{}", before, after)) } else { self.err_fmt( @@ -278,7 +293,9 @@ impl<'a, 'log> Scanner<'a, 'log> { fn err_char(&mut self, pos: usize, what: &'static str) -> PErr { match self.src.get(pos).copied() { None => self.err_fmt(pos, format_args!("{} end of file", what)), - Some(_) if self.redact => self.err_fmt(pos, format_args!("{} (redacted)", what)), + Some(_) if self.redact == RedactLogs::Yes => { + self.err_fmt(pos, format_args!("{} (redacted)", what)) + } Some(c) if c.is_ascii_graphic() => { self.err_fmt(pos, format_args!("{} '{}'", what, c as char)) } @@ -293,7 +310,7 @@ impl<'a, 'log> Scanner<'a, 'log> { while end < self.src.len() && is_bare_key_char(self.peek_at(end)) && end - pos < 64 { end += 1; } - if self.redact || end == pos { + if self.redact == RedactLogs::Yes || end == pos { return self.err(pos, b"Strings must be quoted"); } self.err_fmt( @@ -421,8 +438,8 @@ impl<'a, 'log> Scanner<'a, 'log> { let pos = self.pos; self.pos += 1; // `array-table-open = %x5B.5B`: the second bracket is adjacent. - let aot = self.peek() == b'['; - if aot { + let aot = HeaderKind::from_bool(self.peek() == b'['); + if aot == HeaderKind::ArrayOfTables { self.pos += 1; } return Ok(LineStart::TableOpen { aot, pos }); @@ -435,11 +452,11 @@ impl<'a, 'log> Scanner<'a, 'log> { let pos = self.pos; match self.peek() { b'"' => { - let (text, _) = self.scan_basic_string(false)?; + let (text, _) = self.scan_basic_string(Multiline::No)?; Ok(KeySeg { text, pos }) } b'\'' => { - let (text, _) = self.scan_literal_string(false)?; + let (text, _) = self.scan_literal_string(Multiline::No)?; Ok(KeySeg { text, pos }) } c if is_bare_key_char(c) => { @@ -481,7 +498,8 @@ impl<'a, 'log> Scanner<'a, 'log> { /// After a key segment in a header: `ws` then `.` or the closing /// bracket(s). `]]` must be adjacent (`array-table-close = %x5D.5D`). - fn scan_header_sep(&mut self, aot: bool) -> PResult { + fn scan_header_sep(&mut self, aot: HeaderKind) -> PResult { + let aot = aot == HeaderKind::ArrayOfTables; self.skip_ws(); match self.peek() { b'.' => { @@ -605,17 +623,17 @@ impl<'a, 'log> Scanner<'a, 'log> { let data = match self.peek() { b'"' => { let (text, is_ascii) = if self.src[self.pos..].starts_with(b"\"\"\"") { - self.scan_basic_string(true)? + self.scan_basic_string(Multiline::Yes)? } else { - self.scan_basic_string(false)? + self.scan_basic_string(Multiline::No)? }; ValueData::String { text, is_ascii } } b'\'' => { let (text, is_ascii) = if self.src[self.pos..].starts_with(b"'''") { - self.scan_literal_string(true)? + self.scan_literal_string(Multiline::Yes)? } else { - self.scan_literal_string(false)? + self.scan_literal_string(Multiline::No)? }; ValueData::String { text, is_ascii } } @@ -1146,7 +1164,8 @@ impl<'a, 'log> Scanner<'a, 'log> { /// Returns (decoded bytes, is_ascii). The content borrows the source /// until an escape, CRLF normalization, or quote-run handling forces a /// copy — most strings have neither. - fn scan_basic_string(&mut self, multiline: bool) -> PResult<(&'a [u8], bool)> { + fn scan_basic_string(&mut self, multiline: Multiline) -> PResult<(&'a [u8], bool)> { + let multiline = multiline == Multiline::Yes; let open_pos = self.pos; self.pos += if multiline { 3 } else { 1 }; @@ -1367,7 +1386,8 @@ impl<'a, 'log> Scanner<'a, 'log> { /// Returns (decoded bytes, is_ascii). Literal strings have no escapes, so /// the content borrows the source unless CRLF normalization forces a copy. - fn scan_literal_string(&mut self, multiline: bool) -> PResult<(&'a [u8], bool)> { + fn scan_literal_string(&mut self, multiline: Multiline) -> PResult<(&'a [u8], bool)> { + let multiline = multiline == Multiline::Yes; let open_pos = self.pos; self.pos += if multiline { 3 } else { 1 }; @@ -1531,7 +1551,7 @@ impl<'a, 'log> Parser<'a, 'log> { fn parse_table_header( &mut self, root: *mut E::Object, - aot: bool, + aot: HeaderKind, header_pos: usize, ) -> PResult<*mut E::Object> { let mut path: ArenaVec<'a, KeySeg<'a>> = ArenaVec::with_capacity_in(0, self.bump); @@ -1551,9 +1571,10 @@ impl<'a, 'log> Parser<'a, 'log> { &mut self, root: *mut E::Object, path: &[KeySeg<'a>], - is_aot: bool, + is_aot: HeaderKind, header_pos: usize, ) -> PResult<*mut E::Object> { + let is_aot = is_aot == HeaderKind::ArrayOfTables; let mut cur: *mut E::Object = root; for (i, seg) in path.iter().enumerate() { let last = i + 1 == path.len(); diff --git a/src/parsers/xml.rs b/src/parsers/xml.rs index 1a51bb4bce13..4caa12214d40 100644 --- a/src/parsers/xml.rs +++ b/src/parsers/xml.rs @@ -721,13 +721,25 @@ enum FrameKind { Declarations, } +bun_core::bool_enum!( + /// General (`&name;`) or parameter (`%name;`) entity. + EntityKind { General, Parameter } +); + +bun_core::bool_enum!( + /// Where a general entity reference stands: element content or an attribute value. + RefContext { Content, Attribute } +); + +bun_core::bool_enum!(Endian { Little, Big }); + struct Frame<'a, U: Unit> { src: &'a [U], pos: usize, id: u32, kind: FrameKind, /// The entity this frame is the replacement text of: (name, is-parameter). - entity: Option<(&'a [U], bool)>, + entity: Option<(&'a [U], EntityKind)>, /// Where diagnostics for tokens read from this frame point: the position /// of the outermost reference in the document. report_pos: usize, @@ -744,7 +756,7 @@ struct Scanner<'a, 'log, U: Unit> { pos: usize, frame_id: u32, frame_kind: FrameKind, - frame_entity: Option<(&'a [U], bool)>, + frame_entity: Option<(&'a [U], EntityKind)>, frame_report_pos: usize, suspended: Vec>, next_frame_id: u32, @@ -1060,7 +1072,7 @@ impl<'a, 'log, U: Unit> Scanner<'a, 'log, U> { &mut self, text: &'a [U], kind: FrameKind, - entity: (&'a [U], bool), + entity: (&'a [U], EntityKind), ref_pos: usize, ) -> PResult<()> { if self.suspended.len() >= MAX_ENTITY_DEPTH { @@ -1120,7 +1132,7 @@ impl<'a, 'log, U: Unit> Scanner<'a, 'log, U> { &mut self, name: &'a [U], ref_pos: usize, - in_attribute: bool, + in_attribute: RefContext, ) -> PResult> { if let Some(c) = predefined_entity(name) { return Ok(Resolved::Byte(c)); @@ -1128,12 +1140,13 @@ impl<'a, 'log, U: Unit> Scanner<'a, 'log, U> { match self.entities.general.get(name).copied() { Some(EntityValue::Internal(text)) => Ok(Resolved::Text(text)), // WFC: No External Entity References. - Some(EntityValue::External) if in_attribute => Err(self.err_named( - ref_pos, - "Attribute values cannot reference external entity", - name, - "", - )), + Some(EntityValue::External) if in_attribute == RefContext::Attribute => Err(self + .err_named( + ref_pos, + "Attribute values cannot reference external entity", + name, + "", + )), // A non-validating processor may decline to include an external // entity but must let the application know it was there // (§4.4.3): the reference is kept as written. @@ -1169,9 +1182,12 @@ impl<'a, 'log, U: Unit> Scanner<'a, 'log, U> { fn include_parameter_entity(&mut self, name: &'a [U], ref_pos: usize) -> PResult<()> { self.saw_pe_reference = true; match self.entities.parameter.get(name).copied() { - Some(EntityValue::Internal(text)) => { - self.push_frame(text, FrameKind::Declarations, (name, true), ref_pos) - } + Some(EntityValue::Internal(text)) => self.push_frame( + text, + FrameKind::Declarations, + (name, EntityKind::Parameter), + ref_pos, + ), Some(_) => { self.saw_unread_pe = true; Ok(()) @@ -1208,14 +1224,14 @@ impl<'a, 'log, U: Unit> Scanner<'a, 'log, U> { } else if matches!(self.encoding, InputEncoding::Text | InputEncoding::Latin1) { // A JS string is characters, not bytes: nothing to detect. } else if bytes.starts_with(b"\xFE\xFF") { - self.transcode_utf16(&bytes[2..], true)?; + self.transcode_utf16(&bytes[2..], Endian::Big)?; } else if bytes.starts_with(b"\xFF\xFE") { - self.transcode_utf16(&bytes[2..], false)?; + self.transcode_utf16(&bytes[2..], Endian::Little)?; } else if bytes.starts_with(b"\x00<") { - self.transcode_utf16(bytes, true)?; + self.transcode_utf16(bytes, Endian::Big)?; self.needs_utf16_declaration = true; } else if bytes.starts_with(b"<\x00") { - self.transcode_utf16(bytes, false)?; + self.transcode_utf16(bytes, Endian::Little)?; self.needs_utf16_declaration = true; } } @@ -1251,7 +1267,7 @@ impl<'a, 'log, U: Unit> Scanner<'a, 'log, U> { } } - fn transcode_utf16(&mut self, payload: &[u8], big_endian: bool) -> PResult<()> { + fn transcode_utf16(&mut self, payload: &[u8], big_endian: Endian) -> PResult<()> { let (pairs, rest) = payload.as_chunks::<2>(); if !rest.is_empty() { return Err(self.err(payload.len(), "UTF-16 input has an odd number of bytes")); @@ -1259,7 +1275,7 @@ impl<'a, 'log, U: Unit> Scanner<'a, 'log, U> { let units: Vec = pairs .iter() .map(|&p| { - if big_endian { + if big_endian == Endian::Big { u16::from_be_bytes(p) } else { u16::from_le_bytes(p) @@ -1627,11 +1643,14 @@ impl<'a, 'log, U: Unit> Scanner<'a, 'log, U> { } else { let name = self .scan_reference_name("Expected an entity name after '&' but found")?; - match self.resolve_general_entity(name, ref_pos, true)? { + match self.resolve_general_entity(name, ref_pos, RefContext::Attribute)? { Resolved::Byte(byte) => b.push(U::ascii(byte)), - Resolved::Text(text) => { - self.push_frame(text, FrameKind::Literal, (name, false), ref_pos)? - } + Resolved::Text(text) => self.push_frame( + text, + FrameKind::Literal, + (name, EntityKind::General), + ref_pos, + )?, Resolved::Unexpanded => Self::push_reference(b, name), } } @@ -1702,9 +1721,12 @@ impl<'a, 'log, U: Unit> Scanner<'a, 'log, U> { } self.saw_pe_reference = true; match self.entities.parameter.get(name).copied() { - Some(EntityValue::Internal(text)) => { - self.push_frame(text, FrameKind::Literal, (name, true), ref_pos)? - } + Some(EntityValue::Internal(text)) => self.push_frame( + text, + FrameKind::Literal, + (name, EntityKind::Parameter), + ref_pos, + )?, Some(_) => { return Err(self.err_named( ref_pos, @@ -2341,11 +2363,14 @@ impl<'a, 'log, U: Unit> Scanner<'a, 'log, U> { } else { let name = self .scan_reference_name("Expected an entity name after '&' but found")?; - match self.resolve_general_entity(name, ref_pos, false)? { + match self.resolve_general_entity(name, ref_pos, RefContext::Content)? { Resolved::Byte(byte) => b.push(U::ascii(byte)), - Resolved::Text(text) => { - self.push_frame(text, FrameKind::Content, (name, false), ref_pos)? - } + Resolved::Text(text) => self.push_frame( + text, + FrameKind::Content, + (name, EntityKind::General), + ref_pos, + )?, Resolved::Unexpanded => Self::push_reference(b, name), } start = self.pos; @@ -2543,7 +2568,8 @@ impl<'a> Tape<'a> { let (first, count) = tape.append_props(props, locs); self.props.truncate(mark); // SAFETY: as above — the tape's own pointer, and it outlives the node. - let object = unsafe { E::ObjectJSON::new(self.tape, first, count, false, loc) }; + let object = + unsafe { E::ObjectJSON::new(self.tape, first, count, E::IsSingleLine::No, loc) }; let Data::EObjectJSON(row) = Expr::init(object, loc).data else { unreachable!() }; @@ -2576,7 +2602,7 @@ impl<'a> Tape<'a> { // SAFETY: see `object_from`. let (first, count) = unsafe { tape.as_mut() }.append_items(items, locs); // SAFETY: see `object_from`. - let array = unsafe { E::ArrayJSON::new(tape, first, count, false, loc) }; + let array = unsafe { E::ArrayJSON::new(tape, first, count, E::IsSingleLine::No, loc) }; let Data::EArrayJSON(row) = Expr::init(array, loc).data else { unreachable!() }; @@ -3095,6 +3121,16 @@ impl<'a, U: Unit> AttList<'a, U> { /// `Parser::attribute_names`. const LINEAR_ATTRIBUTE_LIMIT: usize = 8; +bun_core::bool_enum!( + /// The declaration an `ExternalID` belongs to; a NOTATION also admits a bare `PublicID`. + ForNotation +); + +bun_core::bool_enum!( + /// What an ATTLIST `( x | y )` group lists: name tokens (an enumeration) or notation names. + EnumerationOf { Nmtokens, NotationNames } +); + /// Elements open at once. Parsing is iterative, so this is not about the /// native stack; it bounds memory on hostile input and keeps the (recursive) /// consumers of the result safe. `Bun.XML.parse` reports it as a `RangeError`. @@ -3499,7 +3535,7 @@ impl<'a, 'log, U: Unit, S: Sink<'a, U>> Parser<'a, 'log, U, S> { if matches!(self.scanner.tok.kind, Kind::Name(n) if eq_ascii(n, b"SYSTEM") || eq_ascii(n, b"PUBLIC")) { self.require_spaced()?; - self.parse_external_id(false)?; + self.parse_external_id(ForNotation::No)?; self.scanner.has_external_subset = true; } match self.scanner.tok.kind { @@ -3523,7 +3559,7 @@ impl<'a, 'log, U: Unit, S: Sink<'a, U>> Parser<'a, 'log, U, S> { /// also a `PublicID` without system identifier; the current token is /// `SYSTEM` or `PUBLIC`. The identifiers are checked and dropped (nothing /// external is read). Ends on the token after the last literal. - fn parse_external_id(&mut self, notation: bool) -> PResult<()> { + fn parse_external_id(&mut self, notation: ForNotation) -> PResult<()> { if matches!(self.scanner.tok.kind, Kind::Name(n) if eq_ascii(n, b"SYSTEM")) { self.advance_literal(Literal::System)?; if !matches!(self.scanner.tok.kind, Kind::Literal(_)) { @@ -3542,7 +3578,7 @@ impl<'a, 'log, U: Unit, S: Sink<'a, U>> Parser<'a, 'log, U, S> { self.require_spaced()?; return self.advance(); } - if notation { + if notation == ForNotation::Yes { return Ok(()); } Err(self.unexpected("a quoted system identifier after the public identifier")) @@ -3788,12 +3824,12 @@ impl<'a, 'log, U: Unit, S: Sink<'a, U>> Parser<'a, 'log, U, S> { return Err(self.unexpected("'(' after NOTATION")); } self.require_spaced()?; - self.parse_enumeration(true)?; + self.parse_enumeration(EnumerationOf::NotationNames)?; false } Kind::ParenOpen => { self.require_spaced()?; - self.parse_enumeration(false)?; + self.parse_enumeration(EnumerationOf::Nmtokens)?; false } _ => return Err(self.unexpected("an attribute type (CDATA, ID, IDREF, IDREFS, ENTITY, ENTITIES, NMTOKEN, NMTOKENS, NOTATION or an enumeration)")), @@ -3841,7 +3877,8 @@ impl<'a, 'log, U: Unit, S: Sink<'a, U>> Parser<'a, 'log, U, S> { /// `'(' S? x (S? '|' S? x)* S? ')'` where `x` is a `Name` (NOTATION /// types, `names`) or an `Nmtoken` (enumerations); the current token is /// `(`. Ends on `)`. - fn parse_enumeration(&mut self, names: bool) -> PResult<()> { + fn parse_enumeration(&mut self, names: EnumerationOf) -> PResult<()> { + let names = names == EnumerationOf::NotationNames; loop { self.advance()?; match self.scanner.tok.kind { @@ -3890,7 +3927,7 @@ impl<'a, 'log, U: Unit, S: Sink<'a, U>> Parser<'a, 'log, U, S> { } Kind::Name(n) if eq_ascii(n, b"SYSTEM") || eq_ascii(n, b"PUBLIC") => { self.require_spaced()?; - self.parse_external_id(false)?; + self.parse_external_id(ForNotation::No)?; if matches!(self.scanner.tok.kind, Kind::Name(n) if eq_ascii(n, b"NDATA")) { self.require_spaced()?; if parameter { @@ -3935,7 +3972,7 @@ impl<'a, 'log, U: Unit, S: Sink<'a, U>> Parser<'a, 'log, U, S> { return Err(self.unexpected("SYSTEM or PUBLIC in the notation declaration")); } self.require_spaced()?; - self.parse_external_id(true)?; + self.parse_external_id(ForNotation::Yes)?; self.expect_gt("'>' to end the notation declaration") } diff --git a/src/parsers/yaml.rs b/src/parsers/yaml.rs index 05b9187015f4..e09b89c53c55 100644 --- a/src/parsers/yaml.rs +++ b/src/parsers/yaml.rs @@ -170,6 +170,11 @@ impl Chomp { pub(crate) const DEFAULT: Chomp = Chomp::Clip; } +bun_core::bool_enum!( + /// [170] `|` literal or [174] `>` folded block scalar. + BlockScalarStyle { Literal, Folded } +); + // ─────────────────────────────────────────────────────────────────────────── // Indent // ─────────────────────────────────────────────────────────────────────────── @@ -4881,7 +4886,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { &mut self, indent_indicator: IndentIndicator, chomp: Chomp, - folded: bool, + folded: BlockScalarStyle, start: Pos, line: Line, ) -> Result, ParseError> { @@ -4893,7 +4898,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { content_indent: Indent, max_leading_indent: Indent, line: Line, - folded: bool, + folded: BlockScalarStyle, explicit_indent: bool, /// Folded: was the previous content line more-indented (started with /// space/tab beyond content_indent)? Breaks adjacent to such lines @@ -4963,7 +4968,10 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { // First content of a new line after one or more line breaks: // flush them, then remember whether *this* line is more-indented // for the next fold decision. - if self.folded && !self.prev_more_indented && !self.cur_more_indented { + if self.folded == BlockScalarStyle::Folded + && !self.prev_more_indented + && !self.cur_more_indented + { if self.leading_newlines == 1 { self.text.push(Enc::ch(b' ')); } else { @@ -5233,8 +5241,13 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { let (indent_indicator, chomp) = self.scan_block_header()?; - let result = - self.scan_auto_indented_literal_scalar(indent_indicator, chomp, false, start, line); + let result = self.scan_auto_indented_literal_scalar( + indent_indicator, + chomp, + BlockScalarStyle::Literal, + start, + line, + ); self.whitespace_buf.clear(); result } @@ -5245,7 +5258,13 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { let (indent_indicator, chomp) = self.scan_block_header()?; - self.scan_auto_indented_literal_scalar(indent_indicator, chomp, true, start, line) + self.scan_auto_indented_literal_scalar( + indent_indicator, + chomp, + BlockScalarStyle::Folded, + start, + line, + ) } fn scan_single_quoted_scalar(&mut self) -> Result, ParseError> { diff --git a/src/paths/Path.rs b/src/paths/Path.rs index 61f6da43926d..d79bc281b2a7 100644 --- a/src/paths/Path.rs +++ b/src/paths/Path.rs @@ -364,6 +364,8 @@ pub(crate) struct Buf { len: usize, } +bun_core::bool_enum!(pub(crate) AddSeparator); + impl Buf { #[inline] fn set_length(&mut self, new_len: usize) { @@ -371,9 +373,9 @@ impl Buf { } /// Append `characters` (same code-unit width as `U`), optionally prefixing a separator. - fn append(&mut self, characters: &[U], add_separator: bool) { + fn append(&mut self, characters: &[U], add_separator: AddSeparator) { let buf = U::buffer_as_mut_slice(&mut self.pooled); - if add_separator { + if add_separator == AddSeparator::Yes { buf[self.len] = match PathSeparators::from_u8(SEP_OPT) { PathSeparators::Any | PathSeparators::Auto => U::from_u8(SEP), PathSeparators::Posix => U::from_u8(SEP_POSIX), @@ -402,9 +404,9 @@ impl Buf { } /// Append `characters` of the *other* code-unit width, transcoding into the buffer. - fn append_other(&mut self, characters: &[U::Other], add_separator: bool) { + fn append_other(&mut self, characters: &[U::Other], add_separator: AddSeparator) { let buf = U::buffer_as_mut_slice(&mut self.pooled); - if add_separator { + if add_separator == AddSeparator::Yes { buf[self.len] = match PathSeparators::from_u8(SEP_OPT) { PathSeparators::Any | PathSeparators::Auto => U::from_u8(SEP), PathSeparators::Posix => U::from_u8(SEP_POSIX), @@ -646,7 +648,7 @@ impl let mut this = Self::init(); // top_level_dir is &[u8]; `buf_append_input` routes it through // `append_other` (transcoding) when U == u16. - this.buf_append_input(trimmed, false); + this.buf_append_input(trimmed, AddSeparator::No); this } @@ -656,10 +658,13 @@ impl #[cfg(windows)] { - this.buf_append_input(crate::windows::long_path_prefix_for::(), false); + this.buf_append_input( + crate::windows::long_path_prefix_for::(), + AddSeparator::No, + ); } - this.buf_append_input(trimmed, false); + this.buf_append_input(trimmed, AddSeparator::No); this } @@ -779,17 +784,20 @@ impl let mut this = Self::init(); #[cfg(windows)] { - this.buf_append_input(crate::windows::long_path_prefix_for::(), false); + this.buf_append_input( + crate::windows::long_path_prefix_for::(), + AddSeparator::No, + ); } - this.buf_append_input(trimmed, false); + this.buf_append_input(trimmed, AddSeparator::No); Ok(this) } pub fn from(input: &[C]) -> options::Result { let trimmed = Self::trim_input_for_kind(input)?; let mut this = Self::init(); - this.buf_append_input(trimmed, false); + this.buf_append_input(trimmed, AddSeparator::No); Ok(this) } @@ -908,6 +916,7 @@ impl !self.slice()[self.len() - 1].eq_ascii(PathSeparators::from_u8(SEP_OPT).char()) } }; + let add_sep = AddSeparator::from_bool(needs_sep); match Kind::from_u8(KIND) { Kind::Abs => { @@ -940,7 +949,7 @@ impl } } - self.buf_append_input(trimmed, needs_sep); + self.buf_append_input(trimmed, add_sep); } Kind::Rel => { debug_assert!(!is_input_absolute(input)); @@ -957,7 +966,7 @@ impl } } - self.buf_append_input(trimmed, needs_sep); + self.buf_append_input(trimmed, add_sep); } Kind::Any => { let input_is_absolute = is_input_absolute(input); @@ -992,7 +1001,7 @@ impl } } - self.buf_append_input(trimmed, needs_sep); + self.buf_append_input(trimmed, add_sep); } } Ok(()) @@ -1235,7 +1244,7 @@ impl /// Dispatch `Buf::append` / `Buf::append_other` based on whether the input /// element type matches `U`. - fn buf_append_input(&mut self, characters: &[C], add_separator: bool) { + fn buf_append_input(&mut self, characters: &[C], add_separator: AddSeparator) { use core::any::TypeId; // Route via concrete `u8`/`u16` using the safe trait-dispatched // identity casts (`id_u8`/`id_from_u8` etc.) — each is the literal diff --git a/src/paths/resolve_path.rs b/src/paths/resolve_path.rs index 6cd439d8eea6..a8ebc35fd81e 100644 --- a/src/paths/resolve_path.rs +++ b/src/paths/resolve_path.rs @@ -558,9 +558,9 @@ pub fn relative_normalized_buf<'a, P: PlatformT, const ALWAYS_COPY: bool>( to: &'a [u8], ) -> &'a [u8] { let equal = if P::P == Platform::Windows { - strings::eql_case_insensitive_ascii(from, to, true) + strings::eql_case_insensitive_ascii(from, to, strings::CheckLen::Yes) } else { - from.len() == to.len() && strings::eql_long(from, to, false) + from.len() == to.len() && strings::eql_long(from, to, strings::CheckLen::No) }; if equal { return b""; @@ -1887,7 +1887,7 @@ fn join_abs_string_buf_windows<'a, const IS_SENTINEL: bool>( // skip over volume name let volume = &part[0..windows_volume_name_len(part).0]; - if !volume.is_empty() && !strings::eql_long(volume, root, true) { + if !volume.is_empty() && !strings::eql_long(volume, root, strings::CheckLen::Yes) { continue; } diff --git a/src/picohttp/lib.rs b/src/picohttp/lib.rs index ac1689919ff5..2e6683905c40 100644 --- a/src/picohttp/lib.rs +++ b/src/picohttp/lib.rs @@ -224,7 +224,7 @@ pub struct HeaderList<'a> { impl<'a> HeaderList<'a> { pub fn get(&self, name: &[u8]) -> Option<&'a [u8]> { for header in self.list { - if strings::eql_case_insensitive_ascii(header.name(), name, true) { + if strings::eql_case_insensitive_ascii(header.name(), name, strings::CheckLen::Yes) { return Some(header.value()); } } @@ -240,11 +240,13 @@ impl<'a> HeaderList<'a> { let other = other.as_ref(); let mut value: Option<&'a [u8]> = None; for header in self.list { - if strings::eql_case_insensitive_ascii(header.name(), other, true) { + if strings::eql_case_insensitive_ascii(header.name(), other, strings::CheckLen::Yes) { return None; } - if value.is_none() && strings::eql_case_insensitive_ascii(header.name(), name, true) { + if value.is_none() + && strings::eql_case_insensitive_ascii(header.name(), name, strings::CheckLen::Yes) + { value = Some(header.value()); } } @@ -265,8 +267,14 @@ pub struct Request<'a> { pub bytes_read: u32, } +bun_core::bool_enum!(pub IgnoreInsecure); + impl<'a> Request<'a> { - pub fn curl(&self, ignore_insecure: bool, body: &'a [u8]) -> RequestCurlFormatter<'_> { + pub fn curl( + &self, + ignore_insecure: IgnoreInsecure, + body: &'a [u8], + ) -> RequestCurlFormatter<'_> { RequestCurlFormatter { request: self, ignore_insecure, @@ -322,7 +330,7 @@ impl fmt::Display for Request<'_> { pub struct RequestCurlFormatter<'a> { request: &'a Request<'a>, - ignore_insecure: bool, + ignore_insecure: IgnoreInsecure, body: &'a [u8], } @@ -358,7 +366,7 @@ impl fmt::Display for RequestCurlFormatter<'_> { write!(f, " -X {}", BStr::new(request.method))?; } - if self.ignore_insecure { + if self.ignore_insecure == IgnoreInsecure::Yes { f.write_str(" -k")?; } @@ -367,14 +375,22 @@ impl fmt::Display for RequestCurlFormatter<'_> { for header in request.headers { f.write_str(" ")?; if content_type.is_empty() { - if strings::eql_case_insensitive_ascii(b"content-type", header.name(), true) { + if strings::eql_case_insensitive_ascii( + b"content-type", + header.name(), + strings::CheckLen::Yes, + ) { content_type = header.value(); } } write!(f, "{}", header.curl())?; - if strings::eql_case_insensitive_ascii(b"accept-encoding", header.name(), true) { + if strings::eql_case_insensitive_ascii( + b"accept-encoding", + header.name(), + strings::CheckLen::Yes, + ) { f.write_str(" --compressed")?; } } diff --git a/src/ptr/CowSlice.rs b/src/ptr/CowSlice.rs index 95131fefae56..9f7682983b7d 100644 --- a/src/ptr/CowSlice.rs +++ b/src/ptr/CowSlice.rs @@ -43,6 +43,8 @@ pub struct CowSliceZ { debug: Option>, } +bun_core::bool_enum!(pub Ownership { Borrowed, Owned }); + /// `packed struct(usize) { len: u(BITS-1), is_owned: bool }` #[repr(transparent)] #[derive(Clone, Copy)] @@ -53,9 +55,16 @@ impl Flags { const LEN_MASK: usize = !Self::IS_OWNED_BIT; #[inline] - const fn new(len: usize, is_owned: bool) -> Self { + const fn new(len: usize, is_owned: Ownership) -> Self { debug_assert!(len <= Self::LEN_MASK); - Self((len & Self::LEN_MASK) | if is_owned { Self::IS_OWNED_BIT } else { 0 }) + Self( + (len & Self::LEN_MASK) + | if matches!(is_owned, Ownership::Owned) { + Self::IS_OWNED_BIT + } else { + 0 + }, + ) } #[inline] @@ -134,7 +143,7 @@ impl CowSliceZ { let ptr = bun_core::heap::into_raw(data).cast::(); Self { ptr, - flags: Flags::new(len, true), + flags: Flags::new(len, Ownership::Owned), #[cfg(debug_assertions)] debug: Some(DebugData::new_boxed()), } @@ -173,7 +182,7 @@ impl CowSliceZ { Self { // SAFETY: const semantics are enforced by is_owned flag ptr: data.as_ptr().cast_mut(), - flags: Flags::new(data.len(), false), + flags: Flags::new(data.len(), Ownership::Borrowed), #[cfg(debug_assertions)] debug: None, } @@ -247,7 +256,7 @@ impl CowSliceZ { } Self { ptr: self.ptr, - flags: Flags::new(self.flags.len(), false), + flags: Flags::new(self.flags.len(), Ownership::Borrowed), #[cfg(debug_assertions)] debug: self.debug, } @@ -309,7 +318,7 @@ impl CowSliceZ { /// `data` is the logical slice. For `Z = true` with `is_owned = true`, the /// backing allocation must physically hold `data.len() + 1` elements (the /// sentinel beyond the slice), as `Drop` frees the physical length. - pub fn init_unchecked(data: &[T], is_owned: bool) -> Self { + pub fn init_unchecked(data: &[T], is_owned: Ownership) -> Self { Self { // SAFETY: const semantics are enforced by is_owned flag ptr: data.as_ptr().cast_mut(), diff --git a/src/react_compiler/codegen.rs b/src/react_compiler/codegen.rs index 7b67a32344b2..b29ac6a562c6 100644 --- a/src/react_compiler/codegen.rs +++ b/src/react_compiler/codegen.rs @@ -40,7 +40,7 @@ use crate::hir::{ IdentifierId, IdentifierName, InstructionKind, InstructionValue, JsxAttribute, JsxTag, LogicalOperator, NonLocalKind, ObjectPattern, ObjectPropertyKey, ObjectPropertyOrSpread, ObjectPropertyType, ParamPattern, Pattern, Place, PlaceOrSpread, PrimitiveValue, - PropertyLiteral, ScopeId, + PropertyLiteral, ScopeId, UpdatePosition, }; use crate::reactive_scopes::visitors::{ReactiveFunctionVisitor, visit_reactive_function}; use crate::reactive_scopes::{ @@ -48,7 +48,7 @@ use crate::reactive_scopes::{ rename_variables, }; -use crate::program::{Host, JsxImportKind}; +use crate::program::{Host, JsxImportKind, RuntimeSentinel}; /// Result of code generation for a single function. pub struct CodegenFunction { @@ -157,12 +157,18 @@ impl<'h> Codegen<'h> { } else { let r = self .host - .runtime_sentinel(matches!(w, WellKnown::EarlyReturnSentinel)); + .runtime_sentinel(RuntimeSentinel::from_bool(matches!( + w, + WellKnown::EarlyReturnSentinel + ))); self.well_known[w as usize] = Some(r); r }; self.host.record_usage(r); - Expr::init(E::ImportIdentifier::new(r, false), loc) + Expr::init( + E::ImportIdentifier::new(r, E::WasOriginallyIdentifier::No), + loc, + ) } fn well_known_global(&mut self, w: WellKnown, name: &[u8]) -> Ref { @@ -191,7 +197,10 @@ impl<'h> Codegen<'h> { if ref_.is_symbol() { if let Some(sym) = self.host.symbols().get(ref_.inner_index() as usize) { if sym.kind == bun_ast::symbol::Kind::Import { - return Expr::init(E::ImportIdentifier::new(ref_, true), loc); + return Expr::init( + E::ImportIdentifier::new(ref_, E::WasOriginallyIdentifier::Yes), + loc, + ); } } } @@ -250,7 +259,7 @@ pub(crate) fn codegen_function( let use_memo_cache = Expr::init( E::ImportIdentifier::new( cx.cg.well_known(WellKnown::UseMemoCache, b"useMemoCache"), - true, + E::WasOriginallyIdentifier::Yes, ), loc, ); @@ -2113,7 +2122,7 @@ fn codegen_base_instruction_value( let arg = codegen_place_to_expression(cx, lvalue)?; Ok(Expr::init( E::Unary { - op: convert_update_operator(*operation, false), + op: convert_update_operator(*operation, UpdatePosition::Postfix), value: arg, flags: E::UnaryFlags::empty(), }, @@ -2126,7 +2135,7 @@ fn codegen_base_instruction_value( let arg = codegen_place_to_expression(cx, lvalue)?; Ok(Expr::init( E::Unary { - op: convert_update_operator(*operation, true), + op: convert_update_operator(*operation, UpdatePosition::Prefix), value: arg, flags: E::UnaryFlags::empty(), }, @@ -2234,7 +2243,10 @@ fn codegen_base_instruction_value( } let fragment_ref = cx.cg.host.jsx_import(JsxImportKind::Fragment); cx.cg.host.record_usage(fragment_ref); - let tag_value = Expr::init(E::ImportIdentifier::new(fragment_ref, true), loc); + let tag_value = Expr::init( + E::ImportIdentifier::new(fragment_ref, E::WasOriginallyIdentifier::Yes), + loc, + ); Ok(codegen_jsx_call( cx, tag_value, @@ -2676,7 +2688,10 @@ fn codegen_jsx_call( Expr::init( E::Call { - target: Expr::init(E::ImportIdentifier::new(target_ref, true), loc), + target: Expr::init( + E::ImportIdentifier::new(target_ref, E::WasOriginallyIdentifier::Yes), + loc, + ), args, can_be_unwrapped_if_unused: E::CallUnwrap::IfUnused, was_jsx_element: true, @@ -3101,13 +3116,13 @@ fn convert_logical_operator(op: LogicalOperator) -> OpCode { } } -fn convert_update_operator(op: crate::hir::UpdateOperator, prefix: bool) -> OpCode { +fn convert_update_operator(op: crate::hir::UpdateOperator, prefix: UpdatePosition) -> OpCode { use crate::hir::UpdateOperator as U; match (op, prefix) { - (U::Increment, true) => OpCode::UnPreInc, - (U::Increment, false) => OpCode::UnPostInc, - (U::Decrement, true) => OpCode::UnPreDec, - (U::Decrement, false) => OpCode::UnPostDec, + (U::Increment, UpdatePosition::Prefix) => OpCode::UnPreInc, + (U::Increment, UpdatePosition::Postfix) => OpCode::UnPostInc, + (U::Decrement, UpdatePosition::Prefix) => OpCode::UnPreDec, + (U::Decrement, UpdatePosition::Postfix) => OpCode::UnPostDec, } } diff --git a/src/react_compiler/hir/cfg_utils.rs b/src/react_compiler/hir/cfg_utils.rs index 678228ed5f75..d38b0b454da9 100644 --- a/src/react_compiler/hir/cfg_utils.rs +++ b/src/react_compiler/hir/cfg_utils.rs @@ -15,6 +15,8 @@ use super::{ SourceLocation, Terminal, }; +bun_core::bool_enum!(pub(crate) BlockUsed); + /// Compute a reverse-postorder of blocks reachable from the entry. /// /// Visits successors in reverse order so that when the postorder list is @@ -35,12 +37,13 @@ pub fn get_reverse_postordered_blocks( fn visit( hir: &HIR, block_id: BlockId, - is_used: bool, + is_used: BlockUsed, visited: &mut IndexSet, used: &mut IndexSet, used_fallthroughs: &mut IndexSet, postorder: &mut Vec, ) { + let is_used = is_used == BlockUsed::Yes; let was_used = used.contains(&block_id); let was_visited = visited.contains(&block_id); visited.insert(block_id); @@ -69,13 +72,21 @@ pub fn get_reverse_postordered_blocks( if is_used { used_fallthroughs.insert(ft); } - visit(hir, ft, false, visited, used, used_fallthroughs, postorder); + visit( + hir, + ft, + BlockUsed::No, + visited, + used, + used_fallthroughs, + postorder, + ); } for successor in successors { visit( hir, successor, - is_used, + BlockUsed::from_bool(is_used), visited, used, used_fallthroughs, @@ -91,7 +102,7 @@ pub fn get_reverse_postordered_blocks( visit( hir, hir.entry, - true, + BlockUsed::Yes, &mut visited, &mut used, &mut used_fallthroughs, diff --git a/src/react_compiler/hir/globals.rs b/src/react_compiler/hir/globals.rs index 7d481a0bc557..ca2cf458f04c 100644 --- a/src/react_compiler/hir/globals.rs +++ b/src/react_compiler/hir/globals.rs @@ -102,16 +102,18 @@ bun_core::comptime_string_map! { }; } +bun_core::bool_enum!(RegistryMode { Builder, Overlay }); + /// Registry mapping global names to their types. /// /// Supports two modes: -/// - **Builder mode** (`base=false`): wraps a single HashMap, used during +/// - **Builder mode**: wraps a single HashMap, used during /// `build_default_globals` to construct the static base. -/// - **Overlay mode** (`base=true`): lookups check the extras HashMap first, +/// - **Overlay mode**: lookups check the extras HashMap first, /// then fall back to the static `BASE_GLOBAL_INDEX` / `BASE.globals` table. /// Inserts go into extras. Cloning only copies the extras map. pub struct GlobalRegistry { - base: bool, + mode: RegistryMode, entries: HashMap, Global>, } @@ -119,7 +121,7 @@ impl GlobalRegistry { /// Create an empty builder-mode registry. pub fn new() -> Self { Self { - base: false, + mode: RegistryMode::Builder, entries: HashMap::new(), } } @@ -127,7 +129,7 @@ impl GlobalRegistry { /// Create an overlay-mode registry backed by the static base. pub fn with_base() -> Self { Self { - base: true, + mode: RegistryMode::Overlay, entries: HashMap::new(), } } @@ -136,7 +138,7 @@ impl GlobalRegistry { if let Some(v) = self.entries.get(key) { return Some(v); } - if self.base { + if self.mode == RegistryMode::Overlay { return lookup_base_global(key); } None @@ -148,15 +150,15 @@ impl GlobalRegistry { pub fn contains_key(&self, key: &str) -> bool { self.entries.contains_key(key) - || (self.base && BASE_GLOBAL_INDEX.contains_key(key.as_bytes())) + || (self.mode == RegistryMode::Overlay + && BASE_GLOBAL_INDEX.contains_key(key.as_bytes())) } /// Iterate over all keys in the registry (base + extras). /// Keys in extras that shadow base keys appear only once. pub fn keys(&self) -> impl Iterator { let entries = &self.entries; - let base_keys = self - .base + let base_keys = (self.mode == RegistryMode::Overlay) .then(|| BASE_GLOBAL_INDEX.keys()) .into_iter() .flatten() @@ -170,7 +172,7 @@ impl GlobalRegistry { /// Only valid in builder mode (no base). pub fn into_inner(self) -> HashMap, Global> { debug_assert!( - !self.base, + self.mode == RegistryMode::Builder, "into_inner() called on overlay-mode GlobalRegistry" ); self.entries @@ -180,7 +182,7 @@ impl GlobalRegistry { impl Clone for GlobalRegistry { fn clone(&self) -> Self { Self { - base: self.base, + mode: self.mode, entries: self.entries.clone(), } } @@ -302,7 +304,7 @@ fn install_type_config_inner( ..Default::default() }, None, - false, + IsConstructor::No, ) } TypeConfig::Hook(hook_config) => { @@ -434,7 +436,7 @@ fn simple_function( ..Default::default() }, None, - false, + IsConstructor::No, ) } @@ -466,7 +468,7 @@ fn build_array_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let at = add_function( shapes, @@ -479,7 +481,7 @@ fn build_array_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let concat = add_function( shapes, @@ -494,7 +496,7 @@ fn build_array_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let join = pure_primitive_fn(shapes); let slice = add_function( @@ -510,7 +512,7 @@ fn build_array_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let map = add_function( shapes, @@ -570,7 +572,7 @@ fn build_array_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let filter = add_function( shapes, @@ -587,7 +589,7 @@ fn build_array_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let find = add_function( shapes, @@ -602,7 +604,7 @@ fn build_array_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let find_index = add_function( shapes, @@ -617,7 +619,7 @@ fn build_array_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let every = add_function( shapes, @@ -632,7 +634,7 @@ fn build_array_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let some = add_function( shapes, @@ -647,7 +649,7 @@ fn build_array_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let flat_map = add_function( shapes, @@ -664,7 +666,7 @@ fn build_array_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let length = Type::Primitive; let push = add_function( @@ -700,7 +702,7 @@ fn build_array_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); add_object( @@ -741,7 +743,7 @@ fn build_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let add = add_function( shapes, @@ -777,7 +779,7 @@ fn build_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let clear = add_function( shapes, @@ -789,7 +791,7 @@ fn build_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let delete = add_function( shapes, @@ -802,7 +804,7 @@ fn build_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let size = Type::Primitive; let difference = add_function( @@ -818,7 +820,7 @@ fn build_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let union = add_function( shapes, @@ -833,7 +835,7 @@ fn build_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let symmetrical_difference = add_function( shapes, @@ -848,7 +850,7 @@ fn build_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let is_subset_of = add_function( shapes, @@ -861,7 +863,7 @@ fn build_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let is_superset_of = add_function( shapes, @@ -874,7 +876,7 @@ fn build_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let for_each = add_function( shapes, @@ -889,7 +891,7 @@ fn build_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let values = add_function( shapes, @@ -901,7 +903,7 @@ fn build_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let keys = add_function( shapes, @@ -913,7 +915,7 @@ fn build_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let entries = add_function( shapes, @@ -925,7 +927,7 @@ fn build_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); add_object( @@ -963,7 +965,7 @@ fn build_map_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let get = add_function( shapes, @@ -976,7 +978,7 @@ fn build_map_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let clear = add_function( shapes, @@ -988,7 +990,7 @@ fn build_map_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let set = add_function( shapes, @@ -1003,7 +1005,7 @@ fn build_map_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let delete = add_function( shapes, @@ -1016,7 +1018,7 @@ fn build_map_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let size = Type::Primitive; let for_each = add_function( @@ -1032,7 +1034,7 @@ fn build_map_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let values = add_function( shapes, @@ -1044,7 +1046,7 @@ fn build_map_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let keys = add_function( shapes, @@ -1056,7 +1058,7 @@ fn build_map_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let entries = add_function( shapes, @@ -1068,7 +1070,7 @@ fn build_map_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); add_object( @@ -1106,7 +1108,7 @@ fn build_weak_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let delete = add_function( shapes, @@ -1119,7 +1121,7 @@ fn build_weak_set_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); add_object( @@ -1144,7 +1146,7 @@ fn build_weak_map_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let set = add_function( shapes, @@ -1159,7 +1161,7 @@ fn build_weak_map_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let delete = add_function( shapes, @@ -1172,7 +1174,7 @@ fn build_weak_map_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); add_object( @@ -1195,7 +1197,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); add_object( shapes, @@ -1218,7 +1220,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let mixed_index_of = add_function( shapes, @@ -1230,7 +1232,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let mixed_includes = add_function( shapes, @@ -1242,7 +1244,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let mixed_at = add_function( shapes, @@ -1257,7 +1259,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let mixed_map = add_function( shapes, @@ -1273,7 +1275,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let mixed_flat_map = add_function( shapes, @@ -1289,7 +1291,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let mixed_filter = add_function( shapes, @@ -1305,7 +1307,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let mixed_concat = add_function( shapes, @@ -1320,7 +1322,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let mixed_slice = add_function( shapes, @@ -1335,7 +1337,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let mixed_every = add_function( shapes, @@ -1350,7 +1352,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let mixed_some = add_function( shapes, @@ -1365,7 +1367,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let mixed_find = add_function( shapes, @@ -1382,7 +1384,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let mixed_find_index = add_function( shapes, @@ -1397,7 +1399,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let mixed_join = add_function( shapes, @@ -1409,7 +1411,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) { ..Default::default() }, None, - false, + IsConstructor::No, ); let mut mixed_props: HashMap<&'static str, Type> = HashMap::new(); mixed_props.insert("toString", mixed_to_string); @@ -1482,7 +1484,7 @@ fn build_state_shapes(shapes: &mut ShapeRegistry) { ..Default::default() }, Some(BUILT_IN_SET_STATE_ID), - false, + IsConstructor::No, ); // BuiltInUseState: object with [0] = Poly (state), [1] = setState function @@ -1503,7 +1505,7 @@ fn build_state_shapes(shapes: &mut ShapeRegistry) { ..Default::default() }, Some(BUILT_IN_SET_ACTION_STATE_ID), - false, + IsConstructor::No, ); // BuiltInUseActionState: [0] = Poly, [1] = setActionState function @@ -1524,7 +1526,7 @@ fn build_state_shapes(shapes: &mut ShapeRegistry) { ..Default::default() }, Some(BUILT_IN_DISPATCH_ID), - false, + IsConstructor::No, ); // BuiltInUseReducer: [0] = Poly, [1] = dispatch function @@ -1545,7 +1547,7 @@ fn build_state_shapes(shapes: &mut ShapeRegistry) { ..Default::default() }, Some(BUILT_IN_START_TRANSITION_ID), - false, + IsConstructor::No, ); // BuiltInUseTransition: [0] = Primitive (isPending), [1] = startTransition function @@ -1566,7 +1568,7 @@ fn build_state_shapes(shapes: &mut ShapeRegistry) { ..Default::default() }, Some(BUILT_IN_SET_OPTIMISTIC_ID), - false, + IsConstructor::No, ); // BuiltInUseOptimistic: [0] = Poly, [1] = setOptimistic function @@ -1592,7 +1594,7 @@ fn build_hook_shapes(shapes: &mut ShapeRegistry) { ..Default::default() }, Some(BUILT_IN_EFFECT_EVENT_ID), - false, + IsConstructor::No, ); } @@ -1674,7 +1676,7 @@ pub fn get_reanimated_module_type(shapes: &mut ShapeRegistry) -> Type { ..Default::default() }, None, - false, + IsConstructor::No, ); reanimated_type.push((*func_name, func_type)); } @@ -1996,7 +1998,7 @@ fn build_react_apis( ..Default::default() }, Some(BUILT_IN_USE_OPERATOR_ID), - false, + IsConstructor::No, ); react_apis.push(("use", use_fn)); @@ -2067,7 +2069,7 @@ fn build_typed_globals( ..Default::default() }, None, - false, + IsConstructor::No, ); let obj_from_entries = add_function( shapes, @@ -2081,7 +2083,7 @@ fn build_typed_globals( ..Default::default() }, None, - false, + IsConstructor::No, ); let obj_entries = add_function( shapes, @@ -2114,7 +2116,7 @@ fn build_typed_globals( ..Default::default() }, None, - false, + IsConstructor::No, ); let obj_values = add_function( shapes, @@ -2147,7 +2149,7 @@ fn build_typed_globals( ..Default::default() }, None, - false, + IsConstructor::No, ); let object_global = add_object( shapes, @@ -2173,7 +2175,7 @@ fn build_typed_globals( ..Default::default() }, None, - false, + IsConstructor::No, ); let array_from = add_function( shapes, @@ -2192,7 +2194,7 @@ fn build_typed_globals( ..Default::default() }, None, - false, + IsConstructor::No, ); let array_of = add_function( shapes, @@ -2206,7 +2208,7 @@ fn build_typed_globals( ..Default::default() }, None, - false, + IsConstructor::No, ); let array_global = add_object( shapes, @@ -2239,7 +2241,7 @@ fn build_typed_globals( ..Default::default() }, None, - false, + IsConstructor::No, ); math_props.push(("random", math_random)); let math_global = add_object(shapes, Some("Math"), math_props); @@ -2259,7 +2261,7 @@ fn build_typed_globals( ..Default::default() }, None, - false, + IsConstructor::No, ); let perf_global = add_object(shapes, Some("performance"), vec![("now", perf_now)]); typed_globals.push(("performance", perf_global.clone())); @@ -2278,7 +2280,7 @@ fn build_typed_globals( ..Default::default() }, None, - false, + IsConstructor::No, ); let date_global = add_object(shapes, Some("Date"), vec![("now", date_now)]); typed_globals.push(("Date", date_global.clone())); @@ -2332,7 +2334,7 @@ fn build_typed_globals( ..Default::default() }, None, - true, + IsConstructor::Yes, ); typed_globals.push(("Map", map_ctor.clone())); globals.insert("Map", map_ctor); @@ -2349,7 +2351,7 @@ fn build_typed_globals( ..Default::default() }, None, - true, + IsConstructor::Yes, ); typed_globals.push(("Set", set_ctor.clone())); globals.insert("Set", set_ctor); @@ -2366,7 +2368,7 @@ fn build_typed_globals( ..Default::default() }, None, - true, + IsConstructor::Yes, ); typed_globals.push(("WeakMap", weak_map_ctor.clone())); globals.insert("WeakMap", weak_map_ctor); @@ -2383,7 +2385,7 @@ fn build_typed_globals( ..Default::default() }, None, - true, + IsConstructor::Yes, ); typed_globals.push(("WeakSet", weak_set_ctor.clone())); globals.insert("WeakSet", weak_set_ctor); @@ -2400,7 +2402,7 @@ fn build_typed_globals( ..Default::default() }, None, - false, + IsConstructor::No, ); let react_clone_element = add_function( shapes, @@ -2412,7 +2414,7 @@ fn build_typed_globals( ..Default::default() }, None, - false, + IsConstructor::No, ); let react_create_ref = add_function( shapes, @@ -2426,7 +2428,7 @@ fn build_typed_globals( ..Default::default() }, None, - false, + IsConstructor::No, ); // Build React namespace properties from react_apis + React-specific functions @@ -2450,7 +2452,7 @@ fn build_typed_globals( ..Default::default() }, None, - false, + IsConstructor::No, ); typed_globals.push(("_jsx", jsx_fn.clone())); globals.insert("_jsx", jsx_fn); diff --git a/src/react_compiler/hir/mod.rs b/src/react_compiler/hir/mod.rs index f8c8f5556fe5..a0819dffe615 100644 --- a/src/react_compiler/hir/mod.rs +++ b/src/react_compiler/hir/mod.rs @@ -298,6 +298,8 @@ pub struct HirFunction { pub aliasing_effects: Option>, } +bun_core::bool_enum!(pub FunctionNesting { TopLevel, Nested }); + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ReactFunctionType { Component, @@ -1090,6 +1092,8 @@ pub enum UpdateOperator { Decrement, } +bun_core::bool_enum!(pub UpdatePosition { Postfix, Prefix }); + impl std::fmt::Display for UpdateOperator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/src/react_compiler/hir/object_shape.rs b/src/react_compiler/hir/object_shape.rs index 084e02c72e88..10214bf2915b 100644 --- a/src/react_compiler/hir/object_shape.rs +++ b/src/react_compiler/hir/object_shape.rs @@ -225,6 +225,8 @@ impl Clone for ShapeRegistry { // Builder functions (matching TS addFunction, addHook, addObject) // ============================================================================= +bun_core::bool_enum!(pub IsConstructor); + /// Add a non-hook function to a ShapeRegistry. /// Returns a `Type::Function` representing the added function. #[cold] @@ -234,7 +236,7 @@ pub fn add_function( properties: Vec<(&'static str, Type)>, sig: FunctionSignatureBuilder, id: Option<&'static str>, - is_constructor: bool, + is_constructor: IsConstructor, ) -> Type { let shape_id: &'static str = id.unwrap_or_else(|| registry.next_anon_id()); let return_type = sig.return_type.clone(); @@ -261,7 +263,7 @@ pub fn add_function( Type::Function { shape_id: Some(shape_id), return_type: Box::new(return_type), - is_constructor, + is_constructor: is_constructor == IsConstructor::Yes, } } diff --git a/src/react_compiler/imports.rs b/src/react_compiler/imports.rs index 70adc8751221..648b00b48a4d 100644 --- a/src/react_compiler/imports.rs +++ b/src/react_compiler/imports.rs @@ -16,7 +16,7 @@ use bun_ast::{ use crate::hir::environment::OutputMode; use crate::options::ReactCompilerOptions; -use crate::program::Host; +use crate::program::{Host, ModuleScopeOptOut}; /// An import specifier tracked by ProgramContext. /// Corresponds to NonLocalImportSpecifier in the TS compiler. @@ -35,7 +35,7 @@ pub(crate) struct ProgramContext { pub(crate) code: Option, pub(crate) react_runtime_module: &'static str, pub(crate) output_mode: OutputMode, - pub(crate) has_module_scope_opt_out: bool, + pub(crate) has_module_scope_opt_out: ModuleScopeOptOut, // Pre-resolved import local names for codegen pub(crate) instrument_fn_name: Option, @@ -55,7 +55,7 @@ impl ProgramContext { opts: ReactCompilerOptions, filename: Option, code: Option, - has_module_scope_opt_out: bool, + has_module_scope_opt_out: ModuleScopeOptOut, ) -> Self { let react_runtime_module = get_react_compiler_runtime_module(opts.target.as_deref()); Self { diff --git a/src/react_compiler/inference/analyse_functions.rs b/src/react_compiler/inference/analyse_functions.rs index ebba3279a17a..34cba19915d5 100644 --- a/src/react_compiler/inference/analyse_functions.rs +++ b/src/react_compiler/inference/analyse_functions.rs @@ -18,8 +18,8 @@ use crate::diagnostics::{CompilerDiagnostic, cold_invariant}; use crate::hir::environment::Environment; use crate::hir::{ - AliasingEffect, AstAlloc, BlockId, Effect, EvaluationOrder, FunctionId, HIR, HirFunction, - IdentifierId, InstructionValue, Place, ReactFunctionType, + AliasingEffect, AstAlloc, BlockId, Effect, EvaluationOrder, FunctionId, FunctionNesting, HIR, + HirFunction, IdentifierId, InstructionValue, Place, ReactFunctionType, }; /// Analyse all nested function expressions and object methods in `func`. @@ -109,7 +109,9 @@ where // inferMutationAliasingEffects on the inner function crate::inference::infer_mutation_aliasing_effects::infer_mutation_aliasing_effects( - func, env, true, + func, + env, + FunctionNesting::Nested, )?; // Check for invariant errors (e.g., uninitialized value kind) @@ -125,7 +127,9 @@ where // inferMutationAliasingRanges — returns the externally-visible function effects let function_effects = crate::inference::infer_mutation_aliasing_ranges::infer_mutation_aliasing_ranges( - func, env, true, + func, + env, + FunctionNesting::Nested, )?; // rewriteInstructionKindsBasedOnReassignment diff --git a/src/react_compiler/inference/infer_mutation_aliasing_effects.rs b/src/react_compiler/inference/infer_mutation_aliasing_effects.rs index c49bd0bf2ada..46a50ea86f2e 100644 --- a/src/react_compiler/inference/infer_mutation_aliasing_effects.rs +++ b/src/react_compiler/inference/infer_mutation_aliasing_effects.rs @@ -23,6 +23,7 @@ use crate::hir::BlockId; use crate::hir::DeclarationId; use crate::hir::Effect; use crate::hir::FunctionId; +use crate::hir::FunctionNesting; use crate::hir::HirFunction; use crate::hir::HirVec; use crate::hir::IdentifierId; @@ -57,7 +58,7 @@ use crate::hir::visitors; pub(crate) fn infer_mutation_aliasing_effects( func: &mut HirFunction, env: &mut Environment, - is_function_expression: bool, + is_function_expression: FunctionNesting, ) -> Result<(), CompilerDiagnostic> { // ValueIds are dense and pass-local so `InferenceState.values` can be a // flat Vec; allocation starts at 0 and continues via `Context.next_value_id`. @@ -82,7 +83,7 @@ pub(crate) fn infer_mutation_aliasing_effects( initial_state.define(ctx_place.identifier, value_id); } - let param_kind: AbstractValue = if is_function_expression { + let param_kind: AbstractValue = if is_function_expression == FunctionNesting::Nested { AbstractValue { kind: ValueKind::Mutable, reason: hashset_of(ValueReason::Other), @@ -635,8 +636,8 @@ struct InferenceState { } impl InferenceState { - fn empty(is_function_expression: bool, identifier_capacity: usize) -> Self { - let variables = if is_function_expression { + fn empty(is_function_expression: FunctionNesting, identifier_capacity: usize) -> Self { + let variables = if is_function_expression == FunctionNesting::Nested { Variables::Sparse(HashMap::default()) } else { let mut vec = Vec::with_capacity(identifier_capacity); @@ -923,7 +924,7 @@ struct Context { interned_effects: HashMap, instruction_signature_cache: HashMap, catch_handlers: HashMap, - is_function_expression: bool, + is_function_expression: FunctionNesting, hoisted_context_declarations: HashMap>, non_mutating_spreads: HashSet, /// Cache of ValueIds keyed by effect hash, ensuring stable allocation-site identity @@ -1490,7 +1491,7 @@ fn infer_block( } } TerminalAction::Return => { - if !context.is_function_expression { + if context.is_function_expression == FunctionNesting::TopLevel { let block_mut = func.body.blocks.get_mut(&block_id).unwrap(); if let crate::hir::Terminal::Return { ref value, @@ -2200,7 +2201,7 @@ fn apply_effect( )?; } - if *is_spread { + if *is_spread == IsSpread::Yes { let ty = &env.types [env.identifiers[operand.identifier.0 as usize].type_.0 as usize]; if let Some(mutate_iter) = conditionally_mutate_iterator(operand, ty) { @@ -2237,7 +2238,7 @@ fn apply_effect( // so NO pairs are skipped when the outer arg is a Spread // (including self-pairs, producing self-captures). for (other, _other_is_func, _other_is_spread) in &all_operands { - if !is_spread && other.identifier == operand.identifier { + if *is_spread == IsSpread::No && other.identifier == operand.identifier { continue; } apply_effect( @@ -3124,7 +3125,8 @@ fn compute_effects_for_legacy_signature( } else { signature.rest_param.unwrap_or(Effect::ConditionallyMutate) }; - let (effect, err_detail) = get_argument_effect(sig_effect, is_spread, place.loc); + let (effect, err_detail) = + get_argument_effect(sig_effect, IsSpread::from_bool(is_spread), place.loc); if let Some(d) = err_detail { todo_errors.push(d); } @@ -3158,10 +3160,10 @@ fn compute_effects_for_legacy_signature( fn get_argument_effect( sig_effect: Effect, - is_spread: bool, + is_spread: IsSpread, spread_loc: Option, ) -> (Effect, Option) { - if !is_spread { + if is_spread == IsSpread::No { (sig_effect, None) } else if sig_effect == Effect::Mutate || sig_effect == Effect::ConditionallyMutate { (sig_effect, None) @@ -3976,20 +3978,27 @@ fn place_or_spread_to_hole(pos: &PlaceOrSpread) -> PlaceOrSpreadOrHole { use crate::hir::JsxTag; +bun_core::bool_enum!(IsFunctionOperand); +bun_core::bool_enum!(IsSpread); + fn build_apply_operands( receiver: &Place, function: &Place, args: &[PlaceOrSpreadOrHole], -) -> Vec<(Place, bool, bool)> { +) -> Vec<(Place, IsFunctionOperand, IsSpread)> { let mut result = vec![ - (receiver.clone(), false, false), - (function.clone(), true, false), + (receiver.clone(), IsFunctionOperand::No, IsSpread::No), + (function.clone(), IsFunctionOperand::Yes, IsSpread::No), ]; for arg in args { match arg { PlaceOrSpreadOrHole::Hole => continue, - PlaceOrSpreadOrHole::Place(p) => result.push((p.clone(), false, false)), - PlaceOrSpreadOrHole::Spread(s) => result.push((s.place.clone(), false, true)), + PlaceOrSpreadOrHole::Place(p) => { + result.push((p.clone(), IsFunctionOperand::No, IsSpread::No)) + } + PlaceOrSpreadOrHole::Spread(s) => { + result.push((s.place.clone(), IsFunctionOperand::No, IsSpread::Yes)) + } } } result diff --git a/src/react_compiler/inference/infer_mutation_aliasing_ranges.rs b/src/react_compiler/inference/infer_mutation_aliasing_ranges.rs index 2d8795ca1042..c81222be5398 100644 --- a/src/react_compiler/inference/infer_mutation_aliasing_ranges.rs +++ b/src/react_compiler/inference/infer_mutation_aliasing_ranges.rs @@ -26,8 +26,9 @@ use crate::hir::visitors::{ for_each_instruction_value_operand_mut, for_each_terminal_operand_mut, }; use crate::hir::{ - AliasingEffect, BlockId, Effect, EvaluationOrder, FunctionId, HirFunction, IdentifierId, - InstructionValue, MutationReason, Place, SourceLocation, is_jsx_type, is_primitive_type, + AliasingEffect, BlockId, Effect, EvaluationOrder, FunctionId, FunctionNesting, HirFunction, + IdentifierId, InstructionValue, MutationReason, Place, SourceLocation, is_jsx_type, + is_primitive_type, }; // ============================================================================= @@ -108,6 +109,9 @@ struct AliasingState { nodes: IndexMap, } +bun_core::bool_enum!(MutationScope { Local, Transitive }); +bun_core::bool_enum!(RecordErrors); + impl AliasingState { fn new() -> Self { AliasingState { @@ -238,13 +242,15 @@ impl AliasingState { index: usize, start: IdentifierId, end: Option, // None for simulated mutations - transitive: bool, + transitive: MutationScope, start_kind: MutationKind, loc: Option, reason: Option, env: &mut Environment, - should_record_errors: bool, + should_record_errors: RecordErrors, ) { + let transitive = transitive == MutationScope::Transitive; + let should_record_errors = should_record_errors == RecordErrors::Yes; #[derive(Clone)] struct QueueEntry { place: IdentifierId, @@ -456,7 +462,7 @@ fn append_function_errors(env: &mut Environment, function_id: FunctionId) { pub(crate) fn infer_mutation_aliasing_ranges( func: &mut HirFunction, env: &mut Environment, - is_function_expression: bool, + is_function_expression: FunctionNesting, ) -> Result, CompilerDiagnostic> { let mut function_effects: Vec = Vec::new(); @@ -475,7 +481,7 @@ pub(crate) fn infer_mutation_aliasing_ranges( struct PendingMutation { index: usize, id: EvaluationOrder, - transitive: bool, + transitive: MutationScope, kind: MutationKind, place: Place, reason: Option, @@ -490,7 +496,8 @@ pub(crate) fn infer_mutation_aliasing_ranges( let mut index: usize = 0; - let should_record_errors = !is_function_expression && env.enable_validations(); + let should_record_errors = + is_function_expression == FunctionNesting::TopLevel && env.enable_validations(); // Create nodes for params, context vars, and return for param in &func.params { @@ -586,7 +593,7 @@ pub(crate) fn infer_mutation_aliasing_ranges( mutations.push(PendingMutation { index: index, id: instr_eval_order, - transitive: true, + transitive: MutationScope::Transitive, kind: if is_transitive_conditional { MutationKind::Conditional } else { @@ -601,7 +608,7 @@ pub(crate) fn infer_mutation_aliasing_ranges( mutations.push(PendingMutation { index: index, id: instr_eval_order, - transitive: false, + transitive: MutationScope::Local, kind: MutationKind::Definite, reason: reason.clone(), place: value.clone(), @@ -612,7 +619,7 @@ pub(crate) fn infer_mutation_aliasing_ranges( mutations.push(PendingMutation { index: index, id: instr_eval_order, - transitive: false, + transitive: MutationScope::Local, kind: MutationKind::Conditional, reason: None, place: value.clone(), @@ -699,7 +706,7 @@ pub(crate) fn infer_mutation_aliasing_ranges( mutation.place.loc, mutation.reason.clone(), env, - should_record_errors, + RecordErrors::from_bool(should_record_errors), ); } @@ -1010,7 +1017,7 @@ pub(crate) fn infer_mutation_aliasing_ranges( let block = func.body.blocks.get_mut(&block_id).unwrap(); match &mut block.terminal { crate::hir::Terminal::Return { value, .. } => { - value.effect = if is_function_expression { + value.effect = if is_function_expression == FunctionNesting::Nested { Effect::Read } else { Effect::Freeze @@ -1066,12 +1073,12 @@ pub(crate) fn infer_mutation_aliasing_ranges( mutation_index, into.identifier, None, // simulated mutation - true, + MutationScope::Transitive, MutationKind::Conditional, into.loc, None, env, - false, // never record errors for simulated mutations + RecordErrors::No, // never record errors for simulated mutations ); for j in 0..tracked.len() { diff --git a/src/react_compiler/lib.rs b/src/react_compiler/lib.rs index 3c92fef62491..54abf5d179f2 100644 --- a/src/react_compiler/lib.rs +++ b/src/react_compiler/lib.rs @@ -33,6 +33,7 @@ pub mod program; pub use compile_result::{CompileDiagnostic, CompileOutput}; pub use options::ReactCompilerOptions; pub use program::{ - CompileResult, Host, JsxImportKind, PendingCompile, ReactCompilerState, SymbolHost, - collect_import_bindings, finish, has_module_scope_opt_out, maybe_compile_pending, + CompileResult, Host, JsxImportKind, ModuleScopeOptOut, PendingCompile, ReactCompilerState, + RuntimeSentinel, SymbolHost, collect_import_bindings, finish, has_module_scope_opt_out, + maybe_compile_pending, }; diff --git a/src/react_compiler/lowering/build_hir/expr.rs b/src/react_compiler/lowering/build_hir/expr.rs index 2a6c45593e5f..407c407a326e 100644 --- a/src/react_compiler/lowering/build_hir/expr.rs +++ b/src/react_compiler/lowering/build_hir/expr.rs @@ -9,10 +9,10 @@ use bun_ast::{self as ast, E, Expr, G, Loc, OpCode, Ref, StoreRef, symbol}; use super::function::{lower_function_to_value, lower_object_method}; use super::helpers::{ - AssignmentStyle, MemberProperty, build_temporary_place, expression_type_name, lower_arguments, - lower_assignment, lower_expression_to_temporary, lower_identifier, lower_member_expression, - lower_object_property_key, lower_optional_call_expression, lower_optional_member_expression, - lower_value_to_temporary, + AssignmentStyle, ComputedKey, MemberProperty, build_temporary_place, expression_type_name, + lower_arguments, lower_assignment, lower_expression_to_temporary, lower_identifier, + lower_member_expression, lower_object_property_key, lower_optional_call_expression, + lower_optional_member_expression, lower_value_to_temporary, }; use super::jsx::lower_jsx_call; use crate::lowering::FunctionNode; @@ -145,7 +145,7 @@ pub(crate) fn lower_expression( .key .as_ref() .ok_or_else(|| cold_todo("object property without key", loc))?; - let computed = prop.flags.contains(PF::IsComputed); + let computed = ComputedKey::from_bool(prop.flags.contains(PF::IsComputed)); let key = match lower_object_property_key(builder, key_expr, computed)? { Some(k) => k, None => continue, @@ -1044,7 +1044,7 @@ fn lower_update( unary: &E::Unary, loc: Option, ) -> Result { - let prefix = matches!(unary.op, OpCode::UnPreInc | OpCode::UnPreDec); + let prefix = UpdatePosition::from_bool(matches!(unary.op, OpCode::UnPreInc | OpCode::UnPreDec)); let operation = match unary.op { OpCode::UnPreInc | OpCode::UnPostInc => UpdateOperator::Increment, OpCode::UnPreDec | OpCode::UnPostDec => UpdateOperator::Decrement, @@ -1100,7 +1100,10 @@ fn lower_update( )?, }; - let result_place = if prefix { new_value_place } else { prev_value }; + let result_place = match prefix { + UpdatePosition::Prefix => new_value_place, + UpdatePosition::Postfix => prev_value, + }; Ok(InstructionValue::LoadLocal { loc: result_place.loc, place: result_place, @@ -1129,7 +1132,7 @@ fn lower_update_identifier( builder: &mut HirBuilder, ref_: Ref, arg_bun_loc: Loc, - prefix: bool, + prefix: UpdatePosition, operation: UpdateOperator, loc: Option, ) -> Result { @@ -1178,7 +1181,7 @@ fn lower_update_identifier( let value = lower_identifier(builder, ref_, ident_loc)?; - if prefix { + if prefix == UpdatePosition::Prefix { Ok(InstructionValue::PrefixUpdate { lvalue: lvalue_place, operation, diff --git a/src/react_compiler/lowering/build_hir/function.rs b/src/react_compiler/lowering/build_hir/function.rs index 92a8dc3598ea..5848d602a64b 100644 --- a/src/react_compiler/lowering/build_hir/function.rs +++ b/src/react_compiler/lowering/build_hir/function.rs @@ -5,15 +5,16 @@ use crate::diagnostics::{ CompilerDiagnostic, CompilerError, CompilerErrorDetail, ErrorCategory, SourceLocation, }; use crate::hir::{ - Effect, FunctionExpressionType, InstructionKind, InstructionValue, LValue, LoweredFunction, - ObjectProperty, ObjectPropertyKey, ObjectPropertyType, Place, StoreStr, VariableBinding, + Effect, FunctionExpressionType, FunctionNesting, InstructionKind, InstructionValue, LValue, + LoweredFunction, ObjectProperty, ObjectPropertyKey, ObjectPropertyType, Place, StoreStr, + VariableBinding, }; use bun_ast::expr::Data; use bun_ast::{self as ast, E, Expr, G, Loc, Ref}; use super::super::hir_builder::{HirBuilder, convert_loc}; use super::FunctionNode; -use super::helpers::{lower_expression_to_temporary, lower_value_to_temporary}; +use super::helpers::{ComputedKey, lower_expression_to_temporary, lower_value_to_temporary}; use super::{gather_captured_context, lower_inner}; pub(super) fn lower_function_to_value( @@ -89,7 +90,7 @@ fn lower_function( component_scope, &context_ids, &import_bindings, - false, // nested function + FunctionNesting::Nested, )?; builder.merge_used_refs(&child_used_refs); @@ -148,7 +149,7 @@ pub(super) fn lower_function_declaration( component_scope, &context_ids, &import_bindings, - false, // nested function + FunctionNesting::Nested, )?; builder.merge_used_refs(&child_used_refs); @@ -275,7 +276,7 @@ fn lower_function_for_object_method( component_scope, &context_ids, &import_bindings, - false, // nested function + FunctionNesting::Nested, )?; builder.merge_used_refs(&child_used_refs); @@ -314,7 +315,7 @@ pub(super) fn lower_object_method( return Ok(None); } - let computed = method.flags.contains(PF::IsComputed); + let computed = ComputedKey::from_bool(method.flags.contains(PF::IsComputed)); let key = match key_expr { Some(k) => { lower_object_property_key(builder, k, computed)?.unwrap_or(ObjectPropertyKey::String { @@ -363,8 +364,9 @@ pub(super) fn lower_object_method( fn lower_object_property_key( builder: &mut HirBuilder<'_>, key: &Expr, - computed: bool, + computed: ComputedKey, ) -> Result, CompilerError> { + let computed = computed == ComputedKey::Yes; match &key.data { // Upstream matches `StringLiteral` regardless of `computed`, so a // constant string in a computed key (`{["foo"]: x}`) still lowers to diff --git a/src/react_compiler/lowering/build_hir/helpers.rs b/src/react_compiler/lowering/build_hir/helpers.rs index 18dd45369bbb..06978b4ae214 100644 --- a/src/react_compiler/lowering/build_hir/helpers.rs +++ b/src/react_compiler/lowering/build_hir/helpers.rs @@ -850,7 +850,7 @@ pub(super) fn lower_assignment( let Some(key_expr) = prop.key.as_ref() else { continue; }; - let key = match lower_object_property_key(builder, key_expr, false)? { + let key = match lower_object_property_key(builder, key_expr, ComputedKey::No)? { Some(k) => k, None => continue, }; @@ -1126,11 +1126,14 @@ fn lower_member_store( } } +bun_core::bool_enum!(pub(crate) ComputedKey); + pub(super) fn lower_object_property_key( builder: &mut HirBuilder, key: &Expr, - computed: bool, + computed: ComputedKey, ) -> Result, CompilerError> { + let computed = computed == ComputedKey::Yes; match &key.data { Data::EString(s) => { let name = if s.is_utf16 { diff --git a/src/react_compiler/lowering/build_hir/jsx.rs b/src/react_compiler/lowering/build_hir/jsx.rs index 3e588e01b911..ed56ab8eb830 100644 --- a/src/react_compiler/lowering/build_hir/jsx.rs +++ b/src/react_compiler/lowering/build_hir/jsx.rs @@ -257,7 +257,7 @@ pub(super) fn lower_jsx_call( // expression (which may itself be a user-authored array) is passed // through verbatim. Recover that bit so lower_jsx_children knows // whether an EArray is the transform's container or a real child. - let is_static_children = if args.len() == 6 { + let is_static_children = StaticChildren::from_bool(if args.len() == 6 { matches!(args[3].data, ExprData::EBoolean(b) if b.value) } else { match &call.target.data { @@ -267,7 +267,7 @@ pub(super) fn lower_jsx_call( } _ => false, } - }; + }); // `key` was hoisted out of the props object into args[2] by the visit // pass. Lower it BEFORE children so instruction order matches JSX @@ -398,15 +398,17 @@ fn is_jsx_runtime_fragment(builder: &HirBuilder, tag: &Expr) -> bool { host.ref_name(ref_) == b"Fragment" && host.module_scope().generated.contains(&ref_) } +bun_core::bool_enum!(StaticChildren); + /// The visit pass packs children into the props object as either a single /// expression or an `E::Array` (when there were ≥2 children or a spread child). fn lower_jsx_children( builder: &mut HirBuilder, value: &Expr, - is_static_children: bool, + is_static_children: StaticChildren, out: &mut HirVec, ) -> Result<(), CompilerError> { - if is_static_children { + if is_static_children == StaticChildren::Yes { if let ExprData::EArray(arr) = &value.data { for item in arr.items.iter() { match &item.data { diff --git a/src/react_compiler/lowering/build_hir/mod.rs b/src/react_compiler/lowering/build_hir/mod.rs index 9226a7e551be..0dd53b09c5d4 100644 --- a/src/react_compiler/lowering/build_hir/mod.rs +++ b/src/react_compiler/lowering/build_hir/mod.rs @@ -19,10 +19,10 @@ use crate::diagnostics::{ CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, ErrorCategory, }; use crate::hir::{ - AstAlloc, BlockKind, Effect, EvaluationOrder, HirFunction, HirVec, IdentifierId, - InstructionKind, InstructionValue, ParamPattern, Place, PrimitiveValue, ReactFunctionType, - ReturnVariant, SourceLocation, SpreadPattern, StoreStr, Terminal, VariableBinding, - environment::Environment, + AstAlloc, BlockKind, Effect, EvaluationOrder, FunctionNesting, HirFunction, HirVec, + IdentifierId, InstructionKind, InstructionValue, ParamPattern, Place, PrimitiveValue, + ReactFunctionType, ReturnVariant, SourceLocation, SpreadPattern, StoreStr, Terminal, + VariableBinding, environment::Environment, }; use bun_ast::expr::Data as ExprData; use bun_ast::stmt::Data as StmtData; @@ -102,7 +102,7 @@ pub(crate) fn lower( scope, // component_scope = function_scope for top-level &context_identifiers, import_bindings, - true, // is_top_level + FunctionNesting::TopLevel, )?; Ok(hir_func) @@ -123,7 +123,7 @@ pub(super) fn lower_inner<'h>( component_scope: &'h ast::Scope, context_identifiers: &RefSet, import_bindings: &IndexMap, - is_top_level: bool, + is_top_level: FunctionNesting, ) -> Result<(HirFunction, IndexSet, IndexMap), CompilerError> { // `validate_ts_this_parameter`: Bun's parser strips `this` parameters // before this pass runs, so the upstream check is a no-op here. @@ -316,10 +316,9 @@ pub(super) fn lower_inner<'h>( loc, id, name_hint: None, - fn_type: if is_top_level { - env.fn_type - } else { - ReactFunctionType::Other + fn_type: match is_top_level { + FunctionNesting::TopLevel => env.fn_type, + FunctionNesting::Nested => ReactFunctionType::Other, }, params: hir_params, return_type_annotation: None, diff --git a/src/react_compiler/lowering/build_hir/stmt.rs b/src/react_compiler/lowering/build_hir/stmt.rs index 7ad735a7d736..adfd342153d6 100644 --- a/src/react_compiler/lowering/build_hir/stmt.rs +++ b/src/react_compiler/lowering/build_hir/stmt.rs @@ -17,7 +17,7 @@ use crate::lowering::hir_builder::{HirBuilder, convert_loc}; use super::expr::lower_reorderable_expression; use super::function::lower_function_declaration; use super::helpers::{ - AssignmentStyle, IdentifierForAssignment, build_temporary_place, lower_assignment, + AssignmentStyle, ComputedKey, IdentifierForAssignment, build_temporary_place, lower_assignment, lower_expression_to_temporary, lower_identifier_for_assignment, lower_object_property_key, lower_value_to_temporary, promote_temporary, }; @@ -1954,7 +1954,7 @@ pub(super) fn lower_assignment_binding( continue; } - let key = match lower_object_property_key(builder, &prop.key, false)? { + let key = match lower_object_property_key(builder, &prop.key, ComputedKey::No)? { Some(k) => k, None => continue, }; diff --git a/src/react_compiler/lowering/hir_builder.rs b/src/react_compiler/lowering/hir_builder.rs index e0a08e3bc45c..f69cb44db4d7 100644 --- a/src/react_compiler/lowering/hir_builder.rs +++ b/src/react_compiler/lowering/hir_builder.rs @@ -12,6 +12,7 @@ use crate::diagnostics::CompilerError; use crate::diagnostics::CompilerErrorDetail; use crate::diagnostics::ErrorCategory; use crate::diagnostics::Position; +use crate::hir::cfg_utils::BlockUsed; use crate::hir::cfg_utils::mark_instruction_ids; use crate::hir::environment::Environment; use crate::hir::visitors::each_terminal_successor; @@ -1077,12 +1078,13 @@ fn get_reverse_postordered_blocks( fn visit( hir: &HIR, block_id: BlockId, - is_used: bool, + is_used: BlockUsed, visited: &mut IndexSet, used: &mut IndexSet, used_fallthroughs: &mut IndexSet, postorder: &mut Vec, ) { + let is_used = is_used == BlockUsed::Yes; let was_used = used.contains(&block_id); let was_visited = visited.contains(&block_id); visited.insert(block_id); @@ -1107,13 +1109,21 @@ fn get_reverse_postordered_blocks( if is_used { used_fallthroughs.insert(ft); } - visit(hir, ft, false, visited, used, used_fallthroughs, postorder); + visit( + hir, + ft, + BlockUsed::No, + visited, + used, + used_fallthroughs, + postorder, + ); } for successor in successors { visit( hir, successor, - is_used, + BlockUsed::from_bool(is_used), visited, used, used_fallthroughs, @@ -1129,7 +1139,7 @@ fn get_reverse_postordered_blocks( visit( hir, hir.entry, - true, + BlockUsed::Yes, &mut visited, &mut used, &mut used_fallthroughs, diff --git a/src/react_compiler/optimization/drop_manual_memoization.rs b/src/react_compiler/optimization/drop_manual_memoization.rs index f342ef79105d..09e99b124c7d 100644 --- a/src/react_compiler/optimization/drop_manual_memoization.rs +++ b/src/react_compiler/optimization/drop_manual_memoization.rs @@ -367,7 +367,7 @@ fn collect_temporaries( _ => {} } - let is_optional = sidemap.optionals.contains(&lvalue_id); + let is_optional = IsOptional::from_bool(sidemap.optionals.contains(&lvalue_id)); let maybe_dep = collect_maybe_memo_dependencies(&instr.value, &sidemap.maybe_deps, is_optional, env); if let Some(dep) = maybe_dep { @@ -387,14 +387,17 @@ fn collect_temporaries( // collectMaybeMemoDependencies // ============================================================================= +bun_core::bool_enum!(pub(crate) IsOptional); + /// Collect loads from named variables and property reads into `maybe_deps`. /// Returns the variable + property reads represented by the instruction value. pub(crate) fn collect_maybe_memo_dependencies( value: &InstructionValue, maybe_deps: &HashMap, - optional: bool, + optional: IsOptional, env: &Environment, ) -> Option { + let optional = optional == IsOptional::Yes; match value { InstructionValue::LoadGlobal { binding, loc, .. } => Some(ManualMemoDependency { root: ManualMemoDependencyRoot::Global { diff --git a/src/react_compiler/pipeline.rs b/src/react_compiler/pipeline.rs index 8844745854a8..328f83d2443e 100644 --- a/src/react_compiler/pipeline.rs +++ b/src/react_compiler/pipeline.rs @@ -23,6 +23,7 @@ use std::collections::HashSet; use crate::diagnostics::CompilerError; +use crate::hir::FunctionNesting; use crate::hir::ReactFunctionType; use crate::hir::environment::{Environment, OutputMode}; use crate::hir::environment_config::EnvironmentConfig; @@ -438,7 +439,7 @@ fn run_hir_passes( timed!( "InferMutationAliasingEffects", - crate::inference::infer_mutation_aliasing_effects(hir, env, false) + crate::inference::infer_mutation_aliasing_effects(hir, env, FunctionNesting::TopLevel) )?; if env.output_mode == OutputMode::Ssr { @@ -460,7 +461,7 @@ fn run_hir_passes( timed!( "InferMutationAliasingRanges", - crate::inference::infer_mutation_aliasing_ranges(hir, env, false) + crate::inference::infer_mutation_aliasing_ranges(hir, env, FunctionNesting::TopLevel) )?; if env.enable_validations() { diff --git a/src/react_compiler/program.rs b/src/react_compiler/program.rs index 94b2421769f7..961a5057c5c9 100644 --- a/src/react_compiler/program.rs +++ b/src/react_compiler/program.rs @@ -51,6 +51,8 @@ pub enum JsxImportKind { CreateElement, } +bun_core::bool_enum!(pub RuntimeSentinel { MemoCache, EarlyReturn }); + /// Parser-side state the React Compiler needs. Implemented by `P` at the /// hook site so this crate stays free of a `bun_js_parser` dependency. /// @@ -95,7 +97,7 @@ pub trait Host { /// runtime export, declared on the parser's `runtime_imports` table on /// first use so the linker wires it to `runtime.js` exactly like /// `__toESM` / `__require` (no `S::Import` AST node). - fn runtime_sentinel(&mut self, _early: bool) -> Ref { + fn runtime_sentinel(&mut self, _early: RuntimeSentinel) -> Ref { unreachable!("runtime_sentinel requires the bun parser host") } @@ -919,12 +921,14 @@ fn is_valid_component_params(host: &dyn Host, args: &[G::Arg], has_rest_arg: boo // Function type detection // ----------------------------------------------------------------------- +bun_core::bool_enum!(InReactHoc); + fn get_react_function_type( host: &dyn Host, name: Option<&[u8]>, func: &FunctionNode<'_>, body_directives: &[&[u8]], - in_react_hoc: bool, + in_react_hoc: InReactHoc, opts: &ReactCompilerOptions, ) -> Option { let has_dynamic_gating_directive = opts.dynamic_gating.is_some() @@ -956,7 +960,7 @@ fn get_component_or_hook_like( host: &dyn Host, name: Option<&[u8]>, func: &FunctionNode<'_>, - in_react_hoc: bool, + in_react_hoc: InReactHoc, ) -> Option { let body = func.body().stmts.slice(); if let Some(fn_name) = name { @@ -978,7 +982,7 @@ fn get_component_or_hook_like( } } - if in_react_hoc { + if in_react_hoc == InReactHoc::Yes { return if calls_hooks_or_creates_jsx_in_stmts(host, body) { Some(ReactFunctionType::Component) } else { @@ -1093,6 +1097,8 @@ fn build_outlined_decl(outlined: CodegenFunction) -> Stmt { // finalized after. // ----------------------------------------------------------------------- +bun_core::bool_enum!(pub ModuleScopeOptOut); + pub struct ReactCompilerState { options: ReactCompilerOptions, env_config: EnvironmentConfig, @@ -1115,12 +1121,12 @@ impl ReactCompilerState { /// `p.react_compiler = Some(..)` without a `&mut p` borrow conflict. pub fn new( options: ReactCompilerOptions, - has_module_scope_opt_out: bool, + has_module_scope_opt_out: ModuleScopeOptOut, import_bindings: IndexMap, ) -> Self { bun_core::scoped_log!( react_compiler, - "ReactCompilerState::new opt_out={}", + "ReactCompilerState::new opt_out={:?}", has_module_scope_opt_out ); let context = ProgramContext::new( @@ -1231,7 +1237,7 @@ pub fn maybe_compile_pending( host, FunctionNode::Function(&tmp), name, - pending.in_react_hoc, + InReactHoc::from_bool(pending.in_react_hoc), )?; let mut flags = pending.flags; set_flag(&mut flags, flags::Function::IsAsync, cf.is_async); @@ -1251,7 +1257,7 @@ fn maybe_compile_node( host: &mut dyn Host, node: FunctionNode<'_>, name: Option<&[u8]>, - in_react_hoc: bool, + in_react_hoc: InReactHoc, ) -> Option { bun_core::scoped_log!( react_compiler, @@ -1307,7 +1313,7 @@ fn maybe_compile_node( // validation diagnostics; Bun skips early — the diagnostics channel is not // wired yet, and skipping avoids registering a spurious runtime import. if find_directive_disabling_memoization(&body_directives).is_some() - || state.context.has_module_scope_opt_out + || state.context.has_module_scope_opt_out == ModuleScopeOptOut::Yes { bun_core::scoped_log!(react_compiler, " -> bail: opt-out directive"); return None; diff --git a/src/react_compiler/reactive_scopes/promote_used_temporaries.rs b/src/react_compiler/reactive_scopes/promote_used_temporaries.rs index 8668a0b0b367..23b5e1645faa 100644 --- a/src/react_compiler/reactive_scopes/promote_used_temporaries.rs +++ b/src/react_compiler/reactive_scopes/promote_used_temporaries.rs @@ -87,7 +87,7 @@ pub(crate) fn promote_used_temporaries(func: &mut ReactiveFunction, env: &mut En } } } - let mut inter_state: IdMap = IdMap::new(); + let mut inter_state: IdMap = IdMap::new(); promote_interposed_block( &func.body, &mut state, @@ -544,10 +544,12 @@ fn visit_hir_function_for_promotion(func_id: FunctionId, state: &mut State, env: // Phase 3: PromoteInterposedTemporaries // ============================================================================= +bun_core::bool_enum!(NeedsPromotion); + fn promote_interposed_block( block: &ReactiveBlock, state: &mut State, - inter_state: &mut IdMap, + inter_state: &mut IdMap, consts: &mut HashSet, globals: &mut HashSet, env: &mut Environment, @@ -587,13 +589,16 @@ fn promote_interposed_block( fn promote_interposed_place( place: &Place, state: &mut State, - inter_state: &mut IdMap, + inter_state: &mut IdMap, consts: &HashSet, env: &mut Environment, ) { if let Some(&(id, needs_promotion)) = inter_state.get(place.identifier) { let identifier = &env.identifiers[id.0 as usize]; - if needs_promotion && identifier.name.is_none() && !consts.contains(&id) { + if needs_promotion == NeedsPromotion::Yes + && identifier.name.is_none() + && !consts.contains(&id) + { promote_identifier(id, state, env); } } @@ -602,7 +607,7 @@ fn promote_interposed_place( fn promote_interposed_instruction( instr: &ReactiveInstruction, state: &mut State, - inter_state: &mut IdMap, + inter_state: &mut IdMap, consts: &mut HashSet, globals: &mut HashSet, env: &mut Environment, @@ -671,13 +676,14 @@ fn promote_interposed_instruction( { // Mark all tracked temporaries as needing promotion for entry in inter_state.values_mut() { - entry.1 = true; + entry.1 = NeedsPromotion::Yes; } } if let Some(lvalue) = &instr.lvalue { let identifier = &env.identifiers[lvalue.identifier.0 as usize]; if identifier.name.is_none() { - inter_state.insert(lvalue.identifier, (lvalue.identifier, false)); + inter_state + .insert(lvalue.identifier, (lvalue.identifier, NeedsPromotion::No)); } } } @@ -705,7 +711,8 @@ fn promote_interposed_instruction( if consts.contains(&load_place.identifier) { consts.insert(lvalue.identifier); } - inter_state.insert(lvalue.identifier, (lvalue.identifier, false)); + inter_state + .insert(lvalue.identifier, (lvalue.identifier, NeedsPromotion::No)); } } // Visit operands @@ -722,7 +729,8 @@ fn promote_interposed_instruction( } let identifier = &env.identifiers[lvalue.identifier.0 as usize]; if identifier.name.is_none() { - inter_state.insert(lvalue.identifier, (lvalue.identifier, false)); + inter_state + .insert(lvalue.identifier, (lvalue.identifier, NeedsPromotion::No)); } } // Visit operands @@ -780,7 +788,7 @@ fn promote_interposed_instruction( fn promote_interposed_value( value: &ReactiveValue, state: &mut State, - inter_state: &mut IdMap, + inter_state: &mut IdMap, consts: &mut HashSet, globals: &mut HashSet, env: &mut Environment, @@ -824,7 +832,7 @@ fn promote_interposed_value( fn promote_interposed_terminal( stmt: &ReactiveTerminalStatement, state: &mut State, - inter_state: &mut IdMap, + inter_state: &mut IdMap, consts: &mut HashSet, globals: &mut HashSet, env: &mut Environment, diff --git a/src/react_compiler/reactive_scopes/prune_non_escaping_scopes.rs b/src/react_compiler/reactive_scopes/prune_non_escaping_scopes.rs index 491076bd79f0..d8cf35977461 100644 --- a/src/react_compiler/reactive_scopes/prune_non_escaping_scopes.rs +++ b/src/react_compiler/reactive_scopes/prune_non_escaping_scopes.rs @@ -43,6 +43,8 @@ use crate::reactive_scopes::visitors::Transformed; use crate::reactive_scopes::visitors::transform_reactive_function; use crate::reactive_scopes::visitors::visit_reactive_function; +bun_core::bool_enum!(ForceMemoize); + // ============================================================================= // Public entry point // ============================================================================= @@ -1108,11 +1110,12 @@ fn compute_memoized_identifiers(state: &CollectState) -> HashSet fn visit( id: DeclarationId, - force_memoize: bool, + force_memoize: ForceMemoize, identifier_nodes: &mut IdMap, scope_nodes: &mut IdMap, bool)>, memoized: &mut HashSet, ) -> bool { + let force_memoize = force_memoize == ForceMemoize::Yes; let Some(&(level, _, _, _, seen)) = identifier_nodes.get(id) else { return false; }; @@ -1134,7 +1137,13 @@ fn compute_memoized_identifiers(state: &CollectState) -> HashSet .collect(); let mut has_memoized_dependency = false; for dep in deps { - let is_dep_memoized = visit(dep, false, identifier_nodes, scope_nodes, memoized); + let is_dep_memoized = visit( + dep, + ForceMemoize::No, + identifier_nodes, + scope_nodes, + memoized, + ); has_memoized_dependency |= is_dep_memoized; } @@ -1176,7 +1185,13 @@ fn compute_memoized_identifiers(state: &CollectState) -> HashSet let deps: Vec = scope_nodes.get(id).unwrap().0.clone(); for dep in deps { - visit(dep, true, identifier_nodes, scope_nodes, memoized); + visit( + dep, + ForceMemoize::Yes, + identifier_nodes, + scope_nodes, + memoized, + ); } } @@ -1185,7 +1200,7 @@ fn compute_memoized_identifiers(state: &CollectState) -> HashSet for value in escaping { visit( value, - false, + ForceMemoize::No, &mut identifier_nodes, &mut scope_nodes, &mut memoized, diff --git a/src/react_compiler/validation/validate_exhaustive_dependencies.rs b/src/react_compiler/validation/validate_exhaustive_dependencies.rs index 3a20a45ac1f1..00c9b0f79f3c 100644 --- a/src/react_compiler/validation/validate_exhaustive_dependencies.rs +++ b/src/react_compiler/validation/validate_exhaustive_dependencies.rs @@ -11,8 +11,8 @@ use crate::hir::visitors::{ each_instruction_value_operand_with_functions, each_lvalue, each_terminal_operand, }; use crate::hir::{ - ArrayElement, AstAlloc, BlockId, DependencyPathEntry, HirFunction, HirVec, Identifier, - IdentifierId, InstructionKind, InstructionValue, ManualMemoDependency, + ArrayElement, AstAlloc, BlockId, DependencyPathEntry, FunctionNesting, HirFunction, HirVec, + Identifier, IdentifierId, InstructionKind, InstructionValue, ManualMemoDependency, ManualMemoDependencyRoot, NonLocalBinding, Place, PlaceOrSpread, PropertyLiteral, StoreStr, Terminal, Type, hir_vec, }; @@ -69,7 +69,7 @@ pub(crate) fn validate_exhaustive_dependencies( &env.functions, &mut temporaries, &mut Some(&mut callbacks), - false, + FunctionNesting::TopLevel, )?; // Set has_invalid_deps on StartMemoize instructions that had validation errors @@ -468,12 +468,12 @@ fn collect_dependencies( functions: &[HirFunction], temporaries: &mut IdMap, callbacks: &mut Option<&mut Callbacks<'_>>, - is_function_expression: bool, + is_function_expression: FunctionNesting, ) -> Result { let optionals = find_optional_places(func); let mut locals: HashSet = HashSet::new(); - if is_function_expression { + if is_function_expression == FunctionNesting::Nested { for param in &func.params { let place = param.place(); locals.insert(place.identifier); @@ -751,7 +751,7 @@ fn collect_dependencies( functions, temporaries, &mut None, - true, + FunctionNesting::Nested, )?; temporaries.insert(lvalue_id, function_deps.clone()); add_dependency(&function_deps, &mut dependencies, &locals); diff --git a/src/react_compiler/validation/validate_no_derived_computations_in_effects.rs b/src/react_compiler/validation/validate_no_derived_computations_in_effects.rs index f1aad450210b..82db86eeef0e 100644 --- a/src/react_compiler/validation/validate_no_derived_computations_in_effects.rs +++ b/src/react_compiler/validation/validate_no_derived_computations_in_effects.rs @@ -166,6 +166,8 @@ impl LocKey { } } +bun_core::bool_enum!(IsStateSource); + #[derive(Debug, Clone)] struct DerivationCache { has_changes: bool, @@ -243,9 +245,9 @@ impl DerivationCache { derived_name: Option, source_ids: crate::collections::IndexSet, type_of_value: TypeOfValue, - is_state_source: bool, + is_state_source: IsStateSource, ) { - let mut final_is_source = is_state_source; + let mut final_is_source = is_state_source == IsStateSource::Yes; if !final_is_source { for source_id in &source_ids { if let Some(source_metadata) = self.cache.get(source_id) { @@ -495,7 +497,7 @@ fn record_phi_derivations( name, source_ids, type_of_value, - false, + IsStateSource::No, ); } } @@ -522,7 +524,7 @@ fn record_instruction_derivations( ); let mut type_of_value = TypeOfValue::Ignored; - let is_source = false; + let is_source = IsStateSource::No; let mut sources: crate::collections::IndexSet = crate::collections::IndexSet::new(); @@ -579,7 +581,7 @@ fn record_instruction_derivations( name, crate::collections::IndexSet::new(), TypeOfValue::FromState, - true, + IsStateSource::Yes, ); return Ok(()); } @@ -618,7 +620,7 @@ fn record_instruction_derivations( name, crate::collections::IndexSet::new(), TypeOfValue::FromState, - true, + IsStateSource::Yes, ); return Ok(()); } @@ -694,7 +696,7 @@ fn record_instruction_derivations( name, sources.clone(), type_of_value, - false, + IsStateSource::No, ); } } @@ -813,13 +815,16 @@ fn build_tree_node( children } +bun_core::bool_enum!(IsLast); + fn render_tree( node: &TreeNode, indent: &str, - is_last: bool, + is_last: IsLast, props_set: &mut crate::collections::IndexSet, state_set: &mut crate::collections::IndexSet, ) -> String { + let is_last = is_last == IsLast::Yes; let prefix = format!( "{}{}", indent, @@ -855,7 +860,7 @@ fn render_tree( if !node.children.is_empty() { result += "\n"; for (index, child) in node.children.iter().enumerate() { - let is_last_child = index == node.children.len() - 1; + let is_last_child = IsLast::from_bool(index == node.children.len() - 1); result += &render_tree(child, &child_indent, is_last_child, props_set, state_set); if index < node.children.len() - 1 { result += "\n"; @@ -1091,7 +1096,7 @@ fn validate_effect( render_tree( node, "", - index == root_nodes.len() - 1, + IsLast::from_bool(index == root_nodes.len() - 1), &mut props_set, &mut state_set, ) diff --git a/src/react_compiler/validation/validate_preserved_manual_memoization.rs b/src/react_compiler/validation/validate_preserved_manual_memoization.rs index 374b76eacb64..c8ada279a404 100644 --- a/src/react_compiler/validation/validate_preserved_manual_memoization.rs +++ b/src/react_compiler/validation/validate_preserved_manual_memoization.rs @@ -429,7 +429,7 @@ fn record_deps_in_value(value: &ReactiveValue, state: &mut VisitorState) { crate::optimization::drop_manual_memoization::collect_maybe_memo_dependencies( iv, &state.temporaries, - false, + crate::optimization::drop_manual_memoization::IsOptional::No, state.env, ) { diff --git a/src/resolver/data_url.rs b/src/resolver/data_url.rs index 2f06f4539302..b915bdb8fc79 100644 --- a/src/resolver/data_url.rs +++ b/src/resolver/data_url.rs @@ -167,7 +167,11 @@ impl<'a> DataURL<'a> { } pub fn decode_mime_type(&self) -> bun_http_types::MimeType::MimeType { - bun_http_types::MimeType::MimeType::init(self.mime_type, false, None) + bun_http_types::MimeType::MimeType::init( + self.mime_type, + bun_http_types::MimeType::Dupe::No, + None, + ) } /// Decodes the data from the data URL. Always returns an owned slice. diff --git a/src/resolver/fs.rs b/src/resolver/fs.rs index d1edb9f43517..25a76d1f7512 100644 --- a/src/resolver/fs.rs +++ b/src/resolver/fs.rs @@ -94,6 +94,8 @@ impl strings::Appender for FilenameStoreAppender { // `EntryKindResolver` trait so this block does not depend on `crate::fs::RealFS`. // ══════════════════════════════════════════════════════════════════════════ +bun_core::bool_enum!(pub StoreFd); + /// Decouples `Entry::kind`/`symlink` (the lazy-stat path) from the concrete /// `RealFS` type; the inline-`fs::RealFS` in `lib.rs` impls this by forwarding /// to `RealFS::kind`. @@ -103,7 +105,7 @@ pub trait EntryKindResolver { dir: &[u8], base: &[u8], existing_fd: Fd, - store_fd: bool, + store_fd: StoreFd, ) -> crate::CrateResult; } @@ -222,7 +224,7 @@ impl Entry { // borrow while a `&mut Entry` (borrowed out of `RealFS.entries`) is live. // Generic over `R: EntryKindResolver` so this block is independent of // which `RealFS` copy `fs` points at (see file-top comment). - pub unsafe fn kind(&self, fs: *mut R, store_fd: bool) -> EntryKind { + pub unsafe fn kind(&self, fs: *mut R, store_fd: StoreFd) -> EntryKind { if self.need_stat.load(Ordering::Acquire) { let _guard = self.mutex.lock_guard(); // Relaxed: every write happens under `mutex`, which we hold. @@ -258,7 +260,7 @@ impl Entry { pub(crate) unsafe fn symlink( &self, fs: *mut R, - store_fd: bool, + store_fd: StoreFd, ) -> &'static [u8] { if self.need_stat.load(Ordering::Acquire) { let _guard = self.mutex.lock_guard(); @@ -728,20 +730,31 @@ unsafe impl Send for Entry {} // `use_shared_buffer`/`stream` at runtime and want only the bytes. // ══════════════════════════════════════════════════════════════════════════ +bun_core::bool_enum!(pub UseSharedBuffer); +bun_core::bool_enum!(pub Stream); + /// Runtime-bool → const-generic dispatcher for `cache::Fs::read_file{,_shared}`. /// Returns just the contents (`Cow::Borrowed` ⇢ shared-buffer arm, /// `Cow::Owned` ⇢ heap arm); callers `.map(Contents::from)` to tag provenance. pub fn read_file_contents<'buf>( file: &bun_sys::File, - use_shared_buffer: bool, + use_shared_buffer: UseSharedBuffer, shared: &'buf mut MutableString, - stream: bool, + stream: Stream, ) -> crate::CrateResult> { match (use_shared_buffer, stream) { - (true, true) => read_file_with_handle_impl::(None, file, shared), - (true, false) => read_file_with_handle_impl::(None, file, shared), - (false, true) => read_file_with_handle_impl::(None, file, shared), - (false, false) => read_file_with_handle_impl::(None, file, shared), + (UseSharedBuffer::Yes, Stream::Yes) => { + read_file_with_handle_impl::(None, file, shared) + } + (UseSharedBuffer::Yes, Stream::No) => { + read_file_with_handle_impl::(None, file, shared) + } + (UseSharedBuffer::No, Stream::Yes) => { + read_file_with_handle_impl::(None, file, shared) + } + (UseSharedBuffer::No, Stream::No) => { + read_file_with_handle_impl::(None, file, shared) + } } .map(|p| p.contents) } diff --git a/src/resolver/lib.rs b/src/resolver/lib.rs index 208c1678127e..67259f0d603c 100644 --- a/src/resolver/lib.rs +++ b/src/resolver/lib.rs @@ -461,7 +461,7 @@ pub mod fs { &mut self, dir: &[u8], generation: Generation, - store_fd: bool, + store_fd: StoreFd, ) -> crate::CrateResult<&'static mut EntriesOption> { let r = self.fs.read_directory(dir, None, generation, store_fd)?; // SAFETY: `r` borrows the BSSMap singleton (process-lifetime); re-erase @@ -687,7 +687,7 @@ pub mod fs { // Re-exported here so the public path `bun_resolver::fs::*` is preserved. pub use crate::fs_full::{ DirEntry, DirEntryIterator, Entry, EntryCache, EntryKind, EntryKindResolver, EntryLookup, - FilenameStoreAppender, dir_entry, + FilenameStoreAppender, StoreFd, dir_entry, }; use bun_core::Generation; @@ -1120,7 +1120,7 @@ pub mod fs { /// fresh `DirEntry` (re-using `prev_map` Entry slots where the name matches). fn readdir( &mut self, - store_fd: bool, + store_fd: StoreFd, mut prev_map: Option<&mut dir_entry::EntryMap>, dir_: &'static [u8], generation: Generation, @@ -1130,7 +1130,7 @@ pub mod fs { let mut iter = bun_sys::iterate_dir(handle); let mut dir = DirEntry::init(dir_, generation); - if store_fd { + if store_fd == StoreFd::Yes { FileSystem::set_max_fd(bun_sys::Fd::native(handle)); dir.fd = handle; } @@ -1194,7 +1194,7 @@ pub mod fs { dir_: &[u8], handle_: Option, generation: Generation, - store_fd: bool, + store_fd: StoreFd, ) -> crate::CrateResult<&mut EntriesOption> { self.read_directory_with_iterator(dir_, handle_, generation, store_fd, ()) } @@ -1214,7 +1214,7 @@ pub mod fs { dir_maybe_trail_slash: &[u8], maybe_handle: Option, generation: Generation, - store_fd: bool, + store_fd: StoreFd, iterator: I, ) -> crate::CrateResult<&'static mut EntriesOption> { let dir = strings::paths::without_trailing_slash_windows_path(dir_maybe_trail_slash); @@ -1270,7 +1270,8 @@ pub mod fs { // Close the handle on every exit path. Use // scopeguard so close happens even if `readdir`/`put` early-return with `?`. - let should_close_handle = !had_handle && (!store_fd || self.need_to_close_files()); + let should_close_handle = + !had_handle && (store_fd == StoreFd::No || self.need_to_close_files()); let _close_guard = scopeguard::guard(handle, move |h| { if should_close_handle { let _ = bun_sys::close(h); @@ -1322,7 +1323,7 @@ pub mod fs { // SAFETY: BSSMap-owned; entries_mutex held. unsafe { (*original).data.clear() }; } - if store_fd && !entries.fd.is_valid() { + if store_fd == StoreFd::Yes && !entries.fd.is_valid() { entries.fd = handle; } @@ -1371,7 +1372,7 @@ pub mod fs { dir_: &[u8], base: &[u8], existing_fd: Fd, - store_fd: bool, + store_fd: StoreFd, ) -> crate::CrateResult { use bun_paths::resolve_path::{join_abs_string_buf, platform}; #[cfg(not(windows))] @@ -1492,7 +1493,7 @@ pub mod fs { if is_symlink { let file: Fd = if let Some(valid) = existing_fd.unwrap_valid() { valid - } else if store_fd { + } else if store_fd == StoreFd::Yes { bun_sys::open_file_absolute_z( absolute_path_c, bun_sys::OpenFlags::READ_ONLY, @@ -1515,7 +1516,9 @@ pub mod fs { let need_to_close_files = self.need_to_close_files(); let cache_ptr: *mut EntryCache = &raw mut cache; let _guard = scopeguard::guard(file, move |file| { - if (!store_fd || need_to_close_files) && !existing_fd.is_valid() { + if (store_fd == StoreFd::No || need_to_close_files) + && !existing_fd.is_valid() + { let _ = bun_sys::close(file); } else if bun_core::feature_flags::STORE_FILE_DESCRIPTORS { // SAFETY: `cache_ptr` points into a stack local that outlives this guard. @@ -1552,7 +1555,7 @@ pub mod fs { dir: &[u8], base: &[u8], existing_fd: bun_sys::Fd, - store_fd: bool, + store_fd: StoreFd, ) -> crate::CrateResult { self.kind(dir, base, existing_fd, store_fd) } @@ -1624,7 +1627,7 @@ pub mod fs { }); // SAFETY: see above — exclusive `&mut` on the prev map for the duration of `readdir`. let prev = Some(unsafe { &mut (*e_ptr).data }); - match self.readdir(false, prev, dir, generation, handle, ()) { + match self.readdir(StoreFd::No, prev, dir, generation, handle, ()) { Ok(new_entry) => { // SAFETY: see above. unsafe { (*e_ptr).data.clear() }; @@ -1739,8 +1742,8 @@ pub mod fs { /// `bun_bundler::cache`) routes through ONE body instead of inlining a /// subset of `readFileWithHandleAndAllocator`. pub use super::fs_full::{ - BOM, PathContentsPair, read_file_contents, read_file_contents_in_arena, - read_file_with_handle_impl, + BOM, PathContentsPair, Stream, UseSharedBuffer, read_file_contents, + read_file_contents_in_arena, read_file_with_handle_impl, }; /// Re-export `StatHash` from the full `fs.rs` port so `bun_runtime::server::FileRoute` @@ -1808,7 +1811,9 @@ pub mod fs { // owns this impl. See PORTING.md §Dispatch. // ────────────────────────────────────────────────────────────────────────── pub mod dir_entry_accessor { - use crate::fs::{DirEntry, EntriesOption, Entry, EntryKind, FileSystem as FS, Implementation}; + use crate::fs::{ + DirEntry, EntriesOption, Entry, EntryKind, FileSystem as FS, Implementation, StoreFd, + }; use bun_core::ZStr; use bun_glob::walk::{Accessor, AccessorDirEntry, AccessorDirIter, AccessorHandle}; use bun_paths::{PathBuffer, Platform, resolve_path}; @@ -1917,7 +1922,7 @@ pub mod dir_entry_accessor { let fs: *mut Implementation = &raw mut FS::instance().fs; // SAFETY: fs points at the process-global RealFS; the lazy-stat // rewrite inside `kind()` is serialized on the per-entry mutex. - let kind = unsafe { entry.kind(fs, true) }; + let kind = unsafe { entry.kind(fs, StoreFd::Yes) }; let fskind = match kind { EntryKind::File => bun_sys::FileKind::File, EntryKind::Dir => bun_sys::FileKind::Directory, @@ -2029,7 +2034,7 @@ pub mod dir_entry_accessor { // return Err(SysError::from_code(E::NOTDIR, Syscall::Tag::open)); let res = FS::instance() .fs - .read_directory(path, None, 0, false) + .read_directory(path, None, 0, StoreFd::No) .map_err(crate::Error::into_core)?; match res { EntriesOption::Entries(entry) => { @@ -2084,7 +2089,7 @@ pub mod cache { shared_buffer: MutableString::init(0).expect("unreachable"), macro_shared_buffer: MutableString::init(0).expect("unreachable"), use_alternate_source_cache: false, - stream: false, + stream: fs_mod::Stream::No, }, json: Json::init(), } @@ -2098,7 +2103,7 @@ pub mod cache { pub(crate) macro_shared_buffer: MutableString, pub use_alternate_source_cache: bool, - pub(crate) stream: bool, + pub(crate) stream: fs_mod::Stream, } /// Optional external destructor (`function(ctx)`) for foreign-owned @@ -2135,7 +2140,7 @@ pub mod cache { /// (forbidden per docs/PORTING.md §Forbidden patterns). The enum makes /// provenance explicit so `deinit` matches on the variant instead of /// guessing — the old scheme would `heap::take` a `MutableString`-owned - /// pointer on the `use_shared_buffer=true` path (UB). + /// pointer on the `UseSharedBuffer::Yes` path (UB). #[derive(Default)] pub enum Contents { /// Empty / static literal. No-op on `deinit`. @@ -2221,7 +2226,7 @@ pub mod cache { /// Adapter for the canonical `fs::read_file_contents` (returns /// `Cow<'buf,[u8]>` per the spec `PathContentsPair` shape). `Borrowed` /// always points into the per-thread `shared_buffer` on the - /// `use_shared_buffer=true` path → tag as `SharedBuffer` so `deinit` is a + /// `UseSharedBuffer::Yes` path → tag as `SharedBuffer` so `deinit` is a /// no-op; `Owned` is the heap arm. impl<'buf> From> for Contents { fn from(c: std::borrow::Cow<'buf, [u8]>) -> Self { @@ -2329,7 +2334,7 @@ pub mod cache { _fs: &mut fs_mod::FileSystem, path: &[u8], dirname_fd: Fd, - use_shared_buffer: bool, + use_shared_buffer: fs_mod::UseSharedBuffer, _file_handle: Option, arena: Option<&bun_alloc::Arena>, ) -> crate::CrateResult { @@ -2394,7 +2399,7 @@ pub mod cache { // Read straight into the per-call arena so the source bytes // are reclaimed by `mi_heap_destroy` instead of pinning a // segment in the worker thread's default heap. - (false, Some(arena)) => { + (fs_mod::UseSharedBuffer::No, Some(arena)) => { match fs_mod::read_file_contents_in_arena(file_handle, path, arena) { Ok((_, 0)) => Contents::Empty, Ok((ptr, len)) => Contents::Arena { ptr, len }, diff --git a/src/resolver/options.rs b/src/resolver/options.rs index 6000dfd81eb7..c470b1e56605 100644 --- a/src/resolver/options.rs +++ b/src/resolver/options.rs @@ -118,12 +118,19 @@ impl Default for ExtensionOrder { } } } +bun_core::bool_enum!(pub(crate) IsNodeModules); + impl ExtensionOrder { /// Returns the /// [`ExtOrder`] tag; resolve to a slice via /// [`BundleOptions::ext_order_slice`]. - pub(crate) fn kind(&self, kind: bun_ast::ImportKind, is_node_modules: bool) -> ExtOrder { + pub(crate) fn kind( + &self, + kind: bun_ast::ImportKind, + is_node_modules: IsNodeModules, + ) -> ExtOrder { use bun_ast::ImportKind as K; + let is_node_modules = is_node_modules == IsNodeModules::Yes; match kind { K::Url | K::AtConditional | K::At => ExtOrder::Css, K::Stmt | K::EntryPointBuild | K::EntryPointRun | K::Dynamic => { diff --git a/src/resolver/package_json.rs b/src/resolver/package_json.rs index 12dd6aa32601..dc35014a93eb 100644 --- a/src/resolver/package_json.rs +++ b/src/resolver/package_json.rs @@ -409,7 +409,7 @@ impl PackageJSON { r_fs, package_json_path, dirname_fd, - false, + fs::UseSharedBuffer::No, None, None, ) { @@ -1540,6 +1540,8 @@ fn module_bufs() -> *mut ModuleBufs { }) } +bun_core::bool_enum!(SubpathKind { Exports, Imports }); + // `module_type` / `debug_logs` are `&'a mut T`, so reading/writing them // requires `&mut self`. All resolution methods take `&mut self`. impl<'a> ESModule<'a> { @@ -1561,7 +1563,7 @@ impl<'a> ESModule<'a> { }; } - let result = self.resolve_imports_exports(specifier, imports, true, b"/"); + let result = self.resolve_imports_exports(specifier, imports, SubpathKind::Imports, b"/"); match result.status { Status::Undefined | Status::Null => Resolution { @@ -1663,14 +1665,20 @@ impl<'a> ESModule<'a> { if let Some(main_export) = main_export { if !matches!(main_export.data, EntryData::Null) { - let result = self.resolve_target::(package_url, main_export, b"", false); + let result = self.resolve_target::( + package_url, + main_export, + b"", + SubpathKind::Exports, + ); if result.status != Status::Null && result.status != Status::Undefined { return result; } } } } else if matches!(exports.data, EntryData::Map(_)) && exports.keys_start_with_dot() { - let result = self.resolve_imports_exports(subpath, exports, false, package_url); + let result = + self.resolve_imports_exports(subpath, exports, SubpathKind::Exports, package_url); if result.status != Status::Null && result.status != Status::Undefined { return result; } @@ -1700,7 +1708,7 @@ impl<'a> ESModule<'a> { &mut self, match_key: &[u8], match_obj: &Entry, - is_imports: bool, + is_imports: SubpathKind, package_url: &[u8], ) -> Resolution { if let Some(logs) = self.debug_logs.as_deref_mut() { @@ -1813,7 +1821,7 @@ impl<'a> ESModule<'a> { package_url: &[u8], target: &Entry, subpath: &[u8], - internal: bool, + internal: SubpathKind, ) -> Resolution { match &target.data { EntryData::String(str) => { @@ -1928,7 +1936,7 @@ impl<'a> ESModule<'a> { )); } - if internal + if internal == SubpathKind::Imports && !strings::has_prefix(str, b"../") && !strings::has_prefix(str, b"/") { diff --git a/src/resolver/resolver.rs b/src/resolver/resolver.rs index 1e45ac9687a1..c228b69ed02c 100644 --- a/src/resolver/resolver.rs +++ b/src/resolver/resolver.rs @@ -264,6 +264,7 @@ pub use crate::data_url::DataURL; pub use crate::dir_info as DirInfo; pub use crate::dir_info::DirInfoRef; pub use ::bun_options_types::global_cache::GlobalCache; +use ::bun_options_types::global_cache::HasNodeModulesFolder; // Sibling resolver modules. They retain the same item names so cross-references // inside `impl Resolver` resolve unchanged. @@ -511,7 +512,7 @@ pub struct Resolver<'a> { // `&Loader` across that `&mut Loader` would be aliased-&mut UB; a raw // pointer carries no aliasing guarantee. pub env_loader: Option>, - pub store_fd: bool, + pub store_fd: Fs::StoreFd, pub standalone_module_graph: Option<&'a dyn StandaloneModuleGraph>, @@ -594,6 +595,9 @@ impl Drop for ResolverLogScope { } } +bun_core::bool_enum!(pub(crate) ForbidImports); +bun_core::bool_enum!(EnableLogging); + impl<'a> Resolver<'a> { /// Per-worker constructor — replaces the bundler's prior bitwise /// copy-from-`from` for the resolver @@ -932,7 +936,7 @@ impl<'a> Resolver<'a> { package_manager: None, on_wake_package_manager: Default::default(), env_loader: None, - store_fd: false, + store_fd: Fs::StoreFd::No, standalone_module_graph: None, prefer_module_field: true, custom_dir_paths: None, @@ -1644,7 +1648,7 @@ impl<'a> Resolver<'a> { // length so `buf` can be re-borrowed for null-termination below. let out_len = self.fs_ref().abs_buf(&parts, &mut buf).len(); - let store_fd = self.store_fd; + let store_fd = self.store_fd == Fs::StoreFd::Yes; if !query.entry().cache().fd.is_valid() && store_fd { buf[out_len] = 0; @@ -2157,7 +2161,10 @@ impl<'a> Resolver<'a> { let prev_extension_order = self.extension_order; // NOTE: defer restore reshaped — restored before each return if strings::path_contains_node_modules_folder(abs_path) { - self.extension_order = self.opts.extension_order.kind(kind, true); + self.extension_order = self + .opts + .extension_order + .kind(kind, options::IsNodeModules::Yes); } // Re-append the separator the join stripped so "." resolves like "./". @@ -2267,7 +2274,7 @@ impl<'a> Resolver<'a> { kind, source_dir_info, global_cache, - false, + ForbidImports::No, &mut node_module, ) .is_success() @@ -2506,7 +2513,7 @@ impl<'a> Resolver<'a> { // to `&DirInfo` per use so overlapping shared reads are sound. _dir_info: DirInfoRef, global_cache: GlobalCache, - forbid_imports: bool, + forbid_imports: ForbidImports, out: &mut MatchResult, ) -> MatchStatus { let mut dir_info: DirInfoRef = _dir_info; @@ -2568,7 +2575,10 @@ impl<'a> Resolver<'a> { if let Some(_dir_info_package_json) = dir_info_package_json { let package_json = _dir_info_package_json.package_json().unwrap(); - if import_path.starts_with(b"#") && !forbid_imports && package_json.imports.is_some() { + if import_path.starts_with(b"#") + && forbid_imports == ForbidImports::No + && package_json.imports.is_some() + { let r = self.load_package_imports( import_path, _dir_info_package_json, @@ -2601,7 +2611,7 @@ impl<'a> Resolver<'a> { let esm_ = crate::package_json::Package::parse(import_path, bufs!(esm_subpath)); let source_dir_info = dir_info; - let mut any_node_modules_folder = false; + let mut any_node_modules_folder = HasNodeModulesFolder::No; let use_node_module_resolver = global_cache != GlobalCache::force; // Then check for the package in any enclosing "node_modules" directories @@ -2614,7 +2624,7 @@ impl<'a> Resolver<'a> { if !(dir_info.has_node_modules() || is_self_reference) { break 'node_modules; } - any_node_modules_folder = true; + any_node_modules_folder = HasNodeModulesFolder::Yes; let abs_path: &[u8] = if is_self_reference { dir_info.abs_path } else { @@ -2650,7 +2660,10 @@ impl<'a> Resolver<'a> { ast::ImportKind::Url | ast::ImportKind::AtConditional | ast::ImportKind::At => options::ExtOrder::Css, - _ => self.opts.extension_order.kind(kind, true), + _ => self + .opts + .extension_order + .kind(kind, options::IsNodeModules::Yes), }; if let Some(package_json) = pkg_dir_info.package_json() { @@ -2944,8 +2957,11 @@ impl<'a> Resolver<'a> { } for (dependency_id, dependency) in dependencies_list.iter().enumerate() { - if !strings::eql_long(dependency.name.slice(string_buf), esm.name, true) - { + if !strings::eql_long( + dependency.name.slice(string_buf), + esm.name, + strings::CheckLen::Yes, + ) { continue; } @@ -3427,7 +3443,7 @@ impl<'a> Resolver<'a> { unsafe { &mut *existing }.data.clear(); } - if self.store_fd { + if self.store_fd == Fs::StoreFd::Yes { new_entry.fd = open_dir; } // NOTE: see `dir_info_cached_maybe_log` — `DirEntry.data` holds a `NonNull`, @@ -3675,9 +3691,12 @@ impl<'a> Resolver<'a> { if kind == ast::ImportKind::At || kind == ast::ImportKind::AtConditional { self.extension_order } else { - self.opts - .extension_order - .kind(kind, resolved_dir_info.is_inside_node_modules()) + self.opts.extension_order.kind( + kind, + options::IsNodeModules::from_bool( + resolved_dir_info.is_inside_node_modules(), + ), + ) }; let base = bun_paths::basename(abs_esm_path); @@ -4008,7 +4027,14 @@ impl<'a> Resolver<'a> { out: &mut MatchResult, ) -> MatchStatus { if is_package_path(import_path) { - self.load_node_modules(import_path, kind, source_dir_info, global_cache, false, out) + self.load_node_modules( + import_path, + kind, + source_dir_info, + global_cache, + ForbidImports::No, + out, + ) } else { let Some(resolved) = self.fs_ref().abs_buf_checked( &[source_dir_info.abs_path, import_path], @@ -4033,7 +4059,7 @@ impl<'a> Resolver<'a> { unsafe { &mut *self.fs() }, file, dirname_fd, - false, + Fs::UseSharedBuffer::No, None, None, )?; @@ -4155,16 +4181,18 @@ impl<'a> Resolver<'a> { } fn dir_info_cached(&mut self, path: &[u8]) -> crate::CrateResult> { - self.dir_info_cached_maybe_log(true, path) + self.dir_info_cached_maybe_log(EnableLogging::Yes, path) } pub fn read_dir_info(&mut self, path: &[u8]) -> crate::CrateResult> { - self.dir_info_cached_maybe_log(false, path) + self.dir_info_cached_maybe_log(EnableLogging::No, path) } /// Like `readDirInfo`, but returns `null` instead of throwing an error. pub fn read_dir_info_ignore_error(&mut self, path: &[u8]) -> Option { - self.dir_info_cached_maybe_log(false, path).ok().flatten() + self.dir_info_cached_maybe_log(EnableLogging::No, path) + .ok() + .flatten() } // NOTE: `follow_symlinks` is `true` at every call @@ -4173,7 +4201,7 @@ impl<'a> Resolver<'a> { // monomorphizes to a single copy instead of two faulted in at startup. fn dir_info_cached_maybe_log( &mut self, - enable_logging: bool, + enable_logging: EnableLogging, raw_input_path: &[u8], ) -> crate::CrateResult> { // `self.mutex` is `&'static Mutex` (Copy) — bind it first so the guard @@ -4265,7 +4293,7 @@ impl<'a> Resolver<'a> { #[inline(never)] fn dir_info_cached_miss( &mut self, - enable_logging: bool, + enable_logging: EnableLogging, input_path: &[u8], top_result: allocators::Result, ) -> crate::CrateResult> { @@ -4417,7 +4445,7 @@ impl<'a> Resolver<'a> { // `Fs.FileSystem.setMaxFd()` which can flip `needToCloseFiles()` // mid-walk. Reach the RealFS via the `&'static` singleton accessor // instead of capturing a raw `*mut RealFS` (the read is `&self`-only). - let close_dirs_store_fd = self.store_fd; + let close_dirs_store_fd = self.store_fd == Fs::StoreFd::Yes; scopeguard::defer! { let n = open_dir_count.get(); if n > 0 && (!close_dirs_store_fd || Fs::FileSystem::get().fs.need_to_close_files()) { @@ -4548,7 +4576,7 @@ impl<'a> Resolver<'a> { self.dir_cache_mut().mark_not_found(queue_top.result); rfs!().entries.mark_not_found(cached_dir_entry_result); if err != crate::Error::Sys(bun_errno::SystemErrno::ENOENT) { - if enable_logging { + if enable_logging == EnableLogging::Yes { let pretty = queue_top_unsafe_path; let _ = self.log_mut().add_error_fmt( None, @@ -4695,7 +4723,11 @@ impl<'a> Resolver<'a> { // NOTE: bun_collections::StringHashMap exposes `clear`, which drops all entries. unsafe { &mut *existing }.data.clear(); } - new_entry.fd = if self.store_fd { open_dir } else { FD::INVALID }; + new_entry.fd = if self.store_fd == Fs::StoreFd::Yes { + open_dir + } else { + FD::INVALID + }; // NOTE: `DirEntry.data` is a `HashMap` // (`NonNull` inside), so a zeroed slot is UB and `*ptr = new_entry` would drop it. // Box `new_entry` directly for the fresh case; assign-into only for `in_place`. @@ -4819,7 +4851,7 @@ impl<'a> Resolver<'a> { .iter() .zip(tsconfig.paths.values().iter()) { - if strings::eql_long(key, path, true) { + if strings::eql_long(key, path, strings::CheckLen::Yes) { for original_path in value.iter() { let mut absolute_original_path: &[u8] = original_path; @@ -5056,7 +5088,7 @@ impl<'a> Resolver<'a> { kind, dir_info, global_cache, - true, + ForbidImports::Yes, out, ); } @@ -6300,7 +6332,7 @@ impl<'a> Resolver<'a> { let entries_fd = entries!().fd; if entries_fd.is_valid() && !lookup.entry().cache().fd.is_valid() - && self.store_fd + && self.store_fd == Fs::StoreFd::Yes { // Every cached-`Entry` rewrite takes the per-entry mutex. let _entry_guard = lookup.entry().mutex.lock_guard(); diff --git a/src/router/lib.rs b/src/router/lib.rs index 98bfb8cf3785..32adb871f3bd 100644 --- a/src/router/lib.rs +++ b/src/router/lib.rs @@ -576,7 +576,8 @@ impl<'a> RouteLoader<'a> { // once the stub forwards it. // SAFETY: no other live borrow of `*entry_ptr` here; // `resolver.fs_impl()` points at the process-global RealFS. - let kind = unsafe { (&*entry_ptr).kind(resolver.fs_impl(), false) }; + let kind = + unsafe { (&*entry_ptr).kind(resolver.fs_impl(), bun_resolver::fs::StoreFd::No) }; // SAFETY: shared read-only borrow for the match arms; the only // subsequent mutation is via `Route::parse` which takes the raw // pointer and reborrows internally. diff --git a/src/runtime/api/Archive.rs b/src/runtime/api/Archive.rs index f62b51994e5f..6671940a69ba 100644 --- a/src/runtime/api/Archive.rs +++ b/src/runtime/api/Archive.rs @@ -4,7 +4,7 @@ use std::ffi::CString; use crate::webcore::Blob; use crate::webcore::BlobExt as _; -use crate::webcore::blob::{Store as BlobStore, StoreRef}; +use crate::webcore::blob::{Store as BlobStore, StoreRef, WasString}; use bun_core::zig_string::Slice as ZigStringSlice; use bun_core::{self, Output, ZBox, strings}; use bun_glob as glob; @@ -857,8 +857,11 @@ impl TaskContext for BlobContext { // self.result already replaced with Uncompressed above — ownership transferred Ok(PromiseResult::Resolve(match self.output_type { BlobOutputType::Blob => { - let blob_ptr = - Blob::new(Blob::create_with_bytes_and_allocator(data, global, false)); + let blob_ptr = Blob::new(Blob::create_with_bytes_and_allocator( + data, + global, + WasString::No, + )); // SAFETY: blob_ptr is the heap allocation just produced by Blob::new. unsafe { (*blob_ptr).to_js(global) } } @@ -1165,8 +1168,11 @@ impl TaskContext for FilesContext { for entry in entries.iter_mut() { let data = core::mem::take(&mut entry.data); // Ownership transferred - let blob_ptr = - Blob::new(Blob::create_with_bytes_and_allocator(data, global, false)); + let blob_ptr = Blob::new(Blob::create_with_bytes_and_allocator( + data, + global, + WasString::No, + )); // SAFETY: blob_ptr is the heap allocation just produced by Blob::new. let blob = unsafe { &mut *blob_ptr }; blob.is_jsdom_file.set(true); diff --git a/src/runtime/api/BunObject.rs b/src/runtime/api/BunObject.rs index 25189b61d259..0dfabbf78f4a 100644 --- a/src/runtime/api/BunObject.rs +++ b/src/runtime/api/BunObject.rs @@ -82,7 +82,7 @@ use bun_jsc::{ // `bun_jsc::VirtualMachine` is the *module* re-export; the struct lives one level deeper. use crate::cli::open::Editor; use bun_core::{String as BunString, ZigString, strings}; -use bun_jsc::virtual_machine::{ResolveMode, VirtualMachine}; +use bun_jsc::virtual_machine::{IsRejection, ResolveMode, VirtualMachine}; use bun_paths::MAX_PATH_BYTES; #[cfg(not(windows))] use bun_paths::PathBuffer; @@ -411,7 +411,7 @@ fn shell_escape(global_this: &JSGlobalObject, callframe: &CallFrame) -> JsResult if bun_shell_parser::needs_escape_bunstr(*bunstr) { let result = bun_shell_parser::escape_bun_str::(*bunstr, &mut outbuf)?; - if !result { + if result == bun_shell_parser::Utf16Validity::Invalid { return Err(global_this.throw(format_args!( "String has invalid utf-16: {}", bstr::BStr::new(bunstr.byte_slice()), @@ -983,7 +983,7 @@ fn open_in_editor(global_this: &JSGlobalObject, callframe: &CallFrame) -> JsResu if let Some(sliced) = &editor_name { let prev_name = edit.name; - if !strings::eql_long(prev_name, sliced.slice(), true) { + if !strings::eql_long(prev_name, sliced.slice(), strings::CheckLen::Yes) { let prev = core::mem::take(edit); // Own the bytes in `name_storage` and // hand back a thread-lifetime borrow. @@ -1082,7 +1082,7 @@ fn sleep_sync(global_object: &JSGlobalObject, callframe: &CallFrame) -> JsResult // HOST_EXPORT(Bun__gc, c) pub fn gc(vm: &mut VirtualMachine, sync: bool) -> usize { - vm.garbage_collect(sync) + vm.garbage_collect(bun_jsc::GcMode::from_bool(sync)) } #[bun_jsc::host_fn] @@ -1845,6 +1845,7 @@ fn get_s3_default_client(global_this: &JSGlobalObject, _: &JSObject) -> JsResult // That can't compile in `bun_jsc`, so port the body here where the S3 // types are in scope and store the cached value through the public // `RareData.s3_default_client: Strong` field. + use crate::webcore::s3::credentials_jsc::RequestPayer; use crate::webcore::s3_client::S3Client; use bun_jsc::StrongOptional; // SAFETY: bun_vm() returns the live thread-local VM for a Bun-owned global. @@ -1869,7 +1870,7 @@ fn get_s3_default_client(global_this: &JSGlobalObject, _: &JSObject) -> JsResult None, None, None, - false, + RequestPayer::No, global_this, ) { Ok(v) => v, @@ -1882,7 +1883,7 @@ fn get_s3_default_client(global_this: &JSGlobalObject, _: &JSObject) -> JsResult options: aws_options.options, acl: aws_options.acl, storage_class: aws_options.storage_class, - request_payer: aws_options.request_payer, + request_payer: RequestPayer::from_bool(aws_options.request_payer), }; let js_client = ::to_js(client, global_this); js_client.ensure_still_alive(); @@ -1935,7 +1936,7 @@ fn get_is_standalone_executable(global_this: &JSGlobalObject, _: &JSObject) -> J } fn get_embedded_files(global_this: &JSGlobalObject, _: &JSObject) -> JsResult { - use crate::webcore::blob::{Blob, BlobExt as _}; + use crate::webcore::blob::{Blob, BlobExt as _, IncludeContentType}; use bun_standalone_graph::{File as GraphFile, Graph as StandaloneModuleGraph}; // SAFETY: bun_vm() returns the live thread-local VM for a Bun-owned global. let vm = global_this.bun_vm(); @@ -1986,7 +1987,7 @@ fn get_embedded_files(global_this: &JSGlobalObject, _: &JSObject) -> JsResult JsResult { let (buffer_value, options_val) = parse_compress_args(global_this, callframe)?; - gzip_or_deflate_sync(global_this, buffer_value, options_val, true) + gzip_or_deflate_sync(global_this, buffer_value, options_val, ZlibFormat::Gzip) } #[bun_jsc::host_fn] @@ -2354,7 +2355,7 @@ pub mod JSZlib { callframe: &CallFrame, ) -> JsResult { let (buffer_value, options_val) = parse_compress_args(global_this, callframe)?; - gunzip_or_inflate_sync(global_this, buffer_value, options_val, false) + gunzip_or_inflate_sync(global_this, buffer_value, options_val, ZlibFormat::Deflate) } #[bun_jsc::host_fn] @@ -2363,7 +2364,7 @@ pub mod JSZlib { callframe: &CallFrame, ) -> JsResult { let (buffer_value, options_val) = parse_compress_args(global_this, callframe)?; - gzip_or_deflate_sync(global_this, buffer_value, options_val, false) + gzip_or_deflate_sync(global_this, buffer_value, options_val, ZlibFormat::Deflate) } #[bun_jsc::host_fn] @@ -2372,15 +2373,18 @@ pub mod JSZlib { callframe: &CallFrame, ) -> JsResult { let (buffer_value, options_val) = parse_compress_args(global_this, callframe)?; - gunzip_or_inflate_sync(global_this, buffer_value, options_val, true) + gunzip_or_inflate_sync(global_this, buffer_value, options_val, ZlibFormat::Gzip) } + bun_core::bool_enum!(ZlibFormat { Deflate, Gzip }); + fn gunzip_or_inflate_sync( global_this: &JSGlobalObject, buffer_value: JSValue, options_val_: Option, - is_gzip: bool, + format: ZlibFormat, ) -> JsResult { + let is_gzip = format == ZlibFormat::Gzip; let mut opts = zlib::Options { gzip: is_gzip, window_bits: if is_gzip { 31 } else { -15 }, @@ -2480,7 +2484,7 @@ pub mod JSZlib { } }; - if reader.read_all(true).is_err() { + if reader.read_all(zlib::Chunk::Last).is_err() { let msg = reader.error_message().unwrap_or(b"Zlib returned an error"); return Err(global_this .throw_value(ZigString::init(msg).to_error_instance(global_this))); @@ -2550,8 +2554,9 @@ pub mod JSZlib { global_this: &JSGlobalObject, buffer_value: JSValue, options_val_: Option, - is_gzip: bool, + format: ZlibFormat, ) -> JsResult { + let is_gzip = format == ZlibFormat::Gzip; let mut level: Option = None; let mut library = Library::Zlib; let mut window_bits: i32 = 0; @@ -2804,12 +2809,14 @@ pub mod JSZstd { // --- Async versions --- + bun_core::bool_enum!(pub(crate) ZstdOp { Decompress, Compress }); + /// `Bun.zstdCompress` / `Bun.zstdDecompress` off the JS thread. pub(crate) struct ZstdJob { /// Created with `Flavor::Async` (JS-backed buffer protected); the /// [`bun_jsc::ThreadSafe`] releases that with the job. pub buffer: bun_jsc::ThreadSafe, - pub is_compress: bool, + pub op: ZstdOp, pub level: i32, pub output: Vec, pub error_message: Option<&'static [u8]>, @@ -2825,7 +2832,7 @@ pub mod JSZstd { ) -> Option> { let input = this.buffer.slice(); - if this.is_compress { + if this.op == ZstdOp::Compress { let max_size = bun_zstd::compress_bound(input.len()); // Surface OOM as a rejected promise instead of aborting. The // zero-fill is output-irrelevant (zstd overwrites the prefix it reports). @@ -2896,7 +2903,7 @@ pub mod JSZstd { fn create_job( global_this: &JSGlobalObject, buffer: node::StringOrBuffer, - is_compress: bool, + op: ZstdOp, level: i32, ) -> JSValue { let cx = global_this.js_thread(); @@ -2906,7 +2913,7 @@ pub mod JSZstd { &cx, ZstdJob { buffer: bun_jsc::ThreadSafe::adopt(buffer), - is_compress, + op, level, output: Vec::new(), error_message: None, @@ -2922,7 +2929,7 @@ pub mod JSZstd { callframe: &CallFrame, ) -> JsResult { let (buffer, _, level) = get_options_async(global_this, callframe)?; - Ok(create_job(global_this, buffer, true, level)) + Ok(create_job(global_this, buffer, ZstdOp::Compress, level)) } #[bun_jsc::host_fn] @@ -2931,7 +2938,7 @@ pub mod JSZstd { callframe: &CallFrame, ) -> JsResult { let (buffer, _, _) = get_options_async(global_this, callframe)?; - Ok(create_job(global_this, buffer, false, 0)) // level is ignored for decompression + Ok(create_job(global_this, buffer, ZstdOp::Decompress, 0)) // level is ignored for decompression } } diff --git a/src/runtime/api/JSBundler.rs b/src/runtime/api/JSBundler.rs index 53326b259f43..1a29d28cc1b7 100644 --- a/src/runtime/api/JSBundler.rs +++ b/src/runtime/api/JSBundler.rs @@ -8,7 +8,7 @@ use crate::webcore::blob::BlobExt; use bun_ast::Target; use bun_bundler::BundleV2; use bun_bundler::options; -use bun_collections::{StringMap, StringSet}; +use bun_collections::{DupeKeys, StringMap, StringSet}; use bun_core::MutableString; use bun_core::Output; use bun_core::{String as BunString, ZigString}; @@ -174,7 +174,7 @@ pub mod js_bundler { react_compiler: bun_ast::runtime::ReactCompilerMode::Disabled, react_compiler_parse_test_pragmas: false, react_compiler_output_mode: None, - define: StringMap::init(false), + define: StringMap::init(DupeKeys::No), loaders: None, dir: OwnedString::default(), outdir: OwnedString::default(), @@ -449,7 +449,7 @@ pub mod js_bundler { // is rejected by rustc (E0509). Construct default then mutate instead. let mut this = Config::default(); // `define` defaults to `StringMap::init(false)`; only the flag differs. - this.define.dupe_keys = true; + this.define.dupe_keys = DupeKeys::Yes; // errdefer this.deinit(allocator) — handled by `impl Drop for Config` on `?` paths. // errdefer if (plugins.*) |plugin| plugin.deinit() — scopeguard below. let mut plugins = scopeguard::guard(plugins, |p| { @@ -539,8 +539,8 @@ pub mod js_bundler { function, config, onstart_promise_array, - is_last, - false, + IsLast::from_bool(is_last), + IsBake::No, )? }; @@ -1671,6 +1671,9 @@ pub mod js_bundler { ) -> JSValue; } + bun_core::bool_enum!(pub IsLast); + bun_core::bool_enum!(pub IsBake); + /// JSC-aware methods on the C++ `JSBundlerPlugin` opaque. The opaque type /// itself is owned by `bun_bundler` (lower tier, no JSC dep), so these are /// added as an extension trait rather than an inherent `impl`. @@ -1696,8 +1699,8 @@ pub mod js_bundler { object: JSValue, config: JSValue, onstart_promises_array: JSValue, - is_last: bool, - is_bake: bool, + is_last: IsLast, + is_bake: IsBake, ) -> JsResult; fn set_config(&mut self, config: *mut c_void); /// Thin FFI forward; the host-call wrapper / exception check is the @@ -1774,8 +1777,8 @@ pub mod js_bundler { object: JSValue, config: JSValue, onstart_promises_array: JSValue, - is_last: bool, - is_bake: bool, + is_last: IsLast, + is_bake: IsBake, ) -> JsResult { jsc::mark_binding(); let _tracer = bun_core::perf::trace("JSBundler.addPlugin"); @@ -1789,8 +1792,8 @@ pub mod js_bundler { object, config, onstart_promises_array, - JSValue::from(is_last), - JSValue::from(is_bake), + JSValue::from(is_last == IsLast::Yes), + JSValue::from(is_bake == IsBake::Yes), ) }) } diff --git a/src/runtime/api/JSTranspiler.rs b/src/runtime/api/JSTranspiler.rs index 67d0ef9e5155..e89f6edc5b45 100644 --- a/src/runtime/api/JSTranspiler.rs +++ b/src/runtime/api/JSTranspiler.rs @@ -11,7 +11,7 @@ use bun_ast::Expr; use bun_ast::Loader; use bun_ast::{ImportRecord, ImportRecordFlags}; use bun_bundler::options::{self, PackagesOption, SourceMapOption}; -use bun_bundler::transpiler::{MacroJSCtx, ParseOptions, ParseResult}; +use bun_bundler::transpiler::{AutoJsx, MacroJSCtx, ParseOptions, ParseResult}; use bun_bundler::{self as Transpiler}; use bun_js_parser::lexer as JSLexer; use bun_js_parser::parser::Runtime; @@ -1037,7 +1037,7 @@ impl JSTranspiler { transpiler.set_log(&raw mut config.log); transpiler.options.no_macros = config.no_macros; - transpiler.configure_linker_with_auto_jsx(false); + transpiler.configure_linker_with_auto_jsx(AutoJsx::No); transpiler.options.env.behavior = options::EnvBehavior::disable; if let Err(err) = transpiler.configure_defines() { let log = &mut config.log; @@ -1359,7 +1359,7 @@ impl JSTranspiler { let named_imports_value = named_imports_to_js( global, parse_result.ast.import_records.as_slice(), - self.config.get().trim_unused_imports.unwrap_or(false), + TrimUnusedImports::from_bool(self.config.get().trim_unused_imports.unwrap_or(false)), )?; let named_exports_value = named_exports_to_js(global, &mut parse_result.ast.named_exports)?; @@ -1608,11 +1608,14 @@ fn named_exports_to_js( bun_jsc::bun_string_jsc::to_js_array(global, &names) } +bun_core::bool_enum!(TrimUnusedImports); + fn named_imports_to_js( global: &JSGlobalObject, import_records: &[ImportRecord], - trim_unused_imports: bool, + trim_unused_imports: TrimUnusedImports, ) -> JsResult { + let trim_unused_imports = trim_unused_imports == TrimUnusedImports::Yes; let path_label = ZigString::static_(b"path"); let kind_label = ZigString::static_(b"kind"); @@ -1780,7 +1783,9 @@ impl JSTranspiler { named_imports_to_js( global, self.scan_pass_result.get().import_records.as_slice(), - self.config.get().trim_unused_imports.unwrap_or(false), + TrimUnusedImports::from_bool( + self.config.get().trim_unused_imports.unwrap_or(false), + ), ) })(); self.scan_pass_result.with_mut(|s| s.reset()); diff --git a/src/runtime/api/TOMLObject.rs b/src/runtime/api/TOMLObject.rs index 313b0efca6d8..4b2c1e8a67eb 100644 --- a/src/runtime/api/TOMLObject.rs +++ b/src/runtime/api/TOMLObject.rs @@ -4,7 +4,7 @@ use bun_core::{OwnedString, String as BunString}; use bun_jsc::{ self as jsc, CallFrame, JSGlobalObject, JSValue, JsError, JsResult, TemporalType, wtf, }; -use bun_parsers::toml::TOML; +use bun_parsers::toml::{RedactLogs, TOML}; pub(crate) fn create(global: &JSGlobalObject) -> JSValue { bun_jsc::create_host_function_object( @@ -25,7 +25,7 @@ pub(crate) fn parse(global: &JSGlobalObject, frame: &CallFrame) -> JsResult v, Err(bun_parsers::Error::StackOverflow) => { return Err(global.throw_stack_overflow()); @@ -148,10 +148,16 @@ struct Stringifier { wrote: bool, } +bun_core::bool_enum!(OwnHeader); +bun_core::bool_enum!(HeaderKind { + Table, + ArrayOfTables +}); + impl Stringifier { fn stringify_root(&mut self, global: &JSGlobalObject, root: JSValue) -> StringifyResult<()> { self.mark_visiting(global, root)?; - self.stringify_table_body(global, root, false)?; + self.stringify_table_body(global, root, OwnHeader::No)?; self.visiting.remove(&root); Ok(()) } @@ -213,12 +219,12 @@ impl Stringifier { &mut self, global: &JSGlobalObject, table: JSValue, - own_header: bool, + own_header: OwnHeader, ) -> StringifyResult<()> { if !self.stack_check.is_safe_to_recurse() { return Err(StringifyError::StackOverflow); } - let mut header_pending = own_header; + let mut header_pending = own_header == OwnHeader::Yes; let iter_options = jsc::JSPropertyIteratorOptions { skip_empty_name: false, @@ -241,7 +247,7 @@ impl Stringifier { }; if header_pending { header_pending = false; - self.append_header(false); + self.append_header(HeaderKind::Table); } self.append_key_segment(&prop_name); self.builder.append_latin1(b" = "); @@ -262,7 +268,7 @@ impl Stringifier { header_pending = false; self.mark_visiting(global, value)?; self.path.push(prop_name); - self.stringify_table_body(global, value, true)?; + self.stringify_table_body(global, value, OwnHeader::Yes)?; self.path.pop(); self.visiting.remove(&value); } @@ -283,8 +289,8 @@ impl Stringifier { return Err(self.err_changed(global)); } self.mark_visiting(global, item)?; - self.append_header(true); - self.stringify_table_body(global, item, false)?; + self.append_header(HeaderKind::ArrayOfTables); + self.stringify_table_body(global, item, OwnHeader::No)?; self.visiting.remove(&item); } self.path.pop(); @@ -295,7 +301,7 @@ impl Stringifier { // An empty table is materialized only by its header. if header_pending { - self.append_header(false); + self.append_header(HeaderKind::Table); } Ok(()) @@ -406,7 +412,8 @@ impl Stringifier { /// `[a.b.c]` or `[[a.b.c]]` from `self.path`, preceded by a blank line /// when the document already has content. - fn append_header(&mut self, array_of_tables: bool) { + fn append_header(&mut self, kind: HeaderKind) { + let array_of_tables = kind == HeaderKind::ArrayOfTables; if self.wrote { self.builder.append_lchar(b'\n'); } diff --git a/src/runtime/api/XMLObject.rs b/src/runtime/api/XMLObject.rs index f3f9e562f6aa..cf6dbef8aab6 100644 --- a/src/runtime/api/XMLObject.rs +++ b/src/runtime/api/XMLObject.rs @@ -267,6 +267,9 @@ fn skipped(value: JSValue) -> bool { value.is_undefined() || value.is_symbol() || value.is_function() } +bun_core::bool_enum!(Separate); +bun_core::bool_enum!(EscapeContext { Text, Attribute }); + impl Stringifier { fn mark_visiting(&mut self, global: &JSGlobalObject, value: JSValue) -> StringifyResult<()> { let was_present = self @@ -585,7 +588,7 @@ impl Stringifier { )) .into()); }; - self.stringify_compact_element(global, &name, value, false) + self.stringify_compact_element(global, &name, value, Separate::No) } /// Whether a compact property value produces any output: everything but @@ -616,7 +619,7 @@ impl Stringifier { global: &JSGlobalObject, name: &BunString, value: JSValue, - separate: bool, + separate: Separate, ) -> StringifyResult<()> { if !self.stack_check.is_safe_to_recurse() { return Err(StringifyError::StackOverflow); @@ -641,7 +644,7 @@ impl Stringifier { global: &JSGlobalObject, name: &BunString, value: JSValue, - separate: bool, + separate: Separate, ) -> StringifyResult<()> { let mut iter = value.array_iterator(global)?; let mut first = true; @@ -658,7 +661,7 @@ impl Stringifier { )) .into()); } - if !first && separate { + if !first && separate == Separate::Yes { self.newline(); } first = false; @@ -762,7 +765,7 @@ impl Stringifier { if pretty { self.newline(); } - self.stringify_compact_element(global, &key, child, pretty)?; + self.stringify_compact_element(global, &key, child, Separate::from_bool(pretty))?; } if pretty { self.indent -= 1; @@ -869,7 +872,7 @@ impl Stringifier { self.builder.append_lchar(b' '); self.builder.append_string(*name); self.builder.append_latin1(b"=\""); - self.append_escaped(global, value, true)?; + self.append_escaped(global, value, EscapeContext::Attribute)?; self.builder.append_lchar(b'"'); Ok(()) } @@ -877,15 +880,16 @@ impl Stringifier { /// Character data with `& < >` and CR escaped (`>` for the `]]>` rule, /// CR because a literal one would be normalized to LF when parsed). fn append_text(&mut self, global: &JSGlobalObject, text: &BunString) -> StringifyResult<()> { - self.append_escaped(global, text, false) + self.append_escaped(global, text, EscapeContext::Text) } fn append_escaped( &mut self, global: &JSGlobalObject, text: &BunString, - attribute: bool, + context: EscapeContext, ) -> StringifyResult<()> { + let attribute = context == EscapeContext::Attribute; let len = text.length(); let mut i = 0; while i < len { diff --git a/src/runtime/api/bun/SSLContextCache.rs b/src/runtime/api/bun/SSLContextCache.rs index f8db90a310f1..9467db0dc915 100644 --- a/src/runtime/api/bun/SSLContextCache.rs +++ b/src/runtime/api/bun/SSLContextCache.rs @@ -54,7 +54,7 @@ impl ArrayHashContext for DigestContext { } #[inline] fn eql(&self, a: &Digest, b: &Digest, _b_index: usize) -> bool { - bun_core::strings::eql_long(a, b, false) + bun_core::strings::eql_long(a, b, bun_core::strings::CheckLen::No) } } diff --git a/src/runtime/api/bun/Terminal.rs b/src/runtime/api/bun/Terminal.rs index e181da0fbb13..6510f4bf90af 100644 --- a/src/runtime/api/bun/Terminal.rs +++ b/src/runtime/api/bun/Terminal.rs @@ -493,7 +493,7 @@ impl Terminal { // Start writer with the write fd - adds a ref match terminal .writer - .with_mut(|w| w.start(pty_result.write_fd, true)) + .with_mut(|w| w.start(pty_result.write_fd, bun_io::IsPollable::Yes)) { sys::Result::Ok(()) => terminal.ref_(), sys::Result::Err(_) => { @@ -520,7 +520,7 @@ impl Terminal { // Start reader with the read fd - adds a ref match terminal .reader - .with_mut(|r| r.start(pty_result.read_fd, true)) + .with_mut(|r| r.start(pty_result.read_fd, bun_io::IsPollable::Yes)) { sys::Result::Err(_) => { // Reader never started: closeInternal skips reader.close() but @@ -1081,9 +1081,9 @@ fn create_pty_posix(cols: u16, rows: u16) -> Result { }; // Set non-blocking on master side fds (for async I/O in the event loop) - let _ = sys::update_nonblocking(master_fd_desc, true); - let _ = sys::update_nonblocking(read_fd, true); - let _ = sys::update_nonblocking(write_fd, true); + let _ = sys::update_nonblocking(master_fd_desc, sys::IoMode::NonBlocking); + let _ = sys::update_nonblocking(read_fd, sys::IoMode::NonBlocking); + let _ = sys::update_nonblocking(write_fd, sys::IoMode::NonBlocking); // Note: slave_fd stays blocking - child processes expect blocking I/O // Set close-on-exec on master side fds only @@ -1681,8 +1681,8 @@ fn get_termios(fd: Fd) -> Option { /// Set terminal attributes using tcsetattr (TCSANOW = immediate) #[cfg(unix)] -fn set_termios(fd: Fd, termios_p: &Termios) -> bool { - sys::posix::tcsetattr(fd.native(), sys::posix::TCSA::Now, termios_p).is_ok() +fn set_termios(fd: Fd, termios_p: &Termios) -> Result<(), sys::Error> { + sys::posix::tcsetattr(fd.native(), sys::posix::TCSA::Now, termios_p) } impl Terminal { diff --git a/src/runtime/api/bun/h2/connection.rs b/src/runtime/api/bun/h2/connection.rs index 29473d5d5e56..f0cc2be555d1 100644 --- a/src/runtime/api/bun/h2/connection.rs +++ b/src/runtime/api/bun/h2/connection.rs @@ -10,8 +10,10 @@ use super::flow_control::{RecvWindow, SendWindow}; use super::hpack; use super::settings::{self, Settings}; use super::stream::{self, State}; -use super::wire::{self, ErrorCode, FrameHeader, FrameType, SettingId}; +use super::wire::{self, Ack, ErrorCode, FrameHeader, FrameType, Padded, SettingId}; use bun_collections::HashMap; +use bun_http::h2::EndStream; +use bun_http::lshpack::NeverIndex; use std::num::NonZeroU32; /// Pseudo-header presence bits shared by the per-field decode loop and the RFC 9113 §8.3.1 @@ -146,6 +148,11 @@ pub struct Feed { pub fatal: bool, } +bun_core::bool_enum!( + /// Whether handling a frame tore the connection down (GOAWAY sent / session terminated). + Fatal +); + /// nghttp2's NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM: outbound PING/SETTINGS ACKs that may pile up /// behind a non-reading peer before the session is treated as flooded (NGHTTP2_ERR_FLOODED). const MAX_OUTBOUND_ACK_QUEUE: u32 = 1000; @@ -162,7 +169,7 @@ pub trait Sink { fn on_error(&self, lib_error_code: i32, last_stream_id: u32, debug: &[u8]); fn on_local_settings(&self, settings: &Settings); fn on_remote_settings(&self, settings: &Settings); - fn on_ping(&self, payload: &[u8], is_ack: bool); + fn on_ping(&self, payload: &[u8], is_ack: Ack); /// `code` is the raw u32 from the wire so unknown error codes survive to JS (node parity). fn on_go_away(&self, code: u32, last_stream_id: u32, debug: &[u8]); /// After a WINDOW_UPDATE has been applied (for resuming sends). @@ -188,9 +195,9 @@ pub trait Sink { /// A SETTINGS entry with an id outside the standard registry (node's remoteCustomSettings). fn on_remote_custom_setting(&self, _id: u16, _value: u32) {} /// One decoded header field. `name`/`value` alias a shared buffer — copy before returning. - fn on_header(&self, _stream_id: u32, _name: &[u8], _value: &[u8], _never_index: bool) {} + fn on_header(&self, _stream_id: u32, _name: &[u8], _value: &[u8], _never_index: NeverIndex) {} /// The header block for `stream_id` is complete. `end_stream` = the HEADERS carried END_STREAM. - fn on_headers_complete(&self, _stream_id: u32, _end_stream: bool, _flags: u8) {} + fn on_headers_complete(&self, _stream_id: u32, _end_stream: EndStream, _flags: u8) {} /// A DATA payload (padding already stripped). fn on_data(&self, _stream_id: u32, _data: &[u8]) {} /// The stream half/fully closed; `state` is the `stream::State` integer. @@ -239,8 +246,13 @@ pub trait Sink { } } +bun_core::bool_enum!( + /// Which end of the HTTP/2 connection this engine is. + pub Side { Client, Server } +); + pub struct Connection { - pub is_server: bool, + pub side: Side, /// Wire frames fully accepted from the peer (perf_hooks http2 session stats). pub frames_received: u64, /// Wire frames this engine itself has written (the embedder counts its own). @@ -315,9 +327,9 @@ pub struct Connection { } impl Connection { - pub fn new(is_server: bool, local: Settings) -> Self { + pub fn new(side: Side, local: Settings) -> Self { Connection { - is_server, + side, local_settings: local, remote_settings: Settings::default(), local_settings_acked: false, @@ -348,6 +360,11 @@ impl Connection { } } + #[inline] + pub fn is_server(&self) -> bool { + self.side == Side::Server + } + // ---- Outbound ------------------------------------------------------- fn write_frame( @@ -448,7 +465,7 @@ impl Connection { } // §3.4: server validates the 24-octet client preface before any frame. - if self.is_server && self.preface_received < wire::CONNECTION_PREFACE.len() { + if self.is_server() && self.preface_received < wire::CONNECTION_PREFACE.len() { let need = wire::CONNECTION_PREFACE.len() - self.preface_received; let avail = need.min(bytes.len()); let expect = @@ -531,7 +548,12 @@ impl Connection { let avail_payload = remaining.len() - wire::FRAME_HEADER_SIZE; // With PADDED set the first payload octet is Pad Length; wait for it. if !padded || avail_payload >= 1 { - match self.begin_streamed_data(sink, &hdr, remaining, padded) { + match self.begin_streamed_data( + sink, + &hdr, + remaining, + Padded::from_bool(padded), + ) { StreamedDataStart::Fatal => { return Feed { consumed: offset, @@ -547,7 +569,7 @@ impl Connection { break; } let payload = &remaining[wire::FRAME_HEADER_SIZE..total]; - if self.dispatch(sink, &hdr, payload) { + if self.dispatch(sink, &hdr, payload) == Fatal::Yes { return Feed { consumed: offset + total, fatal: true, @@ -609,7 +631,7 @@ impl Connection { } /// Dispatch one fully-buffered frame. Returns true if the connection is now fatally closing. - fn dispatch(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> bool { + fn dispatch(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> Fatal { // GOAWAY is excluded: it terminates the session, so node's statistics — which are // read off a session that stopped processing at that frame — never include it. if !matches!(hdr.typ(), Some(FrameType::GoAway)) { @@ -628,7 +650,7 @@ impl Connection { ErrorCode::ProtocolError, b"expected CONTINUATION frame", ); - return true; + return Fatal::Yes; } } @@ -637,11 +659,11 @@ impl Connection { wire::HeaderValidation::Ok => {} wire::HeaderValidation::ConnectionError(code) => { self.send_go_away(sink, code, b"frame validation failed"); - return true; + return Fatal::Yes; } wire::HeaderValidation::StreamError { id, code } => { self.send_rst_stream(sink, id, code); - return false; + return Fatal::No; } } @@ -658,13 +680,13 @@ impl Connection { Some(FrameType::AltSvc) => self.handle_altsvc(sink, hdr, payload), Some(FrameType::Origin) => self.handle_origin(sink, hdr, payload), // PRIORITY has no scheduling effect here; structurally validated above, otherwise ignored. - Some(FrameType::Priority) => false, + Some(FrameType::Priority) => Fatal::No, // §4.1: unknown frame types are silently discarded. - _ => false, + _ => Fatal::No, } } - fn handle_settings(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> bool { + fn handle_settings(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> Fatal { if wire::flags::has(hdr.flags, wire::flags::ACK) { if hdr.length != 0 { self.send_go_away( @@ -672,7 +694,7 @@ impl Connection { ErrorCode::FrameSizeError, b"SETTINGS ACK with payload", ); - return true; + return Fatal::Yes; } // An ACK with no outstanding SETTINGS submission is unsolicited; nghttp2 silently // ignores it (it never reaches node's HandleSettingsFrame defensive branch). The @@ -680,7 +702,7 @@ impl Connection { // pending_settings_window_submissions is drained into this queue between batches), // so the first ACK falls back to local_settings rather than being dropped. if self.local_settings_acked && self.pending_local_settings_acks.is_empty() { - return false; + return Fatal::No; } self.local_settings_acked = true; // §6.5.3: this ACK acknowledges the oldest outstanding SETTINGS, whose values may @@ -696,7 +718,7 @@ impl Connection { // limit it carried. self.enforced_max_header_list_size = acked.settings.max_header_list_size; sink.on_local_settings(&acked.settings); - return false; + return Fatal::No; } // node's maxSettings (nghttp2 max_settings): refuse SETTINGS frames carrying more entries // than the session allows before applying or surfacing any of them. @@ -706,12 +728,12 @@ impl Connection { ErrorCode::EnhanceYourCalm, b"SETTINGS: too many settings entries", ); - return true; + return Fatal::Yes; } // §6.5.2: validate value ranges before applying. if let Some(code) = settings::validate_payload(payload) { self.send_go_away(sink, code, b"SETTINGS value out of range"); - return true; + return Fatal::Yes; } let old_table = self.remote_settings.header_table_size; let old_initial_window = self.remote_settings.initial_window_size; @@ -736,7 +758,7 @@ impl Connection { ErrorCode::ProtocolError, b"SETTINGS: server attempted to disable enableConnectProtocol", ); - return true; + return Fatal::Yes; } self.remote_settings.apply(sid, value); } else { @@ -762,10 +784,10 @@ impl Connection { let snapshot = self.remote_settings; sink.on_remote_settings(&snapshot); self.send_settings_ack(sink); - if self.note_outbound_ack(sink) { - return true; + if self.note_outbound_ack(sink) == Fatal::Yes { + return Fatal::Yes; } - false + Fatal::No } /// The embedder confirms its outbound buffer is fully drained to the @@ -781,10 +803,10 @@ impl Connection { /// note_outbound_drained() when the transport actually drains, so detection /// is independent of recv() chunk size. Returns true when the session was /// torn down. - fn note_outbound_ack(&mut self, sink: &impl Sink) -> bool { + fn note_outbound_ack(&mut self, sink: &impl Sink) -> Fatal { self.obq_ack_pending = self.obq_ack_pending.saturating_add(1); if self.obq_ack_pending < MAX_OUTBOUND_ACK_QUEUE { - return false; + return Fatal::No; } self.local_connection_error( sink, @@ -792,30 +814,30 @@ impl Connection { wire::lib_error::FLOODED, b"too many outbound control frames queued", ); - true + Fatal::Yes } - fn handle_ping(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> bool { + fn handle_ping(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> Fatal { if wire::flags::has(hdr.flags, wire::flags::ACK) { - sink.on_ping(payload, true); - return false; + sink.on_ping(payload, Ack::Yes); + return Fatal::No; } // copy the 8-byte payload before echoing (sink.write may reuse buffers). let mut echo = [0u8; 8]; echo.copy_from_slice(&payload[..8]); self.send_ping_ack(sink, &echo); - sink.on_ping(&echo, false); - if self.note_outbound_ack(sink) { - return true; + sink.on_ping(&echo, Ack::No); + if self.note_outbound_ack(sink) == Fatal::Yes { + return Fatal::Yes; } - false + Fatal::No } - fn handle_go_away(&mut self, sink: &impl Sink, payload: &[u8]) -> bool { + fn handle_go_away(&mut self, sink: &impl Sink, payload: &[u8]) -> Fatal { // §6.8: GOAWAY carries at least an 8-octet last-stream-id + error-code prefix. if payload.len() < 8 { self.send_go_away(sink, ErrorCode::FrameSizeError, b"GOAWAY too short"); - return true; + return Fatal::Yes; } let last_stream_id = u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]) & 0x7fff_ffff; @@ -823,7 +845,7 @@ impl Connection { // nghttp2 (nghttp2_session_on_goaway_received): the Last-Stream-ID must refer to a stream // the *receiver* initiated (or 0) — for a server that means an even id, for a client an // odd id. Anything else is a connection PROTOCOL_ERROR. - let initiated_locally = if self.is_server { + let initiated_locally = if self.is_server() { last_stream_id.is_multiple_of(2) } else { !last_stream_id.is_multiple_of(2) @@ -834,11 +856,11 @@ impl Connection { ErrorCode::ProtocolError, b"GOAWAY: invalid last_stream_id", ); - return true; + return Fatal::Yes; } self.going_away = true; sink.on_go_away(code_raw, last_stream_id, &payload[8..]); - false + Fatal::No } fn handle_window_update( @@ -846,7 +868,7 @@ impl Connection { sink: &impl Sink, hdr: &FrameHeader, payload: &[u8], - ) -> bool { + ) -> Fatal { let increment = u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]) & 0x7fff_ffff; // §6.9.1: a 0 increment is an error (connection error on stream 0). @@ -857,7 +879,7 @@ impl Connection { ErrorCode::ProtocolError, b"WINDOW_UPDATE with 0 increment", ); - return true; + return Fatal::Yes; } // Locally-initiated stream RST: close the engine entry and tell the embedder, the // same as handle_data's Rst path, so the JS stream learns it was reset and the @@ -867,7 +889,7 @@ impl Connection { s.state = State::Closed; } sink.on_stream_reset(hdr.stream_id, ErrorCode::ProtocolError.as_u32()); - return false; + return Fatal::No; } if hdr.stream_id == 0 { // 6.9.1: the connection window must not exceed 2^31-1. @@ -877,7 +899,7 @@ impl Connection { ErrorCode::FlowControlError, b"connection flow-control window overflow", ); - return true; + return Fatal::Yes; } } else if let Some(s) = self.streams.get_mut(&hdr.stream_id) { // 6.9.1: a per-stream overflow is a stream error, not a connection error. @@ -885,31 +907,31 @@ impl Connection { s.state = State::Closed; self.send_rst_stream(sink, hdr.stream_id, ErrorCode::FlowControlError); sink.on_stream_reset(hdr.stream_id, ErrorCode::FlowControlError.as_u32()); - return false; + return Fatal::No; } } sink.on_window_update(hdr.stream_id, increment); - false + Fatal::No } // ---- Stream-level inbound ------------------------------------------ /// RFC 9113 §6.2 HEADERS: strip padding/priority, then begin (or complete) the header block. - fn handle_headers(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> bool { + fn handle_headers(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> Fatal { let mut off = 0usize; let mut end = payload.len(); if wire::flags::has(hdr.flags, wire::flags::PADDED) { if payload.is_empty() { self.send_go_away(sink, ErrorCode::FrameSizeError, b"HEADERS padded but empty"); - return true; + return Fatal::Yes; } let pad = payload[0] as usize; off = 1; // §6.1: padding that spans the whole frame is a PROTOCOL_ERROR. if off + pad > end { self.send_go_away(sink, ErrorCode::ProtocolError, b"HEADERS padding too large"); - return true; + return Fatal::Yes; } end -= pad; } @@ -921,7 +943,7 @@ impl Connection { ErrorCode::FrameSizeError, b"HEADERS priority truncated", ); - return true; + return Fatal::Yes; } off += 5; } @@ -938,15 +960,15 @@ impl Connection { // would open an even-id stream is a connection PROTOCOL_ERROR. (Monotonicity is not // checked here: a client legitimately receives HEADERS on even promised ids that are // numerically below its own latest odd id.) - if is_new && self.is_server && hdr.stream_id.is_multiple_of(2) { + if is_new && self.is_server() && hdr.stream_id.is_multiple_of(2) { self.send_go_away( sink, ErrorCode::ProtocolError, b"invalid stream id for HEADERS", ); - return true; + return Fatal::Yes; } - let refused = is_new && self.is_server && !sink.can_open_stream(); + let refused = is_new && self.is_server() && !sink.can_open_stream(); let mut disposition = if refused { BlockDisposition::Refused } else { @@ -975,7 +997,7 @@ impl Connection { ErrorCode::ProtocolError, b"HEADERS in invalid stream state", ); - return true; + return Fatal::Yes; } Err(stream::TransitionError::StreamClosed) => { // nghttp2 (session_on_*_headers_received): HEADERS for a stream whose remote @@ -993,7 +1015,7 @@ impl Connection { wire::lib_error::STREAM_CLOSED, b"HEADERS: stream closed", ); - return true; + return Fatal::Yes; } disposition = BlockDisposition::StreamClosed; } @@ -1021,7 +1043,7 @@ impl Connection { // Only a server receives request blocks via HEADERS; on a client every response block // looks "new" (the engine only tracks inbound-created streams), so without this gate it // would be misclassified as a request. PUSH_PROMISE sets the flag itself. - is_request: self.is_server && is_new, + is_request: self.is_server() && is_new, disposition, }; self.finish_or_park_header_block(sink, hdr, meta) @@ -1035,19 +1057,24 @@ impl Connection { sink: &impl Sink, hdr: &FrameHeader, meta: HeaderBlockMeta, - ) -> bool { + ) -> Fatal { if !wire::flags::has(hdr.flags, wire::flags::END_HEADERS) { self.header_block_in_flight = Some(HeaderBlockInFlight { continuation_stream: hdr.stream_id, meta, }); - return false; + return Fatal::No; } self.finish_header_block(sink, &meta) } /// RFC 9113 §6.10 CONTINUATION: append the fragment; complete the block on END_HEADERS. - fn handle_continuation(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> bool { + fn handle_continuation( + &mut self, + sink: &impl Sink, + hdr: &FrameHeader, + payload: &[u8], + ) -> Fatal { let Some(inflight) = self.header_block_in_flight.take() else { // §6.10: a CONTINUATION with no header block in progress is a connection PROTOCOL_ERROR. self.send_go_away( @@ -1055,7 +1082,7 @@ impl Connection { ErrorCode::ProtocolError, b"unexpected CONTINUATION frame", ); - return true; + return Fatal::Yes; }; // dispatch() already enforced that we are assembling this exact stream. // Cap the reassembled block at the header-list limit (floored so tiny custom settings @@ -1070,12 +1097,12 @@ impl Connection { // nghttp2's NGHTTP2_MAX_HEADERSLEN (65536) overflow returns NGHTTP2_ERR_HEADER_COMP, // which node surfaces as a session COMPRESSION_ERROR. self.send_go_away(sink, ErrorCode::CompressionError, b"header block too large"); - return true; + return Fatal::Yes; } self.header_block.extend_from_slice(payload); if !wire::flags::has(hdr.flags, wire::flags::END_HEADERS) { self.header_block_in_flight = Some(inflight); - return false; + return Fatal::No; } self.finish_header_block(sink, &inflight.meta) } @@ -1083,7 +1110,7 @@ impl Connection { /// Decode the assembled header block (HPACK) and dispatch each field, then finalize. The whole /// block is always decoded so the connection-scoped HPACK table stays in sync (§4.3). Works for /// both a normal HEADERS block and a PUSH_PROMISE block (`meta.push_parent` set). - fn finish_header_block(&mut self, sink: &impl Sink, meta: &HeaderBlockMeta) -> bool { + fn finish_header_block(&mut self, sink: &impl Sink, meta: &HeaderBlockMeta) -> Fatal { let HeaderBlockMeta { target, push_parent, @@ -1170,14 +1197,14 @@ impl Connection { // a malformed block. (The client direction also constrains pseudo // headers, but inbound PUSH_PROMISE blocks legitimately carry request // pseudo-headers, so that check needs the push context first.) - let wrong_direction = self.is_server && rest == b"status"; + let wrong_direction = self.is_server() && rest == b"status"; // RFC 8441 §4: :protocol is only valid when SETTINGS_ENABLE_CONNECT_PROTOCOL // has been enabled by this endpoint. nghttp2 (and so node) checks the // submitted local value here, not the ACKed one — so a request that arrives // after a server has set enableConnectProtocol back to false is rejected at // the protocol level and never reaches the JS 'stream' handler // (test-http2-connect-method-extended-cant-turn-off). - let protocol_disabled = self.is_server + let protocol_disabled = self.is_server() && rest == b"protocol" && self.local_settings.enable_connect_protocol == 0; // nghttp2 (check_pseudo_header) treats an empty pseudo-header value as @@ -1225,7 +1252,12 @@ impl Connection { if malformed { continue; } - sink.on_header(target, h.name, h.value, h.never_index); + sink.on_header( + target, + h.name, + h.value, + NeverIndex::from_bool(h.never_index), + ); } Err(_) => { // §4.3: a header-block decoding error is a connection COMPRESSION_ERROR. @@ -1239,7 +1271,7 @@ impl Connection { self.header_block = block; self.header_block.clear(); if fatal { - return true; + return Fatal::Yes; } match disposition { BlockDisposition::Refused => { @@ -1248,7 +1280,7 @@ impl Connection { // is what node's own test-http2-max-session-memory asserts. self.send_rst_stream(sink, target, ErrorCode::EnhanceYourCalm); sink.on_stream_rejected(target); - return false; + return Fatal::No; } BlockDisposition::StreamClosed => { // §5.1: HEADERS on a closed/half-closed-remote stream is a stream error of type @@ -1258,7 +1290,7 @@ impl Connection { s.state = State::Closed; } sink.on_stream_reset(target, ErrorCode::StreamClosed.as_u32()); - return false; + return Fatal::No; } BlockDisposition::Deliver => {} } @@ -1278,7 +1310,7 @@ impl Connection { || (extended_connect && (!saw_connect || (seen_pseudo & AUTHORITY) == 0)) }; } - if push_parent.is_none() && self.is_server && !malformed && !rejected { + if push_parent.is_none() && self.is_server() && !malformed && !rejected { if let Some(s) = self.streams.get_mut(&target) { if !saw_connect && s.content_length.is_none() { s.content_length = content_length; @@ -1300,7 +1332,7 @@ impl Connection { if count > self.max_invalid_frames { self.terminated = true; sink.on_too_many_invalid_frames(); - return true; + return Fatal::Yes; } // RFC 9113 §8.2: a malformed header block gets a stream error of type PROTOCOL_ERROR and // is not delivered to the application. @@ -1310,7 +1342,7 @@ impl Connection { } sink.on_stream_reset(target, ErrorCode::ProtocolError.as_u32()); sink.on_stream_rejected(target); - return false; + return Fatal::No; } if rejected { // Refuse the oversized header list with a stream error (matches the legacy engine and @@ -1321,7 +1353,7 @@ impl Connection { } sink.on_stream_reset(target, ErrorCode::EnhanceYourCalm.as_u32()); sink.on_stream_rejected(target); - return false; + return Fatal::No; } if push_parent.is_none() && !informational @@ -1329,14 +1361,14 @@ impl Connection { { s.recv_final_headers = true; } - sink.on_headers_complete(target, end_stream, flags); + sink.on_headers_complete(target, EndStream::from_bool(end_stream), flags); if end_stream { let state = self.streams.get(&target).map(|s| s.state as u8); if let Some(state) = state { sink.on_stream_end(target, state); } } - false + Fatal::No } /// RFC 9113 §6.1 DATA: strip padding, enforce flow control, deliver, replenish windows. @@ -1349,12 +1381,12 @@ impl Connection { sink: &impl Sink, hdr: &FrameHeader, remaining: &[u8], - padded: bool, + padded: Padded, ) -> StreamedDataStart { let payload_avail = &remaining[wire::FRAME_HEADER_SIZE..]; let mut pad = 0usize; let mut off = 0usize; - if padded { + if padded == Padded::Yes { pad = payload_avail[0] as usize; off = 1; if 1 + pad > hdr.length as usize { @@ -1464,19 +1496,19 @@ impl Connection { } } - fn handle_data(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> bool { + fn handle_data(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> Fatal { let mut off = 0usize; let mut end = payload.len(); if wire::flags::has(hdr.flags, wire::flags::PADDED) { if payload.is_empty() { self.send_go_away(sink, ErrorCode::FrameSizeError, b"DATA padded but empty"); - return true; + return Fatal::Yes; } let pad = payload[0] as usize; off = 1; if off + pad > end { self.send_go_away(sink, ErrorCode::ProtocolError, b"DATA padding too large"); - return true; + return Fatal::Yes; } end -= pad; } @@ -1490,7 +1522,7 @@ impl Connection { ErrorCode::FlowControlError, b"connection flow-control window exceeded", ); - return true; + return Fatal::Yes; } // An empty DATA frame that does not end the stream carries no information and is only @@ -1502,7 +1534,7 @@ impl Connection { if count > self.max_invalid_frames { self.terminated = true; sink.on_too_many_invalid_frames(); - return true; + return Fatal::Yes; } } @@ -1550,7 +1582,7 @@ impl Connection { } // Surface the stream error (e.g. a peer protocol violation) to the embedder. sink.on_stream_reset(hdr.stream_id, code.as_u32()); - return false; + return Fatal::No; } DataDecision::FlowControlViolation => { // nghttp2 (nghttp2_session_update_recv_stream_window_size): a stream flow-control @@ -1561,7 +1593,7 @@ impl Connection { ErrorCode::FlowControlError, b"stream flow-control window exceeded", ); - return true; + return Fatal::Yes; } DataDecision::Deliver(inc) => inc, }; @@ -1578,7 +1610,7 @@ impl Connection { if end_stream { if self.enforce_content_length(sink, hdr.stream_id) { - return false; + return Fatal::No; } let state = match self.streams.get_mut(&hdr.stream_id) { Some(s) => { @@ -1593,14 +1625,14 @@ impl Connection { sink.on_stream_end(hdr.stream_id, state); } } - false + Fatal::No } /// RFC 9113 §8.1.1: once END_STREAM arrives, a request whose received DATA total contradicts /// its declared `content-length` is malformed. Resets the stream with PROTOCOL_ERROR instead /// of signalling end-of-stream and returns true if it did so. fn enforce_content_length(&mut self, sink: &impl Sink, stream_id: u32) -> bool { - if !self.is_server { + if !self.is_server() { return false; } let mismatch = self.streams.get(&stream_id).is_some_and(|s| { @@ -1619,7 +1651,7 @@ impl Connection { } /// RFC 9113 §6.4 RST_STREAM. - fn handle_rst_stream(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> bool { + fn handle_rst_stream(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> Fatal { let code_raw = u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]); // §5.1: RST_STREAM on an idle (or never-seen) stream is a connection PROTOCOL_ERROR. let mut on_idle = match self.streams.get_mut(&hdr.stream_id) { @@ -1649,27 +1681,32 @@ impl Connection { && (hdr.stream_id <= self.last_stream_id || hdr.stream_id <= sink.highest_started_stream_id()) { - return false; + return Fatal::No; } if on_idle { self.send_go_away(sink, ErrorCode::ProtocolError, b"RST_STREAM on idle stream"); - return true; + return Fatal::Yes; } sink.on_stream_reset(hdr.stream_id, code_raw); - false + Fatal::No } /// RFC 9113 §6.6 PUSH_PROMISE (clients only, §8.4): reserve the promised stream and assemble its /// request header block (decoded in finish_header_block, which fires on_push_promise first). - fn handle_push_promise(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> bool { + fn handle_push_promise( + &mut self, + sink: &impl Sink, + hdr: &FrameHeader, + payload: &[u8], + ) -> Fatal { // 8.4: a server must never receive PUSH_PROMISE. - if self.is_server { + if self.is_server() { self.send_go_away( sink, ErrorCode::ProtocolError, b"server received PUSH_PROMISE", ); - return true; + return Fatal::Yes; } // 6.6: a client that disabled push (SETTINGS_ENABLE_PUSH=0) must treat the receipt of a // PUSH_PROMISE as a connection error of type PROTOCOL_ERROR. @@ -1679,7 +1716,7 @@ impl Connection { ErrorCode::ProtocolError, b"PUSH_PROMISE with push disabled", ); - return true; + return Fatal::Yes; } let mut off = 0usize; let mut end = payload.len(); @@ -1690,7 +1727,7 @@ impl Connection { ErrorCode::FrameSizeError, b"PUSH_PROMISE padded but empty", ); - return true; + return Fatal::Yes; } let pad = payload[0] as usize; off = 1; @@ -1700,7 +1737,7 @@ impl Connection { ErrorCode::ProtocolError, b"PUSH_PROMISE padding too large", ); - return true; + return Fatal::Yes; } end -= pad; } @@ -1710,7 +1747,7 @@ impl Connection { ErrorCode::FrameSizeError, b"PUSH_PROMISE missing promised id", ); - return true; + return Fatal::Yes; } let promised = u32::from_be_bytes([ payload[off], @@ -1727,7 +1764,7 @@ impl Connection { ErrorCode::ProtocolError, b"PUSH_PROMISE invalid promised stream id", ); - return true; + return Fatal::Yes; } // Reserve the promised (even) stream. @@ -1757,38 +1794,38 @@ impl Connection { } /// RFC 7838 §4 ALTSVC: optional 2-byte origin-length + origin, then the Alt-Svc field value. - fn handle_altsvc(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> bool { + fn handle_altsvc(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> Fatal { if payload.len() < 2 { - return false; // malformed ALTSVC is ignored (§4) + return Fatal::No; // malformed ALTSVC is ignored (§4) } let origin_len = u16::from_be_bytes([payload[0], payload[1]]) as usize; if 2 + origin_len > payload.len() { - return false; + return Fatal::No; } let origin = &payload[2..2 + origin_len]; let value = &payload[2 + origin_len..]; // RFC 7838 4 MUST-ignore rules: a server never accepts ALTSVC; on stream 0 the origin // must be present; on a request stream it must be empty (the stream's own origin applies). - if self.is_server + if self.is_server() || (hdr.stream_id == 0 && origin.is_empty()) || (hdr.stream_id != 0 && !origin.is_empty()) { - return false; + return Fatal::No; } sink.on_altsvc(hdr.stream_id, origin, value); - false + Fatal::No } /// RFC 8336 §2 ORIGIN: a sequence of (2-byte length + origin) entries on stream 0. - fn handle_origin(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> bool { + fn handle_origin(&mut self, sink: &impl Sink, hdr: &FrameHeader, payload: &[u8]) -> Fatal { // §2.1: ORIGIN on a non-zero stream is ignored, and like ALTSVC it is server-to-client // only - a server receiving it must ignore it. The whole payload is delivered once; the // embedder iterates the (2-byte length, origin) entries and surfaces a single event. - if hdr.stream_id != 0 || self.is_server { - return false; + if hdr.stream_id != 0 || self.is_server() { + return Fatal::No; } sink.on_origin(payload); - false + Fatal::No } // ---- Outbound stream API (called by the embedder) ------------------ @@ -1802,7 +1839,7 @@ impl Connection { } /// HPACK-encode one header field into the current block. Returns false on encode failure. - pub fn encode_header(&mut self, name: &[u8], value: &[u8], never_index: bool) -> bool { + pub fn encode_header(&mut self, name: &[u8], value: &[u8], never_index: NeverIndex) -> bool { let old = self.enc_buf.len(); self.enc_buf.resize(old + name.len() + value.len() + 16, 0); match self @@ -1822,7 +1859,8 @@ impl Connection { /// Emit the accumulated header block as a HEADERS frame, splitting into CONTINUATION frames when /// it exceeds the peer's max frame size (§4.3/§6.10), and advance the send-side stream state. - pub fn send_header_block(&mut self, sink: &impl Sink, stream_id: u32, end_stream: bool) { + pub fn send_header_block(&mut self, sink: &impl Sink, stream_id: u32, end_stream: EndStream) { + let end_stream = end_stream == EndStream::Yes; let block = std::mem::take(&mut self.enc_buf); let max = (self.remote_settings.max_frame_size as usize).max(1); let total = block.len(); @@ -1889,8 +1927,9 @@ impl Connection { sink: &impl Sink, stream_id: u32, data: &[u8], - end_stream: bool, + end_stream: EndStream, ) -> usize { + let end_stream = end_stream == EndStream::Yes; let conn_avail = self.send_window.available(); let stream_avail = self .streams @@ -2013,14 +2052,14 @@ mod tests { #[derive(Default)] struct CaptureSink { out: RefCell>, - pings: RefCell, bool)>>, + pings: RefCell, Ack)>>, remote_settings: Cell, goaway: Cell>, /// nghttp2-style lib error code from on_error (locally-detected connection errors). local_error: Cell>, opens: RefCell>, headers: RefCell, Vec)>>, - headers_done: RefCell>, + headers_done: RefCell>, data: RefCell)>>, ended: RefCell>, resets: RefCell>, @@ -2041,7 +2080,7 @@ mod tests { fn on_remote_settings(&self, _s: &Settings) { self.remote_settings.set(self.remote_settings.get() + 1); } - fn on_ping(&self, payload: &[u8], is_ack: bool) { + fn on_ping(&self, payload: &[u8], is_ack: Ack) { self.pings.borrow_mut().push((payload.to_vec(), is_ack)); } fn on_go_away(&self, c: u32, l: u32, _d: &[u8]) { @@ -2051,12 +2090,12 @@ mod tests { fn on_stream_open(&self, id: u32) { self.opens.borrow_mut().push(id); } - fn on_header(&self, id: u32, name: &[u8], value: &[u8], _never: bool) { + fn on_header(&self, id: u32, name: &[u8], value: &[u8], _never: NeverIndex) { self.headers .borrow_mut() .push((id, name.to_vec(), value.to_vec())); } - fn on_headers_complete(&self, id: u32, end_stream: bool, _flags: u8) { + fn on_headers_complete(&self, id: u32, end_stream: EndStream, _flags: u8) { self.headers_done.borrow_mut().push((id, end_stream)); } fn on_data(&self, id: u32, data: &[u8]) { @@ -2087,7 +2126,9 @@ mod tests { let mut buf = vec![0u8; 4096]; let mut off = 0usize; for (name, value) in pairs { - off += coder.encode(name, value, false, &mut buf, off).unwrap(); + off += coder + .encode(name, value, NeverIndex::No, &mut buf, off) + .unwrap(); } buf.truncate(off); buf @@ -2111,7 +2152,7 @@ mod tests { #[test] fn ping_is_acked_with_echo() { let sink = CaptureSink::default(); - let mut c = Connection::new(true, Settings::default()); + let mut c = Connection::new(Side::Server, Settings::default()); c.preface_received = wire::CONNECTION_PREFACE.len(); // skip preface let payload = [1u8, 2, 3, 4, 5, 6, 7, 8]; let f = frame(FrameType::Ping, 0, 0, &payload); @@ -2131,7 +2172,7 @@ mod tests { #[test] fn window_update_zero_increment_on_conn_is_goaway() { let sink = CaptureSink::default(); - let mut c = Connection::new(true, Settings::default()); + let mut c = Connection::new(Side::Server, Settings::default()); c.preface_received = wire::CONNECTION_PREFACE.len(); let f = frame(FrameType::WindowUpdate, 0, 0, &[0, 0, 0, 0]); let fed = c.receive(&sink, &f); @@ -2142,7 +2183,7 @@ mod tests { #[test] fn settings_value_out_of_range_is_goaway() { let sink = CaptureSink::default(); - let mut c = Connection::new(true, Settings::default()); + let mut c = Connection::new(Side::Server, Settings::default()); c.preface_received = wire::CONNECTION_PREFACE.len(); // ENABLE_PUSH = 2 (invalid) let payload = [0u8, 0x02, 0, 0, 0, 2]; @@ -2155,7 +2196,7 @@ mod tests { #[test] fn headers_decode_and_open_stream() { let sink = CaptureSink::default(); - let mut c = Connection::new(true, Settings::default()); + let mut c = Connection::new(Side::Server, Settings::default()); c.preface_received = wire::CONNECTION_PREFACE.len(); let block = encode_block(&[ (b":method", b"GET"), @@ -2181,7 +2222,7 @@ mod tests { .iter() .any(|(id, n, v)| *id == 1 && n == b":path" && v == b"/") ); - assert_eq!(*sink.headers_done.borrow(), vec![(1, true)]); + assert_eq!(*sink.headers_done.borrow(), vec![(1, EndStream::Yes)]); assert_eq!(*sink.ended.borrow(), vec![1]); assert_eq!( c.streams.get(&1).map(|s| s.state), @@ -2192,7 +2233,7 @@ mod tests { #[test] fn data_after_headers_is_delivered() { let sink = CaptureSink::default(); - let mut c = Connection::new(true, Settings::default()); + let mut c = Connection::new(Side::Server, Settings::default()); c.preface_received = wire::CONNECTION_PREFACE.len(); let block = encode_block(&[ (b":method", b"POST"), @@ -2219,7 +2260,7 @@ mod tests { #[test] fn rst_stream_on_idle_is_goaway() { let sink = CaptureSink::default(); - let mut c = Connection::new(true, Settings::default()); + let mut c = Connection::new(Side::Server, Settings::default()); c.preface_received = wire::CONNECTION_PREFACE.len(); let f = frame( FrameType::RstStream, @@ -2236,13 +2277,13 @@ mod tests { fn headers_roundtrip_client_to_server() { // Client engine encodes + emits a HEADERS frame... let csink = CaptureSink::default(); - let mut client = Connection::new(false, Settings::default()); + let mut client = Connection::new(Side::Client, Settings::default()); client.begin_header_block(); - assert!(client.encode_header(b":method", b"GET", false)); - assert!(client.encode_header(b":scheme", b"http", false)); - assert!(client.encode_header(b":path", b"/x", false)); - assert!(client.encode_header(b":authority", b"localhost", false)); - client.send_header_block(&csink, 1, true); + assert!(client.encode_header(b":method", b"GET", NeverIndex::No)); + assert!(client.encode_header(b":scheme", b"http", NeverIndex::No)); + assert!(client.encode_header(b":path", b"/x", NeverIndex::No)); + assert!(client.encode_header(b":authority", b"localhost", NeverIndex::No)); + client.send_header_block(&csink, 1, EndStream::Yes); let wire_bytes = csink.out.borrow().clone(); assert_eq!( client.streams.get(&1).map(|s| s.state), @@ -2251,7 +2292,7 @@ mod tests { // ...and a server engine decodes the exact same bytes back to the original fields. let ssink = CaptureSink::default(); - let mut server = Connection::new(true, Settings::default()); + let mut server = Connection::new(Side::Server, Settings::default()); server.preface_received = wire::CONNECTION_PREFACE.len(); let fed = server.receive(&ssink, &wire_bytes); assert!(!fed.fatal); @@ -2277,12 +2318,12 @@ mod tests { fn push_promise_roundtrip_server_to_client() { // Server stages the promised request headers and emits PUSH_PROMISE on parent stream 1. let ssink = CaptureSink::default(); - let mut server = Connection::new(true, Settings::default()); + let mut server = Connection::new(Side::Server, Settings::default()); server.begin_header_block(); - assert!(server.encode_header(b":method", b"GET", false)); - assert!(server.encode_header(b":scheme", b"http", false)); - assert!(server.encode_header(b":path", b"/pushed", false)); - assert!(server.encode_header(b":authority", b"localhost", false)); + assert!(server.encode_header(b":method", b"GET", NeverIndex::No)); + assert!(server.encode_header(b":scheme", b"http", NeverIndex::No)); + assert!(server.encode_header(b":path", b"/pushed", NeverIndex::No)); + assert!(server.encode_header(b":authority", b"localhost", NeverIndex::No)); server.send_push_promise(&ssink, 1, 2); let bytes = ssink.out.borrow().clone(); assert_eq!( @@ -2292,7 +2333,7 @@ mod tests { // Client receives it: on_push_promise(parent=1, promised=2) then the request headers. let csink = CaptureSink::default(); - let mut client = Connection::new(false, Settings::default()); + let mut client = Connection::new(Side::Client, Settings::default()); client.preface_received = wire::CONNECTION_PREFACE.len(); let fed = client.receive(&csink, &bytes); assert!(!fed.fatal); @@ -2314,7 +2355,7 @@ mod tests { #[test] fn server_rejects_inbound_push_promise() { let sink = CaptureSink::default(); - let mut c = Connection::new(true, Settings::default()); + let mut c = Connection::new(Side::Server, Settings::default()); c.preface_received = wire::CONNECTION_PREFACE.len(); // promised id + empty block let f = frame( @@ -2331,18 +2372,18 @@ mod tests { #[test] fn send_data_respects_flow_control_window() { let sink = CaptureSink::default(); - let mut c = Connection::new(false, Settings::default()); + let mut c = Connection::new(Side::Client, Settings::default()); // Open a stream (send side) with a tiny peer window. c.begin_header_block(); - assert!(c.encode_header(b":method", b"POST", false)); - c.send_header_block(&sink, 1, false); + assert!(c.encode_header(b":method", b"POST", NeverIndex::No)); + c.send_header_block(&sink, 1, EndStream::No); sink.out.borrow_mut().clear(); if let Some(s) = c.streams.get_mut(&1) { s.send_window = SendWindow::new(4); } c.send_window = SendWindow::new(4); // Only 4 of 10 bytes fit in the window. - let sent = c.send_data(&sink, 1, b"0123456789", true); + let sent = c.send_data(&sink, 1, b"0123456789", EndStream::Yes); assert_eq!(sent, 4); } } diff --git a/src/runtime/api/bun/h2/hpack.rs b/src/runtime/api/bun/h2/hpack.rs index 5b6dfcacb490..7d94657e6763 100644 --- a/src/runtime/api/bun/h2/hpack.rs +++ b/src/runtime/api/bun/h2/hpack.rs @@ -8,7 +8,7 @@ #![allow(dead_code)] -use bun_http::lshpack::{DecodeResult, HpackError, HpackHandle}; +use bun_http::lshpack::{DecodeResult, HpackError, HpackHandle, NeverIndex}; /// RFC 7541 §6.3: a Dynamic Table Size Update integer never needs more than 6 bytes for a u32. pub const MAX_SIZE_UPDATE_BYTES: usize = 6; @@ -55,7 +55,7 @@ impl Coder { &mut self, name: &[u8], value: &[u8], - never_index: bool, + never_index: NeverIndex, dst: &mut [u8], offset: usize, ) -> Result { diff --git a/src/runtime/api/bun/h2/wire.rs b/src/runtime/api/bun/h2/wire.rs index e842287a9eb5..82daa2c75ec9 100644 --- a/src/runtime/api/bun/h2/wire.rs +++ b/src/runtime/api/bun/h2/wire.rs @@ -75,6 +75,15 @@ pub mod flags { } } +bun_core::bool_enum!( + /// The ACK flag on a PING/SETTINGS frame. + pub Ack +); +bun_core::bool_enum!( + /// The PADDED flag on a DATA/HEADERS/PUSH_PROMISE frame. + pub Padded +); + /// RFC 9113 §7 error codes. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[repr(u32)] diff --git a/src/runtime/api/bun/h2_frame_parser.rs b/src/runtime/api/bun/h2_frame_parser.rs index 332ac4dc9b42..dc40a84d1d8b 100644 --- a/src/runtime/api/bun/h2_frame_parser.rs +++ b/src/runtime/api/bun/h2_frame_parser.rs @@ -15,6 +15,8 @@ use core::mem::ManuallyDrop; use core::ptr::NonNull; use std::borrow::Cow; +use crate::api::h2::connection::Side; +use crate::api::h2::wire::Ack; use crate::api::socket::{TCPSocket, TLSSocket}; use crate::node::{Encoding, StringOrBuffer}; use crate::socket::NativeCallbacks; @@ -24,7 +26,9 @@ use bun_collections::{ByteVecExt, HashMap as BunHashMap, HiveArrayFallback, VecE use bun_core::MutableString; use bun_core::String as BunString; use bun_core::strings; +use bun_http::h2::EndStream; use bun_http::lshpack; +use bun_http::lshpack::NeverIndex; use bun_jsc::AbortSignal; use bun_jsc::ErrorCode as JscErrorCode; use bun_jsc::StringJsc as _; @@ -352,13 +356,13 @@ impl UInt31WithReserved { Self(u32_from_bytes(src)) } #[inline] - fn write(self, writer: &mut impl WireWriter) -> bool { + fn write(self, writer: &mut impl WireWriter) -> bun_io::Result<()> { let mut value: u32 = self.uint31(); if self.reserved() { value |= 0x8000_0000; } value = value.swap_bytes(); - writer.write_all(&value.to_ne_bytes()).is_ok() + writer.write_all(&value.to_ne_bytes()) } } @@ -378,10 +382,10 @@ const _: () = assert!(core::mem::size_of::() == StreamPriority:: impl StreamPriority { pub(crate) const BYTE_SIZE: usize = 5; #[inline] - fn write(self, writer: &mut impl WireWriter) -> bool { + fn write(self, writer: &mut impl WireWriter) -> bun_io::Result<()> { let mut swap = self; swap.stream_identifier = swap.stream_identifier.swap_bytes(); - writer.write_all(bytemuck::bytes_of(&swap)).is_ok() + writer.write_all(bytemuck::bytes_of(&swap)) } #[inline] fn from(dst: &mut StreamPriority, src: &[u8]) { @@ -420,7 +424,7 @@ impl Default for FrameHeader { impl FrameHeader { pub const BYTE_SIZE: usize = 9; #[inline] - fn write(&self, writer: &mut impl WireWriter, frames_sent: &Cell) -> bool { + fn write(&self, writer: &mut impl WireWriter, frames_sent: &Cell) -> bun_io::Result<()> { frames_sent.set(frames_sent.get() + 1); let mut buf = [0u8; Self::BYTE_SIZE]; buf[0] = ((self.length >> 16) & 0xFF) as u8; @@ -429,7 +433,7 @@ impl FrameHeader { buf[3] = self.type_; buf[4] = self.flags; buf[5..9].copy_from_slice(&self.stream_identifier.to_be_bytes()); - writer.write_all(&buf).is_ok() + writer.write_all(&buf) } /// Decode a complete 9-byte big-endian frame header. /// @@ -603,7 +607,7 @@ impl FullSettingsPayload { } } - pub(crate) fn write(&self, writer: &mut impl WireWriter) -> bool { + pub(crate) fn write(&self, writer: &mut impl WireWriter) -> bun_io::Result<()> { let mut swap = *self; swap._header_table_size_type = swap._header_table_size_type.swap_bytes(); swap.header_table_size = swap.header_table_size.swap_bytes(); @@ -619,7 +623,7 @@ impl FullSettingsPayload { swap.max_header_list_size = swap.max_header_list_size.swap_bytes(); swap._enable_connect_protocol_type = swap._enable_connect_protocol_type.swap_bytes(); swap.enable_connect_protocol = swap.enable_connect_protocol.swap_bytes(); - writer.write_all(bytemuck::bytes_of(&swap)).is_ok() + writer.write_all(bytemuck::bytes_of(&swap)) } } @@ -1360,7 +1364,7 @@ pub struct H2FrameParser { // Stream id whose header block is awaiting CONTINUATION frames // (RFC 9113 §4.3); 0 when none. expecting_continuation: Cell, - is_server: Cell, + side: Cell, /// A frame callback left an exception pending in this batch (`Sink::should_stop`). left_exception: Cell, preface_received_len: Cell, @@ -1682,6 +1686,12 @@ impl PendingQueue { // PendingQueue::deinit handled by Drop on Vec +bun_core::bool_enum!(CallbackDeferred); +bun_core::bool_enum!( + /// Whether `send_go_away` also dispatches `onError` to JS after writing the frame. + pub(crate) EmitError +); + #[derive(Default)] struct PendingFrame { end_stream: bool, // end_stream flag @@ -1750,7 +1760,9 @@ impl Stream { length: 0, }; owned_frame = Some(frame); - break 'brk data_header.write(&mut writer, &client.frames_sent_legacy); + break 'brk data_header + .write(&mut writer, &client.frames_sent_legacy) + .is_ok(); } else { let max_size = frame_remaining .min( @@ -1938,8 +1950,9 @@ impl Stream { client: &H2FrameParser, bytes: &[u8], callback: JSValue, - end_stream: bool, + end_stream: EndStream, ) { + let end_stream = end_stream == EndStream::Yes; let global_this = client.global_this; // Note: `dispatch_write_callback()` below re-enters JS, which can @@ -2029,13 +2042,20 @@ impl Stream { // SAFETY: `this` is the live `&mut self`; no borrow of `*this` // is held here (the `last_frame` raw pointer is unused past // this point). - return unsafe { (*this).queue_frame(client, more_data, callback, end_stream) }; + return unsafe { + (*this).queue_frame( + client, + more_data, + callback, + EndStream::from_bool(end_stream), + ) + }; } } bun_output::scoped_log!( H2FrameParser, "{} queued {} {}", - if client.is_server.get() { + if client.is_server() { "server" } else { "client" @@ -2242,6 +2262,11 @@ type HeaderValue = lshpack::DecodeResult; // ────────────────────────────────────────────────────────────────────────── impl H2FrameParser { + #[inline] + fn is_server(&self) -> bool { + self.side.get() == Side::Server + } + /// Encodes a single header into the ArrayList, growing if needed. /// Returns the number of bytes written, or error on failure. /// @@ -2251,7 +2276,7 @@ impl H2FrameParser { encoded_headers: &mut Vec, name: &[u8], value: &[u8], - never_index: bool, + never_index: NeverIndex, ) -> crate::Result { let old_len = encoded_headers.len(); let required = old_len + name.len() + value.len() + HPACK_ENTRY_OVERHEAD; @@ -2293,7 +2318,7 @@ impl H2FrameParser { dst_offset: usize, name: &[u8], value: &[u8], - never_index: bool, + never_index: NeverIndex, ) -> crate::Result { self.hpack.with_mut(|hpack| { if let Some(hpack) = hpack.as_mut() { @@ -2319,7 +2344,7 @@ impl H2FrameParser { "adjustWindowSize {} {} {} {}", self.used_window_size.get(), self.window_size.get(), - self.is_server.get(), + self.is_server(), payload_size ); if self.used_window_size.get() > self.window_size.get() { @@ -2329,7 +2354,7 @@ impl H2FrameParser { ErrorCode::FLOW_CONTROL_ERROR, b"Window size overflow", self.last_stream_id.get(), - true, + EmitError::Yes, ); self.used_window_size .set(self.used_window_size.get() - payload_size as u64); @@ -2344,7 +2369,7 @@ impl H2FrameParser { ErrorCode::FLOW_CONTROL_ERROR, b"Window size overflow", self.last_stream_id.get(), - true, + EmitError::Yes, ); s.used_window_size -= payload_size as u64; } @@ -2363,7 +2388,7 @@ impl H2FrameParser { stream.id, stream.used_window_size, stream.window_size, - self.is_server.get() + self.is_server() ); if stream.used_window_size >= stream.window_size / 2 && stream.used_window_size > 0 { let consumed = stream.used_window_size; @@ -2373,7 +2398,7 @@ impl H2FrameParser { "incrementWindowSizeIfNeeded stream {} {} {}", stream.id, stream.window_size, - self.is_server.get() + self.is_server() ); updates.push((stream.id, consumed)); } @@ -2386,7 +2411,7 @@ impl H2FrameParser { "incrementWindowSizeIfNeeded connection {} {} {}", self.used_window_size.get(), self.window_size.get(), - self.is_server.get() + self.is_server() ); if self.used_window_size.get() >= self.window_size.get() / 2 && self.used_window_size.get() > 0 @@ -2445,7 +2470,7 @@ impl H2FrameParser { ErrorCode::MAX_PENDING_SETTINGS_ACK, b"Maximum number of pending settings acknowledgements", self.last_stream_id.get(), - true, + EmitError::Yes, ); return false; } @@ -2567,8 +2592,9 @@ impl H2FrameParser { rst_code: ErrorCode, debug_data: &[u8], last_stream_id: u32, - emit_error: bool, + emit_error: EmitError, ) { + let emit_error = emit_error == EmitError::Yes; bun_output::scoped_log!( H2FrameParser, "HTTP_FRAME_GOAWAY {} code {} debug_data {} emitError {}", @@ -2664,7 +2690,8 @@ impl H2FrameParser { } } - pub(crate) fn send_ping(&self, ack: bool, payload: &[u8]) { + pub(crate) fn send_ping(&self, ack: Ack, payload: &[u8]) { + let ack = ack == Ack::Yes; bun_output::scoped_log!( H2FrameParser, "HTTP_FRAME_PING ack {} payload {}", @@ -3795,7 +3822,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"Invalid frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return None; } @@ -3853,7 +3880,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"Invalid dataframe frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); } @@ -3876,7 +3903,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"WINDOW_UPDATE with 0 increment", self.last_stream_id.get(), - true, + EmitError::Yes, ); } return end; @@ -3901,7 +3928,7 @@ impl H2FrameParser { ErrorCode::FLOW_CONTROL_ERROR, b"flow-control window exceeded 2^31-1", self.last_stream_id.get(), - true, + EmitError::Yes, ); return end; } @@ -3941,13 +3968,13 @@ impl H2FrameParser { frame: FrameHeader, data: &[u8], ) -> JsResult { - if self.is_server.get() { + if self.is_server() { self.send_go_away( frame.stream_identifier, ErrorCode::PROTOCOL_ERROR, b"Server received PUSH_PROMISE", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(data.len()); } @@ -3961,7 +3988,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"Invalid PUSH_PROMISE frame", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(end); } @@ -3980,7 +4007,7 @@ impl H2FrameParser { ErrorCode::COMPRESSION_ERROR, b"Invalid HPACK header block", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(end); } @@ -4049,7 +4076,7 @@ impl H2FrameParser { bun_output::scoped_log!( H2FrameParser, "decodeHeaderBlock isSever: {}", - self.is_server.get() + self.is_server() ); let mut offset: usize = 0; @@ -4078,7 +4105,7 @@ impl H2FrameParser { ErrorCode::COMPRESSION_ERROR, b"Invalid HPACK header block", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(None); } @@ -4090,13 +4117,13 @@ impl H2FrameParser { BStr::new(header.name), BStr::new(header.value) ); - if self.is_server.get() && header.name == b":status" { + if self.is_server() && header.name == b":status" { self.send_go_away( stream_id, ErrorCode::PROTOCOL_ERROR, b"Server received :status header", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(None); } @@ -4121,7 +4148,7 @@ impl H2FrameParser { || is_malformed_field_name(header.name) || is_malformed_field_value(header.value) || (header.name.first() == Some(&b':') - && !if self.is_server.get() { + && !if self.is_server() { is_valid_request_pseudo_header(header.name) } else { is_valid_response_pseudo_header(header.name) @@ -4173,7 +4200,7 @@ impl H2FrameParser { ErrorCode::ENHANCE_YOUR_CALM, b"ENHANCE_YOUR_CALM", self.last_stream_id.get(), - true, + EmitError::Yes, ); } else { self.end_stream(stream, ErrorCode::ENHANCE_YOUR_CALM); @@ -4205,11 +4232,7 @@ impl H2FrameParser { bun_output::scoped_log!( H2FrameParser, "handleDataFrame {} data.len: {}", - if self.is_server.get() { - "server" - } else { - "client" - }, + if self.is_server() { "server" } else { "client" }, data.len() ); self.read_buffer.with_mut(|rb| rb.reset()); @@ -4224,7 +4247,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Data frame on connection stream", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); }; @@ -4244,7 +4267,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"Invalid dataframe frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); } @@ -4269,7 +4292,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"Invalid data frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); } @@ -4293,7 +4316,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Invalid data frame padding", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); } @@ -4304,7 +4327,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"Invalid data frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); } @@ -4399,7 +4422,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"GoAway frame on stream", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); } @@ -4409,7 +4432,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"invalid GoAway frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); } @@ -4469,13 +4492,13 @@ impl H2FrameParser { _: Option<*mut Stream>, ) -> JsResult { bun_output::scoped_log!(H2FrameParser, "handleOriginFrame {}", BStr::new(data)); - if self.is_server.get() { + if self.is_server() { self.send_go_away( frame.stream_identifier, ErrorCode::PROTOCOL_ERROR, b"ORIGIN frame on server", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(data.len()); } @@ -4485,7 +4508,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"ORIGIN frame on stream", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(data.len()); } @@ -4508,7 +4531,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"invalid ORIGIN frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(end); } @@ -4520,7 +4543,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"invalid ORIGIN frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(end); } @@ -4556,14 +4579,14 @@ impl H2FrameParser { stream_: Option<*mut Stream>, ) -> JsResult { bun_output::scoped_log!(H2FrameParser, "handleAltsvcFrame {}", BStr::new(data)); - if self.is_server.get() { + if self.is_server() { // client should not send ALTSVC frame self.send_go_away( frame.stream_identifier, ErrorCode::PROTOCOL_ERROR, b"ALTSVC frame on server", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(data.len()); } @@ -4578,7 +4601,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"invalid ALTSVC frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(end); } @@ -4591,7 +4614,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"invalid ALTSVC frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(end); } @@ -4624,7 +4647,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"RST_STREAM frame on connection stream", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); }; @@ -4637,7 +4660,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"invalid RST_STREAM frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); } @@ -4648,7 +4671,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Headers frame without continuation", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); } @@ -4693,7 +4716,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Ping frame on stream", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); } @@ -4704,7 +4727,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"Invalid ping frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); } @@ -4727,11 +4750,11 @@ impl H2FrameParser { ErrorCode::ENHANCE_YOUR_CALM, b"ENHANCE_YOUR_CALM", self.last_stream_id.get(), - true, + EmitError::Yes, ); return end; } - self.send_ping(true, &payload_owned); + self.send_ping(Ack::Yes, &payload_owned); } else { self.out_standing_pings .set(self.out_standing_pings.get().saturating_sub(1)); @@ -4767,7 +4790,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"invalid Priority frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); } @@ -4786,7 +4809,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Priority frame on connection stream", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); }; @@ -4808,7 +4831,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Priority frame with self dependency", self.last_stream_id.get(), - true, + EmitError::Yes, ); return end; } @@ -4835,7 +4858,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Continuation on connection stream", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(data.len()); }; @@ -4848,7 +4871,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Continuation without headers", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(data.len()); } @@ -4858,7 +4881,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"invalid Continuation frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(data.len()); } @@ -4877,7 +4900,7 @@ impl H2FrameParser { ErrorCode::ENHANCE_YOUR_CALM, b"ENHANCE_YOUR_CALM", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(end); } @@ -4947,11 +4970,7 @@ impl H2FrameParser { bun_output::scoped_log!( H2FrameParser, "handleHeadersFrame {}", - if self.is_server.get() { - "server" - } else { - "client" - } + if self.is_server() { "server" } else { "client" } ); let Some(stream_ptr) = stream_ else { self.send_go_away( @@ -4959,7 +4978,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Headers frame on connection stream", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(data.len()); }; @@ -4972,7 +4991,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"invalid Headers frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(data.len()); } @@ -4983,7 +5002,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Headers frame without continuation", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(data.len()); } @@ -5002,7 +5021,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"invalid Headers frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(end_); } @@ -5020,7 +5039,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"invalid Headers frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(end_); } @@ -5030,7 +5049,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"invalid Headers frame padding", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(end_); } @@ -5055,7 +5074,7 @@ impl H2FrameParser { ErrorCode::ENHANCE_YOUR_CALM, b"ENHANCE_YOUR_CALM", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(end_); } @@ -5085,11 +5104,7 @@ impl H2FrameParser { bun_output::scoped_log!( H2FrameParser, "handleSettingsFrame {} isACK {}", - if self.is_server.get() { - "server" - } else { - "client" - }, + if self.is_server() { "server" } else { "client" }, is_ack ); if frame.stream_identifier != 0 { @@ -5098,7 +5113,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Settings frame on connection stream", self.last_stream_id.get(), - true, + EmitError::Yes, ); return data.len(); } @@ -5114,7 +5129,7 @@ impl H2FrameParser { ErrorCode::FRAME_SIZE_ERROR, b"Invalid settings frame size", self.last_stream_id.get(), - true, + EmitError::Yes, ); if send_ack_on_exit { self.send_settings_ack(); @@ -5234,7 +5249,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Invalid SETTINGS_MAX_FRAME_SIZE", self.last_stream_id.get(), - true, + EmitError::Yes, ); return end; } @@ -5250,7 +5265,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Invalid SETTINGS value", self.last_stream_id.get(), - true, + EmitError::Yes, ); return end; } @@ -5264,7 +5279,7 @@ impl H2FrameParser { ErrorCode::FLOW_CONTROL_ERROR, b"Invalid SETTINGS_INITIAL_WINDOW_SIZE", self.last_stream_id.get(), - true, + EmitError::Yes, ); return end; } @@ -5275,7 +5290,7 @@ impl H2FrameParser { "remoteSettings: {} {} isServer: {}", _ut, _uv, - self.is_server.get() + self.is_server() ); i += setting_byte_size; } @@ -5333,7 +5348,7 @@ impl H2FrameParser { if stream_identifier > self.last_stream_id.get() { self.last_stream_id.set(stream_identifier); } - let peer_parity: u32 = if self.is_server.get() { 1 } else { 0 }; + let peer_parity: u32 = if self.is_server() { 1 } else { 0 }; if stream_identifier % 2 == peer_parity && stream_identifier > self.last_peer_stream_id.get() { @@ -5421,7 +5436,7 @@ impl H2FrameParser { if let Some(stream) = self.streams.get().get(&stream_identifier).copied() { return Some(stream); } - if frame_type != FrameType::HTTP_FRAME_HEADERS as u8 || !self.is_server.get() { + if frame_type != FrameType::HTTP_FRAME_HEADERS as u8 || !self.is_server() { return None; } // RFC 9113 §4.3: while a header block is mid-reassembly the only legal @@ -5445,7 +5460,7 @@ impl H2FrameParser { ErrorCode::ENHANCE_YOUR_CALM, b"ENHANCE_YOUR_CALM", self.last_stream_id.get(), - true, + EmitError::Yes, ); return None; } @@ -5462,7 +5477,7 @@ impl H2FrameParser { return Ok(bytes.len()); } bun_output::scoped_log!(H2FrameParser, "read {}", bytes.len()); - if self.is_server.get() && self.preface_received_len.get() < 24 { + if self.is_server() && self.preface_received_len.get() < 24 { // Handle Server Preface let preface_missing: usize = 24 - self.preface_received_len.get() as usize; let preface_available = preface_missing.min(bytes.len()); @@ -5477,7 +5492,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Invalid preface", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(preface_available); } @@ -5491,11 +5506,7 @@ impl H2FrameParser { bun_output::scoped_log!( H2FrameParser, "current frame {} {} {} {} {}", - if self.is_server.get() { - "server" - } else { - "client" - }, + if self.is_server() { "server" } else { "client" }, header.type_, header.length, header.flags, @@ -5570,11 +5581,7 @@ impl H2FrameParser { bun_output::scoped_log!( H2FrameParser, "new frame {} {} {} {} {}", - if self.is_server.get() { - "server" - } else { - "client" - }, + if self.is_server() { "server" } else { "client" }, header.type_, header.length, header.flags, @@ -5613,7 +5620,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Expected CONTINUATION frame", self.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(bytes.len() + add); } @@ -5703,7 +5710,7 @@ impl H2FrameParser { fn ensure_engine(&self) { if self.engine.borrow().is_none() { let mut conn = crate::api::h2::connection::Connection::new( - self.is_server.get(), + self.side.get(), self.rewrite_local_settings(), ); // The engine is created lazily on the first inbound read, by which point settings() @@ -6057,7 +6064,8 @@ impl crate::api::h2::connection::Sink for H2FrameParser { self.dispatch(JSH2FrameParser::Gc::onRemoteSettings, js); } - fn on_ping(&self, payload: &[u8], is_ack: bool) { + fn on_ping(&self, payload: &[u8], is_ack: Ack) { + let is_ack = is_ack == Ack::Yes; if is_ack { // node (Http2Session::HandlePingFrame): a PING ACK with no outstanding ping is // unsolicited and treated as a connection error (NGHTTP2_ERR_PROTO -> NghttpError @@ -6228,7 +6236,7 @@ impl crate::api::h2::connection::Sink for H2FrameParser { let _ = self.handle_received_stream_id(stream_id); } - fn on_header(&self, _stream_id: u32, name: &[u8], value: &[u8], never_index: bool) { + fn on_header(&self, _stream_id: u32, name: &[u8], value: &[u8], never_index: NeverIndex) { // Accumulate raw bytes; the whole block is materialized into JS values in one // native call at on_headers_complete (see H2HeadersMaterializer.cpp). self.hdr_block.with_mut(|b| { @@ -6237,7 +6245,7 @@ impl crate::api::h2::connection::Sink for H2FrameParser { }); self.hdr_meta.with_mut(|m| { let mut packed_name_len = name.len() as u32; - if never_index { + if never_index == NeverIndex::Yes { packed_name_len |= 0x8000_0000; } m.push(packed_name_len); @@ -6245,7 +6253,8 @@ impl crate::api::h2::connection::Sink for H2FrameParser { }); } - fn on_headers_complete(&self, stream_id: u32, end_stream: bool, flags: u8) { + fn on_headers_complete(&self, stream_id: u32, end_stream: EndStream, flags: u8) { + let end_stream = end_stream == EndStream::Yes; // Bridge: the JS endAfterHeaders getter reads the legacy stream's end_after_headers flag. if let Some(stream) = self.streams.get().get(&stream_id).copied() { // SAFETY: stream is *mut Stream from self.streams; valid while the map entry exists @@ -6363,7 +6372,7 @@ impl crate::api::h2::connection::Sink for H2FrameParser { ErrorCode::ENHANCE_YOUR_CALM, b"ENHANCE_YOUR_CALM", self.last_stream_id.get(), - true, + EmitError::Yes, ); } } @@ -6929,7 +6938,7 @@ impl H2FrameParser { ErrorCode(error_code as u32), &copied, last_stream_id, - false, + EmitError::No, ); return Ok(JSValue::UNDEFINED); } @@ -6937,7 +6946,13 @@ impl H2FrameParser { } } - this.send_go_away(0, ErrorCode(error_code as u32), b"", last_stream_id, false); + this.send_go_away( + 0, + ErrorCode(error_code as u32), + b"", + last_stream_id, + EmitError::No, + ); Ok(JSValue::UNDEFINED) } @@ -6961,7 +6976,7 @@ impl H2FrameParser { if let Some(array_buffer) = payload_arg.as_array_buffer(global_object) { let slice = array_buffer.slice(); - this.send_ping(false, slice); + this.send_ping(Ack::No, slice); return Ok(JSValue::TRUE); } @@ -7327,7 +7342,7 @@ impl H2FrameParser { ErrorCode::PROTOCOL_ERROR, b"Stream with self dependency", this.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(JSValue::FALSE); } @@ -7389,7 +7404,7 @@ impl H2FrameParser { // counts; budget it identically so a flood of refused streams still tears the session // down. Server-side only: a client's GOAWAY sweep resets its own unprocessed streams // with REFUSED_STREAM and must not consume the budget. - if error_code == ErrorCode::REFUSED_STREAM.0 && this.is_server.get() { + if error_code == ErrorCode::REFUSED_STREAM.0 && this.is_server() { this.rejected_streams.set(this.rejected_streams.get() + 1); if this.max_rejected_streams.get() <= this.rejected_streams.get() { this.send_go_away( @@ -7397,7 +7412,7 @@ impl H2FrameParser { ErrorCode::ENHANCE_YOUR_CALM, b"ENHANCE_YOUR_CALM", this.last_stream_id.get(), - true, + EmitError::Yes, ); return Ok(JSValue::UNDEFINED); } @@ -7475,7 +7490,7 @@ impl H2FrameParser { payload: &[u8], callback: JSValue, options: SendDataOptions, - ) -> (u8, bool) { + ) -> (u8, CallbackDeferred) { let SendDataOptions { close, suppress_half_closed_local_dispatch, @@ -7484,11 +7499,7 @@ impl H2FrameParser { bun_output::scoped_log!( H2FrameParser, "HTTP_FRAME_DATA {} sendData({}, {}, {})", - if self.is_server.get() { - "server" - } else { - "client" - }, + if self.is_server() { "server" } else { "client" }, stream.id, payload.len(), close @@ -7513,7 +7524,7 @@ impl H2FrameParser { }; if self.has_backpressure() || self.outbound_queue_size.get() > 0 { enqueued = true; - stream.queue_frame(self, b"", callback, close); + stream.queue_frame(self, b"", callback, EndStream::from_bool(close)); } else { let mut writer = self.to_writer(); let _ = data_header.write(&mut writer, &self.frames_sent_legacy); @@ -7567,7 +7578,7 @@ impl H2FrameParser { } else { JSValue::UNDEFINED }, - offset >= payload.len() && close, + EndStream::from_bool(offset >= payload.len() && close), ); } else { let padding = stream.get_padding(size, max_size - 1); @@ -7678,10 +7689,10 @@ impl H2FrameParser { } let mut settled_state: u8 = 0; - let mut callback_deferred = false; + let mut callback_deferred = CallbackDeferred::No; if !enqueued { if defer_write_callback && callback.is_callable() { - callback_deferred = true; + callback_deferred = CallbackDeferred::Yes; } else { self.dispatch_write_callback(callback); } @@ -8002,7 +8013,7 @@ impl H2FrameParser { &mut encoded_headers, validated_name, value, - never_index, + NeverIndex::from_bool(never_index), ) { Ok(_) => Ok(None), Err(crate::Error::Alloc(bun_alloc::AllocError)) => { @@ -8027,7 +8038,7 @@ impl H2FrameParser { ErrorCode::NO_ERROR, b"", this.last_stream_id.get(), - true, + EmitError::Yes, ); Ok(Some(JSValue::UNDEFINED)) } @@ -8297,7 +8308,7 @@ impl H2FrameParser { // the write callback was not (and will not be) invoked by the engine; the JS caller // completes the Writable callback asynchronously. let mut result = settled_state as u32; - if callback_deferred { + if callback_deferred == CallbackDeferred::Yes { result |= WRITE_FLUSHED_WITHOUT_CALLBACK; } Ok(JSValue::js_number(result as f64)) @@ -8307,7 +8318,7 @@ impl H2FrameParser { /// saturates; callers that open the stream reject anything above `MAX_STREAM_ID`. fn get_next_stream_id(&self) -> u32 { let stream_id = self.last_stream_id.get(); - if self.is_server.get() { + if self.is_server() { if stream_id.is_multiple_of(2) { stream_id.saturating_add(2) } else { @@ -8334,7 +8345,7 @@ impl H2FrameParser { // `id <= 0` check and truncates to 0 here; 0 (and 1 on a client) has no predecessor, // so the subtraction saturates to the initial state instead of wrapping. let next_stream_id = stream_id_arg.to_u32(); - let last_stream_id = if this.is_server.get() { + let last_stream_id = if this.is_server() { if next_stream_id.is_multiple_of(2) { next_stream_id.saturating_sub(2) } else { @@ -8386,7 +8397,7 @@ impl H2FrameParser { global_object: &JSGlobalObject, callframe: &CallFrame, ) -> JsResult { - if !this.is_server.get() { + if !this.is_server() { return Err( global_object.throw(format_args!("Push streams can only be created by servers")) ); @@ -8503,7 +8514,7 @@ impl H2FrameParser { &mut encoded_headers, validated_name, value, - never_index, + NeverIndex::from_bool(never_index), ) .is_err() { @@ -8749,7 +8760,7 @@ impl H2FrameParser { // the lifetime of the entry. Separate heap allocation from `this`, so no aliasing. let stream = unsafe { &mut *stream_ptr }; // this is the oposite logic of emitErrorToallStreams, in this case we wanna to cancel this streams - if this.is_server.get() { + if this.is_server() { if stream.id % 2 == 0 { continue; } @@ -8910,7 +8921,7 @@ impl H2FrameParser { continue; } - if this.is_server.get() { + if this.is_server() { if !is_valid_response_pseudo_header(validated_name) { if !global_object.has_exception() { return Err(global_object.err(JscErrorCode::HTTP2_INVALID_PSEUDOHEADER, format_args!("\"{}\" is an invalid pseudoheader or is used incorrectly", BStr::new(name))).throw()); @@ -8976,7 +8987,7 @@ impl H2FrameParser { &mut encoded_headers, validated_name, value, - never_index, + NeverIndex::from_bool(never_index), ) { if matches!(err, crate::Error::Alloc(_)) { return Err(global_object @@ -9043,7 +9054,7 @@ impl H2FrameParser { continue; } - if this.is_server.get() { + if this.is_server() { if !is_valid_response_pseudo_header(validated_name) { if !global_object.has_exception() { return Err(global_object.err(JscErrorCode::HTTP2_INVALID_PSEUDOHEADER, format_args!("\"{}\" is an invalid pseudoheader or is used incorrectly", BStr::new(name))).throw()); @@ -9143,7 +9154,7 @@ impl H2FrameParser { &mut encoded_headers, validated_name, value, - never_index, + NeverIndex::from_bool(never_index), ) { if matches!(err, crate::Error::Alloc(_)) { return Err(global_object @@ -9213,7 +9224,7 @@ impl H2FrameParser { &mut encoded_headers, validated_name, value, - never_index, + NeverIndex::from_bool(never_index), ) { if matches!(err, crate::Error::Alloc(_)) { return Err(global_object @@ -9300,7 +9311,7 @@ impl H2FrameParser { if end_stream_js.as_boolean() { end_stream = true; // will end the stream after trailers - if !wait_for_trailers || this.is_server.get() { + if !wait_for_trailers || this.is_server() { flags |= HeadersFrameFlags::END_STREAM as u8; } } @@ -9881,7 +9892,7 @@ impl H2FrameParser { last_stream_id: Cell::new(0), last_peer_stream_id: Cell::new(0), expecting_continuation: Cell::new(0), - is_server: Cell::new(false), + side: Cell::new(Side::Client), left_exception: Cell::new(false), preface_received_len: Cell::new(0), write_buffer: JsCell::new(Vec::::default()), @@ -10059,7 +10070,7 @@ impl H2FrameParser { is_server = type_js.is_number() && type_js.to_u32() == 0; } - this_ref.is_server.set(is_server); + this_ref.side.set(Side::from_bool(is_server)); JSH2FrameParser::Gc::context.set(this_value, global_object, context_obj); this_ref diff --git a/src/runtime/api/bun/js_bun_spawn_bindings.rs b/src/runtime/api/bun/js_bun_spawn_bindings.rs index 8c6e65c3868c..4b3f52fdddb8 100644 --- a/src/runtime/api/bun/js_bun_spawn_bindings.rs +++ b/src/runtime/api/bun/js_bun_spawn_bindings.rs @@ -28,6 +28,7 @@ use crate::api::bun_process::SpawnResultExt as _; use crate::api::bun_process::{self as spawn, CStrPtr, Process, Rusage, SpawnOptions}; // User-facing JS `Stdio` enum (extract/as_spawn_option/is_piped). use crate::api::bun_spawn::stdio::{self, Stdio}; +use crate::api::bun_subprocess::subprocess_pipe_reader::LazyStart; use crate::api::bun_subprocess::{ self as Subprocess, Readable, Subprocess as SubprocessT, Writable, }; @@ -1576,7 +1577,7 @@ fn spawn_maybe_sync( core::mem::size_of::<*mut IPC::SendQueue>() as core::ffi::c_int, posix_ipc_fd.native(), 0, - true, + bun_uws::Ipc::Yes, ); if !raw_socket.is_null() { let socket = raw_socket; @@ -1759,7 +1760,7 @@ fn spawn_maybe_sync( } else { // process has already exited, but we haven't called wait4() yet // https://cs.github.com/libuv/libuv/blob/b00d1bd225b602570baee82a6152eaa823a84fa6/src/unix/process.c#L1007 - proc.wait(IS_SYNC); + proc.wait(bun_spawn::WaitMode::from_bool(IS_SYNC)); } } } @@ -1771,7 +1772,11 @@ fn spawn_maybe_sync( // Note: pass `subprocess_nn` (the `NonNull>` // captured above) instead of the live `&mut subprocess`, which would // alias with the `&mut subprocess.stdout` borrow held by `pipe`. - Readable::pipe_reader_mut(pipe).start(subprocess_nn, event_loop_nn, !IS_SYNC && lazy); + Readable::pipe_reader_mut(pipe).start( + subprocess_nn, + event_loop_nn, + LazyStart::from_bool(!IS_SYNC && lazy), + ); if (IS_SYNC || !lazy) && matches!(subprocess.stdout.get(), Readable::Pipe(_)) { if let Readable::Pipe(pipe) = subprocess.stdout.get() { Readable::pipe_reader_mut(pipe).read_all(); @@ -1781,7 +1786,11 @@ fn spawn_maybe_sync( if let Readable::Pipe(pipe) = subprocess.stderr.get() { // Note: see stdout arm above — avoid aliased &mut. - Readable::pipe_reader_mut(pipe).start(subprocess_nn, event_loop_nn, !IS_SYNC && lazy); + Readable::pipe_reader_mut(pipe).start( + subprocess_nn, + event_loop_nn, + LazyStart::from_bool(!IS_SYNC && lazy), + ); if (IS_SYNC || !lazy) && matches!(subprocess.stderr.get(), Readable::Pipe(_)) { if let Readable::Pipe(pipe) = subprocess.stderr.get() { @@ -1860,7 +1869,7 @@ fn spawn_maybe_sync( .counters .mark(jsc::counters::Field::SpawnSyncBlocking); let debug_timer = Output::DebugTimer::start(); - subprocess.process_mut().wait(true); + subprocess.process_mut().wait(bun_spawn::WaitMode::Blocking); bun_output::scoped_log!(Subprocess, "spawnSync fast path took {}", debug_timer); // watchOrReap will handle the already exited case for us. @@ -1882,7 +1891,7 @@ fn spawn_maybe_sync( } } sys::Result::Err(_) => { - subprocess.process_mut().wait(true); + subprocess.process_mut().wait(bun_spawn::WaitMode::Blocking); } } @@ -2192,7 +2201,11 @@ fn append_envp_from_js( let line_bytes = line.as_bytes(); let key_end = strings::index_of_char_usize(line_bytes, b'=').unwrap_or(line_bytes.len()); let is_path_key = if cfg!(windows) { - strings::eql_case_insensitive_ascii(&line_bytes[..key_end], b"PATH", true) + strings::eql_case_insensitive_ascii( + &line_bytes[..key_end], + b"PATH", + strings::CheckLen::Yes, + ) } else { &line_bytes[..key_end] == b"PATH" }; diff --git a/src/runtime/api/bun/subprocess/SubprocessPipeReader.rs b/src/runtime/api/bun/subprocess/SubprocessPipeReader.rs index a5863b92fe43..f38efb143f5d 100644 --- a/src/runtime/api/bun/subprocess/SubprocessPipeReader.rs +++ b/src/runtime/api/bun/subprocess/SubprocessPipeReader.rs @@ -19,6 +19,11 @@ use super::{StdioKind, StdioResult, Subprocess}; pub type IOReader = BufferedReader; +bun_core::bool_enum!( + /// Defer poll/`uv_read_start` until JS first pulls, so the kernel pipe buffer backpressures the child. + pub(crate) LazyStart +); + #[derive(Default)] pub enum State { #[default] @@ -148,8 +153,9 @@ impl PipeReader { &mut self, process: NonNull>, event_loop: NonNull, - lazy: bool, + lazy: LazyStart, ) { + let lazy = lazy == LazyStart::Yes; self.r#ref(); self.process = Some(ParentRef::from(process)); self.event_loop = event_loop.into(); @@ -206,7 +212,9 @@ impl PipeReader { // outlives the guard's drop on return. let _keepalive = unsafe { ScopedRef::new(std::ptr::from_mut::(self)) }; - let _ = self.reader.start(self.stdio_result.unwrap(), true); + let _ = self + .reader + .start(self.stdio_result.unwrap(), bun_io::IsPollable::Yes); #[cfg(unix)] { diff --git a/src/runtime/api/bun/subprocess/Writable.rs b/src/runtime/api/bun/subprocess/Writable.rs index 28df158872b8..2dc4f2c81670 100644 --- a/src/runtime/api/bun/subprocess/Writable.rs +++ b/src/runtime/api/bun/subprocess/Writable.rs @@ -302,7 +302,10 @@ impl<'a> Writable<'a> { .expect("FileSink::create returns non-null"); let pipe = Self::pipe_sink_mut(&pipe_nn); - match pipe.writer.with_mut(|w| w.start(pipe.fd.get(), true)) { + match pipe + .writer + .with_mut(|w| w.start(pipe.fd.get(), bun_io::IsPollable::Yes)) + { bun_sys::Result::Ok(()) => {} bun_sys::Result::Err(_err) => { Self::pipe_release(pipe_nn); diff --git a/src/runtime/api/cron.rs b/src/runtime/api/cron.rs index ea9ede4d3ad9..cbb330c1bf94 100644 --- a/src/runtime/api/cron.rs +++ b/src/runtime/api/cron.rs @@ -2431,7 +2431,11 @@ unsafe fn spawn_cmd_prepare( | PosixFlags::CLOSED_WITHOUT_REPORTING, ); } - if s!().stdout_reader().start(stdout, true).is_err() { + if s!() + .stdout_reader() + .start(stdout, bun_io::IsPollable::Yes) + .is_err() + { s!().set_err(format_args!("Failed to start reading stdout")); return Err(()); } diff --git a/src/runtime/api/filesystem_router.rs b/src/runtime/api/filesystem_router.rs index 7a7f6dc45147..b08ac124dc11 100644 --- a/src/runtime/api/filesystem_router.rs +++ b/src/runtime/api/filesystem_router.rs @@ -410,7 +410,7 @@ impl FileSystemRouter { // SAFETY: `entry_ptr` is a live `*mut Entry` in the process-static // EntryStore (checked non-null above); the lazy-stat rewrite is // serialized on `Entry.mutex`; fs_impl is the process-global RealFS. - unsafe { (&*entry_ptr).kind(fs_impl, false) } + unsafe { (&*entry_ptr).kind(fs_impl, Fs::StoreFd::No) } }; if kind == Fs::EntryKind::Dir { for banned_dir in Router::BANNED_DIRS.iter() { diff --git a/src/runtime/api/glob.rs b/src/runtime/api/glob.rs index d8404a64c412..25316a378606 100644 --- a/src/runtime/api/glob.rs +++ b/src/runtime/api/glob.rs @@ -3,6 +3,7 @@ use core::sync::atomic::{AtomicUsize, Ordering}; use bun_alloc::Arena; use bun_core::String as BunString; use bun_glob::BunGlobWalker as GlobWalker; +use bun_glob::{Absolute, Dot, ErrorOnBrokenSymlinks, FollowSymlinks, OnlyFiles}; use bun_jsc::bun_string_jsc; use bun_jsc::{ ArgumentsSlice, CallFrame, JSGlobalObject, JSPromiseStrong, JSValue, Job, JobContext, JsPtr, @@ -34,7 +35,7 @@ impl ScanOpts { global_this: &JSGlobalObject, _arena: &Arena, cwd_val: JSValue, - absolute: bool, + absolute: Absolute, fn_name: &'static str, ) -> JsResult> { let cwd_string = bun_core::OwnedString::new(BunString::from_js(cwd_val, global_this)?); @@ -60,7 +61,7 @@ impl ScanOpts { // `cwd_utf8` drops at scope exit. let mut path_buf2 = [0u8; MAX_PATH_BYTES * 2]; - if !absolute { + if absolute == Absolute::No { let parts: &[&[u8]] = &[cwd_utf8.slice()]; let cwd_str = join_string_buf::(&mut path_buf2, parts); break 'cwd_str Box::<[u8]>::from(cwd_str); @@ -116,8 +117,13 @@ impl ScanOpts { if !opts_obj.is_object() { if opts_obj.is_string() { { - let result = - Self::parse_cwd(global_this, arena, opts_obj, out.absolute, fn_name)?; + let result = Self::parse_cwd( + global_this, + arena, + opts_obj, + Absolute::from_bool(out.absolute), + fn_name, + )?; if !result.is_empty() { out.cwd = Some(result); } @@ -172,7 +178,13 @@ impl ScanOpts { } { - let result = Self::parse_cwd(global_this, arena, cwd_val, out.absolute, fn_name)?; + let result = Self::parse_cwd( + global_this, + arena, + cwd_val, + Absolute::from_bool(out.absolute), + fn_name, + )?; if !result.is_empty() { out.cwd = Some(result); } @@ -305,11 +317,12 @@ impl Glob { return Ok(None); }; let cwd = match_opts.cwd; - let dot = match_opts.dot; - let absolute = match_opts.absolute; - let follow_symlinks = match_opts.follow_symlinks; - let error_on_broken_symlinks = match_opts.error_on_broken_symlinks; - let only_files = match_opts.only_files; + let dot = Dot::from_bool(match_opts.dot); + let absolute = Absolute::from_bool(match_opts.absolute); + let follow_symlinks = FollowSymlinks::from_bool(match_opts.follow_symlinks); + let error_on_broken_symlinks = + ErrorOnBrokenSymlinks::from_bool(match_opts.error_on_broken_symlinks); + let only_files = OnlyFiles::from_bool(match_opts.only_files); let _ = arena; // arena ownership is no longer threaded through GlobWalker init. diff --git a/src/runtime/api/html_rewriter.rs b/src/runtime/api/html_rewriter.rs index d1d6b09a0788..c021883a6cb3 100644 --- a/src/runtime/api/html_rewriter.rs +++ b/src/runtime/api/html_rewriter.rs @@ -470,7 +470,7 @@ impl HTMLRewriter { }, body_value, BunString::empty(), - false, + webcore::response::Redirected::No, )), Response::finalize, ); @@ -787,6 +787,8 @@ pub struct RewriterPipe { pump_controller_attached: Cell, } +bun_core::bool_enum!(CancelUpstream); + impl RewriterPipe { /// How far the output may run ahead of its reader before the input is /// held (or, unobserved, before the turn is yielded). The same distance @@ -882,11 +884,11 @@ impl RewriterPipe { /// after the first call. Returns the severed handle for /// [`Self::release_input_roots`], which the caller runs after its /// terminal work. - fn detach_input_source(&self, cancel_upstream: bool) -> SourceHandle { + fn detach_input_source(&self, cancel_upstream: CancelUpstream) -> SourceHandle { let mut src = self.input_source.replace(SourceHandle::None); let mut upstream = src; JSSink::::detach(&mut src, &self.global); - if cancel_upstream { + if cancel_upstream == CancelUpstream::Yes { match upstream { SourceHandle::ByteStream(_) | SourceHandle::FileReader(_) => { upstream.close(None); @@ -1062,7 +1064,7 @@ impl RewriterPipe { webcore::body::Value::Locked(pv) }), BunString::empty(), - false, + webcore::response::Redirected::No, )); let result_ref = BackRef::from(result); this.response.set(Some(result_ref)); @@ -1446,7 +1448,7 @@ impl RewriterPipe { // pipe; otherwise the controller's destructor would later dispatch // `__controllerDetached`/`__finalize` on freed memory. The upstream // already ended, so there is nothing to cancel. - let src = self.detach_input_source(false); + let src = self.detach_input_source(CancelUpstream::No); if self.js_pump_reaction_pending.get() { // The pump-promise `.then()` reaction is the single terminal @@ -1495,7 +1497,7 @@ impl RewriterPipe { pub fn cancel_from_output(&self, _err: Option) { let _pin = self.pin(); self.detach_output(); - let src = self.detach_input_source(true); + let src = self.detach_input_source(CancelUpstream::Yes); self.phase.set(RewritePhase::Done); self.done.set(true); self.pending.with_mut(|p| { @@ -1748,7 +1750,7 @@ impl RewriterPipe { let _pin = self.pin(); self.phase.set(RewritePhase::Done); self.done.set(true); - let src = self.detach_input_source(true); + let src = self.detach_input_source(CancelUpstream::Yes); // Settle any `flush(true)`/`write()` promise a direct-stream `pull()` // is parked on so the pump promise can settle (mirrors // `cancel_from_output`). diff --git a/src/runtime/api/js_bundle_completion_task.rs b/src/runtime/api/js_bundle_completion_task.rs index b5099cad4fff..d79360ac8e8e 100644 --- a/src/runtime/api/js_bundle_completion_task.rs +++ b/src/runtime/api/js_bundle_completion_task.rs @@ -14,7 +14,7 @@ use std::io::Write as _; use bun_alloc::Arena; use bun_bundler::bundle_v2::{ - BundleV2, BundleV2Result, CompletionStruct, FileMap as Bv2FileMap, + BundleV2, BundleV2Result, CliWatchFlag, CompletionStruct, FileMap as Bv2FileMap, JSBundleCompletionTask as Bv2OpaqueCompletion, JSBundlerPlugin, dispatch, }; use bun_bundler::options::{self, OutputFile, OutputKind, Side}; @@ -517,7 +517,10 @@ impl JSBundleCompletionTask { flag: FileSystemFlags::W, mode: node_fs::DEFAULT_PERMISSION, file: PathOrFileDescriptor::Path(PathLike::String( - bun_ptr::cow_slice::CowSlice::init_unchecked(write_path, false), + bun_ptr::cow_slice::CowSlice::init_unchecked( + write_path, + bun_ptr::cow_slice::Ownership::Borrowed, + ), )), flush: false, data: StringOrBuffer::EncodedSlice( @@ -1232,7 +1235,15 @@ impl CompletionStruct for JSBundleCompletionTask { let worker_pool = NonNull::new(thread_pool); // `Graph.heap` is a borrow, so reuse the caller-owned `bump`. - let mut bv2 = BundleV2::init(transpiler, None, bump, event_loop, false, worker_pool, bump)?; + let mut bv2 = BundleV2::init( + transpiler, + None, + bump, + event_loop, + CliWatchFlag::No, + worker_pool, + bump, + )?; bv2.plugins = self.plugins(); bv2.completion = Some(self.as_js_bundle_completion_task()); diff --git a/src/runtime/bake/DevServer.rs b/src/runtime/bake/DevServer.rs index ac4a40151a96..08c2ef1070ff 100644 --- a/src/runtime/bake/DevServer.rs +++ b/src/runtime/bake/DevServer.rs @@ -25,7 +25,9 @@ use bun_jsc::virtual_machine::VirtualMachine; use bun_jsc::{self as jsc, CallFrame, JSGlobalObject, JSValue, JsResult}; use bun_paths::{self as paths, PathBuffer}; use bun_sys as sys; -use bun_uws::{self as uws, AnyResponse, Opcode, Request, WebSocketUpgradeContext}; +use bun_uws::{ + self as uws, AnyResponse, CloseConnection, Opcode, Request, WebSocketUpgradeContext, +}; use bun_watcher::WatchItemColumns as _; use bun_wyhash::{Wyhash, hash}; @@ -40,7 +42,7 @@ use bun_ast::Loader; use bun_bundler::{self as bundler, BundleV2, Transpiler}; use bun_http::{Method, MimeType}; use bun_safety::ThreadLock; -use bun_watcher::Watcher; +use bun_watcher::{CloseDescriptors, Watcher}; pub(super) use crate::bake::dev_server::DirectoryWatchStore; pub(super) use crate::bake::dev_server::HmrSocket; @@ -72,6 +74,7 @@ impl LogToJsAggregateErrorExt for Log { } pub(super) use crate::bake::dev_server::HotReloadEvent; pub(super) use crate::bake::dev_server::incremental_graph::IncrementalGraph; +use crate::bake::dev_server::incremental_graph::{NewFilesStale, SsrGraph}; pub(super) use crate::bake::dev_server::memory_cost::MemoryCost; impl DevServer { @@ -990,7 +993,7 @@ pub(crate) fn init(options: Options) -> JsResult> { server_file, RouteIndexAndRecurseFlag::new( framework_router::RouteIndex::init(u32::try_from(i).expect("int cast")), - true, + RecurseWhenVisiting::Yes, ), )?; } @@ -1107,7 +1110,7 @@ impl Drop for DevServer { let watcher = unsafe { ::core::mem::ManuallyDrop::take(&mut self.bun_watcher) }; // SAFETY: `Box::into_raw` yields the unique heap pointer; ownership // transfers to `shutdown`, which reclaims or hands off to the thread. - unsafe { Watcher::shutdown(Box::into_raw(watcher), true) }; + unsafe { Watcher::shutdown(Box::into_raw(watcher), CloseDescriptors::Yes) }; // The map's `Drop` runs `SerializedFailure::drop` for each value. @@ -1277,8 +1280,10 @@ impl DevServer { )?; } - self.server_graph.ensure_stale_bit_capacity(true)?; - self.client_graph.ensure_stale_bit_capacity(true)?; + self.server_graph + .ensure_stale_bit_capacity(NewFilesStale::Yes)?; + self.client_graph + .ensure_stale_bit_capacity(NewFilesStale::Yes)?; Ok(()) } @@ -1385,7 +1390,7 @@ pub(crate) fn is_allowed_host_header( return false; }; let host = host_without_port(host); - if strings::eql_case_insensitive_ascii(host, b"localhost", true) { + if strings::eql_case_insensitive_ascii(host, b"localhost", strings::CheckLen::Yes) { return true; } const DOT_LOCALHOST: &[u8] = b".localhost"; @@ -1393,7 +1398,7 @@ pub(crate) fn is_allowed_host_header( && strings::eql_case_insensitive_ascii( &host[host.len() - DOT_LOCALHOST.len()..], DOT_LOCALHOST, - true, + strings::CheckLen::Yes, ) { return true; @@ -1410,7 +1415,7 @@ pub(crate) fn is_allowed_host_header( hostname: Some(h), .. }) = address { - return strings::eql_case_insensitive_ascii(host, h.as_bytes(), true); + return strings::eql_case_insensitive_ascii(host, h.as_bytes(), strings::CheckLen::Yes); } false } @@ -1455,7 +1460,7 @@ fn is_allowed_dev_origin(req: &Request) -> bool { return false; }; let origin_host = host_without_port(&origin[scheme_end + 3..]); - if strings::eql_case_insensitive_ascii(origin_host, b"localhost", true) { + if strings::eql_case_insensitive_ascii(origin_host, b"localhost", strings::CheckLen::Yes) { return true; } const DOT_LOCALHOST: &[u8] = b".localhost"; @@ -1463,15 +1468,17 @@ fn is_allowed_dev_origin(req: &Request) -> bool { && strings::eql_case_insensitive_ascii( &origin_host[origin_host.len() - DOT_LOCALHOST.len()..], DOT_LOCALHOST, - true, + strings::CheckLen::Yes, ) { return true; } match req.header(b"host") { - Some(host) => { - strings::eql_case_insensitive_ascii(origin_host, host_without_port(host), true) - } + Some(host) => strings::eql_case_insensitive_ascii( + origin_host, + host_without_port(host), + strings::CheckLen::Yes, + ), None => false, } } @@ -1479,7 +1486,10 @@ fn is_allowed_dev_origin(req: &Request) -> bool { fn host_forbidden(resp: AnyResponse) { resp.corked(move || { resp.write_status(b"403 Forbidden"); - resp.end(b"Blocked: Host header does not match the dev server", false); + resp.end( + b"Blocked: Host header does not match the dev server", + CloseConnection::No, + ); }); } @@ -1488,7 +1498,7 @@ fn origin_forbidden(resp: AnyResponse) { resp.write_status(b"403 Forbidden"); resp.end( b"Blocked: Origin header does not match the dev server", - false, + CloseConnection::No, ); }); } @@ -1674,7 +1684,7 @@ impl ResponseLike for bun_uws_sys::response::Response { fn write_status(&mut self, status: &[u8]) { bun_uws_sys::response::Response::::write_status(self, status) } - fn end(&mut self, data: &[u8], close_connection: bool) { + fn end(&mut self, data: &[u8], close_connection: CloseConnection) { bun_uws_sys::response::Response::::end(self, data, close_connection) } fn as_any_response(&mut self) -> bun_uws::AnyResponse { @@ -1714,7 +1724,7 @@ fn not_found(resp: AnyResponse) { fn on_not_found_corked(resp: AnyResponse) { resp.write_status(b"404 Not Found"); - resp.end(b"Not Found", false); + resp.end(b"Not Found", CloseConnection::No); } fn on_outdated_js_corked(resp: AnyResponse) { @@ -1726,7 +1736,7 @@ fn on_outdated_js_corked(resp: AnyResponse) { resp.end( b"try{location.reload()}catch(_){}\n\ addEventListener(\"DOMContentLoaded\",function(event){location.reload()})", - false, + CloseConnection::No, ); } @@ -1826,14 +1836,14 @@ fn on_src_request(_dev: &mut DevServer, req: &mut Request, resp: AnyResponse) { resp.write_status(b"501 Not Implemented"); resp.end( b"Viewing source without opening in editor is not implemented yet!", - false, + CloseConnection::No, ); return; } // TODO: better editor detection. on chloe's dev env, this opens apple terminal + vim resp.write_status(b"501 Not Implemented"); - resp.end(b"TODO", false); + resp.end(b"TODO", CloseConnection::No); } struct RequestEnsureRouteBundledCtx { @@ -1915,7 +1925,7 @@ impl RequestEnsureRouteBundledCtx { } fn on_plugin_error(&mut self) -> JsResult<()> { - self.resp.end(b"Plugin Error", false); + self.resp.end(b"Plugin Error", CloseConnection::No); Ok(()) } @@ -2067,7 +2077,7 @@ fn ensure_route_is_bundled( dev.route_bundle_ptr(route_bundle_index).server_state = route_bundle::State::Bundling; - dev.start_async_bundle(entry_points, false, Instant::now()) + dev.start_async_bundle(entry_points, HadReloadEvent::No, Instant::now()) .expect("oom"); return Ok(()); } @@ -2395,6 +2405,8 @@ struct FrameworkRequestArgs { set_async_local_storage: JSValue, } +bun_core::bool_enum!(pub(crate) FirstRequest); + impl DevServer { /// Note: raw-pointer receiver. A previous version of this body bound /// long-lived `&mut RouteBundle` / `&mut Type` from @@ -2414,8 +2426,9 @@ impl DevServer { route_bundle_index: route_bundle::Index, framework_bundle: &mut route_bundle::Framework, params_js_value: JSValue, - first_request: bool, + first_request: FirstRequest, ) -> JsResult { + let first_request = first_request == FirstRequest::Yes; // SAFETY: `this` is live; `vm` is a `BackRef` (safe Deref); `vm.global` // is valid for VM lifetime. let global = unsafe { &*(&(*this).vm).global }; @@ -2658,7 +2671,7 @@ impl DevServer { route_bundle_index, framework_bundle, params_js_value, - true, + FirstRequest::Yes, ) }?; @@ -3123,7 +3136,7 @@ impl DeferredRequest { // client has framing. r.response.write_status(b"500 Internal Server Error"); r.response.write_header_int(b"Content-Length", 0); - r.response.end_without_body(true); + r.response.end_without_body(CloseConnection::Yes); } Handler::Aborted => {} } @@ -3136,11 +3149,13 @@ pub struct ResponseAndMethod { pub method: Method, } +bun_core::bool_enum!(pub(crate) HadReloadEvent); + impl DevServer { pub(crate) fn start_async_bundle( &mut self, entry_points: EntryPointList, - had_reload_event: bool, + had_reload_event: HadReloadEvent, timer: Instant, ) -> crate::Result<()> { debug_assert!(self.current_bundle.is_none()); @@ -3230,7 +3245,7 @@ impl DevServer { // SAFETY: see `heap_ptr` note above. unsafe { &*heap_ptr }, event_loop, - false, // watching is handled separately + bundler::bundle_v2::CliWatchFlag::No, // watching is handled separately Some(::core::ptr::NonNull::from( bun_threading::work_pool::WorkPool::get(), )), @@ -3277,7 +3292,7 @@ impl DevServer { ast_alloc_state, timer, start_data, - had_reload_event, + had_reload_event: had_reload_event == HadReloadEvent::Yes, requests: ::core::mem::take(&mut self.next_bundle.requests), promise: ::core::mem::take(&mut self.next_bundle.promise), resolution_failure_entries: Default::default(), @@ -3310,12 +3325,12 @@ impl DevServer { bake::Side::Client => self.client_graph.insert_failure( incremental_graph::InsertFailureKey::Index(index), log, - false, + SsrGraph::No, )?, bake::Side::Server => self.server_graph.insert_failure( incremental_graph::InsertFailureKey::Index(index), log, - true, + SsrGraph::Yes, )?, } } @@ -3978,7 +3993,7 @@ pub(super) fn finalize_bundle( .map(|v| v.as_slice().to_vec().into_boxed_slice()), }), }, - false, + SsrGraph::No, )?, graph @ (bake::Graph::Server | bake::Graph::Ssr) => dev.server_graph.receive_chunk( &mut ctx, @@ -3996,7 +4011,7 @@ pub(super) fn finalize_bundle( .map(|v| v.as_slice().to_vec().into_boxed_slice()), }), }, - graph == bake::Graph::Ssr, + SsrGraph::from_bool(graph == bake::Graph::Ssr), )?, } } @@ -4069,7 +4084,7 @@ pub(super) fn finalize_bundle( &mut ctx, index, incremental_graph::ReceiveChunkContent::Css(h), - false, + SsrGraph::No, )?; // If imported on server, there needs to be a server-side file entry @@ -4104,7 +4119,7 @@ pub(super) fn finalize_bundle( code: generated_js, source_map: None, }, - false, + SsrGraph::No, )?; let client_index = ctx .get_cached_index(bake::Side::Client, index) @@ -4196,8 +4211,10 @@ pub(super) fn finalize_bundle( } dev.index_failures()?; - dev.client_graph.ensure_stale_bit_capacity(false)?; - dev.server_graph.ensure_stale_bit_capacity(false)?; + dev.client_graph + .ensure_stale_bit_capacity(NewFilesStale::No)?; + dev.server_graph + .ensure_stale_bit_capacity(NewFilesStale::No)?; dev.generation = dev.generation.wrapping_add(1); if Environment::ENABLE_LOGS { @@ -4917,10 +4934,10 @@ impl DevServer { debug_assert!(unsafe { (*current).debug_mutex.try_lock() }); } - break 'brk (true, reload_event_timer); + break 'brk (HadReloadEvent::Yes, reload_event_timer); } } else { - (false, Instant::now()) + (HadReloadEvent::No, Instant::now()) }; // Note: iterate by index — `route_bundle_ptr` / @@ -4975,17 +4992,17 @@ impl DevServer { bake::Graph::Server => self.server_graph.insert_failure( incremental_graph::InsertFailureKey::AbsPath(abs_path), log, - false, + SsrGraph::No, )?, bake::Graph::Ssr => self.server_graph.insert_failure( incremental_graph::InsertFailureKey::AbsPath(abs_path), log, - true, + SsrGraph::Yes, )?, bake::Graph::Client => self.client_graph.insert_failure( incremental_graph::InsertFailureKey::AbsPath(abs_path), log, - false, + SsrGraph::No, )?, } } @@ -5375,7 +5392,7 @@ impl DevServer { any_blob.to_blob(global), )), BunString::empty(), - false, + crate::webcore::response::Redirected::No, ); let vm = self.vm(); let _exit = vm.enter_event_loop_scope(); @@ -5389,7 +5406,7 @@ impl DevServer { fn send_built_in_not_found(resp: &mut R) { let message = b"404 Not Found"; resp.write_status(b"404 Not Found"); - resp.end(message, true); + resp.end(message, CloseConnection::Yes); } impl DevServer { @@ -5808,7 +5825,7 @@ impl DevServer { pub(crate) fn publish(&self, topic: HmrTopic, message: &[u8], opcode: Opcode) { if let Some(s) = &self.server { - let _ = s.publish(&topic.uws_topic(), message, opcode, false); + let _ = s.publish(&topic.uws_topic(), message, opcode, uws::Compress::No); } } @@ -5860,7 +5877,7 @@ impl DevServer { index, RouteIndexAndRecurseFlag::new( associated_route, - file_kind == framework_router::FileKind::Layout, + RecurseWhenVisiting::from_bool(file_kind == framework_router::FileKind::Layout), ), )?; Ok(to_opaque_file_id::<{ bake::Side::Server }>(index)) @@ -5997,14 +6014,17 @@ impl DevServer { } } +bun_core::bool_enum!(pub(crate) RecurseWhenVisiting); + #[repr(transparent)] #[derive(Copy, Clone)] pub struct RouteIndexAndRecurseFlag(pub u32); impl RouteIndexAndRecurseFlag { pub(crate) fn new( route_index: framework_router::RouteIndex, - should_recurse_when_visiting: bool, + should_recurse_when_visiting: RecurseWhenVisiting, ) -> Self { + let should_recurse_when_visiting = should_recurse_when_visiting == RecurseWhenVisiting::Yes; RouteIndexAndRecurseFlag( (route_index.get() & 0x7FFF_FFFF) | ((should_recurse_when_visiting as u32) << 31), ) @@ -6238,7 +6258,7 @@ impl UnrefSourceMapRequest { source_map_store::RemoveOrUpgradeMode::Remove, ); r.write_status(b"204 No Content"); - r.end(b"", false); + r.end(b"", CloseConnection::No); // SAFETY: ctx is the original heap-allocated pointer; the only borrow // derived from it points into a separate DevServer allocation and has // ended. @@ -6657,7 +6677,7 @@ fn new_route_params_for_bundle_promise( route_bundle_index, &mut *framework_bundle, params_js_value, - false, + FirstRequest::No, ) }?; diff --git a/src/runtime/bake/FrameworkRouter.rs b/src/runtime/bake/FrameworkRouter.rs index aa6522009ff1..6a7a596e6ad0 100644 --- a/src/runtime/bake/FrameworkRouter.rs +++ b/src/runtime/bake/FrameworkRouter.rs @@ -1542,7 +1542,8 @@ impl FrameworkRouter { // SAFETY: `Entry::kind` mutates only the entry's lazily-cached kind; `file_ptr` // is the unique live reference to this entry during the scan, and `fs_impl` // points at the process-global FS implementation. - match unsafe { (*file_ptr).kind(&raw mut *fs_impl, false) } { + match unsafe { (*file_ptr).kind(&raw mut *fs_impl, bun_resolver::fs::StoreFd::No) } + { bun_resolver::fs::EntryKind::Dir => { let t = &self.types[t_index.get() as usize]; if t.ignore_underscores && base.starts_with(b"_") { @@ -1550,7 +1551,7 @@ impl FrameworkRouter { } for banned_dir in t.ignore_dirs.iter() { - if strings::eql_long(base, banned_dir, true) { + if strings::eql_long(base, banned_dir, strings::CheckLen::Yes) { continue 'outer; } } diff --git a/src/runtime/bake/bake_body.rs b/src/runtime/bake/bake_body.rs index d51f80223766..bdb20e79cdd2 100644 --- a/src/runtime/bake/bake_body.rs +++ b/src/runtime/bake/bake_body.rs @@ -19,7 +19,7 @@ use bun_paths::{self as paths, PathBuffer}; // Re-exported from `crate::api::js_bundler` so `SplitBundlerOptions.plugin` // shares the same type the bundler pipeline uses. pub(crate) use crate::api::js_bundler::Plugin; -use crate::api::js_bundler::js_bundler::PluginJscExt as _; +use crate::api::js_bundler::js_bundler::{IsBake, IsLast, PluginJscExt as _}; // Note: parent `mod.rs` already declares `dev_server` / `framework_router` // as sibling modules of this file; pull them in instead of re-declaring (which @@ -362,8 +362,8 @@ impl SplitBundlerOptions { function, empty_object, JSValue::NULL, - false, - true, + IsLast::No, + IsBake::Yes, )?; if let Some(promise) = plugin_result.as_any_promise() { @@ -1199,7 +1199,7 @@ impl Framework { out.options.conditions = bun_bundler::options::ESMConditions::init( out.options.target.default_conditions(), - out.options.target.is_server_side(), + bun_bundler::options::AllowAddons::from_bool(out.options.target.is_server_side()), bundler_options.conditions.keys(), )?; if renderer == Graph::Server && self.server_components.is_some() { @@ -1263,21 +1263,33 @@ impl Framework { bundler_options.define.values.len() ); use bun_bundler::DefineDataExt; + use bun_bundler::defines::{MethodCallMustBeReplacedWithUndefined, Valueless}; for (k, v) in bundler_options .define .keys .iter() .zip(bundler_options.define.values.iter()) { - let parsed = - bun_bundler::defines::DefineData::parse(k, v, false, false, log, arena)?; + let parsed = bun_bundler::defines::DefineData::parse( + k, + v, + Valueless::No, + MethodCallMustBeReplacedWithUndefined::No, + log, + arena, + )?; out.options.define.insert(k, parsed)?; } for drop_item in bundler_options.drop.keys() { if !drop_item.is_empty() { let parsed = bun_bundler::defines::DefineData::parse( - drop_item, b"", true, true, log, arena, + drop_item, + b"", + Valueless::Yes, + MethodCallMustBeReplacedWithUndefined::Yes, + log, + arena, )?; out.options.define.insert(drop_item, parsed)?; } diff --git a/src/runtime/bake/dev_server/error_report_request.rs b/src/runtime/bake/dev_server/error_report_request.rs index 48bd7434f12d..5d678ccabdcc 100644 --- a/src/runtime/bake/dev_server/error_report_request.rs +++ b/src/runtime/bake/dev_server/error_report_request.rs @@ -19,6 +19,7 @@ use bun_alloc::ArenaVecExt as _; use bun_alloc::Arena; // bumpalo::Bump re-export use bun_collections::ArrayHashMap; +use bun_core::output::AnsiColors; use bun_core::{Ordinal, Output}; use bun_core::{String as BunString, strings}; use bun_io::Write as _; @@ -329,8 +330,8 @@ impl ErrorReportRequest { &mut exception, None, stderr, - true, - ansi_colors, + bun_jsc::virtual_machine::AllowSideEffects::Yes, + AnsiColors::from_bool(ansi_colors), ); } diff --git a/src/runtime/bake/dev_server/hmr_socket.rs b/src/runtime/bake/dev_server/hmr_socket.rs index 38ef17a2be0a..44ea718b32c5 100644 --- a/src/runtime/bake/dev_server/hmr_socket.rs +++ b/src/runtime/bake/dev_server/hmr_socket.rs @@ -2,12 +2,12 @@ use bun_collections::HashMap; use bun_core::strings; use bun_core::{Output, feature_flags}; use bun_uws::AnyWebSocket; -use bun_uws_sys::{Opcode, SendStatus}; +use bun_uws_sys::{Compress, Fin, Opcode, SendStatus}; use crate::timer::EventLoopTimerState; use super::source_map_store::{self, RemoveOrUpgradeMode}; -use super::{ConsoleLogKind, DevServer, HmrTopic, IncomingMessageId, MessageId}; +use super::{ConsoleLogKind, DevServer, HadReloadEvent, HmrTopic, IncomingMessageId, MessageId}; use crate::bake::dev_server_body::HmrTopicBits; // Struct definition lives in `dev_server/mod.rs` so the public @@ -50,7 +50,7 @@ impl HmrSocket { let mut header = [0u8; 1 + DevServer::CONFIGURATION_HASH_KEY_LEN]; header[0] = MessageId::Version.char(); header[1..].copy_from_slice(&dev.configuration_hash_key); - let send_status = ws.send(&header, Opcode::Binary, false, true); + let send_status = ws.send(&header, Opcode::Binary, Compress::No, Fin::Yes); self.underlying = Some(ws); if send_status != SendStatus::Dropped { @@ -186,7 +186,7 @@ impl HmrSocket { response[0] = MessageId::SetUrlResponse.char(); response[1..].copy_from_slice(&rbi.get().to_ne_bytes()); - let _ = ws.send(&response, Opcode::Binary, false, true); + let _ = ws.send(&response, Opcode::Binary, Compress::No, Fin::Yes); } x if x == IncomingMessageId::TestingBatchEvents as u8 => { // SAFETY: JS-thread only; sole `&mut DevServer` for this scope. @@ -230,7 +230,7 @@ impl HmrSocket { } let timer = std::time::Instant::now(); - dev.start_async_bundle(event.entry_points, true, timer) + dev.start_async_bundle(event.entry_points, HadReloadEvent::Yes, timer) // bun.handleOom(err) — Rust aborts on OOM by default .expect("OOM"); diff --git a/src/runtime/bake/dev_server/incremental_graph.rs b/src/runtime/bake/dev_server/incremental_graph.rs index 1e0fd98416b2..42d90b77e4c7 100644 --- a/src/runtime/bake/dev_server/incremental_graph.rs +++ b/src/runtime/bake/dev_server/incremental_graph.rs @@ -237,6 +237,9 @@ pub(crate) enum ReceiveChunkContent { Css(u64), } +bun_core::bool_enum!(pub(crate) SsrGraph); +bun_core::bool_enum!(pub(crate) NewFilesStale); + pub struct TakeJSBundleOptionsClient<'a> { pub(crate) kind: ChunkKind, pub(crate) script_id: source_map_store::Key, @@ -433,13 +436,14 @@ impl IncrementalGraph { /// bits with `are_new_files_stale`. pub(crate) fn ensure_stale_bit_capacity( &mut self, - are_new_files_stale: bool, + are_new_files_stale: NewFilesStale, ) -> Result<(), bun_alloc::AllocError> { let want = self.bundled_files.count().max(self.stale_files.bit_length); // Align forward to 8 usize words (8*64 bits). const STEP: usize = core::mem::size_of::() * 8 * 8; let aligned = want.div_ceil(STEP) * STEP; - self.stale_files.resize(aligned, are_new_files_stale) + self.stale_files + .resize(aligned, are_new_files_stale == NewFilesStale::Yes) } /// `IncrementalGraph(side).freeFileContent` (client only). @@ -550,8 +554,9 @@ impl IncrementalGraph { ctx: &mut HotUpdateContext<'_>, index: impl Into, content: ReceiveChunkContent, - is_ssr_graph: bool, + is_ssr_graph: SsrGraph, ) -> Result<(), crate::Error> { + let is_ssr_graph = is_ssr_graph == SsrGraph::Yes; let index: bun_ast::Index = index.into(); // SAFETY: see `owner()`. let dev = unsafe { self.owner() }; @@ -1392,7 +1397,7 @@ impl IncrementalGraph { let key = bun_ptr::RawSlice::new(&**gop.key_ptr); if !found_existing { self.edge_lists.push(EdgeLists::default()); - self.ensure_stale_bit_capacity(true)?; + self.ensure_stale_bit_capacity(NewFilesStale::Yes)?; } Ok(InsertEmptyResult { index: FileIndex::init(idx as u32), @@ -1433,8 +1438,9 @@ impl IncrementalGraph { &mut self, key: InsertFailureKey<'_>, log: &bun_ast::Log, - is_ssr_graph: bool, + is_ssr_graph: SsrGraph, ) -> Result<(), bun_alloc::AllocError> { + let is_ssr_graph = is_ssr_graph == SsrGraph::Yes; let (idx, found_existing) = match key { InsertFailureKey::AbsPath(abs_path) => { let gop = self.bundled_files.get_or_put(abs_path)?; @@ -1449,7 +1455,7 @@ impl IncrementalGraph { } InsertFailureKey::Index(i) => (i as usize, true), }; - self.ensure_stale_bit_capacity(true)?; + self.ensure_stale_bit_capacity(NewFilesStale::Yes)?; self.stale_files.set(idx); match SIDE { diff --git a/src/runtime/bake/dev_server/mod.rs b/src/runtime/bake/dev_server/mod.rs index d5d5373b2803..ce91efb7c7b4 100644 --- a/src/runtime/bake/dev_server/mod.rs +++ b/src/runtime/bake/dev_server/mod.rs @@ -38,6 +38,7 @@ pub(crate) const CLIENT_PREFIX: &str = "/_bun/client"; // blocks and `container_of` submodules name a single type. Re-export so // `crate::bake::dev_server::DevServer` (the public path used by `server/`, // `dispatch.rs`, …) resolves to that one struct. +pub(crate) use super::dev_server_body::HadReloadEvent; pub use super::dev_server_body::{ CacheEntry, CurrentBundle, DeferredPromise, DeferredRequest, DevServer, EntryPointList, HTMLRouter, Magic, NextBundle, Options, PluginState, RouteIndexAndRecurseFlag, TestingBatch, @@ -287,7 +288,7 @@ pub use source_map_store::SourceMapStore; /// grows one, this can be replaced by it. pub trait ResponseLike { fn write_status(&mut self, status: &[u8]); - fn end(&mut self, data: &[u8], close_connection: bool); + fn end(&mut self, data: &[u8], close_connection: bun_uws::CloseConnection); fn as_any_response(&mut self) -> bun_uws::AnyResponse; fn upgrade( &mut self, @@ -307,7 +308,7 @@ impl ResponseLike for bun_uws::AnyResponse { fn write_status(&mut self, status: &[u8]) { (*self).write_status(status) } - fn end(&mut self, data: &[u8], close_connection: bool) { + fn end(&mut self, data: &[u8], close_connection: bun_uws::CloseConnection) { (*self).end(data, close_connection) } fn as_any_response(&mut self) -> bun_uws::AnyResponse { @@ -717,7 +718,7 @@ impl HotReloadEvent { TestingBatchEvents::EnableAfterBundle => debug_assert!(false), } - if let Err(_err) = dev_ref.start_async_bundle(entry_points, true, timer) { + if let Err(_err) = dev_ref.start_async_bundle(entry_points, HadReloadEvent::Yes, timer) { return; } } diff --git a/src/runtime/bake/mod.rs b/src/runtime/bake/mod.rs index 12136842c27d..cd958c4680b7 100644 --- a/src/runtime/bake/mod.rs +++ b/src/runtime/bake/mod.rs @@ -244,7 +244,7 @@ impl Framework { out.options.conditions = bun_bundler::options::ESMConditions::init( out.options.target.default_conditions(), - out.options.target.is_server_side(), + bun_bundler::options::AllowAddons::from_bool(out.options.target.is_server_side()), bundler_options.conditions.keys(), )?; if renderer == Graph::Server && self.server_components.is_some() { @@ -315,21 +315,33 @@ impl Framework { bundler_options.define.values.len() ); use bun_bundler::DefineDataExt; + use bun_bundler::defines::{MethodCallMustBeReplacedWithUndefined, Valueless}; for (k, v) in bundler_options .define .keys .iter() .zip(bundler_options.define.values.iter()) { - let parsed = - bun_bundler::defines::DefineData::parse(k, v, false, false, log, arena)?; + let parsed = bun_bundler::defines::DefineData::parse( + k, + v, + Valueless::No, + MethodCallMustBeReplacedWithUndefined::No, + log, + arena, + )?; out.options.define.insert(k, parsed)?; } for drop_item in bundler_options.drop.keys() { if !drop_item.is_empty() { let parsed = bun_bundler::defines::DefineData::parse( - drop_item, b"", true, true, log, arena, + drop_item, + b"", + Valueless::Yes, + MethodCallMustBeReplacedWithUndefined::Yes, + log, + arena, )?; out.options.define.insert(drop_item, parsed)?; } diff --git a/src/runtime/bake/production.rs b/src/runtime/bake/production.rs index de67f02ea5ee..0c802830e2cf 100644 --- a/src/runtime/bake/production.rs +++ b/src/runtime/bake/production.rs @@ -97,7 +97,7 @@ pub fn build_command(ctx: Context) -> crate::Result<()> { // Create a VM + global for loading the config file, plugins, and // performing build time prerendering. - jsc::initialize(false); + jsc::initialize(jsc::EvalMode::No); bun_ast::initialize_store(); let mut arena = Arena::new(); diff --git a/src/runtime/cli/Arguments.rs b/src/runtime/cli/Arguments.rs index ab0fc39e9b37..5f1b2e9695c8 100644 --- a/src/runtime/cli/Arguments.rs +++ b/src/runtime/cli/Arguments.rs @@ -763,7 +763,9 @@ pub(crate) static Bun__Node__UseSystemCA: core::sync::atomic::AtomicBool = // their private helpers moved to `bun_bunfig::arguments` so `bun_install` can // call them without a tier-6 dependency. Re-export here so existing // `crate::cli::arguments::load_config*` callers are unaffected. -pub use bun_bunfig::arguments::{load_config, load_config_path, load_config_with_cmd_args}; +pub use bun_bunfig::arguments::{ + AutoLoaded, load_config, load_config_path, load_config_with_cmd_args, +}; /// node aliases `-pe` to `--print --eval` as a whole token (node_options.cc): /// it can't be a short in either runtime, being ambiguous with `-p` carrying @@ -799,13 +801,13 @@ pub(crate) fn parse(cmd: CommandTag, ctx: Context<'_>) -> crate::Result { // Report useful error and exit let _ = diag.report(Output::error_writer(), err); - command::tag_print_help(cmd, false); + command::tag_print_help(cmd, crate::cli::ShowAllFlags::No); Global::exit(1); } }; if args.flag(b"--help") { - command::tag_print_help(cmd, true); + command::tag_print_help(cmd, crate::cli::ShowAllFlags::Yes); Output::flush(); Global::exit(0); } diff --git a/src/runtime/cli/audit_command.rs b/src/runtime/cli/audit_command.rs index f23723595e2d..46d2eef7ccca 100644 --- a/src/runtime/cli/audit_command.rs +++ b/src/runtime/cli/audit_command.rs @@ -17,9 +17,9 @@ use bun_libdeflate_sys::libdeflate; use bun_parsers::json as bun_json; use bun_url::URL; -use crate::cli::Command; use crate::cli::install_command::InstallCommand; use crate::cli::package_manager_command::PackageManagerCommand; +use crate::cli::{Command, JsonOutput}; // Boxed to avoid a struct lifetime param; the // clones are per-vulnerability, terminal-UI-bound, and not perf-relevant. @@ -152,7 +152,7 @@ impl AuditCommand { return Err(err.into()); } }; - let json_output = manager.options.json_output; + let json_output = JsonOutput::from_bool(manager.options.json_output); if fix { return Self::audit_fix( ctx, @@ -171,10 +171,11 @@ impl AuditCommand { fn audit( _ctx: Command::Context, pm: &mut PackageManager, - json_output: bool, + json_output: JsonOutput, audit_level: Option, ignore_list: &[&[u8]], ) -> Result { + let json_output = json_output == JsonOutput::Yes; if !json_output && pm.options.should_print_command_name() { print_command_name(false); } @@ -236,11 +237,12 @@ impl AuditCommand { fn audit_fix( ctx: Command::Context, pm: &mut PackageManager, - json_output: bool, + json_output: JsonOutput, audit_level: Option, ignore_list: &[&[u8]], original_cwd: &[u8], ) -> crate::Result { + let json_output = json_output == JsonOutput::Yes; if !json_output && pm.options.should_print_command_name() { print_command_name(true); } diff --git a/src/runtime/cli/build_command.rs b/src/runtime/cli/build_command.rs index 92003b6f7c64..2fcdaf197a23 100644 --- a/src/runtime/cli/build_command.rs +++ b/src/runtime/cli/build_command.rs @@ -1,7 +1,7 @@ use std::io::Write as _; use crate::cli::command::{Context, HotReload}; -use bun_bundler::bundle_v2::{self, BundleV2}; +use bun_bundler::bundle_v2::{self, BundleV2, CliWatchFlag}; use bun_bundler::linker_context::metafile_builder as MetafileBuilder; use bun_bundler::options; use bun_bundler::transpiler; @@ -537,9 +537,13 @@ impl BuildCommand { ct.options.define = options::Define::init( user_defines, None, - this_transpiler.options.define.drop_debugger, - this_transpiler.options.dead_code_elimination - && this_transpiler.options.minify_syntax, + bun_bundler::defines::DropDebugger::from_bool( + this_transpiler.options.define.drop_debugger, + ), + bun_bundler::defines::OmitUnusedGlobalCalls::from_bool( + this_transpiler.options.dead_code_elimination + && this_transpiler.options.minify_syntax, + ), )?; } @@ -603,7 +607,10 @@ impl BuildCommand { if !result.errors.is_empty() || result.output_files.is_empty() { Output::flush(); - exit_or_watch(1, ctx.debug.hot_reload == HotReload::Watch); + exit_or_watch( + 1, + CliWatchFlag::from_bool(ctx.debug.hot_reload == HotReload::Watch), + ); } } @@ -638,7 +645,7 @@ impl BuildCommand { this_transpiler, arena, Some(core::ptr::NonNull::from(&mut event_loop)), - ctx.debug.hot_reload == HotReload::Watch, + bundle_v2::CliWatchFlag::from_bool(ctx.debug.hot_reload == HotReload::Watch), &mut reachable_file_count, &mut minify_duration, &mut input_code_length, @@ -655,7 +662,10 @@ impl BuildCommand { } Output::flush(); - exit_or_watch(1, ctx.debug.hot_reload == HotReload::Watch); + exit_or_watch( + 1, + CliWatchFlag::from_bool(ctx.debug.hot_reload == HotReload::Watch), + ); } }; @@ -675,7 +685,10 @@ impl BuildCommand { "could not open metafile {}", (bun_fmt::quote(&ctx.bundler_options.metafile),), ); - exit_or_watch(1, ctx.debug.hot_reload == HotReload::Watch); + exit_or_watch( + 1, + CliWatchFlag::from_bool(ctx.debug.hot_reload == HotReload::Watch), + ); } }; @@ -687,7 +700,10 @@ impl BuildCommand { "could not write metafile {}", (bun_fmt::quote(&ctx.bundler_options.metafile),), ); - exit_or_watch(1, ctx.debug.hot_reload == HotReload::Watch); + exit_or_watch( + 1, + CliWatchFlag::from_bool(ctx.debug.hot_reload == HotReload::Watch), + ); } } drop(file); @@ -715,7 +731,12 @@ impl BuildCommand { "could not open metafile-md {}", (bun_fmt::quote(&ctx.bundler_options.metafile_md),), ); - exit_or_watch(1, ctx.debug.hot_reload == HotReload::Watch); + exit_or_watch( + 1, + CliWatchFlag::from_bool( + ctx.debug.hot_reload == HotReload::Watch, + ), + ); } }; @@ -727,7 +748,12 @@ impl BuildCommand { "could not write metafile-md {}", (bun_fmt::quote(&ctx.bundler_options.metafile_md),), ); - exit_or_watch(1, ctx.debug.hot_reload == HotReload::Watch); + exit_or_watch( + 1, + CliWatchFlag::from_bool( + ctx.debug.hot_reload == HotReload::Watch, + ), + ); } } drop(file); @@ -746,7 +772,10 @@ impl BuildCommand { &mut output_files, ) { Output::err_generic("{}", (msg.as_str(),)); - exit_or_watch(1, ctx.debug.hot_reload == HotReload::Watch); + exit_or_watch( + 1, + CliWatchFlag::from_bool(ctx.debug.hot_reload == HotReload::Watch), + ); } } @@ -818,7 +847,10 @@ impl BuildCommand { "could not open output directory {}", (bun_fmt::quote(root_path),), ); - exit_or_watch(1, ctx.debug.hot_reload == HotReload::Watch); + exit_or_watch( + 1, + CliWatchFlag::from_bool(ctx.debug.hot_reload == HotReload::Watch), + ); } } }; @@ -847,7 +879,9 @@ impl BuildCommand { print_summary( bundled_end, minify_duration, - opt_minify_identifiers || opt_minify_whitespace || opt_minify_syntax, + Minified::from_bool( + opt_minify_identifiers || opt_minify_whitespace || opt_minify_syntax, + ), input_code_length as usize, reachable_file_count, output_files, @@ -1155,13 +1189,13 @@ impl BuildCommand { ))?; exit_or_watch( if had_err { 1 } else { 0 }, - ctx.debug.hot_reload == HotReload::Watch, + CliWatchFlag::from_bool(ctx.debug.hot_reload == HotReload::Watch), ); } } -fn exit_or_watch(code: u8, watch: bool) -> ! { - if watch { +fn exit_or_watch(code: u8, watch: CliWatchFlag) -> ! { + if watch == CliWatchFlag::Yes { // the watcher thread will exit the process. `std::thread::sleep` // accepts arbitrarily large Durations on every supported platform // (the stdlib loops internally where the OS primitive is narrower), @@ -1171,14 +1205,17 @@ fn exit_or_watch(code: u8, watch: bool) -> ! { Global::exit(u32::from(code)); } +bun_core::bool_enum!(Minified); + fn print_summary( bundled_end: i128, minify_duration: u64, - minified: bool, + minified: Minified, input_code_length: usize, reachable_file_count: usize, output_files: &[options::OutputFile], ) { + let minified = minified == Minified::Yes; let padding_buf = [b' '; 16]; let bundle_until_now = diff --git a/src/runtime/cli/bunx_command.rs b/src/runtime/cli/bunx_command.rs index f978022d1736..23a08df7bf2d 100644 --- a/src/runtime/cli/bunx_command.rs +++ b/src/runtime/cli/bunx_command.rs @@ -7,7 +7,7 @@ use bstr::BStr; use crate::cli::command::ContextData; use crate::cli::{self, Command}; -use crate::run_command::{ConfigureEnvOptions, RunCommand as Run}; +use crate::run_command::{ConfigureEnvOptions, ForceUsingBun, RunCommand as Run}; use bun_alloc::AllocError; use bun_ast::ExprData; @@ -213,6 +213,8 @@ pub(crate) enum GetBinNameError { NeedToInstall, } +bun_core::bool_enum!(WithStaleCheck); + impl BunxCommand { /// Adds `create-` to the string, but also handles scoped packages correctly. /// Always clones the string in the process. @@ -398,10 +400,10 @@ impl BunxCommand { transpiler: &mut Transpiler, tempdir_name: &[u8], package_name: &[u8], - with_stale_check: bool, + with_stale_check: WithStaleCheck, ) -> crate::Result> { let mut subpath = PathBuffer::uninit(); - if with_stale_check { + if with_stale_check == WithStaleCheck::Yes { let len = { let total = subpath.len(); let mut cursor: &mut [u8] = &mut subpath[..]; @@ -510,7 +512,7 @@ impl BunxCommand { transpiler, tempdir_name, package_name, - true, + WithStaleCheck::Yes, ) { Ok(v) => Ok(v), Err(err2) => { @@ -664,7 +666,7 @@ impl BunxCommand { } fn exit_with_usage() -> ! { - crate::cli::command::tag_print_help(Command::Tag::BunxCommand, false); + crate::cli::command::tag_print_help(Command::Tag::BunxCommand, cli::ShowAllFlags::No); Global::exit(1); } @@ -765,7 +767,7 @@ impl BunxCommand { this_transpiler, Some(&mut original_path), root_dir_info.abs_path, - force_using_bun, + ForceUsingBun::from_bool(force_using_bun), )?; let env_loader = this_transpiler.env_mut(); env_loader @@ -910,7 +912,7 @@ impl BunxCommand { if !strings::eql_long( strings::without_trailing_slash(segment), strings::without_trailing_slash(&ignore_cwd), - true, + strings::CheckLen::Yes, ) { new_path.extend_from_slice(segment); } @@ -919,7 +921,7 @@ impl BunxCommand { if !strings::eql_long( strings::without_trailing_slash(segment), strings::without_trailing_slash(&ignore_cwd), - true, + strings::CheckLen::Yes, ) { new_path.push(DELIMITER); new_path.extend_from_slice(segment); @@ -1175,7 +1177,11 @@ impl BunxCommand { ) { Ok(package_name_for_bin) => { // if we check the bin name and its actually the same, we don't need to check $PATH here again - if !strings::eql_long(&package_name_for_bin, initial_bin_name, true) { + if !strings::eql_long( + &package_name_for_bin, + initial_bin_name, + strings::CheckLen::Yes, + ) { absolute_in_cache_dir = { let mut cursor: &mut [u8] = &mut absolute_in_cache_dir_buf[..]; write!( @@ -1315,7 +1321,7 @@ impl BunxCommand { let package_json = match bun_sys::File::create( bunx_install_dir.fd, b"package.json", - /* truncate */ true, + bun_sys::Truncate::Yes, ) { Ok(f) => f, Err(_) => break 'create_package_json, @@ -1520,9 +1526,13 @@ impl BunxCommand { this_transpiler, bunx_cache_dir, result_package_name, - false, + WithStaleCheck::No, ) { - if !strings::eql_long(&package_name_for_bin, initial_bin_name, true) { + if !strings::eql_long( + &package_name_for_bin, + initial_bin_name, + strings::CheckLen::Yes, + ) { absolute_in_cache_dir = { let mut cursor: &mut [u8] = &mut absolute_in_cache_dir_buf[..]; write!( diff --git a/src/runtime/cli/create/SourceFileProjectGenerator.rs b/src/runtime/cli/create/SourceFileProjectGenerator.rs index 3301ba5e0523..f85fc869da06 100644 --- a/src/runtime/cli/create/SourceFileProjectGenerator.rs +++ b/src/runtime/cli/create/SourceFileProjectGenerator.rs @@ -118,13 +118,15 @@ pub(crate) fn generate( Global::exit(0); } +bun_core::bool_enum!(FileCreated { Unchanged, Created }); + // Create a file with given contents, returns if file was newly created -fn create_file(filename: &[u8], contents: &[u8]) -> bun_sys::Result { +fn create_file(filename: &[u8], contents: &[u8]) -> bun_sys::Result { // Check if file exists and has same contents if let Ok(source_contents) = bun_sys::File::read_from(Fd::cwd(), filename) { // `source_contents` is a Vec; freed on drop. - if strings::eql_long(&source_contents, contents, true) { - return bun_sys::Result::Ok(false); + if strings::eql_long(&source_contents, contents, strings::CheckLen::Yes) { + return bun_sys::Result::Ok(FileCreated::Unchanged); } } @@ -142,7 +144,7 @@ fn create_file(filename: &[u8], contents: &[u8]) -> bun_sys::Result { 0o644, )?; match bun_sys::File::from_fd(fd).write_all(contents) { - bun_sys::Result::Ok(()) => bun_sys::Result::Ok(true), + bun_sys::Result::Ok(()) => bun_sys::Result::Ok(FileCreated::Created), bun_sys::Result::Err(err) => bun_sys::Result::Err(err), } } @@ -290,7 +292,7 @@ pub(crate) fn generate_files( )?; match create_file(&file_name, &content) { bun_sys::Result::Ok(new) => { - if new { + if new == FileCreated::Created { max_filename_len = max_filename_len.max(file_name.len()); filenames[index] = Some(file_name); } diff --git a/src/runtime/cli/create_command.rs b/src/runtime/cli/create_command.rs index 62f438717a75..e3468e4ac005 100644 --- a/src/runtime/cli/create_command.rs +++ b/src/runtime/cli/create_command.rs @@ -468,7 +468,7 @@ impl CreateCommand { tarball_bytes.list.as_slice(), &mut tarball_buf_list, )?; - gunzip.read_all(true)?; + gunzip.read_all(Zlib::Chunk::Last)?; drop(gunzip); node.name = @@ -1082,7 +1082,11 @@ impl CreateCommand { if !create_options.skip_git { if !create_options.skip_install { - GitHandler::spawn(destination, path_env, create_options.verbose); + GitHandler::spawn( + destination, + path_env, + Verbose::from_bool(create_options.verbose), + ); } else { if create_options.verbose { create_options.skip_git = @@ -1295,7 +1299,10 @@ impl CreateCommand { let create_options = CreateOptions::parse(ctx)?; let positionals = &create_options.positionals; if positionals.is_empty() { - crate::cli::command::tag_print_help(crate::Command::Tag::CreateCommand, false); + crate::cli::command::tag_print_help( + crate::Command::Tag::CreateCommand, + crate::cli::ShowAllFlags::No, + ); Global::crash(); } @@ -2380,8 +2387,10 @@ static SUCCESS: AtomicU32 = AtomicU32::new(0); static THREAD: bun_core::RacyCell>> = bun_core::RacyCell::new(None); +bun_core::bool_enum!(Verbose); + impl GitHandler { - fn spawn(destination: &[u8], path: &[u8], verbose: bool) { + fn spawn(destination: &[u8], path: &[u8], verbose: Verbose) { SUCCESS.store(0, Ordering::Relaxed); // Own copies so the spawned closure is `'static` without any lifetime @@ -2401,9 +2410,9 @@ impl GitHandler { unsafe { *THREAD.get() = Some(thread) }; } - fn spawn_thread(destination: &[u8], path: &[u8], verbose: bool) { + fn spawn_thread(destination: &[u8], path: &[u8], verbose: Verbose) { Output::Source::configure_named_thread(bun_core::zstr!("git")); - let outcome = if verbose { + let outcome = if verbose == Verbose::Yes { Self::run::(destination, path).unwrap_or(false) } else { Self::run::(destination, path).unwrap_or(false) diff --git a/src/runtime/cli/exec_command.rs b/src/runtime/cli/exec_command.rs index 01952db50bcc..ab53da966fdf 100644 --- a/src/runtime/cli/exec_command.rs +++ b/src/runtime/cli/exec_command.rs @@ -51,7 +51,9 @@ impl ExecCommand { )?; // Read the field before the `&mut` method call (borrowck). let disable_default_env_files = bundle.options.env.disable_default_env_files; - bundle.run_env_loader(disable_default_env_files)?; + bundle.run_env_loader(bun_dotenv::SkipDefaultEnv::from_bool( + disable_default_env_files, + ))?; let mut buf = PathBuffer::uninit(); let cwd: &[u8] = match bun_sys::getcwd(&mut *buf) { Ok(n) => &buf[..n], diff --git a/src/runtime/cli/filter_arg.rs b/src/runtime/cli/filter_arg.rs index e4a705f5e09b..55f1da268dd4 100644 --- a/src/runtime/cli/filter_arg.rs +++ b/src/runtime/cli/filter_arg.rs @@ -302,11 +302,11 @@ impl<'a> PackageFilterIterator<'a> { let walker = OwnedWalker(bun_core::heap::alloc_nn(GlobWalker::init_with_cwd( pattern, self.root_dir, - true, - true, - false, - true, - true, + glob::Dot::Yes, + glob::Absolute::Yes, + glob::FollowSymlinks::No, + glob::ErrorOnBrokenSymlinks::Yes, + glob::OnlyFiles::Yes, Some(glob_ignore_fn), )??)); // SAFETY: `walker` does not touch the allocation until it frees it, and `iter` is dropped diff --git a/src/runtime/cli/filter_run.rs b/src/runtime/cli/filter_run.rs index cce11da791c2..7c45a6b1cc97 100644 --- a/src/runtime/cli/filter_run.rs +++ b/src/runtime/cli/filter_run.rs @@ -8,7 +8,7 @@ use crate::api::bun::process::SpawnResultExt as _; use crate::api::bun::process::{self as spawn, Process, Rusage, SpawnOptions, Status}; use crate::cli::Command; use crate::cli::filter_arg as FilterArg; -use crate::cli::run_command::{ConfigureEnvOptions, RunCommand}; +use crate::cli::run_command::{ConfigureEnvOptions, ForceUsingBun, RunCommand}; use bun_collections::StringHashMap; use bun_core::{Global, Output}; use bun_core::{ZStr, strings}; @@ -166,12 +166,12 @@ impl<'a> ProcessHandle<'a> { if let Some(stdout) = stdout_fd { let _ = sys::set_nonblocking(stdout); handle.remaining_fds += 1; - handle.stdout.start(stdout, true)?; + handle.stdout.start(stdout, bun_io::IsPollable::Yes)?; } if let Some(stderr) = stderr_fd { let _ = sys::set_nonblocking(stderr); handle.remaining_fds += 1; - handle.stderr.start(stderr, true)?; + handle.stderr.start(stderr, bun_io::IsPollable::Yes)?; } } #[cfg(not(unix))] @@ -314,6 +314,8 @@ macro_rules! fmt { }; } +bun_core::bool_enum!(IsAbort); + struct State<'a> { handles: Box<[ProcessHandle<'a>]>, // Raw `*mut` — `init_global` returns the @@ -349,7 +351,7 @@ impl<'a> State<'a> { fn read_chunk(&mut self, handle: &mut ProcessHandle<'a>, chunk: &[u8]) -> crate::Result<()> { if self.pretty_output { handle.buffer.extend_from_slice(chunk); - let _ = self.redraw(false); + let _ = self.redraw(IsAbort::No); } else { let mut content = chunk; self.draw_buf.clear(); @@ -416,7 +418,7 @@ impl<'a> State<'a> { } } if self.pretty_output { - let _ = self.redraw(false); + let _ = self.redraw(IsAbort::No); } else { self.draw_buf.clear(); // flush any remaining buffer @@ -504,7 +506,7 @@ impl<'a> State<'a> { } } - fn redraw(&mut self, is_abort: bool) -> crate::Result<()> { + fn redraw(&mut self, is_abort: IsAbort) -> crate::Result<()> { if !self.pretty_output { return Ok(()); } @@ -523,7 +525,7 @@ impl<'a> State<'a> { for idx in 0..self.handles.len() { let handle = &self.handles[idx]; // normally we truncate the output to 10 lines, but on abort we print everything to aid debugging - let elide_lines = if is_abort { + let elide_lines = if is_abort == IsAbort::Yes { None } else { Some(handle.config.elide_count.unwrap_or(10)) @@ -666,7 +668,7 @@ impl<'a> State<'a> { fn finalize(&mut self) -> u8 { if self.aborted { - let _ = self.redraw(true); + let _ = self.redraw(IsAbort::Yes); } for handle in self.handles.iter() { if let Some(proc) = &handle.process { @@ -825,7 +827,7 @@ pub(crate) fn run_scripts_with_filter( &mut this_transpiler, None, path, - run_in_bun, + ForceUsingBun::from_bool(run_in_bun), )?; for (i, name) in [&pre_script_name[..], script_name, &post_script_name[..]] diff --git a/src/runtime/cli/init_command.rs b/src/runtime/cli/init_command.rs index d802f4f557a5..e287ad756ff8 100644 --- a/src/runtime/cli/init_command.rs +++ b/src/runtime/cli/init_command.rs @@ -73,7 +73,7 @@ impl InitCommand { let e = C::from_index(i); #[allow(clippy::disallowed_methods)] // template selected at runtime per enum variant - Output::pretty_fmt_rt(e.fmt(), colors) + Output::pretty_fmt_rt(e.fmt(), Output::AnsiColors::from_bool(colors)) }) .collect(); @@ -317,7 +317,10 @@ impl InitCommand { let arg = arg_.as_bytes(); if parse_flags && !arg.is_empty() && arg[0] == b'-' { if arg == b"--help" || arg == b"-h" { - CLI::command::tag_print_help(CLI::Command::Tag::InitCommand, true); + CLI::command::tag_print_help( + CLI::Command::Tag::InitCommand, + CLI::ShowAllFlags::Yes, + ); Global::exit(0); } else if arg == b"-m" || arg == b"--minimal" { minimal = true; @@ -793,7 +796,9 @@ impl InitCommand { { Some(f) => (f.handle(), None), None => { - let fd = bun_sys::File::create(Fd::cwd(), b"package.json", true)?.into_raw(); + let fd = + bun_sys::File::create(Fd::cwd(), b"package.json", bun_sys::Truncate::Yes)? + .into_raw(); (fd, Some(bun_sys::CloseOnDrop::new(fd))) } }; @@ -953,6 +958,8 @@ impl InitCommand { pub(crate) struct Assets; +bun_core::bool_enum!(IsTemplate); + impl Assets { // "known" assets pub(crate) const GITIGNORE: &'static [u8] = include_bytes!("init/gitignore.default"); @@ -969,7 +976,7 @@ impl Assets { asset: &'static [u8], args: &[(&[u8], &[u8])], ) -> Result<(), Error> { - let is_template = !args.is_empty(); + let is_template = IsTemplate::from_bool(!args.is_empty()); Self::create_full_inner(asset, asset_name, "", is_template, args) } @@ -978,7 +985,7 @@ impl Assets { contents: &'static [u8], args: &[(&[u8], &[u8])], ) -> Result<(), Error> { - let is_template = !args.is_empty(); + let is_template = IsTemplate::from_bool(!args.is_empty()); Self::create_full_with_contents(asset_name, contents, "", is_template, args) } @@ -1012,7 +1019,7 @@ impl Assets { message_suffix: &'static str, args: &[(&[u8], &[u8])], ) -> Result<(), Error> { - let is_template = !args.is_empty(); + let is_template = IsTemplate::from_bool(!args.is_empty()); Self::create_full_inner(asset, filename, message_suffix, is_template, args) } @@ -1020,7 +1027,7 @@ impl Assets { asset: &'static [u8], filename: &[u8], message_suffix: &'static str, - is_template: bool, + is_template: IsTemplate, args: &[(&[u8], &[u8])], ) -> Result<(), Error> { let file = bun_sys::File::openat( @@ -1031,7 +1038,7 @@ impl Assets { )?; // Write contents of known assets to the new file. Template assets get formatted. - if is_template { + if is_template == IsTemplate::Yes { let buf = bun_fmt::substitute_named(asset, args); file.write_all(&buf)?; } else { @@ -1053,7 +1060,7 @@ impl Assets { // optionally add a suffix to the end of the `+ filename` message. Must have a leading space. message_suffix: &'static str, // Treat the asset as a format string, using `args` to populate it. Only applies to known assets. - is_template: bool, + is_template: IsTemplate, // Format arguments args: &[(&[u8], &[u8])], ) -> Result<(), Error> { @@ -1064,7 +1071,7 @@ impl Assets { 0o666, )?; - if is_template { + if is_template == IsTemplate::Yes { let buf = bun_fmt::substitute_named(contents, args); file.write_all(&buf)?; } else { diff --git a/src/runtime/cli/install_completions_command.rs b/src/runtime/cli/install_completions_command.rs index 09a5b4b08055..99a59fad3238 100644 --- a/src/runtime/cli/install_completions_command.rs +++ b/src/runtime/cli/install_completions_command.rs @@ -528,7 +528,8 @@ impl InstallCompletionsCommand { debug_assert!(!completions_dir.is_empty()); - let output_file: File = match File::create(output_dir, filename, true) { + let output_file: File = match File::create(output_dir, filename, bun_sys::Truncate::Yes) + { Ok(f) => f, Err(err) => { pretty_errorln!( diff --git a/src/runtime/cli/link_command.rs b/src/runtime/cli/link_command.rs index a52f8c3a3429..7f6d9e8d9179 100644 --- a/src/runtime/cli/link_command.rs +++ b/src/runtime/cli/link_command.rs @@ -202,7 +202,7 @@ fn link(ctx: command::Context) -> crate::Result<()> { FileSystem::instance().top_level_dir_without_trailing_slash(), name, // is_directory - true, + bun_sys::SymlinkKind::Directory, ) { if manager.options.log_level != LogLevel::Silent { bun_core::pretty_errorln!( @@ -264,7 +264,7 @@ fn link(ctx: command::Context) -> crate::Result<()> { err: None, skipped_due_to_missing_bin: false, }; - bin_linker.link(true); + bin_linker.link(bun_install::Scope::Global); if let Some(e) = bin_linker.err { if manager.options.log_level != LogLevel::Silent { diff --git a/src/runtime/cli/mod.rs b/src/runtime/cli/mod.rs index f609dfdd4a0b..bf831941dc08 100644 --- a/src/runtime/cli/mod.rs +++ b/src/runtime/cli/mod.rs @@ -594,6 +594,8 @@ pub mod help_command { InvalidCommand, } + bun_core::bool_enum!(pub ShowAllFlags); + #[cold] pub(crate) fn exec() -> crate::Result<()> { exec_with_reason(Reason::Explicit) @@ -684,7 +686,7 @@ pub mod help_command { // Tag/Reason lack `ConstParamTy` in lower-tier crates, so `reason` is a // runtime arg. - pub(crate) fn print_with_reason(reason: Reason, show_all_flags: bool) { + pub(crate) fn print_with_reason(reason: Reason, show_all_flags: ShowAllFlags) { let mut rand = bun_core::rand::DefaultPrng::init( u64::try_from(bun_core::time::milli_timestamp().max(0)).expect("int cast"), ); @@ -718,7 +720,7 @@ pub mod help_command { args, Global::package_json_version_with_revision ); - if show_all_flags { + if show_all_flags == ShowAllFlags::Yes { pretty!("\nFlags:"); bun_clap::simple_help_bun_top_level(arguments::AUTO_PARAMS); pretty!( @@ -744,7 +746,7 @@ Join our Discord community: https://bun.com/discord\n" #[cold] fn exec_with_reason(reason: Reason) -> ! { - print_with_reason(reason, false); + print_with_reason(reason, ShowAllFlags::No); if reason == Reason::InvalidCommand { Global::exit(1); } @@ -752,6 +754,12 @@ Join our Discord community: https://bun.com/discord\n" } } pub use help_command as HelpCommand; +pub use help_command::ShowAllFlags; + +bun_core::bool_enum!( + /// `--json`: emit machine-readable JSON instead of the human table. + pub JsonOutput +); pub mod reserved_command { use super::*; @@ -1516,7 +1524,7 @@ pub mod command { // dir search, profile patching). for a in bun::argv().iter().skip(2) { if matches!(a, b"--help" | b"-h") { - tag_print_help(Tag::InstallCompletionsCommand, true); + tag_print_help(Tag::InstallCompletionsCommand, ShowAllFlags::Yes); Global::exit(0); } } @@ -1575,7 +1583,7 @@ pub mod command { if ctx.positionals.len() > 1 { super::exec_command::ExecCommand::exec(ctx)?; } else { - tag_print_help(Tag::ExecCommand, true); + tag_print_help(Tag::ExecCommand, ShowAllFlags::Yes); } Ok(()) } @@ -1790,7 +1798,7 @@ pub mod command { let args = argv_zslice(); if args.len() <= 2 { - tag_print_help(Tag::CreateCommand, false); + tag_print_help(Tag::CreateCommand, ShowAllFlags::No); Global::exit(1); } @@ -1830,7 +1838,7 @@ pub mod command { || positional_i == 0 || positionals[1].is_empty() { - tag_print_help(Tag::CreateCommand, true); + tag_print_help(Tag::CreateCommand, ShowAllFlags::Yes); Global::exit(0); } @@ -1960,10 +1968,15 @@ To create a project with the official Next.js scaffolding tool, run\n\ } } - super::pm_view_command::view(pm, package_name, property_path, json_output) + super::pm_view_command::view( + pm, + package_name, + property_path, + JsonOutput::from_bool(json_output), + ) } - pub(crate) fn tag_print_help(cmd: Tag, show_all_flags: bool) { + pub(crate) fn tag_print_help(cmd: Tag, show_all_flags: ShowAllFlags) { // the output of --help uses the following syntax highlighting // template: Usage: bun [flags] [arguments] // use [foo] for multiple arguments or flags for foo. @@ -2277,7 +2290,7 @@ Learn more about these at https://bun.com/docs/cli/pm ); Output::flush(); } - _ => HelpCommand::print_with_reason(HelpCommand::Reason::Explicit, false), + _ => HelpCommand::print_with_reason(HelpCommand::Reason::Explicit, ShowAllFlags::No), } } diff --git a/src/runtime/cli/multi_run.rs b/src/runtime/cli/multi_run.rs index 930c556ac928..0c4786a1210a 100644 --- a/src/runtime/cli/multi_run.rs +++ b/src/runtime/cli/multi_run.rs @@ -15,7 +15,7 @@ use bun_paths as path; use crate::Command; use crate::filter_arg as FilterArg; -use crate::run_command::{ConfigureEnvOptions, RunCommand}; +use crate::run_command::{ConfigureEnvOptions, ForceUsingBun, RunCommand}; // `bun.spawn` (Process/Status/SpawnOptions/Rusage/spawnProcess) — // lives under crate::api::bun::process. @@ -45,24 +45,26 @@ struct ScriptConfig { path: Box<[u8]>, } +bun_core::bool_enum!(PipeKind { Stdout, Stderr }); + /// Wraps a BufferedReader and tracks whether it represents stdout or stderr, /// so output can be routed to the correct parent stream. pub struct PipeReader<'a> { reader: BufferedReader, handle: *mut ProcessHandle<'a>, // set in ProcessHandle::start() - is_stderr: bool, + kind: PipeKind, /// Reached EOF or errored; no more chunks will arrive. ended: bool, line_buffer: Vec, } impl<'a> PipeReader<'a> { - fn new(is_stderr: bool) -> Self { + fn new(kind: PipeKind) -> Self { Self { // BufferedReader::init(This) — the parent type fills the vtable. reader: BufferedReader::init::(), handle: ptr::null_mut(), - is_stderr, + kind, ended: false, line_buffer: Vec::new(), } @@ -231,14 +233,14 @@ impl<'a> ProcessHandle<'a> { let _ = bun_sys::set_nonblocking(stdout_fd); self.stdout_reader .reader - .start(stdout_fd, true) + .start(stdout_fd, bun_io::IsPollable::Yes) .map_err(Error::from)?; } if let Some(stderr_fd) = stderr_fd { let _ = bun_sys::set_nonblocking(stderr_fd); self.stderr_reader .reader - .start(stderr_fd, true) + .start(stderr_fd, bun_io::IsPollable::Yes) .map_err(Error::from)?; } } @@ -369,7 +371,7 @@ impl<'a> State<'a> { pipe.line_buffer.extend_from_slice(chunk); // Route to correct parent stream: child stdout -> parent stdout, child stderr -> parent stderr - let writer = if pipe.is_stderr { + let writer = if pipe.kind == PipeKind::Stderr { Output::error_writer() } else { Output::writer() @@ -425,7 +427,7 @@ impl<'a> State<'a> { if !pipe.line_buffer.is_empty() { let line = &pipe.line_buffer[..]; let needs_newline = !line.is_empty() && line[line.len() - 1] != b'\n'; - let writer = if pipe.is_stderr { + let writer = if pipe.kind == PipeKind::Stderr { Output::error_writer() } else { Output::writer() @@ -966,7 +968,7 @@ pub(crate) fn run(ctx: &mut Command::ContextData) -> Result = if !package.json.name.is_empty() { Box::<[u8]>::from(&package.json.name[..]) @@ -1069,7 +1071,7 @@ pub(crate) fn run(ctx: &mut Command::ContextData) -> Result Result FilterType<'a> { // *NOTE*: name and path are not allocated → no Drop impl needed. } +bun_core::bool_enum!(WasFiltered); + impl OutdatedCommand { pub(crate) fn exec(ctx: Command::Context) -> crate::Result<()> { bun_core::prettyln!( @@ -171,7 +174,7 @@ impl OutdatedCommand { manager.options.filter_patterns, original_cwd, ); - (ids, true) + (ids, WasFiltered::Yes) } else { let root_pkg_id = manager .root_package_id @@ -179,7 +182,7 @@ impl OutdatedCommand { if root_pkg_id == bun_install::INVALID_PACKAGE_ID { return Ok(()); } - (vec![root_pkg_id], false) + (vec![root_pkg_id], WasFiltered::No) }; populate_manifest_cache::populate_manifest_cache( manager, @@ -302,7 +305,7 @@ impl OutdatedCommand { fn print_outdated_info_table( manager: &mut PackageManager, workspace_pkg_ids: &[PackageID], - was_filtered: bool, + was_filtered: WasFiltered, ) -> crate::Result<()> { let package_patterns: Option>> = 'package_patterns: { let args = manager.options.positionals.get(1..).unwrap_or(&[]); @@ -350,7 +353,7 @@ impl OutdatedCommand { // `&manager.options`). let cache_ctx = manager.manifest_disk_cache_ctx(); let min_age_ms = manager.options.minimum_release_age_ms; - let needs_extended = min_age_ms.is_some(); + let needs_extended = ExtendedManifest::from_bool(min_age_ms.is_some()); let excludes = manager.options.minimum_release_age_excludes; let mut version_buf: String = String::new(); @@ -543,7 +546,7 @@ impl OutdatedCommand { } // Show workspace column if filtered OR if there are catalog dependencies - let show_workspace_column = was_filtered || has_catalog_deps; + let show_workspace_column = was_filtered == WasFiltered::Yes || has_catalog_deps; let package_column_inside_length = "Packages".len().max(max_name); let current_column_inside_length = "Current".len().max(max_current); diff --git a/src/runtime/cli/pack_command.rs b/src/runtime/cli/pack_command.rs index aafee8e9db8c..d5081de950d4 100644 --- a/src/runtime/cli/pack_command.rs +++ b/src/runtime/cli/pack_command.rs @@ -25,7 +25,7 @@ use bun_paths::{self as path, PathBuffer, SEP_STR}; // borrow_subslice/length live on `cow_slice::CowSliceZ`). use bun_ptr::cow_slice::CowSlice; type CowString = CowSlice; -use crate::cli::run_command::{ConfigureEnvOptions, RunCommand}; +use crate::cli::run_command::{ConfigureEnvOptions, RunCommand, ScriptShell, Silent}; use bun_core::ZBox; use bun_core::{ZStr, strings}; use bun_paths::resolve_path; @@ -348,27 +348,29 @@ const ROOT_DEFAULT_IGNORE_PATTERNS: &[&[u8]] = &[ b"bun.lock", ]; -// (pattern, can_override). `can_override == false` mirrors npm-packlist's +bun_core::bool_enum!(CanOverride); + +// (pattern, can_override). `can_override == No` mirrors npm-packlist's // strict rules (only `.git` and `.npmrc` here; lockfiles live in // ROOT_DEFAULT_IGNORE_PATTERNS); everything else `"files"` can re-include. -const DEFAULT_IGNORE_PATTERNS: &[(&[u8], bool)] = &[ - (b".*.swp", true), - (b"._*", true), - (b".DS_Store", true), - (b".git", false), - (b".gitignore", true), - (b".hg", true), - (b".npmignore", true), - (b".npmrc", false), - (b".lock-wscript", true), - (b".svn", true), - (b".wafpickle-*", true), - (b"CVS", true), - (b"npm-debug.log", true), +const DEFAULT_IGNORE_PATTERNS: &[(&[u8], CanOverride)] = &[ + (b".*.swp", CanOverride::Yes), + (b"._*", CanOverride::Yes), + (b".DS_Store", CanOverride::Yes), + (b".git", CanOverride::No), + (b".gitignore", CanOverride::Yes), + (b".hg", CanOverride::Yes), + (b".npmignore", CanOverride::Yes), + (b".npmrc", CanOverride::No), + (b".lock-wscript", CanOverride::Yes), + (b".svn", CanOverride::Yes), + (b".wafpickle-*", CanOverride::Yes), + (b"CVS", CanOverride::Yes), + (b"npm-debug.log", CanOverride::Yes), // mentioned in the docs but does not appear to be ignored by default - // (b"config.gypi", false), - (b".env.production", true), - (b"bunfig.toml", true), + // (b"config.gypi", CanOverride::No), + (b".env.production", CanOverride::Yes), + (b"bunfig.toml", CanOverride::Yes), ]; struct PackListEntry { @@ -581,7 +583,11 @@ fn iterate_included_project_tree( if entry.kind == bun_sys::FileKind::Directory { for bin in bins { if bin.ty == BinType::Dir - && strings::eql_long(&bin.path, entry_subpath.as_bytes(), true) + && strings::eql_long( + &bin.path, + entry_subpath.as_bytes(), + strings::CheckLen::Yes, + ) { continue 'next_entry; } @@ -601,7 +607,11 @@ fn iterate_included_project_tree( bun_sys::FileKind::Directory => { for bin in bins { if bin.ty == BinType::Dir - && strings::eql_long(&bin.path, entry_subpath.as_bytes(), true) + && strings::eql_long( + &bin.path, + entry_subpath.as_bytes(), + strings::CheckLen::Yes, + ) { continue 'next_entry; } @@ -622,7 +632,11 @@ fn iterate_included_project_tree( for bin in bins { if bin.ty == BinType::File - && strings::eql_long(&bin.path, entry_subpath.as_bytes(), true) + && strings::eql_long( + &bin.path, + entry_subpath.as_bytes(), + strings::CheckLen::Yes, + ) { continue 'next_entry; } @@ -757,7 +771,11 @@ fn add_entire_tree( } for bin in bins { if bin.ty == BinType::File - && strings::eql_long(&bin.path, entry_subpath.as_bytes(), true) + && strings::eql_long( + &bin.path, + entry_subpath.as_bytes(), + strings::CheckLen::Yes, + ) { continue 'next_entry; } @@ -770,7 +788,11 @@ fn add_entire_tree( bun_sys::FileKind::Directory => { for bin in bins { if bin.ty == BinType::Dir - && strings::eql_long(&bin.path, entry_subpath.as_bytes(), true) + && strings::eql_long( + &bin.path, + entry_subpath.as_bytes(), + strings::CheckLen::Yes, + ) { continue 'next_entry; } @@ -913,7 +935,7 @@ fn iterate_bundled_deps( let Some(dep) = bundled_deps.iter_mut().find(|dep| { debug_assert!(dep.from_root_package_json); - strings::eql_long(dep_name.as_bytes(), &dep.name, true) + strings::eql_long(dep_name.as_bytes(), &dep.name, strings::CheckLen::Yes) }) else { continue; }; @@ -943,7 +965,7 @@ fn iterate_bundled_deps( let dep_name = entry_name; let Some(dep) = bundled_deps.iter_mut().find(|dep| { debug_assert!(dep.from_root_package_json); - strings::eql_long(dep_name, &dep.name, true) + strings::eql_long(dep_name, &dep.name, strings::CheckLen::Yes) }) else { continue; }; @@ -1332,7 +1354,11 @@ fn iterate_project_tree( debug_assert!(!entry_subpath_.as_bytes().is_empty()); for bin in bins { if bin.ty == BinType::File - && strings::eql_long(&bin.path, entry_subpath_.as_bytes(), true) + && strings::eql_long( + &bin.path, + entry_subpath_.as_bytes(), + strings::CheckLen::Yes, + ) { continue 'next_entry; } @@ -1345,7 +1371,11 @@ fn iterate_project_tree( bun_sys::FileKind::Directory => { for bin in bins { if bin.ty == BinType::Dir - && strings::eql_long(&bin.path, entry_subpath_.as_bytes(), true) + && strings::eql_long( + &bin.path, + entry_subpath_.as_bytes(), + strings::CheckLen::Yes, + ) { continue 'next_entry; } @@ -1533,7 +1563,7 @@ fn is_package_bin(bins: &[BinInfo], maybe_bin_path: &[u8]) -> bool { for bin in bins { match bin.ty { BinType::File => { - if strings::eql_long(bin.path.as_bytes(), maybe_bin_path, true) { + if strings::eql_long(bin.path.as_bytes(), maybe_bin_path, strings::CheckLen::Yes) { return true; } } @@ -1572,7 +1602,7 @@ fn is_unconditionally_excluded( } for &(pattern, can_override) in DEFAULT_IGNORE_PATTERNS { - if can_override { + if can_override == CanOverride::Yes { continue; } if glob::r#match(pattern, entry_name).matches() { @@ -1613,7 +1643,7 @@ fn is_excluded<'a>( let mut ignored = false; for &(pattern, can_override) in DEFAULT_IGNORE_PATTERNS { - if !can_override { + if can_override == CanOverride::No { continue; } if glob::r#match(pattern, entry_name).matches() { @@ -2077,7 +2107,7 @@ pub(crate) fn pack( b"prepublishOnly", abs_workspace_path, ctx.manager.env_mut(), - ctx.manager.options.log_level == LogLevel::Silent, + Silent::from_bool(ctx.manager.options.log_level == LogLevel::Silent), )?; } } @@ -2092,7 +2122,7 @@ pub(crate) fn pack( b"prepack", abs_workspace_path, ctx.manager.env_mut(), - ctx.manager.options.log_level == LogLevel::Silent, + Silent::from_bool(ctx.manager.options.log_level == LogLevel::Silent), )?; } } @@ -2106,7 +2136,7 @@ pub(crate) fn pack( b"prepare", abs_workspace_path, ctx.manager.env_mut(), - ctx.manager.options.log_level == LogLevel::Silent, + Silent::from_bool(ctx.manager.options.log_level == LogLevel::Silent), )?; } } @@ -2426,7 +2456,7 @@ pub(crate) fn pack( b"postpack", abs_workspace_path, ctx.manager.env_mut(), - ctx.manager.options.log_level == LogLevel::Silent, + Silent::from_bool(ctx.manager.options.log_level == LogLevel::Silent), )?; } @@ -2911,7 +2941,7 @@ pub(crate) fn pack( b"postpack", abs_workspace_path, ctx.manager.env_mut(), - ctx.manager.options.log_level == LogLevel::Silent, + Silent::from_bool(ctx.manager.options.log_level == LogLevel::Silent), )?; } @@ -2948,9 +2978,9 @@ fn run_lifecycle_script( name: &[u8], abs_workspace_path: &[u8], env: &mut bun_dotenv::Loader, - silent: bool, + silent: Silent, ) -> Result<(), PackError> { - let use_system_shell = command_ctx.debug.use_system_shell; + let use_system_shell = ScriptShell::from_bool(command_ctx.debug.use_system_shell); match RunCommand::run_package_script_foreground( command_ctx, script, diff --git a/src/runtime/cli/package_manager_command.rs b/src/runtime/cli/package_manager_command.rs index 62b21f210e1b..d9c191061f75 100644 --- a/src/runtime/cli/package_manager_command.rs +++ b/src/runtime/cli/package_manager_command.rs @@ -6,7 +6,9 @@ use bun_core::fmt::PathSep; use bun_core::strings; use bun_core::{Global, Output, env_var, fmt as bun_fmt}; use bun_install::dependency::Dependency; -use bun_install::lockfile::{LoadResult, LoadStep, Lockfile, package::PackageColumns as _, tree}; +use bun_install::lockfile::{ + LoadResult, LoadStep, Lockfile, PrintNameVersion, package::PackageColumns as _, tree, +}; use bun_install::npm as Npm; use bun_install::package_manager_real::{ CommandLineArguments, Subcommand, fetch_cache_directory_path, get_cache_directory, @@ -335,7 +337,7 @@ Learn more about these at https://bun.com/docs/cli/pm.\n"; } else { b"".as_slice() }; - let json_output = pm.options.json_output; + let json_output = crate::cli::JsonOutput::from_bool(pm.options.json_output); PmViewCommand::view(pm, spec, property_path, json_output)?; Global::exit(0); } else if strings::eql_comptime(subcommand, b"bin") { @@ -383,7 +385,7 @@ Learn more about these at https://bun.com/docs/cli/pm.\n"; let pm = unsafe { &mut *pm_ptr }; let _ = pm .lockfile - .has_meta_hash_changed(false, pm.lockfile.packages.len())?; + .has_meta_hash_changed(PrintNameVersion::No, pm.lockfile.packages.len())?; Output::flush(); Output::disable_buffering(); @@ -409,7 +411,7 @@ Learn more about these at https://bun.com/docs/cli/pm.\n"; let pm = unsafe { &mut *pm_ptr }; let _ = pm .lockfile - .has_meta_hash_changed(true, pm.lockfile.packages.len())?; + .has_meta_hash_changed(PrintNameVersion::Yes, pm.lockfile.packages.len())?; Global::exit(0); } else if strings::eql_comptime(subcommand, b"cache") { if pm.options.positionals.len() > 1 @@ -890,7 +892,7 @@ fn print_node_modules_folder_structure( if strings::eql_long( &possible_path, directories[dir_index].relative_path.as_bytes(), - true, + strings::CheckLen::Yes, ) { found_node_modules = true; let next = directories.remove(dir_index); diff --git a/src/runtime/cli/pm_pkg_command.rs b/src/runtime/cli/pm_pkg_command.rs index 556f5cfd0d0c..0d71b76f01dc 100644 --- a/src/runtime/cli/pm_pkg_command.rs +++ b/src/runtime/cli/pm_pkg_command.rs @@ -48,6 +48,8 @@ struct PackageJson { indentation: bun_ast::Indentation, } +bun_core::bool_enum!(ParseJson); + impl PmPkgCommand { pub(crate) fn exec( ctx: &Context, @@ -286,7 +288,7 @@ impl PmPkgCommand { Global::exit(1); } - let parse_json = pm.options.json_output; + let parse_json = ParseJson::from_bool(pm.options.json_output); let path = Self::find_package_json(cwd)?; @@ -393,7 +395,7 @@ impl PmPkgCommand { let lowercase: Vec = name_str.iter().map(|b| b.to_ascii_lowercase()).collect(); if !strings::eql(name_str, &lowercase) { - Self::set_value(&mut root, b"name", &lowercase, false)?; + Self::set_value(&mut root, b"name", &lowercase, ParseJson::No)?; modified = true; } } @@ -614,7 +616,12 @@ impl PmPkgCommand { Ok(path_parts) } - fn set_value(root: &mut Expr, key: &[u8], value: &[u8], parse_json: bool) -> Result<(), Error> { + fn set_value( + root: &mut Expr, + key: &[u8], + value: &[u8], + parse_json: ParseJson, + ) -> Result<(), Error> { if !matches!(root.data, ExprData::EObject(_)) { return Err(crate::Error::InvalidRoot); } @@ -643,7 +650,7 @@ impl PmPkgCommand { root: &mut Expr, path: &[&[u8]], value: &[u8], - parse_json: bool, + parse_json: ParseJson, ) -> Result<(), Error> { if path.is_empty() { return Ok(()); @@ -685,8 +692,8 @@ impl PmPkgCommand { Self::set_nested(&mut nested, remaining_path, value, parse_json) } - fn parse_value(value: &[u8], parse_json: bool) -> Result { - if parse_json { + fn parse_value(value: &[u8], parse_json: ParseJson) -> Result { + if parse_json == ParseJson::Yes { if value == b"true" { return Ok(Expr::init(E::Boolean { value: true }, Loc::EMPTY)); } else if value == b"false" { diff --git a/src/runtime/cli/pm_trusted_command.rs b/src/runtime/cli/pm_trusted_command.rs index d03bd50c0d34..8559cecceaf2 100644 --- a/src/runtime/cli/pm_trusted_command.rs +++ b/src/runtime/cli/pm_trusted_command.rs @@ -5,6 +5,7 @@ use bun_alloc::Arena as Bump; use bun_collections::{ArrayHashMap, ArrayIdentityContext, StringArrayHashMap}; use bun_core::strings; use bun_core::{Global, Output, Progress}; +use bun_install::lifecycle_script_runner::{Foreground, Optional}; use bun_install::lockfile::{ LoadResult, Lockfile, package::PackageColumns as _, @@ -214,15 +215,20 @@ struct ScriptInfo { skip: bool, } +bun_core::bool_enum!(TrustAll); + impl TrustCommand { fn error_expected_args() -> ! { Output::err_generic("expected package names(s) or --all", ()); Global::crash(); } - fn print_error_zero_untrusted_dependencies_found(trust_all: bool, packages_to_trust: &[&[u8]]) { + fn print_error_zero_untrusted_dependencies_found( + trust_all: TrustAll, + packages_to_trust: &[&[u8]], + ) { Output::print(format_args!("\n")); - if trust_all { + if trust_all == TrustAll::Yes { Output::err_generic( "0 scripts ran. This means all dependencies are already trusted or none have scripts.", (), @@ -324,7 +330,10 @@ impl TrustCommand { } if untrusted_dep_ids.count() == 0 { - Self::print_error_zero_untrusted_dependencies_found(trust_all, &packages_to_trust); + Self::print_error_zero_untrusted_dependencies_found( + TrustAll::from_bool(trust_all), + &packages_to_trust, + ); Global::crash(); } @@ -398,13 +407,15 @@ impl TrustCommand { } for package_name_from_cli in &packages_to_trust { - if strings::eql_long(package_name_from_cli, alias, true) - && !lockfile.has_trusted_dependency( - alias, - packages.items_name()[package_id as usize].slice(buf), - resolution, - ) - { + if strings::eql_long( + package_name_from_cli, + alias, + strings::CheckLen::Yes, + ) && !lockfile.has_trusted_dependency( + alias, + packages.items_name()[package_id as usize].slice(buf), + resolution, + ) { break 'brk false; } } @@ -435,7 +446,10 @@ impl TrustCommand { } if scripts_at_depth.count() == 0 || package_names_to_add.count() == 0 { - Self::print_error_zero_untrusted_dependencies_found(trust_all, &packages_to_trust); + Self::print_error_zero_untrusted_dependencies_found( + TrustAll::from_bool(trust_all), + &packages_to_trust, + ); Global::crash(); } @@ -486,8 +500,8 @@ impl TrustCommand { unsafe { (*pm_raw).sleep() }; } - let output_in_foreground = false; - let optional = false; + let output_in_foreground = Foreground::No; + let optional = Optional::No; // SAFETY: `pm_raw` singleton; `ctx` is the CLI `&mut ContextData`. unsafe { (*pm_raw).spawn_package_lifecycle_scripts( diff --git a/src/runtime/cli/pm_version_command.rs b/src/runtime/cli/pm_version_command.rs index 63e4598b76c9..65bda62938b9 100644 --- a/src/runtime/cli/pm_version_command.rs +++ b/src/runtime/cli/pm_version_command.rs @@ -7,7 +7,7 @@ use crate::api::bun::process::sync::{ Options as SpawnSyncOptions, SyncStdio as Stdio, spawn as spawn_sync, }; use crate::cli::command; -use crate::cli::run_command::RunCommand; +use crate::cli::run_command::{RunCommand, ScriptShell, Silent}; use bun_alloc::{AllocError, Arena}; use bun_ast::ExprData; use bun_core::strings; @@ -148,8 +148,8 @@ impl PmVersionCommand { None }; - let silent = pm.options.log_level == LogLevel::Silent; - let use_system_shell = ctx.debug.use_system_shell; + let silent = Silent::from_bool(pm.options.log_level == LogLevel::Silent); + let use_system_shell = ScriptShell::from_bool(ctx.debug.use_system_shell); if let Some(s) = &scripts_obj { if let Some(script) = s.get(b"preversion") { diff --git a/src/runtime/cli/pm_view_command.rs b/src/runtime/cli/pm_view_command.rs index ebeca184aac6..3cb1751d383f 100644 --- a/src/runtime/cli/pm_view_command.rs +++ b/src/runtime/cli/pm_view_command.rs @@ -8,7 +8,7 @@ use bun_core::{Global, Output, prettyln}; use bun_http as http; use bun_install::PackageManager; use bun_install::dependency; -use bun_install::npm::{self, PackageManifest}; +use bun_install::npm::{self, ExtendedManifest, PackageManifest}; use bun_js_parser as ast; use bun_js_printer as JSPrinter; use bun_parsers::json as JSON; @@ -16,14 +16,16 @@ use bun_paths::PathBuffer; use bun_semver as Semver; use bun_url::URL; // bumpalo::Bump re-export +use crate::cli::JsonOutput; use bun_core::fmt::buf_print_infallible as buf_print; pub(crate) fn view( manager: &mut PackageManager, spec_: &[u8], property_path: Option<&[u8]>, - json_output: bool, + json_output: JsonOutput, ) -> Result<(), crate::Error> { + let json_output = json_output == JsonOutput::Yes; let bump = Bump::new(); let (name, mut version) = dependency::split_name_and_version_or_latest('brk: { // Extremely best effort. @@ -166,10 +168,10 @@ pub(crate) fn view( &mut log, response_buf.list.as_slice(), name, - b"", // last_modified (not needed for view) - b"", // etag (not needed for view) - 0, // public_max_age (not needed for view) - true, // is_extended_manifest (view uses application/json Accept header) + b"", // last_modified (not needed for view) + b"", // etag (not needed for view) + 0, // public_max_age (not needed for view) + ExtendedManifest::Yes, // view uses application/json Accept header ) { Ok(Some(m)) => m, Ok(None) => { diff --git a/src/runtime/cli/publish_command.rs b/src/runtime/cli/publish_command.rs index 60a861015688..271430d69f50 100644 --- a/src/runtime/cli/publish_command.rs +++ b/src/runtime/cli/publish_command.rs @@ -81,7 +81,7 @@ fn is_readme_os_path(name: &[OSPathChar]) -> bool { use crate::cli::init_command::InitCommand; use crate::cli::open; -use crate::run_command::RunCommand as Run; +use crate::run_command::{RunCommand as Run, ScriptShell, Silent}; type SHA1Digest = [u8; sha::SHA1::DIGEST]; type SHA512Digest = [u8; sha::SHA512::DIGEST]; @@ -526,6 +526,9 @@ impl<'a, const DIRECTORY_PUBLISH: bool> Context<'a, DIRECTORY_PUBLISH> { } } +bun_core::bool_enum!(CloseDir); +bun_core::bool_enum!(UsesWorkspaces); + impl PublishCommand { pub(crate) fn exec(ctx: Command::Context) -> Result<(), Error> { bun_core::prettyln!( @@ -699,9 +702,9 @@ impl PublishCommand { &abs_workspace_path, script_env, &[], - context.manager.options.log_level == LogLevel::Silent, + Silent::from_bool(context.manager.options.log_level == LogLevel::Silent), // SAFETY: see above. - unsafe { &*cmd_ctx_ptr }.debug.use_system_shell, + ScriptShell::from_bool(unsafe { &*cmd_ctx_ptr }.debug.use_system_shell), ) { if matches!(e, crate::Error::MissingShell) { Output::err_generic( @@ -723,9 +726,9 @@ impl PublishCommand { &abs_workspace_path, script_env, &[], - context.manager.options.log_level == LogLevel::Silent, + Silent::from_bool(context.manager.options.log_level == LogLevel::Silent), // SAFETY: see above. - unsafe { &*cmd_ctx_ptr }.debug.use_system_shell, + ScriptShell::from_bool(unsafe { &*cmd_ctx_ptr }.debug.use_system_shell), ) { if matches!(e, crate::Error::MissingShell) { Output::err_generic( @@ -915,7 +918,7 @@ impl PublishCommand { } else { None }, - ctx.uses_workspaces, + UsesWorkspaces::from_bool(ctx.uses_workspaces), ctx.manager.options.publish_config.auth_type, )?; @@ -966,13 +969,21 @@ impl PublishCommand { let mut iter = strings::split(www_authenticate, b","); while let Some(part) = iter.next() { let trimmed = strings::trim(part, &strings::WHITESPACE_CHARS); - if strings::eql_case_insensitive_ascii(trimmed, b"ipaddress", true) { + if strings::eql_case_insensitive_ascii( + trimmed, + b"ipaddress", + strings::CheckLen::Yes, + ) { Output::err_generic( "login is not allowed from your IP address", (), ); Global::crash(); - } else if strings::eql_case_insensitive_ascii(trimmed, b"otp", true) { + } else if strings::eql_case_insensitive_ascii( + trimmed, + b"otp", + strings::CheckLen::Yes, + ) { break 'prompt_for_otp true; } } @@ -1021,7 +1032,7 @@ impl PublishCommand { registry, Some(publish_req_body.len()), Some(&otp), - ctx.uses_workspaces, + UsesWorkspaces::from_bool(ctx.uses_workspaces), ctx.manager.options.publish_config.auth_type, )?; @@ -1248,7 +1259,7 @@ impl PublishCommand { registry, None, None, - ctx.uses_workspaces, + UsesWorkspaces::from_bool(ctx.uses_workspaces), ctx.manager.options.publish_config.auth_type, )?; @@ -1780,14 +1791,14 @@ impl PublishCommand { } }; - let mut dirs: Vec<(Fd, Box<[u8]>, bool)> = Vec::new(); + let mut dirs: Vec<(Fd, Box<[u8]>, CloseDir)> = Vec::new(); - dirs.push((bin_dir, normalized_bin_dir.as_bytes().into(), false)); + dirs.push((bin_dir, normalized_bin_dir.as_bytes().into(), CloseDir::No)); while let Some(dir_info) = dirs.pop() { let (dir, dir_subpath, close_dir) = dir_info; let _close = scopeguard::guard(dir, move |d| { - if close_dir { + if close_dir == CloseDir::Yes { let _ = d.close(); } }); @@ -1849,7 +1860,7 @@ impl PublishCommand { else { continue; }; - dirs.push((subdir, subpath.as_bytes().into(), true)); + dirs.push((subdir, subpath.as_bytes().into(), CloseDir::Yes)); } } } @@ -1877,9 +1888,10 @@ impl PublishCommand { registry: &Npm::Registry::Scope, maybe_json_len: Option, maybe_otp: Option<&[u8]>, - uses_workspaces: bool, + uses_workspaces: UsesWorkspaces, auth_type: Option, ) -> Result { + let uses_workspaces = uses_workspaces == UsesWorkspaces::Yes; let mut headers = http::HeaderBuilder::default(); let npm_auth_type: &[u8] = if maybe_otp.is_none() { if let Some(auth) = auth_type { @@ -2063,8 +2075,11 @@ impl PublishCommand { // reserved spare capacity; `fill_spare` commits exactly that count. let count = unsafe { bun_core::vec::fill_spare(&mut buf, encoded_tarball_len, |spare| { - let n = - simdutf::base64::encode_raw(&ctx.tarball_bytes, spare.as_mut_ptr(), false); + let n = simdutf::base64::encode_raw( + &ctx.tarball_bytes, + spare.as_mut_ptr(), + simdutf::base64::Alphabet::Standard, + ); (n, n) }) }; diff --git a/src/runtime/cli/repl.rs b/src/runtime/cli/repl.rs index a4a3131a01a5..efa1d44e0f1c 100644 --- a/src/runtime/cli/repl.rs +++ b/src/runtime/cli/repl.rs @@ -268,7 +268,7 @@ impl History { // Don't add duplicates of the last entry if let Some(last) = self.entries.last() { - if strings::eql_long(last, line, true) { + if strings::eql_long(last, line, strings::CheckLen::Yes) { self.position = self.entries.len(); return Ok(()); } @@ -576,7 +576,7 @@ impl ReplCommand { fn find(name: &[u8]) -> Option<&'static ReplCommand> { Self::ALL.iter().find(|&cmd| { - strings::eql_long(cmd.name, name, true) + strings::eql_long(cmd.name, name, strings::CheckLen::Yes) || (name.len() > 1 && cmd.name.starts_with(name)) }) } @@ -851,6 +851,8 @@ enum InputMode { Editor, } +bun_core::bool_enum!(pub(super) PrintResult); + pub(super) struct Repl<'a> { line_editor: LineEditor, history: History, @@ -1617,7 +1619,7 @@ impl<'a> Repl<'a> { /// result to stdout. Errors are written to stderr. /// Returns true if an error occurred (the caller should set exit_code=1 and /// skip onBeforeExit); false on success (caller preserves process.exitCode). - pub(super) fn eval_script(&mut self, code: &[u8], print_result: bool) -> bool { + pub(super) fn eval_script(&mut self, code: &[u8], print_result: PrintResult) -> bool { let Some(global) = self.global else { return true; }; @@ -1627,11 +1629,12 @@ impl<'a> Repl<'a> { let no_color = env_var::NO_COLOR.get().unwrap_or(false); self.use_colors = Output::enable_ansi_colors_stdout() && !no_color; - let stderr_colors = Output::enable_ansi_colors_stderr() && !no_color; + let stderr_colors = + Output::AnsiColors::from_bool(Output::enable_ansi_colors_stderr() && !no_color); // Empty / whitespace-only script: nothing to do (matches `node -e ""`) if strings::trim(code, b" \t\n\r").is_empty() { - if print_result { + if print_result == PrintResult::Yes { if self.use_colors { self.print(format_args!("{}undefined{}\n", Color::DIM, Color::RESET)); } else { @@ -1731,7 +1734,7 @@ impl<'a> Repl<'a> { vm.as_mut().auto_tick_active(); } - if print_result { + if print_result == PrintResult::Yes { if actual_result.is_undefined() { if self.use_colors { self.print(format_args!("{}undefined{}\n", Color::DIM, Color::RESET)); @@ -2113,14 +2116,18 @@ impl<'a> Repl<'a> { fn print_js_error(&self, error_value: JSValue) { // Interactive REPL writes everything to stdout (single terminal stream). - self.print_js_error_to(error_value, Output::writer(), self.use_colors); + self.print_js_error_to( + error_value, + Output::writer(), + Output::AnsiColors::from_bool(self.use_colors), + ); } fn print_js_error_to( &self, error_value: JSValue, writer: &mut bun_core::io::Writer, - enable_colors: bool, + enable_colors: Output::AnsiColors, ) { // Note: the `bun_core::io::Writer` vtable doesn't implement // `bun_io::Write`, so buffer through a `Vec` (which does) and @@ -2136,7 +2143,7 @@ impl<'a> Repl<'a> { core::slice::from_ref(&error_value), &mut buf, jsc::ConsoleObject::FormatOptions { - enable_colors, + enable_colors: enable_colors == Output::AnsiColors::Enabled, add_newline: true, flush: false, quote_strings: true, diff --git a/src/runtime/cli/repl_command.rs b/src/runtime/cli/repl_command.rs index 061ac4de2b1a..5678c62c0efa 100644 --- a/src/runtime/cli/repl_command.rs +++ b/src/runtime/cli/repl_command.rs @@ -23,7 +23,7 @@ use bun_jsc::{self as jsc, JSGlobalObject}; // module here so `Repl` resolves without touching `cli/mod.rs`. #[path = "repl.rs"] mod repl; -use repl::Repl; +use repl::{PrintResult, Repl}; use crate::Command; use crate::cli::Arguments; @@ -49,14 +49,14 @@ impl ReplCommand { if !ctx.debug.loaded_bunfig { Arguments::load_config_path( Command::Tag::RunCommand, - true, + Arguments::AutoLoaded::Yes, bun_core::zstr!("bunfig.toml"), ctx, )?; } // Initialize JSC - jsc::initialize(true); // true for eval mode + jsc::initialize(jsc::EvalMode::Yes); // true for eval mode bun_ast::initialize_store(); // The arena is threaded into VirtualMachine (vm.arena). `bun_alloc::Arena` @@ -224,7 +224,10 @@ impl<'a, 'r> ReplRunner<'a, 'r> { if !this.eval_script.is_empty() || this.eval_and_print { // Non-interactive: evaluate the -e/--eval or -p/--print script, // drain the event loop, and exit - let had_error = this.repl.eval_script(this.eval_script, this.eval_and_print); + let had_error = this.repl.eval_script( + this.eval_script, + PrintResult::from_bool(this.eval_and_print), + ); Output::flush(); if had_error { // Only overwrite on error so `process.exitCode = N` in the diff --git a/src/runtime/cli/run_command.rs b/src/runtime/cli/run_command.rs index b0a92bc2ac43..2dacdbdc5573 100644 --- a/src/runtime/cli/run_command.rs +++ b/src/runtime/cli/run_command.rs @@ -15,14 +15,15 @@ use bun_core::{self as core, Environment, Global, Output, ZStr}; use bun_core::{pretty, pretty_errorln, prettyln}; use bun_dotenv as DotEnv; use bun_jsc::js_promise::Status as PromiseStatus; -use bun_jsc::virtual_machine::{InitOptions as VmInitOptions, VirtualMachine}; -use bun_jsc::{JSGlobalObject, JSValue}; +use bun_jsc::virtual_machine::{InitOptions as VmInitOptions, IsRejection, VirtualMachine}; +use bun_jsc::{EvalMode, JSGlobalObject, JSValue}; use bun_md::root as md; use bun_options_types::schema::api; #[cfg(windows)] use bun_paths::WPathBuffer; use bun_paths::strings; use bun_paths::{self as paths, DELIMITER, MAX_PATH_BYTES, PathBuffer, SEP}; +use bun_resolver::fs::StoreFd; use bun_resolver::package_json::PackageJSON; use bun_sys::{self as sys, Fd, FdExt as _}; use bun_which::which; @@ -106,6 +107,15 @@ pub(crate) struct ConfigureEnvOptions { pub(crate) struct RunCommand; +bun_core::bool_enum!(pub(crate) Silent); +bun_core::bool_enum!( + /// Which shell interprets a package.json script body: Bun's built-in shell + /// or the system shell (`sh` / `cmd.exe`). + pub(crate) ScriptShell { Bun, System } +); +bun_core::bool_enum!(WithLinker); +bun_core::bool_enum!(pub(crate) ForceUsingBun); + impl RunCommand { /// `bun run --help` body. pub(crate) fn print_help(package_json: Option<&PackageJSON>) { @@ -253,8 +263,8 @@ Full documentation is available at https://bun.com/docs/cli/run cwd: &[u8], env: &mut DotEnv::Loader, passthrough: &[Box<[u8]>], - silent: bool, - use_system_shell: bool, + silent: Silent, + use_system_shell: ScriptShell, ) -> crate::Result<()> { Self::run_package_script_foreground_with_shell_path( ctx, @@ -278,10 +288,11 @@ Full documentation is available at https://bun.com/docs/cli/run cwd: &[u8], env: &mut DotEnv::Loader, passthrough: &[Box<[u8]>], - silent: bool, - use_system_shell: bool, + silent: Silent, + use_system_shell: ScriptShell, shell_path: Option<&[u8]>, ) -> crate::Result<()> { + let silent = silent == Silent::Yes; let shell_search_path = shell_path.unwrap_or_else(|| env.get(b"PATH").unwrap_or(b"")); let shell_bin = Self::find_shell(shell_search_path, cwd).ok_or(crate::Error::MissingShell)?; @@ -305,7 +316,10 @@ Full documentation is available at https://bun.com/docs/cli/run for part in passthrough { copy_script.push(b' '); - if cfg!(windows) && use_system_shell && bun_which::batch_arg_has_cmd_metachars(part) { + if cfg!(windows) + && use_system_shell == ScriptShell::System + && bun_which::batch_arg_has_cmd_metachars(part) + { if !silent { pretty_errorln!( "error: Failed to run script {}: argument {} contains a cmd.exe special character and cannot be passed to the system shell", @@ -330,7 +344,7 @@ Full documentation is available at https://bun.com/docs/cli/run Output::flush(); } - if !use_system_shell { + if use_system_shell == ScriptShell::Bun { // SAFETY: `MiniEventLoop` stores `env` as a raw `*mut`; the loader // outlives the call (process-lifetime in `configure_env_for_run`). let mini = bun_event_loop::MiniEventLoop::init_global( @@ -459,7 +473,9 @@ Full documentation is available at https://bun.com/docs/cli/run pretty_errorln!( "error: script \"{}\" was terminated by signal {}", bstr::BStr::new(name), - bun_sys::SignalCode(sig as u8).fmt(Output::enable_ansi_colors_stderr()), + bun_sys::SignalCode(sig as u8).fmt(Output::AnsiColors::from_bool( + Output::enable_ansi_colors_stderr() + )), ); Output::flush(); @@ -498,7 +514,9 @@ Full documentation is available at https://bun.com/docs/cli/run pretty_errorln!( "error: script \"{}\" was terminated by signal {}", bstr::BStr::new(name), - bun_sys::SignalCode(sig as u8).fmt(Output::enable_ansi_colors_stderr()), + bun_sys::SignalCode(sig as u8).fmt(Output::AnsiColors::from_bool( + Output::enable_ansi_colors_stderr() + )), ); Output::flush(); } @@ -557,7 +575,7 @@ Full documentation is available at https://bun.com/docs/cli/run env: Option<*mut DotEnv::Loader>, opts: ConfigureEnvOptions, ) -> crate::Result { - Self::configure_env_for_run_impl(ctx, this_transpiler, env, opts, true) + Self::configure_env_for_run_impl(ctx, this_transpiler, env, opts, WithLinker::Yes) } /// Like [`Self::configure_env_for_run`] but does **not** construct the @@ -570,7 +588,7 @@ Full documentation is available at https://bun.com/docs/cli/run env: Option<*mut DotEnv::Loader>, opts: ConfigureEnvOptions, ) -> crate::Result { - Self::configure_env_for_run_impl(ctx, this_transpiler, env, opts, false) + Self::configure_env_for_run_impl(ctx, this_transpiler, env, opts, WithLinker::No) } /// `configure_linker()` + `load_tsconfig_json` setup, factored into a @@ -593,7 +611,7 @@ Full documentation is available at https://bun.com/docs/cli/run this_transpiler: &mut ::core::mem::MaybeUninit>, env: Option<*mut DotEnv::Loader>, opts: ConfigureEnvOptions, - with_linker: bool, + with_linker: WithLinker, ) -> crate::Result { let args = ctx.args.clone(); let env_is_none = env.is_none(); @@ -613,14 +631,14 @@ Full documentation is available at https://bun.com/docs/cli/run this_transpiler.resolver.care_about_bin_folder = true; this_transpiler.resolver.care_about_scripts = true; - this_transpiler.resolver.store_fd = opts.store_root_fd; + this_transpiler.resolver.store_fd = StoreFd::from_bool(opts.store_root_fd); // Bundler-linker + JSX-runtime config: only callers that actually // transpile through this `Transpiler` need it. `configure_linker`'s // auto-JSX step reads the cwd `DirInfo` (and, with `load_tsconfig_json` // on, its `tsconfig.json`) — keep it ahead of the `read_dir_info` below // so that read populates/uses the same cache entry. - if with_linker { + if with_linker == WithLinker::Yes { Self::configure_run_transpiler_linker(this_transpiler); } @@ -659,7 +677,7 @@ Full documentation is available at https://bun.com/docs/cli/run Ok(Some(info)) => info, }; - this_transpiler.resolver.store_fd = false; + this_transpiler.resolver.store_fd = StoreFd::No; if env_is_none { // Re-derive — borrowck won't let `env_loader` straddle the @@ -677,7 +695,7 @@ Full documentation is available at https://bun.com/docs/cli/run // Always skip default .env files for package.json script runner // (the script's own bun instance loads .env) - let _ = this_transpiler.run_env_loader(true); + let _ = this_transpiler.run_env_loader(DotEnv::SkipDefaultEnv::Yes); } // Re-derive after `run_env_loader` — that call creates its own @@ -874,7 +892,7 @@ Full documentation is available at https://bun.com/docs/cli/run Global::exit(1); } - bun_http::async_http::preconnect(url, false); + bun_http::async_http::preconnect(url, bun_http::async_http::UrlOwnership::Borrowed); } } @@ -896,7 +914,9 @@ Full documentation is available at https://bun.com/docs/cli/run args.write = Some(false); args.target = Some(api::Target::Bun); let mut bundle = Transpiler::init(runner_arena(), ctx.log, args, None)?; - bundle.run_env_loader(bundle.options.env.disable_default_env_files)?; + bundle.run_env_loader(DotEnv::SkipDefaultEnv::from_bool( + bundle.options.env.disable_default_env_files, + ))?; let top_level_dir: &[u8] = ctx.args.absolute_working_dir.as_deref().unwrap_or(b""); let mini = bun_event_loop::MiniEventLoop::init_global( @@ -931,7 +951,7 @@ Full documentation is available at https://bun.com/docs/cli/run if !ctx.debug.loaded_bunfig { arguments::load_config_path( CommandTag::RunCommand, - true, + arguments::AutoLoaded::Yes, bun_core::zstr!("bunfig.toml"), ctx, )?; @@ -948,7 +968,7 @@ Full documentation is available at https://bun.com/docs/cli/run // dispatch hooks (`jsc_hooks::install_jsc_hooks`) are installed by // `main.rs` before `Cli::start`, so `VirtualMachine::init` already sees // a populated `RuntimeHooks` table. - bun_jsc::initialize(ctx.runtime_options.eval.eval_and_print); + bun_jsc::initialize(EvalMode::from_bool(ctx.runtime_options.eval.eval_and_print)); bun_ast::initialize_store(); let vm_ptr = VirtualMachine::init(VmInitOptions { @@ -969,7 +989,8 @@ Full documentation is available at https://bun.com/docs/cli/run vm.preload = std::mem::take(&mut ctx.preloads); vm.argv = std::mem::take(&mut ctx.passthrough); // `InitOptions` has no `store_fd` field, so set it on the resolver directly. - vm.transpiler.resolver.store_fd = ctx.debug.hot_reload != cli::command::HotReload::None; + vm.transpiler.resolver.store_fd = + StoreFd::from_bool(ctx.debug.hot_reload != cli::command::HotReload::None); // `vm.dns_result_order` is a `u8` until the b2-cycle widens // it to `bun_dns::Order`; the enum is `#[repr(u8)]` so `as u8` is exact. vm.dns_result_order = @@ -1116,7 +1137,7 @@ Full documentation is available at https://bun.com/docs/cli/run ) -> crate::Result<()> { use bun_standalone_graph::StandaloneModuleGraph::Flags as GraphFlags; - bun_jsc::initialize(false); + bun_jsc::initialize(EvalMode::No); bun_analytics::features::standalone_executable.fetch_add(1, Ordering::Relaxed); bun_ast::initialize_store(); @@ -1125,7 +1146,7 @@ Full documentation is available at https://bun.com/docs/cli/run if !ctx.debug.loaded_bunfig && !graph.flags.contains(GraphFlags::DISABLE_AUTOLOAD_BUNFIG) { arguments::load_config_path( CommandTag::RunCommand, - true, + arguments::AutoLoaded::Yes, bun_core::zstr!("bunfig.toml"), ctx, )?; @@ -1426,7 +1447,11 @@ impl Run<'_> { // rejection reports origin "unhandledRejection". let is_rejection = !vm.entry_point_result.evaluated_as_cjs; // SAFETY: `global` valid for VM lifetime. - let handled = vm.uncaught_exception(unsafe { &*global }, result, is_rejection); + let handled = vm.uncaught_exception( + unsafe { &*global }, + result, + IsRejection::from_bool(is_rejection), + ); promise.set_handled(); vm.pending_internal_promise_reported_at = vm.hot_reload_counter; @@ -1460,7 +1485,7 @@ impl Run<'_> { vm.global().vm().release_weak_refs(); // `bun_alloc::Arena` has no per-heap collect to run alongside this // GC; it would only be a memory-usage hint, not correctness. - let _ = vm.global().vm().run_gc(false); + let _ = vm.global().vm().run_gc(bun_jsc::GcMode::Async); vm.tick(); } @@ -1812,7 +1837,7 @@ impl RunCommand { this_transpiler: &mut Transpiler<'static>, original_path: Option<&mut Vec>, cwd: &[u8], - force_using_bun: bool, + force_using_bun: ForceUsingBun, ) -> crate::Result<()> { let mut package_json_dir: &[u8] = b""; @@ -1850,8 +1875,9 @@ impl RunCommand { this_transpiler: &mut Transpiler<'static>, original_path: Option<&mut Vec>, cwd: &[u8], - force_using_bun: bool, + force_using_bun: ForceUsingBun, ) -> crate::Result> { + let force_using_bun = force_using_bun == ForceUsingBun::Yes; let env_loader = this_transpiler.env_mut(); // Snapshot PATH up front. The env // map owns `Box<[u8]>` values, so a borrow would dangle once the @@ -2033,8 +2059,8 @@ impl RunCommand { ) } - fn run_binary_generic_error(executable: &[u8], silent: bool, err: &sys::Error) -> ! { - if !silent { + fn run_binary_generic_error(executable: &[u8], silent: Silent, err: &sys::Error) -> ! { + if silent == Silent::No { pretty_errorln!( "error: Failed to run \"{}\" due to:\n{}", bstr::BStr::new(Self::basename_or_bun(executable)), @@ -2138,14 +2164,14 @@ impl RunCommand { match spawn_result { Err(err) => { // an error occurred while spawning the process - Self::run_binary_generic_error(executable, silent, &err); + Self::run_binary_generic_error(executable, Silent::from_bool(silent), &err); } Ok(result) => { let signal_code = result.status.signal_code(); match result.status { // An error occurred after the process was spawned. SpawnStatus::Err(err) => { - Self::run_binary_generic_error(executable, silent, &err); + Self::run_binary_generic_error(executable, Silent::from_bool(silent), &err); } SpawnStatus::Signaled(signal) => { @@ -2295,7 +2321,7 @@ impl RunCommand { // command opts in via `read_global_config`) then `bunfig.toml`. let _ = arguments::load_config_path( CommandTag::RunCommand, - true, + arguments::AutoLoaded::Yes, bun_core::zstr!("bunfig.toml"), ctx, ); @@ -2343,7 +2369,7 @@ impl RunCommand { this_transpiler, Some(&mut original_path), root_dir_info.abs_path, - force_using_bun, + ForceUsingBun::from_bool(force_using_bun), )?; let env_loader: &mut DotEnv::Loader = this_transpiler.env_mut(); env_loader @@ -2413,8 +2439,8 @@ impl RunCommand { // field of `ctx` but `run_package_script_foreground` // takes `&mut ContextData`; clone the slice up-front. let passthrough: Vec> = ctx.passthrough.clone(); - let silent = ctx.debug.silent; - let use_system_shell = ctx.debug.use_system_shell; + let silent = Silent::from_bool(ctx.debug.silent); + let use_system_shell = ScriptShell::from_bool(ctx.debug.use_system_shell); if let Some(&prescript) = scripts.get(&temp_script_buffer[1..]) { Self::run_package_script_foreground_with_shell_path( @@ -3487,7 +3513,7 @@ impl RunCommand { this_transpiler.resolver.care_about_bin_folder = true; this_transpiler.resolver.care_about_scripts = true; - this_transpiler.resolver.store_fd = true; + this_transpiler.resolver.store_fd = StoreFd::Yes; this_transpiler.configure_linker(); // SAFETY: `Transpiler::fs` is the non-null process-static singleton. @@ -3563,8 +3589,9 @@ impl RunCommand { // SAFETY: `Transpiler::fs` is the non-null process-static // singleton; the lazy-stat rewrite inside `kind()` is // serialized on the per-entry mutex. - if unsafe { value.kind(&raw mut (*this_transpiler.fs).fs, true) } - == bun_resolver::fs::EntryKind::File + if unsafe { + value.kind(&raw mut (*this_transpiler.fs).fs, StoreFd::Yes) + } == bun_resolver::fs::EntryKind::File { if !has_copied { path_buf[..value.dir.len()].copy_from_slice(value.dir); @@ -3633,7 +3660,7 @@ impl RunCommand { // SAFETY: `Transpiler::fs` is the non-null process-static // singleton; the lazy-stat rewrite inside `kind()` is // serialized on the per-entry mutex. - && unsafe { value.kind(&raw mut (*this_transpiler.fs).fs, true) } + && unsafe { value.kind(&raw mut (*this_transpiler.fs).fs, StoreFd::Yes) } == bun_resolver::fs::EntryKind::File { // SAFETY: `Transpiler::fs` is the non-null process-static singleton. diff --git a/src/runtime/cli/test/Scanner.rs b/src/runtime/cli/test/Scanner.rs index 5182755e3614..cac9aaeff30e 100644 --- a/src/runtime/cli/test/Scanner.rs +++ b/src/runtime/cli/test/Scanner.rs @@ -258,7 +258,7 @@ impl<'a> Scanner<'a> { let raw = handle.map(bun_sys::Dir::into_raw); // SAFETY: borrows only the `fs` field; re-entrant access is serialised by `RealFS.entries_mutex`. unsafe { &mut (*fs_ptr).fs } - .read_directory_with_iterator(name, raw, 0, true, iter) + .read_directory_with_iterator(name, raw, 0, fs::StoreFd::Yes, iter) .map_err(Into::into) } @@ -360,7 +360,7 @@ impl<'a> Scanner<'a> { // SAFETY: `self.fs` is the process singleton. let real_fs = unsafe { &raw mut (*self.fs).fs }; // SAFETY: caller holds `entries_mutex`; the direct path is single-threaded. - match unsafe { entry.kind(real_fs, true) } { + match unsafe { entry.kind(real_fs, fs::StoreFd::Yes) } { fs::EntryKind::Dir => { if (!name.is_empty() && name[0] == b'.') || name == b"node_modules" { return; diff --git a/src/runtime/cli/test/Timings.rs b/src/runtime/cli/test/Timings.rs index 5ddb0dff3e56..20f9830732c0 100644 --- a/src/runtime/cli/test/Timings.rs +++ b/src/runtime/cli/test/Timings.rs @@ -17,6 +17,8 @@ use bun_sys::{Fd, File}; use super::parallel::file_range::FileRange; +bun_core::bool_enum!(pub OnlyMeasured); + pub struct Timings { /// The first `--timings` path; `--update-timings` writes here. path: Box<[u8]>, @@ -192,8 +194,8 @@ impl Timings { /// report. `only_measured` (set under `--shard`) writes just the files this /// run ran; otherwise everything read is carried through so a local partial /// run doesn't shrink the table. - pub fn write(&mut self, only_measured: bool) { - let map = if only_measured { + pub fn write(&mut self, only_measured: OnlyMeasured) { + let map = if only_measured == OnlyMeasured::Yes { &mut self.measured } else { &mut self.map diff --git a/src/runtime/cli/test/parallel/Channel.rs b/src/runtime/cli/test/parallel/Channel.rs index 36269cba9e79..a5617d30af37 100644 --- a/src/runtime/cli/test/parallel/Channel.rs +++ b/src/runtime/cli/test/parallel/Channel.rs @@ -220,7 +220,7 @@ impl Channel { // frame. let mut pipe = Box::new(bun_core::ffi::zeroed::()); if let Some(e) = pipe - .init(uv::Loop::get(), true) + .init(uv::Loop::get(), uv::Ipc::Yes) .to_error(bun_sys::Tag::pipe) { bun_core::debug_warn!( @@ -255,7 +255,8 @@ impl Channel { // thread here; route through the safe singleton accessor. let vm: &mut VirtualMachine = VirtualMachine::get().as_mut(); let g = Self::ensure_posix_group(vm); - let Some(sock) = Socket::from_fd(g, uws::SocketKind::Dynamic, fd, this, true) else { + let Some(sock) = Socket::from_fd(g, uws::SocketKind::Dynamic, fd, this, uws::Ipc::Yes) + else { // us_socket_from_fd does NOT take ownership on failure; leaving // the inherited IPC endpoint open keeps the peer process alive. fd.close(); diff --git a/src/runtime/cli/test/parallel/Worker.rs b/src/runtime/cli/test/parallel/Worker.rs index 7030ed3b3839..dfa552a2d8c2 100644 --- a/src/runtime/cli/test/parallel/Worker.rs +++ b/src/runtime/cli/test/parallel/Worker.rs @@ -161,13 +161,13 @@ impl Worker { if let Some(fd) = stdout { this.out .reader - .start(fd, true) + .start(fd, bun_io::IsPollable::Yes) .map_err(|_| crate::Error::PipeStartFailed)?; } if let Some(fd) = stderr { this.err .reader - .start(fd, true) + .start(fd, bun_io::IsPollable::Yes) .map_err(|_| crate::Error::PipeStartFailed)?; } if !extra_pipes.is_empty() { diff --git a/src/runtime/cli/test/parallel/aggregate.rs b/src/runtime/cli/test/parallel/aggregate.rs index c29f11d380e1..7d7dc1f1ef2c 100644 --- a/src/runtime/cli/test/parallel/aggregate.rs +++ b/src/runtime/cli/test/parallel/aggregate.rs @@ -342,7 +342,7 @@ pub(crate) fn merge_coverage_fragments( base, frac.failing, &mut body, - true, + CoverageReportText::IndentName::Yes, ); let _ = body.write_all(Output::pretty_fmt::(" | ").as_ref()); @@ -388,7 +388,7 @@ pub(crate) fn merge_coverage_fragments( base, failing, &mut all_files, - false, + CoverageReportText::IndentName::No, ); let _ = console.write_all(&all_files); let _ = console.write_all(Output::pretty_fmt::(" |\n").as_ref()); diff --git a/src/runtime/cli/test/parallel/runner.rs b/src/runtime/cli/test/parallel/runner.rs index e6b14c5b2100..45d01d264638 100644 --- a/src/runtime/cli/test/parallel/runner.rs +++ b/src/runtime/cli/test/parallel/runner.rs @@ -578,7 +578,7 @@ impl<'a> WorkerLoop<'a> { .bun_test_root .reset_hook_scope_for_test_isolation(); } else { - Global::mimalloc_cleanup(false); + Global::mimalloc_cleanup(bun_core::Force::No); } self.reporter.jest.default_timeout_override = u32::MAX; diff --git a/src/runtime/cli/test_command.rs b/src/runtime/cli/test_command.rs index 577d075d380d..f099bc60cc69 100644 --- a/src/runtime/cli/test_command.rs +++ b/src/runtime/cli/test_command.rs @@ -4,12 +4,12 @@ use crate::cli::Command; use crate::cli::test::changed_files_filter as ChangedFilesFilter; use crate::cli::test::parallel_runner as ParallelRunner; use crate::cli::test::scanner::{self, Scanner}; -use crate::cli::test::timings::Timings; +use crate::cli::test::timings::{OnlyMeasured, Timings}; use bun_collections::BoundedArray; use bun_core::{self as bun, Global, Output, env_var, fmt as bun_fmt}; use bun_core::{pretty_error, pretty_errorln}; use bun_dotenv as DotEnv; -use bun_jsc::virtual_machine::VirtualMachine; +use bun_jsc::virtual_machine::{BlockUntilConnected, VirtualMachine}; use bun_jsc::{self as jsc}; // `set_time_zone` / `delete_module_registry_entry` take the JSC-side // `ZigString` (repr(C)-identical to `bun_core::ZigString`, but with the @@ -35,6 +35,7 @@ bun_output::declare_scope!(bun_test, hidden); // Drop once the body is normalised to call `code_coverage::{text,lcov}` // directly with ``. mod coverage { + pub(super) use bun_sourcemap_jsc::code_coverage::text::IndentName; pub(super) use bun_sourcemap_jsc::code_coverage::{ ByteRangeMapping, Fraction, Report as CodeCoverageReport, lcov as Lcov, }; @@ -85,7 +86,7 @@ mod coverage { failing: Fraction, failed: bool, writer: &mut impl bun_io::Write, - indent_name: bool, + indent_name: text::IndentName, enable_ansi_colors: bool, ) -> bun_io::Result<()> { if enable_ansi_colors { @@ -184,20 +185,20 @@ pub(crate) fn escape_xml(str_: &[u8], writer: &mut impl bun_io::Write) -> crate: fn fmt_status_text_line( status: bun_test::Execution::Result, - emoji_or_color: bool, + emoji_or_color: Output::AnsiColors, ) -> Output::PrettyBuf { // emoji and color might be split into two different options in the future // some terminals support color, but not emoji. // For now, they are the same. match emoji_or_color { - true => match status.basic_result() { + Output::AnsiColors::Enabled => match status.basic_result() { bun_test::BasicResult::Pending => Output::pretty_fmt::(""), bun_test::BasicResult::Pass => Output::pretty_fmt::(""), bun_test::BasicResult::Fail => Output::pretty_fmt::(""), bun_test::BasicResult::Skip => Output::pretty_fmt::("»"), bun_test::BasicResult::Todo => Output::pretty_fmt::(""), }, - false => match status.basic_result() { + Output::AnsiColors::Disabled => match status.basic_result() { bun_test::BasicResult::Pending => Output::pretty_fmt::("(pending)"), bun_test::BasicResult::Pass => Output::pretty_fmt::("(pass)"), bun_test::BasicResult::Fail => Output::pretty_fmt::("(fail)"), @@ -263,6 +264,12 @@ pub struct JunitReporter { pub(crate) hostname_value: Option>, } +bun_core::bool_enum!( + /// Whether a JUnit `` corresponds to a whole test file (as + /// opposed to a `describe` block within one). + pub(crate) IsFileSuite +); + #[derive(Default)] pub struct SuiteInfo { pub name: Box<[u8]>, @@ -389,7 +396,11 @@ impl JunitReporter { } body.extend_from_slice(b" at "); if !func.slice().is_empty() { - let _ = write!(body, "{} (", frame.name_formatter(false)); + let _ = write!( + body, + "{} (", + frame.name_formatter(Output::AnsiColors::Disabled) + ); } let file_start = body.len(); body.extend_from_slice(file); @@ -522,15 +533,16 @@ impl JunitReporter { } pub(crate) fn begin_test_suite(&mut self, name: &[u8]) -> crate::Result<()> { - self.begin_test_suite_with_line(name, 0, true) + self.begin_test_suite_with_line(name, 0, IsFileSuite::Yes) } pub(crate) fn begin_test_suite_with_line( &mut self, name: &[u8], line_number: u32, - is_file_suite: bool, + is_file_suite: IsFileSuite, ) -> crate::Result<()> { + let is_file_suite = is_file_suite == IsFileSuite::Yes; if self.contents.is_empty() && !self.elements_only { self.contents .extend_from_slice(b"\n"); @@ -1333,7 +1345,7 @@ impl CommandLineReporter { ) }; junit - .begin_test_suite_with_line(name, line_no, false) + .begin_test_suite_with_line(name, line_no, IsFileSuite::No) .expect("oom"); describe_suite_index += 1; } @@ -1433,9 +1445,11 @@ impl CommandLineReporter { buntest.bun_test_root.on_before_print(); if Output::enable_ansi_colors_stderr() { - let _ = writer.write_all(&fmt_status_text_line(result, true)); + let _ = writer + .write_all(&fmt_status_text_line(result, Output::AnsiColors::Enabled)); } else { - let _ = writer.write_all(&fmt_status_text_line(result, false)); + let _ = writer + .write_all(&fmt_status_text_line(result, Output::AnsiColors::Disabled)); } let dim = match basic { bun_test::BasicResult::Todo => { @@ -1558,7 +1572,9 @@ impl CommandLineReporter { && self.worker_ipc_file_idx.is_none() && let Some(timings) = self.timings.as_mut() { - timings.write(self.jest.test_options.shard.is_some()); + timings.write(OnlyMeasured::from_bool( + self.jest.test_options.shard.is_some(), + )); } } @@ -1977,7 +1993,7 @@ impl CommandLineReporter { failed, failing, &mut console, - false, + coverage::IndentName::No, ENABLE_ANSI_COLORS, )?; @@ -2131,7 +2147,10 @@ impl TestCommand { // `exec()` never returns before process exit, so the heap allocation // outlives all observers. let mut env_loader: Box = Box::new(DotEnv::Loader::init()); - jsc::initialize_with(false, ctx.test_options.isolate); + jsc::initialize_with( + jsc::EvalMode::No, + jsc::ShortLivedGlobals::from_bool(ctx.test_options.isolate), + ); bun_http::http_thread::init(&Default::default()); let enable_random = ctx.test_options.randomize; @@ -2352,7 +2371,7 @@ impl TestCommand { // Start the debugger before we scan for files // But, don't block the main thread waiting if they used --inspect-wait. - vm.ensure_debugger(false)?; + vm.ensure_debugger(BlockUntilConnected::No)?; let mut scanner = Scanner::init(&vm.transpiler, ctx.positionals.len()).expect("oom"); // SAFETY: lifetime-erase; `path_ignore_patterns_view` lives in this never-returning @@ -3118,7 +3137,7 @@ impl TestCommand { t.record_since(file_name.as_bytes(), started); } reporter.jest.default_timeout_override = u32::MAX; - Global::mimalloc_cleanup(false); + Global::mimalloc_cleanup(bun_core::Force::No); if isolate { crate::jsc_hooks::stop_active_handles_for_test_isolation(vm); vm.swap_global_for_test_isolation(); diff --git a/src/runtime/cli/unlink_command.rs b/src/runtime/cli/unlink_command.rs index cfababcd13c5..05cf338e8271 100644 --- a/src/runtime/cli/unlink_command.rs +++ b/src/runtime/cli/unlink_command.rs @@ -213,7 +213,7 @@ fn unlink(ctx: &mut ContextData) -> crate::Result<()> { err: None, skipped_due_to_missing_bin: false, }; - bin_linker.unlink(true); + bin_linker.unlink(bun_install::Scope::Global); } // delete it if it exists diff --git a/src/runtime/cli/update_interactive_command.rs b/src/runtime/cli/update_interactive_command.rs index cbae1c6acf25..a20837994f06 100644 --- a/src/runtime/cli/update_interactive_command.rs +++ b/src/runtime/cli/update_interactive_command.rs @@ -11,6 +11,7 @@ use bun_core::{Global, Output}; use bun_install::dependency::{self, Behavior}; use bun_install::lockfile::package::PackageColumns as _; use bun_install::lockfile::{LoadResult, LoadStep}; +use bun_install::npm::ExtendedManifest; use bun_install::package_manager::options::Do; use bun_install::package_manager::{ LogLevel, ManifestLoad, Subcommand, WorkspaceFilter, populate_manifest_cache, @@ -158,6 +159,12 @@ struct TerminalSize { width: usize, } +bun_core::bool_enum!( + /// Where `truncate_with_ellipsis` places the ellipsis: in the middle + /// (showing both start and end) or at the end (keeping only the start). + EllipsisAt { Middle, End } +); + impl UpdateInteractiveCommand { // Common utility functions to reduce duplication @@ -833,7 +840,7 @@ impl UpdateInteractiveCommand { // borrow from `manager`, so the caller may keep using it afterwards. let cache_ctx = manager.manifest_disk_cache_ctx(); let min_age_ms = manager.options.minimum_release_age_ms; - let needs_extended = min_age_ms.is_some(); + let needs_extended = ExtendedManifest::from_bool(min_age_ms.is_some()); let excludes = manager.options.minimum_release_age_excludes; let update_to_latest = manager.options.do_.update_to_latest(); let default_url_hash = *bun_install::npm::Registry::DEFAULT_URL_HASH; @@ -1156,7 +1163,7 @@ impl UpdateInteractiveCommand { } // Default fallback } - fn truncate_with_ellipsis(text: &[u8], max_width: usize, only_end: bool) -> Box<[u8]> { + fn truncate_with_ellipsis(text: &[u8], max_width: usize, only_end: EllipsisAt) -> Box<[u8]> { if text.len() <= max_width { return Box::from(text); } @@ -1168,7 +1175,7 @@ impl UpdateInteractiveCommand { // Put ellipsis in the middle to show both start and end of package name let ellipsis = "…".as_bytes(); let available_chars = max_width - 1; // Reserve 1 char for ellipsis - let start_chars = if only_end { + let start_chars = if only_end == EllipsisAt::End { available_chars } else { available_chars / 2 @@ -1409,7 +1416,7 @@ impl UpdateInteractiveCommand { current_size .width .saturating_sub(b"? Select packages to update - ".len()), - true, + EllipsisAt::End, ); bun_core::prettyln!( "? Select packages to update - {}", @@ -1649,8 +1656,11 @@ impl UpdateInteractiveCommand { } else { state.max_name_len }; - let display_name = - Self::truncate_with_ellipsis(&pkg.name, available_name_width, false); + let display_name = Self::truncate_with_ellipsis( + &pkg.name, + available_name_width, + EllipsisAt::Middle, + ); let package_url: Box<[u8]> = if Output::enable_ansi_colors_stdout() && pkg.uses_default_registry { @@ -1716,7 +1726,7 @@ impl UpdateInteractiveCommand { let truncated_current = Self::truncate_with_ellipsis( &pkg.current_version, state.max_current_len, - false, + EllipsisAt::Middle, ); bun_core::pretty!("{}", BStr::new(&truncated_current)); @@ -1742,7 +1752,7 @@ impl UpdateInteractiveCommand { let truncated_target = Self::truncate_with_ellipsis( &pkg.update_version, state.max_update_len, - false, + EllipsisAt::Middle, ); // For width calculation, use the truncated version string length @@ -1813,7 +1823,7 @@ impl UpdateInteractiveCommand { let truncated_latest = Self::truncate_with_ellipsis( &pkg.latest_version, state.max_latest_len, - false, + EllipsisAt::Middle, ); if current_ver_parsed.valid && latest_ver_parsed.valid { let current_full = semver::Version { @@ -1893,7 +1903,7 @@ impl UpdateInteractiveCommand { let truncated_workspace = Self::truncate_with_ellipsis( &pkg.workspace_name, state.max_workspace_len, - true, + EllipsisAt::End, ); bun_core::pretty!("{}", BStr::new(&truncated_workspace)); } diff --git a/src/runtime/cli/upgrade_command.rs b/src/runtime/cli/upgrade_command.rs index fcd16bf1b0bf..06c3eb1112af 100644 --- a/src/runtime/cli/upgrade_command.rs +++ b/src/runtime/cli/upgrade_command.rs @@ -174,6 +174,8 @@ static Bun__githubURL: SyncCStr = SyncCStr( pub(crate) struct UpgradeCommand; +bun_core::bool_enum!(pub(crate) UseProfile); + impl UpgradeCommand { const DEFAULT_GITHUB_HEADERS: &'static [u8] = b"Acceptapplication/vnd.github.v3+json"; @@ -181,8 +183,9 @@ impl UpgradeCommand { env_loader: &mut DotEnv::Loader, refresher: Option<&mut Progress::Progress>, mut progress: Option<&mut Progress::Node>, - use_profile: bool, + use_profile: UseProfile, ) -> crate::Result> { + let use_profile = use_profile == UseProfile::Yes; let mut headers_buf: Vec = Self::DEFAULT_GITHUB_HEADERS.to_vec(); let mut header_entries: headers::EntryList = headers::EntryList::default(); @@ -575,7 +578,7 @@ impl UpgradeCommand { Some(unsafe { &mut *refresher }), // SAFETY: progress points into the same leaked allocation (see above). Some(unsafe { &mut *progress }), - use_profile, + UseProfile::from_bool(use_profile), )? else { return Ok(()); diff --git a/src/runtime/cli/why_command.rs b/src/runtime/cli/why_command.rs index 095c8e8db2ce..9be9260520cf 100644 --- a/src/runtime/cli/why_command.rs +++ b/src/runtime/cli/why_command.rs @@ -266,6 +266,8 @@ impl<'a> GlobPattern<'a> { } } +bun_core::bool_enum!(TopOnly); + impl WhyCommand { fn print_usage() { bun_core::prettyln!( @@ -310,10 +312,10 @@ impl WhyCommand { Self::print_usage(); Global::exit(1); } - return Self::exec_with_manager(ctx, pm, positionals[1], top_only); + return Self::exec_with_manager(ctx, pm, positionals[1], TopOnly::from_bool(top_only)); } - Self::exec_with_manager(ctx, pm, positionals[0], top_only) + Self::exec_with_manager(ctx, pm, positionals[0], TopOnly::from_bool(top_only)) } pub(crate) fn exec_from_pm( @@ -326,15 +328,21 @@ impl WhyCommand { Global::exit(1); } - Self::exec_with_manager(ctx, pm, positionals[1], pm.options.top_only) + Self::exec_with_manager( + ctx, + pm, + positionals[1], + TopOnly::from_bool(pm.options.top_only), + ) } fn exec_with_manager( ctx: command::Context, pm: &mut PackageManager, package_pattern: &[u8], - top_only: bool, + top_only: TopOnly, ) -> Result<(), crate::Error> { + let top_only = top_only == TopOnly::Yes; // Detach the `Box` from `pm` // so `load_from_cwd` can take `Option<&mut PackageManager>` without // overlapping the `&mut self` lockfile borrow. `pm.options.depth` is read @@ -510,8 +518,8 @@ impl WhyCommand { PREFIX_CONTINUE }, 1, - is_last, - dep.workspace, + PrintedBreakLine::from_bool(is_last), + ParentIsWorkspace::from_bool(dep.workspace), ); } } @@ -581,14 +589,19 @@ impl<'a> TreeContext<'a> { } } +bun_core::bool_enum!(PrintedBreakLine); +bun_core::bool_enum!(ParentIsWorkspace); + fn print_dependency_tree( ctx: &mut TreeContext<'_>, current_pkg_id: PackageID, prefix: &[u8], depth: usize, - printed_break_line: bool, - parent_is_workspace: bool, + printed_break_line: PrintedBreakLine, + parent_is_workspace: ParentIsWorkspace, ) { + let printed_break_line = printed_break_line == PrintedBreakLine::Yes; + let parent_is_workspace = parent_is_workspace == ParentIsWorkspace::Yes; if ctx.path_tracker.get(¤t_pkg_id).is_some() { bun_core::prettyln!("{}└─ *circular", BStr::new(prefix)); return; @@ -641,8 +654,8 @@ fn print_dependency_tree( dep.pkg_id, &next_prefix, depth + 1, - printed_break_line || print_break_line, - dep.workspace, + PrintedBreakLine::from_bool(printed_break_line || print_break_line), + ParentIsWorkspace::from_bool(dep.workspace), ); if print_break_line { diff --git a/src/runtime/dispatch.rs b/src/runtime/dispatch.rs index 5111bb4e6f83..cdda4f0b0d3e 100644 --- a/src/runtime/dispatch.rs +++ b/src/runtime/dispatch.rs @@ -650,7 +650,7 @@ pub(crate) unsafe fn __bun_run_file_poll(poll: *mut FilePoll, size_or_offset: i6 // SAFETY: contract above. let poll_ref = unsafe { &mut *poll }; let owner = poll_ref.owner; - let hup = poll_ref.flags.contains(PollFlag::Hup); + let hup = bun_io::ReceivedHup::from_bool(poll_ref.flags.contains(PollFlag::Hup)); debug_assert!(!owner.is_null()); diff --git a/src/runtime/dns_jsc/dns.rs b/src/runtime/dns_jsc/dns.rs index 64e9203f4a13..f8940ecbc171 100644 --- a/src/runtime/dns_jsc/dns.rs +++ b/src/runtime/dns_jsc/dns.rs @@ -4884,12 +4884,12 @@ impl Resolver { // direction armed would busy-loop on level-triggered writable // once the socket connects. Full resync is the simplest // correct path and c-ares DNS fds are short-lived. - let _ = poll.unregister(loop_, false); + let _ = poll.unregister(loop_, Async::ForceUnregister::No); if readable { - let _ = poll.register(loop_, Async::PollKind::Readable, false); + let _ = poll.register(loop_, Async::PollKind::Readable, Async::OneShot::No); } if writable { - let _ = poll.register(loop_, Async::PollKind::Writable, false); + let _ = poll.register(loop_, Async::PollKind::Writable, Async::OneShot::No); } } else { // Only adding directions (or no change). register() issues a @@ -4897,10 +4897,10 @@ impl Resolver { // on kqueue EV_ADD creates a separate (ident, filter) knote // without disturbing the existing one. if readable && !have_readable { - let _ = poll.register(loop_, Async::PollKind::Readable, false); + let _ = poll.register(loop_, Async::PollKind::Readable, Async::OneShot::No); } if writable && !have_writable { - let _ = poll.register(loop_, Async::PollKind::Writable, false); + let _ = poll.register(loop_, Async::PollKind::Writable, Async::OneShot::No); } } } diff --git a/src/runtime/hw_exports.rs b/src/runtime/hw_exports.rs index c3158beb8a39..e84af8bbd0a4 100644 --- a/src/runtime/hw_exports.rs +++ b/src/runtime/hw_exports.rs @@ -416,7 +416,12 @@ pub fn bindgen_bunobject_dispatch_gc( // `garbage_collect(force)`: mimalloc cleanup, then sync `runGC(true)` // when `force`, else `collect_async()` + `heap_size()`. // SAFETY: bun_vm() never null for a Bun-owned global. - unsafe { *out = global.bun_vm().as_mut().garbage_collect(force) }; + unsafe { + *out = global + .bun_vm() + .as_mut() + .garbage_collect(bun_jsc::GcMode::from_bool(force)) + }; true } diff --git a/src/runtime/image/Image.rs b/src/runtime/image/Image.rs index 9269a6e9ca86..48c6fcfb085b 100644 --- a/src/runtime/image/Image.rs +++ b/src/runtime/image/Image.rs @@ -1952,11 +1952,11 @@ impl PipelineTask { d.height = next.height; } if p.flip { - let next = codecs::flip(&d.rgba, d.width, d.height, false)?; + let next = codecs::flip(&d.rgba, d.width, d.height, codecs::FlipAxis::Vertical)?; d.rgba = next; } if p.flop { - let next = codecs::flip(&d.rgba, d.width, d.height, true)?; + let next = codecs::flip(&d.rgba, d.width, d.height, codecs::FlipAxis::Horizontal)?; d.rgba = next; } if let Some(r) = p.resize { @@ -2060,11 +2060,11 @@ fn apply_orientation( ) -> Result<(), codecs::Error> { let t = orient.transform(); if t.flip { - let next = codecs::flip(&d.rgba, d.width, d.height, false)?; + let next = codecs::flip(&d.rgba, d.width, d.height, codecs::FlipAxis::Vertical)?; d.rgba = next; } if t.flop { - let next = codecs::flip(&d.rgba, d.width, d.height, true)?; + let next = codecs::flip(&d.rgba, d.width, d.height, codecs::FlipAxis::Horizontal)?; d.rgba = next; } if t.rotate != 0 { diff --git a/src/runtime/image/backend_coregraphics.rs b/src/runtime/image/backend_coregraphics.rs index 21e6cce2d6a9..a96017bba692 100644 --- a/src/runtime/image/backend_coregraphics.rs +++ b/src/runtime/image/backend_coregraphics.rs @@ -267,12 +267,24 @@ pub(crate) fn rotate(src: &[u8], w: u32, h: u32, quarters: u32) -> Result Result, BackendError> { +pub(crate) fn flip( + src: &[u8], + w: u32, + h: u32, + horizontal: codecs::FlipAxis, +) -> Result, BackendError> { // PERF: zero-fill alloc — profile if hot. let mut out = vec![0u8; (w as usize) * (h as usize) * 4]; // SAFETY: src and out both have w*h*4 bytes. - if unsafe { bun_coregraphics_reflect(src.as_ptr(), w, h, out.as_mut_ptr(), horizontal as i32) } - != CG_OK + if unsafe { + bun_coregraphics_reflect( + src.as_ptr(), + w, + h, + out.as_mut_ptr(), + (horizontal == codecs::FlipAxis::Horizontal) as i32, + ) + } != CG_OK { return Err(BackendError::BackendUnavailable); } diff --git a/src/runtime/image/codecs.rs b/src/runtime/image/codecs.rs index 469b049ed1d8..e0fcb7379e23 100644 --- a/src/runtime/image/codecs.rs +++ b/src/runtime/image/codecs.rs @@ -753,7 +753,9 @@ pub(crate) fn rotate(src: &[u8], w: u32, h: u32, degrees: u32) -> Result Result, Error> { +bun_core::bool_enum!(pub(crate) FlipAxis { Vertical, Horizontal }); + +pub(crate) fn flip(src: &[u8], w: u32, h: u32, horizontal: FlipAxis) -> Result, Error> { #[cfg(target_os = "macos")] if use_system() { match system_backend::BackendError::split(system_backend::flip(src, w, h, horizontal)) { @@ -770,7 +772,7 @@ pub(crate) fn flip(src: &[u8], w: u32, h: u32, horizontal: bool) -> Result Option { if !big && &tiff[0..2] != b"II" { return None; } + let big = Endian::from_bool(big); if rd16(tiff, 2, big)? != 42 { return None; } @@ -180,13 +181,15 @@ fn parse_tiff(tiff: &[u8]) -> Option { None } +bun_core::bool_enum!(Endian { Little, Big }); + #[inline] -fn rd16(b: &[u8], off: usize, big: bool) -> Option { +fn rd16(b: &[u8], off: usize, big: Endian) -> Option { if off + 2 > b.len() { return None; } let bytes = [b[off], b[off + 1]]; - Some(if big { + Some(if big == Endian::Big { u16::from_be_bytes(bytes) } else { u16::from_le_bytes(bytes) @@ -194,12 +197,12 @@ fn rd16(b: &[u8], off: usize, big: bool) -> Option { } #[inline] -fn rd32(b: &[u8], off: usize, big: bool) -> Option { +fn rd32(b: &[u8], off: usize, big: Endian) -> Option { if off + 4 > b.len() { return None; } let bytes = [b[off], b[off + 1], b[off + 2], b[off + 3]]; - Some(if big { + Some(if big == Endian::Big { u32::from_be_bytes(bytes) } else { u32::from_le_bytes(bytes) diff --git a/src/runtime/ipc.rs b/src/runtime/ipc.rs index 36ab0d3560bd..62432fd515e4 100644 --- a/src/runtime/ipc.rs +++ b/src/runtime/ipc.rs @@ -502,7 +502,7 @@ mod json { // .dead if `json_data` exceeds max length let s = BunString::create_external::<*mut bool>( json_data, - true, + bun_core::WTFEncoding::Latin1, &raw mut was_ascii_string_freed, json_ipc_data_string_free_cb, ); @@ -896,6 +896,8 @@ enum CloseFrom { Deinit, } +bun_core::bool_enum!(Notify); + #[derive(Copy, Clone, Eq, PartialEq)] pub enum AckNack { Ack, @@ -1112,7 +1114,7 @@ impl SendQueue { self.windows.with_mut(|w| w.try_close_after_write = true); } else { log!("SendQueue#closeSocket -> close now"); - self.windows_close(from != CloseFrom::Deinit); + self.windows_close(Notify::from_bool(from != CloseFrom::Deinit)); } } #[cfg(not(windows))] @@ -1121,21 +1123,21 @@ impl SendQueue { CloseReason::Normal => bun_uws::CloseCode::Normal, CloseReason::Failure => bun_uws::CloseCode::Failure, }); - self.socket_closed_notify(from != CloseFrom::Deinit); + self.socket_closed_notify(Notify::from_bool(from != CloseFrom::Deinit)); } } None => { - self.socket_closed_notify(from != CloseFrom::Deinit); + self.socket_closed_notify(Notify::from_bool(from != CloseFrom::Deinit)); } } let _ = reason; // suppress unused on windows } fn socket_closed(&self) { - self.socket_closed_notify(true); + self.socket_closed_notify(Notify::Yes); } - fn socket_closed_notify(&self, notify: bool) { + fn socket_closed_notify(&self, notify: Notify) { log!("SendQueue#_socketClosed"); #[cfg(windows)] { @@ -1157,7 +1159,11 @@ impl SendQueue { // can reach this path again with the socket already `.closed`; the // owner is about to free the memory that backs `this`, so scheduling // a task that points back into it would use-after-free. - if notify && was_open && !self.pending_after_close.get() && !self.close_event_sent.get() { + if notify == Notify::Yes + && was_open + && !self.pending_after_close.get() + && !self.close_event_sent.get() + { self.pending_after_close.set(true); self.schedule_deferred(); } @@ -1228,11 +1234,11 @@ impl SendQueue { // SAFETY: recorded at configure time by this live SendQueue; the pipe // leaves the list when `windows_close` issues its uv_close. let this = unsafe { &*this.cast::() }; - this.windows_close(true); + this.windows_close(Notify::Yes); } #[cfg(windows)] - fn windows_close(&self, notify: bool) { + fn windows_close(&self, notify: Notify) { log!("SendQueue#_windowsClose"); let SocketUnion::Open(pipe) = *self.socket.get() else { return; @@ -1883,7 +1889,7 @@ impl SendQueue { bun_core::heap::into_raw(Box::new(bun_core::ffi::zeroed::())); // SAFETY: ipc_pipe just allocated above. if let Some(err) = - unsafe { (*ipc_pipe).init(uv::Loop::get(), true) }.to_error(bun_sys::Tag::pipe) + unsafe { (*ipc_pipe).init(uv::Loop::get(), uv::Ipc::Yes) }.to_error(bun_sys::Tag::pipe) { // SAFETY: ipc_pipe was heap-allocated above and init failed before libuv took ownership. let _ = unsafe { bun_core::heap::take(ipc_pipe) }; diff --git a/src/runtime/ipc_host.rs b/src/runtime/ipc_host.rs index 08f69af9eb7b..434385cd1905 100644 --- a/src/runtime/ipc_host.rs +++ b/src/runtime/ipc_host.rs @@ -522,7 +522,7 @@ pub fn get_ipc_instance( bun_uws::SocketKind::SpawnIpc, fd, send_queue, - true, + bun_uws::Ipc::Yes, ) }; let Some(socket) = socket else { diff --git a/src/runtime/jsc_hooks.rs b/src/runtime/jsc_hooks.rs index edb355d3b465..9f1994a8631e 100644 --- a/src/runtime/jsc_hooks.rs +++ b/src/runtime/jsc_hooks.rs @@ -21,6 +21,7 @@ //! `__bun_http_sync_download_*` — low-tier extern impls. use bun_core::WTFStringImplExt as _; +use bun_core::output::AnsiColors; use bun_options_types::LoaderExt as _; use core::cell::Cell; use core::ffi::c_void; @@ -30,10 +31,11 @@ use bun_jsc::js_promise::Status as PromiseStatus; use bun_jsc::module_loader::{ ArenaResetGuard, FetchBuiltinResult, FetchFlags, LoaderHooks, TranspileArgs, TranspileExtra, }; +use bun_jsc::node_compile_cache::ModuleFormat; use bun_jsc::resolved_source::OwnedResolvedSource; use bun_jsc::virtual_machine::{ - InitOptions, ResolveMode, RuntimeHooks, RuntimeState as OpaqueRuntimeState, SweepResult, - VirtualMachine, + AllowSideEffects, InitOptions, ResolveMode, RuntimeHooks, RuntimeState as OpaqueRuntimeState, + SweepResult, VirtualMachine, }; use bun_jsc::{ AnyPromise, ErrorCode, ErrorableResolvedSource, ErrorableString, JSGlobalObject, @@ -42,8 +44,9 @@ use bun_jsc::{ use bun_ast::ImportKind; use bun_ast::Loader; -use bun_bundler::entry_points::ServerEntryPoint; +use bun_bundler::entry_points::{IsHotReloadEnabled, ServerEntryPoint}; use bun_bundler::options::{self, ModuleType}; +use bun_bundler::transpiler::AutoJsx; use bun_resolve_builtins::Module as HardcodedModule; use bun_resolver::fs as Fs; use bun_resolver::node_fallbacks; @@ -464,7 +467,7 @@ unsafe fn init_runtime_state( unsafe { let t = &mut (*vm).transpiler; t.options.emit_dce_annotations = false; - t.resolver.store_fd = opts.store_fd; + t.resolver.store_fd = bun_resolver::fs::StoreFd::from_bool(opts.store_fd); t.resolver.prefer_module_field = false; // Propagate `--preserve-symlinks` // from CLI args to the resolver so symlinked node_modules @@ -508,7 +511,7 @@ unsafe fn init_runtime_state( // `resolver.opts.load_tsconfig_json = false`, defeating // `compile.autoloadTsconfig: false`. if opts.graph.is_some() { - t.configure_linker_with_auto_jsx(false); + t.configure_linker_with_auto_jsx(AutoJsx::No); } else { t.configure_linker(); } @@ -706,7 +709,12 @@ fn generate_entry_point(_vm: &VirtualMachine, watch: bool, entry_path: &[u8]) -> } // SAFETY: `state` is the live per-thread `RuntimeState` (boxed in // `init_runtime_state`); no other `&mut` to `entry_point` is held here. - ServerEntryPoint::generate(unsafe { &mut (*state).entry_point }, watch, entry_path).is_ok() + ServerEntryPoint::generate( + unsafe { &mut (*state).entry_point }, + IsHotReloadEnabled::from_bool(watch), + entry_path, + ) + .is_ok() } /// `loadPreloads()` — runs `--preload` scripts. Returns the first rejected @@ -1255,7 +1263,7 @@ fn print_exception( // SAFETY: `as_exception` returned a live `*mut Exception` owned by the // JSC heap; we only read through it for the duration of this call. let exception = unsafe { &*exception }; - vm_ref.print_exception(exception, exception_list, writer, true); + vm_ref.print_exception(exception, exception_list, writer, AllowSideEffects::Yes); } else { let mut formatter = bun_jsc::console_object::Formatter::new(global); // `Formatter::new` already @@ -1267,8 +1275,8 @@ fn print_exception( exception_list, &mut formatter, writer, - colors, - true, + AnsiColors::from_bool(colors), + AllowSideEffects::Yes, ); // `defer formatter.deinit()` → Drop. } @@ -1794,7 +1802,7 @@ fn stop_active_handles(vm: &mut VirtualMachine, reason: StopReason) -> SweepResu // registered entry implies `close()` has not run, and `deinit` // cannot fire before `close()` drops the wrapper's Strong ref. ActiveHandle::StatWatcher(w) => bun_ptr::ParentRef::from(w).close(), - ActiveHandle::Server(mut s) => s.stop(true), + ActiveHandle::Server(mut s) => s.stop(crate::server::StopMode::Abrupt), ActiveHandle::Listener(l) => { // SAFETY: live until it unregisters in `do_stop`/`finalize`. crate::socket::Listener::stop_for_vm_teardown(unsafe { l.as_ref() }) @@ -2211,7 +2219,7 @@ fn note_compile_cache_parse_failure( if bun_jsc::node_compile_cache::is_enabled() && loader.is_java_script_like() && path.is_file() { bun_jsc::node_compile_cache::note_parse_failure( path.text, - !matches!(module_type, ModuleType::Esm), + ModuleFormat::from_bool(!matches!(module_type, ModuleType::Esm)), ); } } @@ -3045,7 +3053,7 @@ fn transpile_source_code_inner( { bun_jsc::node_compile_cache::fetch( source.path.text, - is_commonjs_module, + ModuleFormat::from_bool(is_commonjs_module), entry.output_code.byte_slice(), ) } else { @@ -3300,7 +3308,11 @@ fn transpile_source_code_inner( && path.is_file() && loader.is_java_script_like() { - bun_jsc::node_compile_cache::fetch(path.text, is_commonjs_module, written) + bun_jsc::node_compile_cache::fetch( + path.text, + ModuleFormat::from_bool(is_commonjs_module), + written, + ) } else { None }; @@ -3386,7 +3398,11 @@ fn transpile_source_code_inner( && path.is_file() && loader.is_java_script_like() { - bun_jsc::node_compile_cache::fetch(path.text, is_commonjs_module, written) + bun_jsc::node_compile_cache::fetch( + path.text, + ModuleFormat::from_bool(is_commonjs_module), + written, + ) } else { None }; @@ -4853,7 +4869,8 @@ unsafe fn transpile_virtual_module( .copied(); opt.unwrap_or_else(|| { // SAFETY: `jsc_vm` is the live per-thread VM. - if bun_core::strings::eql_long(specifier, unsafe { &*jsc_vm }.main(), true) { + let main = unsafe { &*jsc_vm }.main(); + if bun_core::strings::eql_long(specifier, main, bun_core::strings::CheckLen::Yes) { Loader::Js } else { Loader::File diff --git a/src/runtime/napi/napi_body.rs b/src/runtime/napi/napi_body.rs index 9dc7092069f7..20c64d5a42f5 100644 --- a/src/runtime/napi/napi_body.rs +++ b/src/runtime/napi/napi_body.rs @@ -247,12 +247,14 @@ enum EscapeError { EscapeCalledTwice, } +bun_core::bool_enum!(Escapable); + impl NapiHandleScope { /// Create a new handle scope in the given environment, or return null if creating one now is /// unsafe (i.e. inside a finalizer) - fn open(env: &NapiEnv, escapable: bool) -> *mut NapiHandleScope { + fn open(env: &NapiEnv, escapable: Escapable) -> *mut NapiHandleScope { // SAFETY: env is valid; C++ mutates env's scope stack (interior mutability). - unsafe { NapiHandleScope__open(env.as_mut_ptr(), escapable) } + unsafe { NapiHandleScope__open(env.as_mut_ptr(), escapable == Escapable::Yes) } } /// Closes the given handle scope, releasing all values inside it, if it is safe to do so. @@ -296,7 +298,7 @@ impl NapiHandleScope { #[must_use] fn open_scoped(env: &NapiEnv) -> NapiHandleScopeGuard<'_> { NapiHandleScopeGuard { - scope: Self::open(env, false), + scope: Self::open(env, Escapable::No), env, } } @@ -1191,7 +1193,7 @@ extern "C" fn napi_open_handle_scope( ungated!(env, env_); env.check_gc(); let result = get_out!(env, result_); - *result = NapiHandleScope::open(env, false); + *result = NapiHandleScope::open(env, Escapable::No); env.ok() } @@ -1294,7 +1296,7 @@ extern "C" fn napi_open_escapable_handle_scope( ungated!(env, env_); env.check_gc(); let result = get_out!(env, result_); - *result = NapiHandleScope::open(env, true); + *result = NapiHandleScope::open(env, Escapable::Yes); env.ok() } @@ -2436,6 +2438,9 @@ extern "C" fn napi_internal_enqueue_finalizer( // ThreadSafeFunction // ────────────────────────────────────────────────────────────────────────── +bun_core::bool_enum!(pub(crate) CallMode { NonBlocking, Blocking }); +bun_core::bool_enum!(CallerMustFree); + /// Ownership: the JS thread owns this allocation while the env lives and frees /// it in `destroy`; from `env_teardown_done` on it belongs to the remaining /// `thread_count` references, and whoever drops the last one frees it. @@ -2808,13 +2813,13 @@ impl ThreadSafeFunction { pub(crate) unsafe fn push( this: *mut ThreadSafeFunction, ctx: *mut c_void, - block: bool, + block: CallMode, ) -> napi_status { // SAFETY: live allocation; the borrow is scoped to this call and ends // before the free below. let (status, orphaned) = unsafe { (*this).enqueue(ctx, block) }; - if orphaned { + if orphaned == CallerMustFree::Yes { // SAFETY: the lock is dropped, we dropped the last thread reference // and `env_teardown` already released everything it owned. unsafe { ThreadSafeFunction::free_orphaned(this) }; @@ -2824,9 +2829,9 @@ impl ThreadSafeFunction { /// Returns `(status, caller_must_free)`; the free must happen after the /// lock guard here is dropped, which is why only `push` may call this. - fn enqueue(&mut self, ctx: *mut c_void, block: bool) -> (napi_status, bool) { + fn enqueue(&mut self, ctx: *mut c_void, block: CallMode) -> (napi_status, CallerMustFree) { let _g = self.lock.lock_guard(); - if block { + if block == CallMode::Blocking { while self.queue.is_blocked() && !self.is_closing() { self.blocking_condvar.wait(&self.lock); } @@ -2835,14 +2840,14 @@ impl ThreadSafeFunction { // queue (node's `Push` skips the queue-full check unless it is open), // so the caller's reference is still consumed and it can finalize. // don't set the error on the env as this is run from another thread - return (NapiStatus::queue_full as napi_status, false); + return (NapiStatus::queue_full as napi_status, CallerMustFree::No); } if self.is_closing() { // `env_teardown` sets `closing` under this same lock, so an env that // dies while we wait above lands here, never below. if self.thread_count.load(Ordering::SeqCst) <= 0 { - return (NapiStatus::invalid_arg as napi_status, false); + return (NapiStatus::invalid_arg as napi_status, CallerMustFree::No); } // Consumes this thread's reference, like Node's `Push`, so a thread // that stops calling after napi_closing does not pin the loop. That @@ -2855,7 +2860,7 @@ impl ThreadSafeFunction { let _ = self.queue.count.fetch_add(1, Ordering::SeqCst); let _ = self.queue.data.write_item(ctx); // OOM/capacity failures are fire-and-forget self.schedule_dispatch(); - (NapiStatus::ok as napi_status, false) + (NapiStatus::ok as napi_status, CallerMustFree::No) } /// Caller must hold `lock`. Reached from addon threads (`enqueue`, @@ -3049,7 +3054,7 @@ impl ThreadSafeFunction { unsafe { (*this).release_locked(mode) } }; - if orphaned { + if orphaned == CallerMustFree::Yes { // SAFETY: the lock is dropped, we dropped the last thread reference // and `env_teardown` already released everything it owned. unsafe { ThreadSafeFunction::free_orphaned(this) }; @@ -3062,9 +3067,9 @@ impl ThreadSafeFunction { fn release_locked( &mut self, mode: napi_threadsafe_function_release_mode, - ) -> (napi_status, bool) { + ) -> (napi_status, CallerMustFree) { if self.thread_count.load(Ordering::SeqCst) <= 0 { - return (NapiStatus::invalid_arg as napi_status, false); + return (NapiStatus::invalid_arg as napi_status, CallerMustFree::No); } let prev_remaining = self.thread_count.fetch_sub(1, Ordering::SeqCst); @@ -3076,7 +3081,10 @@ impl ThreadSafeFunction { // released the JS-thread-owned resources; until then it owns us // and will free us itself if we are the last to let go. let orphaned = prev_remaining == 1 && self.env_teardown_done.load(Ordering::SeqCst); - return (NapiStatus::ok as napi_status, orphaned); + return ( + NapiStatus::ok as napi_status, + CallerMustFree::from_bool(orphaned), + ); } if mode == napi_threadsafe_function_release_mode::abort || prev_remaining == 1 { @@ -3099,7 +3107,7 @@ impl ThreadSafeFunction { } } - (NapiStatus::ok as napi_status, false) + (NapiStatus::ok as napi_status, CallerMustFree::No) } } @@ -3233,7 +3241,13 @@ extern "C" fn napi_call_threadsafe_function( // SAFETY: func is non-null per N-API contract, and the caller may not use it // afterwards if this reports napi_closing — that consumes the caller's // thread reference, which can free it. - unsafe { ThreadSafeFunction::push(func, data, is_blocking == NAPI_TSFN_BLOCKING) } + unsafe { + ThreadSafeFunction::push( + func, + data, + CallMode::from_bool(is_blocking == NAPI_TSFN_BLOCKING), + ) + } } #[unsafe(no_mangle)] diff --git a/src/runtime/node.rs b/src/runtime/node.rs index e1834746a6f3..8ad18327673e 100644 --- a/src/runtime/node.rs +++ b/src/runtime/node.rs @@ -17,8 +17,8 @@ pub mod assert { pub mod types; pub use types::{ BlobOrStringOrBuffer, Dirent, Encoding, FileBlobs, FileSystemFlags, Flavor, PathLike, - PathOrBlob, PathOrFileDescriptor, StringObjects, StringOrBuffer, Valid, VectorArrayBuffer, - mode_from_js, + PathOrBlob, PathOrFileDescriptor, PinBuffers, StringObjects, StringOrBuffer, Valid, + VectorArrayBuffer, mode_from_js, }; pub use bun_jsc::MarkedArrayBuffer as Buffer; diff --git a/src/runtime/node/Stat.rs b/src/runtime/node/Stat.rs index b2ce2375b57b..a4a233a43440 100644 --- a/src/runtime/node/Stat.rs +++ b/src/runtime/node/Stat.rs @@ -180,9 +180,14 @@ pub(crate) fn create_stats_for_ino( // SAFETY: all-zero is a valid PosixStat (repr(C) POD with no NonNull/NonZero fields). let mut stat_: PosixStat = bun_core::ffi::zeroed(); stat_.ino = ino_arg.to_uint64_no_truncate(); - Stats::init(&stat_, big_arg.to_boolean()).to_js_newly_created(global) + Stats::init(&stat_, StatsKind::from_bool(big_arg.to_boolean())).to_js_newly_created(global) } +bun_core::bool_enum!( + /// `Stats` (JS numbers) vs `BigIntStats` — the `bigint` option. + pub StatsKind { Number, BigInt } +); + /// Union between `Stats` and `BigIntStats` where the type can be decided at runtime pub enum Stats { Big(StatsBig), @@ -191,8 +196,8 @@ pub enum Stats { impl Stats { #[inline] - pub(crate) fn init(stat_: &PosixStat, big: bool) -> Stats { - if big { + pub(crate) fn init(stat_: &PosixStat, big: StatsKind) -> Stats { + if big == StatsKind::BigInt { Stats::Big(StatsBig::init(stat_)) } else { Stats::Small(StatsSmall::init(stat_)) diff --git a/src/runtime/node/StatFS.rs b/src/runtime/node/StatFS.rs index daf0254f27d8..dda4e1b1e5bf 100644 --- a/src/runtime/node/StatFS.rs +++ b/src/runtime/node/StatFS.rs @@ -1,6 +1,8 @@ //! StatFS and BigIntStatFS classes from node:fs use bun_jsc::{JSGlobalObject, JSValue, JsResult}; + +use crate::node::stat::StatsKind; // On POSIX this is `libc::statfs`; on Windows it's `uv_statfs_t` (the value // `sys_uv::statfs` returns / `uv_fs_statfs` writes into `req.ptr`). Field // names match (`f_type`/`f_bsize`/…); widths differ (u64 vs platform-specific) @@ -140,8 +142,8 @@ pub enum StatFS { impl StatFS { #[inline] - pub(crate) fn init(stat_: &RawStatFS, big: bool) -> StatFS { - if big { + pub(crate) fn init(stat_: &RawStatFS, big: StatsKind) -> StatFS { + if big == StatsKind::BigInt { StatFS::Big(StatFSBig::init(stat_)) } else { StatFS::Small(StatFSSmall::init(stat_)) diff --git a/src/runtime/node/fs_events.rs b/src/runtime/node/fs_events.rs index 16159194793f..a404281fdd3e 100644 --- a/src/runtime/node/fs_events.rs +++ b/src/runtime/node/fs_events.rs @@ -10,6 +10,7 @@ use bun_threading::{Mutex, Semaphore, UnboundedQueue}; // Both siblings are wired into `crate::node`, and intra-crate module cycles // are fine in Rust, so import the real shapes instead of mirroring them. use super::node_fs_watcher::Event; +use super::node_fs_watcher::Recursive; use super::node_fs_watcher::WatchEventKind; type CFAbsoluteTime = f64; @@ -586,7 +587,7 @@ impl FSEventsLoop { // Do not emit events from subdirectories (without option set) if path.is_empty() || (bun_core::strings::index_of_char_usize(path, b'/').is_some() - && !handle.recursive) + && handle.recursive == Recursive::No) { continue; } @@ -864,7 +865,7 @@ pub struct FSEventsWatcher { pub callback: Callback, pub(crate) flush_callback: UpdateEndCallback, pub(crate) loop_: core::cell::Cell>, - pub(crate) recursive: bool, + pub(crate) recursive: Recursive, pub ctx: *mut c_void, } @@ -876,7 +877,7 @@ impl FSEventsWatcher { fn init( loop_: &'static FSEventsLoop, path: &[u8], - recursive: bool, + recursive: Recursive, callback: Callback, update_end: UpdateEndCallback, ctx: *mut c_void, @@ -913,7 +914,7 @@ impl Drop for FSEventsWatcher { pub(crate) fn watch( path: &[u8], - recursive: bool, + recursive: Recursive, callback: Callback, update_end: UpdateEndCallback, ctx: *mut c_void, diff --git a/src/runtime/node/memory_pressure.rs b/src/runtime/node/memory_pressure.rs index b45c180481d5..6f97a913d16c 100644 --- a/src/runtime/node/memory_pressure.rs +++ b/src/runtime/node/memory_pressure.rs @@ -193,7 +193,11 @@ mod posix { ); // SAFETY: `poll` is the fresh hive slot; `platform_event_loop` is the live uws loop. let result = unsafe { - (*poll).register(ctx.platform_event_loop(), Flags::MemoryPressure, false) + (*poll).register( + ctx.platform_event_loop(), + Flags::MemoryPressure, + bun_io::OneShot::No, + ) }; if result.is_err() { // SAFETY: fresh hive slot never handed out. diff --git a/src/runtime/node/node_assert.rs b/src/runtime/node/node_assert.rs index d3bdb9b83b6c..38fe60d112c2 100644 --- a/src/runtime/node/node_assert.rs +++ b/src/runtime/node/node_assert.rs @@ -6,6 +6,12 @@ use bun_jsc::{FromAny, JSGlobalObject, JSObject, JSValue, JsError, JsResult}; use super::assert::myers_diff as MyersDiff; use super::assert::myers_diff::{Diff, DiffKind, Line}; +bun_core::bool_enum!(pub(crate) CheckCommaDisparity); +bun_core::bool_enum!( + /// Split `actual` and `expected` into lines before diffing. + pub(crate) DiffMode { Chars, Lines } +); + /// Compare `actual` and `expected`, producing a diff that would turn `actual` /// into `expected`. /// @@ -24,9 +30,9 @@ pub(crate) fn myers_diff( expected: &BunString, // If true, strings that have a trailing comma but are otherwise equal are // considered equal. - check_comma_disparity: bool, + check_comma_disparity: CheckCommaDisparity, // split `actual` and `expected` into lines before diffing - lines: bool, + lines: DiffMode, ) -> JsResult { // Short circuit on empty strings. Note that, in release builds where // assertions are disabled, if `actual` and `expected` are both dead, this @@ -40,7 +46,7 @@ pub(crate) fn myers_diff( let actual_encoding = actual.encoding(); let expected_encoding = expected.encoding(); - if lines { + if lines == DiffMode::Lines { if actual_encoding != expected_encoding { let actual_utf8 = actual.to_utf8_without_ref(); let expected_utf8 = expected.to_utf8_without_ref(); @@ -100,7 +106,7 @@ fn diff_lines<'s, T>( global: &JSGlobalObject, actual: &'s [T], expected: &'s [T], - check_comma_disparity: bool, + check_comma_disparity: CheckCommaDisparity, ) -> JsResult where T: PartialEq + Copy + From, @@ -109,7 +115,7 @@ where let a = MyersDiff::split::(actual); let e = MyersDiff::split::(expected); - let diff: MyersDiff::DiffList<&'s [T]> = if check_comma_disparity { + let diff: MyersDiff::DiffList<&'s [T]> = if check_comma_disparity == CheckCommaDisparity::Yes { MyersDiff::Differ::<&'s [T], true>::diff(a.as_slice(), e.as_slice()) .map_err(|err| map_diff_error(global, err))? } else { diff --git a/src/runtime/node/node_assert_binding.rs b/src/runtime/node/node_assert_binding.rs index dc9f897a6d5c..2e9f81cd3dd6 100644 --- a/src/runtime/node/node_assert_binding.rs +++ b/src/runtime/node/node_assert_binding.rs @@ -2,6 +2,7 @@ use bun_core as bstring; use bun_jsc::{CallFrame, JSFunction, JSGlobalObject, JSValue, JsResult}; use super::node_assert; +use super::node_assert::{CheckCommaDisparity, DiffMode}; /// ```ts /// const enum DiffType { @@ -21,11 +22,17 @@ fn myers_diff(global: &JSGlobalObject, frame: &CallFrame) -> JsResult { let actual_arg: JSValue = frame.argument(0); let expected_arg: JSValue = frame.argument(1); - let (check_comma_disparity, lines): (bool, bool) = match nargs { + let (check_comma_disparity, lines): (CheckCommaDisparity, DiffMode) = match nargs { 0 | 1 => unreachable!(), - 2 => (false, false), - 3 => (frame.argument(2).is_truthy(), false), - _ => (frame.argument(2).is_truthy(), frame.argument(3).is_truthy()), + 2 => (CheckCommaDisparity::No, DiffMode::Chars), + 3 => ( + CheckCommaDisparity::from_bool(frame.argument(2).is_truthy()), + DiffMode::Chars, + ), + _ => ( + CheckCommaDisparity::from_bool(frame.argument(2).is_truthy()), + DiffMode::from_bool(frame.argument(3).is_truthy()), + ), }; if !actual_arg.is_string() { diff --git a/src/runtime/node/node_fs.rs b/src/runtime/node/node_fs.rs index dd6773bdc442..a8b9fa278024 100644 --- a/src/runtime/node/node_fs.rs +++ b/src/runtime/node/node_fs.rs @@ -4,6 +4,7 @@ use bun_paths::strings; use core::ffi::{c_char, c_int, c_uint, c_void}; +use core::ops::ControlFlow; use core::ptr::NonNull; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; @@ -18,6 +19,7 @@ use bun_jsc::debugger::AsyncTaskTracker; use bun_jsc::virtual_machine::VirtualMachine; use bun_jsc::{EventLoopHandle, JSGlobalObject, JSValue, JsResult, ThreadSafe, Unprotect}; use bun_paths::{self as paths, OSPathBuffer, OSPathChar, OSPathSliceZ, PathBuffer}; +use bun_standalone_graph::Recursive; use bun_sys::FdExt as _; use bun_sys::{self as sys, E, Fd as FD, Maybe, Mode, SystemErrno}; use bun_threading::UnboundedQueue; @@ -150,12 +152,12 @@ use bun_jsc::AbortSignalRef; // Wired to the real sibling modules under `super::` (rather than a // `bun_jsc::node` re-export shim) so this file compiles standalone. -use super::stat::Stats; +use super::stat::{Stats, StatsKind}; use super::time_like::TimeLike; use super::types::{ ArgumentsSlice, Dirent, Encoding, FdArgExt as _, FileSystemFlags, FileSystemFlagsKind, - NameTooLong, PathLike, PathLikeExt as _, PathOrFdExt as _, StringObjects, StringOrBuffer, - VectorArrayBuffer, + NameTooLong, PathLike, PathLikeExt as _, PathOrFdExt as _, PinBuffers, StringObjects, + StringOrBuffer, VectorArrayBuffer, }; // Re-exported publicly: `crate::node::fs::PathOrFileDescriptor` is the // canonical path used by `cli/build_command.rs` et al., and `node_fs::Flavor` @@ -475,6 +477,17 @@ pub(crate) const DEFAULT_PERMISSION: Mode = 0; // `&AbortSignal` inherent methods — the former `AbortSignalRefExt` shim with // per-call `unsafe { self.as_ref() }` is gone. `unref()` is handled by `Drop`. +bun_core::bool_enum!( + /// Recursive readdir: whether `basename` is the user-supplied root (opened + /// relative to cwd) or a subdirectory (opened relative to `root_fd`). + pub(crate) IsRoot +); +bun_core::bool_enum!( + /// Recursive readdir: whether entry names honour `args.encoding` (sync) or + /// are always cloned as UTF-8 (async). + pub ApplyEncoding +); + // ────────────────────────────────────────────────────────────────────────── // Async task type aliases // ────────────────────────────────────────────────────────────────────────── @@ -590,7 +603,8 @@ mod _async_tasks { let path = unsafe { &*self.path }; let result = node_fs.mkdir_recursive(&args::Mkdir { path: PathLike::String(bun_ptr::cow_slice::CowSlice::init_unchecked( - path, false, + path, + bun_ptr::cow_slice::Ownership::Borrowed, )), recursive: true, ..Default::default() @@ -1916,7 +1930,6 @@ mod _async_tasks { ); } - // returns boolean `should_continue` fn cp_async_directory( nodefs: &mut NodeFS, args: args::CpFlags, @@ -1925,7 +1938,7 @@ mod _async_tasks { src_dir_len: PathInt, dest_buf: &mut OSPathBuffer, dest_dir_len: PathInt, - ) -> bool { + ) -> ControlFlow<()> { // SAFETY: `this` is the live Box-leaked task. Shared borrow only — spawned // `CpSingleTask`s on other workpool threads may concurrently hold `&Self`. // The raw `*mut` is threaded through (instead of `&Self`) so that the @@ -1953,14 +1966,14 @@ mod _async_tasks { // `errno_sys_p` // already boxed `src.as_bytes()` into `err.path`, so just forward. this_ref.finish_concurrently(err); - return false; + return ControlFlow::Break(()); } // Other errors may be due to clonefile() not being supported // We'll fall back to other implementations _ => {} } } else { - return true; + return ControlFlow::Continue(()); } } @@ -1970,7 +1983,7 @@ mod _async_tasks { this_ref.finish_concurrently(Err( err.with_path(nodefs.os_path_into_sync_error_buf(src)) )); - return false; + return ControlFlow::Break(()); } Ok(fd_) => fd_, }; @@ -1992,7 +2005,7 @@ mod _async_tasks { ) { Err(err) => { this_ref.finish_concurrently(Err(err)); - return false; + return ControlFlow::Break(()); } Ok(n) => n, }; @@ -2003,7 +2016,7 @@ mod _async_tasks { match mkdir_ { Err(err) => { this_ref.finish_concurrently(Err(err)); - return false; + return ControlFlow::Break(()); } Ok(_) => { this_ref.on_copy(src, normdest); @@ -2024,7 +2037,7 @@ mod _async_tasks { this_ref.finish_concurrently(Err( err.with_path(nodefs.os_path_into_sync_error_buf(src)) )); - return false; + return ControlFlow::Break(()); } Ok(ent) => match ent { Some(e) => e, @@ -2047,7 +2060,7 @@ mod _async_tasks { .into(), ..Default::default() })); - return false; + return ControlFlow::Break(()); } match current.kind { @@ -2070,8 +2083,8 @@ mod _async_tasks { dest_buf, (dd + 1 + cname.len()) as PathInt, ); - if !should_continue { - return false; + if should_continue.is_break() { + return ControlFlow::Break(()); } } _ => { @@ -2105,7 +2118,7 @@ mod _async_tasks { entry = iterator.next(); } - true + ControlFlow::Continue(()) } } @@ -2183,7 +2196,7 @@ mod _async_tasks { }; // May finish synchronously (no subdirectories) or fan out; the last // subtask finishes the token. - this.perform_work(root_path_z, &mut buf, true); + this.perform_work(root_path_z, &mut buf, IsRoot::Yes); None } @@ -2313,7 +2326,7 @@ mod _async_tasks { // refcount. `from_raw_mut` was used at enqueue, so write provenance is // present; this work-pool callback is the sole holder of `&mut` to the // parent's per-result fields (it pushes to a lock-free queue). - unsafe { readdir_task.assume_mut() }.perform_work(basename_z, &mut buf, false); + unsafe { readdir_task.assume_mut() }.perform_work(basename_z, &mut buf, IsRoot::No); } } @@ -2395,7 +2408,7 @@ mod _async_tasks { &mut self, basename: &ZStr, buf: &mut PathBuffer, - is_root: bool, + is_root: IsRoot, ) { macro_rules! impl_tag { ($T:ty, $variant:ident) => {{ @@ -2721,7 +2734,7 @@ pub mod args { })?, // The iovec pointers outlive this call on the async path; root // each element and pin its backing store until completion. - arguments.will_be_async, + PinBuffers::from_bool(arguments.will_be_async), )?; let position: Option = arguments .next_eat() @@ -3250,13 +3263,19 @@ pub mod args { } impl Rm { pub fn from_js(ctx: &JSGlobalObject, arguments: &mut ArgumentsSlice) -> JsResult { - Ok(Rm(RmDir::from_js_impl(ctx, arguments, true)?)) + Ok(Rm(RmDir::from_js_impl( + ctx, + arguments, + StrictBooleans::Yes, + )?)) } pub(crate) fn to_thread_safe(&mut self) { self.0.to_thread_safe(); } } + bun_core::bool_enum!(StrictBooleans); + pub struct RmDir { pub path: PathLike, pub(crate) force: bool, @@ -3278,7 +3297,7 @@ pub mod args { fs_args_path_forwarders!(RmDir; path); impl RmDir { pub fn from_js(ctx: &JSGlobalObject, arguments: &mut ArgumentsSlice) -> JsResult { - Self::from_js_impl(ctx, arguments, false) + Self::from_js_impl(ctx, arguments, StrictBooleans::No) } /// `strict_booleans` selects node's `validateRmOptions` behavior (used by /// `fs.rm`): a present-but-`undefined` `recursive`/`force` is a type @@ -3286,7 +3305,7 @@ pub mod args { fn from_js_impl( ctx: &JSGlobalObject, arguments: &mut ArgumentsSlice, - strict_booleans: bool, + strict_booleans: StrictBooleans, ) -> JsResult { let path = PathLike::from_js_required(ctx, arguments, "path")?; let mut recursive = false; @@ -3297,7 +3316,7 @@ pub mod args { arguments.eat(); if val.is_object() { let get_option = |name: &'static str| -> JsResult> { - if strict_booleans { + if strict_booleans == StrictBooleans::Yes { let key = bun_core::String::borrow_utf8(name.as_bytes()); val.get_own(ctx, &key) } else { @@ -5329,7 +5348,7 @@ impl NodeFS { } }; - Ok(sys::exists_os_path(slice, false)) + Ok(sys::exists_os_path(slice, sys::FileOnly::No)) } pub(crate) fn chown(&mut self, args: &args::Chown, _: Flavor) -> Maybe { @@ -5402,12 +5421,15 @@ impl NodeFS { #[cfg(any(target_os = "linux", target_os = "android"))] if sys::SUPPORTS_STATX_ON_LINUX.load(Ordering::Relaxed) { return match sys::fstatx(args.fd, sys::STATX_MASK_FOR_STATS) { - Ok(result) => Ok(Stats::init(&result, args.big_int)), + Ok(result) => Ok(Stats::init(&result, StatsKind::from_bool(args.big_int))), Err(err) => Err(err), }; } match Syscall::fstat(args.fd) { - Ok(result) => Ok(Stats::init(&PosixStat::init(&result), args.big_int)), + Ok(result) => Ok(Stats::init( + &PosixStat::init(&result), + StatsKind::from_bool(args.big_int), + )), Err(err) => Err(err), } } @@ -5539,7 +5561,7 @@ impl NodeFS { if let Some(result) = graph.stat(path.as_bytes()) { return Ok(StatOrNotFound::Stats(Box::new(Stats::init( &PosixStat::init(&result), - args.big_int, + StatsKind::from_bool(args.big_int), )))); } } @@ -5548,7 +5570,7 @@ impl NodeFS { return match sys::lstatx(path, sys::STATX_MASK_FOR_STATS) { Ok(result) => Ok(StatOrNotFound::Stats(Box::new(Stats::init( &result, - args.big_int, + StatsKind::from_bool(args.big_int), )))), Err(err) => { if !args.throw_if_no_entry && err.get_errno() == E::ENOENT { @@ -5561,7 +5583,7 @@ impl NodeFS { match Syscall::lstat(path) { Ok(result) => Ok(StatOrNotFound::Stats(Box::new(Stats::init( &PosixStat::init(&result), - args.big_int, + StatsKind::from_bool(args.big_int), )))), Err(err) => { if !args.throw_if_no_entry && err.get_errno() == E::ENOENT { @@ -5995,7 +6017,10 @@ impl NodeFS { // before `uv_fs_req_cleanup` releases the backing storage. let statfs_: super::statfs::RawStatFS = unsafe { core::ptr::read_unaligned(req.ptr_as::()) }; - Ok(ret::StatFS::init(&statfs_, args.big_int)) + Ok(ret::StatFS::init( + &statfs_, + StatsKind::from_bool(args.big_int), + )) } fn read_inner(&mut self, args: &args::Read) -> Maybe { @@ -6291,19 +6316,19 @@ impl NodeFS { ret::ReaddirTag::Buffers => Self::readdir_inner::( &mut self.sync_error_buf, args, - args.recursive, + Recursive::from_bool(args.recursive), flavor, ), ret::ReaddirTag::WithFileTypes => Self::readdir_inner::( &mut self.sync_error_buf, args, - args.recursive, + Recursive::from_bool(args.recursive), flavor, ), ret::ReaddirTag::Files => Self::readdir_inner::( &mut self.sync_error_buf, args, - args.recursive, + Recursive::from_bool(args.recursive), flavor, ), }; @@ -6444,8 +6469,9 @@ impl NodeFS { async_task: &mut AsyncReaddirRecursiveTask, basename: &ZStr, entries: &mut Vec, - is_root: bool, + is_root: IsRoot, ) -> Maybe<()> { + let is_root = is_root == IsRoot::Yes; // `root_path` is never mutated for the lifetime of the task, but // borrowck can't see that across `async_task.enqueue(&mut self, …)`. Detach // the slice via raw-pointer round-trip. @@ -6614,7 +6640,7 @@ impl NodeFS { &dirent_path_prev, effective_kind, async_task.encoding, - false, + ApplyEncoding::No, ); } @@ -6796,7 +6822,7 @@ impl NodeFS { &dirent_path_prev, effective_kind, args.encoding, - true, + ApplyEncoding::Yes, ); } dirent_path_prev.deref(); @@ -6831,7 +6857,7 @@ impl NodeFS { fn readdir_inner( buf: &mut PathBuffer, args: &args::Readdir, - recursive: bool, + recursive: Recursive, flavor: Flavor, ) -> Maybe { let path = args.path.slice_z(buf); @@ -6848,7 +6874,7 @@ impl NodeFS { } } - if recursive && flavor == Flavor::Sync { + if recursive == Recursive::Yes && flavor == Flavor::Sync { let mut buf_to_pass = PathBuffer::uninit(); let mut entries: Vec = Vec::new(); return match Self::readdir_with_entries_recursive_sync::( @@ -6867,7 +6893,7 @@ impl NodeFS { }; } - if recursive { + if recursive == Recursive::Yes { panic!( "This code path should never be reached. It should only go through readdirWithEntriesRecursiveAsync." ); @@ -6904,7 +6930,7 @@ impl NodeFS { graph: &bun_standalone_graph::Graph, path: &[u8], args: &args::Readdir, - recursive: bool, + recursive: Recursive, flavor: Flavor, ) -> Maybe { let Some(list) = graph.readdir(path, recursive) else { @@ -6924,13 +6950,12 @@ impl NodeFS { }; let mut joined: Vec = Vec::new(); #[allow(unused_mut)] - for (mut name, is_dir) in list { - let kind = if is_dir { - sys::FileKind::Directory - } else { - sys::FileKind::File + for (mut name, entry_kind) in list { + let kind = match entry_kind { + bun_standalone_graph::DirEntryKind::Dir => sys::FileKind::Directory, + bun_standalone_graph::DirEntryKind::File => sys::FileKind::File, }; - if recursive { + if recursive == Recursive::Yes { #[cfg(windows)] for b in name.iter_mut() { if *b == b'/' { @@ -6959,7 +6984,7 @@ impl NodeFS { &dirent_path, kind, args.encoding, - flavor == Flavor::Sync, + ApplyEncoding::from_bool(flavor == Flavor::Sync), ); dirent_path.deref(); } else { @@ -7549,7 +7574,7 @@ impl NodeFS { let link_path: &[u8] = &outbuf[..link_len]; if args.encoding == Encoding::Utf8 { if let PathLike::SliceWithUnderlyingString(s) = &args.path { - if strings::eql_long(s.slice(), link_path, true) { + if strings::eql_long(s.slice(), link_path, strings::CheckLen::Yes) { return Ok(StringOrBuffer::String(s.dupe_ref())); } } @@ -7639,7 +7664,7 @@ impl NodeFS { } if args.encoding == Encoding::Utf8 { if let PathLike::SliceWithUnderlyingString(s) = &args.path { - if strings::eql_long(s.slice(), buf, true) { + if strings::eql_long(s.slice(), buf, strings::CheckLen::Yes) { return Ok(StringOrBuffer::String(s.dupe_ref())); } } @@ -7692,7 +7717,7 @@ impl NodeFS { let _ = variant; if args.encoding == Encoding::Utf8 { if let PathLike::SliceWithUnderlyingString(s) = &args.path { - if strings::eql_long(s.slice(), buf, true) { + if strings::eql_long(s.slice(), buf, strings::CheckLen::Yes) { return Ok(StringOrBuffer::String(s.dupe_ref())); } } @@ -7830,7 +7855,10 @@ impl NodeFS { pub(crate) fn statfs(&mut self, args: &args::StatFS, _: Flavor) -> Maybe { match Syscall::statfs(args.path.slice_z(&mut self.sync_error_buf)) { - Ok(result) => Ok(ret::StatFS::init(&result, args.big_int)), + Ok(result) => Ok(ret::StatFS::init( + &result, + StatsKind::from_bool(args.big_int), + )), Err(err) => Err(err), } } @@ -7841,7 +7869,7 @@ impl NodeFS { if let Some(result) = graph.stat(path.as_bytes()) { return Ok(StatOrNotFound::Stats(Box::new(Stats::init( &PosixStat::init(&result), - args.big_int, + StatsKind::from_bool(args.big_int), )))); } } @@ -7850,7 +7878,7 @@ impl NodeFS { return match sys::statx(path, sys::STATX_MASK_FOR_STATS) { Ok(result) => Ok(StatOrNotFound::Stats(Box::new(Stats::init( &result, - args.big_int, + StatsKind::from_bool(args.big_int), )))), Err(err) => { if !args.throw_if_no_entry && err.get_errno() == E::ENOENT { @@ -7863,7 +7891,7 @@ impl NodeFS { match Syscall::stat(path) { Ok(result) => Ok(StatOrNotFound::Stats(Box::new(Stats::init( &PosixStat::init(&result), - args.big_int, + StatsKind::from_bool(args.big_int), )))), Err(err) => { if !args.throw_if_no_entry && err.get_errno() == E::ENOENT { @@ -9155,7 +9183,7 @@ impl NodeFS { let mkdir_result = self.mkdir_recursive(&args::Mkdir { path: PathLike::String(bun_ptr::cow_slice::CowSlice::init_unchecked( &bytes[..len], - false, + bun_ptr::cow_slice::Ownership::Borrowed, )), recursive: true, ..Default::default() @@ -9383,7 +9411,7 @@ pub trait ReaddirEntry: Sized { dirent_path: &BunString, kind: sys::FileKind, encoding: Encoding, - apply_encoding: bool, + apply_encoding: ApplyEncoding, ); } impl ReaddirEntry for BunString { @@ -9431,10 +9459,10 @@ impl ReaddirEntry for BunString { _dirent_path: &BunString, _kind: sys::FileKind, encoding: Encoding, - apply_encoding: bool, + apply_encoding: ApplyEncoding, ) { let bytes = without_nt_prefix::(name_to_copy); - entries.push(if apply_encoding { + entries.push(if apply_encoding == ApplyEncoding::Yes { webcore::encoding::to_bun_string(bytes, encoding) } else { BunString::clone_utf8(bytes) @@ -9486,10 +9514,10 @@ impl ReaddirEntry for Dirent { dirent_path: &BunString, kind: sys::FileKind, encoding: Encoding, - apply_encoding: bool, + apply_encoding: ApplyEncoding, ) { entries.push(Dirent { - name: if apply_encoding { + name: if apply_encoding == ApplyEncoding::Yes { webcore::encoding::to_bun_string(utf8_name, encoding) } else { BunString::clone_utf8(utf8_name) @@ -9537,7 +9565,7 @@ impl ReaddirEntry for Buffer { _dirent_path: &BunString, _kind: sys::FileKind, _encoding: Encoding, - _apply_encoding: bool, + _apply_encoding: ApplyEncoding, ) { entries.push(Buffer::from_string(without_nt_prefix::(name_to_copy)).expect("oom")); } @@ -9622,7 +9650,8 @@ pub(crate) unsafe extern "C" fn Bun__mkdirp( node_fs .mkdir_recursive(&args::Mkdir { path: PathLike::String(bun_ptr::cow_slice::CowSlice::init_unchecked( - path_bytes, false, + path_bytes, + bun_ptr::cow_slice::Ownership::Borrowed, )), recursive: true, ..Default::default() diff --git a/src/runtime/node/node_fs_stat_watcher.rs b/src/runtime/node/node_fs_stat_watcher.rs index 651d661d57f9..7759c5b2c458 100644 --- a/src/runtime/node/node_fs_stat_watcher.rs +++ b/src/runtime/node/node_fs_stat_watcher.rs @@ -21,7 +21,7 @@ use bun_resolver::fs; use bun_sys::{self, PosixStat}; use bun_threading::{Guarded, UnboundedQueue}; -use crate::node::stat::{StatsBig, StatsSmall}; +use crate::node::stat::{StatsBig, StatsKind, StatsSmall}; use crate::node::types::PathLikeExt; use crate::timer::{EventLoopTimer, EventLoopTimerState, EventLoopTimerTag}; @@ -34,9 +34,9 @@ macro_rules! log { fn stat_to_js_stats( global_this: &JSGlobalObject, stats: &PosixStat, - bigint: bool, + bigint: StatsKind, ) -> JsResult { - if bigint { + if bigint == StatsKind::BigInt { StatsBig::init(stats).to_js(global_this) } else { StatsSmall::init(stats).to_js(global_this) @@ -844,7 +844,11 @@ impl StatWatcher { // Propagated to the task fold: reporting here would leave a // termination pending for the next queued task's JS entry. - let jsvalue = stat_to_js_stats(global_this, &this_ref.get_last_stat(), this_ref.bigint)?; + let jsvalue = stat_to_js_stats( + global_this, + &this_ref.get_last_stat(), + StatsKind::from_bool(this_ref.bigint), + )?; js::gc::prev_stat::set(js_this, global_this, jsvalue); // SAFETY: scheduler is live (`RefPtr`); `this` is live (ref'd, guard above). @@ -869,7 +873,11 @@ impl StatWatcher { return Ok(()); }; let global_this = this_ref.global_this(); - let jsvalue = stat_to_js_stats(global_this, &this_ref.get_last_stat(), this_ref.bigint)?; + let jsvalue = stat_to_js_stats( + global_this, + &this_ref.get_last_stat(), + StatsKind::from_bool(this_ref.bigint), + )?; js::gc::prev_stat::set(js_this, global_this, jsvalue); let result = js::listener_get_cached(js_this).unwrap().call( @@ -956,8 +964,11 @@ impl StatWatcher { }; let global_this = this_ref.global_this(); let prev_jsvalue = js::gc::prev_stat::get(js_this).unwrap_or(JSValue::UNDEFINED); - let current_jsvalue = - stat_to_js_stats(global_this, &this_ref.get_last_stat(), this_ref.bigint)?; + let current_jsvalue = stat_to_js_stats( + global_this, + &this_ref.get_last_stat(), + StatsKind::from_bool(this_ref.bigint), + )?; js::gc::prev_stat::set(js_this, global_this, current_jsvalue); // Propagate to the dispatcher: `report_error_or_terminate` reports a diff --git a/src/runtime/node/node_fs_watcher.rs b/src/runtime/node/node_fs_watcher.rs index b581a1c3ddf7..ffe009af2f01 100644 --- a/src/runtime/node/node_fs_watcher.rs +++ b/src/runtime/node/node_fs_watcher.rs @@ -164,10 +164,16 @@ impl Taskable for FSWatchTaskPosix { } } +bun_core::bool_enum!( + /// Whether the queued `Event` owns its path payload and must be dropped in + /// `clean_entries`. + pub NeedsFree +); + #[cfg(not(windows))] pub struct Entry { event: Event, - needs_free: bool, + needs_free: NeedsFree, } #[cfg(not(windows))] @@ -180,7 +186,7 @@ impl FSWatchTaskPosix { self.ctx.as_ref().expect("FSWatchTask.ctx unset").get() } - pub(crate) fn append(&mut self, event: Event, needs_free: bool) { + pub(crate) fn append(&mut self, event: Event, needs_free: NeedsFree) { if self.count == 8 { self.enqueue(); let ctx = self.ctx; @@ -229,7 +235,7 @@ impl FSWatchTaskPosix { } pub(crate) fn append_abort(&mut self) { - self.append(Event::Abort, false); + self.append(Event::Abort, NeedsFree::No); self.enqueue(); } @@ -277,7 +283,7 @@ impl FSWatchTaskPosix { for i in 0..self.count as usize { // SAFETY: entries [0..count) were written by `append`. let needs_free = unsafe { self.entries[i].assume_init_ref() }.needs_free; - if needs_free { + if needs_free == NeedsFree::Yes { // SAFETY: entries [0..count) were written by `append`; dropped at most once // (count is reset to 0 below). unsafe { self.entries[i].assume_init_drop() }; @@ -336,12 +342,21 @@ impl WatchEventKind { } } +bun_core::bool_enum!( + /// Whether an emitted error also closes the `FSWatcher`. + pub CloseWatcher +); +bun_core::bool_enum!( + /// `fs.watch(path, { recursive })` — watch the whole subtree. + pub Recursive +); + pub enum Event { Rename(EventPathString), Change(EventPathString), Error { err: bun_sys::Error, - close: bool, + close: CloseWatcher, }, /// An event with no filename, surfaced to JS with `null`, matching node: /// `Change` when the OS event queue overflowed and changes were lost, @@ -400,7 +415,7 @@ impl Default for FSWatchTaskWindows { syscall: bun_sys::Tag::watch, ..Default::default() }, - close: true, + close: CloseWatcher::Yes, }, ctx: None, } @@ -579,7 +594,8 @@ impl FSWatcher { } } - this.current_task.with_mut(|t| t.append(event, true)); + this.current_task + .with_mut(|t| t.append(event, NeedsFree::Yes)); } #[cfg(windows)] @@ -874,7 +890,7 @@ impl FSWatcher { /// R-2: see `emit_abort` — `&self` + `Cell` so the trailing `close()` /// observes a re-entrant `watcher.close()` from inside the listener. - pub(crate) fn emit_error(&self, err: &bun_sys::Error, close: bool) { + pub(crate) fn emit_error(&self, err: &bun_sys::Error, close: CloseWatcher) { if self.closed.get() { return; } @@ -898,7 +914,7 @@ impl FSWatcher { } } - if close { + if close == CloseWatcher::Yes { self.close(); } } @@ -1211,12 +1227,17 @@ impl FSWatcher { // backend dropped the callback parameters — only one valid // value each), so the call is cfg-split. #[cfg(windows)] - let r = path_watcher::watch(vm_ref, file_path, args.recursive, ctx as *mut c_void); + let r = path_watcher::watch( + vm_ref, + file_path, + Recursive::from_bool(args.recursive), + ctx as *mut c_void, + ); #[cfg(not(windows))] let r = path_watcher::watch( vm_ref, file_path, - args.recursive, + Recursive::from_bool(args.recursive), FSWatcher::ON_PATH_UPDATE, FSWatcher::on_update_end, ctx.cast::(), diff --git a/src/runtime/node/node_process.rs b/src/runtime/node/node_process.rs index d021b241ea51..c2d42ec1293e 100644 --- a/src/runtime/node/node_process.rs +++ b/src/runtime/node/node_process.rs @@ -83,7 +83,10 @@ pub(crate) extern "C" fn exit(global_object: &JSGlobalObject, code: u8) { ); bun_jsc::node_compile_cache::persist_now(); bun_core::Output::flush(); - bun_core::reload_process(should_clear_terminal, false); + bun_core::reload_process( + bun_core::ClearTerminal::from_bool(should_clear_terminal), + bun_core::MayReturn::No, + ); } vm.on_exit(); vm.global_exit(); diff --git a/src/runtime/node/path.rs b/src/runtime/node/path.rs index 6c784488465b..4c5e658a68dd 100644 --- a/src/runtime/node/path.rs +++ b/src/runtime/node/path.rs @@ -127,7 +127,7 @@ fn eql_ignore_case_t(a: &[T], b: &[T]) -> bool { // T == u8 when !IS_U16; bytemuck statically checks the layout. let a8: &[u8] = bytemuck::cast_slice::(a); let b8: &[u8] = bytemuck::cast_slice::(b); - return strings::eql_case_insensitive_ascii(a8, b8, true); + return strings::eql_case_insensitive_ascii(a8, b8, strings::CheckLen::Yes); } // In practice the only callers instantiate with `T == u8`; provide a sound // u16 compare so diff --git a/src/runtime/node/path_watcher.rs b/src/runtime/node/path_watcher.rs index 715150032bac..d22e4d87e4e2 100644 --- a/src/runtime/node/path_watcher.rs +++ b/src/runtime/node/path_watcher.rs @@ -55,7 +55,7 @@ use bun_wyhash::hash; use bun_jsc::VirtualMachineRef as VirtualMachine; -use crate::node::node_fs_watcher::{Event, FSWatcher, WatchEventKind}; +use crate::node::node_fs_watcher::{CloseWatcher, Event, FSWatcher, Recursive, WatchEventKind}; #[cfg(target_os = "macos")] use crate::node::fs_events as fsevents; @@ -165,9 +165,13 @@ impl PathWatcherManager { } /// Build the dedup key into `buf`. Not null-terminated; only used as a hashmap key. - fn make_key<'a>(buf: &'a mut [u8], resolved_path: &[u8], recursive: bool) -> &'a [u8] { + fn make_key<'a>(buf: &'a mut [u8], resolved_path: &[u8], recursive: Recursive) -> &'a [u8] { buf[..resolved_path.len()].copy_from_slice(resolved_path); - buf[resolved_path.len()] = if recursive { b'R' } else { b'N' }; + buf[resolved_path.len()] = if recursive == Recursive::Yes { + b'R' + } else { + b'N' + }; &buf[..resolved_path.len() + 1] } @@ -196,7 +200,7 @@ pub struct PathWatcher { #[cfg(not(windows))] path: ZBox, #[cfg(not(windows))] - recursive: bool, + recursive: Recursive, #[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))] is_file: bool, @@ -305,7 +309,7 @@ impl PathWatcher { } #[cfg(not(windows))] - fn emit_error(&self, err: &sys::Error, close: bool) { + fn emit_error(&self, err: &sys::Error, close: CloseWatcher) { for &ctx in self.handlers.keys() { (FSWatcher::ON_PATH_UPDATE)( Some(ctx), @@ -423,7 +427,7 @@ impl PathWatcher { pub(crate) fn watch( vm: &VirtualMachine, path: &ZStr, - recursive: bool, + recursive: Recursive, callback: Callback, update_end: UpdateEndCallback, ctx: *mut c_void, @@ -562,7 +566,7 @@ pub(crate) fn watch( // SAFETY: watcher live under manager.mutex; `emit_error`/`flush` // take `&self`. unsafe { - (*watcher).emit_error(&err, true); + (*watcher).emit_error(&err, CloseWatcher::Yes); (*watcher).flush(); } manager.mutex.unlock(); @@ -757,10 +761,10 @@ impl Linux { // Borrowck: clone path to avoid &/&mut overlap on watcher. let root = watcher.path.clone(); Linux::add_one(manager, watcher, &root, b"")?; - if watcher.recursive && !watcher.is_file { + if watcher.recursive == Recursive::Yes && !watcher.is_file { if let Some(err) = Linux::walk_and_add(manager, watcher, &root, b"") { // Partial coverage: emit 'error' but keep the watcher, like node. - watcher.emit_error(&err, false); + watcher.emit_error(&err, CloseWatcher::No); watcher.flush(); } } @@ -912,7 +916,7 @@ impl Linux { for &w in watchers.values() { // SAFETY: holding manager.mutex; w is live. unsafe { - (*w).emit_error(&err, true); + (*w).emit_error(&err, CloseWatcher::Yes); (*w).flush(); } } @@ -971,7 +975,9 @@ impl Linux { // SAFETY: o.watcher live under manager.mutex; shared // access only — `emit_unsuppressed` takes `&self`. let w = unsafe { &*o.watcher }; - if o.subpath.as_bytes().is_empty() && (w.is_file || !w.recursive) { + if o.subpath.as_bytes().is_empty() + && (w.is_file || w.recursive == Recursive::No) + { w.emit_unsuppressed( WatchEventKind::Rename, path::basename(w.path.as_bytes()), @@ -1061,7 +1067,7 @@ impl Linux { let (watcher_is_file, watcher_recursive, watcher_path): (bool, bool, &[u8]) = unsafe { ( (*owner_watcher).is_file, - (*owner_watcher).recursive, + (*owner_watcher).recursive == Recursive::Yes, &*std::ptr::from_ref::<[u8]>((*owner_watcher).path.as_bytes()), ) }; @@ -1165,7 +1171,7 @@ impl Linux { if let Some(err) = add_err { // SAFETY: owner_watcher live under manager.mutex; // `emit_error` takes `&self`. - unsafe { (*owner_watcher).emit_error(&err, false) }; + unsafe { (*owner_watcher).emit_error(&err, CloseWatcher::No) }; } } @@ -1415,7 +1421,7 @@ impl Kqueue { let root = watcher.path.clone(); let is_file = watcher.is_file; Kqueue::add_one(manager, watcher, &root, b"", is_file)?; - if watcher.recursive && !watcher.is_file { + if watcher.recursive == Recursive::Yes && !watcher.is_file { // kqueue needs an open fd per *file* as well as per directory. let mut first_err: Option = None; walk_subtree::(&root, b"", &mut |abs, rel, is_file| { @@ -1427,7 +1433,7 @@ impl Kqueue { }); if let Some(err) = first_err { // Partial coverage: emit 'error' but keep the watcher, like node. - watcher.emit_error(&err, false); + watcher.emit_error(&err, CloseWatcher::No); watcher.flush(); } } diff --git a/src/runtime/node/quic/endpoint.rs b/src/runtime/node/quic/endpoint.rs index e8ce43d7bb33..09d5d31f2927 100644 --- a/src/runtime/node/quic/endpoint.rs +++ b/src/runtime/node/quic/endpoint.rs @@ -17,10 +17,11 @@ use bun_uws as uws; use crate::jsc_hooks::timer_all_mut as timer_all; use crate::timer::{EventLoopTimer, EventLoopTimerState, EventLoopTimerTag}; +use super::Side; use super::callbacks; use super::ffi::lsquic_callback; use super::now_ns; -use super::session::{self, QuicSession, SOCKADDR_IN_LEN, SOCKADDR_IN6_LEN, StoredAddr}; +use super::session::{self, CloseKind, QuicSession, SOCKADDR_IN_LEN, SOCKADDR_IN6_LEN, StoredAddr}; use super::stream; use super::tls::{TlsConfig, TlsContext}; @@ -317,11 +318,15 @@ impl QuicEndpoint { fn mark_driver_pending(&self) { self.nq_driver.with_mut(|d| d.pending = 1); } +} + +bun_core::bool_enum!(DeferCloses); +impl QuicEndpoint { /// Runs a driver pass, or hands `pending` back when one cannot run now. /// `defer_closes` distinguishes the microtask-drain pass, which must not /// let a session end mid-chain, from the loop_pre/loop_post pass. - fn run_driver_pass(&self, defer_closes: bool) { + fn run_driver_pass(&self, defer_closes: DeferCloses) { if self.closed.get() { return; } @@ -336,7 +341,7 @@ impl QuicEndpoint { // SAFETY: `global_ptr` is the realm that created this endpoint and // outlives it; null was ruled out above. let global = unsafe { &*global_ptr }; - self.defer_closes.set(defer_closes); + self.defer_closes.set(defer_closes == DeferCloses::Yes); self.process(global); self.defer_closes.set(false); } @@ -361,7 +366,7 @@ impl QuicEndpoint { #[unsafe(no_mangle)] pub(crate) unsafe extern "C" fn Bun__nodeQuic__drainEndpoint(owner: *mut c_void) { // SAFETY: guaranteed by this function's contract. - unsafe { QuicEndpoint::from_driver_owner(owner) }.run_driver_pass(true); + unsafe { QuicEndpoint::from_driver_owner(owner) }.run_driver_pass(DeferCloses::Yes); } /// One process pass per loop turn (loop_pre/loop_post): the writes a JS turn @@ -372,7 +377,7 @@ pub(crate) unsafe extern "C" fn Bun__nodeQuic__drainEndpoint(owner: *mut c_void) #[unsafe(no_mangle)] pub(crate) unsafe extern "C" fn Bun__nodeQuic__processEndpoint(owner: *mut c_void) { // SAFETY: guaranteed by this function's contract. - unsafe { QuicEndpoint::from_driver_owner(owner) }.run_driver_pass(false); + unsafe { QuicEndpoint::from_driver_owner(owner) }.run_driver_pass(DeferCloses::No); } bun_event_loop::impl_timer_owner!(QuicEndpoint; from_timer_ptr => event_loop_timer); @@ -669,7 +674,7 @@ lsquic_callback! { // (connect() then immediate close()). Report the peer's // own code so `closed` settles the way node's does. session.push_event(session::SessionEvent::PeerClose { - app_error: false, + app_error: CloseKind::Transport, code: error_code & !PEER_CLOSE_BIT, reason: Vec::new(), }); @@ -682,7 +687,7 @@ lsquic_callback! { // dropped packets): node's server surfaces the idle death // of a handshaking session as a clean close, not an error. session.push_event(session::SessionEvent::PeerClose { - app_error: false, + app_error: CloseKind::Transport, code: 0, reason: Vec::new(), }); @@ -697,7 +702,7 @@ lsquic_callback! { b"handshake failed" }; session.push_event(session::SessionEvent::PeerClose { - app_error: false, + app_error: CloseKind::Transport, code, reason: reason.to_vec(), }); @@ -1728,7 +1733,7 @@ impl QuicEndpoint { core::ptr::from_ref(self).cast_mut(), endpoint_handle, null_mut(), - true, + Side::Server, )?; let applied = self.apply_server_session_options(global, session); self.sessions.with_mut(|v| v.push(session)); @@ -1751,7 +1756,7 @@ impl QuicEndpoint { return false; }; session.push_event(session::SessionEvent::PeerClose { - app_error: false, + app_error: CloseKind::Transport, code: CRYPTO_ERROR_HANDSHAKE_FAILURE, reason: b"handshake failed".to_vec(), }); @@ -1882,7 +1887,7 @@ impl QuicEndpoint { core::ptr::from_ref(self).cast_mut(), endpoint_handle, conn, - true, + Side::Server, ) { Ok((session, _handle)) => { if let Err(err) = self.apply_server_session_options(global, session) { @@ -1908,8 +1913,8 @@ impl QuicEndpoint { } } - pub(super) fn configured_alpn(&self, is_server: bool) -> Option> { - let alpn = if is_server { + pub(super) fn configured_alpn(&self, is_server: Side) -> Option> { + let alpn = if is_server == Side::Server { self.server_alpn.get() } else { self.client_alpn.get() @@ -1922,8 +1927,8 @@ impl QuicEndpoint { } } - pub(super) fn is_http(&self, is_server: bool) -> bool { - if is_server { + pub(super) fn is_http(&self, is_server: Side) -> bool { + if is_server == Side::Server { self.server_is_http.get() } else { self.client_is_http.get() @@ -1972,11 +1977,12 @@ impl QuicEndpoint { fn build_engine( &self, - is_server: bool, + is_server: Side, config: &TlsConfig, options: JSValue, global: &JSGlobalObject, ) -> JsResult<*mut lsquic::lsquic_engine> { + let is_server = is_server == Side::Server; let tls = TlsContext::new(config).map_err(|e| global.throw(format_args!("tls: {}", e)))?; // Node accepts a list, so own ALPN on the SSL_CTX and pass NULL here. let alpn_cstr = TlsContext::alpn_cstr(config); @@ -2235,7 +2241,7 @@ impl QuicEndpoint { self.origin_blob.with_mut(|b| *b = blob); } } - let mut config = TlsConfig::from_js(global, tls, true)?; + let mut config = TlsConfig::from_js(global, tls, Side::Server)?; if config.alpn.is_empty() { // Node's default ALPN is `h3`. config.alpn = b"\x02h3".to_vec(); @@ -2247,7 +2253,7 @@ impl QuicEndpoint { self.sni_contexts.with_mut(|m| *m = built); } } - let engine = self.build_engine(true, &config, options, global)?; + let engine = self.build_engine(Side::Server, &config, options, global)?; self.server_engine.set(engine); } // SAFETY: state buffer is live. @@ -2288,9 +2294,9 @@ impl QuicEndpoint { return self.connect_verneg_probe(global, frame.this(), remote, version, min_version); } let tls = options.get(global, "tls")?.unwrap_or(JSValue::UNDEFINED); - let config = TlsConfig::from_js(global, tls, false)?; + let config = TlsConfig::from_js(global, tls, Side::Client)?; if self.client_engine.get().is_null() { - let engine = self.build_engine(false, &config, options, global)?; + let engine = self.build_engine(Side::Client, &config, options, global)?; self.client_engine.set(engine); } else { if alpn_cstr_is_http(&TlsContext::alpn_cstr(&config)) != self.client_is_http.get() { @@ -2350,7 +2356,7 @@ impl QuicEndpoint { core::ptr::from_ref(self).cast_mut(), frame.this(), null_mut(), - false, + Side::Client, )?; // `TlsConfig::from_js` defaults servername to "localhost\0" (Node parity). let sni = config.servername.as_ref(); @@ -2440,7 +2446,7 @@ impl QuicEndpoint { core::ptr::from_ref(self).cast_mut(), this_value, null_mut(), - false, + Side::Client, )?; let mut dcid = [0u8; VERNEG_PROBE_CID_LEN]; let mut scid = [0u8; VERNEG_PROBE_CID_LEN]; @@ -2593,7 +2599,7 @@ impl QuicEndpoint { if !value.is_object() { continue; } - let mut config = TlsConfig::from_js(global, value, true)?; + let mut config = TlsConfig::from_js(global, value, Side::Server)?; if config.alpn.is_empty() { config.alpn = alpn.to_vec(); } diff --git a/src/runtime/node/quic/mod.rs b/src/runtime/node/quic/mod.rs index c1bc7322ad0f..c3538dc34ebf 100644 --- a/src/runtime/node/quic/mod.rs +++ b/src/runtime/node/quic/mod.rs @@ -10,6 +10,11 @@ pub use endpoint::QuicEndpoint; pub use session::QuicSession; pub use stream::QuicStream; +bun_core::bool_enum!( + /// Which half of the endpoint a session/engine/TLS config belongs to. + pub Side { Client, Server } +); + /// Monotonic nanoseconds, mirroring Node's use of `uv_hrtime()` for the /// `*_AT` stats slots. pub(crate) fn now_ns() -> u64 { diff --git a/src/runtime/node/quic/session.rs b/src/runtime/node/quic/session.rs index 6eb816790bd4..2780f7a27a43 100644 --- a/src/runtime/node/quic/session.rs +++ b/src/runtime/node/quic/session.rs @@ -10,6 +10,9 @@ use bun_jsc::{ }; use bun_lsquic_sys as lsquic; +use bun_uws::Fin; + +use super::Side; use super::callbacks; use super::endpoint::{MS_PER_SEC, QuicEndpoint, alloc_exposed_array_buffer}; @@ -265,12 +268,20 @@ pub(super) struct DeferredAbort { marker: u64, } +bun_core::bool_enum!( + /// CONNECTION_CLOSE frame type: transport (0x1c) vs application (0x1d). + pub(super) CloseKind { Transport, Application } +); +bun_core::bool_enum!(pub(super) StreamDirection { Bidi, Uni }); +bun_core::bool_enum!(StreamOrigin { Remote, Local }); +bun_core::bool_enum!(HandshakeOk); + pub(super) enum SessionEvent { HandshakeDone { ok: bool, }, PeerClose { - app_error: bool, + app_error: CloseKind, code: u64, reason: Vec, }, @@ -366,10 +377,10 @@ pub struct QuicSession { /// on the next depth-0 pass so its GOAWAY/CONNECTION_CLOSE does not share /// a flight with data written by the same dispatch (node's close lands an /// RTT after the data because its stream close is ack-gated). - pending_graceful: JsCell)>>, + pending_graceful: JsCell)>>, pub(super) verneg: Cell>, - peer_close: JsCell)>>, - self_close: JsCell)>>, + peer_close: JsCell)>>, + self_close: JsCell)>>, datagram_drop_newest: Cell, qlog_enabled: Cell, qlog_fin_sent: Cell, @@ -387,7 +398,7 @@ pub struct QuicSession { handshake_reported: Cell, new_token_reported: Cell, close_when_bound: Cell, - deferred_close: JsCell)>>, + deferred_close: JsCell)>>, handshake_pending_ok: Cell, reject_unverified_peer: Cell, peer_cert_rejected: Cell, @@ -454,7 +465,7 @@ impl QuicSession { endpoint: *mut QuicEndpoint, endpoint_handle: JSValue, conn: *mut lsquic::lsquic_conn, - is_server: bool, + is_server: Side, ) -> JsResult<(*mut QuicSession, JSValue)> { let raw = bun_core::heap::into_raw(Box::new(Self::new(global, vtable))); let handle = crate::generated_classes::js_QuicSession::to_js(raw, global); @@ -496,7 +507,7 @@ impl QuicSession { this.endpoint.set(endpoint); this.endpoint_js .set(Some(Strong::create(endpoint_handle, global))); - this.is_server.set(is_server); + this.is_server.set(is_server == Side::Server); this.this_value.with_mut(|r| r.set_strong(handle, global)); this.write_stat(IDX_STATS_SESSION_CREATED_AT, now_ns()); let _ = this.vtable; @@ -613,6 +624,9 @@ impl QuicSession { pub(super) fn is_server(&self) -> bool { self.is_server.get() } + fn side(&self) -> Side { + Side::from_bool(self.is_server.get()) + } pub(super) fn push_event(&self, event: SessionEvent) { self.events.with_mut(|e| e.push(event)); } @@ -690,9 +704,9 @@ impl QuicSession { } pub(super) fn take_pending_local_stream( &self, - uni: bool, + uni: StreamDirection, ) -> Option<*mut super::stream::QuicStream> { - let queue = if uni { + let queue = if uni == StreamDirection::Uni { &self.pending_local_uni } else { &self.pending_local_bidi @@ -722,12 +736,12 @@ impl QuicSession { }); } } - fn bump_stream_stat(&self, id: u64, local: bool) { + fn bump_stream_stat(&self, id: u64, local: StreamOrigin) { let idx = match (id & STREAM_ID_UNI_BIT != 0, local) { - (false, false) => IDX_STATS_SESSION_BIDI_IN_STREAM_COUNT, - (false, true) => IDX_STATS_SESSION_BIDI_OUT_STREAM_COUNT, - (true, false) => IDX_STATS_SESSION_UNI_IN_STREAM_COUNT, - (true, true) => IDX_STATS_SESSION_UNI_OUT_STREAM_COUNT, + (false, StreamOrigin::Remote) => IDX_STATS_SESSION_BIDI_IN_STREAM_COUNT, + (false, StreamOrigin::Local) => IDX_STATS_SESSION_BIDI_OUT_STREAM_COUNT, + (true, StreamOrigin::Remote) => IDX_STATS_SESSION_UNI_IN_STREAM_COUNT, + (true, StreamOrigin::Local) => IDX_STATS_SESSION_UNI_OUT_STREAM_COUNT, }; let stats = self.stats.get(); if !stats.is_null() { @@ -764,7 +778,7 @@ impl QuicSession { unsafe { (*qs).mark_wrote_to_lsquic() }; // SAFETY: `raw` is the live stream lsquic just created. if let Some(s) = unsafe { lsquic::Stream::from_raw(raw) } { - self.bump_stream_stat(s.id(), false); + self.bump_stream_stat(s.id(), StreamOrigin::Remote); } self.push_event(SessionEvent::StreamReady { stream: qs, @@ -929,18 +943,18 @@ impl QuicSession { SessionEvent::HandshakeDone { ok } => { if ok { self.capture_hsk_snapshot(); - if self.is_server.get() || self.peer_verification_refused() { + if self.is_server() || self.peer_verification_refused() { // Node's server reports at handshake COMPLETION // (session.cc: server completion == confirmation // per RFC 9001 §4.1.2). - self.maybe_report_handshake(global, true); + self.maybe_report_handshake(global, HandshakeOk::Yes); } else { // Node's client `opened` settles only for // connections the server actually accepted. self.handshake_pending_ok.set(true); } } else { - self.maybe_report_handshake(global, false); + self.maybe_report_handshake(global, HandshakeOk::No); } } SessionEvent::HandshakeConfirmed => { @@ -958,7 +972,7 @@ impl QuicSession { SessionEvent::PeerClose { app_error, code, .. } => { - return !*app_error && *code != 0; + return *app_error == CloseKind::Transport && *code != 0; } SessionEvent::Closed => return true, _ => {} @@ -968,7 +982,7 @@ impl QuicSession { }); if !close_wins { self.handshake_pending_ok.set(false); - self.maybe_report_handshake(global, true); + self.maybe_report_handshake(global, HandshakeOk::Yes); } } } @@ -1004,7 +1018,7 @@ impl QuicSession { && self.with_state(|st| st.graceful_close == 1) && !self .endpoint_ref() - .map(|ep| ep.is_http(self.is_server.get())) + .map(|ep| ep.is_http(self.side())) .unwrap_or(false) { stream.suppress_announce(); @@ -1349,7 +1363,7 @@ impl QuicSession { // Node passes each fact only from the side that owns it: // the server knows the previous path, the client knows it // migrated to the preferred address. - let (old_local_js, old_remote_js, preferred_js) = if self.is_server.get() { + let (old_local_js, old_remote_js, preferred_js) = if self.is_server() { ( old_local.to_js_socket_address(global), old_remote.to_js_socket_address(global), @@ -1428,15 +1442,16 @@ impl QuicSession { Ok(()) } - fn maybe_report_handshake(&self, global: &JSGlobalObject, ok: bool) { + fn maybe_report_handshake(&self, global: &JSGlobalObject, ok: HandshakeOk) { if self.handshake_reported.replace(true) || self.destroyed.get() { return; } + let ok = ok == HandshakeOk::Yes; if ok { self.capture_hsk_snapshot(); } let cert_ok = { - if let Some(endpoint) = self.endpoint_ref().filter(|_| ok && self.is_server.get()) { + if let Some(endpoint) = self.endpoint_ref().filter(|_| ok && self.is_server()) { let verify_client = endpoint.server_verify_client.get(); !verify_client || self @@ -1498,7 +1513,7 @@ impl QuicSession { // close-error codes to RFC 9114's H3_NO_ERROR / H3_INTERNAL_ERROR. let is_http = self .endpoint_ref() - .map(|ep| ep.is_http(self.is_server.get())) + .map(|ep| ep.is_http(self.side())) .unwrap_or(false) && alpn_bytes .as_deref() @@ -1526,7 +1541,7 @@ impl QuicSession { // no client certificate reports X509_V_ERR_UNSPECIFIED. let pair = match snap_validation { Some(pair) => Some(pair), - None if self.is_server.get() && !have_peer_cert => { + None if self.is_server() && !have_peer_cert => { Some(tls::validation_error_strings(tls::X509_V_ERR_UNSPECIFIED)) } None => None, @@ -1566,12 +1581,12 @@ impl QuicSession { let chunk = format!( "\u{1e}{{\"qlog_version\":\"0.3\",\"qlog_format\":\"JSON-SEQ\",\"title\":\"bun node:quic\"}}\n\u{1e}{{\"time\":{t},\"name\":\"connectivity:connection_started\",\"data\":{{}}}}\n" ); - self.emit_qlog(global, &chunk, false); + self.emit_qlog(global, &chunk, Fin::No); } // Node destroys the early streams and fires `onearlyrejected` — on // the CLIENT only. - if early_data.0 && !early_data.1 && !self.is_server.get() { + if early_data.0 && !early_data.1 && !self.is_server() { if let Some(callback) = callbacks::get(global, "onSessionEarlyDataRejected") { let vm = global.bun_vm().as_mut(); vm.event_loop_ref() @@ -1584,7 +1599,7 @@ impl QuicSession { if !cert_ok && !self.destroyed.get() && !self.conn.get().is_null() { self.self_close.with_mut(|s| { *s = Some(( - false, + CloseKind::Transport, CRYPTO_ERROR_CERTIFICATE_REQUIRED, b"peer did not provide a certificate".to_vec(), )); @@ -1622,7 +1637,7 @@ impl QuicSession { let ssl = conn.ssl().cast(); let alpn = tls::negotiated_alpn(ssl).or_else(|| { self.endpoint_ref() - .and_then(|ep| ep.configured_alpn(self.is_server.get())) + .and_then(|ep| ep.configured_alpn(self.side())) }); let validation = tls::validation_error(ssl); let peer_cert_der = tls::peer_certificate_der(ssl); @@ -1645,10 +1660,11 @@ impl QuicSession { /// Deliver one qlog chunk (RFC 7464 JSON-SEQ records) via /// `onSessionQlog(data, fin)`. - fn emit_qlog(&self, global: &JSGlobalObject, data: &str, fin: bool) { + fn emit_qlog(&self, global: &JSGlobalObject, data: &str, fin: Fin) { if !self.qlog_enabled.get() || self.qlog_fin_sent.get() { return; } + let fin = fin == Fin::Yes; if fin { self.qlog_fin_sent.set(true); } @@ -1687,15 +1703,21 @@ impl QuicSession { let chunk = format!( "\u{1e}{{\"time\":{t},\"name\":\"connectivity:connection_closed\",\"data\":{{}}}}\n" ); - self.emit_qlog(global, &chunk, true); + self.emit_qlog(global, &chunk, Fin::Yes); } let (error_type, code, reason): (i32, u64, Option>) = match taken { - Some((app, code, reason)) => (if app { 1 } else { 0 }, code, Some(reason)), + Some((app, code, reason)) => ( + if app == CloseKind::Application { 1 } else { 0 }, + code, + Some(reason), + ), None if self.conn.get().is_null() => { match self.final_conn_status.with_mut(Option::take) { - Some((status, msg)) => { - map_conn_status(status, msg, self.handshake_reported.get()) - } + Some((status, msg)) => map_conn_status( + status, + msg, + HandshakeReported::from_bool(self.handshake_reported.get()), + ), None => (0, 0, None), } } @@ -1711,7 +1733,11 @@ impl QuicSession { let msg = unsafe { core::ffi::CStr::from_ptr(buf.as_ptr()) } .to_bytes() .to_vec(); - map_conn_status(status, msg, self.handshake_reported.get()) + map_conn_status( + status, + msg, + HandshakeReported::from_bool(self.handshake_reported.get()), + ) } }; // `close_reported` is already latched above, so returning here would @@ -1803,19 +1829,21 @@ impl QuicSession { &self, global: &JSGlobalObject, options: JSValue, - ) -> JsResult<(bool, u64, Vec)> { - let mut app = false; + ) -> JsResult<(CloseKind, u64, Vec)> { + let mut app = CloseKind::Transport; let mut code = 0u64; let mut reason = Vec::new(); if options.is_object() { - app = options - .get(global, "type")? - .map(|v| { - bun_core::String::from_js(v, global) - .map(|s| s.to_utf8_bytes() == b"application") - }) - .transpose()? - .unwrap_or(false); + app = CloseKind::from_bool( + options + .get(global, "type")? + .map(|v| { + bun_core::String::from_js(v, global) + .map(|s| s.to_utf8_bytes() == b"application") + }) + .transpose()? + .unwrap_or(false), + ); code = super::endpoint::read_u64_option(global, options, "code")?.unwrap_or(0); reason = options .get(global, "reason")? @@ -1831,12 +1859,12 @@ impl QuicSession { Ok((app, code, reason)) } - fn apply_graceful_close(&self, app: bool, code: u64, reason: Vec) { + fn apply_graceful_close(&self, app: CloseKind, code: u64, reason: Vec) { let is_http = self .endpoint_ref() - .map(|ep| ep.is_http(self.is_server.get())) + .map(|ep| ep.is_http(self.side())) .unwrap_or(false); - if is_http && !app && code == 0 && !self.streams.get().is_empty() { + if is_http && app == CloseKind::Transport && code == 0 && !self.streams.get().is_empty() { // RFC 9114 §5.2. if let Some(c) = self.conn() { c.going_away(); @@ -1868,8 +1896,9 @@ impl QuicSession { false } - fn apply_close(&self, app: bool, code: u64, reason: &[u8]) { + fn apply_close(&self, app: CloseKind, code: u64, reason: &[u8]) { let Some(c) = self.conn() else { return }; + let app = app == CloseKind::Application; if app || code != 0 || reason.len() > 1 { let creason = core::ffi::CStr::from_bytes_until_nul(reason).unwrap_or(c"close"); c.abort_error(app, code.min(u32::MAX as u64) as core::ffi::c_uint, creason); @@ -1921,7 +1950,7 @@ impl QuicSession { self.parse_close_options(global, frame.arguments_as_array::<1>()[0])?; self.with_state(|s| s.graceful_close = 1); if self.conn.get().is_null() { - if self.is_server.get() && !self.close_reported.get() { + if self.is_server() && !self.close_reported.get() { self.pending_graceful .with_mut(|p| *p = Some((app, code, reason))); self.close_when_bound.set(true); @@ -2014,7 +2043,10 @@ impl QuicSession { self.pending_local_bidi.with_mut(|q| q.push_back(qs)); } self.streams.with_mut(|v| v.push(qs)); - self.bump_stream_stat(if unidirectional { STREAM_ID_UNI_BIT } else { 0 }, true); + self.bump_stream_stat( + if unidirectional { STREAM_ID_UNI_BIT } else { 0 }, + StreamOrigin::Local, + ); if let Some(buf) = body.as_array_buffer(global) { // SAFETY: `qs` was just created. unsafe { @@ -2056,7 +2088,7 @@ impl QuicSession { // (RFC 9297 §2.1.1); otherwise not sent (0n). let is_http = self .endpoint_ref() - .is_some_and(|ep| ep.is_http(self.is_server.get())); + .is_some_and(|ep| ep.is_http(self.side())); if is_http && self.conn().and_then(|c| c.peer_h3_datagram()) == Some(false) { return JSValue::from_uint64_no_truncate(global, 0); } @@ -2309,7 +2341,7 @@ impl QuicSession { let Some(ep) = self.endpoint_ref() else { return Ok(JSValue::UNDEFINED); }; - let tp = if self.is_server.get() { + let tp = if self.is_server() { ep.server_local_tp.get() } else { ep.client_local_tp.get() @@ -2368,7 +2400,7 @@ lsquic_callback! { session.peer_cert_rejected.set(true); session.self_close.with_mut(|s| { *s = Some(( - false, + CloseKind::Transport, CRYPTO_ERROR_BAD_CERTIFICATE, b"peer certificate verification failed".to_vec(), )); @@ -2386,17 +2418,19 @@ lsquic_callback! { } } +bun_core::bool_enum!(HandshakeReported); + /// Map an `lsquic_conn_status` to Node's `onSessionClose(type, code, /// reason)` shape (`type`: 0=transport, 1=application, 2=version-neg, /// 3=idle). fn map_conn_status( status: c_int, msg: Vec, - handshake_reported: bool, + handshake_reported: HandshakeReported, ) -> (i32, u64, Option>) { match status { // Node rejects `opened` with a transport error. - lsquic::LSCONN_ST_TIMED_OUT if !handshake_reported => ( + lsquic::LSCONN_ST_TIMED_OUT if handshake_reported == HandshakeReported::No => ( 0, CRYPTO_ERROR_HANDSHAKE_FAILURE, Some(b"handshake timed out".to_vec()), @@ -2450,7 +2484,7 @@ lsquic_callback! { } }; session.push_event(SessionEvent::PeerClose { - app_error: app_error == 1, + app_error: CloseKind::from_bool(app_error == 1), code, reason, }); diff --git a/src/runtime/node/quic/stream.rs b/src/runtime/node/quic/stream.rs index 1d9937165621..a8aa6996f0de 100644 --- a/src/runtime/node/quic/stream.rs +++ b/src/runtime/node/quic/stream.rs @@ -8,10 +8,11 @@ use bun_jsc::{ Strong, }; use bun_lsquic_sys as lsquic; +use bun_uws::Fin; use super::endpoint::alloc_exposed_array_buffer; use super::ffi::lsquic_callback; -use super::session::{QuicSession, SessionEvent}; +use super::session::{QuicSession, SessionEvent, StreamDirection}; const QUIC_STREAM_HEADERS_KIND_HINTS: u32 = 0; const QUIC_STREAM_HEADERS_KIND_INITIAL: u32 = 1; @@ -360,7 +361,7 @@ impl QuicStream { self.with_state(|s| s.id) } - fn push_inbound(&self, data: &[u8], fin: bool) { + fn push_inbound(&self, data: &[u8], fin: Fin) { if self.destroyed.get() { return; } @@ -368,7 +369,7 @@ impl QuicStream { if !data.is_empty() { inbound.chunks.push_back(data.to_vec()); } - if fin { + if fin == Fin::Yes { inbound.ended = true; } }); @@ -382,7 +383,7 @@ impl QuicStream { self.read_stat(IDX_STATS_MAX_BYTES_ACCUMULATED).max(acc), ); } - if fin { + if fin == Fin::Yes { self.with_state(|s| s.fin_received = 1); } } @@ -1000,7 +1001,9 @@ pub(super) unsafe extern "C" fn on_new_stream( let id = stream.id(); let is_local = (id & 1 == 0) != session.is_server(); if is_local { - if let Some(qs) = session.take_pending_local_stream(id & STREAM_ID_UNI_BIT as u64 != 0) { + if let Some(qs) = session.take_pending_local_stream(StreamDirection::from_bool( + id & STREAM_ID_UNI_BIT as u64 != 0, + )) { // SAFETY: pending streams are kept alive by their wrapper Strong. unsafe { (*qs).bind_raw(s) }; session.push_event(SessionEvent::StreamReady { @@ -1080,11 +1083,11 @@ pub(super) unsafe extern "C" fn on_stream_read(ctx: *mut c_void, s: *mut lsquic: let n = stream.read(&mut buf); match n { n if n > 0 => { - qs.push_inbound(&buf[..n as usize], false); + qs.push_inbound(&buf[..n as usize], Fin::No); got_any = true; } 0 => { - qs.push_inbound(&[], true); + qs.push_inbound(&[], Fin::Yes); stream.want_read(false); let write_done = qs.with_state(|s| s.fin_sent != 0 || s.write_ended != 0); if write_done { diff --git a/src/runtime/node/quic/tls.rs b/src/runtime/node/quic/tls.rs index b072647d323a..caed0eaa7db3 100644 --- a/src/runtime/node/quic/tls.rs +++ b/src/runtime/node/quic/tls.rs @@ -4,8 +4,10 @@ use core::ptr::null_mut; use bun_boringssl_sys as ssl; use bun_jsc::{JSGlobalObject, JSValue, JsResult, StringJsc}; +use super::Side; + pub(super) struct TlsConfig { - pub is_server: bool, + pub is_server: Side, pub alpn: Vec, pub servername: Option>, pub certs_pem: Vec>, @@ -87,7 +89,7 @@ impl TlsConfig { pub(super) fn from_js( global: &JSGlobalObject, tls: JSValue, - is_server: bool, + is_server: Side, ) -> JsResult { let mut config = TlsConfig { is_server, @@ -471,7 +473,7 @@ impl TlsContext { if config.keylog { ssl::SSL_CTX_set_keylog_callback(ctx, Some(keylog_cb)); } - if config.is_server { + if config.is_server == Side::Server { if config.verify_client { // SSL_VERIFY_PEER alone matches Node's TLS 1.3 semantics (see QuicSession::maybe_report_handshake). ssl::SSL_CTX_set_verify(ctx, ssl::SSL_VERIFY_PEER, None); diff --git a/src/runtime/node/types.rs b/src/runtime/node/types.rs index 465c7c6f07b1..93a3cb595967 100644 --- a/src/runtime/node/types.rs +++ b/src/runtime/node/types.rs @@ -938,7 +938,7 @@ pub trait PathLikeExt { fn from_bun_string( global: &JSGlobalObject, str: &mut bun_core::String, - will_be_async: bool, + will_be_async: Flavor, ) -> JsResult where Self: Sized; @@ -1217,11 +1217,12 @@ impl PathLikeExt for PathLike { arguments.eat(); - Ok(Some(Self::from_bun_string( - ctx, - &mut str, - arguments.will_be_async, - )?)) + let flavor = if arguments.will_be_async { + Flavor::Async + } else { + Flavor::Sync + }; + Ok(Some(Self::from_bun_string(ctx, &mut str, flavor)?)) } _ => { if let Some(domurl) = jsc::DOMURL::cast(arg) { @@ -1263,11 +1264,12 @@ impl PathLikeExt for PathLike { } arguments.eat(); - return Ok(Some(Self::from_bun_string( - ctx, - &mut str, - arguments.will_be_async, - )?)); + let flavor = if arguments.will_be_async { + Flavor::Async + } else { + Flavor::Sync + }; + return Ok(Some(Self::from_bun_string(ctx, &mut str, flavor)?)); } Ok(None) @@ -1278,9 +1280,9 @@ impl PathLikeExt for PathLike { fn from_bun_string( global: &JSGlobalObject, str: &mut bun_core::String, - will_be_async: bool, + will_be_async: Flavor, ) -> JsResult { - if will_be_async { + if will_be_async == Flavor::Async { let sliced = str.to_thread_safe_slice(); let sliced = scopeguard::guard(sliced, |s| s.deinit()); @@ -1445,6 +1447,8 @@ unsafe extern "C" fn append_buffer_span( out.views.push(element); } +bun_core::bool_enum!(pub PinBuffers); + impl VectorArrayBuffer { /// Collect an array of ArrayBufferViews into iovecs. Every element is read /// before any raw pointer is taken, so user code run by an indexed read (a @@ -1457,8 +1461,9 @@ impl VectorArrayBuffer { pub fn from_js( global_object: &JSGlobalObject, val: JSValue, - pin: bool, + pin: PinBuffers, ) -> JsResult { + let pin = pin == PinBuffers::Yes; let mut out = VectorArrayBuffer { value: val, buffers: Vec::new(), diff --git a/src/runtime/node/win_watcher.rs b/src/runtime/node/win_watcher.rs index c97a3e5e0a65..92c99a0c9099 100644 --- a/src/runtime/node/win_watcher.rs +++ b/src/runtime/node/win_watcher.rs @@ -19,7 +19,9 @@ use bun_threading::Mutex; use super::node_fs_watcher::WatchEventKind; // The callbacks are *associated functions* on `FSWatcher`, not free fns. -use crate::node::node_fs_watcher::{Event, FSWatcher, StringOrBytesToDecode}; +use crate::node::node_fs_watcher::{ + CloseWatcher, Event, FSWatcher, Recursive, StringOrBytesToDecode, +}; #[allow(non_upper_case_globals)] const on_path_update_fn: fn(Option<*mut c_void>, Event, bool) = FSWatcher::ON_PATH_UPDATE; #[allow(non_upper_case_globals)] @@ -230,7 +232,7 @@ impl PathWatcher { Some(ctx), Event::Error { err: err.clone(), - close: true, + close: CloseWatcher::Yes, }, false, ); @@ -337,7 +339,7 @@ impl PathWatcher { fn init( manager: *mut PathWatcherManager, path: &ZStr, - recursive: bool, + recursive: Recursive, ) -> sys::Result<*mut PathWatcher> { let mut outbuf = PathBuffer::uninit(); // Windows `sys::readlink` returns the byte length; the link target is @@ -395,7 +397,7 @@ impl PathWatcher { ptr::addr_of_mut!((*this).handle), Some(PathWatcher::uv_event_callback), event_path.as_ptr().cast::(), - if recursive { + if recursive == Recursive::Yes { uv::UV_FS_EVENT_RECURSIVE as u32 } else { 0 @@ -510,7 +512,7 @@ impl PathWatcher { pub(crate) fn watch( vm: &'static jsc::VirtualMachineRef, path: &ZStr, - recursive: bool, + recursive: Recursive, ctx: *mut c_void, ) -> sys::Result<*mut PathWatcher> { #[cfg(not(windows))] diff --git a/src/runtime/server/DirectoryRoute.rs b/src/runtime/server/DirectoryRoute.rs index 96201de0034e..87b6d16ed9f9 100644 --- a/src/runtime/server/DirectoryRoute.rs +++ b/src/runtime/server/DirectoryRoute.rs @@ -42,6 +42,10 @@ pub struct DirectoryRoute { stat_cache_path_bytes: Cell, } +bun_core::bool_enum!(pub StatCache { Disabled, Enabled }); +bun_core::bool_enum!(TrailingSlash); +bun_core::bool_enum!(IsIndex); + impl DirectoryRoute { #[inline] pub fn set_server(&self, server: Option) { @@ -60,7 +64,7 @@ impl DirectoryRoute { global: &JSGlobalObject, root: &[u8], url_prefix: &[u8], - enable_stat_cache: bool, + enable_stat_cache: StatCache, ) -> JsResult<*mut DirectoryRoute> { debug_assert!(url_prefix.last() == Some(&b'/')); debug_assert!(!strings::contains(url_prefix, b"//")); @@ -77,7 +81,7 @@ impl DirectoryRoute { } }; - let slots = if enable_stat_cache { + let slots = if enable_stat_cache == StatCache::Enabled { STAT_CACHE_SLOTS } else { 0 @@ -196,7 +200,7 @@ impl DirectoryRoute { write_any_status(resp, status_code); resp.write_mark(); - let ext: &[u8] = if is_index { + let ext: &[u8] = if is_index == IsIndex::Yes { b"html" } else { extension_for_mime(rel) @@ -280,7 +284,7 @@ impl DirectoryRoute { /// URL had a trailing slash, otherwise ask the caller to 301-redirect to /// the slash form so the new request re-enters routing (the served /// resource's canonical URL may be owned by a more-specific route). - fn open_subpath(&self, rel: &[u8], had_trailing_slash: bool) -> Option { + fn open_subpath(&self, rel: &[u8], had_trailing_slash: TrailingSlash) -> Option { let open_and_stat = |p: &[u8]| -> Option<(File, bun_sys::Stat)> { let f = self.open_beneath(p)?; let s = f.stat().ok()?; @@ -288,14 +292,17 @@ impl DirectoryRoute { }; if rel.is_empty() { let (f, s) = open_and_stat(b"index.html")?; - return bun_sys::S::ISREG(s.st_mode as bun_sys::Mode) - .then_some(Subpath::File(f, s, true)); + return bun_sys::S::ISREG(s.st_mode as bun_sys::Mode).then_some(Subpath::File( + f, + s, + IsIndex::Yes, + )); } let (file, stat) = open_and_stat(rel)?; let mode = stat.st_mode as bun_sys::Mode; if bun_sys::S::ISDIR(mode) { drop(file); - if !had_trailing_slash { + if had_trailing_slash == TrailingSlash::No { return Some(Subpath::RedirectSlash); } let mut buf = bun_paths::path_buffer_pool::get(); @@ -304,12 +311,16 @@ impl DirectoryRoute { &[rel, b"index.html"], ); let (f, s) = open_and_stat(joined)?; - return bun_sys::S::ISREG(s.st_mode as bun_sys::Mode) - .then_some(Subpath::File(f, s, true)); + return bun_sys::S::ISREG(s.st_mode as bun_sys::Mode).then_some(Subpath::File( + f, + s, + IsIndex::Yes, + )); } // Trailing slash on a regular file is a miss (nginx, npm `send`): // `/file/` would route past an exact `/file` handler in uWS. - (bun_sys::S::ISREG(mode) && !had_trailing_slash).then_some(Subpath::File(file, stat, false)) + (bun_sys::S::ISREG(mode) && had_trailing_slash == TrailingSlash::No) + .then_some(Subpath::File(file, stat, IsIndex::No)) } /// `openat2(RESOLVE_IN_ROOT|NO_MAGICLINKS)` on Linux, `openat` elsewhere. @@ -412,7 +423,7 @@ fn on_stream_error(ctx: *mut c_void, resp: AnyResponse, _err: bun_sys::Error) { // `Stat` is ~144 bytes; boxing it would add a heap alloc on the hot path. #[allow(clippy::large_enum_variant)] enum Subpath { - File(File, bun_sys::Stat, bool), + File(File, bun_sys::Stat, IsIndex), RedirectSlash, } @@ -502,7 +513,11 @@ fn is_url_path_literal(b: u8) -> bool { /// canonical relative path. `None` for any input that would make the served /// path differ from the routed path (see comment on the segment scan below). /// Writes into `out`; returns `(len, had_trailing_slash)`. -fn resolve_subpath(url: &[u8], url_prefix: &[u8], out: &mut [u8]) -> Option<(usize, bool)> { +fn resolve_subpath( + url: &[u8], + url_prefix: &[u8], + out: &mut [u8], +) -> Option<(usize, TrailingSlash)> { let (path, _query) = path_and_query(url); let after_prefix = if strings::starts_with(path, url_prefix) { &path[url_prefix.len()..] @@ -559,7 +574,7 @@ fn resolve_subpath(url: &[u8], url_prefix: &[u8], out: &mut [u8]) -> Option<(usi return None; } if decoded_len == 0 { - return Some((0, false)); + return Some((0, TrailingSlash::No)); } let had_trailing_slash = decoded[decoded_len - 1] == b'/'; let end = decoded_len - usize::from(had_trailing_slash); @@ -577,7 +592,7 @@ fn resolve_subpath(url: &[u8], url_prefix: &[u8], out: &mut [u8]) -> Option<(usi } i += 1; } - Some((end, had_trailing_slash)) + Some((end, TrailingSlash::from_bool(had_trailing_slash))) } /// `W/"-"` (nginx/send scheme). @@ -600,7 +615,8 @@ mod tests { fn resolve(url: &[u8], prefix: &[u8]) -> Option<(Vec, bool)> { let mut out = [0u8; 4096]; - resolve_subpath(url, prefix, &mut out).map(|(n, s)| (out[..n].to_vec(), s)) + resolve_subpath(url, prefix, &mut out) + .map(|(n, s)| (out[..n].to_vec(), s == TrailingSlash::Yes)) } fn ok(bytes: &[u8], slash: bool) -> Option<(Vec, bool)> { Some((bytes.to_vec(), slash)) diff --git a/src/runtime/server/FileResponseStream.rs b/src/runtime/server/FileResponseStream.rs index 0e81c6fefba8..f127181c2eab 100644 --- a/src/runtime/server/FileResponseStream.rs +++ b/src/runtime/server/FileResponseStream.rs @@ -14,7 +14,7 @@ use core::ffi::c_void; use bun_io::Closer; #[cfg(windows)] use bun_io::pipe_reader::WindowsFlags as ReaderFlags; -use bun_io::{BufferedReader, FileType, ReadState}; +use bun_io::{BufferedReader, FileType, IsPollable, ReadState}; #[cfg(unix)] use bun_io::{FilePollFlag, PosixFlags as ReaderFlags}; use bun_jsc::JsCell; @@ -209,9 +209,13 @@ impl FileResponseStream { // the parent pointer (`loop_`/`event_loop`), so no cell borrow spans them. let reader = this_ref.reader_mut(); let start_result = if opts.offset > 0 { - reader.start_file_offset(opts.fd, opts.pollable, opts.offset as usize) + reader.start_file_offset( + opts.fd, + IsPollable::from_bool(opts.pollable), + opts.offset as usize, + ) } else { - reader.start(opts.fd, opts.pollable) + reader.start(opts.fd, IsPollable::from_bool(opts.pollable)) }; if let Err(err) = start_result { this_ref.fail_with(err); diff --git a/src/runtime/server/HTMLBundle.rs b/src/runtime/server/HTMLBundle.rs index 73e22881373d..ae8ecba87239 100644 --- a/src/runtime/server/HTMLBundle.rs +++ b/src/runtime/server/HTMLBundle.rs @@ -7,7 +7,7 @@ use core::mem; use core::ptr::NonNull; use bun_ast::Loader; -use bun_ast::Log; +use bun_ast::{Log, Recycled}; use bun_bundler::bundle_v2::BundleV2Result; use bun_bundler::options::{self as bundler_options, LoaderExt as _}; use bun_core::strings; @@ -15,7 +15,7 @@ use bun_http::Headers; use bun_http_types::Method::Method; use bun_jsc::JsCell; use bun_ptr::{AsCtxPtr, IntrusiveRc, RefCount}; -use bun_uws::{AnyRequest, AnyResponse}; +use bun_uws::{AnyRequest, AnyResponse, CloseConnection}; use crate::api::js_bundle_completion_task::{ JSBundleCompletionTask, create_and_schedule_completion_task, @@ -222,6 +222,8 @@ impl State { } } +bun_core::bool_enum!(IsHead); + impl Route { pub(crate) fn memory_cost(&self) -> usize { let mut cost: usize = 0; @@ -250,14 +252,14 @@ impl Route { } pub(crate) fn on_request(this: *mut Self, req: AnyRequest, resp: AnyResponse) { - Self::on_any_request(this, req, resp, false); + Self::on_any_request(this, req, resp, IsHead::No); } pub(crate) fn on_head_request(this: *mut Self, req: AnyRequest, resp: AnyResponse) { - Self::on_any_request(this, req, resp, true); + Self::on_any_request(this, req, resp, IsHead::Yes); } - fn on_any_request(this: *mut Self, mut req: AnyRequest, resp: AnyResponse, is_head: bool) { + fn on_any_request(this: *mut Self, mut req: AnyRequest, resp: AnyResponse, is_head: IsHead) { // SAFETY: `this` is a live IntrusiveRc-managed allocation; `ScopedRef` // bumps the count and derefs on every exit path. let _keep_alive = unsafe { bun_ptr::ScopedRef::new(this) }; @@ -267,7 +269,7 @@ impl Route { let route = unsafe { &*this }; let Some(server) = route.server.get() else { - resp.end_without_body(true); + resp.end_without_body(CloseConnection::Yes); return; }; @@ -290,7 +292,7 @@ impl Route { } AnyRequest::H3(_) => { resp.write_status(b"503 Service Unavailable"); - resp.end(b"DevServer HMR is HTTP/1.1 only", true); + resp.end(b"DevServer HMR is HTTP/1.1 only", CloseConnection::Yes); } } return; @@ -332,7 +334,7 @@ impl Route { // create the PendingResponse, add it to the list let Some(method) = Method::which(req.method()) else { resp.write_status(b"405 Method Not Allowed"); - resp.end_without_body(true); + resp.end_without_body(CloseConnection::Yes); return; }; let pending = bun_core::heap::into_raw(Box::new(PendingResponse { @@ -365,7 +367,7 @@ impl Route { ); } // TODO: use the code from DevServer.rs to render the error - resp.end_without_body(true); + resp.end_without_body(CloseConnection::Yes); } State::Html(html) => { if bun_core::Environment::ENABLE_LOGS { @@ -375,7 +377,7 @@ impl Route { bstr::BStr::new(req.url()) ); } - if is_head { + if is_head == IsHead::Yes { // SAFETY: `*html` is a live intrusive-refcounted allocation. unsafe { StaticRoute::on_head_request(*html, req, resp) }; } else { @@ -572,7 +574,9 @@ impl Route { bun_output::scoped_log!(debug, "onComplete: err - {}", err); } let mut log = Log::init(); - completion_task.log.clone_to_with_recycled(&mut log, true); + completion_task + .log + .clone_to_with_recycled(&mut log, Recycled::Yes); if server.config().is_development() { // `Output.errorWriterBuffered()` → process-global writer; // `Log::print` accepts it via the `*mut io::Writer` @@ -789,11 +793,11 @@ impl Route { // socket; write Content-Length so the client has framing. resp.write_status(b"500 Build Failed"); resp.write_header_int(b"Content-Length", 0); - resp.end_without_body(true); + resp.end_without_body(CloseConnection::Yes); } _ => { resp.write_header_int(b"Content-Length", 0); - resp.end_without_body(true); + resp.end_without_body(CloseConnection::Yes); } } } @@ -829,7 +833,7 @@ impl Drop for PendingResponse { if self.is_response_pending.get() { self.resp.clear_aborted(); self.resp.clear_on_writable(); - self.resp.end_without_body(true); + self.resp.end_without_body(CloseConnection::Yes); } // SAFETY: `route` was a live IntrusiveRc-managed Route when stored; // matches the `ref()` taken when this PendingResponse was created. diff --git a/src/runtime/server/NodeHTTPResponse.rs b/src/runtime/server/NodeHTTPResponse.rs index bfcf97a09630..81e0f29dc407 100644 --- a/src/runtime/server/NodeHTTPResponse.rs +++ b/src/runtime/server/NodeHTTPResponse.rs @@ -10,6 +10,7 @@ use bun_core::scoped_log; use bun_core::{ZigString, ZigStringSlice}; use bun_http::Method as HttpMethod; use bun_jsc::JsCell; +use bun_jsc::virtual_machine::IsRejection; use bun_ptr::AsCtxPtr; use bun_uws as uws; use bun_uws_sys as uws_sys; @@ -1273,6 +1274,8 @@ pub enum AbortEvent { Timeout = 2, } +bun_core::bool_enum!(IsLast); + impl NodeHTTPResponse { fn handle_abort_or_timeout(&self, js_value: JSValue) { // defer { if event == abort, raw_response = None } @@ -1337,7 +1340,7 @@ impl NodeHTTPResponse { // (on_data_or_aborted runs the ondata callback). Clearing the slot // alone would un-root `pinned_value` while it is still read later. self.clear_pending_pinned_write(vm_get().global(), js_this); - self.on_data_or_aborted(b"", true, AbortEvent::Abort, js_this); + self.on_data_or_aborted(b"", IsLast::Yes, AbortEvent::Abort, js_this); } // `raw_response` is cleared before `deref()` because @@ -1525,7 +1528,9 @@ fn node_http_request_on_resolve(global_object: &JSGlobalObject, callframe: &Call raw_response.clear_on_writable(); raw_response.clear_timeout(); if raw_response.state().is_response_pending() { - raw_response.end_without_body(raw_response.state().is_http_connection_close()); + raw_response.end_without_body(uws::CloseConnection::from_bool( + raw_response.state().is_http_connection_close(), + )); } } this.on_request_complete(); @@ -1575,13 +1580,15 @@ fn node_http_request_on_reject(global_object: &JSGlobalObject, callframe: &CallF if !raw_response.state().is_http_status_called() { raw_response.write_status(b"500 Internal Server Error"); } - raw_response.end_stream(raw_response.state().is_http_connection_close()); + raw_response.end_stream(uws::CloseConnection::from_bool( + raw_response.state().is_http_connection_close(), + )); } this.on_request_complete(); } - let _ = bun_vm_mut(global_object).uncaught_exception(global_object, err, true); + let _ = bun_vm_mut(global_object).uncaught_exception(global_object, err, IsRejection::Yes); if had_promise { this.deref(); } @@ -1617,7 +1624,7 @@ impl NodeHTTPResponse { raw_response.clear_on_data(); raw_response.clear_on_writable(); raw_response.clear_timeout(); - raw_response.end_without_body(true); + raw_response.end_without_body(uws::CloseConnection::Yes); } self.on_request_complete(); Ok(JSValue::UNDEFINED) @@ -1661,7 +1668,11 @@ impl NodeHTTPResponse { Ok(b) => b, Err(err) => { let exc = global_this.take_exception(err); - let _ = bun_vm_mut(global_this).uncaught_exception(global_this, exc, false); + let _ = bun_vm_mut(global_this).uncaught_exception( + global_this, + exc, + IsRejection::No, + ); return JSValue::UNDEFINED; } }; @@ -1679,7 +1690,11 @@ impl NodeHTTPResponse { Ok(b) => b, Err(err) => { let exc = global_this.take_exception(err); - let _ = bun_vm_mut(global_this).uncaught_exception(global_this, exc, false); + let _ = bun_vm_mut(global_this).uncaught_exception( + global_this, + exc, + IsRejection::No, + ); return JSValue::UNDEFINED; } }; @@ -1687,7 +1702,14 @@ impl NodeHTTPResponse { bytes } - fn on_data_or_aborted(&self, chunk: &[u8], last: bool, event: AbortEvent, this_value: JSValue) { + fn on_data_or_aborted( + &self, + chunk: &[u8], + last: IsLast, + event: AbortEvent, + this_value: JSValue, + ) { + let last = last == IsLast::Yes; scoped_log!( NodeHTTPResponse, "onDataOrAborted({}, {})", @@ -1771,7 +1793,7 @@ impl NodeHTTPResponse { armed } }; - self.on_data_or_aborted(chunk, last, AbortEvent::None, this_value); + self.on_data_or_aborted(chunk, IsLast::from_bool(last), AbortEvent::None, this_value); } /// Release the pin + GC root + byte owner taken by a zero-copy write. @@ -1825,7 +1847,7 @@ impl NodeHTTPResponse { return false; } let remaining = p.remaining(); - let consumed = response.try_write_body(remaining, false); + let consumed = response.try_write_body(remaining, uws::FirstBodyWrite::No); if consumed < remaining.len() { self.pending_pinned_write.set(PendingPinnedWrite { remaining: ptr::from_ref(&remaining[consumed..]), @@ -2101,9 +2123,14 @@ impl NodeHTTPResponse { self.update_flags(|f| f.insert(Flags::ENDED)); let raw_response = self.raw_response.get().unwrap(); if !state.is_http_write_called() || !bytes.is_empty() { - raw_response.end(bytes, state.is_http_connection_close()); + raw_response.end( + bytes, + uws::CloseConnection::from_bool(state.is_http_connection_close()), + ); } else { - raw_response.end_stream(state.is_http_connection_close()); + raw_response.end_stream(uws::CloseConnection::from_bool( + state.is_http_connection_close(), + )); } self.on_request_complete(); @@ -2120,7 +2147,7 @@ impl NodeHTTPResponse { let is_buffer = matches!(string_or_buffer, crate::node::StringOrBuffer::Buffer(_)); scoped_log!(NodeHTTPResponse, "tryWriteBody({} bytes)", bytes_len); - let consumed = raw_response.try_write_body(bytes, true); + let consumed = raw_response.try_write_body(bytes, uws::FirstBodyWrite::Yes); if consumed >= bytes_len { raw_response.clear_on_writable(); js::on_writable_set_cached(js_this, global_object, JSValue::UNDEFINED); @@ -2424,7 +2451,7 @@ impl NodeHTTPResponse { if !flags.contains(Flags::SOCKET_CLOSED) && !flags.contains(Flags::UPGRADED) { if let Some(raw_response) = self.raw_response.get() { // Don't flush immediately; queue a microtask to uncork the socket. - raw_response.flush_headers(false); + raw_response.flush_headers(uws::FlushImmediately::No); if raw_response.is_corked() { self.register_auto_flush(); } diff --git a/src/runtime/server/RequestContext.rs b/src/runtime/server/RequestContext.rs index 0e337adb45d5..140cb331716c 100644 --- a/src/runtime/server/RequestContext.rs +++ b/src/runtime/server/RequestContext.rs @@ -5,6 +5,7 @@ use core::ptr::NonNull; use bun_sys::FdExt as _; use bun_core::String as BunString; +use bun_dotenv::HttpScheme; use bun_http_types::Method::Method; use bun_jsc::JsCell; use bun_uws::{self as uws, WebSocketUpgradeContext}; @@ -1135,7 +1136,7 @@ where } } - pub(crate) fn end(&self, data: &[u8], close_connection: bool) { + pub(crate) fn end(&self, data: &[u8], close_connection: uws::CloseConnection) { ctx_log!("end"); if let Some(resp) = self.resp.get() { self.detach_response(); @@ -1149,7 +1150,7 @@ where } } - pub(crate) fn end_stream(&self, close_connection: bool) { + pub(crate) fn end_stream(&self, close_connection: uws::CloseConnection) { ctx_log!("endStream"); if let Some(resp) = self.resp.get() { self.detach_response(); @@ -1197,7 +1198,7 @@ where } } - pub(crate) fn end_without_body(&self, close_connection: bool) { + pub(crate) fn end_without_body(&self, close_connection: uws::CloseConnection) { ctx_log!("endWithoutBody"); if let Some(resp) = self.resp.get() { self.detach_response(); @@ -2476,7 +2477,7 @@ where .as_mut() .transpiler .env_mut() - .get_http_proxy(true, None, None) + .get_http_proxy(HttpScheme::Http, None, None) .map(|proxy| proxy.href); let _ = S3::client::stat( @@ -3436,12 +3437,12 @@ where } #[inline] - pub(crate) fn should_close_connection(&self) -> bool { + pub(crate) fn should_close_connection(&self) -> uws::CloseConnection { if let Some(resp) = self.resp.get() { // SAFETY: FFI handle return resp.should_close_connection(); } - false + uws::CloseConnection::No } fn finish_running_error_handler(&self, value: JSValue, status: u16) { @@ -3957,7 +3958,7 @@ where resp.write_status(b"413 Payload Too Large"); } } - this.end_without_body(!HTTP3); + this.end_without_body(uws::CloseConnection::from_bool(!HTTP3)); return; } @@ -4063,7 +4064,7 @@ where resp.write_status(b"413 Payload Too Large"); } } - this.end_without_body(!HTTP3); + this.end_without_body(uws::CloseConnection::from_bool(!HTTP3)); return; } @@ -4647,7 +4648,10 @@ fn get_content_type(headers: Option<&mut FetchHeaders>, blob: &AnyBlob) -> (Mime let content_slice = content.to_slice(); // Dupe only when the latin1/utf16 slice was heap-converted. - let dupe = matches!(content_slice, bun_core::ZigStringSlice::Owned(_)); + let dupe = bun_http_types::MimeType::Dupe::from_bool(matches!( + content_slice, + bun_core::ZigStringSlice::Owned(_) + )); let mt = MimeType::init( content_slice.slice(), dupe, diff --git a/src/runtime/server/ServerConfig.rs b/src/runtime/server/ServerConfig.rs index 76cebaaa4fc0..4f91d94881c9 100644 --- a/src/runtime/server/ServerConfig.rs +++ b/src/runtime/server/ServerConfig.rs @@ -324,6 +324,8 @@ impl ServerConfig { // NOTE: free `extern "C"` fns are monomorphized per `` and registered // via the raw `c::uws_method_handler` overload. +bun_core::bool_enum!(pub(crate) PathHasUserHeadRoute); + /// # Safety /// `entry` must be a live route pointer that outlives `app` — it is registered /// as the uWS userdata and dereferenced from request callbacks for the lifetime @@ -338,7 +340,7 @@ pub(crate) fn apply_static_route( entry: *mut T, path: &[u8], method: http_method::Optional, - path_has_user_head_route: bool, + path_has_user_head_route: PathHasUserHeadRoute, ) where T: StaticRouteLike, { @@ -393,7 +395,7 @@ pub(crate) fn apply_static_route( // Only answer HEAD from an entry that serves GET (HEAD must mirror GET, // RFC 9110 section 9.3.2) or HEAD itself, and never displace an explicit HEAD // handler route: uWS keeps the last registration for the same method and path. - if !path_has_user_head_route && serves_head(&method) { + if path_has_user_head_route == PathHasUserHeadRoute::No && serves_head(&method) { app.head(path, Some(head::), user_data); } match method { @@ -433,7 +435,7 @@ pub(crate) fn apply_static_route_h3( entry: *mut T, path: &[u8], method: http_method::Optional, - path_has_user_head_route: bool, + path_has_user_head_route: PathHasUserHeadRoute, ) where T: StaticRouteLike, { @@ -469,7 +471,7 @@ pub(crate) fn apply_static_route_h3( }; } - if !path_has_user_head_route && serves_head(&method) { + if path_has_user_head_route == PathHasUserHeadRoute::No && serves_head(&method) { app.head(path, entry, head::); } match method { diff --git a/src/runtime/server/ServerWebSocket.rs b/src/runtime/server/ServerWebSocket.rs index f32b5b897b45..2f18e6185b6a 100644 --- a/src/runtime/server/ServerWebSocket.rs +++ b/src/runtime/server/ServerWebSocket.rs @@ -6,7 +6,7 @@ use core::ptr::NonNull; use bun_jsc::JsCell; use bun_uws::{self as uws, AnyWebSocket, WebSocketBehavior}; use bun_uws_sys::web_socket::{WebSocketHandler, WebSocketUpgradeServer, Wrap}; -use bun_uws_sys::{Opcode, SendStatus}; +use bun_uws_sys::{Compress, Fin, Opcode, SendStatus}; use crate::server::WebSocketServerHandler; use crate::server::jsc::{ @@ -286,7 +286,7 @@ impl ServerWebSocket { fn_name: &'static str, compress_value: JSValue, args_len: usize, - ) -> JsResult { + ) -> JsResult { if !compress_value.is_boolean() && !compress_value.is_undefined() && !compress_value.is_empty() @@ -295,7 +295,9 @@ impl ServerWebSocket { global_this.throw(format_args!("{fn_name} expects compress to be a boolean")) ); } - Ok(args_len > 1 && compress_value.to_boolean()) + Ok(Compress::from_bool( + args_len > 1 && compress_value.to_boolean(), + )) } /// Route a publish through either the per-socket uWS handle (when @@ -308,7 +310,7 @@ impl ServerWebSocket { topic: &[u8], buffer: &[u8], opcode: Opcode, - compress: bool, + compress: Compress, ) -> JSValue { let status = if !ctx.publish_to_self && !self.is_closed() { self.websocket().publish(topic, buffer, opcode, compress) @@ -1080,7 +1082,8 @@ impl ServerWebSocket { if let Some(buffer) = message_value.as_array_buffer(global_this) { let slice = buffer.slice(); return Ok(send_status_to_js( - self.websocket().send(slice, Opcode::Binary, compress, true), + self.websocket() + .send(slice, Opcode::Binary, compress, Fin::Yes), slice.len(), "send", "bytes", @@ -1089,7 +1092,8 @@ impl ServerWebSocket { if let Some(slice) = blob_payload(global_this, "send", message_value)? { let ret = send_status_to_js( - self.websocket().send(slice, Opcode::Binary, compress, true), + self.websocket() + .send(slice, Opcode::Binary, compress, Fin::Yes), slice.len(), "send", "bytes", @@ -1105,7 +1109,8 @@ impl ServerWebSocket { let buffer = slice.slice(); let ret = send_status_to_js( - self.websocket().send(buffer, Opcode::Text, compress, true), + self.websocket() + .send(buffer, Opcode::Text, compress, Fin::Yes), buffer.len(), "send", "bytes string", @@ -1150,7 +1155,8 @@ impl ServerWebSocket { let buffer = slice.slice(); let ret = send_status_to_js( - self.websocket().send(buffer, Opcode::Text, compress, true), + self.websocket() + .send(buffer, Opcode::Text, compress, Fin::Yes), buffer.len(), "sendText", "bytes string", @@ -1187,7 +1193,8 @@ impl ServerWebSocket { if let Some(buffer) = message_value.as_array_buffer(global_this) { let slice = buffer.slice(); return Ok(send_status_to_js( - self.websocket().send(slice, Opcode::Binary, compress, true), + self.websocket() + .send(slice, Opcode::Binary, compress, Fin::Yes), slice.len(), "sendBinary", "bytes", @@ -1196,7 +1203,8 @@ impl ServerWebSocket { if let Some(slice) = blob_payload(global_this, "sendBinary", message_value)? { let ret = send_status_to_js( - self.websocket().send(slice, Opcode::Binary, compress, true), + self.websocket() + .send(slice, Opcode::Binary, compress, Fin::Yes), slice.len(), "sendBinary", "bytes", @@ -1247,7 +1255,8 @@ impl ServerWebSocket { return Err(throw_control_frame_too_large(global_this, buffer.len())); } return Ok(send_status_to_js( - self.websocket().send(buffer, opcode, false, true), + self.websocket() + .send(buffer, opcode, Compress::No, Fin::Yes), buffer.len(), name, "bytes", @@ -1257,7 +1266,8 @@ impl ServerWebSocket { return Err(throw_control_frame_too_large(global_this, buffer.len())); } let ret = send_status_to_js( - self.websocket().send(buffer, opcode, false, true), + self.websocket() + .send(buffer, opcode, Compress::No, Fin::Yes), buffer.len(), name, "bytes", @@ -1272,7 +1282,8 @@ impl ServerWebSocket { return Err(throw_control_frame_too_large(global_this, buffer.len())); } return Ok(send_status_to_js( - self.websocket().send(buffer, opcode, false, true), + self.websocket() + .send(buffer, opcode, Compress::No, Fin::Yes), buffer.len(), name, "bytes", @@ -1287,7 +1298,7 @@ impl ServerWebSocket { } Ok(send_status_to_js( - self.websocket().send(&[], opcode, false, true), + self.websocket().send(&[], opcode, Compress::No, Fin::Yes), 0, name, "bytes", diff --git a/src/runtime/server/WebSocketServerContext.rs b/src/runtime/server/WebSocketServerContext.rs index ba718f89f2b8..a04698a93a35 100644 --- a/src/runtime/server/WebSocketServerContext.rs +++ b/src/runtime/server/WebSocketServerContext.rs @@ -1,6 +1,7 @@ use core::ffi::c_void; use crate::server::jsc::{JSGlobalObject, JSValue, JsResult, VirtualMachine}; +use bun_jsc::virtual_machine::IsRejection; use bun_uws as uws; pub struct WebSocketServerContext { @@ -102,10 +103,11 @@ impl Handler { return Ok(()); } - let _ = - VirtualMachine::get() - .as_mut() - .uncaught_exception(global_object, error_value, false); + let _ = VirtualMachine::get().as_mut().uncaught_exception( + global_object, + error_value, + IsRejection::No, + ); Ok(()) } diff --git a/src/runtime/server/mod.rs b/src/runtime/server/mod.rs index 40f943ea7956..c4c4b9f6addf 100644 --- a/src/runtime/server/mod.rs +++ b/src/runtime/server/mod.rs @@ -32,6 +32,7 @@ use bun_uws as uws; use bun_uws_sys as uws_sys; use bun_uws_sys::app::c as uws_app_c; +use bun_jsc::virtual_machine::IsRejection; use bun_jsc::{JSGlobalObject, JSValue, JsResult}; // ─── httplog ───────────────────────────────────────────────────────────────── @@ -232,6 +233,8 @@ bitflags::bitflags! { /// not a correctness invariant. const N_HTTP_METHODS: usize = 36; +bun_core::bool_enum!(pub StopMode { Graceful, Abrupt }); + pub struct NewServer { pub(crate) app: Option<*mut uws_sys::NewApp>, pub(crate) listener: Option<*mut uws_sys::app::ListenSocket>, @@ -750,7 +753,7 @@ impl NewServer { // Abort the request very early. if len > server.config.max_request_body_size { resp_ref.write_status(b"413 Request Entity Too Large"); - resp_ref.end_without_body(true); + resp_ref.end_without_body(uws::CloseConnection::Yes); return None; } @@ -1336,7 +1339,7 @@ impl NewServer { // answer it natively as above. if !result.is_empty() && !result.is_termination_exception() { // SAFETY: `vm` is the process-static VirtualMachine. - let _ = unsafe { (*vm).uncaught_exception(global, result, false) }; + let _ = unsafe { (*vm).uncaught_exception(global, result, IsRejection::No) }; } server_body::respond_stopped_503(resp); // SAFETY: same `this`; balances `on_pending_request` above. @@ -1443,7 +1446,7 @@ impl NewServer { (*vm).uncaught_exception( global, *err, - matches!(http_result, HttpResult::Rejection(_)), + IsRejection::from_bool(matches!(http_result, HttpResult::Rejection(_))), ) }; @@ -1458,9 +1461,9 @@ impl NewServer { { if raw.state().is_http_status_called() { raw.write_status(b"500 Internal Server Error"); - raw.end_without_body(true); + raw.end_without_body(uws::CloseConnection::Yes); } else { - raw.end_stream(true); + raw.end_stream(uws::CloseConnection::Yes); } } } @@ -1664,8 +1667,9 @@ impl NewServer { self.poll_ref.unref(self.vm.loop_ctx()); } - pub(crate) fn stop_listening(&mut self, abrupt: bool) { + pub(crate) fn stop_listening(&mut self, abrupt: StopMode) { // httplog!("stopListening", .{}); + let abrupt = abrupt == StopMode::Abrupt; if let Some(handles) = crate::jsc_hooks::active_handles() { handles.swap_remove(&crate::jsc_hooks::ActiveHandle::Server(AnyServer::from( @@ -1766,7 +1770,8 @@ impl NewServer { if let Some(app) = self.app { self.deinit_running.set(true); // S012: `NewApp` is a ZST opaque — safe `*mut → &mut` deref. - let _closed = bun_opaque::opaque_deref_mut(app).close_idle_connections(true); + let _closed = bun_opaque::opaque_deref_mut(app) + .close_idle_connections(uws::CloseWhenIdle::Yes); self.deinit_running.set(false); } } @@ -1795,7 +1800,7 @@ impl NewServer { } } - pub(crate) fn stop(&mut self, abrupt: bool) { + pub(crate) fn stop(&mut self, abrupt: StopMode) { if self.config.allow_hot && !self.config.id.is_empty() { // `hot_map()` is reached via the thread-local VM singleton (raw ptr // deref) and does not borrow `self`, so it cannot overlap with the @@ -2466,15 +2471,15 @@ impl NewServer { // path: uWS keeps the last registration for a method and path, and // static routes register after user routes. let path_has_user_head_route = - self.user_routes - .iter() - .any(|route| match &route.route.method { + server_config::PathHasUserHeadRoute::from_bool(self.user_routes.iter().any( + |route| match &route.route.method { server_config::RouteMethod::Specific(method) => { *method == http_method::Method::HEAD && route.route.path.as_bytes() == &*entry.path } server_config::RouteMethod::Any => false, - }); + }, + )); // Each `p`/`r` is the live `RefPtr<_>` stored in `entry.route`; // `app`/`h3_app` are the live uWS app handles owned by `self`. @@ -2850,7 +2855,11 @@ impl NewServer { let server_name = unsafe { bun_core::ffi::cstr(name_ptr) }; // S012: `NewApp` is a ZST opaque — safe `*mut → &mut` deref. if bun_opaque::opaque_deref_mut(app) - .add_server_name_with_options(server_name, &ssl_options, false) + .add_server_name_with_options( + server_name, + &ssl_options, + uws::ApplyClientCertPolicy::No, + ) .is_err() { if !global.has_exception() && !throw_ssl_error_if_necessary(global) { @@ -2933,7 +2942,11 @@ impl NewServer { } // S012: `NewApp` is a ZST opaque — safe `*mut → &mut` deref. if bun_opaque::opaque_deref_mut(app) - .add_server_name_with_options(sni_name, &sni_opts, true) + .add_server_name_with_options( + sni_name, + &sni_opts, + uws::ApplyClientCertPolicy::Yes, + ) .is_err() { if !global.has_exception() && !throw_ssl_error_if_necessary(global) { @@ -3336,7 +3349,7 @@ mod trampoline { // S008: `Response` is a ZST opaque — safe `*mut → &mut` deref. let resp = bun_opaque::opaque_deref_mut(res.cast::>()); resp.write_status(b"404 Not Found"); - resp.end(b"", false); + resp.end(b"", uws::CloseConnection::No); } pub(super) extern "C" fn on_request( @@ -3932,7 +3945,7 @@ impl AnyServer { any_server_dispatch_mut!(self, |s| s.on_static_request_complete()) } - pub(crate) fn stop(&mut self, abrupt: bool) { + pub(crate) fn stop(&mut self, abrupt: StopMode) { any_server_dispatch_mut!(self, |s| s.stop(abrupt)) } @@ -3955,7 +3968,7 @@ impl AnyServer { topic: &[u8], message: &[u8], opcode: uws::Opcode, - compress: bool, + compress: uws::Compress, ) -> uws::SendStatus { any_server_dispatch!(self, |s| match s.app { // S012: `NewApp` is a ZST opaque — safe `*mut → &mut` via diff --git a/src/runtime/server/server_body.rs b/src/runtime/server/server_body.rs index d5f704f50fca..816e11e5a07d 100644 --- a/src/runtime/server/server_body.rs +++ b/src/runtime/server/server_body.rs @@ -10,6 +10,7 @@ use crate::bake::framework_router as FrameworkRouter; use crate::bake::{self as bake}; use crate::node::types::PathLikeExt as _; use crate::webcore::BlobExt; +use crate::webcore::blob::CheckS3; use crate::webcore::body::Value as BodyValue; use crate::webcore::fetch as Fetch; use crate::webcore::response::HeadersRef; @@ -327,7 +328,7 @@ impl ReqLike for uws_sys::h3::Request { pub(super) trait RespLike { const IS_H3: bool; fn write_status(&mut self, status: &[u8]); - fn end_without_body(&mut self, close_connection: bool); + fn end_without_body(&mut self, close_connection: uws::CloseConnection); fn timeout(&mut self, seconds: u8); fn on_timeout_warn(&mut self, ud: *mut c_void); fn to_any_response(&mut self) -> uws::AnyResponse; @@ -339,7 +340,7 @@ impl RespLike for uws_sys::NewAppResponse { uws_sys::NewAppResponse::::write_status(self, s) } #[inline] - fn end_without_body(&mut self, c: bool) { + fn end_without_body(&mut self, c: uws::CloseConnection) { uws_sys::NewAppResponse::::end_without_body(self, c) } #[inline] @@ -379,7 +380,7 @@ impl RespLike for uws_sys::h3::Response { uws_sys::h3::Response::write_status(self, s) } #[inline] - fn end_without_body(&mut self, c: bool) { + fn end_without_body(&mut self, c: uws::CloseConnection) { uws_sys::h3::Response::end_without_body(self, c) } #[inline] @@ -412,7 +413,7 @@ impl RespLike for uws_sys::h3::Response { #[inline] pub(super) fn respond_stopped_503(resp: &mut R) { resp.write_status(b"503 Service Unavailable"); - resp.end_without_body(!R::IS_H3); + resp.end_without_body(uws::CloseConnection::from_bool(!R::IS_H3)); } /// RFC 6455 §4.1: |Sec-WebSocket-Key| is the base64 encoding of a 16-byte @@ -547,7 +548,7 @@ impl AnyRoute { let mut path = Node::PathOrFileDescriptor::Path(Node::PathLike::from_bun_string( init_ctx.global, &mut path_string, - false, + crate::node::Flavor::Sync, )?); // NOTE: `from_bun_string` clones // the bytes (or bumps the WTF ref) into the PathLike payload, so we can @@ -657,7 +658,7 @@ impl AnyRoute { path: &mut Node::PathOrFileDescriptor, ) -> JsResult { // The file/static route doesn't ref it. - let blob = ::find_or_create_file_from_path(path, global, false); + let blob = ::find_or_create_file_from_path(path, global, CheckS3::No); if blob.needs_to_read_file() { // Throw a more helpful error upfront if the file does not exist. @@ -802,7 +803,7 @@ impl AnyRoute { global, relative_root, url_prefix, - stat_cache, + super::directory_route::StatCache::from_bool(stat_cache), )?; return Ok(Some(AnyRoute::Directory(NonNull::new(route).expect( "DirectoryRoute::create returns a fresh heap allocation", @@ -1303,7 +1304,7 @@ fn on_timeout_for_idle_warn() { // methods on the same type — there is no separate Phase-A struct. pub(super) use super::{ CreateJsRequest, DebugHTTPSServer, DebugHTTPServer, HTTPSServer, HTTPServer, NewServer, - ServerFlags, UserRoute, + ServerFlags, StopMode, UserRoute, }; /// Generic over the @@ -1678,7 +1679,7 @@ where // compress defaults to true when the argument is omitted. let compress_js = compress_value.unwrap_or(JSValue::TRUE); - let compress = compress_js.to_boolean(); + let compress = uws::Compress::from_bool(compress_js.to_boolean()); if let Some(buffer) = message_value.as_array_buffer(global) { let status = AnyWebSocket::publish_with_options( @@ -2039,9 +2040,13 @@ where // A request that does not name "websocket" in its |Upgrade| token list, // or whose |Sec-WebSocket-Key| is not base64 of 16 bytes, is not a // WebSocket handshake; fall through so the caller's fetch() can respond. - if !strings::split(upgrade_header.slice(), b",") - .any(|t| strings::eql_case_insensitive_ascii(t.trim_ascii(), b"websocket", true)) - { + if !strings::split(upgrade_header.slice(), b",").any(|t| { + strings::eql_case_insensitive_ascii( + t.trim_ascii(), + b"websocket", + strings::CheckLen::Yes, + ) + }) { return Ok(JSValue::FALSE); } if !is_valid_sec_websocket_key(sec_websocket_key_str.slice()) { @@ -2056,7 +2061,7 @@ where // SAFETY: upgrader_ptr is live (ref_() above) let upgrader = unsafe { &*upgrader_ptr }; upgrader.flags.set_has_written_status(true); - upgrader.end_without_body(true); + upgrader.end_without_body(uws::CloseConnection::Yes); return Ok(JSValue::FALSE); } if sec_websocket_protocol.len > 0 { @@ -2611,7 +2616,9 @@ where // it. One-shot sweep (Node semantics): busy connections are spared // and are NOT marked to close later. self.deinit_running.set(true); - let closed = self.app_mut().close_idle_connections(false); + let closed = self + .app_mut() + .close_idle_connections(uws::CloseWhenIdle::No); self.deinit_running.set(false); self.deinit_if_we_can(); Ok(JSValue::js_number(closed as f64)) @@ -2629,7 +2636,7 @@ where && !self.flags.contains(ServerFlags::TERMINATED) && !self.deinit_running.get()) { - self.stop(abrupt); + self.stop(StopMode::from_bool(abrupt)); } rc @@ -2639,7 +2646,7 @@ where if self.has_listener() || (!self.flags.contains(ServerFlags::TERMINATED) && !self.deinit_running.get()) { - self.stop(true); + self.stop(StopMode::Abrupt); } JSValue::UNDEFINED } @@ -2876,7 +2883,7 @@ where unreachable!(); } resp.write_status(b"404 Not Found"); - resp.end(b"", false); + resp.end(b"", uws::CloseConnection::No); } #[bun_jsc::host_fn(method)] @@ -2938,7 +2945,7 @@ where resp.write_header(b"Cache-Control", b"public, max-age=3600"); resp.write_header_int(b"Age", 0); let buffer = writer.ctx.written(); - resp.end(buffer, false); + resp.end(buffer, uws::CloseConnection::No); self.pending_requests.set(self.pending_requests.get() - 1); } @@ -3115,7 +3122,7 @@ where if Ctx::IS_H3 { if ReqLike::header(req, b"transfer-encoding").is_some() { RespLike::write_status(resp, b"400 Bad Request"); - RespLike::end_without_body(resp, false); + RespLike::end_without_body(resp, uws::CloseConnection::No); return None; } } @@ -3138,7 +3145,7 @@ where // would CONNECTION_CLOSE every sibling stream on the conn. if len > server.config.max_request_body_size { RespLike::write_status(resp, b"413 Request Entity Too Large"); - RespLike::end_without_body(resp, !Ctx::IS_H3); + RespLike::end_without_body(resp, uws::CloseConnection::from_bool(!Ctx::IS_H3)); return None; } @@ -3467,7 +3474,7 @@ where // require fetch method to be set otherwise we dont know what route to call // this should be the fallback in case no route is provided to upgrade resp.write_status(b"403 Forbidden"); - resp.end_without_body(true); + resp.end_without_body(uws::CloseConnection::Yes); return; } this.on_pending_request(); diff --git a/src/runtime/shell/Builtin.rs b/src/runtime/shell/Builtin.rs index 6ce494deb4ed..8b02edf8f4d0 100644 --- a/src/runtime/shell/Builtin.rs +++ b/src/runtime/shell/Builtin.rs @@ -610,7 +610,7 @@ impl Builtin { perm, &mut pollable, &mut is_socket, - false, + bun_io::ForceSync::No, &mut is_nonblocking, (), |_| {}, diff --git a/src/runtime/shell/IOReader.rs b/src/runtime/shell/IOReader.rs index 0484e6ed5c2b..fdba3c19904b 100644 --- a/src/runtime/shell/IOReader.rs +++ b/src/runtime/shell/IOReader.rs @@ -216,7 +216,7 @@ impl IOReader { }; if need_start { let fd = self.state().fd; - if let Err(e) = r.start(fd, true) { + if let Err(e) = r.start(fd, bun_io::IsPollable::Yes) { self.on_reader_error(&e); } } @@ -399,7 +399,8 @@ impl Drop for IOReader { // return the FilePoll to its pool. Do it explicitly (without // closing the fd — we own that and close it ourselves below). if matches!(r.handle, bun_io::pipes::PollOrFd::Poll(_)) { - r.handle.close_impl(None, None::, false); + r.handle + .close_impl(None, None::, bun_io::CloseFd::No); } let _ = sys::close(s.fd); } diff --git a/src/runtime/shell/IOWriter.rs b/src/runtime/shell/IOWriter.rs index 4f8d012cab07..f91b75d01fb1 100644 --- a/src/runtime/shell/IOWriter.rs +++ b/src/runtime/shell/IOWriter.rs @@ -167,7 +167,7 @@ pub(crate) type Poll = WriterImpl; /// can drop the last external ref without freeing `self` while PipeWriter is /// still on the stack. #[cfg(not(windows))] -pub(crate) fn on_poll(writer: &mut Poll, size_hint: isize, hup: bool) { +pub(crate) fn on_poll(writer: &mut Poll, size_hint: isize, hup: bun_io::ReceivedHup) { use bun_io::pipe_writer::PosixPipeWriter; let parent = writer.parent.expect("IOWriter writer.parent unset"); // `parent` is the backref stashed via `set_parent` in `IOWriter::init`; @@ -352,7 +352,10 @@ impl IOWriter { fn __start(&self) -> sys::Result<()> { let s = self.state(); crate::shell_log!("IOWriter(fd={}) __start()", s.fd); - if let Err(e) = s.writer.start(s.fd, s.flags.pollable) { + if let Err(e) = s + .writer + .start(s.fd, bun_io::IsPollable::from_bool(s.flags.pollable)) + { #[cfg(not(windows))] { // We get this if we pass in a file descriptor that is not @@ -369,9 +372,11 @@ impl IOWriter { s.flags.nonblock = false; s.flags.is_socket = false; if matches!(s.writer.handle, bun_io::pipes::PollOrFd::Poll(_)) { - s.writer - .handle - .close_impl(None, None::, false); + s.writer.handle.close_impl( + None, + None::, + bun_io::CloseFd::No, + ); } s.writer.handle = bun_io::pipes::PollOrFd::Closed; return self.__start(); @@ -385,9 +390,11 @@ impl IOWriter { s.flags.nonblock = false; s.flags.is_socket = false; if matches!(s.writer.handle, bun_io::pipes::PollOrFd::Poll(_)) { - s.writer - .handle - .close_impl(None, None::, false); + s.writer.handle.close_impl( + None, + None::, + bun_io::CloseFd::No, + ); } s.writer.handle = bun_io::pipes::PollOrFd::Closed; return self.__start(); @@ -516,7 +523,10 @@ impl IOWriter { return WriteOutcome::Suspended; } } - if let Err(e) = s.writer.start(s.fd, s.flags.pollable) { + if let Err(e) = s + .writer + .start(s.fd, bun_io::IsPollable::from_bool(s.flags.pollable)) + { return WriteOutcome::Failed(e); } WriteOutcome::Suspended @@ -1196,7 +1206,7 @@ impl Drop for IOWriter { if matches!(s.writer.handle, bun_io::pipes::PollOrFd::Poll(_)) { s.writer .handle - .close_impl(None, None::, false); + .close_impl(None, None::, bun_io::CloseFd::No); } } #[cfg(windows)] diff --git a/src/runtime/shell/builtin/cp.rs b/src/runtime/shell/builtin/cp.rs index 8a7a39d19a51..bb59eda572cf 100644 --- a/src/runtime/shell/builtin/cp.rs +++ b/src/runtime/shell/builtin/cp.rs @@ -712,11 +712,11 @@ impl ShellCpTask { let args = crate::node::fs::args::Cp { src: bun_jsc::node::PathLike::String(bun_ptr::cow_slice::CowSlice::init_unchecked( self.src_absolute.as_deref().unwrap(), - false, + bun_ptr::cow_slice::Ownership::Borrowed, )), dest: bun_jsc::node::PathLike::String(bun_ptr::cow_slice::CowSlice::init_unchecked( self.tgt_absolute.as_deref().unwrap(), - false, + bun_ptr::cow_slice::Ownership::Borrowed, )), flags: crate::node::fs::args::CpFlags { recursive: self.opts.recursive, diff --git a/src/runtime/shell/builtin/echo.rs b/src/runtime/shell/builtin/echo.rs index 625863a9b7af..c8e257ae1006 100644 --- a/src/runtime/shell/builtin/echo.rs +++ b/src/runtime/shell/builtin/echo.rs @@ -64,7 +64,7 @@ impl Echo { let is_last = i == args_len - 1; if escape_sequences { - stop_output = append_with_escapes(&mut out, thearg); + stop_output = append_with_escapes(&mut out, thearg).is_break(); } else if is_last { if thearg.last() == Some(&b'\n') { has_leading_newline = true; @@ -119,8 +119,8 @@ impl Echo { } /// Appends `input` to `output`, interpreting backslash escape sequences. -/// Returns true if a `\c` escape was encountered (meaning stop all output). -fn append_with_escapes(output: &mut Vec, input: &[u8]) -> bool { +/// Returns `Break` if a `\c` escape was encountered (meaning stop all output). +fn append_with_escapes(output: &mut Vec, input: &[u8]) -> core::ops::ControlFlow<()> { let mut i = 0usize; while i < input.len() { if input[i] == b'\\' && i + 1 < input.len() { @@ -139,7 +139,7 @@ fn append_with_escapes(output: &mut Vec, input: &[u8]) -> bool { } b'c' => { // \c: produce no further output - return true; + return core::ops::ControlFlow::Break(()); } b'e' | b'E' => { output.push(0x1b); @@ -199,5 +199,5 @@ fn append_with_escapes(output: &mut Vec, input: &[u8]) -> bool { i += 1; } } - false + core::ops::ControlFlow::Continue(()) } diff --git a/src/runtime/shell/builtin/mkdir.rs b/src/runtime/shell/builtin/mkdir.rs index a4695d61c6b2..d8e12b182c35 100644 --- a/src/runtime/shell/builtin/mkdir.rs +++ b/src/runtime/shell/builtin/mkdir.rs @@ -312,7 +312,7 @@ impl ShellMkdirTask { let args = fs_args::Mkdir { path: PathLike::String(bun_ptr::cow_slice::CowSlice::init_unchecked( filepath.as_bytes(), - false, + bun_ptr::cow_slice::Ownership::Borrowed, )), recursive: this.opts.parents, mode: fs_args::Mkdir::DEFAULT_MODE, diff --git a/src/runtime/shell/interpreter.rs b/src/runtime/shell/interpreter.rs index f0e6da780e48..07e6166e1cdc 100644 --- a/src/runtime/shell/interpreter.rs +++ b/src/runtime/shell/interpreter.rs @@ -403,6 +403,8 @@ impl ShellArgs { /// Only used by the construction path (`Interpreter::init`). pub(crate) type ShellResult = Result; +bun_core::bool_enum!(RunFrom { File, Source }); + impl Interpreter { /// Lex `src` (ASCII or Unicode), build a `Parser`, and return the root /// `ast::Script`. Tokens and AST nodes are bump-allocated into `arena`. @@ -597,7 +599,7 @@ impl Interpreter { // On failure, deref root_io + deinit root_shell + free. if let Err(e) = interpreter .root_shell - .with_mut(|rs| rs.change_cwd_impl(c, true)) + .with_mut(|rs| rs.change_cwd_impl(c, InInit::Yes)) { // `deinit_from_exec` performs the full teardown (drops // `root_io` Arcs, frees env maps, closes `cwd_fd`, consumes @@ -620,7 +622,8 @@ impl Interpreter { self.root_io.set(IO::default()); // Free buffered IO, env // maps, cwd fd; do NOT free the struct itself (it's embedded). - self.root_shell.with_mut(|rs| rs.deinit_embedded(true)); + self.root_shell + .with_mut(|rs| rs.deinit_embedded(FreeBufferedIo::Yes)); // `vm_args_utf8` slices Drop themselves (`ZigStringSlice` has a Drop // impl that derefs the WTF backing); the Vec frees on box drop. } @@ -637,7 +640,14 @@ impl Interpreter { path: &[u8], src: &[u8], ) -> crate::Result { - Self::init_and_run_impl(ctx, mini, bun_paths::basename(path), src, None, false) + Self::init_and_run_impl( + ctx, + mini, + bun_paths::basename(path), + src, + None, + RunFrom::File, + ) } /// Standalone-shell entrypoint for `bun run