#![allow(non_snake_case)]
use core::fmt;
use core::ops::{Deref, DerefMut};
use core::ptr::NonNull;
pub use bun_collections::VecExt as _VecExtReexport;
use bun_collections::{ArrayHashMap, AutoContext, MultiArrayList, StringHashMap};
use bun_core::Output;
use crate::char_freq::CHAR_FREQ_COUNT;
use crate::{Binding, E, Expr, Index, Ref, Scope, Stmt, symbol};
pub use crate::flags as Flags;
#[repr(transparent)]
pub struct StoreRef<T>(NonNull<T>);
unsafe impl<T: Send> Send for StoreRef<T> {}
unsafe impl<T: Sync> Sync for StoreRef<T> {}
impl<T> StoreRef<T> {
#[inline]
pub const fn from_non_null(p: NonNull<T>) -> Self {
StoreRef(p)
}
#[inline]
pub fn from_raw(p: *mut T) -> Self {
StoreRef(NonNull::new(p).expect("StoreRef::from_raw: null pointer"))
}
#[inline]
pub fn from_bump(r: &mut T) -> Self {
StoreRef(NonNull::from(r))
}
#[inline]
pub fn from_box(b: Box<T>) -> Self {
StoreRef(bun_core::heap::into_raw_nn(b))
}
#[inline]
pub const fn as_ptr(self) -> *mut T {
self.0.as_ptr()
}
#[inline]
pub const fn from_static(r: &'static T) -> Self {
StoreRef(unsafe { NonNull::new_unchecked(core::ptr::from_ref(r).cast_mut()) })
}
#[inline]
pub fn get(&self) -> &T {
self
}
}
impl<T> Clone for StoreRef<T> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for StoreRef<T> {}
impl<T> Deref for StoreRef<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { self.0.as_ref() }
}
}
impl<T> DerefMut for StoreRef<T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
unsafe { self.0.as_mut() }
}
}
impl<T> From<NonNull<T>> for StoreRef<T> {
#[inline]
fn from(p: NonNull<T>) -> Self {
StoreRef(p)
}
}
impl<T> PartialEq for StoreRef<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T> Eq for StoreRef<T> {}
pub type ExprNodeIndex = Expr;
pub type StmtNodeIndex = Stmt;
pub type BindingNodeIndex = Binding;
pub(crate) type ArenaStr = StoreStr;
#[inline]
pub(crate) const fn empty_arena_str() -> ArenaStr {
StoreStr::EMPTY
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct StoreStr {
ptr: core::ptr::NonNull<u8>,
len: usize,
}
unsafe impl Send for StoreStr {}
unsafe impl Sync for StoreStr {}
impl StoreStr {
pub const EMPTY: StoreStr = StoreStr {
ptr: core::ptr::NonNull::<u8>::dangling(),
len: 0,
};
#[inline]
pub const fn new(s: &[u8]) -> Self {
match core::ptr::NonNull::new(s.as_ptr().cast_mut()) {
Some(ptr) => StoreStr { ptr, len: s.len() },
None => StoreStr::EMPTY,
}
}
#[inline]
pub const fn as_ptr(self) -> *const u8 {
self.ptr.as_ptr()
}
#[inline]
pub const fn raw_len(self) -> usize {
self.len
}
#[inline]
pub fn slice<'a>(self) -> &'a [u8] {
unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
#[inline]
pub fn as_raw(self) -> *const [u8] {
core::ptr::slice_from_raw_parts(self.ptr.as_ptr(), self.len)
}
}
impl Default for StoreStr {
#[inline]
fn default() -> Self {
StoreStr::EMPTY
}
}
impl core::ops::Deref for StoreStr {
type Target = [u8];
#[inline]
fn deref(&self) -> &[u8] {
self.slice()
}
}
impl AsRef<[u8]> for StoreStr {
#[inline]
fn as_ref(&self) -> &[u8] {
self.slice()
}
}
impl core::borrow::Borrow<[u8]> for StoreStr {
#[inline]
fn borrow(&self) -> &[u8] {
self.slice()
}
}
impl<const N: usize> From<&[u8; N]> for StoreStr {
#[inline]
fn from(s: &[u8; N]) -> Self {
StoreStr::new(s)
}
}
impl From<&[u8]> for StoreStr {
#[inline]
fn from(s: &[u8]) -> Self {
StoreStr::new(s)
}
}
impl From<&str> for StoreStr {
#[inline]
fn from(s: &str) -> Self {
StoreStr::new(s.as_bytes())
}
}
impl PartialEq for StoreStr {
#[inline]
fn eq(&self, other: &StoreStr) -> bool {
self.slice() == other.slice()
}
}
impl Eq for StoreStr {}
impl PartialEq<[u8]> for StoreStr {
#[inline]
fn eq(&self, other: &[u8]) -> bool {
self.slice() == other
}
}
impl<const N: usize> PartialEq<&[u8; N]> for StoreStr {
#[inline]
fn eq(&self, other: &&[u8; N]) -> bool {
self.slice() == *other
}
}
impl<const N: usize> PartialEq<[u8; N]> for StoreStr {
#[inline]
fn eq(&self, other: &[u8; N]) -> bool {
self.slice() == other
}
}
impl PartialEq<&[u8]> for StoreStr {
#[inline]
fn eq(&self, other: &&[u8]) -> bool {
self.slice() == *other
}
}
impl core::hash::Hash for StoreStr {
#[inline]
fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
self.slice().hash(h)
}
}
impl core::fmt::Debug for StoreStr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
bstr::BStr::new(self.slice()).fmt(f)
}
}
#[repr(C)]
pub struct StoreSlice<T> {
ptr: core::ptr::NonNull<T>,
len: u32,
}
impl<T> Copy for StoreSlice<T> {}
impl<T> Clone for StoreSlice<T> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
unsafe impl<T: Send> Send for StoreSlice<T> {}
unsafe impl<T: Sync> Sync for StoreSlice<T> {}
impl<T> StoreSlice<T> {
pub const EMPTY: StoreSlice<T> = StoreSlice {
ptr: core::ptr::NonNull::<T>::dangling(),
len: 0,
};
#[inline]
pub const fn new(s: &[T]) -> Self {
debug_assert!(s.len() <= u32::MAX as usize);
match core::ptr::NonNull::new(s.as_ptr().cast_mut()) {
Some(ptr) => StoreSlice {
ptr,
len: s.len() as u32,
},
None => StoreSlice::EMPTY,
}
}
#[inline]
pub fn new_mut(s: &mut [T]) -> Self {
debug_assert!(s.len() <= u32::MAX as usize);
match core::ptr::NonNull::new(s.as_mut_ptr()) {
Some(ptr) => StoreSlice {
ptr,
len: s.len() as u32,
},
None => StoreSlice::EMPTY,
}
}
#[inline]
pub const fn as_ptr(self) -> *const T {
self.ptr.as_ptr()
}
#[inline]
pub const fn raw_len(self) -> u32 {
self.len
}
#[inline]
pub fn slice<'a>(self) -> &'a [T] {
unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), self.len as usize) }
}
#[inline]
pub fn slice_mut<'a>(self) -> &'a mut [T] {
unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len as usize) }
}
#[inline]
pub fn truncate(&mut self, new_len: usize) {
assert!(new_len <= self.len as usize);
self.len = new_len as u32;
}
#[inline]
pub fn from_bump<'b>(v: bun_alloc::ArenaVec<'b, T>) -> Self {
use bun_alloc::ArenaVecExt as _;
StoreSlice::new_mut(v.into_bump_slice_mut())
}
}
impl<'a, T> From<bun_alloc::ArenaVec<'a, T>> for StoreSlice<T> {
#[inline]
fn from(v: bun_alloc::ArenaVec<'a, T>) -> Self {
StoreSlice::from_bump(v)
}
}
impl<T> Default for StoreSlice<T> {
#[inline]
fn default() -> Self {
StoreSlice::EMPTY
}
}
impl<T> core::ops::Deref for StoreSlice<T> {
type Target = [T];
#[inline]
fn deref(&self) -> &[T] {
self.slice()
}
}
impl<T> AsRef<[T]> for StoreSlice<T> {
#[inline]
fn as_ref(&self) -> &[T] {
self.slice()
}
}
impl<T> From<&[T]> for StoreSlice<T> {
#[inline]
fn from(s: &[T]) -> Self {
StoreSlice::new(s)
}
}
impl<T> From<&mut [T]> for StoreSlice<T> {
#[inline]
fn from(s: &mut [T]) -> Self {
StoreSlice::new_mut(s)
}
}
impl<T: core::fmt::Debug> core::fmt::Debug for StoreSlice<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.slice().fmt(f)
}
}
pub const NAMESPACE_EXPORT_PART_INDEX: u32 = 0;
pub type ExprNodeList = Vec<Expr, bun_alloc::AstAlloc>;
pub type StmtNodeList = StoreSlice<Stmt>;
pub type BindingNodeList = StoreSlice<Binding>;
#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug, strum::IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
pub enum ImportItemStatus {
None,
Generated,
Missing,
}
#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Default, strum::IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
pub enum AssignTarget {
#[default]
None = 0,
Replace = 1,
Update = 2,
}
#[derive(Copy, Clone)]
pub struct LocRef {
pub loc: crate::Loc,
pub ref_: Option<Ref>,
}
impl Default for LocRef {
fn default() -> Self {
Self {
loc: crate::Loc::EMPTY,
ref_: None,
}
}
}
pub struct ClauseItem {
pub alias: ArenaStr,
pub alias_loc: crate::Loc,
pub name: LocRef,
pub original_name: ArenaStr,
}
impl ClauseItem {
pub const DEFAULT_ALIAS: &'static [u8] = b"default";
}
impl Default for ClauseItem {
fn default() -> Self {
Self {
alias: empty_arena_str(),
alias_loc: crate::Loc::EMPTY,
name: LocRef::default(),
original_name: empty_arena_str(),
}
}
}
#[derive(Copy, Clone, Default)]
pub struct SlotCounts {
pub slots: symbol::SlotNamespaceCountsArray,
}
impl SlotCounts {
pub fn union_max(&mut self, other: SlotCounts) {
for (a, b) in self.slots.values_mut().zip(other.slots.values()) {
if *a < *b {
*a = *b;
}
}
}
}
pub struct NameMinifier {
pub head: Vec<u8>,
pub tail: Vec<u8>,
}
impl NameMinifier {
pub const DEFAULT_HEAD: &'static [u8] =
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$";
pub const DEFAULT_TAIL: &'static [u8] =
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$";
pub fn init() -> NameMinifier {
NameMinifier {
head: Vec::new(),
tail: Vec::new(),
}
}
pub fn number_to_minified_name(
&self,
name: &mut Vec<u8>,
i_: isize,
) -> core::result::Result<(), bun_alloc::AllocError> {
name.clear();
let mut i = i_;
let mut j = usize::try_from(i.rem_euclid(54)).expect("int cast");
name.extend_from_slice(&self.head[j..j + 1]);
i = i.div_euclid(54);
while i > 0 {
i -= 1;
j = usize::try_from(i.rem_euclid(CHAR_FREQ_COUNT as isize)).expect("int cast");
name.extend_from_slice(&self.tail[j..j + 1]);
i = i.div_euclid(CHAR_FREQ_COUNT as isize);
}
Ok(())
}
pub fn default_number_to_minified_name(
i_: isize,
) -> core::result::Result<Vec<u8>, bun_alloc::AllocError> {
let mut i = i_;
let mut j = usize::try_from(i.rem_euclid(54)).expect("int cast");
let mut name: Vec<u8> = Vec::new();
name.extend_from_slice(&Self::DEFAULT_HEAD[j..j + 1]);
i = i.div_euclid(54);
while i > 0 {
i -= 1;
j = usize::try_from(i.rem_euclid(CHAR_FREQ_COUNT as isize)).expect("int cast");
name.extend_from_slice(&Self::DEFAULT_TAIL[j..j + 1]);
i = i.div_euclid(CHAR_FREQ_COUNT as isize);
}
Ok(name)
}
}
#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug, strum::IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
pub enum OptionalChain {
Start,
Continuation,
}
pub struct EnumValue {
pub loc: crate::Loc,
pub ref_: Ref,
pub name: ArenaStr,
pub value: Option<ExprNodeIndex>,
}
impl EnumValue {
pub fn name_as_e_string(&self, bump: &bun_alloc::Arena) -> E::String {
E::String::init_re_encode_utf8(self.name.slice(), bump)
}
}
pub struct Catch {
pub loc: crate::Loc,
pub binding: Option<BindingNodeIndex>,
pub body: StmtNodeList,
pub body_loc: crate::Loc,
}
pub struct Finally {
pub loc: crate::Loc,
pub stmts: StmtNodeList,
}
pub struct Case {
pub loc: crate::Loc,
pub value: Option<ExprNodeIndex>,
pub body: StmtNodeList,
}
pub struct ArrayBinding {
pub binding: BindingNodeIndex,
pub default_value: Option<ExprNodeIndex>,
}
#[derive(Copy, Clone)]
pub struct TlaCheck {
pub depth: u32,
pub parent: crate::base::IndexInt,
pub import_record_index: crate::base::IndexInt,
}
impl Default for TlaCheck {
fn default() -> Self {
Self {
depth: 0,
parent: Index::INVALID.get(),
import_record_index: Index::INVALID.get(),
}
}
}
#[derive(Copy, Clone)]
pub struct Span {
pub text: ArenaStr,
pub range: crate::Range,
}
impl Default for Span {
fn default() -> Self {
Self {
text: empty_arena_str(),
range: crate::Range::default(),
}
}
}
#[derive(Copy, Clone)]
pub struct InlinedEnumValue {
pub raw_data: u64,
}
#[derive(Copy, Clone)]
pub enum InlinedEnumValueDecoded {
String(*const E::String),
Number(f64),
}
impl InlinedEnumValue {
const DOUBLE_ENCODE_OFFSET: u64 = 1 << 49;
const PURE_NAN: f64 = f64::from_bits(0x7ff8000000000000);
fn purify_nan(value: f64) -> f64 {
if value.is_nan() {
Self::PURE_NAN
} else {
value
}
}
pub fn encode(decoded: InlinedEnumValueDecoded) -> InlinedEnumValue {
let encoded = InlinedEnumValue {
raw_data: match decoded {
InlinedEnumValueDecoded::String(ptr) => {
(ptr as usize as u64) & 0x0000_FFFF_FFFF_FFFF
} InlinedEnumValueDecoded::Number(num) => {
Self::purify_nan(num).to_bits() + Self::DOUBLE_ENCODE_OFFSET
}
},
};
if cfg!(debug_assertions) {
debug_assert!(match encoded.decode() {
InlinedEnumValueDecoded::String(str_) => match decoded {
InlinedEnumValueDecoded::String(orig) => core::ptr::eq(str_, orig),
_ => false,
},
InlinedEnumValueDecoded::Number(num) => match decoded {
InlinedEnumValueDecoded::Number(orig) =>
num.to_bits() == Self::purify_nan(orig).to_bits(),
_ => false,
},
});
}
encoded
}
pub fn decode(self) -> InlinedEnumValueDecoded {
if self.raw_data > 0x0000_FFFF_FFFF_FFFF {
InlinedEnumValueDecoded::Number(f64::from_bits(
self.raw_data - Self::DOUBLE_ENCODE_OFFSET,
))
} else {
InlinedEnumValueDecoded::String(self.raw_data as usize as *const E::String)
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, strum::IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
pub enum ExportsKind {
None,
Cjs,
Esm,
EsmWithDynamicFallback,
EsmWithDynamicFallbackFromCjs,
}
impl ExportsKind {
pub fn is_dynamic(self) -> bool {
matches!(
self,
Self::Cjs | Self::EsmWithDynamicFallback | Self::EsmWithDynamicFallbackFromCjs
)
}
pub fn is_esm_with_dynamic_fallback(self) -> bool {
matches!(
self,
Self::EsmWithDynamicFallback | Self::EsmWithDynamicFallbackFromCjs
)
}
}
#[derive(Copy, Clone)]
pub struct DeclaredSymbol {
pub ref_: Ref,
pub is_top_level: bool,
}
pub struct DeclaredSymbolList {
pub entries: MultiArrayList<DeclaredSymbol, bun_alloc::AstAlloc>,
}
impl Default for DeclaredSymbolList {
fn default() -> Self {
Self {
entries: MultiArrayList::new_in(bun_alloc::AstAlloc),
}
}
}
impl DeclaredSymbolList {
pub fn refs(&self) -> &[Ref] {
self.entries.items::<"ref_", Ref>()
}
pub fn to_owned_slice(&mut self) -> DeclaredSymbolList {
core::mem::take(self)
}
pub fn clone(&self) -> core::result::Result<DeclaredSymbolList, bun_alloc::AllocError> {
Ok(DeclaredSymbolList {
entries: self.entries.clone()?,
})
}
#[inline]
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn append(
&mut self,
entry: DeclaredSymbol,
) -> core::result::Result<(), bun_alloc::AllocError> {
self.ensure_unused_capacity(1)?;
self.append_assume_capacity(entry);
Ok(())
}
pub fn append_list(
&mut self,
other: &DeclaredSymbolList,
) -> core::result::Result<(), bun_alloc::AllocError> {
self.ensure_unused_capacity(other.len())?;
self.append_list_assume_capacity(other);
Ok(())
}
pub fn append_list_assume_capacity(&mut self, other: &DeclaredSymbolList) {
// PERF(port): was assume_capacity
self.entries.append_list_assume_capacity(&other.entries);
}
pub fn append_assume_capacity(&mut self, entry: DeclaredSymbol) {
// PERF(port): was assume_capacity
self.entries.append_assume_capacity(entry);
}
pub fn ensure_total_capacity(
&mut self,
count: usize,
) -> core::result::Result<(), bun_alloc::AllocError> {
self.entries.ensure_total_capacity(count)
}
pub fn ensure_unused_capacity(
&mut self,
count: usize,
) -> core::result::Result<(), bun_alloc::AllocError> {
self.entries.ensure_unused_capacity(count)
}
pub fn clear_retaining_capacity(&mut self) {
self.entries.clear_retaining_capacity();
}
// `deinit` → Drop on MultiArrayList; no explicit body needed.
pub fn init_capacity(
capacity: usize,
) -> core::result::Result<DeclaredSymbolList, bun_alloc::AllocError> {
let mut entries = MultiArrayList::new_in(bun_alloc::AstAlloc);
entries.ensure_unused_capacity(capacity)?;
Ok(DeclaredSymbolList { entries })
}
pub fn from_slice(
entries: &[DeclaredSymbol],
) -> core::result::Result<DeclaredSymbolList, bun_alloc::AllocError> {
let mut this = Self::init_capacity(entries.len())?;
// errdefer this.deinit() → Drop handles it
for entry in entries {
this.append_assume_capacity(*entry);
}
Ok(this)
}
}
impl DeclaredSymbol {
fn for_each_top_level_symbol_with_type<C>(
decls: &DeclaredSymbolList,
ctx: &mut C,
f: impl Fn(&mut C, Ref),
) {
let entries = decls.entries.slice();
let is_top_level: &[bool] = entries.items::<"is_top_level", bool>();
let refs: &[Ref] = entries.items::<"ref_", Ref>();
// TODO: SIMD
debug_assert_eq!(is_top_level.len(), refs.len());
for (top, ref_) in is_top_level.iter().zip(refs.iter()) {
if *top {
// PERF(port): was @call(bun.callmod_inline, ...) — relies on inlining.
f(ctx, *ref_);
}
}
}
pub fn for_each_top_level_symbol<C>(
decls: &DeclaredSymbolList,
ctx: &mut C,
f: impl Fn(&mut C, Ref),
) {
Self::for_each_top_level_symbol_with_type(decls, ctx, f);
}
}
#[derive(Copy, Clone)]
pub struct Dependency {
pub source_index: Index,
pub part_index: u32, // Index.Int
}
impl Default for Dependency {
fn default() -> Self {
Self {
source_index: Index::INVALID,
part_index: 0,
}
}
}
pub type DependencyList = bun_alloc::AstVec<Dependency>;
pub type ExprList = Vec<Expr>;
pub type StmtList = Vec<Stmt>;
pub type BindingList = Vec<Binding>;
// PERF(port): Zig `std.array_list.Managed` — these may be arena-backed in
// callers; revisit with bumpalo::collections::Vec if profiling shows churn.
/// Each file is made up of multiple parts, and each part consists of one or
/// more top-level statements. Parts are used for tree shaking and code
/// splitting analysis. Individual parts of a file can be discarded by tree
/// shaking and can be assigned to separate chunks (i.e. output files) by code
/// splitting.
pub struct Part {
pub stmts: StoreSlice<Stmt>,
pub scopes: StoreSlice<*mut Scope>, // TODO(port): &'bump mut [&'bump mut Scope]
/// Each is an index into the file-level import record list
pub import_record_indices: PartImportRecordIndices,
/// All symbols that are declared in this part. Note that a given symbol may
/// have multiple declarations, and so may end up being declared in multiple
/// parts (e.g. multiple "var" declarations with the same name). Also note
/// that this list isn't deduplicated and may contain duplicates.
pub declared_symbols: DeclaredSymbolList,
/// An estimate of the number of uses of all symbols used within this part.
pub symbol_uses: PartSymbolUseMap,
/// This tracks property accesses off of imported symbols. We don't know
/// during parsing if an imported symbol is going to be an inlined enum
/// value or not. This is only known during linking. So we defer adding
/// a dependency on these imported symbols until we know whether the
/// property access is an inlined enum value or not.
pub import_symbol_property_uses: PartSymbolPropertyUseMap,
/// The indices of the other parts in this file that are needed if this part
/// is needed.
pub dependencies: DependencyList,
/// If true, this part can be removed if none of the declared symbols are
/// used. If the file containing this part is imported, then all parts that
/// don't have this flag enabled must be included.
pub can_be_removed_if_unused: bool,
/// This is used for generated parts that we don't want to be present if they
/// aren't needed. This enables tree shaking for these parts even if global
/// tree shaking isn't enabled.
pub force_tree_shaking: bool,
// Liveness moved out to a sidecar `LinkerGraph::parts_live` bitset so the
// tree-shaking recursion's hot "already visited?" check touches a few KB
// of bitset words instead of striding across every 272-byte `Part`.
pub tag: PartTag,
}
pub type PartImportRecordIndices = Vec<u32, bun_alloc::AstAlloc>;
pub type PartList<'a> = bun_alloc::ArenaVec<'a, Part>;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum PartTag {
None,
JsxImport,
Runtime,
CjsImports,
ReactFastRefresh,
DirnameFilename,
BunTest,
DeadDueToInlining,
CommonjsNamedExport,
ImportToConvertFromRequire,
}
// Zig: std.ArrayHashMapUnmanaged(Ref, Symbol.Use, RefHashCtx, false)
// TODO(port): bun_collections::ArrayHashMap must accept a custom hasher ctx (RefHashCtx).
pub type PartSymbolUseMap = ArrayHashMap<Ref, symbol::Use, AutoContext, bun_alloc::AstAlloc>;
pub type PartSymbolPropertyUseMap = ArrayHashMap<
Ref,
StringHashMap<symbol::Use, bun_alloc::AstAlloc>,
AutoContext,
bun_alloc::AstAlloc,
>;
impl Default for Part {
fn default() -> Self {
Self {
stmts: StoreSlice::EMPTY,
scopes: StoreSlice::EMPTY,
import_record_indices: PartImportRecordIndices::new_in(bun_alloc::AstAlloc),
declared_symbols: DeclaredSymbolList::default(),
symbol_uses: PartSymbolUseMap::default(),
import_symbol_property_uses: PartSymbolPropertyUseMap::default(),
dependencies: Vec::new_in(bun_alloc::AstAlloc),
can_be_removed_if_unused: false,
force_tree_shaking: false,
tag: PartTag::None,
}
}
}
#[derive(Clone, Copy)]
pub enum StmtOrExpr {
Stmt(Stmt),
Expr(Expr),
}
impl Default for StmtOrExpr {
fn default() -> Self {
StmtOrExpr::Expr(Expr::default())
}
}
impl StmtOrExpr {
pub fn to_expr(self) -> Expr {
match self {
StmtOrExpr::Expr(expr) => expr,
StmtOrExpr::Stmt(stmt) => match stmt.data {
crate::stmt::Data::SFunction(mut s) => {
// PORT NOTE: Zig moved `func.func` out by value; StoreRef arena
// slot is never individually dropped, so `take` (replace with
// Default) is the safe Rust equivalent.
let func = core::mem::take(&mut s.func);
Expr::init(E::Function { func }, stmt.loc)
}
crate::stmt::Data::SClass(mut s) => {
let class = core::mem::take(&mut s.class);
Expr::init::<E::Class>(class, stmt.loc)
}
other => Output::panic(format_args!(
"Unexpected statement type in default export: .{}",
<&'static str>::from(other.tag())
)),
},
}
}
}
pub struct NamedImport {
/// Parts within this file that use this import
pub local_parts_with_uses: bun_alloc::AstVec<u32>,
/// The original export name from the source module being imported.
/// Examples:
/// - `import { foo } from 'module'` → alias = "foo"
/// - `import { foo as bar } from 'module'` → alias = "foo" (original export name)
/// - `import * as ns from 'module'` → alias_is_star = true, alias = ""
/// This field is used by the bundler to match imports with their corresponding
/// exports and for error reporting when imports can't be resolved.
pub alias: Option<ArenaStr>,
pub alias_loc: Option<crate::Loc>,
pub namespace_ref: Option<Ref>,
pub import_record_index: u32,
/// If true, the alias refers to the entire export namespace object of a
/// module. This is no longer represented as an alias called "*" because of
/// the upcoming "Arbitrary module namespace identifier names" feature:
/// https://github.com/tc39/ecma262/pull/2154
pub alias_is_star: bool,
/// It's useful to flag exported imports because if they are in a TypeScript
/// file, we can't tell if they are a type or a value.
pub is_exported: bool,
}
impl Default for NamedImport {
fn default() -> Self {
Self {
local_parts_with_uses: bun_alloc::AstAlloc::vec(),
alias: None,
alias_loc: None,
namespace_ref: None,
import_record_index: 0,
alias_is_star: false,
is_exported: false,
}
}
}
#[derive(Copy, Clone)]
pub struct NamedExport {
pub ref_: Ref,
pub alias_loc: crate::Loc,
}
#[repr(u8)] // Zig: enum(u4)
#[derive(Copy, Clone, PartialEq, Eq, Debug, strum::IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
pub enum StrictModeKind {
SloppyMode,
ExplicitStrictMode,
ImplicitStrictModeImport,
ImplicitStrictModeExport,
ImplicitStrictModeTopLevelAwait,
ImplicitStrictModeClass,
}
pub fn printmem(args: fmt::Arguments<'_>) {
// `defer Output.flush()` → executes after print; emulate ordering explicitly.
Output::init_test();
Output::print(args);
Output::flush();
}
// TODO(port): `thiserror` not in this crate's deps; hand-roll Display/Error.
#[derive(Debug, Copy, Clone, PartialEq, Eq, strum::IntoStaticStr)]
pub enum ToJSError {
#[strum(serialize = "Cannot convert argument type to JS")]
CannotConvertArgumentTypeToJS,
#[strum(serialize = "Cannot convert identifier to JS. Try a statically-known value")]
CannotConvertIdentifierToJS,
MacroError,
OutOfMemory,
JSError,
JSTerminated,
}
bun_core::impl_tag_error!(ToJSError);
bun_core::named_error_set!(ToJSError);
/// Say you need to allocate a bunch of tiny arrays
/// You could just do separate allocations for each, but that is slow
/// With std.ArrayList, pointers invalidate on resize and that means it will crash.
/// So a better idea is to batch up your allocations into one larger allocation
/// and then just make all the arrays point to different parts of the larger allocation
pub struct Batcher<T> {
pub head: StoreSlice<T>,
}
impl<T> Batcher<T> {
pub fn init(
bump: &bun_alloc::Arena,
count: usize,
) -> core::result::Result<Self, bun_alloc::AllocError>
where
T: Default,
{
// TODO(port): bumpalo alloc_slice for uninit T — Zig `arena.alloc(Type, count)`.
// PERF(port): Zig left the slice uninitialized; bumpalo requires Default fill.
let all = bump.alloc_slice_fill_default(count);
Ok(Self {
head: StoreSlice::new_mut(all),
})
}
pub fn done(&mut self) {
debug_assert!(self.head.is_empty()); // count to init() was too large, overallocation
}
pub fn eat(&mut self, value: T) -> *mut T {
// PORT NOTE: Zig source `@ptrCast(&this.head.eat1(value).ptr)` appears to
// intend `this.eat1(value).ptr` cast to *T. Porting the apparent intent.
self.eat1(value).as_ptr().cast_mut()
}
pub fn eat1(&mut self, value: T) -> StoreSlice<T> {
// `head` has at least 1 element remaining (caller contract — Zig would
// panic on bounds); `Batcher` holds the unique view of the allocation.
let head = self.head.slice_mut();
let (prev, rest) = head.split_at_mut(1);
prev[0] = value;
self.head = StoreSlice::new_mut(rest);
StoreSlice::new_mut(prev)
}
pub fn next<const N: usize>(&mut self, values: [T; N]) -> StoreSlice<T> {
// `head` has at least N elements remaining; see `eat1`.
let head = self.head.slice_mut();
let (prev, rest) = head.split_at_mut(N);
for (dst, src) in prev.iter_mut().zip(values) {
*dst = src;
}
self.head = StoreSlice::new_mut(rest);
StoreSlice::new_mut(prev)
}
}
// Zig: `pub fn NewBatcher(comptime Type: type) type` → Rust generic struct above.
pub type NewBatcher<T> = Batcher<T>;
// ═════════════════════════════════════════════════════════════════════════
// Symbols pulled DOWN from higher-tier
// crates so lower-tier callers (css, interchange, js_parser itself) can
// resolve them here without forming a cycle. Ground truth for each port is
// the named .zig file, NOT the sibling .rs (which may already forward-ref).
// ═════════════════════════════════════════════════════════════════════════
// ─── from bun_jsc::math (src/jsc/jsc.zig) ───────────────────────────────────
pub mod math {
/// `Number.MAX_SAFE_INTEGER` (2^53 - 1)
pub const MAX_SAFE_INTEGER: f64 = 9007199254740991.0;
/// `Number.MIN_SAFE_INTEGER` (-(2^53 - 1))
pub const MIN_SAFE_INTEGER: f64 = -9007199254740991.0;
/// WebKit `maxExponentForIntegerMathPow` (`Source/JavaScriptCore/runtime/
/// MathCommon.h`) — exponent ceiling for the exact integer fast path.
const MAX_EXPONENT_FOR_INTEGER_MATH_POW: i32 = 1000;
/// JSC-compatible `Math.pow`: port of WebKit `operationMathPow`
/// (`Source/JavaScriptCore/runtime/MathCommon.cpp`, oven-sh/WebKit @
/// 7a0b13626e5db69aa5a32d037431d381df5dfb61), previously reached through
/// the Math.pow FFI in upstream `src/jsc/bindings/bindings.cpp`.
///
/// Diverges from plain `powf` on: NaN exponent, |base| == 1 with infinite
/// exponent (NaN), ±0.5 exponents (sqrt path), and small non-negative
/// integer exponents (exact exponentiation-by-squaring).
pub fn pow(x: f64, y: f64) -> f64 {
if y.is_nan() {
return f64::NAN;
}
let absolute_base = x.abs();
if absolute_base == 1.0 && y.is_infinite() {
return f64::NAN;
}
if y == 0.5 {
if absolute_base == 0.0 {
return 0.0;
}
if absolute_base == f64::INFINITY {
return f64::INFINITY;
}
return x.sqrt();
}
if y == -0.5 {
if absolute_base == 0.0 {
return f64::INFINITY;
}
if absolute_base == f64::INFINITY {
return 0.0;
}
return 1.0 / x.sqrt();
}
// `as i32` saturates (out-of-range → i32::MIN/MAX sentinel), failing the
// round-trip below exactly like C++'s hardware cvttsd2si conversion.
let mut y_as_int = y as i32;
if f64::from(y_as_int) == y
&& (0..=MAX_EXPONENT_FOR_INTEGER_MATH_POW).contains(&y_as_int)
{
// If the exponent is a small positive int32 integer, we do a fast exponentiation
let mut result = 1.0;
let mut xd = x;
while y_as_int != 0 {
if y_as_int & 1 != 0 {
result *= xd;
}
xd *= xd;
y_as_int >>= 1;
}
return result;
}
// WebKit `mathPowInternal` is libm `pow` on every non-Darwin-ARM target.
x.powf(y)
}
#[cfg(test)]
mod pow_tests {
use super::*;
#[test]
fn exponent_zero_and_zero_base() {
// 0**0, ±0**±0 → 1 (integer fast path, y == 0).
assert_eq!(pow(0.0, 0.0), 1.0);
assert_eq!(pow(-0.0, 0.0), 1.0);
assert_eq!(pow(0.0, -0.0), 1.0);
assert_eq!(pow(-0.0, -0.0), 1.0);
// NaN base with zero exponent still folds to 1 (exponent checked first).
assert_eq!(pow(f64::NAN, 0.0), 1.0);
}
#[test]
fn integer_exponentiation_by_squaring() {
assert_eq!(pow(2.0, 10.0), 1024.0);
assert_eq!(pow(-2.0, 3.0), -8.0);
assert_eq!(pow(-2.0, 2.0), 4.0);
assert_eq!(pow(-8.0, 3.0), -512.0);
assert_eq!(pow(1.5, 4.0), 5.0625);
assert_eq!(pow(-1.5, 3.0), -3.375);
assert_eq!(pow(7.0, 5.0), 16807.0);
// Boundary of the fast path: 2^1000 exact, 2^1001 via powf.
assert_eq!(pow(2.0, 1000.0), 2f64.powi(1000));
assert_eq!(pow(2.0, 1001.0), 2f64.powi(1001));
// Negative integer exponents fall through to libm pow.
assert_eq!(pow(2.0, -2.0), 0.25);
}
#[test]
fn nan_and_infinity_propagation() {
assert!(pow(f64::NAN, 1.0).is_nan());
assert!(pow(1.0, f64::NAN).is_nan());
// |base| == 1 with ±∞ exponent → NaN (ECMA-262 Math.pow).
assert!(pow(1.0, f64::INFINITY).is_nan());
assert!(pow(-1.0, f64::INFINITY).is_nan());
assert!(pow(1.0, f64::NEG_INFINITY).is_nan());
assert_eq!(pow(f64::INFINITY, 2.0), f64::INFINITY);
assert_eq!(pow(2.0, f64::INFINITY), f64::INFINITY);
}
#[test]
fn half_exponent_sqrt_paths() {
assert_eq!(pow(4.0, 0.5), 2.0);
assert_eq!(pow(2.0, 0.5), 2f64.sqrt());
assert_eq!(pow(0.0, 0.5), 0.0);
assert_eq!(pow(f64::INFINITY, 0.5), f64::INFINITY);
assert_eq!(pow(4.0, -0.5), 0.5);
assert_eq!(pow(0.0, -0.5), f64::INFINITY);
assert_eq!(pow(f64::INFINITY, -0.5), 0.0);
// Negative base with 0.5 → sqrt(Negative) → NaN.
assert!(pow(-4.0, 0.5).is_nan());
}
}
}
// ─── from bun_bundler::v2::MangledProps (src/bundler/bundle_v2.zig) ─────────
// Zig: `std.AutoArrayHashMapUnmanaged(Ref, []const u8)`
// LIFETIMES.tsv: value slices point into the parser arena → `StoreStr`
// (arena-owned, no `'bump` cascade).
pub type MangledProps = ArrayHashMap<Ref, StoreStr>;