Skip to content

Commit ccffcaf

Browse files
committed
Auto merge of #86532 - estebank:delete-suggestion-underline, r=petrochenkov
Make deleted code in a suggestion clearer Show suggestions that include deletions in a way similar to `diff`'s output. <img width="628" alt="" src="https://user-images.githubusercontent.com/1606434/123350316-9078e580-d50f-11eb-89b9-78431b85e23f.png"> For changes that do not have deletions, use `+` as an underline for additions and `~` as an underline for replacements. <img width="631" alt="" src="https://user-images.githubusercontent.com/1606434/123701745-1ac68f80-d817-11eb-950b-09e5afd7532f.png"> For multiline suggestions, we now use `~` in the gutter to signal replacements and `+` to signal whole line replacements/additions. <img width="834" alt="" src="https://user-images.githubusercontent.com/1606434/123701331-8eb46800-d816-11eb-9dcd-ef9098071afb.png"> In all cases we now use color to highlight the specific spans and snippets.
2 parents 362e0f5 + 657caa5 commit ccffcaf

File tree

952 files changed

+5657
-4232
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

952 files changed

+5657
-4232
lines changed

compiler/rustc_errors/src/emitter.rs

+102-18
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ use rustc_span::{MultiSpan, SourceFile, Span};
1414

1515
use crate::snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, Style, StyledString};
1616
use crate::styled_buffer::StyledBuffer;
17-
use crate::{CodeSuggestion, Diagnostic, DiagnosticId, Level, SubDiagnostic, SuggestionStyle};
17+
use crate::{
18+
CodeSuggestion, Diagnostic, DiagnosticId, Level, SubDiagnostic, SubstitutionHighlight,
19+
SuggestionStyle,
20+
};
1821

1922
use rustc_lint_defs::pluralize;
2023

@@ -1590,25 +1593,38 @@ impl EmitterWriter {
15901593
);
15911594

