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 TryFlatJoinIterExt, 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 bail!("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_module_info = self.async_module_info();
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 .map(async |m| Ok(async_module_info.is_async(m).await?.then_some(*m)))
832 .try_flat_join()
833 .await?;
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 #[turbo_tasks::function]
1527 pub async fn module_count(&self) -> Result<Vc<u64>> {
1528 Ok(Vc::cell(self.number_of_modules as u64))
1529 }
1530}
1531
1532#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1533pub enum SingleModuleGraphNode {
1534 Module(ResolvedVc<Box<dyn Module>>),
1535 VisitedModule {
1537 idx: GraphNodeIndex,
1538 module: ResolvedVc<Box<dyn Module>>,
1539 },
1540}
1541
1542impl SingleModuleGraphNode {
1543 pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1544 match self {
1545 SingleModuleGraphNode::Module(module) => *module,
1546 SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1547 }
1548 }
1549 pub fn target_idx(&self, direction: Direction) -> Option<GraphNodeIndex> {
1550 match self {
1551 SingleModuleGraphNode::VisitedModule { idx, .. } => match direction {
1552 Direction::Outgoing => Some(*idx),
1553 Direction::Incoming => None,
1554 },
1555 SingleModuleGraphNode::Module(_) => None,
1556 }
1557 }
1558}
1559
1560#[derive(PartialEq, Eq, Debug)]
1561pub enum GraphTraversalAction {
1562 Continue,
1564 Skip,
1566 Exclude,
1568}
1569
1570#[derive(Clone, Hash, PartialEq, Eq)]
1573enum SingleModuleGraphBuilderNode {
1574 Module {
1576 module: ResolvedVc<Box<dyn Module>>,
1577 ident: Option<ReadRef<RcStr>>,
1579 },
1580 VisitedModule {
1582 module: ResolvedVc<Box<dyn Module>>,
1583 idx: GraphNodeIndex,
1584 },
1585}
1586
1587impl SingleModuleGraphBuilderNode {
1588 async fn new_module(emit_spans: bool, module: ResolvedVc<Box<dyn Module>>) -> Result<Self> {
1589 Ok(Self::Module {
1590 module,
1591 ident: if emit_spans {
1592 Some(module.ident_string().untracked().await?)
1594 } else {
1595 None
1596 },
1597 })
1598 }
1599 fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1600 Self::VisitedModule { module, idx }
1601 }
1602}
1603
1604struct SingleModuleGraphBuilder<'a> {
1605 visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1606
1607 emit_spans: bool,
1608
1609 include_traced: bool,
1611
1612 include_binding_usage: bool,
1614}
1615impl Visit<SingleModuleGraphBuilderNode, RefData> for SingleModuleGraphBuilder<'_> {
1616 type EdgesIntoIter = Vec<(SingleModuleGraphBuilderNode, RefData)>;
1617 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1618
1619 fn visit(
1620 &mut self,
1621 node: &SingleModuleGraphBuilderNode,
1622 edge: Option<&RefData>,
1623 ) -> VisitControlFlow {
1624 if let Some(edge) = edge
1625 && matches!(edge.chunking_type, ChunkingType::Traced)
1626 {
1627 return VisitControlFlow::Skip;
1629 }
1630 match node {
1631 SingleModuleGraphBuilderNode::Module { .. } => VisitControlFlow::Continue,
1632 SingleModuleGraphBuilderNode::VisitedModule { .. } => VisitControlFlow::Skip,
1634 }
1635 }
1636
1637 fn edges(
1638 &mut self,
1639 node: &SingleModuleGraphBuilderNode,
1642 ) -> Self::EdgesFuture {
1643 let &SingleModuleGraphBuilderNode::Module { module, .. } = node else {
1645 unreachable!()
1647 };
1648 let visited_modules = self.visited_modules;
1649 let emit_spans = self.emit_spans;
1650 let include_traced = self.include_traced;
1651 let include_binding_usage = self.include_binding_usage;
1652 async move {
1653 let refs_cell = primary_chunkable_referenced_modules(
1654 *module,
1655 include_traced,
1656 include_binding_usage,
1657 );
1658 let refs = match refs_cell.await {
1659 Ok(refs) => refs,
1660 Err(e) => {
1661 return Err(e.context(module.ident().to_string().await?));
1662 }
1663 };
1664
1665 refs.iter()
1666 .flat_map(|(reference, resolved)| {
1667 resolved.modules.iter().map(|m| {
1668 (
1669 *reference,
1670 resolved.chunking_type.clone(),
1671 resolved.binding_usage.clone(),
1672 *m,
1673 )
1674 })
1675 })
1676 .map(async |(reference, ty, binding_usage, target)| {
1677 let to = if let Some(idx) = visited_modules.get(&target) {
1678 SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1679 } else {
1680 SingleModuleGraphBuilderNode::new_module(emit_spans, target).await?
1681 };
1682 Ok((
1683 to,
1684 RefData {
1685 chunking_type: ty,
1686 binding_usage,
1687 reference,
1688 },
1689 ))
1690 })
1691 .try_join()
1692 .await
1693 }
1694 }
1695
1696 fn span(
1697 &mut self,
1698 node: &SingleModuleGraphBuilderNode,
1699 edge: Option<&RefData>,
1700 ) -> tracing::Span {
1701 if !self.emit_spans {
1702 return Span::none();
1703 }
1704
1705 let mut span = match node {
1706 SingleModuleGraphBuilderNode::Module {
1707 ident: Some(ident), ..
1708 } => {
1709 tracing::info_span!("module", name = display(ident))
1710 }
1711 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1712 tracing::info_span!("visited module")
1713 }
1714 _ => unreachable!(),
1715 };
1716
1717 if let Some(edge) = edge {
1718 match &edge.chunking_type {
1719 ChunkingType::Parallel {
1720 inherit_async: _,
1721 hoisted: _,
1722 } => {}
1723 ChunkingType::Traced => {
1724 let _span = span.entered();
1725 span = tracing::info_span!("traced reference");
1726 }
1727 ChunkingType::Async => {
1728 let _span = span.entered();
1729 span = tracing::info_span!("async reference");
1730 }
1731 ChunkingType::Isolated { _ty: ty, merge_tag } => {
1732 let _span = span.entered();
1733 span = tracing::info_span!(
1734 "isolated reference",
1735 ty = debug(&ty),
1736 merge_tag = debug(&merge_tag)
1737 );
1738 }
1739 ChunkingType::Shared {
1740 inherit_async: _,
1741 merge_tag,
1742 } => {
1743 let _span = span.entered();
1744 span = tracing::info_span!("shared reference", merge_tag = debug(&merge_tag));
1745 }
1746 };
1747 }
1748
1749 span
1750 }
1751}
1752
1753#[cfg(test)]
1754pub mod tests {
1755 use anyhow::Result;
1756 use rustc_hash::FxHashMap;
1757 use turbo_rcstr::{RcStr, rcstr};
1758 use turbo_tasks::{ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc};
1759 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
1760 use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
1761
1762 use super::*;
1763 use crate::{
1764 asset::{Asset, AssetContent},
1765 ident::AssetIdent,
1766 module::{Module, ModuleSideEffects},
1767 reference::{ModuleReference, ModuleReferences, SingleChunkableModuleReference},
1768 resolve::ExportUsage,
1769 };
1770
1771 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1772 async fn test_traverse_dfs_from_entries_diamond() {
1773 run_graph_test(
1774 vec![rcstr!("a.js")],
1775 {
1776 let mut deps = FxHashMap::default();
1777 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1779 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
1780 deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
1781 deps
1782 },
1783 |graph, entry_modules, module_to_name| {
1784 let mut preorder_visits = Vec::new();
1785 let mut postorder_visits = Vec::new();
1786
1787 graph.traverse_edges_dfs(
1788 entry_modules,
1789 &mut (),
1790 |parent, target, _| {
1791 preorder_visits.push((
1792 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1793 module_to_name.get(&target).unwrap().clone(),
1794 ));
1795 Ok(GraphTraversalAction::Continue)
1796 },
1797 |parent, target, _| {
1798 postorder_visits.push((
1799 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1800 module_to_name.get(&target).unwrap().clone(),
1801 ));
1802 Ok(())
1803 },
1804 )?;
1805 assert_eq!(
1806 vec![
1807 (None, rcstr!("a.js")),
1808 (Some(rcstr!("a.js")), rcstr!("b.js")),
1809 (Some(rcstr!("b.js")), rcstr!("d.js")),
1810 (Some(rcstr!("a.js")), rcstr!("c.js")),
1811 (Some(rcstr!("c.js")), rcstr!("d.js"))
1812 ],
1813 preorder_visits
1814 );
1815 assert_eq!(
1816 vec![
1817 (Some(rcstr!("b.js")), rcstr!("d.js")),
1818 (Some(rcstr!("a.js")), rcstr!("b.js")),
1819 (Some(rcstr!("c.js")), rcstr!("d.js")),
1820 (Some(rcstr!("a.js")), rcstr!("c.js")),
1821 (None, rcstr!("a.js"))
1822 ],
1823 postorder_visits
1824 );
1825 Ok(())
1826 },
1827 )
1828 .await;
1829 }
1830
1831 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1832 async fn test_traverse_dfs_from_entries_cycle() {
1833 run_graph_test(
1834 vec![rcstr!("a.js")],
1835 {
1836 let mut deps = FxHashMap::default();
1837 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1839 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1840 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1841 deps
1842 },
1843 |graph, entry_modules, module_to_name| {
1844 let mut preorder_visits = Vec::new();
1845 let mut postorder_visits = Vec::new();
1846
1847 graph.traverse_edges_dfs(
1848 entry_modules,
1849 &mut (),
1850 |parent, target, _| {
1851 preorder_visits.push((
1852 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1853 module_to_name.get(&target).unwrap().clone(),
1854 ));
1855 Ok(GraphTraversalAction::Continue)
1856 },
1857 |parent, target, _| {
1858 postorder_visits.push((
1859 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1860 module_to_name.get(&target).unwrap().clone(),
1861 ));
1862 Ok(())
1863 },
1864 )?;
1865 assert_eq!(
1866 vec![
1867 (None, rcstr!("a.js")),
1868 (Some(rcstr!("a.js")), rcstr!("b.js")),
1869 (Some(rcstr!("b.js")), rcstr!("c.js")),
1870 (Some(rcstr!("c.js")), rcstr!("a.js")),
1871 ],
1872 preorder_visits
1873 );
1874 assert_eq!(
1875 vec![
1876 (Some(rcstr!("c.js")), rcstr!("a.js")),
1877 (Some(rcstr!("b.js")), rcstr!("c.js")),
1878 (Some(rcstr!("a.js")), rcstr!("b.js")),
1879 (None, rcstr!("a.js"))
1880 ],
1881 postorder_visits
1882 );
1883 Ok(())
1884 },
1885 )
1886 .await;
1887 }
1888
1889 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1890 async fn test_traverse_edges_fixed_point_with_priority_cycle() {
1891 run_graph_test(
1892 vec![rcstr!("a.js")],
1893 {
1894 let mut deps = FxHashMap::default();
1895 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1897 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1898 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1899 deps
1900 },
1901 |graph, entry_modules, module_to_name| {
1902 let mut visits = Vec::new();
1903 let mut count = 0;
1904
1905 graph.traverse_edges_fixed_point_with_priority(
1906 entry_modules.into_iter().map(|m| (m, 0)),
1907 &mut (),
1908 |parent, target, _| {
1909 visits.push((
1910 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
1911 module_to_name.get(&target).unwrap().clone(),
1912 ));
1913 count += 1;
1914
1915 Ok(if count < 6 {
1917 GraphTraversalAction::Continue
1918 } else {
1919 GraphTraversalAction::Skip
1920 })
1921 },
1922 |_, _| Ok(0),
1923 )?;
1924 assert_eq!(
1925 vec![
1926 (None, rcstr!("a.js")),
1927 (Some(rcstr!("a.js")), rcstr!("b.js")),
1928 (Some(rcstr!("b.js")), rcstr!("c.js")),
1929 (Some(rcstr!("c.js")), rcstr!("a.js")),
1930 (Some(rcstr!("a.js")), rcstr!("b.js")),
1932 (Some(rcstr!("b.js")), rcstr!("c.js")),
1933 ],
1934 visits
1935 );
1936
1937 Ok(())
1938 },
1939 )
1940 .await;
1941 }
1942
1943 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1944 async fn test_traverse_edges_fixed_point_no_priority_is_bfs() {
1945 run_graph_test(
1946 vec![rcstr!("a.js")],
1947 {
1948 let mut deps = FxHashMap::default();
1949 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1954 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js"), rcstr!("e.js")]);
1955 deps.insert(rcstr!("c.js"), vec![rcstr!("e.js"), rcstr!("f.js")]);
1956 deps
1957 },
1958 |graph, entry_modules, module_to_name| {
1959 let mut visits = Vec::new();
1960 let mut count = 0;
1961
1962 graph.traverse_edges_fixed_point_with_priority(
1963 entry_modules.into_iter().map(|m| (m, 0)),
1964 &mut (),
1965 |parent, target, _| {
1966 visits.push((
1967 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
1968 module_to_name.get(&target).unwrap().clone(),
1969 ));
1970 count += 1;
1971
1972 Ok(if count < 6 {
1974 GraphTraversalAction::Continue
1975 } else {
1976 GraphTraversalAction::Skip
1977 })
1978 },
1979 |_, _| Ok(0),
1980 )?;
1981
1982 assert_eq!(
1983 vec![
1984 (None, rcstr!("a.js")),
1985 (Some(rcstr!("a.js")), rcstr!("c.js")),
1986 (Some(rcstr!("a.js")), rcstr!("b.js")),
1987 (Some(rcstr!("b.js")), rcstr!("e.js")),
1988 (Some(rcstr!("b.js")), rcstr!("d.js")),
1989 (Some(rcstr!("c.js")), rcstr!("f.js")),
1990 (Some(rcstr!("c.js")), rcstr!("e.js")),
1991 ],
1992 visits
1993 );
1994
1995 Ok(())
1996 },
1997 )
1998 .await;
1999 }
2000
2001 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2002 async fn test_traverse_cycles() {
2003 run_graph_test(
2004 vec![rcstr!("a.js")],
2005 {
2006 let mut deps = FxHashMap::default();
2007 deps.insert(
2014 rcstr!("a.js"),
2015 vec![rcstr!("i.js"), rcstr!("s.js"), rcstr!("x.js")],
2016 );
2017 deps.insert(rcstr!("i.js"), vec![rcstr!("j.js")]);
2018 deps.insert(rcstr!("j.js"), vec![rcstr!("k.js")]);
2019 deps.insert(rcstr!("k.js"), vec![rcstr!("i.js")]);
2020 deps.insert(rcstr!("s.js"), vec![rcstr!("s.js")]);
2021 deps
2022 },
2023 |graph, _, module_to_name| {
2024 let mut cycles = vec![];
2025
2026 graph.traverse_cycles(
2027 |_| true,
2028 |cycle| {
2029 cycles.push(
2030 cycle
2031 .iter()
2032 .map(|n| module_to_name.get(*n).unwrap().clone())
2033 .collect::<Vec<_>>(),
2034 );
2035 Ok(())
2036 },
2037 )?;
2038
2039 assert_eq!(
2040 cycles,
2041 vec![
2042 vec![rcstr!("k.js"), rcstr!("j.js"), rcstr!("i.js")],
2043 vec![rcstr!("s.js")]
2044 ],
2045 );
2046
2047 Ok(())
2048 },
2049 )
2050 .await;
2051 }
2052
2053 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2054 async fn test_reverse_edges_through_layered_graph() {
2055 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2056 BackendOptions::default(),
2057 noop_backing_storage(),
2058 ));
2059 tt.run_once(async move {
2060 #[turbo_tasks::value]
2061 struct ReverseTraversalResults {
2062 forward: Vec<RcStr>,
2063 reverse_from_d: Vec<RcStr>,
2064 reverse_from_b: Vec<RcStr>,
2065 }
2066
2067 #[turbo_tasks::function(operation)]
2068 async fn reverse_traversal_results_operation() -> Result<Vc<ReverseTraversalResults>> {
2069 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2070 let root = fs.root().await?;
2071
2072 let graph = {
2075 let mut deps = FxHashMap::default();
2076
2077 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("d.js")]);
2078 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2079 deps
2080 };
2081 let repo = TestRepo {
2082 repo: graph
2083 .iter()
2084 .map(|(k, v)| {
2085 (
2086 root.join(k).unwrap(),
2087 v.iter().map(|f| root.join(f).unwrap()).collect(),
2088 )
2089 })
2090 .collect(),
2091 }
2092 .cell();
2093 let make_module = |name| {
2094 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2095 .to_resolved()
2096 };
2097 let a_module = make_module("a.js").await?;
2098 let b_module = make_module("b.js").await?;
2099
2100 let parent_graph = SingleModuleGraph::new_with_entries(
2101 ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![b_module])]),
2102 false,
2103 false,
2104 );
2105
2106 let module_graph = ModuleGraph::from_graphs(vec![
2107 parent_graph,
2108 SingleModuleGraph::new_with_entries_visited(
2109 ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![a_module])]),
2110 VisitedModules::from_graph(parent_graph),
2111 false,
2112 false,
2113 ),
2114 ])
2115 .connect();
2116 let child_graph = module_graph
2117 .iter_graphs()
2118 .await?
2119 .get(1)
2120 .unwrap()
2121 .connect()
2122 .await?;
2123
2124 let mut visited_forward = Vec::new();
2126 child_graph.traverse_edges_dfs(
2127 vec![a_module],
2128 &mut (),
2129 |_parent, child, _state_| {
2130 visited_forward.push(child);
2131 Ok(GraphTraversalAction::Continue)
2132 },
2133 |_, _, _| Ok(()),
2134 )?;
2135 let forward = visited_forward
2136 .iter()
2137 .map(|m| m.ident().to_string().owned())
2138 .try_join()
2139 .await?;
2140
2141 let d_module = child_graph
2143 .enumerate_nodes()
2144 .map(|(_index, module)| async move {
2145 Ok(match module {
2146 crate::module_graph::SingleModuleGraphNode::Module(module) => {
2147 if module.ident().to_string().owned().await? == "[test]/d.js" {
2148 Some(*module)
2149 } else {
2150 None
2151 }
2152 }
2153 crate::module_graph::SingleModuleGraphNode::VisitedModule {
2154 ..
2155 } => None,
2156 })
2157 })
2158 .try_flat_join()
2159 .await?
2160 .into_iter()
2161 .next()
2162 .unwrap();
2163
2164 async fn get_reverse_from(
2165 graph: &ModuleGraphLayer,
2166 module: ResolvedVc<Box<dyn Module>>,
2167 ) -> Result<Vec<RcStr>> {
2168 let mut visited = Vec::new();
2169 graph.traverse_edges_reverse_dfs(
2170 vec![module],
2171 &mut (),
2172 |_parent, child, _state_| {
2173 visited.push(child);
2174 Ok(GraphTraversalAction::Continue)
2175 },
2176 |_, _, _| Ok(()),
2177 )?;
2178 visited
2179 .iter()
2180 .map(|m| m.ident().to_string().owned())
2181 .try_join()
2182 .await
2183 }
2184
2185 Ok(ReverseTraversalResults {
2186 forward,
2187 reverse_from_d: get_reverse_from(&child_graph, d_module).await?,
2188 reverse_from_b: get_reverse_from(&child_graph, b_module).await?,
2189 }
2190 .cell())
2191 }
2192
2193 let traversal_results = reverse_traversal_results_operation()
2194 .read_strongly_consistent()
2195 .await?;
2196
2197 assert_eq!(
2198 traversal_results.forward,
2199 vec![
2200 rcstr!("[test]/a.js"),
2201 rcstr!("[test]/b.js"),
2202 rcstr!("[test]/d.js")
2203 ]
2204 );
2205
2206 assert_eq!(
2207 traversal_results.reverse_from_d,
2208 vec![rcstr!("[test]/d.js"), rcstr!("[test]/a.js")]
2209 );
2210
2211 assert_eq!(
2212 traversal_results.reverse_from_b,
2213 vec![rcstr!("[test]/b.js"), rcstr!("[test]/a.js")]
2214 );
2215
2216 Ok(())
2217 })
2218 .await
2219 .unwrap();
2220 }
2221
2222 #[turbo_tasks::value(shared)]
2223 struct TestRepo {
2224 repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
2225 }
2226 #[turbo_tasks::value]
2227 struct MockModule {
2228 path: FileSystemPath,
2229 repo: ResolvedVc<TestRepo>,
2230 }
2231 #[turbo_tasks::value_impl]
2232 impl MockModule {
2233 #[turbo_tasks::function]
2234 fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
2235 Self { path, repo }.cell()
2236 }
2237 }
2238
2239 #[turbo_tasks::value_impl]
2240 impl Asset for MockModule {
2241 #[turbo_tasks::function]
2242 fn content(&self) -> Vc<AssetContent> {
2243 panic!("MockModule::content shouldn't be called")
2244 }
2245 }
2246
2247 #[turbo_tasks::value_impl]
2248 impl Module for MockModule {
2249 #[turbo_tasks::function]
2250 fn ident(&self) -> Vc<AssetIdent> {
2251 AssetIdent::from_path(self.path.clone())
2252 }
2253
2254 #[turbo_tasks::function]
2255 fn source(&self) -> Vc<crate::source::OptionSource> {
2256 Vc::cell(None)
2257 }
2258
2259 #[turbo_tasks::function]
2260 async fn references(&self) -> Result<Vc<ModuleReferences>> {
2261 let repo = self.repo.await?;
2262 let references = match repo.repo.get(&self.path) {
2263 Some(deps) => {
2264 deps.iter()
2265 .map(|p| {
2266 Vc::upcast::<Box<dyn ModuleReference>>(
2267 SingleChunkableModuleReference::new(
2268 Vc::upcast(MockModule::new(p.clone(), *self.repo)),
2269 rcstr!("normal-dep"),
2270 ExportUsage::all(),
2271 ),
2272 )
2273 .to_resolved()
2274 })
2275 .try_join()
2276 .await?
2277 }
2278 None => vec![],
2279 };
2280
2281 Ok(Vc::cell(references))
2282 }
2283 #[turbo_tasks::function]
2284 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
2285 ModuleSideEffects::SideEffectful.cell()
2286 }
2287 }
2288
2289 async fn run_graph_test(
2303 entries: Vec<RcStr>,
2304 graph: FxHashMap<RcStr, Vec<RcStr>>,
2305 test_fn: impl FnOnce(
2306 &ModuleGraph,
2307 Vec<ResolvedVc<Box<dyn Module>>>,
2308 FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2309 ) -> Result<()>
2310 + Send
2311 + 'static,
2312 ) {
2313 #[turbo_tasks::value(serialization = "none", eq = "manual", cell = "new")]
2314 struct SetupGraph {
2315 module_graph: ReadRef<ModuleGraph>,
2316 entry_modules: Vec<ResolvedVc<Box<dyn Module>>>,
2317 module_to_name: FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2318 }
2319
2320 #[turbo_tasks::function(operation)]
2321 async fn setup_graph(
2322 entries: Vec<RcStr>,
2323 graph_entries: Vec<(RcStr, Vec<RcStr>)>,
2324 ) -> Result<Vc<SetupGraph>> {
2325 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2326 let root = fs.root().await?;
2327
2328 let repo = TestRepo {
2329 repo: graph_entries
2330 .iter()
2331 .map(|(k, v)| {
2332 (
2333 root.join(k).unwrap(),
2334 v.iter().map(|f| root.join(f).unwrap()).collect(),
2335 )
2336 })
2337 .collect(),
2338 }
2339 .cell();
2340 let entry_modules = entries
2341 .iter()
2342 .map(|e| {
2343 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
2344 .to_resolved()
2345 })
2346 .try_join()
2347 .await?;
2348 let graph = SingleModuleGraph::new_with_entries(
2349 GraphEntries::resolved_cell(GraphEntries(vec![ChunkGroupEntry::Entry(
2350 entry_modules.clone(),
2351 )])),
2352 false,
2353 false,
2354 );
2355
2356 let module_to_name = graph
2360 .connect()
2361 .await?
2362 .modules
2363 .keys()
2364 .map(|m| async move { Ok((*m, m.ident().await?.path.path.clone())) })
2365 .try_join()
2366 .await?
2367 .into_iter()
2368 .collect();
2369 let module_graph = ModuleGraph::from_single_graph(graph).connect().await?;
2370
2371 Ok(SetupGraph {
2372 module_graph,
2373 entry_modules,
2374 module_to_name,
2375 }
2376 .cell())
2377 }
2378
2379 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2380 BackendOptions::default(),
2381 noop_backing_storage(),
2382 ));
2383 let graph_entries = graph.into_iter().collect::<Vec<_>>();
2384 tt.run_once(async move {
2385 let setup = setup_graph(entries, graph_entries)
2386 .read_strongly_consistent()
2387 .await?;
2388
2389 test_fn(
2390 &setup.module_graph,
2391 setup.entry_modules.clone(),
2392 setup.module_to_name.clone(),
2393 )
2394 })
2395 .await
2396 .unwrap();
2397 }
2398}