Skip to main content

turbopack_core/module_graph/
mod.rs

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