Skip to content

Commit 67d762d

Browse files
committed
Refactor suggestion diagnostic API to allow for multiple suggestions
1 parent 58b33ad commit 67d762d

File tree

5 files changed

+149
-102
lines changed

5 files changed

+149
-102
lines changed

src/librustc_errors/diagnostic.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub struct Diagnostic {
2323
pub code: Option<String>,
2424
pub span: MultiSpan,
2525
pub children: Vec<SubDiagnostic>,
26-
pub suggestion: Option<CodeSuggestion>,
26+
pub suggestions: Vec<CodeSuggestion>,
2727
}
2828

2929
/// For example a note attached to an error.
@@ -87,7 +87,7 @@ impl Diagnostic {
8787
code: code,
8888
span: MultiSpan::new(),
8989
children: vec![],
90-
suggestion: None,
90+
suggestions: vec![],
9191
}
9292
}
9393

@@ -204,10 +204,16 @@ impl Diagnostic {
204204
///
205205
/// See `diagnostic::CodeSuggestion` for more information.
206206
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],
207+
self.suggestions.push(CodeSuggestion {
208+
substitutes: vec![(sp, vec![suggestion])],
209+
msg: msg.to_owned(),
210+
});
211+
self
212+
}
213+
214+
pub fn span_suggestions(&mut self, sp: Span, msg: &str, suggestions: Vec<String>) -> &mut Self {
215+
self.suggestions.push(CodeSuggestion {
216+
substitutes: vec![(sp, suggestions)],
211217
msg: msg.to_owned(),
212218
});
213219
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: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,37 @@ 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());
38+
if db.suggestions.len() == 1 {
39+
let sugg = &db.suggestions[0];
4040
// don't display multispans as labels
4141
if sugg.substitutes.len() == 1 &&
42+
// don't display multi-suggestions as labels
43+
sugg.substitutes[0].1.len() == 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.substitutes[0].1[0].find('\n').is_none() {
48+
let msg = format!("help: {} `{}`", sugg.msg, sugg.substitutes[0].1[0]);
49+
primary_span.push_span_label(sugg.substitutes[0].0, msg);
4850
} else {
4951
children.push(SubDiagnostic {
5052
level: Level::Help,
5153
message: Vec::new(),
5254
span: MultiSpan::new(),
53-
render_span: Some(Suggestion(sugg)),
55+
render_span: Some(Suggestion(sugg.clone())),
56+
});
57+
}
58+
} else {
59+
// if there are multiple suggestions, print them all in full
60+
// to be consistent. We could try to figure out if we can
61+
// make one (or the first one) inline, but that would give
62+
// undue importance to a semi-random suggestion
63+
for sugg in &db.suggestions {
64+
children.push(SubDiagnostic {
65+
level: Level::Help,
66+
message: Vec::new(),
67+
span: MultiSpan::new(),
68+
render_span: Some(Suggestion(sugg.clone())),
5469
});
5570
}
5671
}
@@ -1054,38 +1069,38 @@ impl EmitterWriter {
10541069
-> io::Result<()> {
10551070
use std::borrow::Borrow;
10561071

1057-
let primary_span = suggestion.msp.primary_span().unwrap();
1072+
let primary_span = suggestion.substitutes[0].0;
10581073
if let Some(ref cm) = self.cm {
10591074
let mut buffer = StyledBuffer::new();
10601075

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-
10691076
let lines = cm.span_to_lines(primary_span).unwrap();
10701077

10711078
assert!(!lines.lines.is_empty());
10721079

1073-
let complete = suggestion.splice_lines(cm.borrow());
1074-
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();
1079-
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-
}
1080+
for complete in suggestion.splice_lines(cm.borrow()) {
1081+
buffer.append(0, &level.to_string(), Style::Level(level.clone()));
1082+
buffer.append(0, ": ", Style::HeaderMsg);
1083+
self.msg_to_buffer(&mut buffer,
1084+
&[(suggestion.msg.to_owned(), Style::NoStyle)],
1085+
max_line_num_len,
1086+
"suggestion",
1087+
Some(Style::HeaderMsg));
1088+
1089+
// print the suggestion without any line numbers, but leave
1090+
// space for them. This helps with lining up with previous
1091+
// snippets from the actual error being reported.
1092+
let mut lines = complete.lines();
1093+
let mut row_num = 1;
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+
}
10891104
}
10901105
emit_to_destination(&buffer.render(), level, &mut self.dst)?;
10911106
}

