Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 84a1747

Browse files
committed
Auto merge of #146077 - Zalathar:rollup-l7ip5yi, r=Zalathar
Rollup of 5 pull requests Successful merges: - #145468 (dedup recip, powi, to_degrees, and to_radians float tests) - #145643 (coverage: Build an "expansion tree" and use it to unexpand raw spans) - #145754 (fix(lexer): Don't require frontmatters to be escaped with indented fences) - #146060 (fixup nix dev shell again) - #146068 (compiletest: Capture panic messages via a custom panic hook) r? `@ghost` `@rustbot` modify labels: rollup
2 parents be4e9b7 + 92bc467 commit 84a1747

File tree

28 files changed

+597
-479
lines changed

28 files changed

+597
-479
lines changed

‎compiler/rustc_lexer/src/lib.rs

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -550,28 +550,20 @@ impl Cursor<'_> {
550550
self.eat_while(|ch| ch != '\n' && is_whitespace(ch));
551551
let invalid_infostring = self.first() != '\n';
552552

553-
let mut s = self.as_str();
554553
let mut found = false;
555-
let mut size = 0;
556-
while let Some(closing) = s.find(&"-".repeat(length_opening as usize)) {
557-
let preceding_chars_start = s[..closing].rfind("\n").map_or(0, |i| i + 1);
558-
if s[preceding_chars_start..closing].chars().all(is_whitespace) {
559-
// candidate found
560-
self.bump_bytes(size + closing);
561-
// in case like
562-
// ---cargo
563-
// --- blahblah
564-
// or
565-
// ---cargo
566-
// ----
567-
// combine those stuff into this frontmatter token such that it gets detected later.
568-
self.eat_until(b'\n');
569-
found = true;
570-
break;
571-
} else {
572-
s = &s[closing + length_opening as usize..];
573-
size += closing + length_opening as usize;
574-
}
554+
let nl_fence_pattern = format!("\n{:-<1$}", "", length_opening as usize);
555+
if let Some(closing) = self.as_str().find(&nl_fence_pattern) {
556+
// candidate found
557+
self.bump_bytes(closing + nl_fence_pattern.len());
558+
// in case like
559+
// ---cargo
560+
// --- blahblah
561+
// or
562+
// ---cargo
563+
// ----
564+
// combine those stuff into this frontmatter token such that it gets detected later.
565+
self.eat_until(b'\n');
566+
found = true;
575567
}
576568

577569
if !found {
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet, IndexEntry};
2+
use rustc_middle::mir::coverage::BasicCoverageBlock;
3+
use rustc_span::{ExpnId, ExpnKind, Span};
4+
5+
#[derive(Clone, Copy, Debug)]
6+
pub(crate) struct SpanWithBcb {
7+
pub(crate) span: Span,
8+
pub(crate) bcb: BasicCoverageBlock,
9+
}
10+
11+
#[derive(Debug)]
12+
pub(crate) struct ExpnTree {
13+
nodes: FxIndexMap<ExpnId, ExpnNode>,
14+
}
15+
16+
impl ExpnTree {
17+
pub(crate) fn get(&self, expn_id: ExpnId) -> Option<&ExpnNode> {
18+
self.nodes.get(&expn_id)
19+
}
20+
21+
/// Yields the tree node for the given expansion ID (if present), followed
22+
/// by the nodes of all of its descendants in depth-first order.
23+
pub(crate) fn iter_node_and_descendants(
24+
&self,
25+
root_expn_id: ExpnId,
26+
) -> impl Iterator<Item = &ExpnNode> {
27+
gen move {
28+
let Some(root_node) = self.get(root_expn_id) else { return };
29+
yield root_node;
30+
31+
// Stack of child-node-ID iterators that drives the depth-first traversal.
32+
let mut iter_stack = vec![root_node.child_expn_ids.iter()];
33+
34+
while let Some(curr_iter) = iter_stack.last_mut() {
35+
// Pull the next ID from the top of the stack.
36+
let Some(&curr_id) = curr_iter.next() else {
37+
iter_stack.pop();
38+
continue;
39+
};
40+
41+
// Yield this node.
42+
let Some(node) = self.get(curr_id) else { continue };
43+
yield node;
44+
45+
// Push the node's children, to be traversed next.
46+
if !node.child_expn_ids.is_empty() {
47+
iter_stack.push(node.child_expn_ids.iter());
48+
}
49+
}
50+
}
51+
}
52+
}
53+
54+
#[derive(Debug)]
55+
pub(crate) struct ExpnNode {
56+
/// Storing the expansion ID in its own node is not strictly necessary,
57+
/// but is helpful for debugging and might be useful later.
58+
#[expect(dead_code)]
59+
pub(crate) expn_id: ExpnId,
60+
61+
// Useful info extracted from `ExpnData`.
62+
pub(crate) expn_kind: ExpnKind,
63+
/// Non-dummy `ExpnData::call_site` span.
64+
pub(crate) call_site: Option<Span>,
65+
/// Expansion ID of `call_site`, if present.
66+
/// This links an expansion node to its parent in the tree.
67+
pub(crate) call_site_expn_id: Option<ExpnId>,
68+
69+
/// Spans (and their associated BCBs) belonging to this expansion.
70+
pub(crate) spans: Vec<SpanWithBcb>,
71+
/// Expansions whose call-site is in this expansion.
72+
pub(crate) child_expn_ids: FxIndexSet<ExpnId>,
73+
}
74+
75+
impl ExpnNode {
76+
fn new(expn_id: ExpnId) -> Self {
77+
let expn_data = expn_id.expn_data();
78+
79+
let call_site = Some(expn_data.call_site).filter(|sp| !sp.is_dummy());
80+
let call_site_expn_id = try { call_site?.ctxt().outer_expn() };
81+
82+
Self {
83+
expn_id,
84+
85+
expn_kind: expn_data.kind.clone(),
86+
call_site,
87+
call_site_expn_id,
88+
89+
spans: vec![],
90+
child_expn_ids: FxIndexSet::default(),
91+
}
92+
}
93+
}
94+
95+
/// Given a collection of span/BCB pairs from potentially-different syntax contexts,
96+
/// arranges them into an "expansion tree" based on their expansion call-sites.
97+
pub(crate) fn build_expn_tree(spans: impl IntoIterator<Item = SpanWithBcb>) -> ExpnTree {
98+
let mut nodes = FxIndexMap::default();
99+
let new_node = |&expn_id: &ExpnId| ExpnNode::new(expn_id);
100+
101+
for span_with_bcb in spans {
102+
// Create a node for this span's enclosing expansion, and add the span to it.
103+
let expn_id = span_with_bcb.span.ctxt().outer_expn();
104+
let node = nodes.entry(expn_id).or_insert_with_key(new_node);
105+
node.spans.push(span_with_bcb);
106+
107+
// Now walk up the expansion call-site chain, creating nodes and registering children.
108+
let mut prev = expn_id;
109+
let mut curr_expn_id = node.call_site_expn_id;
110+
while let Some(expn_id) = curr_expn_id {
111+
let entry = nodes.entry(expn_id);
112+
let node_existed = matches!(entry, IndexEntry::Occupied(_));
113+
114+
let node = entry.or_insert_with_key(new_node);
115+
node.child_expn_ids.insert(prev);
116+
117+
if node_existed {
118+
break;
119+
}
120+
121+
prev = expn_id;
122+
curr_expn_id = node.call_site_expn_id;
123+
}
124+
}
125+
126+
ExpnTree { nodes }
127+
}

‎compiler/rustc_mir_transform/src/coverage/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::coverage::graph::CoverageGraph;
88
use crate::coverage::mappings::ExtractedMappings;
99

1010
mod counters;
11+
mod expansion;
1112
mod graph;
1213
mod hir_info;
1314
mod mappings;

‎compiler/rustc_mir_transform/src/coverage/spans.rs

Lines changed: 80 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
use rustc_data_structures::fx::FxHashSet;
21
use rustc_middle::mir;
32
use rustc_middle::mir::coverage::{Mapping, MappingKind, START_BCB};
43
use rustc_middle::ty::TyCtxt;
54
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};
76
use tracing::instrument;
87

8+
use crate::coverage::expansion::{self, ExpnTree, SpanWithBcb};
99
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
1010
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};
1312

1413
mod 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

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /