|
1 | 1 | use rustc_data_structures::fx::FxHashSet;
|
2 | 2 | use rustc_middle::mir;
|
3 | 3 | use rustc_middle::ty::TyCtxt;
|
4 |
| -use rustc_span::{DesugaringKind, ExpnKind, MacroKind, Span}; |
| 4 | +use rustc_span::source_map::SourceMap; |
| 5 | +use rustc_span::{BytePos, DesugaringKind, ExpnKind, MacroKind, Span}; |
5 | 6 | use tracing::instrument;
|
6 | 7 |
|
7 | 8 | use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
|
@@ -83,8 +84,18 @@ pub(super) fn extract_refined_covspans<'tcx>(
|
83 | 84 | // Discard any span that overlaps with a hole.
|
84 | 85 | discard_spans_overlapping_holes(&mut covspans, &holes);
|
85 | 86 |
|
86 |
| - // Perform more refinement steps after holes have been dealt with. |
| 87 | + // Discard spans that overlap in unwanted ways. |
87 | 88 | let mut covspans = remove_unwanted_overlapping_spans(covspans);
|
| 89 | + |
| 90 | + // For all empty spans, either enlarge them to be non-empty, or discard them. |
| 91 | + let source_map = tcx.sess.source_map(); |
| 92 | + covspans.retain_mut(|covspan| { |
| 93 | + let Some(span) = ensure_non_empty_span(source_map, covspan.span) else { return false }; |
| 94 | + covspan.span = span; |
| 95 | + true |
| 96 | + }); |
| 97 | + |
| 98 | + // Merge covspans that can be merged. |
88 | 99 | covspans.dedup_by(|b, a| a.merge_if_eligible(b));
|
89 | 100 |
|
90 | 101 | code_mappings.extend(covspans.into_iter().map(|Covspan { span, bcb }| {
|
@@ -230,3 +241,26 @@ fn compare_spans(a: Span, b: Span) -> std::cmp::Ordering {
|
230 | 241 | // - Both have the same start and span A extends further right
|
231 | 242 | .then_with(|| Ord::cmp(&a.hi(), &b.hi()).reverse())
|
232 | 243 | }
|
| 244 | + |
| 245 | +fn ensure_non_empty_span(source_map: &SourceMap, span: Span) -> Option<Span> { |
| 246 | + if !span.is_empty() { |
| 247 | + return Some(span); |
| 248 | + } |
| 249 | + |
| 250 | + // The span is empty, so try to enlarge it to cover an adjacent '{' or '}'. |
| 251 | + source_map |
| 252 | + .span_to_source(span, |src, start, end| try { |
| 253 | + // Adjusting span endpoints by `BytePos(1)` is normally a bug, |
| 254 | + // but in this case we have specifically checked that the character |
| 255 | + // we're skipping over is one of two specific ASCII characters, so |
| 256 | + // adjusting by exactly 1 byte is correct. |
| 257 | + if src.as_bytes().get(end).copied() == Some(b'{') { |
| 258 | + Some(span.with_hi(span.hi() + BytePos(1))) |
| 259 | + } else if start > 0 && src.as_bytes()[start - 1] == b'}' { |
| 260 | + Some(span.with_lo(span.lo() - BytePos(1))) |
| 261 | + } else { |
| 262 | + None |
| 263 | + } |
| 264 | + }) |
| 265 | + .ok()? |
| 266 | +} |
0 commit comments