Skip to content

Commit 407ae0a

Browse files
committed
don't emit suggestions with overlapping spans and count omitted suggestions
1 parent 436c2eb commit 407ae0a

File tree

6 files changed

+81
-11
lines changed

6 files changed

+81
-11
lines changed

compiler/rustc_errors/src/emitter.rs

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

20442044
// Render the replacements for each suggestion
2045-
let suggestions = suggestion.splice_lines(sm);
2045+
let (suggestions, num_omitted) = suggestion.splice_lines(sm);
20462046
debug!(?suggestions);
20472047

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

2057-
let mut buffer = StyledBuffer::new();
2058-
20592070
// Render the suggestion message
20602071
buffer.append(0, level.to_str(), Style::Level(*level));
20612072
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::hygiene::ExpnData;
2424
use rustc_span::source_map::{FilePathMapping, SourceMap};
2525
use serde::Serialize;
2626
use termcolor::{ColorSpec, WriteColor};
27+
use tracing::debug;
2728

2829
use crate::diagnostic::IsLint;
2930
use crate::emitter::{
@@ -554,6 +555,21 @@ impl DiagnosticSpan {
554555
suggestion
555556
.substitutions
556557
.iter()
558+
.filter_map(|substitution| {
559+
let mut parts = substitution.parts.clone();
560+
// check that all spans are distjoint, otherwise rustfix doesn't know what to do.
561+
// suggestions that don't have that have a hint about omitted suggestions in them
562+
parts.sort_by_key(|part| part.span.lo());
563+
// Verify the assumption that all spans are disjoint
564+
if parts.array_windows().find(|[a, b]| a.span.overlaps(b.span)).is_some() {
565+
debug!(
566+
"from_suggestion: suggestion contains an invalid span: {substitution:?}"
567+
);
568+
None
569+
} else {
570+
Some(substitution)
571+
}
572+
})
557573
.flat_map(|substitution| {
558574
substitution.parts.iter().map(move |suggestion_inner| {
559575
let span_label =

compiler/rustc_errors/src/lib.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -304,19 +304,27 @@ fn as_substr<'a>(original: &'a str, suggestion: &'a str) -> Option<(usize, &'a s
304304
}
305305
}
306306

307+
/// Represents suggestions that were emitted because their span was ambiguous.
308+
pub(crate) struct SuggestionsOmitted(usize);
309+
307310
impl CodeSuggestion {
308311
/// Returns the assembled code suggestions, whether they should be shown with an underline
309312
/// and whether the substitution only differs in capitalization.
310313
pub(crate) fn splice_lines(
311314
&self,
312315
sm: &SourceMap,
313-
) -> Vec<(String, Vec<SubstitutionPart>, Vec<Vec<SubstitutionHighlight>>, ConfusionType)> {
316+
) -> (
317+
Vec<(String, Vec<SubstitutionPart>, Vec<Vec<SubstitutionHighlight>>, ConfusionType)>,
318+
SuggestionsOmitted,
319+
) {
314320
// For the `Vec<Vec<SubstitutionHighlight>>` value, the first level of the vector
315321
// corresponds to the output snippet's lines, while the second level corresponds to the
316322
// substrings within that line that should be highlighted.
317323

318324
use rustc_span::{CharPos, Pos};
319325

326+
let num_omitted = Cell::new(0);
327+
320328
/// Extracts a substring from the provided `line_opt` based on the specified low and high
321329
/// indices, appends it to the given buffer `buf`, and returns the count of newline
322330
/// characters in the substring for accurate highlighting. If `line_opt` is `None`, a
@@ -365,13 +373,15 @@ impl CodeSuggestion {
365373

366374
assert!(!self.substitutions.is_empty());
367375

368-
self.substitutions
376+
let res = self
377+
.substitutions
369378
.iter()
370379
.filter(|subst| {
371380
// Suggestions coming from macros can have malformed spans. This is a heavy
372381
// handed approach to avoid ICEs by ignoring the suggestion outright.
373382
let invalid = subst.parts.iter().any(|item| sm.is_valid_span(item.span).is_err());
374383
if invalid {
384+
num_omitted.update(|i| i + 1);
375385
debug!("splice_lines: suggestion contains an invalid span: {:?}", subst);
376386
}
377387
!invalid
@@ -382,11 +392,16 @@ impl CodeSuggestion {
382392
// are disjoint. Sort in ascending order.
383393
substitution.parts.sort_by_key(|part| part.span.lo());
384394
// Verify the assumption that all spans are disjoint
385-
assert_eq!(
386-
substitution.parts.array_windows().find(|[a, b]| a.span.overlaps(b.span)),
387-
None,
388-
"all spans must be disjoint",
389-
);
395+
if substitution
396+
.parts
397+
.array_windows()
398+
.find(|[a, b]| a.span.overlaps(b.span))
399+
.is_some()
400+
{
401+
debug!("splice_lines: suggestion contains an invalid span: {substitution:?}");
402+
num_omitted.update(|i| i + 1);
403+
return None;
404+
}
390405

391406
// Account for cases where we are suggesting the same code that's already
392407
// there. This shouldn't happen often, but in some cases for multipart
@@ -524,7 +539,9 @@ impl CodeSuggestion {
524539
Some((buf, substitution.parts, highlights, confusion_type))
525540
}
526541
})
527-
.collect()
542+
.collect();
543+
544+
(res, SuggestionsOmitted(num_omitted.get()))
528545
}
529546
}
530547

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)