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