use rustc_data_structures::fx::{FxIndexMap, FxIndexSet, IndexEntry}; use rustc_middle::mir::coverage::BasicCoverageBlock; use rustc_span::{ExpnId, ExpnKind, Span}; #[derive(Clone, Copy, Debug)] pub(crate) struct SpanWithBcb { pub(crate) span: Span, pub(crate) bcb: BasicCoverageBlock, } #[derive(Debug)] pub(crate) struct ExpnTree { nodes: FxIndexMap, } impl ExpnTree { pub(crate) fn get(&self, expn_id: ExpnId) -> Option<&ExpnNode> { self.nodes.get(&expn_id) } /// Yields the tree node for the given expansion ID (if present), followed /// by the nodes of all of its descendants in depth-first order. pub(crate) fn iter_node_and_descendants( &self, root_expn_id: ExpnId, ) -> impl Iterator { gen move { let Some(root_node) = self.get(root_expn_id) else { return }; yield root_node; // Stack of child-node-ID iterators that drives the depth-first traversal. let mut iter_stack = vec![root_node.child_expn_ids.iter()]; while let Some(curr_iter) = iter_stack.last_mut() { // Pull the next ID from the top of the stack. let Some(&curr_id) = curr_iter.next() else { iter_stack.pop(); continue; }; // Yield this node. let Some(node) = self.get(curr_id) else { continue }; yield node; // Push the node's children, to be traversed next. if !node.child_expn_ids.is_empty() { iter_stack.push(node.child_expn_ids.iter()); } } } } } #[derive(Debug)] pub(crate) struct ExpnNode { /// Storing the expansion ID in its own node is not strictly necessary, /// but is helpful for debugging and might be useful later. #[expect(dead_code)] pub(crate) expn_id: ExpnId, // Useful info extracted from `ExpnData`. pub(crate) expn_kind: ExpnKind, /// Non-dummy `ExpnData::call_site` span. pub(crate) call_site: Option, /// Expansion ID of `call_site`, if present. /// This links an expansion node to its parent in the tree. pub(crate) call_site_expn_id: Option, /// Spans (and their associated BCBs) belonging to this expansion. pub(crate) spans: Vec, /// Expansions whose call-site is in this expansion. pub(crate) child_expn_ids: FxIndexSet, } impl ExpnNode { fn new(expn_id: ExpnId) -> Self { let expn_data = expn_id.expn_data(); let call_site = Some(expn_data.call_site).filter(|sp| !sp.is_dummy()); let call_site_expn_id = try { call_site?.ctxt().outer_expn() }; Self { expn_id, expn_kind: expn_data.kind, call_site, call_site_expn_id, spans: vec![], child_expn_ids: FxIndexSet::default(), } } } /// Given a collection of span/BCB pairs from potentially-different syntax contexts, /// arranges them into an "expansion tree" based on their expansion call-sites. pub(crate) fn build_expn_tree(spans: impl IntoIterator) -> ExpnTree { let mut nodes = FxIndexMap::default(); let new_node = |&expn_id: &ExpnId| ExpnNode::new(expn_id); for span_with_bcb in spans { // Create a node for this span's enclosing expansion, and add the span to it. let expn_id = span_with_bcb.span.ctxt().outer_expn(); let node = nodes.entry(expn_id).or_insert_with_key(new_node); node.spans.push(span_with_bcb); // Now walk up the expansion call-site chain, creating nodes and registering children. let mut prev = expn_id; let mut curr_expn_id = node.call_site_expn_id; while let Some(expn_id) = curr_expn_id { let entry = nodes.entry(expn_id); let node_existed = matches!(entry, IndexEntry::Occupied(_)); let node = entry.or_insert_with_key(new_node); node.child_expn_ids.insert(prev); if node_existed { break; } prev = expn_id; curr_expn_id = node.call_site_expn_id; } } ExpnTree { nodes } }