Skip to content

Commit

Permalink
Account for removal of multiline span in suggestion
Browse files Browse the repository at this point in the history
When highlighting the removed parts of a suggestion, properly account for spans that cover more than one line.

Fix #134485.
  • Loading branch information
estebank committed Dec 22, 2024
1 parent 387b245 commit 0edaf9a
Show file tree
Hide file tree
Showing 3 changed files with 608 additions and 7 deletions.
58 changes: 51 additions & 7 deletions compiler/rustc_errors/src/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2216,6 +2216,11 @@ impl HumanEmitter {
show_code_change
{
for part in parts {
let snippet = if let Ok(snippet) = sm.span_to_snippet(part.span) {
snippet
} else {
String::new()
};
let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;

Expand Down Expand Up @@ -2263,13 +2268,52 @@ impl HumanEmitter {
}
if let DisplaySuggestion::Diff = show_code_change {
// Colorize removal with red in diff format.
buffer.set_style_range(
row_num - 2,
(padding as isize + span_start_pos as isize) as usize,
(padding as isize + span_end_pos as isize) as usize,
Style::Removal,
true,
);

let newlines = snippet.lines().count();
if newlines > 0 && row_num > newlines {
// Account for removals where the part being removed spans multiple
// lines.
// FIXME: We check the number of rows because in some cases, like in
// `tests/ui/lint/invalid-nan-comparison-suggestion.rs`, the rendered
// suggestion will only show the first line of code being replaced. The
// proper way of doing this would be to change the suggestion rendering
// logic to show the whole prior snippet, but the current output is not
// too bad to begin with, so we side-step that issue here.
for (i, line) in snippet.lines().enumerate() {
let line = normalize_whitespace(line);
let row = row_num - 2 - (newlines - i - 1);
// On the first line, we highlight between the start of the part
// span, and the end of that line.
// On the last line, we highlight between the start of the line, and
// the column of the part span end.
// On all others, we highlight the whole line.
let start = if i == 0 {
(padding as isize + span_start_pos as isize) as usize
} else {
padding
};
let end = if i == 0 {
(padding as isize
+ span_start_pos as isize
+ line.len() as isize)
as usize
} else if i == newlines - 1 {
(padding as isize + span_end_pos as isize) as usize
} else {
(padding as isize + line.len() as isize) as usize
};
buffer.set_style_range(row, start, end, Style::Removal, true);
}
} else {
// The removed code fits all in one line.
buffer.set_style_range(
row_num - 2,
(padding as isize + span_start_pos as isize) as usize,
(padding as isize + span_end_pos as isize) as usize,
Style::Removal,
true,
);
}
}

// length of the code after substitution
Expand Down
53 changes: 53 additions & 0 deletions tests/ui/error-emitter/multiline-removal-suggestion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Make sure suggestion for removal of a span that covers multiple lines is properly highlighted.
//@ compile-flags: --error-format=human --color=always
//@ edition:2018
//@ ignore-tidy-tab
use std::collections::{HashMap, HashSet};
fn foo() -> Vec<(bool, HashSet<u8>)> {
let mut hm = HashMap::<bool, Vec<HashSet<u8>>>::new();

Check failure on line 7 in tests/ui/error-emitter/multiline-removal-suggestion.rs

View workflow job for this annotation

GitHub Actions / PR - mingw-check-tidy

tab character
hm.into_iter()

Check failure on line 8 in tests/ui/error-emitter/multiline-removal-suggestion.rs

View workflow job for this annotation

GitHub Actions / PR - mingw-check-tidy

tab character
.map(|(is_true, ts)| {

Check failure on line 9 in tests/ui/error-emitter/multiline-removal-suggestion.rs

View workflow job for this annotation

GitHub Actions / PR - mingw-check-tidy

tab character
ts.into_iter()

Check failure on line 10 in tests/ui/error-emitter/multiline-removal-suggestion.rs

View workflow job for this annotation

GitHub Actions / PR - mingw-check-tidy

tab character
.map(|t| {

Check failure on line 11 in tests/ui/error-emitter/multiline-removal-suggestion.rs

View workflow job for this annotation

GitHub Actions / PR - mingw-check-tidy

tab character
(

Check failure on line 12 in tests/ui/error-emitter/multiline-removal-suggestion.rs

View workflow job for this annotation

GitHub Actions / PR - mingw-check-tidy

tab character
is_true,

Check failure on line 13 in tests/ui/error-emitter/multiline-removal-suggestion.rs

View workflow job for this annotation

GitHub Actions / PR - mingw-check-tidy

tab character
t,

Check failure on line 14 in tests/ui/error-emitter/multiline-removal-suggestion.rs

View workflow job for this annotation

GitHub Actions / PR - mingw-check-tidy

tab character
)

Check failure on line 15 in tests/ui/error-emitter/multiline-removal-suggestion.rs

View workflow job for this annotation

GitHub Actions / PR - mingw-check-tidy

tab character
}).flatten()

Check failure on line 16 in tests/ui/error-emitter/multiline-removal-suggestion.rs

View workflow job for this annotation

GitHub Actions / PR - mingw-check-tidy

tab character
})
.flatten()
.collect()
}
fn bar() -> Vec<(bool, HashSet<u8>)> {
let mut hm = HashMap::<bool, Vec<HashSet<u8>>>::new();
hm.into_iter()
.map(|(is_true, ts)| {
ts.into_iter()
.map(|t| (is_true, t))
.flatten()
})
.flatten()
.collect()
}
fn baz() -> Vec<(bool, HashSet<u8>)> {
let mut hm = HashMap::<bool, Vec<HashSet<u8>>>::new();
hm.into_iter()
.map(|(is_true, ts)| {
ts.into_iter().map(|t| {
(is_true, t)
}).flatten()
})
.flatten()
.collect()
}
fn bay() -> Vec<(bool, HashSet<u8>)> {
let mut hm = HashMap::<bool, Vec<HashSet<u8>>>::new();
hm.into_iter()
.map(|(is_true, ts)| {
ts.into_iter()
.map(|t| (is_true, t)).flatten()
})
.flatten()
.collect()
}
fn main() {}
Loading

0 comments on commit 0edaf9a

Please sign in to comment.