1use std::{
2 collections::{BinaryHeap, VecDeque},
3 future::Future,
4 iter::FusedIterator,
5 ops::Deref,
6};
7
8use anyhow::{Context, Result, bail};
9use bincode::{Decode, Encode};
10use petgraph::{
11 Direction,
12 graph::{DiGraph, EdgeIndex, NodeIndex},
13 visit::{EdgeRef, IntoNeighbors, IntoNodeReferences, NodeIndexable, Reversed},
14};
15use rustc_hash::{FxHashMap, FxHashSet};
16use serde::{Deserialize, Serialize};
17use tracing::{Instrument, Level, Span};
18use turbo_rcstr::RcStr;
19use turbo_tasks::{
20 CollectiblesSource, FxIndexMap, NonLocalValue, OperationVc, ReadRef, ResolvedVc,
21 TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc,
22 debug::ValueDebugFormat,
23 graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
24 trace::TraceRawVcs,
25};
26use turbo_tasks_fs::FileSystemPath;
27
28use crate::{
29 chunk::{AsyncModuleInfo, ChunkingContext, ChunkingType},
30 issue::{ImportTracer, ImportTraces, Issue},
31 module::Module,
32 module_graph::{
33 async_module_info::{AsyncModulesInfo, compute_async_module_info},
34 binding_usage_info::BindingUsageInfo,
35 chunk_group_info::{ChunkGroupEntry, ChunkGroupInfo, compute_chunk_group_info},
36 merged_modules::{MergedModuleInfo, compute_merged_modules},
37 module_batches::{ModuleBatchesGraph, compute_module_batches},
38 style_groups::{StyleGroups, StyleGroupsConfig, compute_style_groups},
39 traced_di_graph::TracedDiGraph,
40 },
41 reference::{ModuleReference, primary_chunkable_referenced_modules},
42 resolve::BindingUsage,
43};
44
45pub mod async_module_info;
46pub mod binding_usage_info;
47pub mod chunk_group_info;
48pub mod merged_modules;
49pub mod module_batch;
50pub(crate) mod module_batches;
51mod side_effect_module_info;
52pub(crate) mod style_groups;
53mod traced_di_graph;
54
55pub use self::module_batches::BatchingConfig;
56
57#[derive(
58 Debug,
59 Copy,
60 Clone,
61 Eq,
62 PartialOrd,
63 Ord,
64 Hash,
65 PartialEq,
66 Serialize,
67 Deserialize,
68 TraceRawVcs,
69 Encode,
70 Decode,
71)]
72pub struct GraphNodeIndex {
73 #[turbo_tasks(trace_ignore)]
74 graph_idx: u32,
75 #[turbo_tasks(trace_ignore)]
76 #[bincode(with_serde)]
77 node_idx: NodeIndex,
78}
79impl GraphNodeIndex {
80 fn new(graph_idx: u32, node_idx: NodeIndex) -> Self {
81 Self {
82 graph_idx,
83 node_idx,
84 }
85 }
86}
87
88unsafe impl NonLocalValue for GraphNodeIndex {}
89
90#[derive(
91 Debug,
92 Copy,
93 Clone,
94 Eq,
95 PartialOrd,
96 Ord,
97 Hash,
98 PartialEq,
99 TraceRawVcs,
100 NonLocalValue,
101 Encode,
102 Decode,
103)]
104pub struct GraphEdgeIndex {
105 graph_idx: u32,
106 #[turbo_tasks(trace_ignore)]
107 #[bincode(with_serde)]
108 edge_idx: EdgeIndex,
109}
110
111impl GraphEdgeIndex {
112 fn new(graph_idx: u32, edge_idx: EdgeIndex) -> Self {
113 Self {
114 graph_idx,
115 edge_idx,
116 }
117 }
118}
119
120#[turbo_tasks::value]
121#[derive(Clone, Debug)]
122pub struct VisitedModules {
123 #[bincode(with = "turbo_bincode::indexmap")]
124 pub modules: FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
125 next_graph_idx: u32,
126}
127
128#[turbo_tasks::value_impl]
129impl VisitedModules {
130 #[turbo_tasks::function(operation)]
131 pub fn empty() -> Vc<Self> {
132 Self {
133 modules: Default::default(),
134 next_graph_idx: 0,
135 }
136 .cell()
137 }
138
139 #[turbo_tasks::function(operation)]
140 pub async fn from_graph(graph: OperationVc<SingleModuleGraph>) -> Result<Vc<Self>> {
141 Ok(Self {
142 modules: graph
143 .connect()
144 .await?
145 .enumerate_nodes()
146 .flat_map(|(node_idx, module)| match module {
147 SingleModuleGraphNode::Module(module) => Some((
148 *module,
149 GraphNodeIndex {
150 graph_idx: 0,
151 node_idx,
152 },
153 )),
154 SingleModuleGraphNode::VisitedModule { .. } => None,
155 })
156 .collect(),
157 next_graph_idx: 1,
158 }
159 .cell())
160 }
161
162 #[turbo_tasks::function(operation)]
163 pub async fn with_incremented_index(this: OperationVc<Self>) -> Result<Vc<Self>> {
164 let this = this.connect().await?;
165 Ok(Self {
166 modules: this.modules.clone(),
167 next_graph_idx: this.next_graph_idx + 1,
168 }
169 .cell())
170 }
171
172 #[turbo_tasks::function(operation)]
173 pub async fn concatenate(
174 this: OperationVc<Self>,
175 graph: OperationVc<SingleModuleGraph>,
176 ) -> Result<Vc<Self>> {
177 let graph = graph.connect().await?;
178 let this = this.connect().await?;
179 let iter = this
180 .modules
181 .iter()
182 .map(|(module, idx)| (*module, *idx))
183 .chain(
184 graph
185 .enumerate_nodes()
186 .flat_map(|(node_idx, module)| match module {
187 SingleModuleGraphNode::Module(module) => Some((
188 *module,
189 GraphNodeIndex {
190 graph_idx: this.next_graph_idx,
191 node_idx,
192 },
193 )),
194 SingleModuleGraphNode::VisitedModule { .. } => None,
195 }),
196 );
197
198 let mut map = FxIndexMap::with_capacity_and_hasher(
199 this.modules.len() + graph.number_of_modules,
200 Default::default(),
201 );
202 for (k, v) in iter {
203 map.entry(k).or_insert(v);
204 }
205 map.shrink_to_fit();
206
207 Ok(Self {
208 modules: map,
209 next_graph_idx: this.next_graph_idx + 1,
210 }
211 .cell())
212 }
213}
214
215pub type GraphEntriesT = Vec<ChunkGroupEntry>;
216
217#[turbo_tasks::value(transparent)]
218pub struct GraphEntries(GraphEntriesT);
219
220#[turbo_tasks::value_impl]
221impl GraphEntries {
222 #[turbo_tasks::function]
223 pub fn empty() -> Vc<Self> {
224 Vc::cell(Vec::new())
225 }
226}
227
228#[turbo_tasks::value(cell = "new", eq = "manual")]
229#[derive(Clone, Default)]
230pub struct SingleModuleGraph {
231 pub graph: TracedDiGraph<SingleModuleGraphNode, RefData>,
232
233 pub number_of_modules: usize,
235
236 #[turbo_tasks(trace_ignore)]
243 #[bincode(with_serde)]
244 modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex>,
245
246 #[turbo_tasks(trace_ignore)]
247 pub entries: GraphEntriesT,
248}
249
250#[derive(
251 Debug,
252 Clone,
253 Hash,
254 TraceRawVcs,
255 Serialize,
256 Deserialize,
257 Eq,
258 PartialEq,
259 ValueDebugFormat,
260 NonLocalValue,
261)]
262pub struct RefData {
263 pub chunking_type: ChunkingType,
264 pub binding_usage: BindingUsage,
265 pub reference: ResolvedVc<Box<dyn ModuleReference>>,
266}
267
268impl SingleModuleGraph {
269 async fn new_inner(
273 entries: Vec<ChunkGroupEntry>,
274 visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
275 include_traced: bool,
276 include_binding_usage: bool,
277 ) -> Result<Vc<Self>> {
278 let emit_spans = tracing::enabled!(Level::INFO);
279 let root_nodes = entries
280 .iter()
281 .flat_map(|e| e.entries())
282 .map(|e| SingleModuleGraphBuilderNode::new_module(emit_spans, e))
283 .try_join()
284 .await?;
285
286 let children_nodes_iter = AdjacencyMap::new()
287 .visit(
288 root_nodes,
289 SingleModuleGraphBuilder {
290 visited_modules,
291 emit_spans,
292 include_traced,
293 include_binding_usage,
294 },
295 )
296 .await
297 .completed()?;
298 let node_count = children_nodes_iter.len();
299
300 let mut graph: DiGraph<SingleModuleGraphNode, RefData> = DiGraph::with_capacity(
301 node_count,
302 node_count * 4,
305 );
306
307 let mut number_of_modules = 0;
308 let mut modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
309 FxHashMap::with_capacity_and_hasher(node_count, Default::default());
310 {
311 let _span = tracing::info_span!("build module graph").entered();
312 for (parent, current) in children_nodes_iter.into_breadth_first_edges() {
313 let (module, graph_node, count) = match current {
314 SingleModuleGraphBuilderNode::Module { module, ident: _ } => {
315 (module, SingleModuleGraphNode::Module(module), 1)
316 }
317 SingleModuleGraphBuilderNode::VisitedModule { module, idx } => (
318 module,
319 SingleModuleGraphNode::VisitedModule { idx, module },
320 0,
321 ),
322 };
323
324 let current_idx = if let Some(current_idx) = modules.get(&module) {
326 *current_idx
327 } else {
328 let idx = graph.add_node(graph_node);
329 number_of_modules += count;
330 modules.insert(module, idx);
331 idx
332 };
333 if let Some((SingleModuleGraphBuilderNode::Module { module, .. }, ref_data)) =
335 parent
336 {
337 let parent_idx = *modules.get(&module).unwrap();
338 graph.add_edge(parent_idx, current_idx, ref_data);
339 }
340 }
341 }
342
343 graph.shrink_to_fit();
344
345 #[cfg(debug_assertions)]
346 {
347 use std::sync::LazyLock;
348 static CHECK_FOR_DUPLICATE_MODULES: LazyLock<bool> = LazyLock::new(|| {
349 match std::env::var_os("TURBOPACK_TEMP_DISABLE_DUPLICATE_MODULES_CHECK") {
350 Some(v) => v != "1" && v != "true",
351 None => true,
352 }
353 });
354 if *CHECK_FOR_DUPLICATE_MODULES {
355 let mut duplicates = FxHashSet::default();
356 let mut set = FxHashSet::default();
357 for &module in modules.keys() {
358 let ident = module.ident().to_string().await?;
359 if !set.insert(ident.clone()) {
360 duplicates.insert(ident);
361 }
362 }
363 if !duplicates.is_empty() {
364 use turbo_tasks::TryFlatJoinIterExt;
365
366 let duplicates_clone = duplicates.clone();
367 let duplicate_modules = modules
368 .iter()
369 .map(async |(&m, &idx)| {
370 let id = m.ident().to_string().await?;
371 if duplicates_clone.contains(&id) {
372 let debug = m.value_debug_format(3).try_to_string().await?;
375
376 let parent_modules: Vec<_> = graph
379 .edges_directed(idx, petgraph::Direction::Incoming)
380 .filter_map(|edge| match graph.node_weight(edge.source()) {
381 Some(SingleModuleGraphNode::Module(m)) => Some(*m),
382 Some(SingleModuleGraphNode::VisitedModule {
383 module,
384 ..
385 }) => Some(*module),
386 None => None,
387 })
388 .collect();
389 let parents: Vec<String> = parent_modules
390 .iter()
391 .map(async |p| {
392 let ident = p.ident().to_string().await?;
393 Ok((*ident).to_string())
394 })
395 .try_join()
396 .await?;
397
398 Ok(Some((id, debug, parents)))
399 } else {
400 Ok(None)
401 }
402 })
403 .try_flat_join()
404 .await?;
405 let mut map: FxHashMap<_, Vec<(String, Vec<String>)>> = FxHashMap::default();
407 for (key, debug, parents) in duplicate_modules {
408 map.entry(key).or_default().push((debug, parents));
409 }
410 bail!("Duplicate module idents in graph: {map:#?}",);
411 }
412 }
413 }
414
415 let graph = SingleModuleGraph {
416 graph: TracedDiGraph::new(graph),
417 number_of_modules,
418 modules,
419 entries: entries.clone(),
420 }
421 .cell();
422
423 turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn ImportTracer>>(
424 ModuleGraphImportTracer::new(graph).to_resolved().await?,
425 ));
426 Ok(graph)
427 }
428
429 pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
432 self.graph.node_weights().filter_map(|n| match n {
433 SingleModuleGraphNode::Module(node) => Some(*node),
434 SingleModuleGraphNode::VisitedModule { .. } => None,
435 })
436 }
437
438 pub fn has_entry_module(&self, module: ResolvedVc<Box<dyn Module>>) -> bool {
440 if let Some(index) = self.modules.get(&module) {
441 self.graph
442 .edges_directed(*index, Direction::Incoming)
443 .next()
444 .is_none()
445 } else {
446 false
447 }
448 }
449
450 pub fn entry_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
452 self.entries.iter().flat_map(|e| e.entries())
453 }
454
455 pub fn enumerate_nodes(
458 &self,
459 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
460 self.graph.node_references()
461 }
462
463 fn traverse_cycles<'l>(
464 &'l self,
465 edge_filter: impl Fn(&'l RefData) -> bool,
466 mut visit_cycle: impl FnMut(&[&'l ResolvedVc<Box<dyn Module>>]) -> Result<()>,
467 graph_idx: u32,
468 binding_usage: &'l Option<ReadRef<BindingUsageInfo>>,
469 ) -> Result<()> {
470 #[derive(Clone)]
477 struct NodeState {
478 index: u32,
479 lowlink: u32,
480 on_stack: bool,
481 has_self_loop: bool,
482 }
483 enum VisitStep {
484 UnvisitedNode(NodeIndex),
485 EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
486 AfterVisit(NodeIndex),
487 }
488 let mut node_states = vec![None; self.graph.node_bound()];
489 let mut stack = Vec::new();
490 let mut visit_stack = Vec::new();
491 let mut index = 0;
492 let mut scc = Vec::new();
493 for initial_index in self.graph.node_indices() {
494 if node_states[initial_index.index()].is_some() {
496 continue;
497 }
498 visit_stack.push(VisitStep::UnvisitedNode(initial_index));
499 while let Some(step) = visit_stack.pop() {
500 match step {
501 VisitStep::UnvisitedNode(node) => {
502 node_states[node.index()] = Some(NodeState {
503 index,
504 lowlink: index,
505 on_stack: true,
506 has_self_loop: false,
507 });
508 index += 1;
509 stack.push(node);
510 visit_stack.push(VisitStep::AfterVisit(node));
511 let mut neighbors = self.graph.neighbors(node).detach();
512 while let Some((edge, succ)) = neighbors.next(&self.graph) {
513 if binding_usage.as_ref().is_some_and(|binding_usage| {
514 binding_usage
515 .is_reference_unused_edge(&GraphEdgeIndex::new(graph_idx, edge))
516 }) {
517 continue;
518 }
519
520 let edge_weight = self.graph.edge_weight(edge).unwrap();
521 if !edge_filter(edge_weight) {
522 continue;
523 }
524 let node_state = &node_states[succ.index()];
525 if let Some(node_state) = node_state {
526 if node_state.on_stack {
527 let index = node_state.index;
528 let parent_state = node_states[node.index()].as_mut().unwrap();
529 parent_state.lowlink = parent_state.lowlink.min(index);
530 if succ == node {
531 parent_state.has_self_loop = true;
532 }
533 }
534 } else {
535 visit_stack.push(VisitStep::EdgeAfterVisit {
536 parent: node,
537 child: succ,
538 });
539 visit_stack.push(VisitStep::UnvisitedNode(succ));
540 }
541 }
542 }
543 VisitStep::EdgeAfterVisit { parent, child } => {
544 let child_state = node_states[child.index()].as_ref().unwrap();
545 let lowlink = child_state.lowlink;
546
547 let parent_state = node_states[parent.index()].as_mut().unwrap();
548 parent_state.lowlink = parent_state.lowlink.min(lowlink);
549 }
550 VisitStep::AfterVisit(node) => {
551 let node_state = node_states[node.index()].as_ref().unwrap();
552 let node_has_self_loop = node_state.has_self_loop;
553 if node_state.lowlink == node_state.index {
554 loop {
555 let poppped = stack.pop().unwrap();
556 let popped_state = node_states[poppped.index()].as_mut().unwrap();
557 popped_state.on_stack = false;
558 if let SingleModuleGraphNode::Module(module) =
559 self.graph.node_weight(poppped).unwrap()
560 {
561 scc.push(module);
562 }
563 if poppped == node {
564 break;
565 }
566 }
567 if scc.len() > 1 || node_has_self_loop {
568 visit_cycle(&scc)?;
569 }
570 scc.clear();
571 }
572 }
573 }
574 }
575 }
576 Ok(())
577 }
578}
579
580#[turbo_tasks::value]
581struct ModuleGraphImportTracer {
582 graph: ResolvedVc<SingleModuleGraph>,
583}
584
585#[turbo_tasks::value(shared)]
586struct PathToModulesMap {
587 map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>>,
588}
589
590#[turbo_tasks::value_impl]
591impl ModuleGraphImportTracer {
592 #[turbo_tasks::function]
593 fn new(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
594 Self::cell(Self { graph })
595 }
596
597 #[turbo_tasks::function]
599 async fn path_to_modules(&self) -> Result<Vc<PathToModulesMap>> {
600 let path_and_modules = self
601 .graph
602 .await?
603 .modules
604 .iter()
605 .map(|(&module, _)| async move { Ok((module.ident().await?.path.clone(), module)) })
606 .try_join()
607 .await?;
608 let mut map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>> =
609 FxHashMap::default();
610 for (path, module) in path_and_modules {
611 map.entry(path).or_default().push(module)
612 }
613 Ok(PathToModulesMap::cell(PathToModulesMap { map }))
614 }
615}
616
617#[turbo_tasks::value_impl]
618impl ImportTracer for ModuleGraphImportTracer {
619 #[turbo_tasks::function]
620 async fn get_traces(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<ImportTraces>> {
621 let path_to_modules = self.path_to_modules().await?;
622 let Some(modules) = path_to_modules.map.get(&path) else {
623 return Ok(Vc::default()); };
626 debug_assert!(!modules.is_empty(), "modules should not be an empty vec");
627 let graph = &*self.await?.graph.await?;
628
629 let reversed_graph = Reversed(&graph.graph.0);
630 return Ok(ImportTraces::cell(ImportTraces(
631 modules
632 .iter()
633 .map(|m| async move {
634 let Some(&module_idx) = graph.modules.get(m) else {
635 bail!("inconsistent read?")
638 };
639 let Some((_, path)) = petgraph::algo::astar(
641 &reversed_graph,
642 module_idx,
643 |n| reversed_graph.neighbors(n).next().is_none(),
644 |e| match e.weight().chunking_type {
646 ChunkingType::Parallel { .. } => 0,
648 _ => 1,
649 },
650 |_| 0,
664 ) else {
665 unreachable!("there must be a path to a root");
666 };
667
668 let path = path
672 .into_iter()
673 .map(async |n| {
674 graph
675 .graph
676 .node_weight(n)
677 .unwrap() .module()
679 .ident()
680 .await
681 })
682 .try_join()
683 .await?;
684 Ok(path)
685 })
686 .try_join()
687 .await?,
688 )));
689 }
690}
691
692#[turbo_tasks::value(shared, serialization = "skip", eq = "manual", cell = "new")]
695pub struct ModuleGraph {
696 input_graphs: Vec<OperationVc<SingleModuleGraph>>,
697 input_binding_usage: Option<OperationVc<BindingUsageInfo>>,
698
699 snapshot: ModuleGraphSnapshot,
700}
701
702#[turbo_tasks::value_impl]
703impl ModuleGraph {
704 #[turbo_tasks::function(operation)]
707 pub async fn from_graphs(
708 graphs: Vec<OperationVc<SingleModuleGraph>>,
709 binding_usage: Option<OperationVc<BindingUsageInfo>>,
710 ) -> Result<Vc<Self>> {
711 let graph = Self::from_graphs_inner(graphs, binding_usage)
712 .read_strongly_consistent()
713 .await?;
714 Ok(ReadRef::cell(graph))
715 }
716
717 #[turbo_tasks::function(operation, root)]
718 async fn from_graphs_inner(
719 graphs: Vec<OperationVc<SingleModuleGraph>>,
720 binding_usage: Option<OperationVc<BindingUsageInfo>>,
721 ) -> Result<Vc<ModuleGraph>> {
722 Ok(ModuleGraph {
723 input_graphs: graphs.clone(),
724 input_binding_usage: binding_usage,
725 snapshot: ModuleGraphSnapshot {
726 graphs: graphs.iter().map(|g| g.connect()).try_join().await?,
727 skip_visited_module_children: false,
728 graph_idx_override: None,
729 binding_usage: if let Some(binding_usage) = binding_usage {
730 Some(binding_usage.connect().await?)
731 } else {
732 None
733 },
734 },
735 }
736 .cell())
737 }
738
739 #[turbo_tasks::function]
740 pub async fn chunk_group_info(self: Vc<Self>) -> Result<Vc<ChunkGroupInfo>> {
741 compute_chunk_group_info(&*self.await?).await
742 }
743
744 #[turbo_tasks::function]
745 pub async fn merged_modules(self: Vc<Self>) -> Result<Vc<MergedModuleInfo>> {
746 compute_merged_modules(self).await
747 }
748
749 #[turbo_tasks::function]
750 pub async fn module_batches(
751 self: Vc<Self>,
752 config: Vc<BatchingConfig>,
753 ) -> Result<Vc<ModuleBatchesGraph>> {
754 compute_module_batches(self, &*config.await?).await
755 }
756
757 #[turbo_tasks::function]
758 pub async fn style_groups(
759 self: Vc<Self>,
760 chunking_context: Vc<Box<dyn ChunkingContext>>,
761 config: StyleGroupsConfig,
762 ) -> Result<Vc<StyleGroups>> {
763 compute_style_groups(self, chunking_context, &config).await
764 }
765
766 #[turbo_tasks::function(root)]
767 pub async fn async_module_info(self: Vc<Self>) -> Result<Vc<AsyncModulesInfo>> {
768 async move {
771 let result_op = compute_async_module_info(self.to_resolved().await?);
772 let result_vc = result_op.resolve().strongly_consistent().await?;
773 result_op.drop_collectibles::<Box<dyn Issue>>();
774 anyhow::Ok(*result_vc)
775 }
776 .instrument(tracing::info_span!("compute async module info"))
777 .await
778 }
779
780 #[turbo_tasks::function]
781 pub async fn referenced_async_modules(
782 self: Vc<Self>,
783 module: ResolvedVc<Box<dyn Module>>,
784 ) -> Result<Vc<AsyncModuleInfo>> {
785 let graph_ref = self.await?;
786 let async_module_info = self.async_module_info();
787
788 let entry = graph_ref.get_entry(module)?;
789 let referenced_modules = graph_ref
790 .iter_graphs_neighbors_rev(entry, Direction::Outgoing)
791 .filter(|(edge_idx, _)| {
792 let ty = graph_ref.get_edge(*edge_idx).unwrap();
793 ty.chunking_type.is_inherit_async()
794 })
795 .map(|(_, child_idx)| anyhow::Ok(graph_ref.get_node(child_idx)?.module()))
796 .collect::<Result<Vec<_>>>()?
797 .into_iter()
798 .rev()
799 .map(async |m| Ok(async_module_info.is_async(m).await?.then_some(*m)))
800 .try_flat_join()
801 .await?;
802
803 Ok(AsyncModuleInfo::new(referenced_modules))
804 }
805
806 #[turbo_tasks::function]
808 pub fn iter_graphs(&self) -> Vc<ModuleGraphLayers> {
809 Vc::cell(
810 self.input_graphs
811 .iter()
812 .enumerate()
813 .map(|(graph_idx, graph)| {
814 ModuleGraphLayer::new(*graph, graph_idx as u32, self.input_binding_usage)
815 })
816 .collect(),
817 )
818 }
819}
820
821impl Deref for ModuleGraph {
822 type Target = ModuleGraphSnapshot;
823
824 fn deref(&self) -> &Self::Target {
825 &self.snapshot
826 }
827}
828
829#[turbo_tasks::value(shared, serialization = "skip", eq = "manual", cell = "new")]
830pub struct ModuleGraphLayer {
831 snapshot: ModuleGraphSnapshot,
832}
833
834#[turbo_tasks::value_impl]
835impl ModuleGraphLayer {
836 #[turbo_tasks::function(operation, root)]
837 async fn new(
838 graph: OperationVc<SingleModuleGraph>,
839 graph_idx: u32,
840 binding_usage: Option<OperationVc<BindingUsageInfo>>,
841 ) -> Result<Vc<Self>> {
842 Ok(Self {
843 snapshot: ModuleGraphSnapshot {
844 graphs: vec![graph.connect().await?],
845 skip_visited_module_children: true,
846 graph_idx_override: Some(graph_idx),
847 binding_usage: if let Some(binding_usage) = binding_usage {
848 Some(binding_usage.connect().await?)
849 } else {
850 None
851 },
852 },
853 }
854 .cell())
855 }
856}
857
858impl Deref for ModuleGraphLayer {
859 type Target = ModuleGraphSnapshot;
860
861 fn deref(&self) -> &Self::Target {
862 &self.snapshot
863 }
864}
865
866#[turbo_tasks::value(transparent)]
867pub struct ModuleGraphLayers(Vec<OperationVc<ModuleGraphLayer>>);
868
869#[derive(TraceRawVcs, ValueDebugFormat, NonLocalValue)]
870pub struct ModuleGraphSnapshot {
871 pub graphs: Vec<ReadRef<SingleModuleGraph>>,
872 skip_visited_module_children: bool,
875
876 pub graph_idx_override: Option<u32>,
877
878 pub binding_usage: Option<ReadRef<BindingUsageInfo>>,
879}
880
881impl ModuleGraphSnapshot {
882 fn get_entry(&self, entry: ResolvedVc<Box<dyn Module>>) -> Result<GraphNodeIndex> {
883 if self.graph_idx_override.is_some() {
884 debug_assert_eq!(self.graphs.len(), 1,);
885 }
886
887 let Some(idx) = self
888 .graphs
889 .iter()
890 .enumerate()
891 .find_map(|(graph_idx, graph)| {
892 graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
893 graph_idx: self.graph_idx_override.unwrap_or(graph_idx as u32),
894 node_idx: *node_idx,
895 })
896 })
897 else {
898 bail!("Couldn't find entry module {entry:?} in module graph");
899 };
900 Ok(idx)
901 }
902
903 pub fn entries(&self) -> impl Iterator<Item = ChunkGroupEntry> + '_ {
904 self.graphs.iter().flat_map(|g| g.entries.iter().cloned())
905 }
906
907 fn get_graph(&self, graph_idx: u32) -> &ReadRef<SingleModuleGraph> {
908 if self.graph_idx_override.is_some() {
909 self.graphs.first().unwrap()
910 } else {
911 &self.graphs[graph_idx as usize]
912 }
913 }
914
915 fn get_node(&self, node: GraphNodeIndex) -> Result<&SingleModuleGraphNode> {
916 let graph = self.get_graph(node.graph_idx);
917 graph
918 .graph
919 .node_weight(node.node_idx)
920 .context("Expected graph node")
921 }
922
923 fn get_edge(&self, edge: GraphEdgeIndex) -> Result<&RefData> {
924 let graph = self.get_graph(edge.graph_idx);
925 graph
926 .graph
927 .edge_weight(edge.edge_idx)
928 .context("Expected graph node")
929 }
930
931 fn should_visit_node(&self, node: &SingleModuleGraphNode, direction: Direction) -> bool {
932 if self.skip_visited_module_children && direction == Direction::Outgoing {
933 !matches!(node, SingleModuleGraphNode::VisitedModule { .. })
934 } else {
935 true
936 }
937 }
938
939 pub fn enumerate_nodes(
942 &self,
943 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
944 self.graphs.iter().flat_map(|g| g.enumerate_nodes())
945 }
946
947 pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
950 self.graphs.iter().flat_map(|g| g.iter_nodes())
951 }
952
953 fn iter_graphs_neighbors_rev<'a>(
955 &'a self,
956 node: GraphNodeIndex,
957 direction: Direction,
958 ) -> impl Iterator<Item = (GraphEdgeIndex, GraphNodeIndex)> + 'a {
959 let graph = &*self.get_graph(node.graph_idx).graph;
960
961 if cfg!(debug_assertions) && direction == Direction::Outgoing {
962 let node_weight = graph.node_weight(node.node_idx).unwrap();
963 if let SingleModuleGraphNode::VisitedModule { .. } = node_weight {
964 panic!("iter_graphs_neighbors_rev called on VisitedModule node");
965 }
966 }
967
968 let mut walker = graph.neighbors_directed(node.node_idx, direction).detach();
969 std::iter::from_fn(move || {
970 while let Some((edge_idx, succ_idx)) = walker.next(graph) {
971 let edge_idx = GraphEdgeIndex::new(node.graph_idx, edge_idx);
972 if self
973 .binding_usage
974 .as_ref()
975 .is_some_and(|binding_usage| binding_usage.is_reference_unused_edge(&edge_idx))
976 {
977 continue;
979 }
980
981 return Some((edge_idx, GraphNodeIndex::new(node.graph_idx, succ_idx)));
982 }
983 None
984 })
985 }
986
987 pub async fn get_ids(&self) -> Result<FxHashMap<ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>>> {
990 Ok(self
991 .iter_nodes()
992 .map(async |n| Ok((n, n.ident().to_string().await?)))
993 .try_join()
994 .await?
995 .into_iter()
996 .collect::<FxHashMap<_, _>>())
997 }
998
999 pub fn traverse_nodes_dfs<S>(
1008 &self,
1009 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1010 state: &mut S,
1011 visit_preorder: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<GraphTraversalAction>,
1012 mut visit_postorder: impl FnMut(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<()>,
1013 ) -> Result<()> {
1014 let entries = entries.into_iter().collect::<Vec<_>>();
1015
1016 enum Pass {
1017 Visit,
1018 ExpandAndVisit,
1019 }
1020 let mut stack: Vec<(Pass, GraphNodeIndex)> = Vec::with_capacity(entries.len());
1021 for entry in entries.into_iter().rev() {
1022 stack.push((Pass::ExpandAndVisit, self.get_entry(entry)?));
1023 }
1024 let mut expanded = FxHashSet::default();
1025 while let Some((pass, current)) = stack.pop() {
1026 let current_node = self.get_node(current)?;
1027 match pass {
1028 Pass::Visit => {
1029 visit_postorder(current_node.module(), state)?;
1030 }
1031 Pass::ExpandAndVisit => {
1032 if !expanded.insert(current) {
1033 continue;
1034 }
1035 let action = visit_preorder(current_node.module(), state)?;
1036 if action == GraphTraversalAction::Exclude {
1037 continue;
1038 }
1039 stack.push((Pass::Visit, current));
1040 if action == GraphTraversalAction::Continue
1041 && self.should_visit_node(current_node, Direction::Outgoing)
1042 {
1043 let current = current_node
1044 .target_idx(Direction::Outgoing)
1045 .unwrap_or(current);
1046 stack.extend(
1047 self.iter_graphs_neighbors_rev(current, Direction::Outgoing)
1048 .map(|(_, child)| (Pass::ExpandAndVisit, child)),
1049 );
1050 }
1051 }
1052 }
1053 }
1054
1055 Ok(())
1056 }
1057
1058 pub fn traverse_edges_bfs(
1069 &self,
1070 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1071 mut visitor: impl FnMut(
1072 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1073 ResolvedVc<Box<dyn Module>>,
1074 ) -> Result<GraphTraversalAction>,
1075 ) -> Result<()> {
1076 let mut queue = VecDeque::from(
1077 entries
1078 .into_iter()
1079 .map(|e| self.get_entry(e))
1080 .collect::<Result<Vec<_>>>()?,
1081 );
1082 let mut visited = FxHashSet::default();
1083 for entry_node in &queue {
1084 visitor(None, self.get_node(*entry_node)?.module())?;
1085 }
1086 while let Some(node) = queue.pop_front() {
1087 if visited.insert(node) {
1088 let node_weight = self.get_node(node)?;
1089 for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing) {
1090 let succ_weight = self.get_node(succ)?;
1091 let action = visitor(
1092 Some((node_weight.module(), self.get_edge(edge)?)),
1093 succ_weight.module(),
1094 )?;
1095 if !self.should_visit_node(succ_weight, Direction::Outgoing) {
1096 continue;
1097 }
1098 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1099 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1100 queue.push_back(succ);
1101 }
1102 }
1103 }
1104 }
1105
1106 Ok(())
1107 }
1108
1109 pub fn traverse_edges_unordered(
1118 &self,
1119 mut visitor: impl FnMut(
1120 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1121 ResolvedVc<Box<dyn Module>>,
1122 ) -> Result<()>,
1123 ) -> Result<()> {
1124 let entries = self.graphs.iter().flat_map(|g| g.entry_modules());
1125
1126 self.traverse_edges_dfs(
1129 entries,
1130 &mut (),
1131 |parent, target, _| {
1132 visitor(parent, target)?;
1133 Ok(GraphTraversalAction::Continue)
1134 },
1135 |_, _, _| Ok(()),
1136 )
1137 }
1138
1139 pub fn traverse_edges_dfs<S>(
1158 &self,
1159 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1160 state: &mut S,
1161 visit_preorder: impl FnMut(
1162 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1163 ResolvedVc<Box<dyn Module>>,
1164 &mut S,
1165 ) -> Result<GraphTraversalAction>,
1166 visit_postorder: impl FnMut(
1167 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1168 ResolvedVc<Box<dyn Module>>,
1169 &mut S,
1170 ) -> Result<()>,
1171 ) -> Result<()> {
1172 self.traverse_edges_dfs_impl::<S>(
1173 entries,
1174 state,
1175 visit_preorder,
1176 visit_postorder,
1177 Direction::Outgoing,
1178 )
1179 }
1180
1181 pub fn traverse_edges_reverse_dfs<S>(
1198 &self,
1199 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1200 state: &mut S,
1201 visit_preorder: impl FnMut(
1202 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1203 ResolvedVc<Box<dyn Module>>,
1204 &mut S,
1205 ) -> Result<GraphTraversalAction>,
1206 visit_postorder: impl FnMut(
1207 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1208 ResolvedVc<Box<dyn Module>>,
1209 &mut S,
1210 ) -> Result<()>,
1211 ) -> Result<()> {
1212 self.traverse_edges_dfs_impl::<S>(
1213 entries,
1214 state,
1215 visit_preorder,
1216 visit_postorder,
1217 Direction::Incoming,
1218 )
1219 }
1220
1221 fn traverse_edges_dfs_impl<S>(
1222 &self,
1223 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1224 state: &mut S,
1225 mut visit_preorder: impl FnMut(
1226 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1227 ResolvedVc<Box<dyn Module>>,
1228 &mut S,
1229 ) -> Result<GraphTraversalAction>,
1230 mut visit_postorder: impl FnMut(
1231 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1232 ResolvedVc<Box<dyn Module>>,
1233 &mut S,
1234 ) -> Result<()>,
1235 direction: Direction,
1236 ) -> Result<()> {
1237 if direction == Direction::Incoming {
1238 debug_assert!(
1239 self.skip_visited_module_children,
1240 "Can only trace reverse edges in a single layer graph. We do not model cross \
1241 graph reverse edges"
1242 );
1243 }
1244 let entries = entries.into_iter().collect::<Vec<_>>();
1245
1246 enum Pass {
1247 Visit,
1248 ExpandAndVisit,
1249 }
1250 #[allow(clippy::type_complexity)] let mut stack: Vec<(
1252 Pass,
1253 Option<(GraphNodeIndex, GraphEdgeIndex)>,
1254 GraphNodeIndex,
1255 )> = Vec::with_capacity(entries.len());
1256 for entry in entries.into_iter().rev() {
1257 stack.push((Pass::ExpandAndVisit, None, self.get_entry(entry)?));
1258 }
1259 let mut expanded = FxHashSet::default();
1260 while let Some((pass, parent, current)) = stack.pop() {
1261 let parent_arg = match parent {
1262 Some((parent_node, parent_edge)) => Some((
1263 self.get_node(parent_node)?.module(),
1264 self.get_edge(parent_edge)?,
1265 )),
1266 None => None,
1267 };
1268 let current_node = self.get_node(current)?;
1269 match pass {
1270 Pass::Visit => {
1271 visit_postorder(parent_arg, current_node.module(), state)?;
1272 }
1273 Pass::ExpandAndVisit => {
1274 let action = visit_preorder(parent_arg, current_node.module(), state)?;
1275 if action == GraphTraversalAction::Exclude {
1276 continue;
1277 }
1278 stack.push((Pass::Visit, parent, current));
1279 if action == GraphTraversalAction::Continue
1280 && expanded.insert(current)
1281 && self.should_visit_node(current_node, direction)
1282 {
1283 let current = current_node.target_idx(direction).unwrap_or(current);
1284 stack.extend(self.iter_graphs_neighbors_rev(current, direction).map(
1285 |(edge, child)| (Pass::ExpandAndVisit, Some((current, edge)), child),
1286 ));
1287 }
1288 }
1289 }
1290 }
1291
1292 Ok(())
1293 }
1294
1295 pub fn traverse_cycles(
1299 &self,
1300 edge_filter: impl Fn(&RefData) -> bool,
1301 mut visit_cycle: impl FnMut(&[&ResolvedVc<Box<dyn Module>>]) -> Result<()>,
1302 ) -> Result<()> {
1303 for (graph_idx, graph) in self.graphs.iter().enumerate() {
1304 graph.traverse_cycles(
1305 &edge_filter,
1306 &mut visit_cycle,
1307 graph_idx as u32,
1308 &self.binding_usage,
1309 )?;
1310 }
1311 Ok(())
1312 }
1313
1314 pub fn traverse_edges_fixed_point_with_priority<S, P: Ord>(
1336 &self,
1337 entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1338 state: &mut S,
1339 mut visit: impl FnMut(
1340 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData, GraphEdgeIndex)>,
1341 ResolvedVc<Box<dyn Module>>,
1342 &mut S,
1343 ) -> Result<GraphTraversalAction>,
1344 priority: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<P>,
1345 ) -> Result<usize> {
1346 if self.skip_visited_module_children {
1347 panic!(
1348 "traverse_edges_fixed_point_with_priority musn't be called on individual graphs"
1349 );
1350 }
1351
1352 let mut visit_order = 0usize;
1353 let mut order = || {
1354 let order = visit_order;
1355 visit_order += 1;
1356 order
1357 };
1358 #[derive(PartialEq, Eq)]
1359 struct NodeWithPriority<T: Ord> {
1360 node: GraphNodeIndex,
1361 priority: T,
1362 visit_order: usize,
1363 }
1364 impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1365 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1366 Some(self.cmp(other))
1367 }
1368 }
1369 impl<T: Ord> Ord for NodeWithPriority<T> {
1370 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1371 self.priority
1374 .cmp(&other.priority)
1375 .then(self.visit_order.cmp(&other.visit_order))
1378 }
1379 }
1380
1381 let mut queue_set = FxHashSet::default();
1382 let mut queue = BinaryHeap::from_iter(
1383 entries
1384 .into_iter()
1385 .map(|(m, priority)| {
1386 Ok(NodeWithPriority {
1387 node: self.get_entry(m)?,
1388 priority,
1389 visit_order: order(),
1390 })
1391 })
1392 .collect::<Result<Vec<_>>>()?,
1393 );
1394
1395 for entry_node in &queue {
1396 visit(None, self.get_node(entry_node.node)?.module(), state)?;
1397 }
1398
1399 let mut visit_count = 0usize;
1400 while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1401 queue_set.remove(&node);
1402 let node_weight = self.get_node(node)?;
1403 let node = node_weight.target_idx(Direction::Outgoing).unwrap_or(node);
1404
1405 visit_count += 1;
1406
1407 for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing) {
1408 let succ_weight = self.get_node(succ)?;
1409
1410 let action = visit(
1411 Some((node_weight.module(), self.get_edge(edge)?, edge)),
1412 succ_weight.module(),
1413 state,
1414 )?;
1415
1416 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1417 if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1418 queue.push(NodeWithPriority {
1419 node: succ,
1420 priority: priority(succ_weight.module(), state)?,
1421 visit_order: order(),
1422 });
1423 }
1424 }
1425 }
1426
1427 Ok(visit_count)
1428 }
1429
1430 pub fn iter_reachable_modules(
1432 &self,
1433 ) -> Result<impl Iterator<Item = ResolvedVc<Box<dyn Module>>>> {
1434 Ok(self.iter_reachable_nodes()?.filter_map(|n| match n {
1435 SingleModuleGraphNode::Module(m) => Some(*m),
1436 SingleModuleGraphNode::VisitedModule { .. } => None,
1437 }))
1438 }
1439
1440 pub fn iter_reachable_nodes<'a>(
1443 &'a self,
1444 ) -> Result<impl Iterator<Item = &'a SingleModuleGraphNode> + 'a> {
1445 ModuleGraphSnapshotNodeIterator::new(self)
1446 }
1447}
1448
1449struct ModuleGraphSnapshotNodeIterator<'a> {
1450 graph: &'a ModuleGraphSnapshot,
1451 visited: FxHashSet<GraphNodeIndex>,
1452 visit_queue: VecDeque<GraphNodeIndex>,
1453}
1454
1455impl<'a> ModuleGraphSnapshotNodeIterator<'a> {
1456 fn new(graph: &'a ModuleGraphSnapshot) -> Result<Self> {
1457 let entries = graph
1458 .graphs
1459 .iter()
1460 .flat_map(|g| g.entry_modules())
1461 .map(|e| graph.get_entry(e))
1462 .collect::<Result<VecDeque<_>>>()?;
1463
1464 Ok(Self {
1465 graph,
1466 visited: FxHashSet::default(),
1467 visit_queue: entries,
1468 })
1469 }
1470}
1471impl<'a> Iterator for ModuleGraphSnapshotNodeIterator<'a> {
1472 type Item = &'a SingleModuleGraphNode;
1473
1474 fn next(&mut self) -> Option<Self::Item> {
1475 while let Some(node_idx) = self.visit_queue.pop_front() {
1476 if self.visited.insert(node_idx) {
1477 let node_weight = self.graph.get_node(node_idx).unwrap();
1478 if self
1479 .graph
1480 .should_visit_node(node_weight, Direction::Outgoing)
1481 {
1482 let node = node_weight
1483 .target_idx(Direction::Outgoing)
1484 .unwrap_or(node_idx);
1485 self.visit_queue.extend(
1486 self.graph
1487 .iter_graphs_neighbors_rev(node, Direction::Outgoing)
1488 .map(|(_, succ)| succ)
1489 .filter(|succ| !self.visited.contains(succ)),
1490 );
1491 }
1492 return Some(node_weight);
1493 }
1494 }
1495 None
1496 }
1497}
1498impl FusedIterator for ModuleGraphSnapshotNodeIterator<'_> {}
1499
1500#[turbo_tasks::value_impl]
1501impl SingleModuleGraph {
1502 #[turbo_tasks::function(operation)]
1503 pub async fn new_with_entry(
1504 entry: ChunkGroupEntry,
1505 include_traced: bool,
1506 include_binding_usage: bool,
1507 ) -> Result<Vc<Self>> {
1508 SingleModuleGraph::new_inner(
1509 vec![entry],
1510 &Default::default(),
1511 include_traced,
1512 include_binding_usage,
1513 )
1514 .await
1515 }
1516
1517 #[turbo_tasks::function(operation)]
1518 pub async fn new_with_entries(
1519 entries: ResolvedVc<GraphEntries>,
1520 include_traced: bool,
1521 include_binding_usage: bool,
1522 ) -> Result<Vc<Self>> {
1523 SingleModuleGraph::new_inner(
1524 entries.owned().await?,
1525 &Default::default(),
1526 include_traced,
1527 include_binding_usage,
1528 )
1529 .await
1530 }
1531
1532 #[turbo_tasks::function(operation)]
1533 pub async fn new_with_entries_visited(
1534 entries: ResolvedVc<GraphEntries>,
1535 visited_modules: OperationVc<VisitedModules>,
1536 include_traced: bool,
1537 include_binding_usage: bool,
1538 ) -> Result<Vc<Self>> {
1539 SingleModuleGraph::new_inner(
1540 entries.owned().await?,
1541 &visited_modules.connect().await?.modules,
1542 include_traced,
1543 include_binding_usage,
1544 )
1545 .await
1546 }
1547
1548 #[turbo_tasks::function(operation)]
1549 pub async fn new_with_entries_visited_intern(
1550 entries: GraphEntriesT,
1552 visited_modules: OperationVc<VisitedModules>,
1553 include_traced: bool,
1554 include_binding_usage: bool,
1555 ) -> Result<Vc<Self>> {
1556 SingleModuleGraph::new_inner(
1557 entries,
1558 &visited_modules.connect().await?.modules,
1559 include_traced,
1560 include_binding_usage,
1561 )
1562 .await
1563 }
1564
1565 #[turbo_tasks::function]
1566 pub async fn module_count(&self) -> Vc<u64> {
1567 Vc::cell(self.number_of_modules as u64)
1568 }
1569
1570 #[turbo_tasks::function]
1571 pub async fn edge_count(&self) -> Vc<u64> {
1572 Vc::cell(self.graph.edge_count() as u64)
1573 }
1574}
1575
1576#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1577pub enum SingleModuleGraphNode {
1578 Module(ResolvedVc<Box<dyn Module>>),
1579 VisitedModule {
1581 idx: GraphNodeIndex,
1582 module: ResolvedVc<Box<dyn Module>>,
1583 },
1584}
1585
1586impl SingleModuleGraphNode {
1587 pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1588 match self {
1589 SingleModuleGraphNode::Module(module) => *module,
1590 SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1591 }
1592 }
1593 pub fn target_idx(&self, direction: Direction) -> Option<GraphNodeIndex> {
1594 match self {
1595 SingleModuleGraphNode::VisitedModule { idx, .. } => match direction {
1596 Direction::Outgoing => Some(*idx),
1597 Direction::Incoming => None,
1598 },
1599 SingleModuleGraphNode::Module(_) => None,
1600 }
1601 }
1602}
1603
1604#[derive(PartialEq, Eq, Debug)]
1605pub enum GraphTraversalAction {
1606 Continue,
1608 Skip,
1610 Exclude,
1612}
1613
1614#[derive(Clone, Hash, PartialEq, Eq)]
1617enum SingleModuleGraphBuilderNode {
1618 Module {
1620 module: ResolvedVc<Box<dyn Module>>,
1621 ident: Option<ReadRef<RcStr>>,
1623 },
1624 VisitedModule {
1626 module: ResolvedVc<Box<dyn Module>>,
1627 idx: GraphNodeIndex,
1628 },
1629}
1630
1631impl SingleModuleGraphBuilderNode {
1632 async fn new_module(emit_spans: bool, module: ResolvedVc<Box<dyn Module>>) -> Result<Self> {
1633 Ok(Self::Module {
1634 module,
1635 ident: if emit_spans {
1636 Some(module.ident_string().untracked().await?)
1638 } else {
1639 None
1640 },
1641 })
1642 }
1643 fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1644 Self::VisitedModule { module, idx }
1645 }
1646}
1647
1648struct SingleModuleGraphBuilder<'a> {
1649 visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1650
1651 emit_spans: bool,
1652
1653 include_traced: bool,
1655
1656 include_binding_usage: bool,
1658}
1659impl Visit<SingleModuleGraphBuilderNode, RefData> for SingleModuleGraphBuilder<'_> {
1660 type EdgesIntoIter = Vec<(SingleModuleGraphBuilderNode, RefData)>;
1661 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1662
1663 fn visit(
1664 &mut self,
1665 node: &SingleModuleGraphBuilderNode,
1666 edge: Option<&RefData>,
1667 ) -> VisitControlFlow {
1668 if let Some(edge) = edge
1669 && matches!(edge.chunking_type, ChunkingType::Traced)
1670 {
1671 return VisitControlFlow::Skip;
1673 }
1674 match node {
1675 SingleModuleGraphBuilderNode::Module { .. } => VisitControlFlow::Continue,
1676 SingleModuleGraphBuilderNode::VisitedModule { .. } => VisitControlFlow::Skip,
1678 }
1679 }
1680
1681 fn edges(
1682 &mut self,
1683 node: &SingleModuleGraphBuilderNode,
1686 ) -> Self::EdgesFuture {
1687 let &SingleModuleGraphBuilderNode::Module { module, .. } = node else {
1689 unreachable!()
1691 };
1692 let visited_modules = self.visited_modules;
1693 let emit_spans = self.emit_spans;
1694 let include_traced = self.include_traced;
1695 let include_binding_usage = self.include_binding_usage;
1696 async move {
1697 let refs_cell = primary_chunkable_referenced_modules(
1698 *module,
1699 include_traced,
1700 include_binding_usage,
1701 );
1702 let refs = match refs_cell.await {
1703 Ok(refs) => refs,
1704 Err(e) => {
1705 return Err(e.context(module.ident().to_string().await?));
1706 }
1707 };
1708
1709 refs.iter()
1710 .flat_map(|(reference, resolved)| {
1711 resolved.modules.iter().map(|m| {
1712 (
1713 *reference,
1714 resolved.chunking_type.clone(),
1715 resolved.binding_usage.clone(),
1716 *m,
1717 )
1718 })
1719 })
1720 .map(async |(reference, ty, binding_usage, target)| {
1721 let to = if let Some(idx) = visited_modules.get(&target) {
1722 SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1723 } else {
1724 SingleModuleGraphBuilderNode::new_module(emit_spans, target).await?
1725 };
1726 Ok((
1727 to,
1728 RefData {
1729 chunking_type: ty,
1730 binding_usage,
1731 reference,
1732 },
1733 ))
1734 })
1735 .try_join()
1736 .await
1737 }
1738 }
1739
1740 fn span(
1741 &mut self,
1742 node: &SingleModuleGraphBuilderNode,
1743 edge: Option<&RefData>,
1744 ) -> tracing::Span {
1745 if !self.emit_spans {
1746 return Span::none();
1747 }
1748
1749 let mut span = match node {
1750 SingleModuleGraphBuilderNode::Module {
1751 ident: Some(ident), ..
1752 } => {
1753 tracing::info_span!("module", name = display(ident))
1754 }
1755 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1756 tracing::info_span!("visited module")
1757 }
1758 _ => unreachable!(),
1759 };
1760
1761 if let Some(edge) = edge {
1762 match &edge.chunking_type {
1763 ChunkingType::Parallel {
1764 inherit_async: _,
1765 hoisted: _,
1766 } => {}
1767 ChunkingType::Traced => {
1768 let _span = span.entered();
1769 span = tracing::info_span!("traced reference");
1770 }
1771 ChunkingType::Async => {
1772 let _span = span.entered();
1773 span = tracing::info_span!("async reference");
1774 }
1775 ChunkingType::Isolated { _ty: ty, merge_tag } => {
1776 let _span = span.entered();
1777 span = tracing::info_span!(
1778 "isolated reference",
1779 ty = debug(&ty),
1780 merge_tag = debug(&merge_tag)
1781 );
1782 }
1783 ChunkingType::Shared {
1784 inherit_async: _,
1785 merge_tag,
1786 } => {
1787 let _span = span.entered();
1788 span = tracing::info_span!("shared reference", merge_tag = debug(&merge_tag));
1789 }
1790 };
1791 }
1792
1793 span
1794 }
1795}
1796
1797#[cfg(test)]
1798pub mod tests {
1799 use anyhow::Result;
1800 use rustc_hash::FxHashMap;
1801 use turbo_rcstr::{RcStr, rcstr};
1802 use turbo_tasks::{ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc};
1803 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
1804 use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
1805
1806 use super::*;
1807 use crate::{
1808 asset::{Asset, AssetContent},
1809 ident::AssetIdent,
1810 module::{Module, ModuleSideEffects},
1811 reference::{ModuleReference, ModuleReferences, SingleChunkableModuleReference},
1812 resolve::ExportUsage,
1813 };
1814
1815 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1816 async fn test_traverse_dfs_from_entries_diamond() {
1817 run_graph_test(
1818 vec![rcstr!("a.js")],
1819 {
1820 let mut deps = FxHashMap::default();
1821 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1823 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
1824 deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
1825 deps
1826 },
1827 |graph, entry_modules, module_to_name| {
1828 let mut preorder_visits = Vec::new();
1829 let mut postorder_visits = Vec::new();
1830
1831 graph.traverse_edges_dfs(
1832 entry_modules,
1833 &mut (),
1834 |parent, target, _| {
1835 preorder_visits.push((
1836 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1837 module_to_name.get(&target).unwrap().clone(),
1838 ));
1839 Ok(GraphTraversalAction::Continue)
1840 },
1841 |parent, target, _| {
1842 postorder_visits.push((
1843 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1844 module_to_name.get(&target).unwrap().clone(),
1845 ));
1846 Ok(())
1847 },
1848 )?;
1849 assert_eq!(
1850 vec![
1851 (None, rcstr!("a.js")),
1852 (Some(rcstr!("a.js")), rcstr!("b.js")),
1853 (Some(rcstr!("b.js")), rcstr!("d.js")),
1854 (Some(rcstr!("a.js")), rcstr!("c.js")),
1855 (Some(rcstr!("c.js")), rcstr!("d.js"))
1856 ],
1857 preorder_visits
1858 );
1859 assert_eq!(
1860 vec![
1861 (Some(rcstr!("b.js")), rcstr!("d.js")),
1862 (Some(rcstr!("a.js")), rcstr!("b.js")),
1863 (Some(rcstr!("c.js")), rcstr!("d.js")),
1864 (Some(rcstr!("a.js")), rcstr!("c.js")),
1865 (None, rcstr!("a.js"))
1866 ],
1867 postorder_visits
1868 );
1869 Ok(())
1870 },
1871 )
1872 .await;
1873 }
1874
1875 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1876 async fn test_traverse_dfs_from_entries_cycle() {
1877 run_graph_test(
1878 vec![rcstr!("a.js")],
1879 {
1880 let mut deps = FxHashMap::default();
1881 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1883 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1884 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1885 deps
1886 },
1887 |graph, entry_modules, module_to_name| {
1888 let mut preorder_visits = Vec::new();
1889 let mut postorder_visits = Vec::new();
1890
1891 graph.traverse_edges_dfs(
1892 entry_modules,
1893 &mut (),
1894 |parent, target, _| {
1895 preorder_visits.push((
1896 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1897 module_to_name.get(&target).unwrap().clone(),
1898 ));
1899 Ok(GraphTraversalAction::Continue)
1900 },
1901 |parent, target, _| {
1902 postorder_visits.push((
1903 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1904 module_to_name.get(&target).unwrap().clone(),
1905 ));
1906 Ok(())
1907 },
1908 )?;
1909 assert_eq!(
1910 vec![
1911 (None, rcstr!("a.js")),
1912 (Some(rcstr!("a.js")), rcstr!("b.js")),
1913 (Some(rcstr!("b.js")), rcstr!("c.js")),
1914 (Some(rcstr!("c.js")), rcstr!("a.js")),
1915 ],
1916 preorder_visits
1917 );
1918 assert_eq!(
1919 vec![
1920 (Some(rcstr!("c.js")), rcstr!("a.js")),
1921 (Some(rcstr!("b.js")), rcstr!("c.js")),
1922 (Some(rcstr!("a.js")), rcstr!("b.js")),
1923 (None, rcstr!("a.js"))
1924 ],
1925 postorder_visits
1926 );
1927 Ok(())
1928 },
1929 )
1930 .await;
1931 }
1932
1933 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1934 async fn test_traverse_edges_fixed_point_with_priority_cycle() {
1935 run_graph_test(
1936 vec![rcstr!("a.js")],
1937 {
1938 let mut deps = FxHashMap::default();
1939 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1941 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1942 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1943 deps
1944 },
1945 |graph, entry_modules, module_to_name| {
1946 let mut visits = Vec::new();
1947 let mut count = 0;
1948
1949 graph.traverse_edges_fixed_point_with_priority(
1950 entry_modules.into_iter().map(|m| (m, 0)),
1951 &mut (),
1952 |parent, target, _| {
1953 visits.push((
1954 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
1955 module_to_name.get(&target).unwrap().clone(),
1956 ));
1957 count += 1;
1958
1959 Ok(if count < 6 {
1961 GraphTraversalAction::Continue
1962 } else {
1963 GraphTraversalAction::Skip
1964 })
1965 },
1966 |_, _| Ok(0),
1967 )?;
1968 assert_eq!(
1969 vec![
1970 (None, rcstr!("a.js")),
1971 (Some(rcstr!("a.js")), rcstr!("b.js")),
1972 (Some(rcstr!("b.js")), rcstr!("c.js")),
1973 (Some(rcstr!("c.js")), rcstr!("a.js")),
1974 (Some(rcstr!("a.js")), rcstr!("b.js")),
1976 (Some(rcstr!("b.js")), rcstr!("c.js")),
1977 ],
1978 visits
1979 );
1980
1981 Ok(())
1982 },
1983 )
1984 .await;
1985 }
1986
1987 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1988 async fn test_traverse_edges_fixed_point_no_priority_is_bfs() {
1989 run_graph_test(
1990 vec![rcstr!("a.js")],
1991 {
1992 let mut deps = FxHashMap::default();
1993 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1998 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js"), rcstr!("e.js")]);
1999 deps.insert(rcstr!("c.js"), vec![rcstr!("e.js"), rcstr!("f.js")]);
2000 deps
2001 },
2002 |graph, entry_modules, module_to_name| {
2003 let mut visits = Vec::new();
2004 let mut count = 0;
2005
2006 graph.traverse_edges_fixed_point_with_priority(
2007 entry_modules.into_iter().map(|m| (m, 0)),
2008 &mut (),
2009 |parent, target, _| {
2010 visits.push((
2011 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
2012 module_to_name.get(&target).unwrap().clone(),
2013 ));
2014 count += 1;
2015
2016 Ok(if count < 6 {
2018 GraphTraversalAction::Continue
2019 } else {
2020 GraphTraversalAction::Skip
2021 })
2022 },
2023 |_, _| Ok(0),
2024 )?;
2025
2026 assert_eq!(
2027 vec![
2028 (None, rcstr!("a.js")),
2029 (Some(rcstr!("a.js")), rcstr!("c.js")),
2030 (Some(rcstr!("a.js")), rcstr!("b.js")),
2031 (Some(rcstr!("b.js")), rcstr!("e.js")),
2032 (Some(rcstr!("b.js")), rcstr!("d.js")),
2033 (Some(rcstr!("c.js")), rcstr!("f.js")),
2034 (Some(rcstr!("c.js")), rcstr!("e.js")),
2035 ],
2036 visits
2037 );
2038
2039 Ok(())
2040 },
2041 )
2042 .await;
2043 }
2044
2045 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2046 async fn test_traverse_cycles() {
2047 run_graph_test(
2048 vec![rcstr!("a.js")],
2049 {
2050 let mut deps = FxHashMap::default();
2051 deps.insert(
2058 rcstr!("a.js"),
2059 vec![rcstr!("i.js"), rcstr!("s.js"), rcstr!("x.js")],
2060 );
2061 deps.insert(rcstr!("i.js"), vec![rcstr!("j.js")]);
2062 deps.insert(rcstr!("j.js"), vec![rcstr!("k.js")]);
2063 deps.insert(rcstr!("k.js"), vec![rcstr!("i.js")]);
2064 deps.insert(rcstr!("s.js"), vec![rcstr!("s.js")]);
2065 deps
2066 },
2067 |graph, _, module_to_name| {
2068 let mut cycles = vec![];
2069
2070 graph.traverse_cycles(
2071 |_| true,
2072 |cycle| {
2073 cycles.push(
2074 cycle
2075 .iter()
2076 .map(|n| module_to_name.get(*n).unwrap().clone())
2077 .collect::<Vec<_>>(),
2078 );
2079 Ok(())
2080 },
2081 )?;
2082
2083 assert_eq!(
2084 cycles,
2085 vec![
2086 vec![rcstr!("k.js"), rcstr!("j.js"), rcstr!("i.js")],
2087 vec![rcstr!("s.js")]
2088 ],
2089 );
2090
2091 Ok(())
2092 },
2093 )
2094 .await;
2095 }
2096
2097 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2098 async fn test_reverse_edges_through_layered_graph() {
2099 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2100 BackendOptions::default(),
2101 noop_backing_storage(),
2102 ));
2103 tt.run_once(async move {
2104 #[turbo_tasks::value]
2105 struct ReverseTraversalResults {
2106 forward: Vec<RcStr>,
2107 reverse_from_d: Vec<RcStr>,
2108 reverse_from_b: Vec<RcStr>,
2109 }
2110
2111 #[turbo_tasks::function(operation, root)]
2112 async fn reverse_traversal_results_operation() -> Result<Vc<ReverseTraversalResults>> {
2113 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2114 let root = fs.root().await?;
2115
2116 let graph = {
2119 let mut deps = FxHashMap::default();
2120
2121 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("d.js")]);
2122 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2123 deps
2124 };
2125 let repo = TestRepo {
2126 repo: graph
2127 .iter()
2128 .map(|(k, v)| {
2129 (
2130 root.join(k).unwrap(),
2131 v.iter().map(|f| root.join(f).unwrap()).collect(),
2132 )
2133 })
2134 .collect(),
2135 }
2136 .cell();
2137 let make_module = |name| {
2138 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2139 .to_resolved()
2140 };
2141 let a_module = make_module("a.js").await?;
2142 let b_module = make_module("b.js").await?;
2143
2144 let parent_graph = SingleModuleGraph::new_with_entries(
2145 ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![b_module])]),
2146 false,
2147 false,
2148 );
2149
2150 let module_graph = ModuleGraph::from_graphs(
2151 vec![
2152 parent_graph,
2153 SingleModuleGraph::new_with_entries_visited(
2154 ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![a_module])]),
2155 VisitedModules::from_graph(parent_graph),
2156 false,
2157 false,
2158 ),
2159 ],
2160 None,
2161 )
2162 .connect();
2163 let child_graph = module_graph
2164 .iter_graphs()
2165 .await?
2166 .get(1)
2167 .unwrap()
2168 .connect()
2169 .await?;
2170
2171 let mut visited_forward = Vec::new();
2173 child_graph.traverse_edges_dfs(
2174 vec![a_module],
2175 &mut (),
2176 |_parent, child, _state_| {
2177 visited_forward.push(child);
2178 Ok(GraphTraversalAction::Continue)
2179 },
2180 |_, _, _| Ok(()),
2181 )?;
2182 let forward = visited_forward
2183 .iter()
2184 .map(|m| m.ident().to_string().owned())
2185 .try_join()
2186 .await?;
2187
2188 let d_module = child_graph
2190 .enumerate_nodes()
2191 .map(|(_index, module)| async move {
2192 Ok(match module {
2193 crate::module_graph::SingleModuleGraphNode::Module(module) => {
2194 if module.ident().to_string().owned().await? == "[test]/d.js" {
2195 Some(*module)
2196 } else {
2197 None
2198 }
2199 }
2200 crate::module_graph::SingleModuleGraphNode::VisitedModule {
2201 ..
2202 } => None,
2203 })
2204 })
2205 .try_flat_join()
2206 .await?
2207 .into_iter()
2208 .next()
2209 .unwrap();
2210
2211 async fn get_reverse_from(
2212 graph: &ModuleGraphLayer,
2213 module: ResolvedVc<Box<dyn Module>>,
2214 ) -> Result<Vec<RcStr>> {
2215 let mut visited = Vec::new();
2216 graph.traverse_edges_reverse_dfs(
2217 vec![module],
2218 &mut (),
2219 |_parent, child, _state_| {
2220 visited.push(child);
2221 Ok(GraphTraversalAction::Continue)
2222 },
2223 |_, _, _| Ok(()),
2224 )?;
2225 visited
2226 .iter()
2227 .map(|m| m.ident().to_string().owned())
2228 .try_join()
2229 .await
2230 }
2231
2232 Ok(ReverseTraversalResults {
2233 forward,
2234 reverse_from_d: get_reverse_from(&child_graph, d_module).await?,
2235 reverse_from_b: get_reverse_from(&child_graph, b_module).await?,
2236 }
2237 .cell())
2238 }
2239
2240 let traversal_results = reverse_traversal_results_operation()
2241 .read_strongly_consistent()
2242 .await?;
2243
2244 assert_eq!(
2245 traversal_results.forward,
2246 vec![
2247 rcstr!("[test]/a.js"),
2248 rcstr!("[test]/b.js"),
2249 rcstr!("[test]/d.js")
2250 ]
2251 );
2252
2253 assert_eq!(
2254 traversal_results.reverse_from_d,
2255 vec![rcstr!("[test]/d.js"), rcstr!("[test]/a.js")]
2256 );
2257
2258 assert_eq!(
2259 traversal_results.reverse_from_b,
2260 vec![rcstr!("[test]/b.js"), rcstr!("[test]/a.js")]
2261 );
2262
2263 Ok(())
2264 })
2265 .await
2266 .unwrap();
2267 }
2268
2269 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2270 async fn test_iter_nodes_modules_through_layered_graph() {
2271 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2272 BackendOptions::default(),
2273 noop_backing_storage(),
2274 ));
2275 tt.run_once(async move {
2276 #[turbo_tasks::value]
2277 struct Results {
2278 iter_nodes: Vec<RcStr>,
2279 iter_modules: Vec<RcStr>,
2280 iter_nodes_single: Vec<Vec<RcStr>>,
2281 iter_modules_single: Vec<Vec<RcStr>>,
2282 }
2283
2284 #[turbo_tasks::function(operation, root)]
2285 async fn reverse_traversal_results_operation() -> Result<Vc<Results>> {
2286 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2287 let root = fs.root().await?;
2288
2289 let graph = {
2292 let mut deps = FxHashMap::default();
2293
2294 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
2295 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2296 deps.insert(rcstr!("c.js"), vec![rcstr!("x.js")]);
2297 deps.insert(rcstr!("x.js"), vec![rcstr!("y.js")]);
2298 deps.insert(rcstr!("y.js"), vec![rcstr!("z.js")]);
2299 deps
2300 };
2301 let repo = TestRepo {
2302 repo: graph
2303 .iter()
2304 .map(|(k, v)| {
2305 (
2306 root.join(k).unwrap(),
2307 v.iter().map(|f| root.join(f).unwrap()).collect(),
2308 )
2309 })
2310 .collect(),
2311 }
2312 .cell();
2313 let make_module = |name| {
2314 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2315 .to_resolved()
2316 };
2317 let x_module = make_module("x.js").await?;
2318 let a_module = make_module("a.js").await?;
2319
2320 let parent_graph = SingleModuleGraph::new_with_entries(
2321 ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![x_module])]),
2322 false,
2323 false,
2324 );
2325
2326 let module_graph = ModuleGraph::from_graphs(
2327 vec![
2328 parent_graph,
2329 SingleModuleGraph::new_with_entries_visited(
2330 ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![a_module])]),
2331 VisitedModules::from_graph(parent_graph),
2332 false,
2333 false,
2334 ),
2335 ],
2336 None,
2337 )
2338 .connect();
2339 let graph_layers = module_graph.iter_graphs().await?;
2340
2341 Ok(Results {
2342 iter_nodes: module_graph
2343 .await?
2344 .iter_reachable_nodes()?
2345 .map(async |node| {
2346 Ok(match node {
2347 SingleModuleGraphNode::Module(module) => {
2348 module.ident_string().owned().await?
2349 }
2350 SingleModuleGraphNode::VisitedModule { module, .. } => {
2351 format!("visited {}", module.ident_string().owned().await?)
2352 .into()
2353 }
2354 })
2355 })
2356 .try_join()
2357 .await?,
2358 iter_modules: module_graph
2359 .await?
2360 .iter_reachable_modules()?
2361 .map(|m| m.ident_string().owned())
2362 .try_join()
2363 .await?,
2364 iter_nodes_single: graph_layers
2365 .iter()
2366 .map(async |layer| {
2367 layer
2368 .connect()
2369 .await?
2370 .iter_reachable_nodes()?
2371 .map(async |node| {
2372 Ok(match node {
2373 SingleModuleGraphNode::Module(module) => {
2374 module.ident_string().owned().await?
2375 }
2376 SingleModuleGraphNode::VisitedModule { module, .. } => {
2377 format!(
2378 "visited {}",
2379 module.ident_string().owned().await?
2380 )
2381 .into()
2382 }
2383 })
2384 })
2385 .try_join()
2386 .await
2387 })
2388 .try_join()
2389 .await?,
2390 iter_modules_single: graph_layers
2391 .iter()
2392 .map(async |layer| {
2393 layer
2394 .connect()
2395 .await?
2396 .iter_reachable_modules()?
2397 .map(|m| m.ident_string().owned())
2398 .try_join()
2399 .await
2400 })
2401 .try_join()
2402 .await?,
2403 }
2404 .cell())
2405 }
2406
2407 let traversal_results = reverse_traversal_results_operation()
2408 .read_strongly_consistent()
2409 .await?;
2410
2411 assert_eq!(
2412 traversal_results.iter_nodes,
2413 vec![
2414 rcstr!("[test]/x.js"),
2415 rcstr!("[test]/a.js"),
2416 rcstr!("[test]/y.js"),
2417 rcstr!("[test]/b.js"),
2418 rcstr!("[test]/z.js"),
2419 rcstr!("[test]/c.js"),
2420 rcstr!("visited [test]/x.js")
2421 ]
2422 );
2423 assert_eq!(
2424 traversal_results.iter_modules,
2425 vec![
2426 rcstr!("[test]/x.js"),
2427 rcstr!("[test]/a.js"),
2428 rcstr!("[test]/y.js"),
2429 rcstr!("[test]/b.js"),
2430 rcstr!("[test]/z.js"),
2431 rcstr!("[test]/c.js")
2432 ]
2433 );
2434 assert_eq!(
2435 traversal_results.iter_nodes_single,
2436 vec![
2437 vec![
2438 rcstr!("[test]/x.js"),
2439 rcstr!("[test]/y.js"),
2440 rcstr!("[test]/z.js")
2441 ],
2442 vec![
2443 rcstr!("[test]/a.js"),
2444 rcstr!("[test]/b.js"),
2445 rcstr!("[test]/c.js"),
2446 rcstr!("visited [test]/x.js")
2447 ]
2448 ]
2449 );
2450 assert_eq!(
2451 traversal_results.iter_modules_single,
2452 vec![
2453 vec![
2454 rcstr!("[test]/x.js"),
2455 rcstr!("[test]/y.js"),
2456 rcstr!("[test]/z.js")
2457 ],
2458 vec![
2459 rcstr!("[test]/a.js"),
2460 rcstr!("[test]/b.js"),
2461 rcstr!("[test]/c.js")
2462 ]
2463 ]
2464 );
2465
2466 Ok(())
2467 })
2468 .await
2469 .unwrap();
2470 }
2471
2472 #[turbo_tasks::value(shared)]
2473 struct TestRepo {
2474 repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
2475 }
2476 #[turbo_tasks::value]
2477 struct MockModule {
2478 path: FileSystemPath,
2479 repo: ResolvedVc<TestRepo>,
2480 }
2481 #[turbo_tasks::value_impl]
2482 impl MockModule {
2483 #[turbo_tasks::function]
2484 fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
2485 Self { path, repo }.cell()
2486 }
2487 }
2488
2489 #[turbo_tasks::value_impl]
2490 impl Asset for MockModule {
2491 #[turbo_tasks::function]
2492 fn content(&self) -> Vc<AssetContent> {
2493 panic!("MockModule::content shouldn't be called")
2494 }
2495 }
2496
2497 #[turbo_tasks::value_impl]
2498 impl Module for MockModule {
2499 #[turbo_tasks::function]
2500 fn ident(&self) -> Vc<AssetIdent> {
2501 AssetIdent::from_path(self.path.clone()).into_vc()
2502 }
2503
2504 #[turbo_tasks::function]
2505 fn source(&self) -> Vc<crate::source::OptionSource> {
2506 Vc::cell(None)
2507 }
2508
2509 #[turbo_tasks::function]
2510 async fn references(&self) -> Result<Vc<ModuleReferences>> {
2511 let repo = self.repo.await?;
2512 let references = match repo.repo.get(&self.path) {
2513 Some(deps) => {
2514 deps.iter()
2515 .map(|p| {
2516 Vc::upcast::<Box<dyn ModuleReference>>(
2517 SingleChunkableModuleReference::new(
2518 Vc::upcast(MockModule::new(p.clone(), *self.repo)),
2519 rcstr!("normal-dep"),
2520 ExportUsage::all(),
2521 ),
2522 )
2523 .to_resolved()
2524 })
2525 .try_join()
2526 .await?
2527 }
2528 None => vec![],
2529 };
2530
2531 Ok(Vc::cell(references))
2532 }
2533 #[turbo_tasks::function]
2534 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
2535 ModuleSideEffects::SideEffectful.cell()
2536 }
2537 }
2538
2539 async fn run_graph_test(
2553 entries: Vec<RcStr>,
2554 graph: FxHashMap<RcStr, Vec<RcStr>>,
2555 test_fn: impl FnOnce(
2556 &ModuleGraph,
2557 Vec<ResolvedVc<Box<dyn Module>>>,
2558 FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2559 ) -> Result<()>
2560 + Send
2561 + 'static,
2562 ) {
2563 #[turbo_tasks::value(serialization = "skip", eq = "manual", cell = "new")]
2564 struct SetupGraph {
2565 module_graph: ReadRef<ModuleGraph>,
2566 entry_modules: Vec<ResolvedVc<Box<dyn Module>>>,
2567 module_to_name: FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2568 }
2569
2570 #[turbo_tasks::function(operation, root)]
2571 async fn setup_graph(
2572 entries: Vec<RcStr>,
2573 graph_entries: Vec<(RcStr, Vec<RcStr>)>,
2574 ) -> Result<Vc<SetupGraph>> {
2575 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2576 let root = fs.root().await?;
2577
2578 let repo = TestRepo {
2579 repo: graph_entries
2580 .iter()
2581 .map(|(k, v)| {
2582 (
2583 root.join(k).unwrap(),
2584 v.iter().map(|f| root.join(f).unwrap()).collect(),
2585 )
2586 })
2587 .collect(),
2588 }
2589 .cell();
2590 let entry_modules = entries
2591 .iter()
2592 .map(|e| {
2593 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
2594 .to_resolved()
2595 })
2596 .try_join()
2597 .await?;
2598 let graph = SingleModuleGraph::new_with_entries(
2599 GraphEntries::resolved_cell(GraphEntries(vec![ChunkGroupEntry::Entry(
2600 entry_modules.clone(),
2601 )])),
2602 false,
2603 false,
2604 );
2605
2606 let module_to_name = graph
2610 .connect()
2611 .await?
2612 .modules
2613 .keys()
2614 .map(|m| async move { Ok((*m, m.ident().await?.path.path.clone())) })
2615 .try_join()
2616 .await?
2617 .into_iter()
2618 .collect();
2619 let module_graph = ModuleGraph::from_graphs(vec![graph], None)
2620 .connect()
2621 .await?;
2622
2623 Ok(SetupGraph {
2624 module_graph,
2625 entry_modules,
2626 module_to_name,
2627 }
2628 .cell())
2629 }
2630
2631 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2632 BackendOptions::default(),
2633 noop_backing_storage(),
2634 ));
2635 let graph_entries = graph.into_iter().collect::<Vec<_>>();
2636 tt.run_once(async move {
2637 let setup = setup_graph(entries, graph_entries)
2638 .read_strongly_consistent()
2639 .await?;
2640
2641 test_fn(
2642 &setup.module_graph,
2643 setup.entry_modules.clone(),
2644 setup.module_to_name.clone(),
2645 )
2646 })
2647 .await
2648 .unwrap();
2649 }
2650}