bun_ast 0.1.0

A Rust-native programmable browser runtime built on Servo and SpiderMonkey
//! Represents a boundary between client and server code. Every boundary
//! gets bundled twice, once for the desired target, and once to generate
//! a module of "references". Specifically, the generated file takes the
//! canonical Ast as input to derive a wrapper. See `Framework.ServerComponents`
//! for more details about this generated file.
//!
//! This is sometimes abbreviated as SCB

use bun_collections::array_hash_map::ArrayHashAdapter;
use bun_collections::multi_array_list;
use bun_collections::{ArrayHashMap, DynamicBitSetUnmanaged, MultiArrayList};

use super::base::IndexInt;
use super::use_directive::UseDirective;

// `` generates `ServerComponentBoundaryField` +
// `ServerComponentBoundary{Slice,List}Ext` (`source_index()`,
// `items_reference_source_index()`, …) used by the bundler.
#[derive(Clone, Copy)]
pub struct ServerComponentBoundary {
    pub use_directive: UseDirective,

    /// The index of the original file.
    pub source_index: IndexInt,

    /// Index to the file imported on the opposite platform, which is
    /// generated by the bundler. For client components, this is the
    /// server's code. For server actions, this is the client's code.
    pub reference_source_index: IndexInt,

    /// When `bake.Framework.ServerComponents.separate_ssr_graph` is enabled this
    /// points to the separated module. When the SSR graph is not separate, this is
    /// equal to `reference_source_index`
    //
    // TODO: Is this used for server actions.
    pub ssr_source_index: IndexInt,
}

bun_collections::multi_array_columns! {
    pub trait ServerComponentBoundaryColumns for ServerComponentBoundary {
        use_directive: UseDirective,
        source_index: IndexInt,
        reference_source_index: IndexInt,
        ssr_source_index: IndexInt,
    }
}

/// The requirements for this data structure is to have reasonable lookup
/// speed, but also being able to pull a `[]const Index.Int` of all
/// boundaries for iteration.
#[derive(Default)]
pub struct List {
    pub list: MultiArrayList<ServerComponentBoundary>,
    /// Used to facilitate fast lookups into `items` by `.source_index`
    pub map: Map,
}

// Zig: `std.ArrayHashMapUnmanaged(void, void, struct {}, true)` — a keyless
// array-hash-map used purely as an index table; all lookups go through the
// `Adapter` which hashes/compares against `list.items(.source_index)`.
type Map = ArrayHashMap<(), ()>;

impl List {
    /// Can only be called on the bundler thread.
    pub fn put(
        &mut self,
        source_index: IndexInt,
        use_directive: UseDirective,
        reference_source_index: IndexInt,
        ssr_source_index: IndexInt,
    ) -> Result<(), bun_alloc::AllocError> {
        self.list.append(ServerComponentBoundary {
            source_index,
            use_directive,
            reference_source_index,
            ssr_source_index,
        })?;
        // PORT NOTE: reshaped for borrowck — Zig built `Adapter` from
        // `m.list.slice()` while also borrowing `m.map` mutably. Here we hand
        // the adapter just the `source_index` column it needs.
        let gop = self.map.get_or_put_adapted(
            &source_index,
            &Adapter {
                source_indices: self.list.items::<"source_index", IndexInt>(),
            },
        )?;
        debug_assert!(!gop.found_existing);
        Ok(())
    }

    /// Can only be called on the bundler thread.
    pub fn get_index(&self, real_source_index: IndexInt) -> Option<usize> {
        self.map.get_index_adapted(
            &real_source_index,
            &Adapter {
                source_indices: self.list.items::<"source_index", IndexInt>(),
            },
        )
    }

    /// Use this to improve speed of accessing fields at the cost of
    /// storing more pointers. Invalidated when input is mutated.
    pub fn slice(&self) -> Slice<'_> {
        Slice {
            list: self.list.slice(),
            map: &self.map,
        }
    }
}

pub struct Slice<'a> {
    pub list: multi_array_list::Slice<ServerComponentBoundary>,
    pub map: &'a Map,
}

impl<'a> Slice<'a> {
    pub fn get_index(&self, real_source_index: IndexInt) -> Option<usize> {
        self.map.get_index_adapted(
            &real_source_index,
            &Adapter {
                source_indices: self.list.items::<"source_index", IndexInt>(),
            },
        )
    }

    pub fn get_reference_source_index(&self, real_source_index: IndexInt) -> Option<u32> {
        let i = self.map.get_index_adapted(
            &real_source_index,
            &Adapter {
                source_indices: self.list.items::<"source_index", IndexInt>(),
            },
        )?;
        // Zig: `bun.unsafeAssert(l.list.capacity > 0)` — optimization hint for
        // `MultiArrayList.Slice.items`. The Rust `items()` already short-circuits
        // on `capacity == 0`, so the assert is dropped.
        Some(self.list.items::<"reference_source_index", IndexInt>()[i])
    }

    pub fn bit_set(
        &self,
        input_file_count: usize,
    ) -> Result<DynamicBitSetUnmanaged, bun_alloc::AllocError> {
        let mut scb_bitset = DynamicBitSetUnmanaged::init_empty(input_file_count)?;
        for &source_index in self.list.items::<"source_index", IndexInt>() {
            scb_bitset.set(source_index as usize);
        }
        Ok(scb_bitset)
    }
}

// PORT NOTE: Zig stored the full `MultiArrayList.Slice` and called
// `.items(.source_index)` on each compare. The Rust `Slice<T>` is not `Copy`,
// so we cache just the `source_index` column the adapter actually needs.
pub(crate) struct Adapter<'a> {
    pub source_indices: &'a [IndexInt],
}

impl<'a> ArrayHashAdapter<IndexInt, ()> for Adapter<'a> {
    #[inline]
    fn hash(&self, key: &IndexInt) -> u32 {
        bun_wyhash::hash_int(*key)
    }
    #[inline]
    fn eql(&self, a: &IndexInt, _b: &(), b_index: usize) -> bool {
        *a == self.source_indices[b_index]
    }
}

// ported from: src/js_parser/ast/ServerComponentBoundary.zig