Skip to content

Commit 98e791e

Browse files
committed
Auto merge of #45741 - oli-obk:refactor_suggestions, r=estebank
Refactor internal suggestion API ~~The only functional change is that whitespace, which is suggested to be added, also gets `^^^^` under it. An example is shown in the tests (the only test that changed).~~ Continuation of #41876 r? @nagisa the changes are probably best viewed [without whitespace](https://github.com/rust-lang/rust/pull/45741/files?w=1)
2 parents fd9ecfd + dfe218a commit 98e791e

File tree

4 files changed

+112
-122
lines changed

4 files changed

+112
-122
lines changed

src/librustc_errors/diagnostic.rs

+17-10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// except according to those terms.
1010

1111
use CodeSuggestion;
12+
use SubstitutionPart;
1213
use Substitution;
1314
use Level;
1415
use RenderSpan;
@@ -217,9 +218,11 @@ impl Diagnostic {
217218
/// See `CodeSuggestion` for more information.
218219
pub fn span_suggestion_short(&mut self, sp: Span, msg: &str, suggestion: String) -> &mut Self {
219220
self.suggestions.push(CodeSuggestion {
220-
substitution_parts: vec![Substitution {
221-
span: sp,
222-
substitutions: vec![suggestion],
221+
substitutions: vec![Substitution {
222+
parts: vec![SubstitutionPart {
223+
snippet: suggestion,
224+
span: sp,
225+
}],
223226
}],
224227
msg: msg.to_owned(),
225228
show_code_when_inline: false,
@@ -245,9 +248,11 @@ impl Diagnostic {
245248
/// See `CodeSuggestion` for more information.
246249
pub fn span_suggestion(&mut self, sp: Span, msg: &str, suggestion: String) -> &mut Self {
247250
self.suggestions.push(CodeSuggestion {
248-
substitution_parts: vec![Substitution {
249-
span: sp,
250-
substitutions: vec![suggestion],
251+
substitutions: vec![Substitution {
252+
parts: vec![SubstitutionPart {
253+
snippet: suggestion,
254+
span: sp,
255+
}],
251256
}],
252257
msg: msg.to_owned(),
253258
show_code_when_inline: true,
@@ -258,10 +263,12 @@ impl Diagnostic {
258263
/// Prints out a message with multiple suggested edits of the code.
259264
pub fn span_suggestions(&mut self, sp: Span, msg: &str, suggestions: Vec<String>) -> &mut Self {
260265
self.suggestions.push(CodeSuggestion {
261-
substitution_parts: vec![Substitution {
262-
span: sp,
263-
substitutions: suggestions,
264-
}],
266+
substitutions: suggestions.into_iter().map(|snippet| Substitution {
267+
parts: vec![SubstitutionPart {
268+
snippet,
269+
span: sp,
270+
}],
271+
}).collect(),
265272
msg: msg.to_owned(),
266273
show_code_when_inline: true,
267274
});

src/librustc_errors/emitter.rs

+36-31
Original file line numberDiff line numberDiff line change
@@ -38,23 +38,23 @@ impl Emitter for EmitterWriter {
3838

3939
if let Some((sugg, rest)) = db.suggestions.split_first() {
4040
if rest.is_empty() &&
41-
// don't display multipart suggestions as labels
42-
sugg.substitution_parts.len() == 1 &&
4341
// don't display multi-suggestions as labels
44-
sugg.substitutions() == 1 &&
42+
sugg.substitutions.len() == 1 &&
43+
// don't display multipart suggestions as labels
44+
sugg.substitutions[0].parts.len() == 1 &&
4545
// don't display long messages as labels
4646
sugg.msg.split_whitespace().count() < 10 &&
4747
// don't display multiline suggestions as labels
48-
sugg.substitution_parts[0].substitutions[0].find('\n').is_none() {
49-
let substitution = &sugg.substitution_parts[0].substitutions[0];
48+
!sugg.substitutions[0].parts[0].snippet.contains('\n') {
49+
let substitution = &sugg.substitutions[0].parts[0].snippet;
5050
let msg = if substitution.len() == 0 || !sugg.show_code_when_inline {
5151
// This substitution is only removal or we explicitly don't want to show the
5252
// code inline, don't show it
5353
format!("help: {}", sugg.msg)
5454
} else {
5555
format!("help: {}: `{}`", sugg.msg, substitution)
5656
};
57-
primary_span.push_span_label(sugg.substitution_spans().next().unwrap(), msg);
57+
primary_span.push_span_label(sugg.substitutions[0].parts[0].span, msg);
5858
} else {
5959
// if there are multiple suggestions, print them all in full
6060
// to be consistent. We could try to figure out if we can
@@ -1098,14 +1098,10 @@ impl EmitterWriter {
10981098
-> io::Result<()> {
10991099
use std::borrow::Borrow;
11001100

1101-
let primary_sub = &suggestion.substitution_parts[0];
11021101
if let Some(ref cm) = self.cm {
11031102
let mut buffer = StyledBuffer::new();
11041103

1105-
let lines = cm.span_to_lines(primary_sub.span).unwrap();
1106-
1107-
assert!(!lines.lines.is_empty());
1108-
1104+
// Render the suggestion message
11091105
buffer.append(0, &level.to_string(), Style::Level(level.clone()));
11101106
buffer.append(0, ": ", Style::HeaderMsg);
11111107
self.msg_to_buffer(&mut buffer,
@@ -1114,14 +1110,22 @@ impl EmitterWriter {
11141110
"suggestion",
11151111
Some(Style::HeaderMsg));
11161112

1113+
// Render the replacements for each suggestion
11171114
let suggestions = suggestion.splice_lines(cm.borrow());
1118-
let span_start_pos = cm.lookup_char_pos(primary_sub.span.lo());
1119-
let line_start = span_start_pos.line;
1120-
draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1);
1115+
11211116
let mut row_num = 2;
1122-
for (&(ref complete, show_underline), ref sub) in suggestions
1123-
.iter().zip(primary_sub.substitutions.iter()).take(MAX_SUGGESTIONS)
1124-
{
1117+
for &(ref complete, ref parts) in suggestions.iter().take(MAX_SUGGESTIONS) {
1118+
let show_underline = parts.len() == 1
1119+
&& complete.lines().count() == 1
1120+
&& parts[0].snippet.trim() != complete.trim();
1121+
1122+
let lines = cm.span_to_lines(parts[0].span).unwrap();
1123+
1124+
assert!(!lines.lines.is_empty());
1125+
1126+
let span_start_pos = cm.lookup_char_pos(parts[0].span.lo());
1127+
let line_start = span_start_pos.line;
1128+
draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1);
11251129
let mut line_pos = 0;
11261130
// Only show underline if there's a single suggestion and it is a single line
11271131
let mut lines = complete.lines();
@@ -1136,21 +1140,22 @@ impl EmitterWriter {
11361140
buffer.append(row_num, line, Style::NoStyle);
11371141
line_pos += 1;
11381142
row_num += 1;
1139-
// Only show an underline in the suggestions if the suggestion is not the
1140-
// entirety of the code being shown and the displayed code is not multiline.
1141-
if show_underline {
1142-
draw_col_separator(&mut buffer, row_num, max_line_num_len + 1);
1143-
let sub_len = sub.trim_right().len();
1144-
let underline_start = span_start_pos.col.0;
1145-
let underline_end = span_start_pos.col.0 + sub_len;
1146-
for p in underline_start..underline_end {
1147-
buffer.putc(row_num,
1148-
max_line_num_len + 3 + p,
1149-
'^',
1150-
Style::UnderlinePrimary);
1151-
}
1152-
row_num += 1;
1143+
}
1144+
// Only show an underline in the suggestions if the suggestion is not the
1145+
// entirety of the code being shown and the displayed code is not multiline.
1146+
if show_underline {
1147+
draw_col_separator(&mut buffer, row_num, max_line_num_len + 1);
1148+
let start = parts[0].snippet.len() - parts[0].snippet.trim_left().len();
1149+
let sub_len = parts[0].snippet.trim().len();
1150+
let underline_start = span_start_pos.col.0 + start;
1151+
let underline_end = span_start_pos.col.0 + sub_len;
1152+
for p in underline_start..underline_end {
1153+
buffer.putc(row_num,
1154+
max_line_num_len + 3 + p,
1155+
'^',
1156+
Style::UnderlinePrimary);
11531157
}
1158+
row_num += 1;
11541159
}
11551160

11561161
// if we elided some lines, add an ellipsis

src/librustc_errors/lib.rs

+55-77
Original file line numberDiff line numberDiff line change
@@ -76,26 +76,34 @@ pub struct CodeSuggestion {
7676
///
7777
/// ```
7878
/// vec![
79-
/// (0..3, vec!["a", "x"]),
80-
/// (4..7, vec!["b", "y"]),
79+
/// Substitution { parts: vec![(0..3, "a"), (4..7, "b")] },
80+
/// Substitution { parts: vec![(0..3, "x"), (4..7, "y")] },
8181
/// ]
8282
/// ```
8383
///
8484
/// or by replacing the entire span:
8585
///
8686
/// ```
87-
/// vec![(0..7, vec!["a.b", "x.y"])]
87+
/// vec![
88+
/// Substitution { parts: vec![(0..7, "a.b")] },
89+
/// Substitution { parts: vec![(0..7, "x.y")] },
90+
/// ]
8891
/// ```
89-
pub substitution_parts: Vec<Substitution>,
92+
pub substitutions: Vec<Substitution>,
9093
pub msg: String,
9194
pub show_code_when_inline: bool,
9295
}
9396

9497
#[derive(Clone, Debug, PartialEq, Hash, RustcEncodable, RustcDecodable)]
9598
/// See the docs on `CodeSuggestion::substitutions`
9699
pub struct Substitution {
100+
pub parts: Vec<SubstitutionPart>,
101+
}
102+
103+
#[derive(Clone, Debug, PartialEq, Hash, RustcEncodable, RustcDecodable)]
104+
pub struct SubstitutionPart {
97105
pub span: Span,
98-
pub substitutions: Vec<String>,
106+
pub snippet: String,
99107
}
100108

101109
pub trait CodeMapper {
@@ -109,18 +117,8 @@ pub trait CodeMapper {
109117
}
110118

111119
impl CodeSuggestion {
112-
/// Returns the number of substitutions
113-
fn substitutions(&self) -> usize {
114-
self.substitution_parts[0].substitutions.len()
115-
}
116-
117-
/// Returns the number of substitutions
118-
fn substitution_spans<'a>(&'a self) -> impl Iterator<Item = Span> + 'a {
119-
self.substitution_parts.iter().map(|sub| sub.span)
120-
}
121-
122-
/// Returns the assembled code suggestions and wether they should be shown with an underline.
123-
pub fn splice_lines(&self, cm: &CodeMapper) -> Vec<(String, bool)> {
120+
/// Returns the assembled code suggestions and whether they should be shown with an underline.
121+
pub fn splice_lines(&self, cm: &CodeMapper) -> Vec<(String, Vec<SubstitutionPart>)> {
124122
use syntax_pos::{CharPos, Loc, Pos};
125123

126124
fn push_trailing(buf: &mut String,
@@ -142,60 +140,42 @@ impl CodeSuggestion {
142140
}
143141
}
144142

145-
if self.substitution_parts.is_empty() {
146-
return vec![(String::new(), false)];
147-
}
148-
149-
let mut primary_spans: Vec<_> = self.substitution_parts
150-
.iter()
151-
.map(|sub| (sub.span, &sub.substitutions))
152-
.collect();
153-
154-
// Assumption: all spans are in the same file, and all spans
155-
// are disjoint. Sort in ascending order.
156-
primary_spans.sort_by_key(|sp| sp.0.lo());
157-
158-
// Find the bounding span.
159-
let lo = primary_spans.iter().map(|sp| sp.0.lo()).min().unwrap();
160-
let hi = primary_spans.iter().map(|sp| sp.0.hi()).min().unwrap();
161-
let bounding_span = Span::new(lo, hi, NO_EXPANSION);
162-
let lines = cm.span_to_lines(bounding_span).unwrap();
163-
assert!(!lines.lines.is_empty());
164-
165-
// To build up the result, we do this for each span:
166-
// - push the line segment trailing the previous span
167-
// (at the beginning a "phantom" span pointing at the start of the line)
168-
// - push lines between the previous and current span (if any)
169-
// - if the previous and current span are not on the same line
170-
// push the line segment leading up to the current span
171-
// - splice in the span substitution
172-
//
173-
// Finally push the trailing line segment of the last span
174-
let fm = &lines.file;
175-
let mut prev_hi = cm.lookup_char_pos(bounding_span.lo());
176-
prev_hi.col = CharPos::from_usize(0);
177-
178-
let mut prev_line = fm.get_line(lines.lines[0].line_index);
179-
let mut bufs = vec![(String::new(), false); self.substitutions()];
180-
181-
for (sp, substitutes) in primary_spans {
182-
let cur_lo = cm.lookup_char_pos(sp.lo());
183-
for (&mut (ref mut buf, ref mut underline), substitute) in bufs.iter_mut()
184-
.zip(substitutes) {
143+
assert!(!self.substitutions.is_empty());
144+
145+
self.substitutions.iter().cloned().map(|mut substitution| {
146+
// Assumption: all spans are in the same file, and all spans
147+
// are disjoint. Sort in ascending order.
148+
substitution.parts.sort_by_key(|part| part.span.lo());
149+
150+
// Find the bounding span.
151+
let lo = substitution.parts.iter().map(|part| part.span.lo()).min().unwrap();
152+
let hi = substitution.parts.iter().map(|part| part.span.hi()).min().unwrap();
153+
let bounding_span = Span::new(lo, hi, NO_EXPANSION);
154+
let lines = cm.span_to_lines(bounding_span).unwrap();
155+
assert!(!lines.lines.is_empty());
156+
157+
// To build up the result, we do this for each span:
158+
// - push the line segment trailing the previous span
159+
// (at the beginning a "phantom" span pointing at the start of the line)
160+
// - push lines between the previous and current span (if any)
161+
// - if the previous and current span are not on the same line
162+
// push the line segment leading up to the current span
163+
// - splice in the span substitution
164+
//
165+
// Finally push the trailing line segment of the last span
166+
let fm = &lines.file;
167+
let mut prev_hi = cm.lookup_char_pos(bounding_span.lo());
168+
prev_hi.col = CharPos::from_usize(0);
169+
170+
let mut prev_line = fm.get_line(lines.lines[0].line_index);
171+
let mut buf = String::new();
172+
173+
for part in &substitution.parts {
174+
let cur_lo = cm.lookup_char_pos(part.span.lo());
185175
if prev_hi.line == cur_lo.line {
186-
push_trailing(buf, prev_line.as_ref(), &prev_hi, Some(&cur_lo));
187-
188-
// Only show an underline in the suggestions if the suggestion is not the
189-
// entirety of the code being shown and the displayed code is not multiline.
190-
if prev_line.as_ref().unwrap().trim().len() > 0
191-
&& !substitute.ends_with('\n')
192-
&& substitute.lines().count() == 1
193-
{
194-
*underline = true;
195-
}
176+
push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, Some(&cur_lo));
196177
} else {
197-
*underline = false;
198-
push_trailing(buf, prev_line.as_ref(), &prev_hi, None);
178+
push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, None);
199179
// push lines between the previous and current span (if any)
200180
for idx in prev_hi.line..(cur_lo.line - 1) {
201181
if let Some(line) = fm.get_line(idx) {
@@ -207,22 +187,20 @@ impl CodeSuggestion {
207187
buf.push_str(&cur_line[..cur_lo.col.to_usize()]);
208188
}
209189
}
210-
buf.push_str(substitute);
190+
buf.push_str(&part.snippet);
191+
prev_hi = cm.lookup_char_pos(part.span.hi());
192+
prev_line = fm.get_line(prev_hi.line - 1);
211193
}
212-
prev_hi = cm.lookup_char_pos(sp.hi());
213-
prev_line = fm.get_line(prev_hi.line - 1);
214-
}
215-
for &mut (ref mut buf, _) in &mut bufs {
216194
// if the replacement already ends with a newline, don't print the next line
217195
if !buf.ends_with('\n') {
218-
push_trailing(buf, prev_line.as_ref(), &prev_hi, None);
196+
push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, None);
219197
}
220198
// remove trailing newlines
221199
while buf.ends_with('\n') {
222200
buf.pop();
223201
}
224-
}
225-
bufs
202+
(buf, substitution.parts)
203+
}).collect()
226204
}
227205
}
228206

src/libsyntax/json.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -284,17 +284,17 @@ impl DiagnosticSpan {
284284

285285
fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter)
286286
-> Vec<DiagnosticSpan> {
287-
suggestion.substitution_parts
287+
suggestion.substitutions
288288
.iter()
289289
.flat_map(|substitution| {
290-
substitution.substitutions.iter().map(move |suggestion| {
290+
substitution.parts.iter().map(move |suggestion| {
291291
let span_label = SpanLabel {
292-
span: substitution.span,
292+
span: suggestion.span,
293293
is_primary: true,
294294
label: None,
295295
};
296296
DiagnosticSpan::from_span_label(span_label,
297-
Some(suggestion),
297+
Some(&suggestion.snippet),
298298
je)
299299
})
300300
})

0 commit comments

Comments
 (0)