Skip to content

Commit 1f72662

Browse files
Rollup merge of rust-lang#41876 - oli-obk:diagnosing_diagnostics, r=nagisa
Refactor suggestion diagnostic API to allow for multiple suggestions r? @jonathandturner cc @nrc @petrochenkov
2 parents 2519e90 + 3f2bbe3 commit 1f72662

20 files changed

+392
-257
lines changed

src/librustc_errors/diagnostic.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// except according to those terms.
1010

1111
use CodeSuggestion;
12+
use Substitution;
1213
use Level;
1314
use RenderSpan;
1415
use std::fmt;
@@ -23,7 +24,7 @@ pub struct Diagnostic {
2324
pub code: Option<String>,
2425
pub span: MultiSpan,
2526
pub children: Vec<SubDiagnostic>,
26-
pub suggestion: Option<CodeSuggestion>,
27+
pub suggestions: Vec<CodeSuggestion>,
2728
}
2829

2930
/// For example a note attached to an error.
@@ -87,7 +88,7 @@ impl Diagnostic {
8788
code: code,
8889
span: MultiSpan::new(),
8990
children: vec![],
90-
suggestion: None,
91+
suggestions: vec![],
9192
}
9293
}
9394

@@ -204,10 +205,22 @@ impl Diagnostic {
204205
///
205206
/// See `diagnostic::CodeSuggestion` for more information.
206207
pub fn span_suggestion(&mut self, sp: Span, msg: &str, suggestion: String) -> &mut Self {
207-
assert!(self.suggestion.is_none());
208-
self.suggestion = Some(CodeSuggestion {
209-
msp: sp.into(),
210-
substitutes: vec![suggestion],
208+
self.suggestions.push(CodeSuggestion {
209+
substitution_parts: vec![Substitution {
210+
span: sp,
211+
substitutions: vec![suggestion],
212+
}],
213+
msg: msg.to_owned(),
214+
});
215+
self
216+
}
217+
218+
pub fn span_suggestions(&mut self, sp: Span, msg: &str, suggestions: Vec<String>) -> &mut Self {
219+
self.suggestions.push(CodeSuggestion {
220+
substitution_parts: vec![Substitution {
221+
span: sp,
222+
substitutions: suggestions,
223+
}],
211224
msg: msg.to_owned(),
212225
});
213226
self

src/librustc_errors/diagnostic_builder.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@ impl<'a> DiagnosticBuilder<'a> {
148148
msg: &str,
149149
suggestion: String)
150150
-> &mut Self);
151+
forward!(pub fn span_suggestions(&mut self,
152+
sp: Span,
153+
msg: &str,
154+
suggestions: Vec<String>)
155+
-> &mut Self);
151156
forward!(pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self);
152157
forward!(pub fn code(&mut self, s: String) -> &mut Self);
153158

src/librustc_errors/emitter.rs

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,32 @@ impl Emitter for EmitterWriter {
3535
let mut primary_span = db.span.clone();
3636
let mut children = db.children.clone();
3737

38-
if let Some(sugg) = db.suggestion.clone() {
39-
assert_eq!(sugg.msp.primary_spans().len(), sugg.substitutes.len());
40-
// don't display multispans as labels
41-
if sugg.substitutes.len() == 1 &&
38+
if let Some((sugg, rest)) = db.suggestions.split_first() {
39+
if rest.is_empty() &&
40+
// don't display multipart suggestions as labels
41+
sugg.substitution_parts.len() == 1 &&
42+
// don't display multi-suggestions as labels
43+
sugg.substitutions() == 1 &&
4244
// don't display long messages as labels
4345
sugg.msg.split_whitespace().count() < 10 &&
4446
// don't display multiline suggestions as labels
45-
sugg.substitutes[0].find('\n').is_none() {
46-
let msg = format!("help: {} `{}`", sugg.msg, sugg.substitutes[0]);
47-
primary_span.push_span_label(sugg.msp.primary_spans()[0], msg);
47+
sugg.substitution_parts[0].substitutions[0].find('\n').is_none() {
48+
let substitution = &sugg.substitution_parts[0].substitutions[0];
49+
let msg = format!("help: {} `{}`", sugg.msg, substitution);
50+
primary_span.push_span_label(sugg.substitution_spans().next().unwrap(), msg);
4851
} else {
49-
children.push(SubDiagnostic {
50-
level: Level::Help,
51-
message: Vec::new(),
52-
span: MultiSpan::new(),
53-
render_span: Some(Suggestion(sugg)),
54-
});
52+
// if there are multiple suggestions, print them all in full
53+
// to be consistent. We could try to figure out if we can
54+
// make one (or the first one) inline, but that would give
55+
// undue importance to a semi-random suggestion
56+
for sugg in &db.suggestions {
57+
children.push(SubDiagnostic {
58+
level: Level::Help,
59+
message: Vec::new(),
60+
span: MultiSpan::new(),
61+
render_span: Some(Suggestion(sugg.clone())),
62+
});
63+
}
5564
}
5665
}
5766

@@ -66,6 +75,10 @@ impl Emitter for EmitterWriter {
6675

6776
/// maximum number of lines we will print for each error; arbitrary.
6877
pub const MAX_HIGHLIGHT_LINES: usize = 6;
78+
/// maximum number of suggestions to be shown
79+
///
80+
/// Arbitrary, but taken from trait import suggestion limit
81+
pub const MAX_SUGGESTIONS: usize = 4;
6982

7083
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7184
pub enum ColorConfig {
@@ -1054,38 +1067,44 @@ impl EmitterWriter {
10541067
-> io::Result<()> {
10551068
use std::borrow::Borrow;
10561069

1057-
let primary_span = suggestion.msp.primary_span().unwrap();
1070+
let primary_span = suggestion.substitution_spans().next().unwrap();
10581071
if let Some(ref cm) = self.cm {
10591072
let mut buffer = StyledBuffer::new();
10601073

1061-
buffer.append(0, &level.to_string(), Style::Level(level.clone()));
1062-
buffer.append(0, ": ", Style::HeaderMsg);
1063-
self.msg_to_buffer(&mut buffer,
1064-
&[(suggestion.msg.to_owned(), Style::NoStyle)],
1065-
max_line_num_len,
1066-
"suggestion",
1067-
Some(Style::HeaderMsg));
1068-
10691074
let lines = cm.span_to_lines(primary_span).unwrap();
10701075

10711076
assert!(!lines.lines.is_empty());
10721077

1073-
let complete = suggestion.splice_lines(cm.borrow());
1078+
buffer.append(0, &level.to_string(), Style::Level(level.clone()));
1079+
buffer.append(0, ": ", Style::HeaderMsg);
1080+
self.msg_to_buffer(&mut buffer,
1081+
&[(suggestion.msg.to_owned(), Style::NoStyle)],
1082+
max_line_num_len,
1083+
"suggestion",
1084+
Some(Style::HeaderMsg));
10741085

1075-
// print the suggestion without any line numbers, but leave
1076-
// space for them. This helps with lining up with previous
1077-
// snippets from the actual error being reported.
1078-
let mut lines = complete.lines();
1086+
let suggestions = suggestion.splice_lines(cm.borrow());
10791087
let mut row_num = 1;
1080-
for line in lines.by_ref().take(MAX_HIGHLIGHT_LINES) {
1081-
draw_col_separator(&mut buffer, row_num, max_line_num_len + 1);
1082-
buffer.append(row_num, line, Style::NoStyle);
1083-
row_num += 1;
1084-
}
1088+
for complete in suggestions.iter().take(MAX_SUGGESTIONS) {
1089+
1090+
// print the suggestion without any line numbers, but leave
1091+
// space for them. This helps with lining up with previous
1092+
// snippets from the actual error being reported.
1093+
let mut lines = complete.lines();
1094+
for line in lines.by_ref().take(MAX_HIGHLIGHT_LINES) {
1095+
draw_col_separator(&mut buffer, row_num, max_line_num_len + 1);
1096+
buffer.append(row_num, line, Style::NoStyle);
1097+
row_num += 1;
1098+
}
10851099

1086-
// if we elided some lines, add an ellipsis
1087-
if let Some(_) = lines.next() {
1088-
buffer.append(row_num, "...", Style::NoStyle);
1100+
// if we elided some lines, add an ellipsis
1101+
if let Some(_) = lines.next() {
1102+
buffer.append(row_num, "...", Style::NoStyle);
1103+
}
1104+
}
1105+
if suggestions.len() > MAX_SUGGESTIONS {
1106+
let msg = format!("and {} other candidates", suggestions.len() - MAX_SUGGESTIONS);
1107+
buffer.append(row_num, &msg, Style::NoStyle);
10891108
}
10901109
emit_to_destination(&buffer.render(), level, &mut self.dst)?;
10911110
}

src/librustc_errors/lib.rs

Lines changed: 75 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#![feature(staged_api)]
2424
#![feature(range_contains)]
2525
#![feature(libc)]
26+
#![feature(conservative_impl_trait)]
2627

2728
extern crate term;
2829
extern crate libc;
@@ -65,11 +66,35 @@ pub enum RenderSpan {
6566

6667
#[derive(Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)]
6768
pub struct CodeSuggestion {
68-
pub msp: MultiSpan,
69-
pub substitutes: Vec<String>,
69+
/// Each substitute can have multiple variants due to multiple
70+
/// applicable suggestions
71+
///
72+
/// `foo.bar` might be replaced with `a.b` or `x.y` by replacing
73+
/// `foo` and `bar` on their own:
74+
///
75+
/// ```
76+
/// vec![
77+
/// (0..3, vec!["a", "x"]),
78+
/// (4..7, vec!["b", "y"]),
79+
/// ]
80+
/// ```
81+
///
82+
/// or by replacing the entire span:
83+
///
84+
/// ```
85+
/// vec![(0..7, vec!["a.b", "x.y"])]
86+
/// ```
87+
pub substitution_parts: Vec<Substitution>,
7088
pub msg: String,
7189
}
7290

91+
#[derive(Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)]
92+
/// See the docs on `CodeSuggestion::substitutions`
93+
pub struct Substitution {
94+
pub span: Span,
95+
pub substitutions: Vec<String>,
96+
}
97+
7398
pub trait CodeMapper {
7499
fn lookup_char_pos(&self, pos: BytePos) -> Loc;
75100
fn span_to_lines(&self, sp: Span) -> FileLinesResult;
@@ -79,8 +104,18 @@ pub trait CodeMapper {
79104
}
80105

81106
impl CodeSuggestion {
82-
/// Returns the assembled code suggestion.
83-
pub fn splice_lines(&self, cm: &CodeMapper) -> String {
107+
/// Returns the number of substitutions
108+
fn substitutions(&self) -> usize {
109+
self.substitution_parts[0].substitutions.len()
110+
}
111+
112+
/// Returns the number of substitutions
113+
pub fn substitution_spans<'a>(&'a self) -> impl Iterator<Item = Span> + 'a {
114+
self.substitution_parts.iter().map(|sub| sub.span)
115+
}
116+
117+
/// Returns the assembled code suggestions.
118+
pub fn splice_lines(&self, cm: &CodeMapper) -> Vec<String> {
84119
use syntax_pos::{CharPos, Loc, Pos};
85120

86121
fn push_trailing(buf: &mut String,
@@ -102,20 +137,22 @@ impl CodeSuggestion {
102137
}
103138
}
104139

105-
let mut primary_spans = self.msp.primary_spans().to_owned();
106-
107-
assert_eq!(primary_spans.len(), self.substitutes.len());
108-
if primary_spans.is_empty() {
109-
return format!("");
140+
if self.substitution_parts.is_empty() {
141+
return vec![String::new()];
110142
}
111143

144+
let mut primary_spans: Vec<_> = self.substitution_parts
145+
.iter()
146+
.map(|sub| (sub.span, &sub.substitutions))
147+
.collect();
148+
112149
// Assumption: all spans are in the same file, and all spans
113150
// are disjoint. Sort in ascending order.
114-
primary_spans.sort_by_key(|sp| sp.lo);
151+
primary_spans.sort_by_key(|sp| sp.0.lo);
115152

116153
// Find the bounding span.
117-
let lo = primary_spans.iter().map(|sp| sp.lo).min().unwrap();
118-
let hi = primary_spans.iter().map(|sp| sp.hi).min().unwrap();
154+
let lo = primary_spans.iter().map(|sp| sp.0.lo).min().unwrap();
155+
let hi = primary_spans.iter().map(|sp| sp.0.hi).min().unwrap();
119156
let bounding_span = Span {
120157
lo: lo,
121158
hi: hi,
@@ -138,33 +175,40 @@ impl CodeSuggestion {
138175
prev_hi.col = CharPos::from_usize(0);
139176

140177
let mut prev_line = fm.get_line(lines.lines[0].line_index);
141-
let mut buf = String::new();
178+
let mut bufs = vec![String::new(); self.substitutions()];
142179

143-
for (sp, substitute) in primary_spans.iter().zip(self.substitutes.iter()) {
180+
for (sp, substitutes) in primary_spans {
144181
let cur_lo = cm.lookup_char_pos(sp.lo);
145-
if prev_hi.line == cur_lo.line {
146-
push_trailing(&mut buf, prev_line, &prev_hi, Some(&cur_lo));
147-
} else {
148-
push_trailing(&mut buf, prev_line, &prev_hi, None);
149-
// push lines between the previous and current span (if any)
150-
for idx in prev_hi.line..(cur_lo.line - 1) {
151-
if let Some(line) = fm.get_line(idx) {
152-
buf.push_str(line);
153-
buf.push('\n');
182+
for (buf, substitute) in bufs.iter_mut().zip(substitutes) {
183+
if prev_hi.line == cur_lo.line {
184+
push_trailing(buf, prev_line, &prev_hi, Some(&cur_lo));
185+
} else {
186+
push_trailing(buf, prev_line, &prev_hi, None);
187+
// push lines between the previous and current span (if any)
188+
for idx in prev_hi.line..(cur_lo.line - 1) {
189+
if let Some(line) = fm.get_line(idx) {
190+
buf.push_str(line);
191+
buf.push('\n');
192+
}
193+
}
194+
if let Some(cur_line) = fm.get_line(cur_lo.line - 1) {
195+
buf.push_str(&cur_line[..cur_lo.col.to_usize()]);
154196
}
155197
}
156-
if let Some(cur_line) = fm.get_line(cur_lo.line - 1) {
157-
buf.push_str(&cur_line[..cur_lo.col.to_usize()]);
158-
}
198+
buf.push_str(substitute);
159199
}
160-
buf.push_str(substitute);
161200
prev_hi = cm.lookup_char_pos(sp.hi);
162201
prev_line = fm.get_line(prev_hi.line - 1);
163202
}
164-
push_trailing(&mut buf, prev_line, &prev_hi, None);
165-
// remove trailing newline
166-
buf.pop();
167-
buf
203+
for buf in &mut bufs {
204+
// if the replacement already ends with a newline, don't print the next line
205+
if !buf.ends_with('\n') {
206+
push_trailing(buf, prev_line, &prev_hi, None);
207+
}
208+
// remove trailing newline
209+
buf.pop();
210+
}
211+
bufs
168212
}
169213
}
170214

0 commit comments

Comments
 (0)