| 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<ExpnId, ExpnNode>, |
| } |
| |
| 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<Item = &ExpnNode> { |
| 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<Span>, |
| /// 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<ExpnId>, |
| |
| /// Spans (and their associated BCBs) belonging to this expansion. |
| pub(crate) spans: Vec<SpanWithBcb>, |
| /// Expansions whose call-site is in this expansion. |
| pub(crate) child_expn_ids: FxIndexSet<ExpnId>, |
| } |
| |
| 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.clone(), |
| 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<Item = SpanWithBcb>) -> 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 } |
| } |