rustc_mir_transform/coverage/
expansion.rs1use rustc_data_structures::fx::{FxIndexMap, FxIndexSet, IndexEntry};
2use rustc_middle::mir;
3use rustc_middle::mir::coverage::{BasicCoverageBlock, BranchSpan};
4use rustc_span::{ExpnId, ExpnKind, Span};
5
6use crate::coverage::from_mir;
7use crate::coverage::graph::CoverageGraph;
8use crate::coverage::hir_info::ExtractedHirInfo;
9
10#[derive(Clone, Copy, Debug)]
11pub(crate) struct SpanWithBcb {
12 pub(crate) span: Span,
13 pub(crate) bcb: BasicCoverageBlock,
14}
15
16#[derive(Debug)]
17pub(crate) struct ExpnTree {
18 nodes: FxIndexMap<ExpnId, ExpnNode>,
19}
20
21impl ExpnTree {
22 pub(crate) fn get(&self, expn_id: ExpnId) -> Option<&ExpnNode> {
23 self.nodes.get(&expn_id)
24 }
25
26 pub(crate) fn iter_node_and_descendants(
29 &self,
30 root_expn_id: ExpnId,
31 ) -> impl Iterator<Item = &ExpnNode> {
32 gen move {
33 let Some(root_node) = self.get(root_expn_id) else { return };
34 yield root_node;
35
36 let mut iter_stack = vec![root_node.child_expn_ids.iter()];
38
39 while let Some(curr_iter) = iter_stack.last_mut() {
40 let Some(&curr_id) = curr_iter.next() else {
42 iter_stack.pop();
43 continue;
44 };
45
46 let Some(node) = self.get(curr_id) else { continue };
48 yield node;
49
50 if !node.child_expn_ids.is_empty() {
52 iter_stack.push(node.child_expn_ids.iter());
53 }
54 }
55 }
56 }
57}
58
59#[derive(Debug)]
60pub(crate) struct ExpnNode {
61 #[expect(dead_code)]
64 pub(crate) expn_id: ExpnId,
65
66 pub(crate) expn_kind: ExpnKind,
68 pub(crate) call_site: Option<Span>,
70 pub(crate) call_site_expn_id: Option<ExpnId>,
73
74 pub(crate) fn_sig_span: Option<Span>,
77 pub(crate) body_span: Option<Span>,
80
81 pub(crate) spans: Vec<SpanWithBcb>,
83 pub(crate) child_expn_ids: FxIndexSet<ExpnId>,
85
86 pub(crate) branch_spans: Vec<BranchSpan>,
88
89 pub(crate) hole_spans: Vec<Span>,
92}
93
94impl ExpnNode {
95 fn new(expn_id: ExpnId) -> Self {
96 let expn_data = expn_id.expn_data();
97
98 let call_site = Some(expn_data.call_site).filter(|sp| !sp.is_dummy());
99 let call_site_expn_id = try { call_site?.ctxt().outer_expn() };
100
101 Self {
102 expn_id,
103
104 expn_kind: expn_data.kind,
105 call_site,
106 call_site_expn_id,
107
108 fn_sig_span: None,
109 body_span: None,
110
111 spans: vec![],
112 child_expn_ids: FxIndexSet::default(),
113
114 branch_spans: vec![],
115
116 hole_spans: vec![],
117 }
118 }
119}
120
121pub(crate) fn build_expn_tree(
124 mir_body: &mir::Body<'_>,
125 hir_info: &ExtractedHirInfo,
126 graph: &CoverageGraph,
127) -> ExpnTree {
128 let raw_spans = from_mir::extract_raw_spans_from_mir(mir_body, graph);
129
130 let mut nodes = FxIndexMap::default();
131 let new_node = |&expn_id: &ExpnId| ExpnNode::new(expn_id);
132
133 for from_mir::RawSpanFromMir { raw_span, bcb } in raw_spans {
134 let span_with_bcb = SpanWithBcb { span: raw_span, bcb };
135
136 let expn_id = span_with_bcb.span.ctxt().outer_expn();
138 let node = nodes.entry(expn_id).or_insert_with_key(new_node);
139 node.spans.push(span_with_bcb);
140
141 let mut prev = expn_id;
143 let mut curr_expn_id = node.call_site_expn_id;
144 while let Some(expn_id) = curr_expn_id {
145 let entry = nodes.entry(expn_id);
146 let node_existed = matches!(entry, IndexEntry::Occupied(_));
147
148 let node = entry.or_insert_with_key(new_node);
149 node.child_expn_ids.insert(prev);
150
151 if node_existed {
152 break;
153 }
154
155 prev = expn_id;
156 curr_expn_id = node.call_site_expn_id;
157 }
158 }
159
160 if let Some(fn_sig_span) = hir_info.fn_sig_span
163 && let Some(node) = nodes.get_mut(&fn_sig_span.ctxt().outer_expn())
164 {
165 node.fn_sig_span = Some(fn_sig_span);
166 }
167
168 let body_span = hir_info.body_span;
170 if let Some(node) = nodes.get_mut(&body_span.ctxt().outer_expn()) {
171 node.body_span = Some(body_span);
172 }
173
174 for &hole_span in &hir_info.hole_spans {
177 let expn_id = hole_span.ctxt().outer_expn();
178 let Some(node) = nodes.get_mut(&expn_id) else { continue };
179 node.hole_spans.push(hole_span);
180 }
181
182 if let Some(coverage_info_hi) = mir_body.coverage_info_hi.as_deref() {
185 for branch_span in &coverage_info_hi.branch_spans {
186 if let Some(node) = nodes.get_mut(&branch_span.span.ctxt().outer_expn()) {
187 node.branch_spans.push(BranchSpan::clone(branch_span));
188 }
189 }
190 }
191
192 ExpnTree { nodes }
193}