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