1use std::{
2 collections::{BinaryHeap, HashSet, VecDeque},
3 future::Future,
4};
5
6use anyhow::{Context, Result, bail};
7use petgraph::{
8 graph::{DiGraph, EdgeIndex, NodeIndex},
9 visit::{Dfs, EdgeRef, IntoNodeReferences, NodeIndexable, VisitMap, Visitable},
10};
11use rustc_hash::{FxHashMap, FxHashSet};
12use serde::{Deserialize, Serialize};
13use tracing::{Instrument, Span};
14use turbo_rcstr::RcStr;
15use turbo_tasks::{
16 CollectiblesSource, FxIndexMap, NonLocalValue, ReadRef, ResolvedVc, TryJoinIterExt,
17 ValueToString, Vc,
18 graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
19 trace::TraceRawVcs,
20};
21
22use crate::{
23 chunk::{AsyncModuleInfo, ChunkingContext, ChunkingType},
24 issue::Issue,
25 module::Module,
26 module_graph::{
27 async_module_info::{AsyncModulesInfo, compute_async_module_info},
28 chunk_group_info::{ChunkGroupEntry, ChunkGroupInfo, compute_chunk_group_info},
29 module_batches::{ModuleBatchesGraph, compute_module_batches},
30 style_groups::{StyleGroups, StyleGroupsConfig, compute_style_groups},
31 traced_di_graph::{TracedDiGraph, iter_neighbors_rev},
32 },
33 reference::primary_chunkable_referenced_modules,
34};
35
36pub mod async_module_info;
37pub mod chunk_group_info;
38pub mod module_batch;
39pub(crate) mod module_batches;
40pub(crate) mod style_groups;
41mod traced_di_graph;
42
43pub use self::module_batches::BatchingConfig;
44
45#[derive(
46 Debug, Copy, Clone, Eq, PartialOrd, Ord, Hash, PartialEq, Serialize, Deserialize, TraceRawVcs,
47)]
48pub struct GraphNodeIndex {
49 #[turbo_tasks(trace_ignore)]
50 graph_idx: usize,
51 #[turbo_tasks(trace_ignore)]
52 node_idx: NodeIndex,
53}
54unsafe impl NonLocalValue for GraphNodeIndex {}
55
56#[turbo_tasks::value]
57#[derive(Clone, Debug)]
58pub struct VisitedModules {
59 pub modules: FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
60 next_graph_idx: usize,
61}
62
63#[turbo_tasks::value_impl]
64impl VisitedModules {
65 #[turbo_tasks::function]
66 pub async fn empty() -> Vc<Self> {
67 Self {
68 modules: Default::default(),
69 next_graph_idx: 0,
70 }
71 .cell()
72 }
73
74 #[turbo_tasks::function]
75 pub async fn from_graph(graph: Vc<SingleModuleGraph>) -> Result<Vc<Self>> {
76 Ok(Self {
77 modules: graph
78 .await?
79 .enumerate_nodes()
80 .flat_map(|(node_idx, module)| match module {
81 SingleModuleGraphNode::Module(SingleModuleGraphModuleNode {
82 module, ..
83 }) => Some((
84 *module,
85 GraphNodeIndex {
86 graph_idx: 0,
87 node_idx,
88 },
89 )),
90 SingleModuleGraphNode::VisitedModule { .. } => None,
91 })
92 .collect(),
93 next_graph_idx: 1,
94 }
95 .cell())
96 }
97
98 #[turbo_tasks::function]
99 pub async fn with_incremented_index(&self) -> Result<Vc<Self>> {
100 Ok(Self {
101 modules: self.modules.clone(),
102 next_graph_idx: self.next_graph_idx + 1,
103 }
104 .cell())
105 }
106
107 #[turbo_tasks::function]
108 pub async fn concatenate(&self, graph: Vc<SingleModuleGraph>) -> Result<Vc<Self>> {
109 let graph = graph.await?;
110 let iter = self
111 .modules
112 .iter()
113 .map(|(module, idx)| (*module, *idx))
114 .chain(
115 graph
116 .enumerate_nodes()
117 .flat_map(|(node_idx, module)| match module {
118 SingleModuleGraphNode::Module(SingleModuleGraphModuleNode {
119 module,
120 ..
121 }) => Some((
122 *module,
123 GraphNodeIndex {
124 graph_idx: self.next_graph_idx,
125 node_idx,
126 },
127 )),
128 SingleModuleGraphNode::VisitedModule { .. } => None,
129 }),
130 );
131
132 let mut map = FxIndexMap::with_capacity_and_hasher(
133 self.modules.len() + graph.number_of_modules,
134 Default::default(),
135 );
136 for (k, v) in iter {
137 map.entry(k).or_insert(v);
138 }
139 map.shrink_to_fit();
140
141 Ok(Self {
142 modules: map,
143 next_graph_idx: self.next_graph_idx + 1,
144 }
145 .cell())
146 }
147}
148
149pub type GraphEntriesT = Vec<ChunkGroupEntry>;
150
151#[turbo_tasks::value(transparent)]
152pub struct GraphEntries(GraphEntriesT);
153
154#[turbo_tasks::value_impl]
155impl GraphEntries {
156 #[turbo_tasks::function]
157 pub fn empty() -> Vc<Self> {
158 Vc::cell(Vec::new())
159 }
160}
161
162#[turbo_tasks::value(cell = "new", eq = "manual", into = "new")]
163#[derive(Clone, Default)]
164pub struct SingleModuleGraph {
165 graph: TracedDiGraph<SingleModuleGraphNode, ChunkingType>,
166
167 pub number_of_modules: usize,
169
170 #[turbo_tasks(trace_ignore)]
177 modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex>,
178
179 #[turbo_tasks(trace_ignore)]
180 pub entries: GraphEntriesT,
181}
182
183impl SingleModuleGraph {
184 async fn new_inner(
188 entries: &GraphEntriesT,
189 visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
190 include_traced: bool,
191 ) -> Result<Vc<Self>> {
192 let root_edges = entries
193 .iter()
194 .flat_map(|e| e.entries())
195 .map(|e| async move {
196 Ok(SingleModuleGraphBuilderEdge {
197 to: SingleModuleGraphBuilderNode::new_module(e).await?,
198 })
199 })
200 .try_join()
201 .await?;
202
203 let (children_nodes_iter, visited_nodes) = AdjacencyMap::new()
204 .skip_duplicates()
205 .visit(
206 root_edges,
207 SingleModuleGraphBuilder {
208 visited_modules,
209 include_traced,
210 },
211 )
212 .await
213 .completed()?
214 .into_inner_with_visited();
215 let node_count = visited_nodes.0.len();
216 drop(visited_nodes);
217
218 let mut graph = DiGraph::with_capacity(
219 node_count,
220 node_count * 4,
223 );
224
225 let mut number_of_modules = 0;
226 let mut modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
227 FxHashMap::with_capacity_and_hasher(node_count, Default::default());
228 {
229 let _span = tracing::info_span!("build module graph").entered();
230 for (parent, current) in children_nodes_iter.into_breadth_first_edges() {
231 let parent_edge = match parent {
232 Some(SingleModuleGraphBuilderNode::Module { module, .. }) => {
233 Some((*modules.get(&module).unwrap(), COMMON_CHUNKING_TYPE))
234 }
235 Some(SingleModuleGraphBuilderNode::ChunkableReference { .. }) => {
236 continue;
238 }
239 Some(
240 SingleModuleGraphBuilderNode::VisitedModule { .. }
241 | SingleModuleGraphBuilderNode::Issues { .. },
242 ) => unreachable!(),
243 None => None,
244 };
245
246 match current {
247 SingleModuleGraphBuilderNode::Module {
248 module,
249 layer,
250 ident: _,
251 } => {
252 let current_idx = if let Some(current_idx) = modules.get(&module) {
254 *current_idx
255 } else {
256 let idx = graph.add_node(SingleModuleGraphNode::Module(
257 SingleModuleGraphModuleNode {
258 module,
259 issues: Default::default(),
260 layer,
261 },
262 ));
263 number_of_modules += 1;
264 modules.insert(module, idx);
265 idx
266 };
267 if let Some((parent_idx, chunking_type)) = parent_edge {
269 graph.add_edge(parent_idx, current_idx, chunking_type);
270 }
271 }
272 SingleModuleGraphBuilderNode::VisitedModule { module, idx } => {
273 let current_idx = if let Some(current_idx) = modules.get(&module) {
275 *current_idx
276 } else {
277 let idx = graph
278 .add_node(SingleModuleGraphNode::VisitedModule { idx, module });
279 modules.insert(module, idx);
280 idx
281 };
282 if let Some((parent_idx, chunking_type)) = parent_edge {
284 graph.add_edge(parent_idx, current_idx, chunking_type);
285 }
286 }
287 SingleModuleGraphBuilderNode::ChunkableReference {
288 source,
289 target,
290 target_layer,
291 chunking_type,
292 ..
293 } => {
294 let target_idx = if let Some(target_idx) = modules.get(&target) {
296 *target_idx
297 } else {
298 let target_idx = visited_modules.get(&target);
299 let idx = graph.add_node(match target_idx {
300 Some(idx) => SingleModuleGraphNode::VisitedModule {
301 idx: *idx,
302 module: target,
303 },
304 None => {
305 SingleModuleGraphNode::Module(SingleModuleGraphModuleNode {
306 module: target,
307 issues: Default::default(),
308 layer: target_layer,
309 })
310 }
311 });
312 modules.insert(target, idx);
313 idx
314 };
315 graph.add_edge(*modules.get(&source).unwrap(), target_idx, chunking_type);
316 }
317 SingleModuleGraphBuilderNode::Issues(new_issues) => {
318 let (parent_idx, _) = parent_edge.unwrap();
319 let SingleModuleGraphNode::Module(SingleModuleGraphModuleNode {
320 issues,
321 ..
322 }) = graph.node_weight_mut(parent_idx).unwrap()
323 else {
324 bail!("Expected Module node");
325 };
326
327 issues.extend(new_issues);
328 }
329 }
330 }
331 }
332
333 graph.shrink_to_fit();
334
335 #[cfg(debug_assertions)]
336 {
337 let mut duplicates = Vec::new();
338 let mut set = FxHashSet::default();
339 for &module in modules.keys() {
340 let ident = module.ident().to_string().await?;
341 if !set.insert(ident.clone()) {
342 duplicates.push(ident);
343 }
344 }
345 if !duplicates.is_empty() {
346 panic!("Duplicate module idents in graph: {duplicates:#?}");
347 }
348 }
349
350 Ok(SingleModuleGraph {
351 graph: TracedDiGraph::new(graph),
352 number_of_modules,
353 modules,
354 entries: entries.clone(),
355 }
356 .cell())
357 }
358
359 fn get_module(&self, module: ResolvedVc<Box<dyn Module>>) -> Result<NodeIndex> {
360 self.modules
361 .get(&module)
362 .copied()
363 .context("Couldn't find module in graph")
364 }
365
366 pub fn iter_nodes(&self) -> impl Iterator<Item = &'_ SingleModuleGraphModuleNode> + '_ {
368 self.graph.node_weights().filter_map(|n| match n {
369 SingleModuleGraphNode::Module(node) => Some(node),
370 SingleModuleGraphNode::VisitedModule { .. } => None,
371 })
372 }
373
374 pub fn entry_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
376 self.entries.iter().flat_map(|e| e.entries())
377 }
378
379 pub fn enumerate_nodes(
381 &self,
382 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
383 self.graph.node_references()
384 }
385
386 pub fn traverse_from_entry<'a>(
388 &'a self,
389 entry: ResolvedVc<Box<dyn Module>>,
390 mut visitor: impl FnMut(&'a SingleModuleGraphModuleNode),
391 ) -> Result<()> {
392 let entry_node = self.get_module(entry)?;
393
394 let mut dfs = Dfs::new(&*self.graph, entry_node);
395 while let Some(nx) = dfs.next(&*self.graph) {
396 let SingleModuleGraphNode::Module(weight) = self.graph.node_weight(nx).unwrap() else {
397 return Ok(());
398 };
399 visitor(weight);
401 }
402 Ok(())
403 }
404
405 pub fn traverse_edges_from_entries<'a>(
416 &'a self,
417 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
418 mut visitor: impl FnMut(
419 Option<(&'a SingleModuleGraphModuleNode, &'a ChunkingType)>,
420 &'a SingleModuleGraphModuleNode,
421 ) -> GraphTraversalAction,
422 ) -> Result<()> {
423 let graph = &self.graph;
424 let entries = entries.into_iter().map(|e| self.get_module(e).unwrap());
425
426 let mut stack = entries.collect::<Vec<_>>();
427 let mut discovered = graph.visit_map();
428 for entry_node in &stack {
430 let SingleModuleGraphNode::Module(entry_weight) =
431 graph.node_weight(*entry_node).unwrap()
432 else {
433 continue;
434 };
435 visitor(None, entry_weight);
436 }
437
438 while let Some(node) = stack.pop() {
439 let SingleModuleGraphNode::Module(node_weight) = graph.node_weight(node).unwrap()
440 else {
441 continue;
442 };
443 if discovered.visit(node) {
444 let neighbors = {
445 let mut neighbors = vec![];
446 let mut walker = graph.neighbors(node).detach();
447 while let Some((edge, succ)) = walker.next(graph) {
448 neighbors.push((edge, succ));
449 }
450 neighbors
451 };
452
453 for (edge, succ) in neighbors {
454 let SingleModuleGraphNode::Module(succ_weight) =
455 graph.node_weight(succ).unwrap()
456 else {
457 continue;
458 };
459 let edge_weight = graph.edge_weight(edge).unwrap();
460 let action = visitor(Some((node_weight, edge_weight)), succ_weight);
461 if !discovered.is_visited(&succ) && action == GraphTraversalAction::Continue {
462 stack.push(succ);
463 }
464 }
465 }
466 }
467
468 Ok(())
469 }
470
471 pub fn traverse_edges<'a>(
476 &'a self,
477 mut visitor: impl FnMut(
478 (
479 Option<(&'a SingleModuleGraphModuleNode, &'a ChunkingType)>,
480 &'a SingleModuleGraphModuleNode,
481 ),
482 ) -> GraphTraversalAction,
483 ) -> Result<()> {
484 let graph = &self.graph;
485 let mut stack: Vec<NodeIndex> = self
486 .entries
487 .iter()
488 .flat_map(|e| e.entries())
489 .map(|e| *self.modules.get(&e).unwrap())
490 .collect();
491 let mut discovered = graph.visit_map();
492 for entry_node in &stack {
493 let SingleModuleGraphNode::Module(entry_node) = graph.node_weight(*entry_node).unwrap()
494 else {
495 continue;
496 };
497 visitor((None, entry_node));
498 }
499
500 while let Some(node) = stack.pop() {
501 if discovered.visit(node) {
502 let SingleModuleGraphNode::Module(node_weight) = graph.node_weight(node).unwrap()
503 else {
504 continue;
505 };
506 for edge in graph.edges(node).collect::<Vec<_>>() {
507 let edge_weight = edge.weight();
508 let succ = edge.target();
509 let SingleModuleGraphNode::Module(succ_weight) =
510 graph.node_weight(succ).unwrap()
511 else {
512 continue;
513 };
514 let action = visitor((Some((node_weight, edge_weight)), succ_weight));
515 if !discovered.is_visited(&succ) && action == GraphTraversalAction::Continue {
516 stack.push(succ);
517 }
518 }
519 }
520 }
521
522 Ok(())
523 }
524
525 pub fn traverse_edges_from_entries_topological<'a, S>(
543 &'a self,
544 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
545 state: &mut S,
546 mut visit_preorder: impl FnMut(
547 Option<(&'a SingleModuleGraphModuleNode, &'a ChunkingType)>,
548 &'a SingleModuleGraphNode,
549 &mut S,
550 ) -> Result<GraphTraversalAction>,
551 mut visit_postorder: impl FnMut(
552 Option<(&'a SingleModuleGraphModuleNode, &'a ChunkingType)>,
553 &'a SingleModuleGraphNode,
554 &mut S,
555 ),
556 ) -> Result<()> {
557 let graph = &self.graph;
558 let entries = entries.into_iter().map(|e| self.get_module(e).unwrap());
559
560 enum TopologicalPass {
561 Visit,
562 ExpandAndVisit,
563 }
564
565 #[allow(clippy::type_complexity)] let mut stack: Vec<(TopologicalPass, Option<(NodeIndex, EdgeIndex)>, NodeIndex)> = entries
567 .map(|e| (TopologicalPass::ExpandAndVisit, None, e))
568 .collect();
569 let mut expanded = FxHashSet::default();
570 while let Some((pass, parent, current)) = stack.pop() {
571 let parent_arg = parent.map(|parent| {
572 (
573 match graph.node_weight(parent.0).unwrap() {
574 SingleModuleGraphNode::Module(node) => node,
575 SingleModuleGraphNode::VisitedModule { .. } => {
576 unreachable!()
577 }
578 },
579 graph.edge_weight(parent.1).unwrap(),
580 )
581 });
582 match pass {
583 TopologicalPass::Visit => {
584 visit_postorder(parent_arg, graph.node_weight(current).unwrap(), state);
585 }
586 TopologicalPass::ExpandAndVisit => match graph.node_weight(current).unwrap() {
587 current_node @ SingleModuleGraphNode::Module(_) => {
588 let action = visit_preorder(parent_arg, current_node, state)?;
589 if action == GraphTraversalAction::Exclude {
590 continue;
591 }
592 stack.push((TopologicalPass::Visit, parent, current));
593 if action == GraphTraversalAction::Continue && expanded.insert(current) {
594 stack.extend(iter_neighbors_rev(graph, current).map(
595 |(edge, child)| {
596 (
597 TopologicalPass::ExpandAndVisit,
598 Some((current, edge)),
599 child,
600 )
601 },
602 ));
603 }
604 }
605 current_node @ SingleModuleGraphNode::VisitedModule { .. } => {
606 visit_preorder(parent_arg, current_node, state)?;
607 visit_postorder(parent_arg, current_node, state);
608 }
609 },
610 }
611 }
612
613 Ok(())
614 }
615
616 pub fn traverse_cycles<'l>(
617 &'l self,
618 edge_filter: impl Fn(&'l ChunkingType) -> bool,
619 mut visit_cycle: impl FnMut(&[&'l SingleModuleGraphModuleNode]),
620 ) {
621 #[derive(Clone)]
625 struct NodeState {
626 index: u32,
627 lowlink: u32,
628 on_stack: bool,
629 }
630 enum VisitStep {
631 UnvisitedNode(NodeIndex),
632 EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
633 AfterVisit(NodeIndex),
634 }
635 let mut node_states = vec![None; self.graph.node_bound()];
636 let mut stack = Vec::new();
637 let mut visit_stack = Vec::new();
638 let mut index = 0;
639 let mut scc = Vec::new();
640 for initial_index in self.graph.node_indices() {
641 if node_states[initial_index.index()].is_some() {
643 continue;
644 }
645 visit_stack.push(VisitStep::UnvisitedNode(initial_index));
646 while let Some(step) = visit_stack.pop() {
647 match step {
648 VisitStep::UnvisitedNode(node) => {
649 node_states[node.index()] = Some(NodeState {
650 index,
651 lowlink: index,
652 on_stack: true,
653 });
654 index += 1;
655 stack.push(node);
656 visit_stack.push(VisitStep::AfterVisit(node));
657 let mut neighbors = self.graph.neighbors(node).detach();
658 while let Some((edge, succ)) = neighbors.next(&self.graph) {
659 let edge_weight = self.graph.edge_weight(edge).unwrap();
660 if !edge_filter(edge_weight) {
661 continue;
662 }
663 let node_state = &node_states[succ.index()];
664 if let Some(node_state) = node_state {
665 if node_state.on_stack {
666 let index = node_state.index;
667 let parent_state = node_states[node.index()].as_mut().unwrap();
668 parent_state.lowlink = parent_state.lowlink.min(index);
669 }
670 } else {
671 visit_stack.push(VisitStep::EdgeAfterVisit {
672 parent: node,
673 child: succ,
674 });
675 visit_stack.push(VisitStep::UnvisitedNode(succ));
676 }
677 }
678 }
679 VisitStep::EdgeAfterVisit { parent, child } => {
680 let child_state = node_states[child.index()].as_ref().unwrap();
681 let lowlink = child_state.lowlink;
682
683 let parent_state = node_states[parent.index()].as_mut().unwrap();
684 parent_state.lowlink = parent_state.lowlink.min(lowlink);
685 }
686 VisitStep::AfterVisit(node) => {
687 let node_state = node_states[node.index()].as_ref().unwrap();
688 if node_state.lowlink == node_state.index {
689 loop {
690 let poppped = stack.pop().unwrap();
691 let popped_state = node_states[poppped.index()].as_mut().unwrap();
692 popped_state.on_stack = false;
693 if let SingleModuleGraphNode::Module(module) =
694 self.graph.node_weight(poppped).unwrap()
695 {
696 scc.push(module);
697 }
698 if poppped == node {
699 break;
700 }
701 }
702 if scc.len() > 1 {
703 visit_cycle(&scc);
704 }
705 scc.clear();
706 }
707 }
708 }
709 }
710 }
711 }
712}
713
714#[turbo_tasks::value(shared)]
715#[derive(Clone, Default)]
716pub struct ModuleGraph {
717 pub graphs: Vec<ResolvedVc<SingleModuleGraph>>,
718}
719
720#[turbo_tasks::value_impl]
721impl ModuleGraph {
722 #[turbo_tasks::function]
723 pub fn from_graphs(graphs: Vec<ResolvedVc<SingleModuleGraph>>) -> Vc<Self> {
724 Self { graphs }.cell()
725 }
726
727 #[turbo_tasks::function]
728 pub fn from_single_graph(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
729 Self {
730 graphs: vec![graph],
731 }
732 .cell()
733 }
734
735 #[turbo_tasks::function]
736 pub fn from_entry_module(
737 module: ResolvedVc<Box<dyn Module>>,
738 include_traced: bool,
739 ) -> Vc<Self> {
740 Self::from_single_graph(SingleModuleGraph::new_with_entries(
741 Vc::cell(vec![ChunkGroupEntry::Entry(vec![module])]),
742 include_traced,
743 ))
744 }
745
746 #[turbo_tasks::function]
747 pub fn from_modules(modules: Vc<GraphEntries>, include_traced: bool) -> Vc<Self> {
748 Self::from_single_graph(SingleModuleGraph::new_with_entries(modules, include_traced))
749 }
750
751 #[turbo_tasks::function]
752 pub async fn chunk_group_info(&self) -> Result<Vc<ChunkGroupInfo>> {
753 compute_chunk_group_info(self).await
754 }
755
756 #[turbo_tasks::function]
757 pub async fn module_batches(
758 self: Vc<Self>,
759 config: Vc<BatchingConfig>,
760 ) -> Result<Vc<ModuleBatchesGraph>> {
761 compute_module_batches(self, &*config.await?).await
762 }
763
764 #[turbo_tasks::function]
765 pub async fn style_groups(
766 self: Vc<Self>,
767 chunking_context: Vc<Box<dyn ChunkingContext>>,
768 config: StyleGroupsConfig,
769 ) -> Result<Vc<StyleGroups>> {
770 compute_style_groups(self, chunking_context, &config).await
771 }
772
773 #[turbo_tasks::function]
774 pub async fn async_module_info(self: Vc<Self>) -> Result<Vc<AsyncModulesInfo>> {
775 async move {
778 let result_op = compute_async_module_info(self.to_resolved().await?);
779 let result_vc = result_op.resolve_strongly_consistent().await?;
780 let _issues = result_op.take_collectibles::<Box<dyn Issue>>();
781 anyhow::Ok(*result_vc)
782 }
783 .instrument(tracing::info_span!("compute async module info"))
784 .await
785 }
786
787 #[turbo_tasks::function]
788 pub async fn referenced_async_modules(
789 self: Vc<Self>,
790 module: ResolvedVc<Box<dyn Module>>,
791 ) -> Result<Vc<AsyncModuleInfo>> {
792 let this = self.await?;
793 let graphs = this.get_graphs().await?;
794 let async_modules_info = self.async_module_info().await?;
795
796 let entry = ModuleGraph::get_entry(&graphs, module).await?;
797 let referenced_modules = iter_neighbors_rev(&graphs[entry.graph_idx].graph, entry.node_idx)
798 .filter(|(edge_idx, _)| {
799 let ty = graphs[entry.graph_idx]
800 .graph
801 .edge_weight(*edge_idx)
802 .unwrap();
803 ty.is_inherit_async()
804 })
805 .map(|(_, child_idx)| {
806 anyhow::Ok(
807 get_node!(
808 graphs,
809 GraphNodeIndex {
810 graph_idx: entry.graph_idx,
811 node_idx: child_idx
812 }
813 )?
814 .module,
815 )
816 })
817 .collect::<Result<Vec<_>>>()?
818 .into_iter()
819 .rev()
820 .filter(|m| async_modules_info.contains(m))
821 .map(|m| *m)
822 .collect();
823
824 Ok(AsyncModuleInfo::new(referenced_modules))
825 }
826}
827
828macro_rules! get_node {
833 ($graphs:expr, $node:expr) => {{
834 let node_idx = $node;
835 match $graphs[node_idx.graph_idx]
836 .graph
837 .node_weight(node_idx.node_idx)
838 {
839 Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok(node),
840 Some(SingleModuleGraphNode::VisitedModule { idx, .. }) => {
841 match $graphs[idx.graph_idx].graph.node_weight(idx.node_idx) {
842 Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok(node),
843 Some(SingleModuleGraphNode::VisitedModule { .. }) => Err(::anyhow::anyhow!(
844 "Expected visited target node to be module"
845 )),
846 None => Err(::anyhow::anyhow!("Expected visited target node")),
847 }
848 }
849 None => Err(::anyhow::anyhow!("Expected graph node")),
850 }
851 }};
852}
853pub(crate) use get_node;
854macro_rules! get_node_idx {
855 ($graphs:expr, $node:expr) => {{
856 let node_idx = $node;
857 match $graphs[node_idx.graph_idx]
858 .graph
859 .node_weight(node_idx.node_idx)
860 {
861 Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok((node, node_idx)),
862 Some(SingleModuleGraphNode::VisitedModule { idx, .. }) => {
863 match $graphs[idx.graph_idx].graph.node_weight(idx.node_idx) {
864 Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok((node, *idx)),
865 Some(SingleModuleGraphNode::VisitedModule { .. }) => Err(::anyhow::anyhow!(
866 "Expected visited target node to be module"
867 )),
868 None => Err(::anyhow::anyhow!("Expected visited target node")),
869 }
870 }
871 None => Err(::anyhow::anyhow!("Expected graph node")),
872 }
873 }};
874}
875pub(crate) use get_node_idx;
876
877impl ModuleGraph {
878 pub async fn get_graphs(&self) -> Result<Vec<ReadRef<SingleModuleGraph>>> {
879 self.graphs.iter().try_join().await
880 }
881
882 async fn get_entry(
883 graphs: &[ReadRef<SingleModuleGraph>],
884 entry: ResolvedVc<Box<dyn Module>>,
885 ) -> Result<GraphNodeIndex> {
886 let Some(idx) = graphs.iter().enumerate().find_map(|(graph_idx, graph)| {
887 graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
888 graph_idx,
889 node_idx: *node_idx,
890 })
891 }) else {
892 bail!(
893 "Couldn't find entry module {} in module graph (potential entries: {:?})",
894 entry.ident().to_string().await?,
895 graphs
896 .iter()
897 .flat_map(|g| g.entries.iter())
898 .flat_map(|e| e.entries())
899 .map(|e| e.ident().to_string())
900 .try_join()
901 .await?
902 );
903 };
904 Ok(idx)
905 }
906
907 pub async fn traverse_edges_from_entries_bfs(
918 &self,
919 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
920 mut visitor: impl FnMut(
921 Option<(&'_ SingleModuleGraphModuleNode, &'_ ChunkingType)>,
922 &'_ SingleModuleGraphModuleNode,
923 ) -> GraphTraversalAction,
924 ) -> Result<()> {
925 let graphs = self.get_graphs().await?;
926
927 let mut queue = VecDeque::from(
928 entries
929 .into_iter()
930 .map(|e| ModuleGraph::get_entry(&graphs, e))
931 .try_join()
932 .await?,
933 );
934 let mut visited = HashSet::new();
935 for entry_node in &queue {
936 visitor(None, get_node!(graphs, entry_node)?);
937 }
938 while let Some(node) = queue.pop_front() {
939 let graph = &graphs[node.graph_idx].graph;
940 let node_weight = get_node!(graphs, node)?;
941 if visited.insert(node) {
942 let neighbors = iter_neighbors_rev(graph, node.node_idx);
943
944 for (edge, succ) in neighbors {
945 let succ = GraphNodeIndex {
946 graph_idx: node.graph_idx,
947 node_idx: succ,
948 };
949 let succ_weight = get_node!(graphs, succ)?;
950 let edge_weight = graph.edge_weight(edge).unwrap();
951 let action = visitor(Some((node_weight, edge_weight)), succ_weight);
952 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
953 queue.push_back(succ);
954 }
955 }
956 }
957 }
958
959 Ok(())
960 }
961
962 pub async fn traverse_edges_from_entry(
973 &self,
974 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
975 mut visitor: impl FnMut(
976 Option<(&'_ SingleModuleGraphModuleNode, &'_ ChunkingType)>,
977 &'_ SingleModuleGraphModuleNode,
978 ) -> GraphTraversalAction,
979 ) -> Result<()> {
980 let graphs = self.get_graphs().await?;
981
982 let entries = entries.into_iter();
983 let mut stack = Vec::with_capacity(entries.size_hint().0);
984 for entry in entries {
985 stack.push(ModuleGraph::get_entry(&graphs, entry).await?);
986 }
987 let mut visited = HashSet::new();
988 for entry_node in &stack {
989 visitor(None, get_node!(graphs, entry_node)?);
990 }
991 while let Some(node) = stack.pop() {
992 let graph = &graphs[node.graph_idx].graph;
993 let node_weight = get_node!(graphs, node)?;
994 if visited.insert(node) {
995 let neighbors = iter_neighbors_rev(graph, node.node_idx);
996
997 for (edge, succ) in neighbors {
998 let succ = GraphNodeIndex {
999 graph_idx: node.graph_idx,
1000 node_idx: succ,
1001 };
1002 let succ_weight = get_node!(graphs, succ)?;
1003 let edge_weight = graph.edge_weight(edge).unwrap();
1004 let action = visitor(Some((node_weight, edge_weight)), succ_weight);
1005 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1006 stack.push(succ);
1007 }
1008 }
1009 }
1010 }
1011
1012 Ok(())
1013 }
1014
1015 pub async fn traverse_all_edges_unordered(
1024 &self,
1025 mut visitor: impl FnMut(
1026 (&'_ SingleModuleGraphModuleNode, &'_ ChunkingType),
1027 &'_ SingleModuleGraphModuleNode,
1028 ) -> Result<()>,
1029 ) -> Result<()> {
1030 let graphs = self.get_graphs().await?;
1031
1032 for graph in &graphs {
1033 let graph = &graph.graph;
1034 for edge in graph.edge_references() {
1035 let source = match graph.node_weight(edge.source()).unwrap() {
1036 SingleModuleGraphNode::Module(node) => node,
1037 SingleModuleGraphNode::VisitedModule { .. } => unreachable!(),
1038 };
1039 let target = match graph.node_weight(edge.target()).unwrap() {
1040 SingleModuleGraphNode::Module(node) => node,
1041 SingleModuleGraphNode::VisitedModule { idx, .. } => get_node!(graphs, idx)?,
1042 };
1043 visitor((source, edge.weight()), target)?;
1044 }
1045 }
1046
1047 Ok(())
1048 }
1049
1050 pub async fn traverse_edges_from_entries_topological<S>(
1069 &self,
1070 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1071 state: &mut S,
1072 mut visit_preorder: impl FnMut(
1073 Option<(&'_ SingleModuleGraphModuleNode, &'_ ChunkingType)>,
1074 &'_ SingleModuleGraphModuleNode,
1075 &mut S,
1076 ) -> Result<GraphTraversalAction>,
1077 mut visit_postorder: impl FnMut(
1078 Option<(&'_ SingleModuleGraphModuleNode, &'_ ChunkingType)>,
1079 &'_ SingleModuleGraphModuleNode,
1080 &mut S,
1081 ),
1082 ) -> Result<()> {
1083 let graphs = self.get_graphs().await?;
1084
1085 let entries = entries.into_iter().collect::<Vec<_>>();
1086
1087 enum TopologicalPass {
1088 Visit,
1089 ExpandAndVisit,
1090 }
1091 #[allow(clippy::type_complexity)] let mut stack: Vec<(
1093 TopologicalPass,
1094 Option<(GraphNodeIndex, EdgeIndex)>,
1095 GraphNodeIndex,
1096 )> = Vec::with_capacity(entries.len());
1097 for entry in entries.into_iter().rev() {
1098 stack.push((
1099 TopologicalPass::ExpandAndVisit,
1100 None,
1101 ModuleGraph::get_entry(&graphs, entry).await?,
1102 ));
1103 }
1104 let mut expanded = HashSet::new();
1105 while let Some((pass, parent, current)) = stack.pop() {
1106 let parent_arg = match parent {
1107 Some((parent_node, parent_edge)) => Some((
1108 get_node!(graphs, parent_node)?,
1109 graphs[parent_node.graph_idx]
1110 .graph
1111 .edge_weight(parent_edge)
1112 .unwrap(),
1113 )),
1114 None => None,
1115 };
1116 let current_node = get_node!(graphs, current)?;
1117 match pass {
1118 TopologicalPass::Visit => {
1119 visit_postorder(parent_arg, current_node, state);
1120 }
1121 TopologicalPass::ExpandAndVisit => {
1122 let action = visit_preorder(parent_arg, current_node, state)?;
1123 if action == GraphTraversalAction::Exclude {
1124 continue;
1125 }
1126 stack.push((TopologicalPass::Visit, parent, current));
1127 if action == GraphTraversalAction::Continue && expanded.insert(current) {
1128 let graph = &graphs[current.graph_idx].graph;
1129 let (neighbors_rev, current) =
1130 match graph.node_weight(current.node_idx).unwrap() {
1131 SingleModuleGraphNode::Module(_) => {
1132 (iter_neighbors_rev(graph, current.node_idx), current)
1133 }
1134 SingleModuleGraphNode::VisitedModule { idx, .. } => (
1135 iter_neighbors_rev(&graphs[idx.graph_idx].graph, idx.node_idx),
1137 *idx,
1138 ),
1139 };
1140 stack.extend(neighbors_rev.map(|(edge, child)| {
1141 (
1142 TopologicalPass::ExpandAndVisit,
1143 Some((current, edge)),
1144 GraphNodeIndex {
1145 graph_idx: current.graph_idx,
1146 node_idx: child,
1147 },
1148 )
1149 }));
1150 }
1151 }
1152 }
1153 }
1154
1155 Ok(())
1156 }
1157
1158 pub async fn traverse_cycles(
1161 &self,
1162 edge_filter: impl Fn(&ChunkingType) -> bool,
1163 mut visit_cycle: impl FnMut(&[&SingleModuleGraphModuleNode]),
1164 ) -> Result<()> {
1165 for graph in &self.graphs {
1166 graph.await?.traverse_cycles(&edge_filter, &mut visit_cycle);
1167 }
1168 Ok(())
1169 }
1170
1171 pub async fn traverse_edges_fixed_point_with_priority<S, P: Ord>(
1193 &self,
1194 entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1195 state: &mut S,
1196 mut visit: impl FnMut(
1197 Option<(&'_ SingleModuleGraphModuleNode, &'_ ChunkingType)>,
1198 &'_ SingleModuleGraphModuleNode,
1199 &mut S,
1200 ) -> GraphTraversalAction,
1201 priority: impl Fn(&'_ SingleModuleGraphModuleNode, &mut S) -> P,
1202 ) -> Result<usize> {
1203 let graphs = self.get_graphs().await?;
1204
1205 #[derive(PartialEq, Eq)]
1206 struct NodeWithPriority<T: Ord> {
1207 node: GraphNodeIndex,
1208 priority: T,
1209 }
1210 impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1211 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1212 Some(self.cmp(other))
1213 }
1214 }
1215 impl<T: Ord> Ord for NodeWithPriority<T> {
1216 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1217 self.priority
1220 .cmp(&other.priority)
1221 .then(other.node.cmp(&self.node))
1223 }
1224 }
1225
1226 let mut queue_set = FxHashSet::default();
1227 let mut queue = BinaryHeap::from_iter(
1228 entries
1229 .into_iter()
1230 .map(async |(m, priority)| {
1231 Ok(NodeWithPriority {
1232 node: ModuleGraph::get_entry(&graphs, m).await?,
1233 priority,
1234 })
1235 })
1236 .try_join()
1237 .await?,
1238 );
1239 for entry_node in &queue {
1240 visit(None, get_node!(graphs, entry_node.node)?, state);
1241 }
1242
1243 let mut visit_count = 0usize;
1244 while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1245 queue_set.remove(&node);
1246 let (node_weight, node) = get_node_idx!(graphs, node)?;
1247 let graph = &graphs[node.graph_idx].graph;
1248 let neighbors = iter_neighbors_rev(graph, node.node_idx);
1249
1250 visit_count += 1;
1251
1252 for (edge, succ) in neighbors {
1253 let succ = GraphNodeIndex {
1254 graph_idx: node.graph_idx,
1255 node_idx: succ,
1256 };
1257 let (succ_weight, succ) = get_node_idx!(graphs, succ)?;
1258 let edge_weight = graph.edge_weight(edge).unwrap();
1259 let action = visit(Some((node_weight, edge_weight)), succ_weight, state);
1260
1261 if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1262 queue.push(NodeWithPriority {
1263 node: succ,
1264 priority: priority(succ_weight, state),
1265 });
1266 }
1267 }
1268 }
1269
1270 Ok(visit_count)
1271 }
1272}
1273
1274#[turbo_tasks::value_impl]
1275impl SingleModuleGraph {
1276 #[turbo_tasks::function]
1277 pub async fn new_with_entries(
1278 entries: Vc<GraphEntries>,
1279 include_traced: bool,
1280 ) -> Result<Vc<Self>> {
1281 SingleModuleGraph::new_inner(&*entries.await?, &Default::default(), include_traced).await
1282 }
1283
1284 #[turbo_tasks::function]
1285 pub async fn new_with_entries_visited(
1286 entries: Vc<GraphEntries>,
1287 visited_modules: Vc<VisitedModules>,
1288 include_traced: bool,
1289 ) -> Result<Vc<Self>> {
1290 SingleModuleGraph::new_inner(
1291 &*entries.await?,
1292 &visited_modules.await?.modules,
1293 include_traced,
1294 )
1295 .await
1296 }
1297
1298 #[turbo_tasks::function]
1299 pub async fn new_with_entries_visited_intern(
1300 entries: GraphEntriesT,
1302 visited_modules: Vc<VisitedModules>,
1303 include_traced: bool,
1304 ) -> Result<Vc<Self>> {
1305 SingleModuleGraph::new_inner(&entries, &visited_modules.await?.modules, include_traced)
1306 .await
1307 }
1308}
1309
1310#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1311pub struct SingleModuleGraphModuleNode {
1312 pub module: ResolvedVc<Box<dyn Module>>,
1313 pub layer: Option<ReadRef<RcStr>>,
1314 pub issues: Vec<ResolvedVc<Box<dyn Issue>>>,
1315}
1316
1317#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1318pub enum SingleModuleGraphNode {
1319 Module(SingleModuleGraphModuleNode),
1320 VisitedModule {
1321 idx: GraphNodeIndex,
1322 module: ResolvedVc<Box<dyn Module>>,
1323 },
1324}
1325
1326impl SingleModuleGraphNode {
1327 pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1328 match self {
1329 SingleModuleGraphNode::Module(SingleModuleGraphModuleNode { module, .. }) => *module,
1330 SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1331 }
1332 }
1333
1334 }
1345
1346#[derive(PartialEq, Eq, Debug)]
1347pub enum GraphTraversalAction {
1348 Continue,
1350 Skip,
1352 Exclude,
1354}
1355
1356#[derive(Clone, Hash, PartialEq, Eq)]
1359enum SingleModuleGraphBuilderNode {
1360 ChunkableReference {
1362 chunking_type: ChunkingType,
1363 source: ResolvedVc<Box<dyn Module>>,
1364 source_ident: ReadRef<RcStr>,
1365 target: ResolvedVc<Box<dyn Module>>,
1366 target_ident: ReadRef<RcStr>,
1367 target_layer: Option<ReadRef<RcStr>>,
1368 },
1369 Module {
1371 module: ResolvedVc<Box<dyn Module>>,
1372 layer: Option<ReadRef<RcStr>>,
1373 ident: ReadRef<RcStr>,
1374 },
1375 VisitedModule {
1377 module: ResolvedVc<Box<dyn Module>>,
1378 idx: GraphNodeIndex,
1379 },
1380 #[allow(dead_code)]
1382 Issues(Vec<ResolvedVc<Box<dyn Issue>>>),
1383}
1384
1385impl SingleModuleGraphBuilderNode {
1386 async fn new_module(module: ResolvedVc<Box<dyn Module>>) -> Result<Self> {
1387 let ident = module.ident();
1388 Ok(Self::Module {
1389 module,
1390 layer: match ident.await?.layer {
1391 Some(layer) => Some(layer.await?),
1392 None => None,
1393 },
1394 ident: ident.to_string().await?,
1395 })
1396 }
1397 async fn new_chunkable_ref(
1398 source: ResolvedVc<Box<dyn Module>>,
1399 target: ResolvedVc<Box<dyn Module>>,
1400 chunking_type: ChunkingType,
1401 ) -> Result<Self> {
1402 Ok(Self::ChunkableReference {
1403 chunking_type,
1404 source,
1405 source_ident: source.ident().to_string().await?,
1406 target,
1407 target_ident: target.ident().to_string().await?,
1408 target_layer: match target.ident().await?.layer {
1409 Some(layer) => Some(layer.await?),
1410 None => None,
1411 },
1412 })
1413 }
1414 fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1415 Self::VisitedModule { module, idx }
1416 }
1417}
1418struct SingleModuleGraphBuilderEdge {
1419 to: SingleModuleGraphBuilderNode,
1420}
1421
1422const COMMON_CHUNKING_TYPE: ChunkingType = ChunkingType::Parallel {
1425 inherit_async: true,
1426 hoisted: true,
1427};
1428
1429struct SingleModuleGraphBuilder<'a> {
1430 visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1431 include_traced: bool,
1433}
1434impl Visit<SingleModuleGraphBuilderNode> for SingleModuleGraphBuilder<'_> {
1435 type Edge = SingleModuleGraphBuilderEdge;
1436 type EdgesIntoIter = Vec<Self::Edge>;
1437 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1438
1439 fn visit(&mut self, edge: Self::Edge) -> VisitControlFlow<SingleModuleGraphBuilderNode> {
1440 match edge.to {
1441 SingleModuleGraphBuilderNode::Module { .. } => VisitControlFlow::Continue(edge.to),
1442 SingleModuleGraphBuilderNode::ChunkableReference {
1443 ref chunking_type, ..
1444 } => match chunking_type {
1445 ChunkingType::Traced => VisitControlFlow::Skip(edge.to),
1446 _ => VisitControlFlow::Continue(edge.to),
1447 },
1448 SingleModuleGraphBuilderNode::VisitedModule { .. } => VisitControlFlow::Skip(edge.to),
1450 SingleModuleGraphBuilderNode::Issues(_) => VisitControlFlow::Skip(edge.to),
1452 }
1453 }
1454
1455 fn edges(&mut self, node: &SingleModuleGraphBuilderNode) -> Self::EdgesFuture {
1456 let (module, chunkable_ref_target) = match node {
1458 SingleModuleGraphBuilderNode::Module { module, .. } => (Some(*module), None),
1459 SingleModuleGraphBuilderNode::ChunkableReference { target, .. } => {
1460 (None, Some(*target))
1461 }
1462 SingleModuleGraphBuilderNode::VisitedModule { .. }
1464 | SingleModuleGraphBuilderNode::Issues(_) => unreachable!(),
1465 };
1466 let visited_modules = self.visited_modules;
1467 let include_traced = self.include_traced;
1468 async move {
1469 Ok(match (module, chunkable_ref_target) {
1470 (Some(module), None) => {
1471 let refs_cell = primary_chunkable_referenced_modules(*module, include_traced);
1472 let refs = match refs_cell.await {
1473 Ok(refs) => refs,
1474 Err(e) => {
1475 return Err(e.context(module.ident().to_string().await?));
1476 }
1477 };
1478 refs.iter()
1487 .flat_map(|(ty, modules)| modules.iter().map(|m| (ty.clone(), *m)))
1488 .map(async |(ty, target)| {
1489 let to = if ty == COMMON_CHUNKING_TYPE {
1490 if let Some(idx) = visited_modules.get(&target) {
1491 SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1492 } else {
1493 SingleModuleGraphBuilderNode::new_module(target).await?
1494 }
1495 } else {
1496 SingleModuleGraphBuilderNode::new_chunkable_ref(module, target, ty)
1497 .await?
1498 };
1499 Ok(SingleModuleGraphBuilderEdge { to })
1500 })
1501 .try_join()
1502 .await?
1503 }
1504 (None, Some(chunkable_ref_target)) => {
1505 vec![SingleModuleGraphBuilderEdge {
1506 to: if let Some(idx) = visited_modules.get(&chunkable_ref_target) {
1507 SingleModuleGraphBuilderNode::new_visited_module(
1508 chunkable_ref_target,
1509 *idx,
1510 )
1511 } else {
1512 SingleModuleGraphBuilderNode::new_module(chunkable_ref_target).await?
1513 },
1514 }]
1515 }
1516 _ => unreachable!(),
1517 })
1518 }
1519 }
1520
1521 fn span(&mut self, node: &SingleModuleGraphBuilderNode) -> tracing::Span {
1522 match node {
1523 SingleModuleGraphBuilderNode::Module { ident, .. } => {
1524 tracing::info_span!("module", name = display(ident))
1525 }
1526 SingleModuleGraphBuilderNode::Issues(_) => {
1527 tracing::info_span!("issues")
1528 }
1529 SingleModuleGraphBuilderNode::ChunkableReference {
1530 chunking_type,
1531 source_ident,
1532 target_ident,
1533 ..
1534 } => match chunking_type {
1535 ChunkingType::Parallel {
1536 inherit_async: false,
1537 ..
1538 } => Span::current(),
1539 _ => {
1540 tracing::info_span!(
1541 "chunkable reference",
1542 ty = debug(chunking_type),
1543 source = display(source_ident),
1544 target = display(target_ident)
1545 )
1546 }
1547 },
1548 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1549 tracing::info_span!("visited module")
1550 }
1551 }
1552 }
1553}