Skip to content

Commit 81ccf14

Browse files
committed
don't emit suggestions with overlapping spans and count omitted suggestions
1 parent b306a30 commit 81ccf14

File tree

6 files changed

+86
-7
lines changed

6 files changed

+86
-7
lines changed

compiler/rustc_errors/src/emitter.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2038,20 +2038,31 @@ impl HumanEmitter {
20382038
};
20392039

20402040
// Render the replacements for each suggestion
2041-
let suggestions = suggestion.splice_lines(sm);
2041+
let (suggestions, num_omitted) = suggestion.splice_lines(sm);
20422042
debug!(?suggestions);
20432043

2044+
let mut buffer = StyledBuffer::new();
2045+
2046+
if num_omitted.0 > 0 {
2047+
// in this case, do push that we omitted some
2048+
buffer.append(
2049+
0,
2050+
&format!("rendering error: omitted {} suggestion{} that failed to render, likely because of macro expansions", num_omitted.0, if num_omitted.0 > 1 {"s"} else {""}),
2051+
Style::Level(Level::Error),
2052+
);
2053+
}
2054+
20442055
if suggestions.is_empty() {
20452056
// Here we check if there are suggestions that have actual code changes. We sometimes
20462057
// suggest the same code that is already there, instead of changing how we produce the
20472058
// suggestions and filtering there, we just don't emit the suggestion.
20482059
// Suggestions coming from macros can also have malformed spans. This is a heavy handed
20492060
// approach to avoid ICEs by ignoring the suggestion outright.
2061+
2062+
emit_to_destination(&buffer.render(), &Level::Note, &mut self.dst, self.short_message)?;
20502063
return Ok(());
20512064
}
20522065

2053-
let mut buffer = StyledBuffer::new();
2054-
20552066
// Render the suggestion message
20562067
buffer.append(0, level.to_str(), Style::Level(*level));
20572068
buffer.append(0, ": ", Style::HeaderMsg);

compiler/rustc_errors/src/json.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use rustc_span::Span;
2424
use rustc_span::hygiene::ExpnData;
2525
use rustc_span::source_map::{FilePathMapping, SourceMap};
2626
use serde::Serialize;
27+
use tracing::debug;
2728