src/librustc_errors/lib.rs

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,25 @@ pub enum RenderSpan {
6565

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

@@ -79,8 +96,8 @@ pub trait CodeMapper {
7996
}
8097

8198
impl CodeSuggestion {
82-
/// Returns the assembled code suggestion.
83-
pub fn splice_lines(&self, cm: &CodeMapper) -> String {
99+
/// Returns the assembled code suggestions.
100+
pub fn splice_lines(&self, cm: &CodeMapper) -> Vec<String> {
84101
use syntax_pos::{CharPos, Loc, Pos};
85102

86103
fn push_trailing(buf: &mut String,
@@ -102,20 +119,22 @@ impl CodeSuggestion {
102119
}
103120
}
104121

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!("");
122+
if self.substitutes.is_empty() {
123+
return vec![String::new()];
110124
}
111125

126+
let mut primary_spans: Vec<_> = self.substitutes
127+
.iter()
128+
.map(|&(sp, ref sub)| (sp, sub))
129+
.collect();
130+
112131
// Assumption: all spans are in the same file, and all spans
113132
// are disjoint. Sort in ascending order.
114-
primary_spans.sort_by_key(|sp| sp.lo);
133+
primary_spans.sort_by_key(|sp| sp.0.lo);
115134

116135
// 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();
136+
let lo = primary_spans.iter().map(|sp| sp.0.lo).min().unwrap();
137+
let hi = primary_spans.iter().map(|sp| sp.0.hi).min().unwrap();
119138
let bounding_span = Span {
120139
lo: lo,
121140
hi: hi,
@@ -138,33 +157,37 @@ impl CodeSuggestion {
138157
prev_hi.col = CharPos::from_usize(0);
139158

140159
let mut prev_line = fm.get_line(lines.lines[0].line_index);
141-
let mut buf = String::new();
160+
let mut bufs = vec![String::new(); self.substitutes[0].1.len()];
142161

143-
for (sp, substitute) in primary_spans.iter().zip(self.substitutes.iter()) {
162+
for (sp, substitutes) in primary_spans {
144163
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');
164+
for (buf, substitute) in bufs.iter_mut().zip(substitutes) {
165+
if prev_hi.line == cur_lo.line {
166+
push_trailing(buf, prev_line, &prev_hi, Some(&cur_lo));
167+
} else {
168+
push_trailing(buf, prev_line, &prev_hi, None);
169+
// push lines between the previous and current span (if any)
170+
for idx in prev_hi.line..(cur_lo.line - 1) {
171+
if let Some(line) = fm.get_line(idx) {
172+
buf.push_str(line);
173+
buf.push('\n');
174+
}
175+
}
176+
if let Some(cur_line) = fm.get_line(cur_lo.line - 1) {
177+
buf.push_str(&cur_line[..cur_lo.col.to_usize()]);
154178
}
155179
}
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-
}
180+
buf.push_str(substitute);
159181
}
160-
buf.push_str(substitute);
161182
prev_hi = cm.lookup_char_pos(sp.hi);
162183
prev_line = fm.get_line(prev_hi.line - 1);
163184
}
164-
push_trailing(&mut buf, prev_line, &prev_hi, None);
165-
// remove trailing newline
166-
buf.pop();
167-
buf
185+
for buf in &mut bufs {
186+
push_trailing(buf, prev_line, &prev_hi, None);
187+
// remove trailing newline
188+
buf.pop();
189+
}
190+
bufs
168191
}
169192
}
170193

0 commit comments

Comments
 (0)