Skip to main content

turbopack_core/module_graph/
mod.rs

1use std::{
2    collections::{BinaryHeap, VecDeque},
3    future::Future,
4    iter::FusedIterator,
5    ops::Deref,
6};
7
8use anyhow::{Context, Result, bail};
9use bincode::{Decode, Encode};
10use petgraph::{
11    Direction,
12    graph::{DiGraph, EdgeIndex, NodeIndex},
13    visit::{EdgeRef, IntoNeighbors, IntoNodeReferences, NodeIndexable, Reversed},
14};
15use rustc_hash::{FxHashMap, FxHashSet};
16use serde::{Deserialize, Serialize};
17use tracing::{Instrument, Level, Span};
18use turbo_rcstr::RcStr;
19use turbo_tasks::{
20    CollectiblesSource, FxIndexMap, NonLocalValue, OperationVc, ReadRef, ResolvedVc,
21    TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc,
22    debug::ValueDebugFormat,
23    graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
24    trace::TraceRawVcs,
25};
26use turbo_tasks_fs::FileSystemPath;
27
28use crate::{
29    chunk::{AsyncModuleInfo, ChunkingContext, ChunkingType, TracedMode},
30    issue::{ImportTracer, ImportTraces, Issue},
31    module::Module,
32    module_graph::{
33        async_module_info::{AsyncModulesInfo, compute_async_module_info},
34        binding_usage_info::BindingUsageInfo,
35        chunk_group_info::{ChunkGroupEntry, ChunkGroupInfo, compute_chunk_group_info},
36        merged_modules::{MergedModuleInfo, compute_merged_modules},
37        module_batches::{ModuleBatchesGraph, compute_module_batches},
38        style_groups::{StyleGroups, StyleGroupsAlgorithm, StyleGroupsConfig},
39        style_groups_graph::compute_style_groups_graph,
40        style_groups_loose::compute_style_groups,
41        traced_di_graph::TracedDiGraph,
42    },
43    reference::{
44        ModuleReference, primary_chunkable_referenced_modules,
45        referenced_modules_and_affecting_sources,
46    },
47    resolve::BindingUsage,
48};
49
50pub mod async_module_info;
51pub mod binding_usage_info;
52pub mod chunk_group_info;
53pub mod merged_modules;
54pub mod module_batch;
55pub(crate) mod module_batches;
56mod side_effect_module_info;
57pub mod style_groups;
58pub mod style_groups_graph;
59pub mod style_groups_loose;
60mod traced_di_graph;
61
62pub use self::module_batches::BatchingConfig;
63
64#[derive(
65    Debug,
66    Copy,
67    Clone,
68    Eq,
69    PartialOrd,
70    Ord,
71    Hash,
72    PartialEq,
73    Serialize,
74    Deserialize,
75    TraceRawVcs,
76    Encode,
77    Decode,
78)]
79pub struct GraphNodeIndex {
80    #[turbo_tasks(trace_ignore)]
81    graph_idx: u32,
82    #[turbo_tasks(trace_ignore)]
83    #[bincode(with_serde)]
84    node_idx: NodeIndex,
85}
86impl GraphNodeIndex {
87    fn new(graph_idx: u32, node_idx: NodeIndex) -> Self {
88        Self {
89            graph_idx,
90            node_idx,
91        }
92    }
93}
94
95unsafe impl NonLocalValue for GraphNodeIndex {}
96
97#[derive(
98    Debug,
99    Copy,
100    Clone,
101    Eq,
102    PartialOrd,
103    Ord,
104    Hash,
105    PartialEq,
106    TraceRawVcs,
107    NonLocalValue,
108    Encode,
109    Decode,
110)]
111pub struct GraphEdgeIndex {
112    graph_idx: u32,
113    #[turbo_tasks(trace_ignore)]
114    #[bincode(with_serde)]
115    edge_idx: EdgeIndex,
116}
117
118impl GraphEdgeIndex {
119    fn new(graph_idx: u32, edge_idx: EdgeIndex) -> Self {
120        Self {
121            graph_idx,
122            edge_idx,
123        }
124    }
125}
126
127#[turbo_tasks::value]
128#[derive(Clone, Debug)]
129pub struct VisitedModules {
130    #[bincode(with = "turbo_bincode::indexmap")]
131    pub modules: FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
132    next_graph_idx: u32,
133}
134
135#[turbo_tasks::value_impl]
136impl VisitedModules {
137    #[turbo_tasks::function(operation)]
138    pub fn empty() -> Vc<Self> {
139        Self {
140            modules: Default::default(),
141            next_graph_idx: 0,
142        }
143        .cell()
144    }
145
146    #[turbo_tasks::function(operation)]
147    pub async fn from_graph(graph: OperationVc<SingleModuleGraph>) -> Result<Vc<Self>> {
148        Ok(Self {
149            modules: graph
150                .connect()
151                .await?
152                .enumerate_nodes()
153                .flat_map(|(node_idx, module)| match module {
154                    SingleModuleGraphNode::Module(module) => Some((
155                        *module,
156                        GraphNodeIndex {
157                            graph_idx: 0,
158                            node_idx,
159                        },
160                    )),
161                    SingleModuleGraphNode::VisitedModule { .. } => None,
162                })
163                .collect(),
164            next_graph_idx: 1,
165        }
166        .cell())
167    }
168
169    #[turbo_tasks::function(operation)]
170    pub async fn with_incremented_index(this: OperationVc<Self>) -> Result<Vc<Self>> {
171        let this = this.connect().await?;
172        Ok(Self {
173            modules: this.modules.clone(),
174            next_graph_idx: this.next_graph_idx + 1,
175        }
176        .cell())
177    }
178
179    #[turbo_tasks::function(operation)]
180    pub async fn concatenate(
181        this: OperationVc<Self>,
182        graph: OperationVc<SingleModuleGraph>,
183    ) -> Result<Vc<Self>> {
184        let graph = graph.connect().await?;
185        let this = this.connect().await?;
186        let iter = this
187            .modules
188            .iter()
189            .map(|(module, idx)| (*module, *idx))
190            .chain(
191                graph
192                    .enumerate_nodes()
193                    .flat_map(|(node_idx, module)| match module {
194                        SingleModuleGraphNode::Module(module) => Some((
195                            *module,
196                            GraphNodeIndex {
197                                graph_idx: this.next_graph_idx,
198                                node_idx,
199                            },
200                        )),
201                        SingleModuleGraphNode::VisitedModule { .. } => None,
202                    }),
203            );
204
205        let mut map = FxIndexMap::with_capacity_and_hasher(
206            this.modules.len() + graph.number_of_modules,
207            Default::default(),
208        );
209        for (k, v) in iter {
210            map.entry(k).or_insert(v);
211        }
212        map.shrink_to_fit();
213
214        Ok(Self {
215            modules: map,
216            next_graph_idx: this.next_graph_idx + 1,
217        }
218        .cell())
219    }
220}
221
222#[turbo_tasks::value(shared, task_input)]
223#[derive(Debug, Clone, Hash, Default)]
224pub struct GraphEntries {
225    /// The bundled chunk groups (listing their entry modules)
226    chunk_groups: Vec<ChunkGroupEntry>,
227    /// Traced top-level modules, which are not referenced by chunk_groups but should still be
228    /// considered as part of the graph.
229    traced_modules: Vec<ResolvedVc<Box<dyn Module>>>,
230}
231
232#[turbo_tasks::value_impl]
233impl GraphEntries {
234    #[turbo_tasks::function]
235    pub fn empty() -> Vc<Self> {
236        Self::default().cell()
237    }
238}
239
240impl GraphEntries {
241    pub fn new(
242        chunk_groups: Vec<ChunkGroupEntry>,
243        traced_modules: Vec<ResolvedVc<Box<dyn Module>>>,
244    ) -> Self {
245        Self {
246            chunk_groups,
247            traced_modules,
248        }
249    }
250    pub fn from_chunk_groups(chunk_groups: Vec<ChunkGroupEntry>) -> Self {
251        Self {
252            chunk_groups,
253            traced_modules: vec![],
254        }
255    }
256
257    pub fn concatenate(entries: impl IntoIterator<Item = GraphEntries>) -> Self {
258        let (chunk_groups, traced_modules): (Vec<_>, Vec<_>) = entries
259            .into_iter()
260            .map(|e| (e.chunk_groups, e.traced_modules))
261            .unzip();
262        Self {
263            chunk_groups: chunk_groups.into_iter().flatten().collect(),
264            traced_modules: traced_modules.into_iter().flatten().collect(),
265        }
266    }
267
268    /// Returns both chunk group modules and traced modules.
269    pub fn all_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
270        self.chunk_groups
271            .iter()
272            .flat_map(|e| e.entries())
273            .chain(self.traced_modules.iter().cloned())
274    }
275
276    /// Like all_modules, but with a boolean whether the module came from `traced_modules`
277    pub fn all_modules_with_is_traced(
278        &self,
279    ) -> impl Iterator<Item = (ResolvedVc<Box<dyn Module>>, bool)> + '_ {
280        self.chunk_groups
281            .iter()
282            .flat_map(|e| e.entries().map(|m| (m, false)))
283            .chain(self.traced_modules.iter().cloned().map(|m| (m, true)))
284    }
285
286    /// Returns only the bundled modules, not the traced modules.
287    pub fn chunk_group_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
288        self.chunk_groups.iter().flat_map(|e| e.entries())
289    }
290}
291
292#[turbo_tasks::value(cell = "new", eq = "manual")]
293#[derive(Clone, Default)]
294pub struct SingleModuleGraph {
295    pub graph: TracedDiGraph<SingleModuleGraphNode, RefData>,
296
297    /// The number of modules in the graph (excluding VisitedModule nodes)
298    pub number_of_modules: usize,
299
300    // NodeIndex isn't necessarily stable (because of swap_remove), but we never remove nodes.
301    //
302    // HashMaps have nondeterministic order, but this map is only used for lookups (in
303    // `get_module`) and not iteration.
304    //
305    // This contains Vcs, but they are already contained in the graph, so no need to trace this.
306    #[turbo_tasks(trace_ignore)]
307    #[bincode(with_serde)]
308    modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex>,
309
310    #[turbo_tasks(trace_ignore)]
311    pub entries: GraphEntries,
312}
313
314#[derive(
315    Debug,
316    Clone,
317    Hash,
318    TraceRawVcs,
319    Serialize,
320    Deserialize,
321    Eq,
322    PartialEq,
323    ValueDebugFormat,
324    NonLocalValue,
325)]
326pub struct RefData {
327    pub chunking_type: ChunkingType,
328    pub binding_usage: BindingUsage,
329    pub reference: ResolvedVc<Box<dyn ModuleReference>>,
330}
331
332impl SingleModuleGraph {
333    /// Walks the graph starting from the given entries and collects all reachable nodes, skipping
334    /// nodes listed in `visited_modules`
335    /// The resulting graph's outgoing edges are in reverse order.
336    async fn new_inner(
337        entries: &GraphEntries,
338        visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
339        include_traced: bool,
340        include_binding_usage: bool,
341    ) -> Result<Vc<Self>> {
342        let emit_spans = tracing::enabled!(Level::INFO);
343        let root_nodes = entries
344            .all_modules_with_is_traced()
345            .map(|(e, is_traced)| {
346                SingleModuleGraphBuilderNode::new_module(emit_spans, e, is_traced)
347            })
348            .try_join()
349            .await?;
350
351        let children_nodes_iter = AdjacencyMap::new()
352            .visit(
353                root_nodes,
354                SingleModuleGraphBuilder {
355                    visited_modules,
356                    emit_spans,
357                    include_traced,
358                    include_binding_usage,
359                },
360            )
361            .await
362            .completed()?;
363        let node_count = children_nodes_iter.len();
364
365        let mut graph: DiGraph<SingleModuleGraphNode, RefData> = DiGraph::with_capacity(
366            node_count,
367            // From real world measurements each module has about 3-4 children
368            // If it has more this would cause an additional allocation, but that's fine
369            node_count * 4,
370        );
371
372        let mut number_of_modules = 0;
373        let mut modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
374            FxHashMap::with_capacity_and_hasher(node_count, Default::default());
375        {
376            let _span = tracing::info_span!("build module graph").entered();
377            for (parent, current) in children_nodes_iter.into_breadth_first_edges() {
378                let (module, graph_node, count) = match current {
379                    SingleModuleGraphBuilderNode::Module {
380                        module,
381                        is_traced: _,
382                        ident: _,
383                    } => (module, SingleModuleGraphNode::Module(module), 1),
384                    SingleModuleGraphBuilderNode::VisitedModule { module, idx } => (
385                        module,
386                        SingleModuleGraphNode::VisitedModule { idx, module },
387                        0,
388                    ),
389                };
390
391                // Find the current node, if it was already added
392                let current_idx = if let Some(current_idx) = modules.get(&module) {
393                    *current_idx
394                } else {
395                    let idx = graph.add_node(graph_node);
396                    number_of_modules += count;
397                    modules.insert(module, idx);
398                    idx
399                };
400                // Add the edge
401                if let Some((SingleModuleGraphBuilderNode::Module { module, .. }, ref_data)) =
402                    parent
403                {
404                    let parent_idx = *modules.get(&module).unwrap();
405                    graph.add_edge(parent_idx, current_idx, ref_data);
406                }
407            }
408        }
409
410        graph.shrink_to_fit();
411
412        #[cfg(debug_assertions)]
413        {
414            use std::sync::LazyLock;
415            static CHECK_FOR_DUPLICATE_MODULES: LazyLock<bool> = LazyLock::new(|| {
416                match std::env::var_os("TURBOPACK_TEMP_DISABLE_DUPLICATE_MODULES_CHECK") {
417                    Some(v) => v != "1" && v != "true",
418                    None => true,
419                }
420            });
421            if *CHECK_FOR_DUPLICATE_MODULES {
422                let mut duplicates = FxHashSet::default();
423                let mut set = FxHashSet::default();
424                for &module in modules.keys() {
425                    let ident = module.ident().to_string().await?;
426                    if !set.insert(ident.clone()) {
427                        duplicates.insert(ident);
428                    }
429                }
430                if !duplicates.is_empty() {
431                    use turbo_tasks::TryFlatJoinIterExt;
432
433                    let duplicates_clone = duplicates.clone();
434                    let duplicate_modules = modules
435                        .iter()
436                        .map(async |(&m, &idx)| {
437                            let id = m.ident().to_string().await?;
438                            if duplicates_clone.contains(&id) {
439                                // 3 is arbitrary but it is enough to reveal a little bit
440                                // of detail.
441                                let debug = m.value_debug_format(3).try_to_string().await?;
442
443                                // Collect reverse dependencies (parents) to help
444                                // diagnose how this module entered the graph.
445                                let parent_modules: Vec<_> = graph
446                                    .edges_directed(idx, petgraph::Direction::Incoming)
447                                    .filter_map(|edge| match graph.node_weight(edge.source()) {
448                                        Some(SingleModuleGraphNode::Module(m)) => Some(*m),
449                                        Some(SingleModuleGraphNode::VisitedModule {
450                                            module,
451                                            ..
452                                        }) => Some(*module),
453                                        None => None,
454                                    })
455                                    .collect();
456                                let parents: Vec<String> = parent_modules
457                                    .iter()
458                                    .map(async |p| {
459                                        let ident = p.ident().to_string().await?;
460                                        Ok((*ident).to_string())
461                                    })
462                                    .try_join()
463                                    .await?;
464
465                                Ok(Some((id, debug, parents)))
466                            } else {
467                                Ok(None)
468                            }
469                        })
470                        .try_flat_join()
471                        .await?;
472                    // group by ident
473                    let mut map: FxHashMap<_, Vec<(String, Vec<String>)>> = FxHashMap::default();
474                    for (key, debug, parents) in duplicate_modules {
475                        map.entry(key).or_default().push((debug, parents));
476                    }
477                    let result = map
478                        .into_iter()
479                        .map(|(ident, modules)| {
480                            let modules = modules
481                                .into_iter()
482                                .map(|(debug, parents)| {
483                                    format!("Module: {debug}, Parents: {parents:?}")
484                                })
485                                .collect::<Vec<_>>()
486                                .join("\n");
487                            format!("Ident: {ident}\n{modules}")
488                        })
489                        .collect::<Vec<_>>()
490                        .join("\n\n");
491                    bail!("Duplicate module idents in graph: {result}");
492                }
493            }
494        }
495
496        let graph = SingleModuleGraph {
497            graph: TracedDiGraph::new(graph),
498            number_of_modules,
499            modules,
500            entries: entries.clone(),
501        }
502        .cell();
503
504        turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn ImportTracer>>(
505            ModuleGraphImportTracer::new(graph).to_resolved().await?,
506        ));
507        Ok(graph)
508    }
509
510    /// WARNING: using this is discouraged, as it doesn't filter out unused or traced references.
511    /// Use iter_reachable_modules or one of the .traverse_* functions instead.
512    pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
513        self.graph.node_weights().filter_map(|n| match n {
514            SingleModuleGraphNode::Module(node) => Some(*node),
515            SingleModuleGraphNode::VisitedModule { .. } => None,
516        })
517    }
518
519    /// Returns true if the given module is in this graph and is an entry module
520    pub fn has_entry_module(&self, module: ResolvedVc<Box<dyn Module>>) -> bool {
521        if let Some(index) = self.modules.get(&module) {
522            self.graph
523                .edges_directed(*index, Direction::Incoming)
524                .next()
525                .is_none()
526        } else {
527            false
528        }
529    }
530
531    /// Iterate over graph entry points
532    pub fn chunk_group_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
533        self.entries.chunk_group_modules()
534    }
535
536    /// WARNING: using this is discouraged, as it doesn't filter out unused or traced references.
537    /// Use iter_reachable_modules or one of the .traverse_* functions instead.
538    pub fn enumerate_nodes(
539        &self,
540    ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
541        self.graph.node_references()
542    }
543
544    fn traverse_cycles<'l>(
545        &'l self,
546        edge_filter: impl Fn(&'l RefData) -> bool,
547        mut visit_cycle: impl FnMut(&[&'l ResolvedVc<Box<dyn Module>>]) -> Result<()>,
548        graph_idx: u32,
549        binding_usage: &'l Option<ReadRef<BindingUsageInfo>>,
550    ) -> Result<()> {
551        // See https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm, but
552        // implemented iteratively instead of recursively.
553        //
554        // Compared to the standard Tarjan's, this also treated self-references (via
555        // `has_self_loop`) as SCCs.
556
557        #[derive(Clone)]
558        struct NodeState {
559            index: u32,
560            lowlink: u32,
561            on_stack: bool,
562            has_self_loop: bool,
563        }
564        enum VisitStep {
565            UnvisitedNode(NodeIndex),
566            EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
567            AfterVisit(NodeIndex),
568        }
569        let mut node_states = vec![None; self.graph.node_bound()];
570        let mut stack = Vec::new();
571        let mut visit_stack = Vec::new();
572        let mut index = 0;
573        let mut scc = Vec::new();
574        for initial_index in self.graph.node_indices() {
575            // Skip over already visited nodes
576            if node_states[initial_index.index()].is_some() {
577                continue;
578            }
579            visit_stack.push(VisitStep::UnvisitedNode(initial_index));
580            while let Some(step) = visit_stack.pop() {
581                match step {
582                    VisitStep::UnvisitedNode(node) => {
583                        node_states[node.index()] = Some(NodeState {
584                            index,
585                            lowlink: index,
586                            on_stack: true,
587                            has_self_loop: false,
588                        });
589                        index += 1;
590                        stack.push(node);
591                        visit_stack.push(VisitStep::AfterVisit(node));
592                        let mut neighbors = self.graph.neighbors(node).detach();
593                        while let Some((edge, succ)) = neighbors.next(&self.graph) {
594                            if binding_usage.as_ref().is_some_and(|binding_usage| {
595                                binding_usage
596                                    .is_reference_unused_edge(&GraphEdgeIndex::new(graph_idx, edge))
597                            }) {
598                                continue;
599                            }
600
601                            let edge_weight = self.graph.edge_weight(edge).unwrap();
602                            if !edge_filter(edge_weight) {
603                                continue;
604                            }
605                            let node_state = &node_states[succ.index()];
606                            if let Some(node_state) = node_state {
607                                if node_state.on_stack {
608                                    let index = node_state.index;
609                                    let parent_state = node_states[node.index()].as_mut().unwrap();
610                                    parent_state.lowlink = parent_state.lowlink.min(index);
611                                    if succ == node {
612                                        parent_state.has_self_loop = true;
613                                    }
614                                }
615                            } else {
616                                visit_stack.push(VisitStep::EdgeAfterVisit {
617                                    parent: node,
618                                    child: succ,
619                                });
620                                visit_stack.push(VisitStep::UnvisitedNode(succ));
621                            }
622                        }
623                    }
624                    VisitStep::EdgeAfterVisit { parent, child } => {
625                        let child_state = node_states[child.index()].as_ref().unwrap();
626                        let lowlink = child_state.lowlink;
627
628                        let parent_state = node_states[parent.index()].as_mut().unwrap();
629                        parent_state.lowlink = parent_state.lowlink.min(lowlink);
630                    }
631                    VisitStep::AfterVisit(node) => {
632                        let node_state = node_states[node.index()].as_ref().unwrap();
633                        let node_has_self_loop = node_state.has_self_loop;
634                        if node_state.lowlink == node_state.index {
635                            loop {
636                                let poppped = stack.pop().unwrap();
637                                let popped_state = node_states[poppped.index()].as_mut().unwrap();
638                                popped_state.on_stack = false;
639                                if let SingleModuleGraphNode::Module(module) =
640                                    self.graph.node_weight(poppped).unwrap()
641                                {
642                                    scc.push(module);
643                                }
644                                if poppped == node {
645                                    break;
646                                }
647                            }
648                            if scc.len() > 1 || node_has_self_loop {
649                                visit_cycle(&scc)?;
650                            }
651                            scc.clear();
652                        }
653                    }
654                }
655            }
656        }
657        Ok(())
658    }
659}
660
661#[turbo_tasks::value]
662struct ModuleGraphImportTracer {
663    graph: ResolvedVc<SingleModuleGraph>,
664}
665
666#[turbo_tasks::value(shared)]
667struct PathToModulesMap {
668    map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>>,
669}
670
671#[turbo_tasks::value_impl]
672impl ModuleGraphImportTracer {
673    #[turbo_tasks::function]
674    fn new(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
675        Self::cell(Self { graph })
676    }
677
678    // Compute this mapping on demand since it might not always be needed.
679    #[turbo_tasks::function]
680    async fn path_to_modules(&self) -> Result<Vc<PathToModulesMap>> {
681        let path_and_modules = self
682            .graph
683            .await?
684            .modules
685            .iter()
686            .map(|(&module, _)| async move { Ok((module.ident().await?.path.clone(), module)) })
687            .try_join()
688            .await?;
689        let mut map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>> =
690            FxHashMap::default();
691        for (path, module) in path_and_modules {
692            map.entry(path).or_default().push(module)
693        }
694        Ok(PathToModulesMap::cell(PathToModulesMap { map }))
695    }
696}
697
698#[turbo_tasks::value_impl]
699impl ImportTracer for ModuleGraphImportTracer {
700    #[turbo_tasks::function]
701    async fn get_traces(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<ImportTraces>> {
702        let path_to_modules = self.path_to_modules().await?;
703        let Some(modules) = path_to_modules.map.get(&path) else {
704            return Ok(Vc::default()); // This isn't unusual, the file just might not be in this
705            // graph.
706        };
707        debug_assert!(!modules.is_empty(), "modules should not be an empty vec");
708        let graph = &*self.await?.graph.await?;
709
710        let reversed_graph = Reversed(&graph.graph.0);
711        return Ok(ImportTraces::cell(ImportTraces(
712            modules
713                .iter()
714                .map(|m| async move {
715                    let Some(&module_idx) = graph.modules.get(m) else {
716                        // The only way this could really happen is if `path_to_modules` is computed
717                        // from a different graph than graph`.  Just error out.
718                        bail!("inconsistent read?")
719                    };
720                    // compute the path from this index to a root of the graph.
721                    let Some((_, path)) = petgraph::algo::astar(
722                        &reversed_graph,
723                        module_idx,
724                        |n| reversed_graph.neighbors(n).next().is_none(),
725                        // Edge weights
726                        |e| match e.weight().chunking_type {
727                            // Prefer following normal imports/requires when we can
728                            ChunkingType::Parallel { .. } => 0,
729                            _ => 1,
730                        },
731                        // `astar` can be accelerated with a distance estimation heuristic, as long
732                        // as our estimate is never > the actual distance.
733                        // However we don't have a mechanism, so just
734                        // estimate 0 which essentially makes this behave like
735                        // dijktra's shortest path algorithm.  `petgraph` has an implementation of
736                        // dijkstra's but it doesn't report  paths, just distances.
737                        // NOTE: dijkstra's with integer weights can be accelerated with incredibly
738                        // efficient priority queue structures (basically with only 0 and 1 as
739                        // weights you can use a `VecDeque`!).  However,
740                        // this is unlikely to be a performance concern.
741                        // Furthermore, if computing paths _does_ become a performance concern, the
742                        // solution would be a hand written implementation of dijkstras so we can
743                        // hoist redundant work out of this loop.
744                        |_| 0,
745                    ) else {
746                        unreachable!("there must be a path to a root");
747                    };
748
749                    // Represent the path as a sequence of AssetIdents
750                    // TODO: consider hinting at various transitions (e.g. was this an
751                    // import/require/dynamic-import?)
752                    let path = path
753                        .into_iter()
754                        .map(async |n| {
755                            graph
756                                .graph
757                                .node_weight(n)
758                                .unwrap() // This is safe since `astar`` only returns indices from the graph
759                                .module()
760                                .ident()
761                                .await
762                        })
763                        .try_join()
764                        .await?;
765                    Ok(path)
766                })
767                .try_join()
768                .await?,
769        )));
770    }
771}
772
773/// The ReadRef version of ModuleGraphBase. This is better for eventual consistency, as the graphs
774/// aren't awaited multiple times within the same task.
775#[turbo_tasks::value(shared, serialization = "skip", eq = "manual", cell = "new")]
776pub struct ModuleGraph {
777    input_graphs: Vec<OperationVc<SingleModuleGraph>>,
778    input_binding_usage: Option<OperationVc<BindingUsageInfo>>,
779
780    snapshot: ModuleGraphSnapshot,
781}
782
783#[turbo_tasks::value_impl]
784impl ModuleGraph {
785    /// Analyze the module graph and potentially remove unused references (by determining the used
786    /// exports and removing unused imports).
787    #[turbo_tasks::function(operation)]
788    pub async fn from_graphs(
789        graphs: Vec<OperationVc<SingleModuleGraph>>,
790        binding_usage: Option<OperationVc<BindingUsageInfo>>,
791    ) -> Result<Vc<Self>> {
792        let graph = Self::from_graphs_inner(graphs, binding_usage)
793            .read_strongly_consistent()
794            .await?;
795        Ok(ReadRef::cell(graph))
796    }
797
798    #[turbo_tasks::function(operation, root)]
799    async fn from_graphs_inner(
800        graphs: Vec<OperationVc<SingleModuleGraph>>,
801        binding_usage: Option<OperationVc<BindingUsageInfo>>,
802    ) -> Result<Vc<ModuleGraph>> {
803        Ok(ModuleGraph {
804            input_graphs: graphs.clone(),
805            input_binding_usage: binding_usage,
806            snapshot: ModuleGraphSnapshot {
807                graphs: graphs.iter().map(|g| g.connect()).try_join().await?,
808                skip_visited_module_children: false,
809                graph_idx_override: None,
810                binding_usage: if let Some(binding_usage) = binding_usage {
811                    Some(binding_usage.connect().await?)
812                } else {
813                    None
814                },
815            },
816        }
817        .cell())
818    }
819
820    #[turbo_tasks::function]
821    pub async fn chunk_group_info(self: Vc<Self>) -> Result<Vc<ChunkGroupInfo>> {
822        compute_chunk_group_info(&*self.await?).await
823    }
824
825    #[turbo_tasks::function]
826    pub async fn merged_modules(self: Vc<Self>) -> Result<Vc<MergedModuleInfo>> {
827        compute_merged_modules(self).await
828    }
829
830    #[turbo_tasks::function]
831    pub async fn module_batches(
832        self: Vc<Self>,
833        config: Vc<BatchingConfig>,
834    ) -> Result<Vc<ModuleBatchesGraph>> {
835        compute_module_batches(self, &*config.await?).await
836    }
837
838    #[turbo_tasks::function]
839    pub async fn style_groups(
840        self: Vc<Self>,
841        chunking_context: Vc<Box<dyn ChunkingContext>>,
842        config: StyleGroupsConfig,
843    ) -> Result<Vc<StyleGroups>> {
844        match &config.algorithm {
845            StyleGroupsAlgorithm::Default => {
846                compute_style_groups(self, chunking_context, &config).await
847            }
848            StyleGroupsAlgorithm::Graph {
849                weight_distribution,
850                request_cost,
851            } => {
852                compute_style_groups_graph(
853                    self,
854                    chunking_context,
855                    request_cost.get(),
856                    weight_distribution.get(),
857                    config.max_chunk_size as u64,
858                )
859                .await
860            }
861        }
862    }
863
864    #[turbo_tasks::function(root)]
865    pub async fn async_module_info(self: Vc<Self>) -> Result<Vc<AsyncModulesInfo>> {
866        // `compute_async_module_info` calls `module.is_self_async()`, so we need to again ignore
867        // all issues such that they aren't emitted multiple times.
868        async move {
869            let result_op = compute_async_module_info(self.to_resolved().await?);
870            let result_vc = result_op.resolve().strongly_consistent().await?;
871            result_op.drop_collectibles::<Box<dyn Issue>>();
872            anyhow::Ok(*result_vc)
873        }
874        .instrument(tracing::info_span!("compute async module info"))
875        .await
876    }
877
878    #[turbo_tasks::function]
879    pub async fn referenced_async_modules(
880        self: Vc<Self>,
881        module: ResolvedVc<Box<dyn Module>>,
882    ) -> Result<Vc<AsyncModuleInfo>> {
883        let graph_ref = self.await?;
884        let async_module_info = self.async_module_info();
885
886        let entry = graph_ref.get_entry(module)?;
887        let referenced_modules = graph_ref
888            .iter_graphs_neighbors_rev(entry, Direction::Outgoing, false)
889            .filter(|(edge_idx, _)| {
890                let ty = graph_ref.get_edge(*edge_idx).unwrap();
891                ty.chunking_type.is_inherit_async()
892            })
893            .map(|(_, child_idx)| anyhow::Ok(graph_ref.get_node(child_idx)?.module()))
894            .collect::<Result<Vec<_>>>()?
895            .into_iter()
896            .rev()
897            .map(async |m| Ok(async_module_info.is_async(m).await?.then_some(*m)))
898            .try_flat_join()
899            .await?;
900
901        Ok(AsyncModuleInfo::new(referenced_modules))
902    }
903
904    /// Returns the underlying graphs as a list, to be used for individual graph traversals.
905    #[turbo_tasks::function]
906    pub fn iter_graphs(&self) -> Vc<ModuleGraphLayers> {
907        Vc::cell(
908            self.input_graphs
909                .iter()
910                .enumerate()
911                .map(|(graph_idx, graph)| {
912                    ModuleGraphLayer::new(*graph, graph_idx as u32, self.input_binding_usage)
913                })
914                .collect(),
915        )
916    }
917}
918
919impl Deref for ModuleGraph {
920    type Target = ModuleGraphSnapshot;
921
922    fn deref(&self) -> &Self::Target {
923        &self.snapshot
924    }
925}
926
927#[turbo_tasks::value(shared, serialization = "skip", eq = "manual", cell = "new")]
928pub struct ModuleGraphLayer {
929    snapshot: ModuleGraphSnapshot,
930}
931
932#[turbo_tasks::value_impl]
933impl ModuleGraphLayer {
934    #[turbo_tasks::function(operation, root)]
935    async fn new(
936        graph: OperationVc<SingleModuleGraph>,
937        graph_idx: u32,
938        binding_usage: Option<OperationVc<BindingUsageInfo>>,
939    ) -> Result<Vc<Self>> {
940        Ok(Self {
941            snapshot: ModuleGraphSnapshot {
942                graphs: vec![graph.connect().await?],
943                skip_visited_module_children: true,
944                graph_idx_override: Some(graph_idx),
945                binding_usage: if let Some(binding_usage) = binding_usage {
946                    Some(binding_usage.connect().await?)
947                } else {
948                    None
949                },
950            },
951        }
952        .cell())
953    }
954}
955
956impl Deref for ModuleGraphLayer {
957    type Target = ModuleGraphSnapshot;
958
959    fn deref(&self) -> &Self::Target {
960        &self.snapshot
961    }
962}
963
964#[turbo_tasks::value(transparent)]
965pub struct ModuleGraphLayers(Vec<OperationVc<ModuleGraphLayer>>);
966
967/// This struct provides traversal functionality for the module graph.
968///
969/// Some edges might be ignored during traversal: unused references listed in binding_usage are
970/// always skipped, and references with ChunkingType::Traced are skipped by default (can be
971/// overridden in some functions via the include_traced parameter).
972///
973/// The API across the functions is pretty consistent, apart from:
974/// - traverse_edges_fixed_point_with_priority additionally provides the GraphEdgeIndex
975/// - traverse_edges_dfs is the only function with include_traced
976#[derive(TraceRawVcs, ValueDebugFormat, NonLocalValue)]
977pub struct ModuleGraphSnapshot {
978    // TODO make this non-public
979    pub graphs: Vec<ReadRef<SingleModuleGraph>>,
980    /// Whether to simply ignore SingleModuleGraphNode::VisitedModule during traversals. For single
981    /// module graph usecases, this is what you want. For the whole graph, there should be an
982    /// error.
983    skip_visited_module_children: bool,
984
985    graph_idx_override: Option<u32>,
986
987    binding_usage: Option<ReadRef<BindingUsageInfo>>,
988}
989
990impl ModuleGraphSnapshot {
991    fn get_entry(&self, entry: ResolvedVc<Box<dyn Module>>) -> Result<GraphNodeIndex> {
992        if self.graph_idx_override.is_some() {
993            debug_assert_eq!(self.graphs.len(), 1,);
994        }
995
996        let Some(idx) = self
997            .graphs
998            .iter()
999            .enumerate()
1000            .find_map(|(graph_idx, graph)| {
1001                graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
1002                    graph_idx: self.graph_idx_override.unwrap_or(graph_idx as u32),
1003                    node_idx: *node_idx,
1004                })
1005            })
1006        else {
1007            bail!("Couldn't find entry module {entry:?} in module graph");
1008        };
1009        Ok(idx)
1010    }
1011
1012    /// The entry modules of all chunk groups of all graphs.
1013    pub fn all_chunk_group_entries(&self) -> impl Iterator<Item = &ChunkGroupEntry> + '_ {
1014        self.graphs
1015            .iter()
1016            .flat_map(|g| g.entries.chunk_groups.iter())
1017    }
1018
1019    /// The entry modules of all chunk groups of all graphs.
1020    pub fn all_chunk_group_entry_modules(
1021        &self,
1022    ) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
1023        self.graphs
1024            .iter()
1025            .flat_map(|g| g.entries.chunk_group_modules())
1026    }
1027
1028    /// The entry modules of all chunk groups of all graphs. Includes traced entry modules
1029    pub fn all_entry_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
1030        self.graphs.iter().flat_map(|g| g.entries.all_modules())
1031    }
1032
1033    fn get_graph(&self, graph_idx: u32) -> &ReadRef<SingleModuleGraph> {
1034        if self.graph_idx_override.is_some() {
1035            self.graphs.first().unwrap()
1036        } else {
1037            &self.graphs[graph_idx as usize]
1038        }
1039    }
1040
1041    fn get_node(&self, node: GraphNodeIndex) -> Result<&SingleModuleGraphNode> {
1042        let graph = self.get_graph(node.graph_idx);
1043        graph
1044            .graph
1045            .node_weight(node.node_idx)
1046            .context("Expected graph node")
1047    }
1048
1049    fn get_edge(&self, edge: GraphEdgeIndex) -> Result<&RefData> {
1050        let graph = self.get_graph(edge.graph_idx);
1051        graph
1052            .graph
1053            .edge_weight(edge.edge_idx)
1054            .context("Expected graph node")
1055    }
1056
1057    fn should_visit_node(&self, node: &SingleModuleGraphNode, direction: Direction) -> bool {
1058        if self.skip_visited_module_children && direction == Direction::Outgoing {
1059            !matches!(node, SingleModuleGraphNode::VisitedModule { .. })
1060        } else {
1061            true
1062        }
1063    }
1064
1065    /// WARNING: using this is discouraged, as it doesn't filter out unused or traced references.
1066    /// Use iter_reachable_modules or one of the .traverse_* functions instead.
1067    pub fn enumerate_nodes(
1068        &self,
1069    ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
1070        self.graphs.iter().flat_map(|g| g.enumerate_nodes())
1071    }
1072
1073    /// WARNING: using this is discouraged, as it doesn't filter out unused or traced references.
1074    /// Use iter_reachable_modules or one of the .traverse_* functions instead.
1075    pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
1076        self.graphs.iter().flat_map(|g| g.iter_nodes())
1077    }
1078
1079    /// Iterate the edges of a node REVERSED!
1080    fn iter_graphs_neighbors_rev<'a>(
1081        &'a self,
1082        node: GraphNodeIndex,
1083        direction: Direction,
1084        include_traced: bool,
1085    ) -> impl Iterator<Item = (GraphEdgeIndex, GraphNodeIndex)> + 'a {
1086        let graph = &*self.get_graph(node.graph_idx).graph;
1087
1088        if cfg!(debug_assertions) && direction == Direction::Outgoing {
1089            let node_weight = graph.node_weight(node.node_idx).unwrap();
1090            if let SingleModuleGraphNode::VisitedModule { .. } = node_weight {
1091                panic!("iter_graphs_neighbors_rev called on VisitedModule node");
1092            }
1093        }
1094
1095        let mut walker = graph.neighbors_directed(node.node_idx, direction).detach();
1096        std::iter::from_fn(move || {
1097            while let Some((edge_idx, succ_idx)) = walker.next(graph) {
1098                let edge_idx = GraphEdgeIndex::new(node.graph_idx, edge_idx);
1099                if self
1100                    .binding_usage
1101                    .as_ref()
1102                    .is_some_and(|binding_usage| binding_usage.is_reference_unused_edge(&edge_idx))
1103                {
1104                    // Don't just return None here, that would end the iterator
1105                    continue;
1106                }
1107
1108                if !include_traced && self.get_edge(edge_idx).unwrap().chunking_type.is_traced() {
1109                    continue;
1110                }
1111
1112                return Some((edge_idx, GraphNodeIndex::new(node.graph_idx, succ_idx)));
1113            }
1114            None
1115        })
1116    }
1117
1118    /// Returns a map of all modules in the graphs to their identifiers.
1119    /// This is primarily useful for debugging.
1120    pub async fn get_ids(&self) -> Result<FxHashMap<ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>>> {
1121        Ok(self
1122            .iter_nodes()
1123            .map(async |n| Ok((n, n.ident().to_string().await?)))
1124            .try_join()
1125            .await?
1126            .into_iter()
1127            .collect::<FxHashMap<_, _>>())
1128    }
1129
1130    /// Traverses all reachable nodes exactly once and calls the visitor.
1131    ///
1132    /// * `entries` - The entry modules to start the traversal from
1133    /// * `state` mutable state to be shared across the visitors
1134    /// * `visit_preorder` - Called before visiting the children of a node.
1135    ///    - Receives the module and the `state`
1136    ///    - Can return [GraphTraversalAction]s to control the traversal
1137    /// * `visit_postorder` - Called after visiting children of a node.
1138    pub fn traverse_nodes_dfs<S>(
1139        &self,
1140        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1141        state: &mut S,
1142        visit_preorder: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<GraphTraversalAction>,
1143        mut visit_postorder: impl FnMut(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<()>,
1144    ) -> Result<()> {
1145        let entries = entries.into_iter().collect::<Vec<_>>();
1146
1147        enum Pass {
1148            Visit,
1149            ExpandAndVisit,
1150        }
1151        let mut stack: Vec<(Pass, GraphNodeIndex)> = Vec::with_capacity(entries.len());
1152        for entry in entries.into_iter().rev() {
1153            stack.push((Pass::ExpandAndVisit, self.get_entry(entry)?));
1154        }
1155        let mut expanded = FxHashSet::default();
1156        while let Some((pass, current)) = stack.pop() {
1157            let current_node = self.get_node(current)?;
1158            match pass {
1159                Pass::Visit => {
1160                    visit_postorder(current_node.module(), state)?;
1161                }
1162                Pass::ExpandAndVisit => {
1163                    if !expanded.insert(current) {
1164                        continue;
1165                    }
1166                    let action = visit_preorder(current_node.module(), state)?;
1167                    if action == GraphTraversalAction::Exclude {
1168                        continue;
1169                    }
1170                    stack.push((Pass::Visit, current));
1171                    if action == GraphTraversalAction::Continue
1172                        && self.should_visit_node(current_node, Direction::Outgoing)
1173                    {
1174                        let current = current_node
1175                            .target_idx(Direction::Outgoing)
1176                            .unwrap_or(current);
1177                        stack.extend(
1178                            self.iter_graphs_neighbors_rev(current, Direction::Outgoing, false)
1179                                .map(|(_, child)| (Pass::ExpandAndVisit, child)),
1180                        );
1181                    }
1182                }
1183            }
1184        }
1185
1186        Ok(())
1187    }
1188
1189    /// Traverses all reachable edges exactly once and calls the visitor with the edge source and
1190    /// target.
1191    ///
1192    /// This means that target nodes can be revisited (once per incoming edge).
1193    ///
1194    /// * `entry` - The entry module to start the traversal from
1195    /// * `visitor` - Called before visiting the children of a node.
1196    ///    - Receives (originating &SingleModuleGraphNode, edge &ChunkingType), target
1197    ///      &SingleModuleGraphNode, state &S
1198    ///    - Can return [GraphTraversalAction]s to control the traversal
1199    pub fn traverse_edges_bfs(
1200        &self,
1201        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1202        mut visitor: impl FnMut(
1203            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1204            ResolvedVc<Box<dyn Module>>,
1205        ) -> Result<GraphTraversalAction>,
1206    ) -> Result<()> {
1207        let mut queue = VecDeque::from(
1208            entries
1209                .into_iter()
1210                .map(|e| self.get_entry(e))
1211                .collect::<Result<Vec<_>>>()?,
1212        );
1213        let mut visited = FxHashSet::default();
1214        for entry_node in &queue {
1215            visitor(None, self.get_node(*entry_node)?.module())?;
1216        }
1217        while let Some(node) = queue.pop_front() {
1218            if visited.insert(node) {
1219                let node_weight = self.get_node(node)?;
1220                for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing, false)
1221                {
1222                    let succ_weight = self.get_node(succ)?;
1223                    let action = visitor(
1224                        Some((node_weight.module(), self.get_edge(edge)?)),
1225                        succ_weight.module(),
1226                    )?;
1227                    if !self.should_visit_node(succ_weight, Direction::Outgoing) {
1228                        continue;
1229                    }
1230                    let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1231                    if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1232                        queue.push_back(succ);
1233                    }
1234                }
1235            }
1236        }
1237
1238        Ok(())
1239    }
1240
1241    /// Traverses all edges exactly once (in an unspecified order) and calls the visitor with the
1242    /// edge source and target.
1243    ///
1244    /// This means that target nodes can be revisited (once per incoming edge).
1245    ///
1246    /// * `visitor` - Called before visiting the children of a node.
1247    ///    - Receives (originating &SingleModuleGraphNode, edge &ChunkingType), target
1248    ///      &SingleModuleGraphNode
1249    pub fn traverse_edges_unordered(
1250        &self,
1251        mut visitor: impl FnMut(
1252            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1253            ResolvedVc<Box<dyn Module>>,
1254        ) -> Result<()>,
1255    ) -> Result<()> {
1256        // Despite the name we need to do a DFS to respect 'reachability' if an edge was trimmed we
1257        // should not follow it, and this is a reasonable way to do that.
1258        self.traverse_edges_dfs(
1259            self.all_chunk_group_entry_modules(),
1260            &mut (),
1261            |parent, target, _| {
1262                visitor(parent, target)?;
1263                Ok(GraphTraversalAction::Continue)
1264            },
1265            |_, _, _| Ok(()),
1266            false,
1267        )
1268    }
1269
1270    /// Traverses all reachable edges in dfs order. The preorder visitor can be used to
1271    /// forward state down the graph, and to skip subgraphs
1272    ///
1273    /// Use this to collect modules in evaluation order.
1274    ///
1275    /// Target nodes can be revisited (once per incoming edge) in the preorder_visitor, in the post
1276    /// order visitor they are visited exactly once with the first edge they were discovered with.
1277    /// Edges are traversed in normal order, so should correspond to reference order.
1278    ///
1279    /// * `entries` - The entry modules to start the traversal from
1280    /// * `state` - The state to be passed to the visitors
1281    /// * `visit_preorder` - Called before visiting the children of a node.
1282    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1283    ///      &SingleModuleGraphNode, state &S
1284    ///    - Can return [GraphTraversalAction]s to control the traversal
1285    /// * `visit_postorder` - Called after visiting the children of a node. Return
1286    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1287    ///      &SingleModuleGraphNode, state &S
1288    pub fn traverse_edges_dfs<S>(
1289        &self,
1290        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1291        state: &mut S,
1292        visit_preorder: impl FnMut(
1293            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1294            ResolvedVc<Box<dyn Module>>,
1295            &mut S,
1296        ) -> Result<GraphTraversalAction>,
1297        visit_postorder: impl FnMut(
1298            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1299            ResolvedVc<Box<dyn Module>>,
1300            &mut S,
1301        ) -> Result<()>,
1302        include_traced: bool,
1303    ) -> Result<()> {
1304        self.traverse_edges_dfs_impl::<S>(
1305            entries,
1306            state,
1307            visit_preorder,
1308            visit_postorder,
1309            Direction::Outgoing,
1310            include_traced,
1311        )
1312    }
1313
1314    /// Traverses all reachable edges in dfs order over the reversed graph. The preorder visitor can
1315    /// be used to forward state up the graph, and to skip subgraphs
1316    ///
1317    /// Target nodes can be revisited (once per incoming edge) in the preorder_visitor, in the post
1318    /// order visitor they are visited exactly once with the first edge they were discovered with.
1319    /// Edges are traversed in normal order, so should correspond to reference order.
1320    ///
1321    /// * `entries` - The entry modules to start the traversal from
1322    /// * `state` - The state to be passed to the visitors
1323    /// * `visit_preorder` - Called before visiting the children of a node.
1324    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1325    ///      &SingleModuleGraphNode, state &S
1326    ///    - Can return [GraphTraversalAction]s to control the traversal
1327    /// * `visit_postorder` - Called after visiting the parents of a node. Return
1328    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1329    ///      &SingleModuleGraphNode, state &S
1330    pub fn traverse_edges_reverse_dfs<S>(
1331        &self,
1332        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1333        state: &mut S,
1334        visit_preorder: impl FnMut(
1335            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1336            ResolvedVc<Box<dyn Module>>,
1337            &mut S,
1338        ) -> Result<GraphTraversalAction>,
1339        visit_postorder: impl FnMut(
1340            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1341            ResolvedVc<Box<dyn Module>>,
1342            &mut S,
1343        ) -> Result<()>,
1344    ) -> Result<()> {
1345        self.traverse_edges_dfs_impl::<S>(
1346            entries,
1347            state,
1348            visit_preorder,
1349            visit_postorder,
1350            Direction::Incoming,
1351            false,
1352        )
1353    }
1354
1355    fn traverse_edges_dfs_impl<S>(
1356        &self,
1357        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1358        state: &mut S,
1359        mut visit_preorder: impl FnMut(
1360            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1361            ResolvedVc<Box<dyn Module>>,
1362            &mut S,
1363        ) -> Result<GraphTraversalAction>,
1364        mut visit_postorder: impl FnMut(
1365            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1366            ResolvedVc<Box<dyn Module>>,
1367            &mut S,
1368        ) -> Result<()>,
1369        direction: Direction,
1370        include_traced: bool,
1371    ) -> Result<()> {
1372        if direction == Direction::Incoming {
1373            debug_assert!(
1374                self.skip_visited_module_children,
1375                "Can only trace reverse edges in a single layer graph. We do not model cross \
1376                 graph reverse edges"
1377            );
1378        }
1379        let entries = entries.into_iter().collect::<Vec<_>>();
1380
1381        enum Pass {
1382            Visit,
1383            ExpandAndVisit,
1384        }
1385        #[allow(clippy::type_complexity)] // This is a temporary internal structure
1386        let mut stack: Vec<(
1387            Pass,
1388            Option<(GraphNodeIndex, GraphEdgeIndex)>,
1389            GraphNodeIndex,
1390        )> = Vec::with_capacity(entries.len());
1391        for entry in entries.into_iter().rev() {
1392            stack.push((Pass::ExpandAndVisit, None, self.get_entry(entry)?));
1393        }
1394        let mut expanded = FxHashSet::default();
1395        while let Some((pass, parent, current)) = stack.pop() {
1396            let parent_arg = match parent {
1397                Some((parent_node, parent_edge)) => Some((
1398                    self.get_node(parent_node)?.module(),
1399                    self.get_edge(parent_edge)?,
1400                )),
1401                None => None,
1402            };
1403            let current_node = self.get_node(current)?;
1404            match pass {
1405                Pass::Visit => {
1406                    visit_postorder(parent_arg, current_node.module(), state)?;
1407                }
1408                Pass::ExpandAndVisit => {
1409                    let action = visit_preorder(parent_arg, current_node.module(), state)?;
1410                    if action == GraphTraversalAction::Exclude {
1411                        continue;
1412                    }
1413                    stack.push((Pass::Visit, parent, current));
1414                    if action == GraphTraversalAction::Continue
1415                        && expanded.insert(current)
1416                        && self.should_visit_node(current_node, direction)
1417                    {
1418                        let current = current_node.target_idx(direction).unwrap_or(current);
1419                        stack.extend(
1420                            self.iter_graphs_neighbors_rev(current, direction, include_traced)
1421                                .map(|(edge, child)| {
1422                                    (Pass::ExpandAndVisit, Some((current, edge)), child)
1423                                }),
1424                        );
1425                    }
1426                }
1427            }
1428        }
1429
1430        Ok(())
1431    }
1432
1433    /// Traverse all cycles in the graph (where the edge filter returns true for the whole cycle)
1434    /// and call the visitor with the nodes in the cycle.
1435    /// Notably, module self-references are also treated as cycles.
1436    pub fn traverse_cycles(
1437        &self,
1438        edge_filter: impl Fn(&RefData) -> bool,
1439        mut visit_cycle: impl FnMut(&[&ResolvedVc<Box<dyn Module>>]) -> Result<()>,
1440    ) -> Result<()> {
1441        for (graph_idx, graph) in self.graphs.iter().enumerate() {
1442            graph.traverse_cycles(
1443                &edge_filter,
1444                &mut visit_cycle,
1445                graph_idx as u32,
1446                &self.binding_usage,
1447            )?;
1448        }
1449        Ok(())
1450    }
1451
1452    /// Traverses all reachable nodes and also continue revisiting them as long the visitor returns
1453    /// GraphTraversalAction::Continue. The visitor is responsible for the runtime complexity and
1454    /// eventual termination of the traversal. This corresponds to computing a fixed point state for
1455    /// the graph.
1456    ///
1457    /// Nodes are (re)visited according to the returned priority of the node, prioritizing high
1458    /// values. This priority is intended to be used a heuristic to reduce the number of
1459    /// retraversals.
1460    ///
1461    /// * `entries` - The entry modules to start the traversal from
1462    /// * `state` - The state to be passed to the callbacks
1463    /// * `visit` - Called for a specific edge
1464    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1465    ///      &SingleModuleGraphNode, state &S
1466    ///    - Return [GraphTraversalAction]s to control the traversal
1467    /// * `priority` - Called for before visiting the children of a node to determine its priority.
1468    ///    - Receives: target &SingleModuleGraphNode, state &S
1469    ///    - Return a priority value for the node
1470    ///
1471    /// Returns the number of node visits (i.e. higher than the node count if there are
1472    /// retraversals).
1473    pub fn traverse_edges_fixed_point_with_priority<S, P: Ord>(
1474        &self,
1475        entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1476        state: &mut S,
1477        mut visit: impl FnMut(
1478            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData, GraphEdgeIndex)>,
1479            ResolvedVc<Box<dyn Module>>,
1480            &mut S,
1481        ) -> Result<GraphTraversalAction>,
1482        priority: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<P>,
1483    ) -> Result<usize> {
1484        if self.skip_visited_module_children {
1485            panic!(
1486                "traverse_edges_fixed_point_with_priority musn't be called on individual graphs"
1487            );
1488        }
1489
1490        let mut visit_order = 0usize;
1491        let mut order = || {
1492            let order = visit_order;
1493            visit_order += 1;
1494            order
1495        };
1496        #[derive(PartialEq, Eq)]
1497        struct NodeWithPriority<T: Ord> {
1498            node: GraphNodeIndex,
1499            priority: T,
1500            visit_order: usize,
1501        }
1502        impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1503            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1504                Some(self.cmp(other))
1505            }
1506        }
1507        impl<T: Ord> Ord for NodeWithPriority<T> {
1508            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1509                // BinaryHeap prioritizes high values
1510
1511                self.priority
1512                    .cmp(&other.priority)
1513                    // Use visit_order, so when there are ties we prioritize earlier discovered
1514                    // nodes, reverting to a BFS in the the case where all priorities are equal
1515                    .then(self.visit_order.cmp(&other.visit_order))
1516            }
1517        }
1518
1519        let mut queue_set = FxHashSet::default();
1520        let mut queue = BinaryHeap::from_iter(
1521            entries
1522                .into_iter()
1523                .map(|(m, priority)| {
1524                    Ok(NodeWithPriority {
1525                        node: self.get_entry(m)?,
1526                        priority,
1527                        visit_order: order(),
1528                    })
1529                })
1530                .collect::<Result<Vec<_>>>()?,
1531        );
1532
1533        for entry_node in &queue {
1534            visit(None, self.get_node(entry_node.node)?.module(), state)?;
1535        }
1536
1537        let mut visit_count = 0usize;
1538        while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1539            queue_set.remove(&node);
1540            let node_weight = self.get_node(node)?;
1541            let node = node_weight.target_idx(Direction::Outgoing).unwrap_or(node);
1542
1543            visit_count += 1;
1544
1545            for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing, false) {
1546                let succ_weight = self.get_node(succ)?;
1547
1548                let action = visit(
1549                    Some((node_weight.module(), self.get_edge(edge)?, edge)),
1550                    succ_weight.module(),
1551                    state,
1552                )?;
1553
1554                let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1555                if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1556                    queue.push(NodeWithPriority {
1557                        node: succ,
1558                        priority: priority(succ_weight.module(), state)?,
1559                        visit_order: order(),
1560                    });
1561                }
1562            }
1563        }
1564
1565        Ok(visit_count)
1566    }
1567
1568    /// Iterates all reachable modules in the graph, ignoring unused and traced references.
1569    pub fn iter_reachable_modules(
1570        &self,
1571    ) -> Result<impl Iterator<Item = ResolvedVc<Box<dyn Module>>>> {
1572        Ok(self.iter_reachable_nodes()?.filter_map(|n| match n {
1573            SingleModuleGraphNode::Module(m) => Some(*m),
1574            SingleModuleGraphNode::VisitedModule { .. } => None,
1575        }))
1576    }
1577
1578    /// Iterates all reachable nodes in the graph, ignoring unused and traced references.
1579    /// This includes VisitedModule nodes (which means that some modules are returned twice).
1580    pub fn iter_reachable_nodes<'a>(
1581        &'a self,
1582    ) -> Result<impl Iterator<Item = &'a SingleModuleGraphNode> + 'a> {
1583        ModuleGraphSnapshotNodeIterator::new(self)
1584    }
1585}
1586
1587struct ModuleGraphSnapshotNodeIterator<'a> {
1588    graph: &'a ModuleGraphSnapshot,
1589    visited: FxHashSet<GraphNodeIndex>,
1590    visit_queue: VecDeque<GraphNodeIndex>,
1591}
1592
1593impl<'a> ModuleGraphSnapshotNodeIterator<'a> {
1594    fn new(graph: &'a ModuleGraphSnapshot) -> Result<Self> {
1595        let entries = graph
1596            .graphs
1597            .iter()
1598            .flat_map(|g| g.chunk_group_modules())
1599            .map(|e| graph.get_entry(e))
1600            .collect::<Result<VecDeque<_>>>()?;
1601
1602        Ok(Self {
1603            graph,
1604            visited: FxHashSet::default(),
1605            visit_queue: entries,
1606        })
1607    }
1608}
1609impl<'a> Iterator for ModuleGraphSnapshotNodeIterator<'a> {
1610    type Item = &'a SingleModuleGraphNode;
1611
1612    fn next(&mut self) -> Option<Self::Item> {
1613        while let Some(node_idx) = self.visit_queue.pop_front() {
1614            if self.visited.insert(node_idx) {
1615                let node_weight = self.graph.get_node(node_idx).unwrap();
1616                if self
1617                    .graph
1618                    .should_visit_node(node_weight, Direction::Outgoing)
1619                {
1620                    let node = node_weight
1621                        .target_idx(Direction::Outgoing)
1622                        .unwrap_or(node_idx);
1623                    self.visit_queue.extend(
1624                        self.graph
1625                            .iter_graphs_neighbors_rev(node, Direction::Outgoing, false)
1626                            .map(|(_, succ)| succ)
1627                            .filter(|succ| !self.visited.contains(succ)),
1628                    );
1629                }
1630                return Some(node_weight);
1631            }
1632        }
1633        None
1634    }
1635}
1636impl FusedIterator for ModuleGraphSnapshotNodeIterator<'_> {}
1637
1638#[turbo_tasks::value_impl]
1639impl SingleModuleGraph {
1640    #[turbo_tasks::function(operation)]
1641    pub async fn new_with_entry(
1642        entry: ChunkGroupEntry,
1643        include_traced: bool,
1644        include_binding_usage: bool,
1645    ) -> Result<Vc<Self>> {
1646        SingleModuleGraph::new_inner(
1647            &GraphEntries::from_chunk_groups(vec![entry]),
1648            &Default::default(),
1649            include_traced,
1650            include_binding_usage,
1651        )
1652        .await
1653    }
1654
1655    #[turbo_tasks::function(operation)]
1656    pub async fn new_with_entries(
1657        entries: ResolvedVc<GraphEntries>,
1658        include_traced: bool,
1659        include_binding_usage: bool,
1660    ) -> Result<Vc<Self>> {
1661        SingleModuleGraph::new_inner(
1662            &*entries.await?,
1663            &Default::default(),
1664            include_traced,
1665            include_binding_usage,
1666        )
1667        .await
1668    }
1669
1670    #[turbo_tasks::function(operation)]
1671    pub async fn new_with_entries_visited(
1672        entries: ResolvedVc<GraphEntries>,
1673        visited_modules: OperationVc<VisitedModules>,
1674        include_traced: bool,
1675        include_binding_usage: bool,
1676    ) -> Result<Vc<Self>> {
1677        SingleModuleGraph::new_inner(
1678            &*entries.await?,
1679            &visited_modules.connect().await?.modules,
1680            include_traced,
1681            include_binding_usage,
1682        )
1683        .await
1684    }
1685
1686    #[turbo_tasks::function(operation)]
1687    pub async fn new_with_entries_visited_intern(
1688        // This must not be a Vc<Vec<_>> to ensure layout segment optimization hits the cache
1689        entries: GraphEntries,
1690        visited_modules: OperationVc<VisitedModules>,
1691        include_traced: bool,
1692        include_binding_usage: bool,
1693    ) -> Result<Vc<Self>> {
1694        SingleModuleGraph::new_inner(
1695            &entries,
1696            &visited_modules.connect().await?.modules,
1697            include_traced,
1698            include_binding_usage,
1699        )
1700        .await
1701    }
1702
1703    #[turbo_tasks::function]
1704    pub async fn module_count(&self) -> Vc<u64> {
1705        Vc::cell(self.number_of_modules as u64)
1706    }
1707
1708    #[turbo_tasks::function]
1709    pub async fn edge_count(&self) -> Vc<u64> {
1710        Vc::cell(self.graph.edge_count() as u64)
1711    }
1712}
1713
1714#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1715pub enum SingleModuleGraphNode {
1716    Module(ResolvedVc<Box<dyn Module>>),
1717    // Models a module that is referenced but has already been visited by an earlier graph.
1718    VisitedModule {
1719        idx: GraphNodeIndex,
1720        module: ResolvedVc<Box<dyn Module>>,
1721    },
1722}
1723
1724impl SingleModuleGraphNode {
1725    pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1726        match self {
1727            SingleModuleGraphNode::Module(module) => *module,
1728            SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1729        }
1730    }
1731    pub fn target_idx(&self, direction: Direction) -> Option<GraphNodeIndex> {
1732        match self {
1733            SingleModuleGraphNode::VisitedModule { idx, .. } => match direction {
1734                Direction::Outgoing => Some(*idx),
1735                Direction::Incoming => None,
1736            },
1737            SingleModuleGraphNode::Module(_) => None,
1738        }
1739    }
1740}
1741
1742#[derive(PartialEq, Eq, Debug)]
1743pub enum GraphTraversalAction {
1744    /// Continue visiting children
1745    Continue,
1746    /// Skip the immediate children, but visit the node in postorder
1747    Skip,
1748    /// Skip the immediate children and the node in postorder
1749    Exclude,
1750}
1751
1752// These nodes are created while walking the Turbopack modules references, and are used to then
1753// afterwards build the SingleModuleGraph.
1754#[derive(Clone, Hash, PartialEq, Eq)]
1755enum SingleModuleGraphBuilderNode {
1756    /// A regular module
1757    Module {
1758        module: ResolvedVc<Box<dyn Module>>,
1759        /// module.ident().to_string(), eagerly computed for tracing, otherwise None
1760        ident: Option<ReadRef<RcStr>>,
1761        /// whether this module is a tracing context
1762        is_traced: bool,
1763    },
1764    /// A reference to a module that is already listed in visited_modules
1765    VisitedModule {
1766        module: ResolvedVc<Box<dyn Module>>,
1767        idx: GraphNodeIndex,
1768    },
1769}
1770
1771impl SingleModuleGraphBuilderNode {
1772    async fn new_module(
1773        emit_spans: bool,
1774        module: ResolvedVc<Box<dyn Module>>,
1775        is_traced: bool,
1776    ) -> Result<Self> {
1777        Ok(Self::Module {
1778            module,
1779            ident: if emit_spans {
1780                // INVALIDATION: we don't need to invalidate when the span name changes
1781                Some(module.ident_string().untracked().await?)
1782            } else {
1783                None
1784            },
1785            is_traced,
1786        })
1787    }
1788    fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1789        Self::VisitedModule { module, idx }
1790    }
1791}
1792
1793struct SingleModuleGraphBuilder<'a> {
1794    visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1795
1796    emit_spans: bool,
1797
1798    /// Whether to walk ChunkingType::Traced references
1799    include_traced: bool,
1800
1801    /// Whether to read ModuleReference::binding_usage()
1802    include_binding_usage: bool,
1803}
1804impl Visit<SingleModuleGraphBuilderNode, RefData> for SingleModuleGraphBuilder<'_> {
1805    type EdgesIntoIter = Vec<(SingleModuleGraphBuilderNode, RefData)>;
1806    type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1807
1808    fn visit(
1809        &mut self,
1810        node: &SingleModuleGraphBuilderNode,
1811        _edge: Option<&RefData>,
1812    ) -> VisitControlFlow {
1813        match node {
1814            SingleModuleGraphBuilderNode::Module { .. } => VisitControlFlow::Continue,
1815            // Module was already visited previously
1816            SingleModuleGraphBuilderNode::VisitedModule { .. } => VisitControlFlow::Skip,
1817        }
1818    }
1819
1820    fn edges(&mut self, node: &SingleModuleGraphBuilderNode) -> Self::EdgesFuture {
1821        // Destructure beforehand to not have to clone the whole node when entering the async block
1822        let &SingleModuleGraphBuilderNode::Module {
1823            module, is_traced, ..
1824        } = node
1825        else {
1826            // These are always skipped in `visit()`
1827            unreachable!()
1828        };
1829        let visited_modules = self.visited_modules;
1830        let emit_spans = self.emit_spans;
1831        let include_traced = self.include_traced;
1832        let include_binding_usage = self.include_binding_usage;
1833        async move {
1834            let refs_cell = if !is_traced {
1835                primary_chunkable_referenced_modules(*module, include_traced, include_binding_usage)
1836            } else {
1837                // Currently we don't care about the binding usage of traced references
1838                referenced_modules_and_affecting_sources(*module, false)
1839            };
1840            let refs = match refs_cell.await {
1841                Ok(refs) => refs,
1842                Err(e) => {
1843                    return Err(e.context(module.ident().to_string().await?));
1844                }
1845            };
1846
1847            refs.iter()
1848                .flat_map(|(reference, resolved)| {
1849                    resolved.modules.iter().map(|m| {
1850                        (
1851                            *reference,
1852                            resolved.chunking_type.clone(),
1853                            resolved.binding_usage.clone(),
1854                            *m,
1855                        )
1856                    })
1857                })
1858                .filter(|(_, ty, _, _)| {
1859                    // Ignore non-entry traced reference if not already in tracing mode.
1860                    //
1861                    // ChunkingType::Traced{TracedMode::Entry}
1862                    // ==> target is always traced
1863                    // ChunkingType::Traced{TracedMode::Transitive}
1864                    // ==> target only traced if parent is traced
1865                    // ChunkingType::*
1866                    // ==> target only traced if parent is traced
1867                    !matches!(
1868                        ty,
1869                        ChunkingType::Traced {
1870                            mode: TracedMode::Transitive
1871                        }
1872                    ) || is_traced
1873                })
1874                .map(async |(reference, ty, binding_usage, target)| {
1875                    let to = if let Some(idx) = visited_modules.get(&target) {
1876                        SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1877                    } else {
1878                        SingleModuleGraphBuilderNode::new_module(
1879                            emit_spans,
1880                            target,
1881                            is_traced || ty.is_traced(),
1882                        )
1883                        .await?
1884                    };
1885                    Ok((
1886                        to,
1887                        RefData {
1888                            chunking_type: ty,
1889                            binding_usage,
1890                            reference,
1891                        },
1892                    ))
1893                })
1894                .try_join()
1895                .await
1896        }
1897    }
1898
1899    fn span(
1900        &mut self,
1901        node: &SingleModuleGraphBuilderNode,
1902        edge: Option<&RefData>,
1903    ) -> tracing::Span {
1904        if !self.emit_spans {
1905            return Span::none();
1906        }
1907
1908        let mut span = match node {
1909            SingleModuleGraphBuilderNode::Module {
1910                ident: Some(ident), ..
1911            } => {
1912                tracing::info_span!("module", name = display(ident))
1913            }
1914            SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1915                tracing::info_span!("visited module")
1916            }
1917            _ => unreachable!(),
1918        };
1919
1920        if let Some(edge) = edge {
1921            match &edge.chunking_type {
1922                ChunkingType::Parallel {
1923                    inherit_async: _,
1924                    hoisted: _,
1925                } => {}
1926                ChunkingType::Traced { .. } => {
1927                    let _span = span.entered();
1928                    span = tracing::info_span!("traced reference");
1929                }
1930                ChunkingType::Async => {
1931                    let _span = span.entered();
1932                    span = tracing::info_span!("async reference");
1933                }
1934                ChunkingType::Isolated { _ty: ty, merge_tag } => {
1935                    let _span = span.entered();
1936                    span = tracing::info_span!(
1937                        "isolated reference",
1938                        ty = debug(&ty),
1939                        merge_tag = debug(&merge_tag)
1940                    );
1941                }
1942                ChunkingType::Shared {
1943                    inherit_async: _,
1944                    merge_tag,
1945                } => {
1946                    let _span = span.entered();
1947                    span = tracing::info_span!("shared reference", merge_tag = debug(&merge_tag));
1948                }
1949            };
1950        }
1951
1952        span
1953    }
1954}
1955
1956#[cfg(test)]
1957pub mod tests {
1958    use anyhow::Result;
1959    use rustc_hash::FxHashMap;
1960    use turbo_rcstr::{RcStr, rcstr};
1961    use turbo_tasks::{ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc};
1962    use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
1963    use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
1964
1965    use super::*;
1966    use crate::{
1967        asset::{Asset, AssetContent},
1968        ident::AssetIdent,
1969        module::{Module, ModuleSideEffects},
1970        module_graph::chunk_group_info::EntryHeuristics,
1971        reference::{ModuleReference, ModuleReferences},
1972        resolve::ModuleResolveResult,
1973    };
1974
1975    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1976    async fn test_traverse_dfs_from_entries_diamond() {
1977        run_graph_test(
1978            vec![rcstr!("a.js")],
1979            {
1980                let mut deps = FxHashMap::default();
1981                // A classic diamond dependency on d
1982                deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1983                deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
1984                deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
1985                deps
1986            },
1987            |graph, entry_modules, module_to_name| {
1988                let mut preorder_visits = Vec::new();
1989                let mut postorder_visits = Vec::new();
1990
1991                graph.traverse_edges_dfs(
1992                    entry_modules,
1993                    &mut (),
1994                    |parent, target, _| {
1995                        preorder_visits.push((
1996                            parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1997                            module_to_name.get(&target).unwrap().clone(),
1998                        ));
1999                        Ok(GraphTraversalAction::Continue)
2000                    },
2001                    |parent, target, _| {
2002                        postorder_visits.push((
2003                            parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2004                            module_to_name.get(&target).unwrap().clone(),
2005                        ));
2006                        Ok(())
2007                    },
2008                    false,
2009                )?;
2010                assert_eq!(
2011                    vec![
2012                        (None, rcstr!("a.js")),
2013                        (Some(rcstr!("a.js")), rcstr!("b.js")),
2014                        (Some(rcstr!("b.js")), rcstr!("d.js")),
2015                        (Some(rcstr!("a.js")), rcstr!("c.js")),
2016                        (Some(rcstr!("c.js")), rcstr!("d.js"))
2017                    ],
2018                    preorder_visits
2019                );
2020                assert_eq!(
2021                    vec![
2022                        (Some(rcstr!("b.js")), rcstr!("d.js")),
2023                        (Some(rcstr!("a.js")), rcstr!("b.js")),
2024                        (Some(rcstr!("c.js")), rcstr!("d.js")),
2025                        (Some(rcstr!("a.js")), rcstr!("c.js")),
2026                        (None, rcstr!("a.js"))
2027                    ],
2028                    postorder_visits
2029                );
2030                Ok(())
2031            },
2032        )
2033        .await;
2034    }
2035
2036    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2037    async fn test_traverse_dfs_from_entries_cycle() {
2038        run_graph_test(
2039            vec![rcstr!("a.js")],
2040            {
2041                let mut deps = FxHashMap::default();
2042                // A cycle of length 3
2043                deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
2044                deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2045                deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
2046                deps
2047            },
2048            |graph, entry_modules, module_to_name| {
2049                let mut preorder_visits = Vec::new();
2050                let mut postorder_visits = Vec::new();
2051
2052                graph.traverse_edges_dfs(
2053                    entry_modules,
2054                    &mut (),
2055                    |parent, target, _| {
2056                        preorder_visits.push((
2057                            parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2058                            module_to_name.get(&target).unwrap().clone(),
2059                        ));
2060                        Ok(GraphTraversalAction::Continue)
2061                    },
2062                    |parent, target, _| {
2063                        postorder_visits.push((
2064                            parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2065                            module_to_name.get(&target).unwrap().clone(),
2066                        ));
2067                        Ok(())
2068                    },
2069                    false,
2070                )?;
2071                assert_eq!(
2072                    vec![
2073                        (None, rcstr!("a.js")),
2074                        (Some(rcstr!("a.js")), rcstr!("b.js")),
2075                        (Some(rcstr!("b.js")), rcstr!("c.js")),
2076                        (Some(rcstr!("c.js")), rcstr!("a.js")),
2077                    ],
2078                    preorder_visits
2079                );
2080                assert_eq!(
2081                    vec![
2082                        (Some(rcstr!("c.js")), rcstr!("a.js")),
2083                        (Some(rcstr!("b.js")), rcstr!("c.js")),
2084                        (Some(rcstr!("a.js")), rcstr!("b.js")),
2085                        (None, rcstr!("a.js"))
2086                    ],
2087                    postorder_visits
2088                );
2089                Ok(())
2090            },
2091        )
2092        .await;
2093    }
2094
2095    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2096    async fn test_traverse_edges_fixed_point_with_priority_cycle() {
2097        run_graph_test(
2098            vec![rcstr!("a.js")],
2099            {
2100                let mut deps = FxHashMap::default();
2101                // A cycle of length 3
2102                deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
2103                deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2104                deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
2105                deps
2106            },
2107            |graph, entry_modules, module_to_name| {
2108                let mut visits = Vec::new();
2109                let mut count = 0;
2110
2111                graph.traverse_edges_fixed_point_with_priority(
2112                    entry_modules.into_iter().map(|m| (m, 0)),
2113                    &mut (),
2114                    |parent, target, _| {
2115                        visits.push((
2116                            parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
2117                            module_to_name.get(&target).unwrap().clone(),
2118                        ));
2119                        count += 1;
2120
2121                        // We are a cycle so we need to break the loop eventually
2122                        Ok(if count < 6 {
2123                            GraphTraversalAction::Continue
2124                        } else {
2125                            GraphTraversalAction::Skip
2126                        })
2127                    },
2128                    |_, _| Ok(0),
2129                )?;
2130                assert_eq!(
2131                    vec![
2132                        (None, rcstr!("a.js")),
2133                        (Some(rcstr!("a.js")), rcstr!("b.js")),
2134                        (Some(rcstr!("b.js")), rcstr!("c.js")),
2135                        (Some(rcstr!("c.js")), rcstr!("a.js")),
2136                        // we start following the cycle again
2137                        (Some(rcstr!("a.js")), rcstr!("b.js")),
2138                        (Some(rcstr!("b.js")), rcstr!("c.js")),
2139                    ],
2140                    visits
2141                );
2142
2143                Ok(())
2144            },
2145        )
2146        .await;
2147    }
2148
2149    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2150    async fn test_traverse_edges_fixed_point_no_priority_is_bfs() {
2151        run_graph_test(
2152            vec![rcstr!("a.js")],
2153            {
2154                let mut deps = FxHashMap::default();
2155                // a simple triangle
2156                //        a
2157                //      b   c
2158                //   d    e    f
2159                deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
2160                deps.insert(rcstr!("b.js"), vec![rcstr!("d.js"), rcstr!("e.js")]);
2161                deps.insert(rcstr!("c.js"), vec![rcstr!("e.js"), rcstr!("f.js")]);
2162                deps
2163            },
2164            |graph, entry_modules, module_to_name| {
2165                let mut visits = Vec::new();
2166                let mut count = 0;
2167
2168                graph.traverse_edges_fixed_point_with_priority(
2169                    entry_modules.into_iter().map(|m| (m, 0)),
2170                    &mut (),
2171                    |parent, target, _| {
2172                        visits.push((
2173                            parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
2174                            module_to_name.get(&target).unwrap().clone(),
2175                        ));
2176                        count += 1;
2177
2178                        // We are a cycle so we need to break the loop eventually
2179                        Ok(if count < 6 {
2180                            GraphTraversalAction::Continue
2181                        } else {
2182                            GraphTraversalAction::Skip
2183                        })
2184                    },
2185                    |_, _| Ok(0),
2186                )?;
2187
2188                assert_eq!(
2189                    vec![
2190                        (None, rcstr!("a.js")),
2191                        (Some(rcstr!("a.js")), rcstr!("c.js")),
2192                        (Some(rcstr!("a.js")), rcstr!("b.js")),
2193                        (Some(rcstr!("b.js")), rcstr!("e.js")),
2194                        (Some(rcstr!("b.js")), rcstr!("d.js")),
2195                        (Some(rcstr!("c.js")), rcstr!("f.js")),
2196                        (Some(rcstr!("c.js")), rcstr!("e.js")),
2197                    ],
2198                    visits
2199                );
2200
2201                Ok(())
2202            },
2203        )
2204        .await;
2205    }
2206
2207    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2208    async fn test_traverse_cycles() {
2209        run_graph_test(
2210            vec![rcstr!("a.js")],
2211            {
2212                let mut deps = FxHashMap::default();
2213                // The cycles are: (i, j, k), and (s) which a self-import
2214                //          a
2215                //      /   |    \
2216                //     /i   s-\   x
2217                //     |j   \-/
2218                //     \k
2219                deps.insert(
2220                    rcstr!("a.js"),
2221                    vec![rcstr!("i.js"), rcstr!("s.js"), rcstr!("x.js")],
2222                );
2223                deps.insert(rcstr!("i.js"), vec![rcstr!("j.js")]);
2224                deps.insert(rcstr!("j.js"), vec![rcstr!("k.js")]);
2225                deps.insert(rcstr!("k.js"), vec![rcstr!("i.js")]);
2226                deps.insert(rcstr!("s.js"), vec![rcstr!("s.js")]);
2227                deps
2228            },
2229            |graph, _, module_to_name| {
2230                let mut cycles = vec![];
2231
2232                graph.traverse_cycles(
2233                    |_| true,
2234                    |cycle| {
2235                        cycles.push(
2236                            cycle
2237                                .iter()
2238                                .map(|n| module_to_name.get(*n).unwrap().clone())
2239                                .collect::<Vec<_>>(),
2240                        );
2241                        Ok(())
2242                    },
2243                )?;
2244
2245                assert_eq!(
2246                    cycles,
2247                    vec![
2248                        vec![rcstr!("k.js"), rcstr!("j.js"), rcstr!("i.js")],
2249                        vec![rcstr!("s.js")]
2250                    ],
2251                );
2252
2253                Ok(())
2254            },
2255        )
2256        .await;
2257    }
2258
2259    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2260    async fn test_reverse_edges_through_layered_graph() {
2261        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2262            BackendOptions::default(),
2263            noop_backing_storage(),
2264        ));
2265        tt.run_once(async move {
2266            #[turbo_tasks::value]
2267            struct ReverseTraversalResults {
2268                forward: Vec<RcStr>,
2269                reverse_from_d: Vec<RcStr>,
2270                reverse_from_b: Vec<RcStr>,
2271            }
2272
2273            #[turbo_tasks::function(operation, root)]
2274            async fn reverse_traversal_results_operation() -> Result<Vc<ReverseTraversalResults>> {
2275                let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2276                let root = fs.root().await?;
2277
2278                // a simple linear graph a -> b ->c
2279                // but b->c is in a parent graph and a is in the child
2280                let repo = TestRepo::new(
2281                    &root,
2282                    [("a.js", vec!["b.js", "d.js"]), ("b.js", vec!["c.js"])],
2283                );
2284                let make_module = |name| {
2285                    Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2286                        .to_resolved()
2287                };
2288                let a_module = make_module("a.js").await?;
2289                let b_module = make_module("b.js").await?;
2290
2291                let parent_graph = SingleModuleGraph::new_with_entries(
2292                    GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2293                        modules: vec![b_module],
2294                        heuristics: EntryHeuristics::default(),
2295                    }])
2296                    .resolved_cell(),
2297                    false,
2298                    false,
2299                );
2300
2301                let module_graph = ModuleGraph::from_graphs(
2302                    vec![
2303                        parent_graph,
2304                        SingleModuleGraph::new_with_entries_visited(
2305                            GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2306                                modules: vec![a_module],
2307                                heuristics: EntryHeuristics::default(),
2308                            }])
2309                            .resolved_cell(),
2310                            VisitedModules::from_graph(parent_graph),
2311                            false,
2312                            false,
2313                        ),
2314                    ],
2315                    None,
2316                )
2317                .connect();
2318                let child_graph = module_graph
2319                    .iter_graphs()
2320                    .await?
2321                    .get(1)
2322                    .unwrap()
2323                    .connect()
2324                    .await?;
2325
2326                // test traversing forward from a in the child graph
2327                let mut visited_forward = Vec::new();
2328                child_graph.traverse_edges_dfs(
2329                    vec![a_module],
2330                    &mut (),
2331                    |_parent, child, _state_| {
2332                        visited_forward.push(child);
2333                        Ok(GraphTraversalAction::Continue)
2334                    },
2335                    |_, _, _| Ok(()),
2336                    false,
2337                )?;
2338                let forward = visited_forward
2339                    .iter()
2340                    .map(|m| m.ident().to_string().owned())
2341                    .try_join()
2342                    .await?;
2343
2344                // test traversing backwards from 'd' which is only in the child graph
2345                let d_module = child_graph
2346                    .enumerate_nodes()
2347                    .map(|(_index, module)| async move {
2348                        Ok(match module {
2349                            crate::module_graph::SingleModuleGraphNode::Module(module) => {
2350                                if module.ident().to_string().owned().await? == "[test]/d.js" {
2351                                    Some(*module)
2352                                } else {
2353                                    None
2354                                }
2355                            }
2356                            crate::module_graph::SingleModuleGraphNode::VisitedModule {
2357                                ..
2358                            } => None,
2359                        })
2360                    })
2361                    .try_flat_join()
2362                    .await?
2363                    .into_iter()
2364                    .next()
2365                    .unwrap();
2366
2367                async fn get_reverse_from(
2368                    graph: &ModuleGraphLayer,
2369                    module: ResolvedVc<Box<dyn Module>>,
2370                ) -> Result<Vec<RcStr>> {
2371                    let mut visited = Vec::new();
2372                    graph.traverse_edges_reverse_dfs(
2373                        vec![module],
2374                        &mut (),
2375                        |_parent, child, _state_| {
2376                            visited.push(child);
2377                            Ok(GraphTraversalAction::Continue)
2378                        },
2379                        |_, _, _| Ok(()),
2380                    )?;
2381                    visited
2382                        .iter()
2383                        .map(|m| m.ident().to_string().owned())
2384                        .try_join()
2385                        .await
2386                }
2387
2388                Ok(ReverseTraversalResults {
2389                    forward,
2390                    reverse_from_d: get_reverse_from(&child_graph, d_module).await?,
2391                    reverse_from_b: get_reverse_from(&child_graph, b_module).await?,
2392                }
2393                .cell())
2394            }
2395
2396            let traversal_results = reverse_traversal_results_operation()
2397                .read_strongly_consistent()
2398                .await?;
2399
2400            assert_eq!(
2401                traversal_results.forward,
2402                vec![
2403                    rcstr!("[test]/a.js"),
2404                    rcstr!("[test]/b.js"),
2405                    rcstr!("[test]/d.js")
2406                ]
2407            );
2408
2409            assert_eq!(
2410                traversal_results.reverse_from_d,
2411                vec![rcstr!("[test]/d.js"), rcstr!("[test]/a.js")]
2412            );
2413
2414            assert_eq!(
2415                traversal_results.reverse_from_b,
2416                vec![rcstr!("[test]/b.js"), rcstr!("[test]/a.js")]
2417            );
2418
2419            Ok(())
2420        })
2421        .await
2422        .unwrap();
2423    }
2424
2425    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2426    async fn test_iter_nodes_modules_through_layered_graph() {
2427        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2428            BackendOptions::default(),
2429            noop_backing_storage(),
2430        ));
2431        tt.run_once(async move {
2432            #[turbo_tasks::value]
2433            struct Results {
2434                iter_nodes: Vec<RcStr>,
2435                iter_modules: Vec<RcStr>,
2436                iter_nodes_single: Vec<Vec<RcStr>>,
2437                iter_modules_single: Vec<Vec<RcStr>>,
2438            }
2439
2440            #[turbo_tasks::function(operation, root)]
2441            async fn reverse_traversal_results_operation() -> Result<Vc<Results>> {
2442                let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2443                let root = fs.root().await?;
2444
2445                // a simple linear graph a -> b -> c -> x -> y -> z
2446                // but x -> y -> z is in a parent graph
2447
2448                let repo = TestRepo::new_with_chunking_types(
2449                    &root,
2450                    [
2451                        ("a.js", vec!["b.js"]),
2452                        ("b.js", vec!["c.js"]),
2453                        ("c.js", vec!["x.js"]),
2454                        ("x.js", vec!["y.js", "traced.js"]),
2455                        ("y.js", vec!["z.js"]),
2456                    ],
2457                    [(
2458                        "x.js",
2459                        "traced.js",
2460                        ChunkingType::Traced {
2461                            mode: TracedMode::Entry,
2462                        },
2463                    )],
2464                );
2465                let make_module = |name| {
2466                    Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2467                        .to_resolved()
2468                };
2469                let x_module = make_module("x.js").await?;
2470                let a_module = make_module("a.js").await?;
2471
2472                let parent_graph = SingleModuleGraph::new_with_entries(
2473                    GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2474                        modules: vec![x_module],
2475                        heuristics: EntryHeuristics::default(),
2476                    }])
2477                    .resolved_cell(),
2478                    true,
2479                    false,
2480                );
2481
2482                let module_graph = ModuleGraph::from_graphs(
2483                    vec![
2484                        parent_graph,
2485                        SingleModuleGraph::new_with_entries_visited(
2486                            GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2487                                modules: vec![a_module],
2488                                heuristics: EntryHeuristics::default(),
2489                            }])
2490                            .resolved_cell(),
2491                            VisitedModules::from_graph(parent_graph),
2492                            true,
2493                            false,
2494                        ),
2495                    ],
2496                    None,
2497                )
2498                .connect();
2499                let graph_layers = module_graph.iter_graphs().await?;
2500
2501                Ok(Results {
2502                    iter_nodes: module_graph
2503                        .await?
2504                        .iter_reachable_nodes()?
2505                        .map(async |node| {
2506                            Ok(match node {
2507                                SingleModuleGraphNode::Module(module) => {
2508                                    module.ident_string().owned().await?
2509                                }
2510                                SingleModuleGraphNode::VisitedModule { module, .. } => {
2511                                    format!("visited {}", module.ident_string().owned().await?)
2512                                        .into()
2513                                }
2514                            })
2515                        })
2516                        .try_join()
2517                        .await?,
2518                    iter_modules: module_graph
2519                        .await?
2520                        .iter_reachable_modules()?
2521                        .map(|m| m.ident_string().owned())
2522                        .try_join()
2523                        .await?,
2524                    iter_nodes_single: graph_layers
2525                        .iter()
2526                        .map(async |layer| {
2527                            layer
2528                                .connect()
2529                                .await?
2530                                .iter_reachable_nodes()?
2531                                .map(async |node| {
2532                                    Ok(match node {
2533                                        SingleModuleGraphNode::Module(module) => {
2534                                            module.ident_string().owned().await?
2535                                        }
2536                                        SingleModuleGraphNode::VisitedModule { module, .. } => {
2537                                            format!(
2538                                                "visited {}",
2539                                                module.ident_string().owned().await?
2540                                            )
2541                                            .into()
2542                                        }
2543                                    })
2544                                })
2545                                .try_join()
2546                                .await
2547                        })
2548                        .try_join()
2549                        .await?,
2550                    iter_modules_single: graph_layers
2551                        .iter()
2552                        .map(async |layer| {
2553                            layer
2554                                .connect()
2555                                .await?
2556                                .iter_reachable_modules()?
2557                                .map(|m| m.ident_string().owned())
2558                                .try_join()
2559                                .await
2560                        })
2561                        .try_join()
2562                        .await?,
2563                }
2564                .cell())
2565            }
2566
2567            let traversal_results = reverse_traversal_results_operation()
2568                .read_strongly_consistent()
2569                .await?;
2570
2571            assert_eq!(
2572                traversal_results.iter_nodes,
2573                vec![
2574                    rcstr!("[test]/x.js"),
2575                    rcstr!("[test]/a.js"),
2576                    rcstr!("[test]/y.js"),
2577                    rcstr!("[test]/b.js"),
2578                    rcstr!("[test]/z.js"),
2579                    rcstr!("[test]/c.js"),
2580                    rcstr!("visited [test]/x.js")
2581                ]
2582            );
2583            assert_eq!(
2584                traversal_results.iter_modules,
2585                vec![
2586                    rcstr!("[test]/x.js"),
2587                    rcstr!("[test]/a.js"),
2588                    rcstr!("[test]/y.js"),
2589                    rcstr!("[test]/b.js"),
2590                    rcstr!("[test]/z.js"),
2591                    rcstr!("[test]/c.js")
2592                ]
2593            );
2594            assert_eq!(
2595                traversal_results.iter_nodes_single,
2596                vec![
2597                    vec![
2598                        rcstr!("[test]/x.js"),
2599                        rcstr!("[test]/y.js"),
2600                        rcstr!("[test]/z.js")
2601                    ],
2602                    vec![
2603                        rcstr!("[test]/a.js"),
2604                        rcstr!("[test]/b.js"),
2605                        rcstr!("[test]/c.js"),
2606                        rcstr!("visited [test]/x.js")
2607                    ]
2608                ]
2609            );
2610            assert_eq!(
2611                traversal_results.iter_modules_single,
2612                vec![
2613                    vec![
2614                        rcstr!("[test]/x.js"),
2615                        rcstr!("[test]/y.js"),
2616                        rcstr!("[test]/z.js")
2617                    ],
2618                    vec![
2619                        rcstr!("[test]/a.js"),
2620                        rcstr!("[test]/b.js"),
2621                        rcstr!("[test]/c.js")
2622                    ]
2623                ]
2624            );
2625
2626            Ok(())
2627        })
2628        .await
2629        .unwrap();
2630    }
2631
2632    #[turbo_tasks::value(shared)]
2633    struct TestRepo {
2634        repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
2635        chunking_types: FxHashMap<(FileSystemPath, FileSystemPath), ChunkingType>,
2636    }
2637
2638    impl TestRepo {
2639        fn new(
2640            root: &FileSystemPath,
2641            dependencies: impl IntoIterator<Item = (impl AsRef<str>, Vec<impl AsRef<str>>)>,
2642        ) -> Vc<Self> {
2643            Self::new_with_chunking_types(
2644                root,
2645                dependencies,
2646                std::iter::empty::<(RcStr, RcStr, ChunkingType)>(),
2647            )
2648        }
2649
2650        fn new_with_chunking_types(
2651            root: &FileSystemPath,
2652            dependencies: impl IntoIterator<Item = (impl AsRef<str>, Vec<impl AsRef<str>>)>,
2653            chunking_types: impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<str>, ChunkingType)>,
2654        ) -> Vc<Self> {
2655            let chunking_types = chunking_types
2656                .into_iter()
2657                .map(|(from, to, ty)| {
2658                    (
2659                        (
2660                            root.join(from.as_ref()).unwrap(),
2661                            root.join(to.as_ref()).unwrap(),
2662                        ),
2663                        ty,
2664                    )
2665                })
2666                .collect::<FxHashMap<_, _>>();
2667            Self {
2668                repo: dependencies
2669                    .into_iter()
2670                    .map(|(k, v)| {
2671                        (
2672                            root.join(k.as_ref()).unwrap(),
2673                            v.iter().map(|f| root.join(f.as_ref()).unwrap()).collect(),
2674                        )
2675                    })
2676                    .collect(),
2677                chunking_types,
2678            }
2679            .cell()
2680        }
2681    }
2682
2683    #[turbo_tasks::value]
2684    struct MockModule {
2685        path: FileSystemPath,
2686        repo: ResolvedVc<TestRepo>,
2687    }
2688    #[turbo_tasks::value_impl]
2689    impl MockModule {
2690        #[turbo_tasks::function]
2691        fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
2692            Self { path, repo }.cell()
2693        }
2694    }
2695
2696    #[turbo_tasks::value_impl]
2697    impl Asset for MockModule {
2698        #[turbo_tasks::function]
2699        fn content(&self) -> Vc<AssetContent> {
2700            panic!("MockModule::content shouldn't be called")
2701        }
2702    }
2703
2704    #[turbo_tasks::value_impl]
2705    impl Module for MockModule {
2706        #[turbo_tasks::function]
2707        fn ident(&self) -> Vc<AssetIdent> {
2708            AssetIdent::from_path(self.path.clone()).into_vc()
2709        }
2710
2711        #[turbo_tasks::function]
2712        fn source(&self) -> Vc<crate::source::OptionSource> {
2713            Vc::cell(None)
2714        }
2715
2716        #[turbo_tasks::function]
2717        async fn references(&self) -> Result<Vc<ModuleReferences>> {
2718            let repo = self.repo.await?;
2719            let references = match repo.repo.get(&self.path) {
2720                Some(deps) => {
2721                    deps.iter()
2722                        .map(async |p| {
2723                            Vc::upcast::<Box<dyn ModuleReference>>(MockModuleReference::new(
2724                                ResolvedVc::upcast(
2725                                    MockModule::new(p.clone(), *self.repo).to_resolved().await?,
2726                                ),
2727                                rcstr!("normal-dep"),
2728                                repo.chunking_types
2729                                    .get(&(self.path.clone(), p.clone()))
2730                                    .cloned()
2731                                    .unwrap_or(ChunkingType::Parallel {
2732                                        inherit_async: true,
2733                                        hoisted: false,
2734                                    }),
2735                            ))
2736                            .to_resolved()
2737                            .await
2738                        })
2739                        .try_join()
2740                        .await?
2741                }
2742                None => vec![],
2743            };
2744
2745            Ok(Vc::cell(references))
2746        }
2747        #[turbo_tasks::function]
2748        fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
2749            ModuleSideEffects::SideEffectful.cell()
2750        }
2751    }
2752
2753    #[turbo_tasks::value]
2754    #[derive(ValueToString)]
2755    #[value_to_string(self.description)]
2756    struct MockModuleReference {
2757        asset: ResolvedVc<Box<dyn Module>>,
2758        description: RcStr,
2759        chunking_type: ChunkingType,
2760    }
2761
2762    impl MockModuleReference {
2763        pub fn new(
2764            asset: ResolvedVc<Box<dyn Module>>,
2765            description: RcStr,
2766            chunking_type: ChunkingType,
2767        ) -> Vc<Self> {
2768            MockModuleReference {
2769                asset,
2770                description,
2771                chunking_type,
2772            }
2773            .cell()
2774        }
2775    }
2776
2777    #[turbo_tasks::value_impl]
2778    impl ModuleReference for MockModuleReference {
2779        #[turbo_tasks::function]
2780        fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
2781            *ModuleResolveResult::module(self.asset)
2782        }
2783
2784        fn chunking_type(&self) -> Option<ChunkingType> {
2785            Some(self.chunking_type.clone())
2786        }
2787    }
2788
2789    /// Constructs a graph based on the provided dependency adjacency lists and calls the given test
2790    /// function.
2791    ///
2792    /// # Parameters
2793    /// - `entries`: A vector of entry module names (as `RcStr`). These are the starting points for
2794    ///   the graph.
2795    /// - `graph`: A map from module name (`RcStr`) to a vector of its dependency module names
2796    ///   (`RcStr`). Represents the adjacency list of the graph.
2797    /// - `test_fn`: A function that is called with:
2798    ///     - `ReadRef<SingleModuleGraph>`: The constructed module graph.
2799    ///     - `Vec<ResolvedVc<Box<dyn Module>>>`: The resolved entry modules.
2800    ///     - `FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>`: A mapping from module to its name for
2801    ///       easier analysis in tests.
2802    async fn run_graph_test(
2803        entries: Vec<RcStr>,
2804        graph: FxHashMap<RcStr, Vec<RcStr>>,
2805        test_fn: impl FnOnce(
2806            &ModuleGraph,
2807            Vec<ResolvedVc<Box<dyn Module>>>,
2808            FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2809        ) -> Result<()>
2810        + Send
2811        + 'static,
2812    ) {
2813        #[turbo_tasks::value(serialization = "skip", eq = "manual", cell = "new")]
2814        struct SetupGraph {
2815            module_graph: ReadRef<ModuleGraph>,
2816            entry_modules: Vec<ResolvedVc<Box<dyn Module>>>,
2817            module_to_name: FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2818        }
2819
2820        #[turbo_tasks::function(operation, root)]
2821        async fn setup_graph(
2822            entries: Vec<RcStr>,
2823            graph_entries: Vec<(RcStr, Vec<RcStr>)>,
2824        ) -> Result<Vc<SetupGraph>> {
2825            let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2826            let root = fs.root().await?;
2827
2828            let repo = TestRepo::new(&root, graph_entries);
2829            let entry_modules = entries
2830                .iter()
2831                .map(|e| {
2832                    Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
2833                        .to_resolved()
2834                })
2835                .try_join()
2836                .await?;
2837            let graph = SingleModuleGraph::new_with_entries(
2838                GraphEntries::resolved_cell(GraphEntries::new(
2839                    vec![ChunkGroupEntry::Entry {
2840                        modules: entry_modules.clone(),
2841                        heuristics: EntryHeuristics::default(),
2842                    }],
2843                    vec![],
2844                )),
2845                false,
2846                false,
2847            );
2848
2849            // Create a simple name mapping to make analyzing the visitors easier.
2850            // Technically they could always pull this name off of the
2851            // `module.ident().await?.path.path` themselves but you cannot `await` in visitors.
2852            let module_to_name = graph
2853                .connect()
2854                .await?
2855                .modules
2856                .keys()
2857                .map(|m| async move { Ok((*m, m.ident().await?.path.path.clone())) })
2858                .try_join()
2859                .await?
2860                .into_iter()
2861                .collect();
2862            let module_graph = ModuleGraph::from_graphs(vec![graph], None)
2863                .connect()
2864                .await?;
2865
2866            Ok(SetupGraph {
2867                module_graph,
2868                entry_modules,
2869                module_to_name,
2870            }
2871            .cell())
2872        }
2873
2874        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2875            BackendOptions::default(),
2876            noop_backing_storage(),
2877        ));
2878        let graph_entries = graph.into_iter().collect::<Vec<_>>();
2879        tt.run_once(async move {
2880            let setup = setup_graph(entries, graph_entries)
2881                .read_strongly_consistent()
2882                .await?;
2883
2884            test_fn(
2885                &setup.module_graph,
2886                setup.entry_modules.clone(),
2887                setup.module_to_name.clone(),
2888            )
2889        })
2890        .await
2891        .unwrap();
2892    }
2893}