turbopack_core/module_graph/
mod.rs

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