Skip to content

Commit 88687d4

Browse files
Rollup merge of #134664 - estebank:sugg-highlighting, r=jieyouxu
Account for removal of multiline span in suggestion When highlighting the removed parts of a suggestion, properly account for spans that cover more than one line. Fix #134485. ![Screenshot of the highlighted output](https://github.com/user-attachments/assets/18bcd9bc-3bec-4b79-a9d7-e4ea4e6289ad)
2 parents c016cd8 + 12d66d9 commit 88687d4

File tree

3 files changed

+641
-7
lines changed

3 files changed

+641
-7
lines changed

compiler/rustc_errors/src/emitter.rs

+79-7
Original file line numberDiff line numberDiff line change
@@ -2216,6 +2216,11 @@ impl HumanEmitter {
22162216
show_code_change
22172217
{
22182218
for part in parts {
2219+
let snippet = if let Ok(snippet) = sm.span_to_snippet(part.span) {
2220+
snippet
2221+
} else {
2222+
String::new()
2223+
};
22192224
let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
22202225
let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;
22212226

@@ -2263,13 +2268,80 @@ impl HumanEmitter {
22632268
}
22642269
if let DisplaySuggestion::Diff = show_code_change {
22652270
// Colorize removal with red in diff format.
2266-
buffer.set_style_range(
2267-
row_num - 2,
2268-
(padding as isize + span_start_pos as isize) as usize,
2269-
(padding as isize + span_end_pos as isize) as usize,
2270-
Style::Removal,
2271-
true,
2272-
);
2271+
2272+
// Below, there's some tricky buffer indexing going on. `row_num` at this
2273+
// point corresponds to:
2274+
//
2275+
// |
2276+
// LL | CODE
2277+
// | ++++ <- `row_num`
2278+
//
2279+
// in the buffer. When we have a diff format output, we end up with
2280+
//
2281+
// |
2282+
// LL - OLDER <- row_num - 2
2283+
// LL + NEWER
2284+
// | <- row_num
2285+
//
2286+
// The `row_num - 2` is to select the buffer line that has the "old version
2287+
// of the diff" at that point. When the removal is a single line, `i` is
2288+
// `0`, `newlines` is `1` so `(newlines - i - 1)` ends up being `0`, so row
2289+
// points at `LL - OLDER`. When the removal corresponds to multiple lines,
2290+
// we end up with `newlines > 1` and `i` being `0..newlines - 1`.
2291+
//
2292+
// |
2293+
// LL - OLDER <- row_num - 2 - (newlines - last_i - 1)
2294+
// LL - CODE
2295+
// LL - BEING
2296+
// LL - REMOVED <- row_num - 2 - (newlines - first_i - 1)
2297+
// LL + NEWER
2298+
// | <- row_num
2299+
2300+
let newlines = snippet.lines().count();
2301+
if newlines > 0 && row_num > newlines {
2302+
// Account for removals where the part being removed spans multiple
2303+
// lines.
2304+
// FIXME: We check the number of rows because in some cases, like in
2305+
// `tests/ui/lint/invalid-nan-comparison-suggestion.rs`, the rendered
2306+
// suggestion will only show the first line of code being replaced. The
2307+
// proper way of doing this would be to change the suggestion rendering
2308+
// logic to show the whole prior snippet, but the current output is not
2309+
// too bad to begin with, so we side-step that issue here.
2310+
for (i, line) in snippet.lines().enumerate() {
2311+
let line = normalize_whitespace(line);
2312+
let row = row_num - 2 - (newlines - i - 1);
2313+
// On the first line, we highlight between the start of the part
2314+
// span, and the end of that line.
2315+
// On the last line, we highlight between the start of the line, and
2316+
// the column of the part span end.
2317+
// On all others, we highlight the whole line.
2318+
let start = if i == 0 {
2319+
(padding as isize + span_start_pos as isize) as usize
2320+
} else {
2321+
padding
2322+
};
2323+
let end = if i == 0 {
2324+
(padding as isize
2325+
+ span_start_pos as isize
2326+
+ line.len() as isize)
2327+
as usize
2328+
} else if i == newlines - 1 {
2329+
(padding as isize + span_end_pos as isize) as usize
2330+
} else {
2331+
(padding as isize + line.len() as isize) as usize
2332+
};
2333+
buffer.set_style_range(row, start, end, Style::Removal, true);
2334+
}
2335+
} else {
2336+
// The removed code fits all in one line.
2337+
buffer.set_style_range(
2338+
row_num - 2,
2339+
(padding as isize + span_start_pos as isize) as usize,
2340+
(padding as isize + span_end_pos as isize) as usize,
2341+
Style::Removal,
2342+
true,
2343+
);
2344+
}
22732345
}
22742346

22752347
// length of the code after substitution
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Make sure suggestion for removal of a span that covers multiple lines is properly highlighted.
2+
//@ compile-flags: --error-format=human --color=always
3+
//@ edition:2018
4+
//@ only-linux
5+
// ignore-tidy-tab
6+
// We use `\t` instead of spaces for indentation to ensure that the highlighting logic properly
7+
// accounts for replaced characters (like we do for `\t` with ` `). The naïve way of highlighting
8+
// could be counting chars of the original code, instead of operating on the code as it is being
9+
// displayed.
10+
use std::collections::{HashMap, HashSet};
11+
fn foo() -> Vec<(bool, HashSet<u8>)> {
12+
let mut hm = HashMap::<bool, Vec<HashSet<u8>>>::new();
13+
hm.into_iter()
14+
.map(|(is_true, ts)| {
15+
ts.into_iter()
16+
.map(|t| {
17+
(
18+
is_true,
19+
t,
20+
)
21+
}).flatten()
22+
})
23+
.flatten()
24+
.collect()
25+
}
26+
fn bar() -> Vec<(bool, HashSet<u8>)> {
27+
let mut hm = HashMap::<bool, Vec<HashSet<u8>>>::new();
28+
hm.into_iter()
29+
.map(|(is_true, ts)| {
30+
ts.into_iter()
31+
.map(|t| (is_true, t))
32+
.flatten()
33+
})
34+
.flatten()
35+
.collect()
36+
}
37+
fn baz() -> Vec<(bool, HashSet<u8>)> {
38+
let mut hm = HashMap::<bool, Vec<HashSet<u8>>>::new();
39+
hm.into_iter()
40+
.map(|(is_true, ts)| {
41+
ts.into_iter().map(|t| {
42+
(is_true, t)
43+
}).flatten()
44+
})
45+
.flatten()
46+
.collect()
47+
}
48+
fn bay() -> Vec<(bool, HashSet<u8>)> {
49+
let mut hm = HashMap::<bool, Vec<HashSet<u8>>>::new();
50+
hm.into_iter()
51+
.map(|(is_true, ts)| {
52+
ts.into_iter()
53+
.map(|t| (is_true, t)).flatten()
54+
})
55+
.flatten()
56+
.collect()
57+
}
58+
fn main() {}

0 commit comments

Comments
 (0)