]>
Commit | Line | Data |
---|---|---|
9cc50fc6 | 1 | // Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT |
223e47cc LB |
2 | // file at the top-level directory of this distribution and at |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
1a4d82fc JJ |
11 | use self::Destination::*; |
12 | ||
5bcae85e | 13 | use syntax_pos::{COMMAND_LINE_SP, DUMMY_SP, FileMap, Span, MultiSpan, CharPos}; |
223e47cc | 14 | |
9e0c209e | 15 | use {Level, CodeSuggestion, DiagnosticBuilder, SubDiagnostic, CodeMapper}; |
3157f602 | 16 | use RenderSpan::*; |
5bcae85e SL |
17 | use snippet::{StyledString, Style, Annotation, Line}; |
18 | use styled_buffer::StyledBuffer; | |
9cc50fc6 | 19 | |
c34b1796 AL |
20 | use std::io::prelude::*; |
21 | use std::io; | |
9cc50fc6 | 22 | use std::rc::Rc; |
92a42be0 | 23 | use term; |
1a4d82fc | 24 | |
5bcae85e | 25 | /// Emitter trait for emitting errors. |
9cc50fc6 | 26 | pub trait Emitter { |
9cc50fc6 | 27 | /// Emit a structured diagnostic. |
5bcae85e | 28 | fn emit(&mut self, db: &DiagnosticBuilder); |
a7813a04 XL |
29 | } |
30 | ||
5bcae85e SL |
31 | impl Emitter for EmitterWriter { |
32 | fn emit(&mut self, db: &DiagnosticBuilder) { | |
9e0c209e SL |
33 | let mut primary_span = db.span.clone(); |
34 | let mut children = db.children.clone(); | |
35 | self.fix_multispans_in_std_macros(&mut primary_span, &mut children); | |
36 | self.emit_messages_default(&db.level, &db.message, &db.code, &primary_span, &children); | |
1a4d82fc | 37 | } |
223e47cc LB |
38 | } |
39 | ||
9cc50fc6 | 40 | /// maximum number of lines we will print for each error; arbitrary. |
7453a54e SL |
41 | pub const MAX_HIGHLIGHT_LINES: usize = 6; |
42 | ||
9cc50fc6 | 43 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
1a4d82fc JJ |
44 | pub enum ColorConfig { |
45 | Auto, | |
46 | Always, | |
9cc50fc6 | 47 | Never, |
223e47cc LB |
48 | } |
49 | ||
9cc50fc6 SL |
50 | impl ColorConfig { |
51 | fn use_color(&self) -> bool { | |
52 | match *self { | |
53 | ColorConfig::Always => true, | |
54 | ColorConfig::Never => false, | |
55 | ColorConfig::Auto => stderr_isatty(), | |
56 | } | |
d9579d0f AL |
57 | } |
58 | } | |
59 | ||
9cc50fc6 SL |
60 | pub struct EmitterWriter { |
61 | dst: Destination, | |
5bcae85e | 62 | cm: Option<Rc<CodeMapper>>, |
a7813a04 | 63 | } |
223e47cc | 64 | |
5bcae85e SL |
65 | struct FileWithAnnotatedLines { |
66 | file: Rc<FileMap>, | |
67 | lines: Vec<Line>, | |
223e47cc LB |
68 | } |
69 | ||
5bcae85e | 70 | |
c1a9b12d SL |
71 | /// Do not use this for messages that end in `\n` – use `println_maybe_styled` instead. See |
72 | /// `EmitterWriter::print_maybe_styled` for details. | |
73 | macro_rules! print_maybe_styled { | |
9cc50fc6 SL |
74 | ($dst: expr, $style: expr, $($arg: tt)*) => { |
75 | $dst.print_maybe_styled(format_args!($($arg)*), $style, false) | |
c1a9b12d SL |
76 | } |
77 | } | |
78 | ||
79 | macro_rules! println_maybe_styled { | |
9cc50fc6 SL |
80 | ($dst: expr, $style: expr, $($arg: tt)*) => { |
81 | $dst.print_maybe_styled(format_args!($($arg)*), $style, true) | |
c1a9b12d SL |
82 | } |
83 | } | |
84 | ||
1a4d82fc JJ |
85 | impl EmitterWriter { |
86 | pub fn stderr(color_config: ColorConfig, | |
5bcae85e | 87 | code_map: Option<Rc<CodeMapper>>) |
9cc50fc6 SL |
88 | -> EmitterWriter { |
89 | if color_config.use_color() { | |
90 | let dst = Destination::from_stderr(); | |
a7813a04 | 91 | EmitterWriter { dst: dst, |
5bcae85e | 92 | cm: code_map} |
1a4d82fc | 93 | } else { |
a7813a04 | 94 | EmitterWriter { dst: Raw(Box::new(io::stderr())), |
5bcae85e | 95 | cm: code_map} |
1a4d82fc JJ |
96 | } |
97 | } | |
98 | ||
c34b1796 | 99 | pub fn new(dst: Box<Write + Send>, |
5bcae85e | 100 | code_map: Option<Rc<CodeMapper>>) |
9cc50fc6 | 101 | -> EmitterWriter { |
a7813a04 | 102 | EmitterWriter { dst: Raw(dst), |
5bcae85e | 103 | cm: code_map} |
a7813a04 XL |
104 | } |
105 | ||
5bcae85e SL |
106 | fn preprocess_annotations(&self, msp: &MultiSpan) -> Vec<FileWithAnnotatedLines> { |
107 | fn add_annotation_to_file(file_vec: &mut Vec<FileWithAnnotatedLines>, | |
108 | file: Rc<FileMap>, | |
109 | line_index: usize, | |
110 | ann: Annotation) { | |
111 | ||
112 | for slot in file_vec.iter_mut() { | |
113 | // Look through each of our files for the one we're adding to | |
114 | if slot.file.name == file.name { | |
115 | // See if we already have a line for it | |
116 | for line_slot in &mut slot.lines { | |
117 | if line_slot.line_index == line_index { | |
118 | line_slot.annotations.push(ann); | |
119 | return; | |
120 | } | |
121 | } | |
122 | // We don't have a line yet, create one | |
123 | slot.lines.push(Line { | |
124 | line_index: line_index, | |
125 | annotations: vec![ann], | |
126 | }); | |
127 | slot.lines.sort(); | |
128 | return; | |
a7813a04 XL |
129 | } |
130 | } | |
5bcae85e SL |
131 | // This is the first time we're seeing the file |
132 | file_vec.push(FileWithAnnotatedLines { | |
133 | file: file, | |
134 | lines: vec![Line { | |
135 | line_index: line_index, | |
136 | annotations: vec![ann], | |
137 | }], | |
138 | }); | |
a7813a04 | 139 | } |
c1a9b12d | 140 | |
5bcae85e SL |
141 | let mut output = vec![]; |
142 | ||
143 | if let Some(ref cm) = self.cm { | |
144 | for span_label in msp.span_labels() { | |
145 | if span_label.span == DUMMY_SP || span_label.span == COMMAND_LINE_SP { | |
146 | continue; | |
a7813a04 | 147 | } |
5bcae85e SL |
148 | let lo = cm.lookup_char_pos(span_label.span.lo); |
149 | let mut hi = cm.lookup_char_pos(span_label.span.hi); | |
150 | let mut is_minimized = false; | |
151 | ||
152 | // If the span is multi-line, simplify down to the span of one character | |
153 | if lo.line != hi.line { | |
154 | hi.line = lo.line; | |
155 | hi.col = CharPos(lo.col.0 + 1); | |
156 | is_minimized = true; | |
a7813a04 | 157 | } |
5bcae85e SL |
158 | |
159 | // Watch out for "empty spans". If we get a span like 6..6, we | |
160 | // want to just display a `^` at 6, so convert that to | |
161 | // 6..7. This is degenerate input, but it's best to degrade | |
162 | // gracefully -- and the parser likes to supply a span like | |
163 | // that for EOF, in particular. | |
164 | if lo.col == hi.col { | |
165 | hi.col = CharPos(lo.col.0 + 1); | |
a7813a04 | 166 | } |
5bcae85e SL |
167 | |
168 | add_annotation_to_file(&mut output, | |
169 | lo.file, | |
170 | lo.line, | |
171 | Annotation { | |
172 | start_col: lo.col.0, | |
173 | end_col: hi.col.0, | |
174 | is_primary: span_label.is_primary, | |
175 | is_minimized: is_minimized, | |
176 | label: span_label.label.clone(), | |
177 | }); | |
a7813a04 XL |
178 | } |
179 | } | |
5bcae85e SL |
180 | output |
181 | } | |
182 | ||
183 | fn render_source_line(&self, | |
184 | buffer: &mut StyledBuffer, | |
185 | file: Rc<FileMap>, | |
186 | line: &Line, | |
187 | width_offset: usize) { | |
188 | let source_string = file.get_line(line.line_index - 1) | |
189 | .unwrap_or(""); | |
190 | ||
191 | let line_offset = buffer.num_lines(); | |
c1a9b12d | 192 | |
5bcae85e SL |
193 | // First create the source line we will highlight. |
194 | buffer.puts(line_offset, width_offset, &source_string, Style::Quotation); | |
195 | buffer.puts(line_offset, | |
196 | 0, | |
197 | &(line.line_index.to_string()), | |
198 | Style::LineNumber); | |
199 | ||
200 | draw_col_separator(buffer, line_offset, width_offset - 2); | |
201 | ||
202 | if line.annotations.is_empty() { | |
203 | return; | |
a7813a04 | 204 | } |
c1a9b12d | 205 | |
5bcae85e SL |
206 | // We want to display like this: |
207 | // | |
208 | // vec.push(vec.pop().unwrap()); | |
209 | // --- ^^^ _ previous borrow ends here | |
210 | // | | | |
211 | // | error occurs here | |
212 | // previous borrow of `vec` occurs here | |
213 | // | |
214 | // But there are some weird edge cases to be aware of: | |
215 | // | |
216 | // vec.push(vec.pop().unwrap()); | |
217 | // -------- - previous borrow ends here | |
218 | // || | |
219 | // |this makes no sense | |
220 | // previous borrow of `vec` occurs here | |
221 | // | |
222 | // For this reason, we group the lines into "highlight lines" | |
223 | // and "annotations lines", where the highlight lines have the `~`. | |
224 | ||
225 | // Sort the annotations by (start, end col) | |
226 | let mut annotations = line.annotations.clone(); | |
227 | annotations.sort(); | |
228 | ||
229 | // Next, create the highlight line. | |
230 | for annotation in &annotations { | |
231 | for p in annotation.start_col..annotation.end_col { | |
232 | if annotation.is_primary { | |
233 | buffer.putc(line_offset + 1, | |
234 | width_offset + p, | |
235 | '^', | |
236 | Style::UnderlinePrimary); | |
237 | if !annotation.is_minimized { | |
238 | buffer.set_style(line_offset, | |
239 | width_offset + p, | |
240 | Style::UnderlinePrimary); | |
241 | } | |
242 | } else { | |
243 | buffer.putc(line_offset + 1, | |
244 | width_offset + p, | |
245 | '-', | |
246 | Style::UnderlineSecondary); | |
247 | if !annotation.is_minimized { | |
248 | buffer.set_style(line_offset, | |
249 | width_offset + p, | |
250 | Style::UnderlineSecondary); | |
251 | } | |
252 | } | |
a7813a04 | 253 | } |
a7813a04 | 254 | } |
5bcae85e | 255 | draw_col_separator(buffer, line_offset + 1, width_offset - 2); |
a7813a04 | 256 | |
5bcae85e SL |
257 | // Now we are going to write labels in. To start, we'll exclude |
258 | // the annotations with no labels. | |
259 | let (labeled_annotations, unlabeled_annotations): (Vec<_>, _) = annotations.into_iter() | |
260 | .partition(|a| a.label.is_some()); | |
261 | ||
262 | // If there are no annotations that need text, we're done. | |
263 | if labeled_annotations.is_empty() { | |
264 | return; | |
265 | } | |
266 | // Now add the text labels. We try, when possible, to stick the rightmost | |
267 | // annotation at the end of the highlight line: | |
268 | // | |
269 | // vec.push(vec.pop().unwrap()); | |
270 | // --- --- - previous borrow ends here | |
271 | // | |
272 | // But sometimes that's not possible because one of the other | |
273 | // annotations overlaps it. For example, from the test | |
274 | // `span_overlap_label`, we have the following annotations | |
275 | // (written on distinct lines for clarity): | |
276 | // | |
277 | // fn foo(x: u32) { | |
278 | // -------------- | |
279 | // - | |
280 | // | |
281 | // In this case, we can't stick the rightmost-most label on | |
282 | // the highlight line, or we would get: | |
283 | // | |
284 | // fn foo(x: u32) { | |
285 | // -------- x_span | |
286 | // | | |
287 | // fn_span | |
288 | // | |
289 | // which is totally weird. Instead we want: | |
290 | // | |
291 | // fn foo(x: u32) { | |
292 | // -------------- | |
293 | // | | | |
294 | // | x_span | |
295 | // fn_span | |
296 | // | |
297 | // which is...less weird, at least. In fact, in general, if | |
298 | // the rightmost span overlaps with any other span, we should | |
299 | // use the "hang below" version, so we can at least make it | |
300 | // clear where the span *starts*. | |
301 | let mut labeled_annotations = &labeled_annotations[..]; | |
302 | match labeled_annotations.split_last().unwrap() { | |
303 | (last, previous) => { | |
304 | if previous.iter() | |
305 | .chain(&unlabeled_annotations) | |
306 | .all(|a| !overlaps(a, last)) { | |
307 | // append the label afterwards; we keep it in a separate | |
308 | // string | |
309 | let highlight_label: String = format!(" {}", last.label.as_ref().unwrap()); | |
310 | if last.is_primary { | |
311 | buffer.append(line_offset + 1, &highlight_label, Style::LabelPrimary); | |
312 | } else { | |
313 | buffer.append(line_offset + 1, &highlight_label, Style::LabelSecondary); | |
314 | } | |
315 | labeled_annotations = previous; | |
a7813a04 | 316 | } |
c1a9b12d | 317 | } |
5bcae85e SL |
318 | } |
319 | ||
320 | // If that's the last annotation, we're done | |
321 | if labeled_annotations.is_empty() { | |
322 | return; | |
323 | } | |
324 | ||
325 | for (index, annotation) in labeled_annotations.iter().enumerate() { | |
326 | // Leave: | |
327 | // - 1 extra line | |
328 | // - One line for each thing that comes after | |
329 | let comes_after = labeled_annotations.len() - index - 1; | |
330 | let blank_lines = 3 + comes_after; | |
331 | ||
332 | // For each blank line, draw a `|` at our column. The | |
333 | // text ought to be long enough for this. | |
334 | for index in 2..blank_lines { | |
335 | if annotation.is_primary { | |
336 | buffer.putc(line_offset + index, | |
337 | width_offset + annotation.start_col, | |
338 | '|', | |
339 | Style::UnderlinePrimary); | |
340 | } else { | |
341 | buffer.putc(line_offset + index, | |
342 | width_offset + annotation.start_col, | |
343 | '|', | |
344 | Style::UnderlineSecondary); | |
a7813a04 | 345 | } |
5bcae85e SL |
346 | draw_col_separator(buffer, line_offset + index, width_offset - 2); |
347 | } | |
348 | ||
349 | if annotation.is_primary { | |
350 | buffer.puts(line_offset + blank_lines, | |
351 | width_offset + annotation.start_col, | |
352 | annotation.label.as_ref().unwrap(), | |
353 | Style::LabelPrimary); | |
354 | } else { | |
355 | buffer.puts(line_offset + blank_lines, | |
356 | width_offset + annotation.start_col, | |
357 | annotation.label.as_ref().unwrap(), | |
358 | Style::LabelSecondary); | |
c1a9b12d | 359 | } |
5bcae85e | 360 | draw_col_separator(buffer, line_offset + blank_lines, width_offset - 2); |
c1a9b12d | 361 | } |
5bcae85e SL |
362 | } |
363 | ||
364 | fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize { | |
365 | let mut max = 0; | |
366 | if let Some(ref cm) = self.cm { | |
367 | for primary_span in msp.primary_spans() { | |
368 | if primary_span != &DUMMY_SP && primary_span != &COMMAND_LINE_SP { | |
369 | let hi = cm.lookup_char_pos(primary_span.hi); | |
370 | if hi.line > max { | |
371 | max = hi.line; | |
372 | } | |
373 | } | |
374 | } | |
375 | for span_label in msp.span_labels() { | |
376 | if span_label.span != DUMMY_SP && span_label.span != COMMAND_LINE_SP { | |
377 | let hi = cm.lookup_char_pos(span_label.span.hi); | |
378 | if hi.line > max { | |
379 | max = hi.line; | |
380 | } | |
a7813a04 | 381 | } |
7453a54e | 382 | } |
c1a9b12d | 383 | } |
5bcae85e | 384 | max |
c1a9b12d SL |
385 | } |
386 | ||
9e0c209e | 387 | fn get_max_line_num(&mut self, span: &MultiSpan, children: &Vec<SubDiagnostic>) -> usize { |
5bcae85e | 388 | let mut max = 0; |
3157f602 | 389 | |
9e0c209e | 390 | let primary = self.get_multispan_max_line_num(span); |
5bcae85e | 391 | max = if primary > max { primary } else { max }; |
c1a9b12d | 392 | |
9e0c209e | 393 | for sub in children { |
5bcae85e SL |
394 | let sub_result = self.get_multispan_max_line_num(&sub.span); |
395 | max = if sub_result > max { primary } else { max }; | |
c1a9b12d | 396 | } |
5bcae85e | 397 | max |
c1a9b12d SL |
398 | } |
399 | ||
9e0c209e SL |
400 | // This "fixes" MultiSpans that contain Spans that are pointing to locations inside of |
401 | // <*macros>. Since these locations are often difficult to read, we move these Spans from | |
402 | // <*macros> to their corresponding use site. | |
403 | fn fix_multispan_in_std_macros(&mut self, span: &mut MultiSpan) -> bool { | |
404 | let mut spans_updated = false; | |
405 | ||
406 | if let Some(ref cm) = self.cm { | |
407 | let mut before_after: Vec<(Span, Span)> = vec![]; | |
408 | let mut new_labels: Vec<(Span, String)> = vec![]; | |
409 | ||
410 | // First, find all the spans in <*macros> and point instead at their use site | |
411 | for sp in span.primary_spans() { | |
412 | if (*sp == COMMAND_LINE_SP) || (*sp == DUMMY_SP) { | |
413 | continue; | |
414 | } | |
415 | if cm.span_to_filename(sp.clone()).contains("macros>") { | |
416 | let v = cm.macro_backtrace(sp.clone()); | |
417 | if let Some(use_site) = v.last() { | |
418 | before_after.push((sp.clone(), use_site.call_site.clone())); | |
419 | } | |
420 | } | |
421 | for trace in cm.macro_backtrace(sp.clone()).iter().rev() { | |
422 | // Only show macro locations that are local | |
423 | // and display them like a span_note | |
424 | if let Some(def_site) = trace.def_site_span { | |
425 | if (def_site == COMMAND_LINE_SP) || (def_site == DUMMY_SP) { | |
426 | continue; | |
427 | } | |
428 | // Check to make sure we're not in any <*macros> | |
429 | if !cm.span_to_filename(def_site).contains("macros>") && | |
430 | !trace.macro_decl_name.starts_with("#[") | |
431 | { | |
432 | new_labels.push((trace.call_site, | |
433 | "in this macro invocation".to_string())); | |
434 | break; | |
435 | } | |
436 | } | |
437 | } | |
438 | } | |
439 | for (label_span, label_text) in new_labels { | |
440 | span.push_span_label(label_span, label_text); | |
441 | } | |
442 | for sp_label in span.span_labels() { | |
443 | if (sp_label.span == COMMAND_LINE_SP) || (sp_label.span == DUMMY_SP) { | |
444 | continue; | |
445 | } | |
446 | if cm.span_to_filename(sp_label.span.clone()).contains("macros>") { | |
447 | let v = cm.macro_backtrace(sp_label.span.clone()); | |
448 | if let Some(use_site) = v.last() { | |
449 | before_after.push((sp_label.span.clone(), use_site.call_site.clone())); | |
450 | } | |
451 | } | |
452 | } | |
453 | // After we have them, make sure we replace these 'bad' def sites with their use sites | |
454 | for (before, after) in before_after { | |
455 | span.replace(before, after); | |
456 | spans_updated = true; | |
457 | } | |
458 | } | |
459 | ||
460 | spans_updated | |
461 | } | |
462 | ||
463 | // This does a small "fix" for multispans by looking to see if it can find any that | |
464 | // point directly at <*macros>. Since these are often difficult to read, this | |
465 | // will change the span to point at the use site. | |
466 | fn fix_multispans_in_std_macros(&mut self, | |
467 | span: &mut MultiSpan, | |
468 | children: &mut Vec<SubDiagnostic>) { | |
469 | let mut spans_updated = self.fix_multispan_in_std_macros(span); | |
470 | for child in children.iter_mut() { | |
471 | spans_updated |= self.fix_multispan_in_std_macros(&mut child.span); | |
472 | } | |
473 | if spans_updated { | |
474 | children.push(SubDiagnostic { | |
475 | level: Level::Note, | |
476 | message:"this error originates in a macro outside of the current \ | |
477 | crate".to_string(), | |
478 | span: MultiSpan::new(), | |
479 | render_span: None | |
480 | }); | |
481 | } | |
482 | } | |
483 | ||
5bcae85e SL |
484 | fn emit_message_default(&mut self, |
485 | msp: &MultiSpan, | |
486 | msg: &str, | |
487 | code: &Option<String>, | |
488 | level: &Level, | |
489 | max_line_num_len: usize, | |
490 | is_secondary: bool) | |
491 | -> io::Result<()> { | |
492 | let mut buffer = StyledBuffer::new(); | |
493 | ||
494 | if msp.primary_spans().is_empty() && msp.span_labels().is_empty() && is_secondary { | |
495 | // This is a secondary message with no span info | |
496 | for _ in 0..max_line_num_len { | |
497 | buffer.prepend(0, " ", Style::NoStyle); | |
498 | } | |
499 | draw_note_separator(&mut buffer, 0, max_line_num_len + 1); | |
500 | buffer.append(0, &level.to_string(), Style::HeaderMsg); | |
501 | buffer.append(0, ": ", Style::NoStyle); | |
502 | buffer.append(0, msg, Style::NoStyle); | |
503 | } | |
504 | else { | |
505 | buffer.append(0, &level.to_string(), Style::Level(level.clone())); | |
506 | match code { | |
507 | &Some(ref code) => { | |
508 | buffer.append(0, "[", Style::Level(level.clone())); | |
509 | buffer.append(0, &code, Style::Level(level.clone())); | |
510 | buffer.append(0, "]", Style::Level(level.clone())); | |
511 | } | |
512 | _ => {} | |
513 | } | |
514 | buffer.append(0, ": ", Style::HeaderMsg); | |
515 | buffer.append(0, msg, Style::HeaderMsg); | |
516 | } | |
517 | ||
518 | // Preprocess all the annotations so that they are grouped by file and by line number | |
519 | // This helps us quickly iterate over the whole message (including secondary file spans) | |
520 | let mut annotated_files = self.preprocess_annotations(msp); | |
a7813a04 | 521 | |
5bcae85e SL |
522 | // Make sure our primary file comes first |
523 | let primary_lo = | |
524 | if let (Some(ref cm), Some(ref primary_span)) = (self.cm.as_ref(), | |
525 | msp.primary_span().as_ref()) { | |
526 | if primary_span != &&DUMMY_SP && primary_span != &&COMMAND_LINE_SP { | |
527 | cm.lookup_char_pos(primary_span.lo) | |
7453a54e | 528 | } |
a7813a04 | 529 | else { |
5bcae85e SL |
530 | emit_to_destination(&buffer.render(), level, &mut self.dst)?; |
531 | return Ok(()); | |
532 | } | |
533 | } else { | |
534 | // If we don't have span information, emit and exit | |
535 | emit_to_destination(&buffer.render(), level, &mut self.dst)?; | |
536 | return Ok(()); | |
537 | }; | |
538 | if let Ok(pos) = | |
539 | annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name)) { | |
540 | annotated_files.swap(0, pos); | |
541 | } | |
542 | ||
543 | // Print out the annotate source lines that correspond with the error | |
544 | for annotated_file in annotated_files { | |
545 | // print out the span location and spacer before we print the annotated source | |
546 | // to do this, we need to know if this span will be primary | |
547 | let is_primary = primary_lo.file.name == annotated_file.file.name; | |
548 | if is_primary { | |
549 | // remember where we are in the output buffer for easy reference | |
550 | let buffer_msg_line_offset = buffer.num_lines(); | |
551 | ||
552 | buffer.prepend(buffer_msg_line_offset, "--> ", Style::LineNumber); | |
553 | let loc = primary_lo.clone(); | |
554 | buffer.append(buffer_msg_line_offset, | |
555 | &format!("{}:{}:{}", loc.file.name, loc.line, loc.col.0 + 1), | |
556 | Style::LineAndColumn); | |
557 | for _ in 0..max_line_num_len { | |
558 | buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle); | |
559 | } | |
560 | } else { | |
561 | // remember where we are in the output buffer for easy reference | |
562 | let buffer_msg_line_offset = buffer.num_lines(); | |
563 | ||
564 | // Add spacing line | |
565 | draw_col_separator(&mut buffer, buffer_msg_line_offset, max_line_num_len + 1); | |
566 | ||
567 | // Then, the secondary file indicator | |
568 | buffer.prepend(buffer_msg_line_offset + 1, "::: ", Style::LineNumber); | |
569 | buffer.append(buffer_msg_line_offset + 1, | |
570 | &annotated_file.file.name, | |
571 | Style::LineAndColumn); | |
572 | for _ in 0..max_line_num_len { | |
573 | buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle); | |
c1a9b12d | 574 | } |
a7813a04 | 575 | } |
c1a9b12d | 576 | |
5bcae85e SL |
577 | // Put in the spacer between the location and annotated source |
578 | let buffer_msg_line_offset = buffer.num_lines(); | |
579 | draw_col_separator_no_space(&mut buffer, buffer_msg_line_offset, max_line_num_len + 1); | |
580 | ||
581 | // Next, output the annotate source for this file | |
582 | for line_idx in 0..annotated_file.lines.len() { | |
583 | self.render_source_line(&mut buffer, | |
584 | annotated_file.file.clone(), | |
585 | &annotated_file.lines[line_idx], | |
586 | 3 + max_line_num_len); | |
587 | ||
588 | // check to see if we need to print out or elide lines that come between | |
589 | // this annotated line and the next one | |
590 | if line_idx < (annotated_file.lines.len() - 1) { | |
591 | let line_idx_delta = annotated_file.lines[line_idx + 1].line_index - | |
592 | annotated_file.lines[line_idx].line_index; | |
593 | if line_idx_delta > 2 { | |
594 | let last_buffer_line_num = buffer.num_lines(); | |
595 | buffer.puts(last_buffer_line_num, 0, "...", Style::LineNumber); | |
596 | } else if line_idx_delta == 2 { | |
597 | let unannotated_line = annotated_file.file | |
598 | .get_line(annotated_file.lines[line_idx].line_index) | |
599 | .unwrap_or(""); | |
600 | ||
601 | let last_buffer_line_num = buffer.num_lines(); | |
602 | ||
603 | buffer.puts(last_buffer_line_num, | |
604 | 0, | |
605 | &(annotated_file.lines[line_idx + 1].line_index - 1) | |
606 | .to_string(), | |
607 | Style::LineNumber); | |
608 | draw_col_separator(&mut buffer, last_buffer_line_num, 1 + max_line_num_len); | |
609 | buffer.puts(last_buffer_line_num, | |
610 | 3 + max_line_num_len, | |
611 | &unannotated_line, | |
612 | Style::Quotation); | |
c1a9b12d SL |
613 | } |
614 | } | |
7453a54e SL |
615 | } |
616 | } | |
5bcae85e | 617 | |
5bcae85e SL |
618 | // final step: take our styled buffer, render it, then output it |
619 | emit_to_destination(&buffer.render(), level, &mut self.dst)?; | |
620 | ||
621 | Ok(()) | |
622 | } | |
623 | fn emit_suggestion_default(&mut self, | |
624 | suggestion: &CodeSuggestion, | |
625 | level: &Level, | |
626 | msg: &str, | |
627 | max_line_num_len: usize) | |
628 | -> io::Result<()> { | |
629 | use std::borrow::Borrow; | |
630 | ||
631 | let primary_span = suggestion.msp.primary_span().unwrap(); | |
632 | if let Some(ref cm) = self.cm { | |
633 | let mut buffer = StyledBuffer::new(); | |
634 | ||
635 | buffer.append(0, &level.to_string(), Style::Level(level.clone())); | |
636 | buffer.append(0, ": ", Style::HeaderMsg); | |
637 | buffer.append(0, msg, Style::HeaderMsg); | |
638 | ||
639 | let lines = cm.span_to_lines(primary_span).unwrap(); | |
640 | ||
641 | assert!(!lines.lines.is_empty()); | |
642 | ||
643 | let complete = suggestion.splice_lines(cm.borrow()); | |
644 | ||
645 | // print the suggestion without any line numbers, but leave | |
646 | // space for them. This helps with lining up with previous | |
647 | // snippets from the actual error being reported. | |
648 | let mut lines = complete.lines(); | |
649 | let mut row_num = 1; | |
650 | for line in lines.by_ref().take(MAX_HIGHLIGHT_LINES) { | |
651 | draw_col_separator(&mut buffer, row_num, max_line_num_len + 1); | |
652 | buffer.append(row_num, line, Style::NoStyle); | |
653 | row_num += 1; | |
c1a9b12d | 654 | } |
5bcae85e SL |
655 | |
656 | // if we elided some lines, add an ellipsis | |
657 | if let Some(_) = lines.next() { | |
658 | buffer.append(row_num, "...", Style::NoStyle); | |
7453a54e | 659 | } |
5bcae85e | 660 | emit_to_destination(&buffer.render(), level, &mut self.dst)?; |
c1a9b12d | 661 | } |
7453a54e | 662 | Ok(()) |
c1a9b12d | 663 | } |
9e0c209e SL |
664 | fn emit_messages_default(&mut self, |
665 | level: &Level, | |
666 | message: &String, | |
667 | code: &Option<String>, | |
668 | span: &MultiSpan, | |
669 | children: &Vec<SubDiagnostic>) { | |
670 | let max_line_num = self.get_max_line_num(span, children); | |
5bcae85e SL |
671 | let max_line_num_len = max_line_num.to_string().len(); |
672 | ||
9e0c209e SL |
673 | match self.emit_message_default(span, |
674 | message, | |
675 | code, | |
676 | level, | |
5bcae85e SL |
677 | max_line_num_len, |
678 | false) { | |
679 | Ok(()) => { | |
9e0c209e | 680 | if !children.is_empty() { |
5bcae85e SL |
681 | let mut buffer = StyledBuffer::new(); |
682 | draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1); | |
9e0c209e | 683 | match emit_to_destination(&buffer.render(), level, &mut self.dst) { |
5bcae85e SL |
684 | Ok(()) => (), |
685 | Err(e) => panic!("failed to emit error: {}", e) | |
686 | } | |
687 | } | |
9e0c209e | 688 | for child in children { |
5bcae85e SL |
689 | match child.render_span { |
690 | Some(FullSpan(ref msp)) => { | |
691 | match self.emit_message_default(msp, | |
692 | &child.message, | |
693 | &None, | |
694 | &child.level, | |
695 | max_line_num_len, | |
696 | true) { | |
697 | Err(e) => panic!("failed to emit error: {}", e), | |
698 | _ => () | |
699 | } | |
700 | }, | |
701 | Some(Suggestion(ref cs)) => { | |
702 | match self.emit_suggestion_default(cs, | |
703 | &child.level, | |
704 | &child.message, | |
705 | max_line_num_len) { | |
706 | Err(e) => panic!("failed to emit error: {}", e), | |
707 | _ => () | |
708 | } | |
709 | }, | |
710 | None => { | |
711 | match self.emit_message_default(&child.span, | |
712 | &child.message, | |
713 | &None, | |
714 | &child.level, | |
715 | max_line_num_len, | |
716 | true) { | |
717 | Err(e) => panic!("failed to emit error: {}", e), | |
718 | _ => () | |
719 | } | |
720 | } | |
721 | } | |
722 | } | |
723 | } | |
724 | Err(e) => panic!("failed to emit error: {}", e) | |
725 | } | |
726 | match write!(&mut self.dst, "\n") { | |
727 | Err(e) => panic!("failed to emit error: {}", e), | |
9e0c209e SL |
728 | _ => match self.dst.flush() { |
729 | Err(e) => panic!("failed to emit error: {}", e), | |
730 | _ => () | |
9cc50fc6 | 731 | } |
c1a9b12d | 732 | } |
c1a9b12d | 733 | } |
1a4d82fc | 734 | } |
970d7e83 | 735 | |
5bcae85e SL |
736 | fn draw_col_separator(buffer: &mut StyledBuffer, line: usize, col: usize) { |
737 | buffer.puts(line, col, "| ", Style::LineNumber); | |
7453a54e SL |
738 | } |
739 | ||
5bcae85e SL |
740 | fn draw_col_separator_no_space(buffer: &mut StyledBuffer, line: usize, col: usize) { |
741 | buffer.puts(line, col, "|", Style::LineNumber); | |
742 | } | |
743 | ||
744 | fn draw_note_separator(buffer: &mut StyledBuffer, line: usize, col: usize) { | |
745 | buffer.puts(line, col, "= ", Style::LineNumber); | |
746 | } | |
747 | ||
748 | fn overlaps(a1: &Annotation, a2: &Annotation) -> bool { | |
749 | (a2.start_col..a2.end_col).contains(a1.start_col) || | |
750 | (a1.start_col..a1.end_col).contains(a2.start_col) | |
751 | } | |
752 | ||
753 | fn emit_to_destination(rendered_buffer: &Vec<Vec<StyledString>>, | |
754 | lvl: &Level, | |
755 | dst: &mut Destination) -> io::Result<()> { | |
9e0c209e SL |
756 | use lock; |
757 | ||
758 | // In order to prevent error message interleaving, where multiple error lines get intermixed | |
759 | // when multiple compiler processes error simultaneously, we emit errors with additional | |
760 | // steps. | |
761 | // | |
762 | // On Unix systems, we write into a buffered terminal rather than directly to a terminal. When | |
763 | // the .flush() is called we take the buffer created from the buffered writes and write it at | |
764 | // one shot. Because the Unix systems use ANSI for the colors, which is a text-based styling | |
765 | // scheme, this buffered approach works and maintains the styling. | |
766 | // | |
767 | // On Windows, styling happens through calls to a terminal API. This prevents us from using the | |
768 | // same buffering approach. Instead, we use a global Windows mutex, which we acquire long | |
769 | // enough to output the full error message, then we release. | |
770 | let _buffer_lock = lock::acquire_global_lock("rustc_errors"); | |
5bcae85e SL |
771 | for line in rendered_buffer { |
772 | for part in line { | |
773 | dst.apply_style(lvl.clone(), part.style)?; | |
774 | write!(dst, "{}", part.text)?; | |
775 | dst.reset_attrs()?; | |
a7813a04 | 776 | } |
5bcae85e | 777 | write!(dst, "\n")?; |
9cc50fc6 | 778 | } |
9e0c209e | 779 | dst.flush()?; |
9cc50fc6 SL |
780 | Ok(()) |
781 | } | |
782 | ||
c34b1796 AL |
783 | #[cfg(unix)] |
784 | fn stderr_isatty() -> bool { | |
92a42be0 | 785 | use libc; |
c34b1796 AL |
786 | unsafe { libc::isatty(libc::STDERR_FILENO) != 0 } |
787 | } | |
788 | #[cfg(windows)] | |
789 | fn stderr_isatty() -> bool { | |
92a42be0 SL |
790 | type DWORD = u32; |
791 | type BOOL = i32; | |
792 | type HANDLE = *mut u8; | |
793 | const STD_ERROR_HANDLE: DWORD = -12i32 as DWORD; | |
c34b1796 | 794 | extern "system" { |
92a42be0 SL |
795 | fn GetStdHandle(which: DWORD) -> HANDLE; |
796 | fn GetConsoleMode(hConsoleHandle: HANDLE, | |
797 | lpMode: *mut DWORD) -> BOOL; | |
c34b1796 AL |
798 | } |
799 | unsafe { | |
800 | let handle = GetStdHandle(STD_ERROR_HANDLE); | |
801 | let mut out = 0; | |
802 | GetConsoleMode(handle, &mut out) != 0 | |
803 | } | |
804 | } | |
805 | ||
9e0c209e SL |
806 | pub type BufferedStderr = term::Terminal<Output = BufferedWriter> + Send; |
807 | ||
5bcae85e | 808 | pub enum Destination { |
9cc50fc6 | 809 | Terminal(Box<term::StderrTerminal>), |
9e0c209e | 810 | BufferedTerminal(Box<BufferedStderr>), |
9cc50fc6 SL |
811 | Raw(Box<Write + Send>), |
812 | } | |
813 | ||
9e0c209e SL |
814 | /// Buffered writer gives us a way on Unix to buffer up an entire error message before we output |
815 | /// it. This helps to prevent interleaving of multiple error messages when multiple compiler | |
816 | /// processes error simultaneously | |
817 | pub struct BufferedWriter { | |
818 | buffer: Vec<u8>, | |
819 | } | |
820 | ||
821 | impl BufferedWriter { | |
822 | // note: we use _new because the conditional compilation at its use site may make this | |
823 | // this function unused on some platforms | |
824 | fn _new() -> BufferedWriter { | |
825 | BufferedWriter { | |
826 | buffer: vec![] | |
827 | } | |
828 | } | |
829 | } | |
830 | ||
831 | impl Write for BufferedWriter { | |
832 | fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | |
833 | for b in buf { | |
834 | self.buffer.push(*b); | |
835 | } | |
836 | Ok(buf.len()) | |
837 | } | |
838 | fn flush(&mut self) -> io::Result<()> { | |
839 | let mut stderr = io::stderr(); | |
840 | let result = (|| { | |
841 | stderr.write_all(&self.buffer)?; | |
842 | stderr.flush() | |
843 | })(); | |
844 | self.buffer.clear(); | |
845 | result | |
846 | } | |
847 | } | |
848 | ||
9cc50fc6 | 849 | impl Destination { |
9e0c209e SL |
850 | #[cfg(not(windows))] |
851 | /// When not on Windows, prefer the buffered terminal so that we can buffer an entire error | |
852 | /// to be emitted at one time. | |
853 | fn from_stderr() -> Destination { | |
854 | let stderr: Option<Box<BufferedStderr>> = | |
855 | term::TerminfoTerminal::new(BufferedWriter::_new()) | |
856 | .map(|t| Box::new(t) as Box<BufferedStderr>); | |
857 | ||
858 | match stderr { | |
859 | Some(t) => BufferedTerminal(t), | |
860 | None => Raw(Box::new(io::stderr())), | |
861 | } | |
862 | } | |
863 | ||
864 | #[cfg(windows)] | |
865 | /// Return a normal, unbuffered terminal when on Windows. | |
9cc50fc6 | 866 | fn from_stderr() -> Destination { |
9e0c209e SL |
867 | let stderr: Option<Box<term::StderrTerminal>> = |
868 | term::TerminfoTerminal::new(io::stderr()) | |
869 | .map(|t| Box::new(t) as Box<term::StderrTerminal>) | |
870 | .or_else(|| term::WinConsole::new(io::stderr()).ok() | |
871 | .map(|t| Box::new(t) as Box<term::StderrTerminal>)); | |
872 | ||
873 | match stderr { | |
9cc50fc6 SL |
874 | Some(t) => Terminal(t), |
875 | None => Raw(Box::new(io::stderr())), | |
876 | } | |
877 | } | |
878 | ||
a7813a04 XL |
879 | fn apply_style(&mut self, |
880 | lvl: Level, | |
a7813a04 XL |
881 | style: Style) |
882 | -> io::Result<()> { | |
883 | match style { | |
5bcae85e | 884 | Style::FileNameStyle | Style::LineAndColumn => {} |
a7813a04 | 885 | Style::LineNumber => { |
9e0c209e SL |
886 | self.start_attr(term::Attr::Bold)?; |
887 | if cfg!(windows) { | |
888 | self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_CYAN))?; | |
889 | } else { | |
890 | self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?; | |
891 | } | |
a7813a04 | 892 | } |
5bcae85e | 893 | Style::ErrorCode => { |
9e0c209e SL |
894 | self.start_attr(term::Attr::Bold)?; |
895 | self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_MAGENTA))?; | |
a7813a04 | 896 | } |
5bcae85e SL |
897 | Style::Quotation => {} |
898 | Style::OldSchoolNote => { | |
9e0c209e SL |
899 | self.start_attr(term::Attr::Bold)?; |
900 | self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_GREEN))?; | |
a7813a04 | 901 | } |
5bcae85e | 902 | Style::OldSchoolNoteText | Style::HeaderMsg => { |
9e0c209e SL |
903 | self.start_attr(term::Attr::Bold)?; |
904 | if cfg!(windows) { | |
905 | self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_WHITE))?; | |
906 | } | |
a7813a04 XL |
907 | } |
908 | Style::UnderlinePrimary | Style::LabelPrimary => { | |
9e0c209e SL |
909 | self.start_attr(term::Attr::Bold)?; |
910 | self.start_attr(term::Attr::ForegroundColor(lvl.color()))?; | |
a7813a04 | 911 | } |
5bcae85e SL |
912 | Style::UnderlineSecondary | |
913 | Style::LabelSecondary => { | |
9e0c209e SL |
914 | self.start_attr(term::Attr::Bold)?; |
915 | if cfg!(windows) { | |
916 | self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_CYAN))?; | |
917 | } else { | |
918 | self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?; | |
919 | } | |
a7813a04 | 920 | } |
5bcae85e SL |
921 | Style::NoStyle => {} |
922 | Style::Level(l) => { | |
9e0c209e SL |
923 | self.start_attr(term::Attr::Bold)?; |
924 | self.start_attr(term::Attr::ForegroundColor(l.color()))?; | |
a7813a04 XL |
925 | } |
926 | } | |
927 | Ok(()) | |
928 | } | |
929 | ||
930 | fn start_attr(&mut self, attr: term::Attr) -> io::Result<()> { | |
931 | match *self { | |
932 | Terminal(ref mut t) => { t.attr(attr)?; } | |
9e0c209e | 933 | BufferedTerminal(ref mut t) => { t.attr(attr)?; } |
a7813a04 XL |
934 | Raw(_) => { } |
935 | } | |
936 | Ok(()) | |
937 | } | |
938 | ||
939 | fn reset_attrs(&mut self) -> io::Result<()> { | |
940 | match *self { | |
941 | Terminal(ref mut t) => { t.reset()?; } | |
9e0c209e | 942 | BufferedTerminal(ref mut t) => { t.reset()?; } |
a7813a04 XL |
943 | Raw(_) => { } |
944 | } | |
945 | Ok(()) | |
946 | } | |
9cc50fc6 SL |
947 | } |
948 | ||
c34b1796 AL |
949 | impl Write for Destination { |
950 | fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { | |
951 | match *self { | |
952 | Terminal(ref mut t) => t.write(bytes), | |
9e0c209e | 953 | BufferedTerminal(ref mut t) => t.write(bytes), |
c34b1796 AL |
954 | Raw(ref mut w) => w.write(bytes), |
955 | } | |
956 | } | |
957 | fn flush(&mut self) -> io::Result<()> { | |
1a4d82fc | 958 | match *self { |
c34b1796 | 959 | Terminal(ref mut t) => t.flush(), |
9e0c209e | 960 | BufferedTerminal(ref mut t) => t.flush(), |
c34b1796 | 961 | Raw(ref mut w) => w.flush(), |
1a4d82fc JJ |
962 | } |
963 | } | |
9e0c209e | 964 | } |