15921595
let mut row_num = 2;
1596+
draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1);
15931597
let mut notice_capitalization = false;
1594-
for (complete, parts, only_capitalization) in suggestions.iter().take(MAX_SUGGESTIONS) {
1598+
for (complete, parts, highlights, only_capitalization) in
1599+
suggestions.iter().take(MAX_SUGGESTIONS)
1600+
{
15951601
notice_capitalization |= only_capitalization;
15961602
// Only show underline if the suggestion spans a single line and doesn't cover the
15971603
// entirety of the code output. If you have multiple replacements in the same line
15981604
// of code, show the underline.
15991605
let show_underline = !(parts.len() == 1 && parts[0].snippet.trim() == complete.trim())
16001606
&& complete.lines().count() == 1;
16011607

1602-
let lines = sm
1608+
let has_deletion = parts.iter().any(|p| p.is_deletion());
1609+
let is_multiline = complete.lines().count() > 1;
1610+
1611+
let show_diff = has_deletion && !is_multiline;
1612+
1613+
if show_diff {
1614+
row_num += 1;
1615+
}
1616+
1617+
let file_lines = sm
16031618
.span_to_lines(parts[0].span)
16041619
.expect("span_to_lines failed when emitting suggestion");
16051620

1606-
assert!(!lines.lines.is_empty() || parts[0].span.is_dummy());
1621+
assert!(!file_lines.lines.is_empty() || parts[0].span.is_dummy());
16071622

16081623
let line_start = sm.lookup_char_pos(parts[0].span.lo()).line;
16091624
draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1);
16101625
let mut lines = complete.lines();
1611-
for (line_pos, line) in lines.by_ref().take(MAX_SUGGESTION_HIGHLIGHT_LINES).enumerate()
1626+
for (line_pos, (line, parts)) in
1627+
lines.by_ref().zip(highlights).take(MAX_SUGGESTION_HIGHLIGHT_LINES).enumerate()
16121628
{
16131629
// Print the span column to avoid confusion
16141630
buffer.puts(
@@ -1617,9 +1633,60 @@ impl EmitterWriter {
16171633
&self.maybe_anonymized(line_start + line_pos),
16181634
Style::LineNumber,
16191635
);
1636+
if show_diff {
1637+
// Add the line number for both addition and removal to drive the point home.
1638+
//
1639+
// N - fn foo<A: T>(bar: A) {
1640+
// N + fn foo(bar: impl T) {
1641+
buffer.puts(
1642+
row_num - 1,
1643+
0,
1644+
&self.maybe_anonymized(line_start + line_pos),
1645+
Style::LineNumber,
1646+
);
1647+
buffer.puts(row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
1648+
buffer.puts(
1649+
row_num - 1,
1650+
max_line_num_len + 3,
1651+
&replace_tabs(
1652+
&*file_lines
1653+
.file
1654+
.get_line(file_lines.lines[line_pos].line_index)
1655+
.unwrap(),
1656+
),
1657+
Style::NoStyle,
1658+
);
1659+
buffer.puts(row_num, max_line_num_len + 1, "+ ", Style::Addition);
1660+
} else if is_multiline {
1661+
match &parts[..] {
1662+
[SubstitutionHighlight { start: 0, end }] if *end == line.len() => {
1663+
buffer.puts(row_num, max_line_num_len + 1, "+ ", Style::Addition);
1664+
}
1665+
[] => {
1666+
draw_col_separator(&mut buffer, row_num, max_line_num_len + 1);
1667+
}
1668+
_ => {
1669+
buffer.puts(row_num, max_line_num_len + 1, "~ ", Style::Addition);
1670+
}
1671+
}
1672+
} else {
1673+
draw_col_separator(&mut buffer, row_num, max_line_num_len + 1);
1674+
}
1675+
16201676
// print the suggestion
1621-
draw_col_separator(&mut buffer, row_num, max_line_num_len + 1);
16221677
buffer.append(row_num, &replace_tabs(line), Style::NoStyle);
1678+
1679+
if is_multiline {
1680+
for SubstitutionHighlight { start, end } in parts {
1681+
buffer.set_style_range(
1682+
row_num,
1683+
max_line_num_len + 3 + start,
1684+
max_line_num_len + 3 + end,
1685+
Style::Addition,
1686+
true,
1687+
);
1688+
}
1689+
}
16231690
row_num += 1;
16241691
}
16251692

@@ -1654,25 +1721,36 @@ impl EmitterWriter {
16541721
let underline_start = (span_start_pos + start) as isize + offset;
16551722
let underline_end = (span_start_pos + start + sub_len) as isize + offset;
16561723
assert!(underline_start >= 0 && underline_end >= 0);
1724+
let padding: usize = max_line_num_len + 3;
16571725
for p in underline_start..underline_end {
1658-
buffer.putc(
1659-
row_num,
1660-
((max_line_num_len + 3) as isize + p) as usize,
1661-
'^',
1662-
Style::UnderlinePrimary,
1726+
// Colorize addition/replacements with green.
1727+
buffer.set_style(
1728+
row_num - 1,
1729+
(padding as isize + p) as usize,
1730+
Style::Addition,
1731+
true,
16631732
);
1664-
}
1665-
// underline removals too
1666-
if underline_start == underline_end {
1667-
for p in underline_start - 1..underline_start + 1 {
1733+
if !show_diff {
1734+
// If this is a replacement, underline with `^`, if this is an addition
1735+
// underline with `+`.
16681736
buffer.putc(
16691737
row_num,
1670-
((max_line_num_len + 3) as isize + p) as usize,
1671-
'-',
1672-
Style::UnderlineSecondary,
1738+
(padding as isize + p) as usize,
1739+
if part.is_addition(&sm) { '+' } else { '~' },
1740+
Style::Addition,
16731741
);
16741742
}
16751743
}
1744+
if show_diff {
1745+
// Colorize removal with red in diff format.
1746+
buffer.set_style_range(
1747+
row_num - 2,
1748+
(padding as isize + span_start_pos as isize) as usize,
1749+
(padding as isize + span_end_pos as isize) as usize,
1750+
Style::Removal,
1751+
true,
1752+
);
1753+
}
16761754

16771755
// length of the code after substitution
16781756
let full_sub_len = part
@@ -2129,6 +2207,12 @@ impl<'a> WritableDst<'a> {
21292207
fn apply_style(&mut self, lvl: Level, style: Style) -> io::Result<()> {
21302208
let mut spec = ColorSpec::new();
21312209
match style {
2210+
Style::Addition => {
2211+
spec.set_fg(Some(Color::Green)).set_intense(true);
2212+
}
2213+
Style::Removal => {
2214+
spec.set_fg(Some(Color::Red)).set_intense(true);
2215+
}
21322216
Style::LineAndColumn => {}
21332217
Style::LineNumber => {
21342218
spec.set_bold(true);

compiler/rustc_errors/src/lib.rs

+76-7
Original file line numberDiff line numberDiff line change
@@ -160,32 +160,77 @@ pub struct SubstitutionPart {
160160
pub snippet: String,
161161
}
162162

163+
/// Used to translate between `Span`s and byte positions within a single output line in highlighted
164+
/// code of structured suggestions.
165+
#[derive(Debug, Clone, Copy)]
166+
pub struct SubstitutionHighlight {
167+
start: usize,
168+
end: usize,
169+
}
170+
171+
impl SubstitutionPart {
172+
pub fn is_addition(&self, sm: &SourceMap) -> bool {
173+
!self.snippet.is_empty()
174+
&& sm
175+
.span_to_snippet(self.span)
176+
.map_or(self.span.is_empty(), |snippet| snippet.trim().is_empty())
177+
}
178+
179+
pub fn is_deletion(&self) -> bool {
180+
self.snippet.trim().is_empty()
181+
}
182+
183+
pub fn is_replacement(&self, sm: &SourceMap) -> bool {
184+
!self.snippet.is_empty()
185+
&& sm
186+
.span_to_snippet(self.span)
187+
.map_or(!self.span.is_empty(), |snippet| !snippet.trim().is_empty())
188+
}
189+
}
190+
163191
impl CodeSuggestion {
164192
/// Returns the assembled code suggestions, whether they should be shown with an underline
165193
/// and whether the substitution only differs in capitalization.
166-
pub fn splice_lines(&self, sm: &SourceMap) -> Vec<(String, Vec<SubstitutionPart>, bool)> {
194+
pub fn splice_lines(
195+
&self,
196+
sm: &SourceMap,
197+
) -> Vec<(String, Vec<SubstitutionPart>, Vec<Vec<SubstitutionHighlight>>, bool)> {
198+
// For the `Vec<Vec<SubstitutionHighlight>>` value, the first level of the vector
199+
// corresponds to the output snippet's lines, while the second level corresponds to the
200+
// substrings within that line that should be highlighted.
201+
167202
use rustc_span::{CharPos, Pos};
168203

204+
/// Append to a buffer the remainder of the line of existing source code, and return the
205+
/// count of lines that have been added for accurate highlighting.
169206
fn push_trailing(
170207
buf: &mut String,
171208
line_opt: Option<&Cow<'_, str>>,
172209
lo: &Loc,
173210
hi_opt: Option<&Loc>,
174-
) {
211+
) -> usize {
212+
let mut line_count = 0;
175213
let (lo, hi_opt) = (lo.col.to_usize(), hi_opt.map(|hi| hi.col.to_usize()));
176214
if let Some(line) = line_opt {
177215
if let Some(lo) = line.char_indices().map(|(i, _)| i).nth(lo) {
178216
let hi_opt = hi_opt.and_then(|hi| line.char_indices().map(|(i, _)| i).nth(hi));
179217
match hi_opt {
180-
Some(hi) if hi > lo => buf.push_str(&line[lo..hi]),
218+
Some(hi) if hi > lo => {
219+
line_count = line[lo..hi].matches('\n').count();
220+
buf.push_str(&line[lo..hi])
221+
}
181222
Some(_) => (),
182-
None => buf.push_str(&line[lo..]),
223+
None => {
224+
line_count = line[lo..].matches('\n').count();
225+
buf.push_str(&line[lo..])
226+
}
183227
}
184228
}
185229
if hi_opt.is_none() {
186230
buf.push('\n');
187231
}
188232
}
233+
line_count
189234
}
190235

191236
assert!(!self.substitutions.is_empty());
@@ -220,6 +265,7 @@ impl CodeSuggestion {
220265
return None;
221266
}
222267

268+
let mut highlights = vec![];
223269
// To build up the result, we do this for each span:
224270
// - push the line segment trailing the previous span
225271
// (at the beginning a "phantom" span pointing at the start of the line)
@@ -236,17 +282,29 @@ impl CodeSuggestion {
236282
lines.lines.get(0).and_then(|line0| sf.get_line(line0.line_index));
237283
let mut buf = String::new();
238284

285+
let mut line_highlight = vec![];
239286
for part in &substitution.parts {
240287
let cur_lo = sm.lookup_char_pos(part.span.lo());
241288
if prev_hi.line == cur_lo.line {
242-
push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, Some(&cur_lo));
289+
let mut count =
290+
push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, Some(&cur_lo));
291+
while count > 0 {
292+
highlights.push(std::mem::take(&mut line_highlight));
293+
count -= 1;
294+
}
243295
} else {
244-
push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, None);
296+
highlights.push(std::mem::take(&mut line_highlight));
297+
let mut count = push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, None);
298+
while count > 0 {
299+
highlights.push(std::mem::take(&mut line_highlight));
300+
count -= 1;
301+
}
245302
// push lines between the previous and current span (if any)
246303
for idx in prev_hi.line..(cur_lo.line - 1) {
247304
if let Some(line) = sf.get_line(idx) {
248305
buf.push_str(line.as_ref());
249306
buf.push('\n');
307+
highlights.push(std::mem::take(&mut line_highlight));
250308
}
251309
}
252310
if let Some(cur_line) = sf.get_line(cur_lo.line - 1) {
@@ -257,10 +315,21 @@ impl CodeSuggestion {
257315
buf.push_str(&cur_line[..end]);
258316
}
259317
}
318+
// Add a whole line highlight per line in the snippet.
319+
line_highlight.push(SubstitutionHighlight {
320+
start: cur_lo.col.0,
321+
end: cur_lo.col.0
322+
+ part.snippet.split('\n').next().unwrap_or(&part.snippet).len(),
323+
});
324+
for line in part.snippet.split('\n').skip(1) {
325+
highlights.push(std::mem::take(&mut line_highlight));
326+
line_highlight.push(SubstitutionHighlight { start: 0, end: line.len() });
327+
}
260328
buf.push_str(&part.snippet);
261329
prev_hi = sm.lookup_char_pos(part.span.hi());
262330
prev_line = sf.get_line(prev_hi.line - 1);
263331
}
332+
highlights.push(std::mem::take(&mut line_highlight));
264333
let only_capitalization = is_case_difference(sm, &buf, bounding_span);
265334
// if the replacement already ends with a newline, don't print the next line
266335
if !buf.ends_with('\n') {
@@ -270,7 +339,7 @@ impl CodeSuggestion {
270339
while buf.ends_with('\n') {
271340
buf.pop();
272341
}
273-
Some((buf, substitution.parts, only_capitalization))
342+
Some((buf, substitution.parts, highlights, only_capitalization))
274343
})
275344
.collect()
276345
}

compiler/rustc_errors/src/snippet.rs

+2
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,6 @@ pub enum Style {
177177
NoStyle,
178178
Level(Level),
179179
Highlight,
180+
Addition,
181+
Removal,
180182
}

src/test/rustdoc-ui/infinite-recursive-type-impl-trait-return.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ LL | This(E),
1010
help: insert some indirection (e.g., a `DEF_ID` representable
1111
|
1212
LL | This(Box<E>),
13-
| ^^^^ ^
13+
| ++++ +
1414

1515
error: aborting due to previous error
1616

src/test/rustdoc-ui/infinite-recursive-type-impl-trait.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ LL | V(E),
1010
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `f::E` representable
1111
|
1212
LL | V(Box<E>),
13-
| ^^^^ ^
13+
| ++++ +
1414

1515
error: aborting due to previous error
1616

src/test/rustdoc-ui/infinite-recursive-type.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ LL | V(E),
1010
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `E` representable
1111
|
1212
LL | V(Box<E>),
13-
| ^^^^ ^
13+
| ++++ +
1414

1515
error: aborting due to previous error
1616

0 commit comments

Comments
 (0)