1- use rustc_data_structures:: fx:: FxHashSet ;
21use rustc_middle:: mir;
32use rustc_middle:: mir:: coverage:: { Mapping , MappingKind , START_BCB } ;
43use rustc_middle:: ty:: TyCtxt ;
54use rustc_span:: source_map:: SourceMap ;
6- use rustc_span:: { BytePos , DesugaringKind , ExpnKind , MacroKind , Span } ;
5+ use rustc_span:: { BytePos , DesugaringKind , ExpnId , ExpnKind , MacroKind , Span } ;
76use tracing:: instrument;
87
8+ use crate :: coverage:: expansion:: { self , ExpnTree , SpanWithBcb } ;
99use crate :: coverage:: graph:: { BasicCoverageBlock , CoverageGraph } ;
1010use 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 } ;
1312
1413mod from_mir;
1514
@@ -34,19 +33,51 @@ pub(super) fn extract_refined_covspans<'tcx>(
3433 let & ExtractedHirInfo { body_span, .. } = hir_info;
3534
3635 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+ }
5081
5182 // Only proceed if we found at least one usable span.
5283 if covspans. is_empty ( ) {
@@ -57,17 +88,10 @@ pub(super) fn extract_refined_covspans<'tcx>(
5788 // Otherwise, add a fake span at the start of the body, to avoid an ugly
5889 // gap between the start of the body and the first real span.
5990 // 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+ } ) ;
7195
7296 let compare_covspans = |a : & Covspan , b : & Covspan | {
7397 compare_spans ( a. span , b. span )
@@ -117,43 +141,37 @@ pub(super) fn extract_refined_covspans<'tcx>(
117141 } ) ) ;
118142}
119143
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) ) ?
142161 }
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+ } ;
151166
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, '!' ) ;
156172 }
173+
174+ Some ( Covspan { span, bcb } )
157175}
158176
159177/// Discard all covspans that overlap a hole.
0 commit comments