2829
use crate::diagnostic::IsLint;
2930
use crate::emitter::{
@@ -543,6 +544,21 @@ impl DiagnosticSpan {
543544
suggestion
544545
.substitutions
545546
.iter()
547+
.filter_map(|substitution| {
548+
let mut parts = substitution.parts.clone();
549+
// check that all spans are distjoint, otherwise rustfix doesn't know what to do.
550+
// suggestions that don't have that have a hint about omitted suggestions in them
551+
parts.sort_by_key(|part| part.span.lo());
552+
// Verify the assumption that all spans are disjoint
553+
if parts.array_windows().find(|[a, b]| a.span.overlaps(b.span)).is_some() {
554+
debug!(
555+
"from_suggestion: suggestion contains an invalid span: {substitution:?}"
556+
);
557+
None
558+
} else {
559+
Some(substitution)
560+
}
561+
})
546562
.flat_map(|substitution| {
547563
substitution.parts.iter().map(move |suggestion_inner| {
548564
let span_label =

compiler/rustc_errors/src/lib.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -322,20 +322,27 @@ fn as_substr<'a>(original: &'a str, suggestion: &'a str) -> Option<(usize, &'a s
322322
}
323323
}
324324

325+
/// Represents suggestions that were emitted because their span was ambiguous.
326+
pub(crate) struct SuggestionsOmitted(usize);
327+
325328
impl CodeSuggestion {
326329
/// Returns the assembled code suggestions, whether they should be shown with an underline
327330
/// and whether the substitution only differs in capitalization.
328331
pub(crate) fn splice_lines(
329332
&self,
330333
sm: &SourceMap,
331-
) -> Vec<(String, Vec<TrimmedSubstitutionPart>, Vec<Vec<SubstitutionHighlight>>, ConfusionType)>
332-
{
334+
) -> (
335+
Vec<(String, Vec<TrimmedSubstitutionPart>, Vec<Vec<SubstitutionHighlight>>, ConfusionType)>,
336+
SuggestionsOmitted,
337+
) {
333338
// For the `Vec<Vec<SubstitutionHighlight>>` value, the first level of the vector
334339
// corresponds to the output snippet's lines, while the second level corresponds to the
335340
// substrings within that line that should be highlighted.
336341

337342
use rustc_span::{CharPos, Pos};
338343

344+
let num_omitted = Cell::new(0);
345+
339346
/// Extracts a substring from the provided `line_opt` based on the specified low and high
340347
/// indices, appends it to the given buffer `buf`, and returns the count of newline
341348
/// characters in the substring for accurate highlighting. If `line_opt` is `None`, a
@@ -384,13 +391,15 @@ impl CodeSuggestion {
384391

385392
assert!(!self.substitutions.is_empty());
386393

387-
self.substitutions
394+
let res = self
395+
.substitutions
388396
.iter()
389397
.filter(|subst| {
390398
// Suggestions coming from macros can have malformed spans. This is a heavy
391399
// handed approach to avoid ICEs by ignoring the suggestion outright.
392400
let invalid = subst.parts.iter().any(|item| sm.is_valid_span(item.span).is_err());
393401
if invalid {
402+
num_omitted.update(|i| i + 1);
394403
debug!("splice_lines: suggestion contains an invalid span: {:?}", subst);
395404
}
396405
!invalid
@@ -400,6 +409,21 @@ impl CodeSuggestion {
400409
// Assumption: all spans are in the same file, and all spans
401410
// are disjoint. Sort in ascending order.
402411
substitution.parts.sort_by_key(|part| part.span.lo());
412+
if substitution
413+
.parts
414+
.array_windows()
415+
.find(|[a, b]| a.span.overlaps(b.span))
416+
.is_some()
417+
{
418+
debug!("splice_lines: suggestion contains an invalid span: {substitution:?}");
419+
num_omitted.update(|i| i + 1);
420+
return None;
421+
}
422+
423+
// Account for cases where we are suggesting the same code that's already
424+
// there. This shouldn't happen often, but in some cases for multipart
425+
// suggestions it's much easier to handle it here than in the origin.
426+
substitution.parts.retain(|p| is_different(sm, &p.snippet, p.span));
403427

404428
// Find the bounding span.
405429
let lo = substitution.parts.iter().map(|part| part.span.lo()).min()?;
@@ -541,7 +565,9 @@ impl CodeSuggestion {
541565
Some((buf, trimmed_parts, highlights, confusion_type))
542566
}
543567
})
544-
.collect()
568+
.collect();
569+
570+
(res, SuggestionsOmitted(num_omitted.get()))
545571
}
546572
}
547573

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// regression test for #146808
2+
//@ proc-macro: all_spans_same.rs
3+
//@ run-rustfix
4+
//@ rustfix-only-machine-applicable
5+
6+
extern crate all_spans_same;
7+
8+
#[all_spans_same::all_spans_same]
9+
//~^ ERROR wrong meta list delimiters
10+
#[allow{}]
11+
fn main() {}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
// regression test for #146808
22
//@ proc-macro: all_spans_same.rs
3+
//@ run-rustfix
4+
//@ rustfix-only-machine-applicable
5+
36
extern crate all_spans_same;
47

58
#[all_spans_same::all_spans_same]
9+
//~^ ERROR wrong meta list delimiters
610
#[allow{}]
711
fn main() {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error: wrong meta list delimiters
2+
--> $DIR/invalid-delimeter-ice-146808.rs:8:1
3+
|
4+
LL | #[all_spans_same::all_spans_same]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: this error originates in the attribute macro `all_spans_same::all_spans_same` (in Nightly builds, run with -Z macro-backtrace for more info)
8+
rendering error: omitted 1 suggestion that failed to render, likely because of macro expansions
9+
10+
error: aborting due to 1 previous error
11+

0 commit comments

Comments
 (0)