turbopack_core/module_graph/
mod.rs

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