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