1
- use rustc_data_structures:: fx:: FxHashSet ;
2
1
use rustc_middle:: mir;
3
2
use rustc_middle:: mir:: coverage:: { Mapping , MappingKind , START_BCB } ;
4
3
use rustc_middle:: ty:: TyCtxt ;
5
4
use rustc_span:: source_map:: SourceMap ;
6
- use rustc_span:: { BytePos , DesugaringKind , ExpnKind , MacroKind , Span } ;
5
+ use rustc_span:: { BytePos , DesugaringKind , ExpnId , ExpnKind , MacroKind , Span } ;
7
6
use tracing:: instrument;
8
7
8
+ use crate :: coverage:: expansion:: { self , ExpnTree , SpanWithBcb } ;
9
9
use crate :: coverage:: graph:: { BasicCoverageBlock , CoverageGraph } ;
10
10
use crate :: coverage:: hir_info:: ExtractedHirInfo ;
11
- use crate :: coverage:: spans:: from_mir:: { Hole , RawSpanFromMir , SpanFromMir } ;
12
- use crate :: coverage:: unexpand;
11
+ use crate :: coverage:: spans:: from_mir:: { Hole , RawSpanFromMir } ;
13
12
14
13
mod from_mir;
15
14
@@ -34,19 +33,51 @@ pub(super) fn extract_refined_covspans<'tcx>(
34
33
let & ExtractedHirInfo { body_span, .. } = hir_info;
35
34
36
35
let raw_spans = from_mir:: extract_raw_spans_from_mir ( mir_body, graph) ;
37
- let mut covspans = raw_spans
38
- . into_iter ( )
39
- . filter_map ( |RawSpanFromMir { raw_span, bcb } | try {
40
- let ( span, expn_kind) =
41
- unexpand:: unexpand_into_body_span_with_expn_kind ( raw_span, body_span) ?;
42
- // Discard any spans that fill the entire body, because they tend
43
- // to represent compiler-inserted code, e.g. implicitly returning `()`.
44
- if span. source_equal ( body_span) {
45
- return None ;
46
- } ;
47
- SpanFromMir { span, expn_kind, bcb }
48
- } )
49
- . collect :: < Vec < _ > > ( ) ;
36
+ // Use the raw spans to build a tree of expansions for this function.
37
+ let expn_tree = expansion:: build_expn_tree (
38
+ raw_spans
39
+ . into_iter ( )
40
+ . map ( |RawSpanFromMir { raw_span, bcb } | SpanWithBcb { span : raw_span, bcb } ) ,
41
+ ) ;
42
+
43
+ let mut covspans = vec ! [ ] ;
44
+ let mut push_covspan = |covspan : Covspan | {
45
+ let covspan_span = covspan. span ;
46
+ // Discard any spans not contained within the function body span.
47
+ // Also discard any spans that fill the entire body, because they tend
48
+ // to represent compiler-inserted code, e.g. implicitly returning `()`.
49
+ if !body_span. contains ( covspan_span) || body_span. source_equal ( covspan_span) {
50
+ return ;
51
+ }
52
+
53
+ // Each pushed covspan should have the same context as the body span.
54
+ // If it somehow doesn't, discard the covspan, or panic in debug builds.
55
+ if !body_span. eq_ctxt ( covspan_span) {
56
+ debug_assert ! (
57
+ false ,
58
+ "span context mismatch: body_span={body_span:?}, covspan.span={covspan_span:?}"
59
+ ) ;
60
+ return ;
61
+ }
62
+
63
+ covspans. push ( covspan) ;
64
+ } ;
65
+
66
+ if let Some ( node) = expn_tree. get ( body_span. ctxt ( ) . outer_expn ( ) ) {
67
+ for & SpanWithBcb { span, bcb } in & node. spans {
68
+ push_covspan ( Covspan { span, bcb } ) ;
69
+ }
70
+
71
+ // For each expansion with its call-site in the body span, try to
72
+ // distill a corresponding covspan.
73
+ for & child_expn_id in & node. child_expn_ids {
74
+ if let Some ( covspan) =
75
+ single_covspan_for_child_expn ( tcx, graph, & expn_tree, child_expn_id)
76
+ {
77
+ push_covspan ( covspan) ;
78
+ }
79
+ }
80
+ }
50
81
51
82
// Only proceed if we found at least one usable span.
52
83
if covspans. is_empty ( ) {
@@ -57,17 +88,10 @@ pub(super) fn extract_refined_covspans<'tcx>(
57
88
// Otherwise, add a fake span at the start of the body, to avoid an ugly
58
89
// gap between the start of the body and the first real span.
59
90
// FIXME: Find a more principled way to solve this problem.
60
- covspans. push ( SpanFromMir :: for_fn_sig (
61
- hir_info. fn_sig_span . unwrap_or_else ( || body_span. shrink_to_lo ( ) ) ,
62
- ) ) ;
63
-
64
- // First, perform the passes that need macro information.
65
- covspans. sort_by ( |a, b| graph. cmp_in_dominator_order ( a. bcb , b. bcb ) ) ;
66
- remove_unwanted_expansion_spans ( & mut covspans) ;
67
- shrink_visible_macro_spans ( tcx, & mut covspans) ;
68
-
69
- // We no longer need the extra information in `SpanFromMir`, so convert to `Covspan`.
70
- let mut covspans = covspans. into_iter ( ) . map ( SpanFromMir :: into_covspan) . collect :: < Vec < _ > > ( ) ;
91
+ covspans. push ( Covspan {
92
+ span : hir_info. fn_sig_span . unwrap_or_else ( || body_span. shrink_to_lo ( ) ) ,
93
+ bcb : START_BCB ,
94
+ } ) ;
71
95
72
96
let compare_covspans = |a : & Covspan , b : & Covspan | {
73
97
compare_spans ( a. span , b. span )
@@ -117,43 +141,37 @@ pub(super) fn extract_refined_covspans<'tcx>(
117
141
} ) ) ;
118
142
}
119
143
120
- /// Macros that expand into branches (e.g. `assert!`, `trace!`) tend to generate
121
- /// multiple condition/consequent blocks that have the span of the whole macro
122
- /// invocation, which is unhelpful. Keeping only the first such span seems to
123
- /// give better mappings, so remove the others.
124
- ///
125
- /// Similarly, `await` expands to a branch on the discriminant of `Poll`, which
126
- /// leads to incorrect coverage if the `Future` is immediately ready (#98712).
127
- ///
128
- /// (The input spans should be sorted in BCB dominator order, so that the
129
- /// retained "first" span is likely to dominate the others.)
130
- fn remove_unwanted_expansion_spans ( covspans : & mut Vec < SpanFromMir > ) {
131
- let mut deduplicated_spans = FxHashSet :: default ( ) ;
132
-
133
- covspans. retain ( |covspan| {
134
- match covspan. expn_kind {
135
- // Retain only the first await-related or macro-expanded covspan with this span.
136
- Some ( ExpnKind :: Desugaring ( DesugaringKind :: Await ) ) => {
137
- deduplicated_spans. insert ( covspan. span )
138
- }
139
- Some ( ExpnKind :: Macro ( MacroKind :: Bang , _) ) => deduplicated_spans. insert ( covspan. span ) ,
140
- // Ignore (retain) other spans.
141
- _ => true ,
144
+ /// For a single child expansion, try to distill it into a single span+BCB mapping.
145
+ fn single_covspan_for_child_expn (
146
+ tcx : TyCtxt < ' _ > ,
147
+ graph : & CoverageGraph ,
148
+ expn_tree : & ExpnTree ,
149
+ expn_id : ExpnId ,
150
+ ) -> Option < Covspan > {
151
+ let node = expn_tree. get ( expn_id) ?;
152
+
153
+ let bcbs =
154
+ expn_tree. iter_node_and_descendants ( expn_id) . flat_map ( |n| n. spans . iter ( ) . map ( |s| s. bcb ) ) ;
155
+
156
+ let bcb = match node. expn_kind {
157
+ // For bang-macros (e.g. `assert!`, `trace!`) and for `await`, taking
158
+ // the "first" BCB in dominator order seems to give good results.
159
+ ExpnKind :: Macro ( MacroKind :: Bang , _) | ExpnKind :: Desugaring ( DesugaringKind :: Await ) => {
160
+ bcbs. min_by ( |& a, & b| graph. cmp_in_dominator_order ( a, b) ) ?
142
161
}
143
- } ) ;
144
- }
145
-
146
- /// When a span corresponds to a macro invocation that is visible from the
147
- /// function body, truncate it to just the macro name plus `!`.
148
- /// This seems to give better results for code that uses macros.
149
- fn shrink_visible_macro_spans ( tcx : TyCtxt < ' _ > , covspans : & mut Vec < SpanFromMir > ) {
150
- let source_map = tcx. sess . source_map ( ) ;
162
+ // For other kinds of expansion, taking the "last" (most-dominated) BCB
163
+ // seems to give good results.
164
+ _ => bcbs. max_by ( |& a, & b| graph. cmp_in_dominator_order ( a, b) ) ?,
165
+ } ;
151
166
152
- for covspan in covspans {
153
- if matches ! ( covspan. expn_kind, Some ( ExpnKind :: Macro ( MacroKind :: Bang , _) ) ) {
154
- covspan. span = source_map. span_through_char ( covspan. span , '!' ) ;
155
- }
167
+ // For bang-macro expansions, limit the call-site span to just the macro
168
+ // name plus `!`, excluding the macro arguments.
169
+ let mut span = node. call_site ?;
170
+ if matches ! ( node. expn_kind, ExpnKind :: Macro ( MacroKind :: Bang , _) ) {
171
+ span = tcx. sess . source_map ( ) . span_through_char ( span, '!' ) ;
156
172
}
173
+
174
+ Some ( Covspan { span, bcb } )
157
175
}
158
176
159
177
/// Discard all covspans that overlap a hole.
0 commit comments