]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/diagnostic.rs
Auto merge of #22517 - brson:relnotes, r=Gankro
[rust.git] / src / libsyntax / diagnostic.rs
1 // Copyright 2012 The Rust Project Developers. See the COPYRIGHT
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
11 pub use self::Level::*;
12 pub use self::RenderSpan::*;
13 pub use self::ColorConfig::*;
14 use self::Destination::*;
15
16 use codemap::{COMMAND_LINE_SP, COMMAND_LINE_EXPN, Pos, Span};
17 use codemap;
18 use diagnostics;
19
20 use std::cell::{RefCell, Cell};
21 use std::fmt;
22 use std::old_io;
23 use std::string::String;
24 use term::WriterWrapper;
25 use term;
26
27 /// maximum number of lines we will print for each error; arbitrary.
28 static MAX_LINES: usize = 6;
29
30 #[derive(Clone, Copy)]
31 pub enum RenderSpan {
32     /// A FullSpan renders with both with an initial line for the
33     /// message, prefixed by file:linenum, followed by a summary of
34     /// the source code covered by the span.
35     FullSpan(Span),
36
37     /// A FileLine renders with just a line for the message prefixed
38     /// by file:linenum.
39     FileLine(Span),
40 }
41
42 impl RenderSpan {
43     fn span(self) -> Span {
44         match self {
45             FullSpan(s) | FileLine(s) => s
46         }
47     }
48     fn is_full_span(&self) -> bool {
49         match self {
50             &FullSpan(..) => true,
51             &FileLine(..) => false,
52         }
53     }
54 }
55
56 #[derive(Clone, Copy)]
57 pub enum ColorConfig {
58     Auto,
59     Always,
60     Never
61 }
62
63 pub trait Emitter {
64     fn emit(&mut self, cmsp: Option<(&codemap::CodeMap, Span)>,
65             msg: &str, code: Option<&str>, lvl: Level);
66     fn custom_emit(&mut self, cm: &codemap::CodeMap,
67                    sp: RenderSpan, msg: &str, lvl: Level);
68 }
69
70 /// This structure is used to signify that a task has panicked with a fatal error
71 /// from the diagnostics. You can use this with the `Any` trait to figure out
72 /// how a rustc task died (if so desired).
73 #[derive(Copy)]
74 pub struct FatalError;
75
76 /// Signifies that the compiler died with an explicit call to `.bug`
77 /// or `.span_bug` rather than a failed assertion, etc.
78 #[derive(Copy)]
79 pub struct ExplicitBug;
80
81 /// A span-handler is like a handler but also
82 /// accepts span information for source-location
83 /// reporting.
84 pub struct SpanHandler {
85     pub handler: Handler,
86     pub cm: codemap::CodeMap,
87 }
88
89 impl SpanHandler {
90     pub fn span_fatal(&self, sp: Span, msg: &str) -> ! {
91         self.handler.emit(Some((&self.cm, sp)), msg, Fatal);
92         panic!(FatalError);
93     }
94     pub fn span_fatal_with_code(&self, sp: Span, msg: &str, code: &str) -> ! {
95         self.handler.emit_with_code(Some((&self.cm, sp)), msg, code, Fatal);
96         panic!(FatalError);
97     }
98     pub fn span_err(&self, sp: Span, msg: &str) {
99         self.handler.emit(Some((&self.cm, sp)), msg, Error);
100         self.handler.bump_err_count();
101     }
102     pub fn span_err_with_code(&self, sp: Span, msg: &str, code: &str) {
103         self.handler.emit_with_code(Some((&self.cm, sp)), msg, code, Error);
104         self.handler.bump_err_count();
105     }
106     pub fn span_warn(&self, sp: Span, msg: &str) {
107         self.handler.emit(Some((&self.cm, sp)), msg, Warning);
108     }
109     pub fn span_warn_with_code(&self, sp: Span, msg: &str, code: &str) {
110         self.handler.emit_with_code(Some((&self.cm, sp)), msg, code, Warning);
111     }
112     pub fn span_note(&self, sp: Span, msg: &str) {
113         self.handler.emit(Some((&self.cm, sp)), msg, Note);
114     }
115     pub fn span_end_note(&self, sp: Span, msg: &str) {
116         self.handler.custom_emit(&self.cm, FullSpan(sp), msg, Note);
117     }
118     pub fn span_help(&self, sp: Span, msg: &str) {
119         self.handler.emit(Some((&self.cm, sp)), msg, Help);
120     }
121     pub fn fileline_note(&self, sp: Span, msg: &str) {
122         self.handler.custom_emit(&self.cm, FileLine(sp), msg, Note);
123     }
124     pub fn fileline_help(&self, sp: Span, msg: &str) {
125         self.handler.custom_emit(&self.cm, FileLine(sp), msg, Help);
126     }
127     pub fn span_bug(&self, sp: Span, msg: &str) -> ! {
128         self.handler.emit(Some((&self.cm, sp)), msg, Bug);
129         panic!(ExplicitBug);
130     }
131     pub fn span_unimpl(&self, sp: Span, msg: &str) -> ! {
132         self.span_bug(sp, &format!("unimplemented {}", msg)[]);
133     }
134     pub fn handler<'a>(&'a self) -> &'a Handler {
135         &self.handler
136     }
137 }
138
139 /// A handler deals with errors; certain errors
140 /// (fatal, bug, unimpl) may cause immediate exit,
141 /// others log errors for later reporting.
142 pub struct Handler {
143     err_count: Cell<usize>,
144     emit: RefCell<Box<Emitter + Send>>,
145     pub can_emit_warnings: bool
146 }
147
148 impl Handler {
149     pub fn fatal(&self, msg: &str) -> ! {
150         self.emit.borrow_mut().emit(None, msg, None, Fatal);
151         panic!(FatalError);
152     }
153     pub fn err(&self, msg: &str) {
154         self.emit.borrow_mut().emit(None, msg, None, Error);
155         self.bump_err_count();
156     }
157     pub fn bump_err_count(&self) {
158         self.err_count.set(self.err_count.get() + 1);
159     }
160     pub fn err_count(&self) -> usize {
161         self.err_count.get()
162     }
163     pub fn has_errors(&self) -> bool {
164         self.err_count.get() > 0
165     }
166     pub fn abort_if_errors(&self) {
167         let s;
168         match self.err_count.get() {
169           0 => return,
170           1 => s = "aborting due to previous error".to_string(),
171           _   => {
172             s = format!("aborting due to {} previous errors",
173                         self.err_count.get());
174           }
175         }
176         self.fatal(&s[]);
177     }
178     pub fn warn(&self, msg: &str) {
179         self.emit.borrow_mut().emit(None, msg, None, Warning);
180     }
181     pub fn note(&self, msg: &str) {
182         self.emit.borrow_mut().emit(None, msg, None, Note);
183     }
184     pub fn help(&self, msg: &str) {
185         self.emit.borrow_mut().emit(None, msg, None, Help);
186     }
187     pub fn bug(&self, msg: &str) -> ! {
188         self.emit.borrow_mut().emit(None, msg, None, Bug);
189         panic!(ExplicitBug);
190     }
191     pub fn unimpl(&self, msg: &str) -> ! {
192         self.bug(&format!("unimplemented {}", msg)[]);
193     }
194     pub fn emit(&self,
195                 cmsp: Option<(&codemap::CodeMap, Span)>,
196                 msg: &str,
197                 lvl: Level) {
198         if lvl == Warning && !self.can_emit_warnings { return }
199         self.emit.borrow_mut().emit(cmsp, msg, None, lvl);
200     }
201     pub fn emit_with_code(&self,
202                           cmsp: Option<(&codemap::CodeMap, Span)>,
203                           msg: &str,
204                           code: &str,
205                           lvl: Level) {
206         if lvl == Warning && !self.can_emit_warnings { return }
207         self.emit.borrow_mut().emit(cmsp, msg, Some(code), lvl);
208     }
209     pub fn custom_emit(&self, cm: &codemap::CodeMap,
210                        sp: RenderSpan, msg: &str, lvl: Level) {
211         if lvl == Warning && !self.can_emit_warnings { return }
212         self.emit.borrow_mut().custom_emit(cm, sp, msg, lvl);
213     }
214 }
215
216 pub fn mk_span_handler(handler: Handler, cm: codemap::CodeMap) -> SpanHandler {
217     SpanHandler {
218         handler: handler,
219         cm: cm,
220     }
221 }
222
223 pub fn default_handler(color_config: ColorConfig,
224                        registry: Option<diagnostics::registry::Registry>,
225                        can_emit_warnings: bool) -> Handler {
226     mk_handler(can_emit_warnings, box EmitterWriter::stderr(color_config, registry))
227 }
228
229 pub fn mk_handler(can_emit_warnings: bool, e: Box<Emitter + Send>) -> Handler {
230     Handler {
231         err_count: Cell::new(0),
232         emit: RefCell::new(e),
233         can_emit_warnings: can_emit_warnings
234     }
235 }
236
237 #[derive(Copy, PartialEq, Clone, Debug)]
238 pub enum Level {
239     Bug,
240     Fatal,
241     Error,
242     Warning,
243     Note,
244     Help,
245 }
246
247 impl fmt::Display for Level {
248     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
249         use std::fmt::Display;
250
251         match *self {
252             Bug => "error: internal compiler error".fmt(f),
253             Fatal | Error => "error".fmt(f),
254             Warning => "warning".fmt(f),
255             Note => "note".fmt(f),
256             Help => "help".fmt(f),
257         }
258     }
259 }
260
261 impl Level {
262     fn color(self) -> term::color::Color {
263         match self {
264             Bug | Fatal | Error => term::color::BRIGHT_RED,
265             Warning => term::color::BRIGHT_YELLOW,
266             Note => term::color::BRIGHT_GREEN,
267             Help => term::color::BRIGHT_CYAN,
268         }
269     }
270 }
271
272 fn print_maybe_styled(w: &mut EmitterWriter,
273                       msg: &str,
274                       color: term::attr::Attr) -> old_io::IoResult<()> {
275     match w.dst {
276         Terminal(ref mut t) => {
277             try!(t.attr(color));
278             // If `msg` ends in a newline, we need to reset the color before
279             // the newline. We're making the assumption that we end up writing
280             // to a `LineBufferedWriter`, which means that emitting the reset
281             // after the newline ends up buffering the reset until we print
282             // another line or exit. Buffering the reset is a problem if we're
283             // sharing the terminal with any other programs (e.g. other rustc
284             // instances via `make -jN`).
285             //
286             // Note that if `msg` contains any internal newlines, this will
287             // result in the `LineBufferedWriter` flushing twice instead of
288             // once, which still leaves the opportunity for interleaved output
289             // to be miscolored. We assume this is rare enough that we don't
290             // have to worry about it.
291             if msg.ends_with("\n") {
292                 try!(t.write_str(&msg[..msg.len()-1]));
293                 try!(t.reset());
294                 try!(t.write_str("\n"));
295             } else {
296                 try!(t.write_str(msg));
297                 try!(t.reset());
298             }
299             Ok(())
300         }
301         Raw(ref mut w) => {
302             w.write_str(msg)
303         }
304     }
305 }
306
307 fn print_diagnostic(dst: &mut EmitterWriter, topic: &str, lvl: Level,
308                     msg: &str, code: Option<&str>) -> old_io::IoResult<()> {
309     if !topic.is_empty() {
310         try!(write!(&mut dst.dst, "{} ", topic));
311     }
312
313     try!(print_maybe_styled(dst,
314                             &format!("{}: ", lvl.to_string())[],
315                             term::attr::ForegroundColor(lvl.color())));
316     try!(print_maybe_styled(dst,
317                             &format!("{}", msg)[],
318                             term::attr::Bold));
319
320     match code {
321         Some(code) => {
322             let style = term::attr::ForegroundColor(term::color::BRIGHT_MAGENTA);
323             try!(print_maybe_styled(dst, &format!(" [{}]", code.clone())[], style));
324         }
325         None => ()
326     }
327     try!(dst.dst.write_char('\n'));
328     Ok(())
329 }
330
331 pub struct EmitterWriter {
332     dst: Destination,
333     registry: Option<diagnostics::registry::Registry>
334 }
335
336 enum Destination {
337     Terminal(Box<term::Terminal<WriterWrapper> + Send>),
338     Raw(Box<Writer + Send>),
339 }
340
341 impl EmitterWriter {
342     pub fn stderr(color_config: ColorConfig,
343                   registry: Option<diagnostics::registry::Registry>) -> EmitterWriter {
344         let stderr = old_io::stderr();
345
346         let use_color = match color_config {
347             Always => true,
348             Never  => false,
349             Auto   => stderr.get_ref().isatty()
350         };
351
352         if use_color {
353             let dst = match term::stderr() {
354                 Some(t) => Terminal(t),
355                 None    => Raw(box stderr),
356             };
357             EmitterWriter { dst: dst, registry: registry }
358         } else {
359             EmitterWriter { dst: Raw(box stderr), registry: registry }
360         }
361     }
362
363     pub fn new(dst: Box<Writer + Send>,
364                registry: Option<diagnostics::registry::Registry>) -> EmitterWriter {
365         EmitterWriter { dst: Raw(dst), registry: registry }
366     }
367 }
368
369 impl Writer for Destination {
370     fn write_all(&mut self, bytes: &[u8]) -> old_io::IoResult<()> {
371         match *self {
372             Terminal(ref mut t) => t.write_all(bytes),
373             Raw(ref mut w) => w.write_all(bytes),
374         }
375     }
376 }
377
378 impl Emitter for EmitterWriter {
379     fn emit(&mut self,
380             cmsp: Option<(&codemap::CodeMap, Span)>,
381             msg: &str, code: Option<&str>, lvl: Level) {
382         let error = match cmsp {
383             Some((cm, COMMAND_LINE_SP)) => emit(self, cm,
384                                                 FileLine(COMMAND_LINE_SP),
385                                                 msg, code, lvl, false),
386             Some((cm, sp)) => emit(self, cm, FullSpan(sp), msg, code, lvl, false),
387             None => print_diagnostic(self, "", lvl, msg, code),
388         };
389
390         match error {
391             Ok(()) => {}
392             Err(e) => panic!("failed to print diagnostics: {:?}", e),
393         }
394     }
395
396     fn custom_emit(&mut self, cm: &codemap::CodeMap,
397                    sp: RenderSpan, msg: &str, lvl: Level) {
398         match emit(self, cm, sp, msg, None, lvl, true) {
399             Ok(()) => {}
400             Err(e) => panic!("failed to print diagnostics: {:?}", e),
401         }
402     }
403 }
404
405 fn emit(dst: &mut EmitterWriter, cm: &codemap::CodeMap, rsp: RenderSpan,
406         msg: &str, code: Option<&str>, lvl: Level, custom: bool) -> old_io::IoResult<()> {
407     let sp = rsp.span();
408
409     // We cannot check equality directly with COMMAND_LINE_SP
410     // since PartialEq is manually implemented to ignore the ExpnId
411     let ss = if sp.expn_id == COMMAND_LINE_EXPN {
412         "<command line option>".to_string()
413     } else {
414         cm.span_to_string(sp)
415     };
416     if custom {
417         // we want to tell compiletest/runtest to look at the last line of the
418         // span (since `custom_highlight_lines` displays an arrow to the end of
419         // the span)
420         let span_end = Span { lo: sp.hi, hi: sp.hi, expn_id: sp.expn_id};
421         let ses = cm.span_to_string(span_end);
422         try!(print_diagnostic(dst, &ses[], lvl, msg, code));
423         if rsp.is_full_span() {
424             try!(custom_highlight_lines(dst, cm, sp, lvl, cm.span_to_lines(sp)));
425         }
426     } else {
427         try!(print_diagnostic(dst, &ss[], lvl, msg, code));
428         if rsp.is_full_span() {
429             try!(highlight_lines(dst, cm, sp, lvl, cm.span_to_lines(sp)));
430         }
431     }
432     if sp != COMMAND_LINE_SP {
433         try!(print_macro_backtrace(dst, cm, sp));
434     }
435     match code {
436         Some(code) =>
437             match dst.registry.as_ref().and_then(|registry| registry.find_description(code)) {
438                 Some(_) => {
439                     try!(print_diagnostic(dst, &ss[], Help,
440                                           &format!("pass `--explain {}` to see a detailed \
441                                                    explanation", code)[], None));
442                 }
443                 None => ()
444             },
445         None => (),
446     }
447     Ok(())
448 }
449
450 fn highlight_lines(err: &mut EmitterWriter,
451                    cm: &codemap::CodeMap,
452                    sp: Span,
453                    lvl: Level,
454                    lines: codemap::FileLines) -> old_io::IoResult<()> {
455     let fm = &*lines.file;
456
457     let mut elided = false;
458     let mut display_lines = &lines.lines[];
459     if display_lines.len() > MAX_LINES {
460         display_lines = &display_lines[0..MAX_LINES];
461         elided = true;
462     }
463     // Print the offending lines
464     for &line_number in display_lines {
465         if let Some(line) = fm.get_line(line_number) {
466             try!(write!(&mut err.dst, "{}:{} {}\n", fm.name,
467                         line_number + 1, line));
468         }
469     }
470     if elided {
471         let last_line = display_lines[display_lines.len() - 1];
472         let s = format!("{}:{} ", fm.name, last_line + 1);
473         try!(write!(&mut err.dst, "{0:1$}...\n", "", s.len()));
474     }
475
476     // FIXME (#3260)
477     // If there's one line at fault we can easily point to the problem
478     if lines.lines.len() == 1 {
479         let lo = cm.lookup_char_pos(sp.lo);
480         let mut digits = 0;
481         let mut num = (lines.lines[0] + 1) / 10;
482
483         // how many digits must be indent past?
484         while num > 0 { num /= 10; digits += 1; }
485
486         let mut s = String::new();
487         // Skip is the number of characters we need to skip because they are
488         // part of the 'filename:line ' part of the previous line.
489         let skip = fm.name.width(false) + digits + 3;
490         for _ in 0..skip {
491             s.push(' ');
492         }
493         if let Some(orig) = fm.get_line(lines.lines[0]) {
494             let mut col = skip;
495             let mut lastc = ' ';
496             let mut iter = orig.chars().enumerate();
497             for (pos, ch) in iter.by_ref() {
498                 lastc = ch;
499                 if pos >= lo.col.to_usize() { break; }
500                 // Whenever a tab occurs on the previous line, we insert one on
501                 // the error-point-squiggly-line as well (instead of a space).
502                 // That way the squiggly line will usually appear in the correct
503                 // position.
504                 match ch {
505                     '\t' => {
506                         col += 8 - col%8;
507                         s.push('\t');
508                     },
509                     c => for _ in 0..c.width(false).unwrap_or(0) {
510                         col += 1;
511                         s.push(' ');
512                     },
513                 }
514             }
515
516             try!(write!(&mut err.dst, "{}", s));
517             let mut s = String::from_str("^");
518             let count = match lastc {
519                 // Most terminals have a tab stop every eight columns by default
520                 '\t' => 8 - col%8,
521                 _ => lastc.width(false).unwrap_or(0),
522             };
523             col += count;
524             s.extend(::std::iter::repeat('~').take(count));
525
526             let hi = cm.lookup_char_pos(sp.hi);
527             if hi.col != lo.col {
528                 for (pos, ch) in iter {
529                     if pos >= hi.col.to_usize() { break; }
530                     let count = match ch {
531                         '\t' => 8 - col%8,
532                         _ => ch.width(false).unwrap_or(0),
533                     };
534                     col += count;
535                     s.extend(::std::iter::repeat('~').take(count));
536                 }
537             }
538
539             if s.len() > 1 {
540                 // One extra squiggly is replaced by a "^"
541                 s.pop();
542             }
543
544             try!(print_maybe_styled(err,
545                                     &format!("{}\n", s)[],
546                                     term::attr::ForegroundColor(lvl.color())));
547         }
548     }
549     Ok(())
550 }
551
552 /// Here are the differences between this and the normal `highlight_lines`:
553 /// `custom_highlight_lines` will always put arrow on the last byte of the
554 /// span (instead of the first byte). Also, when the span is too long (more
555 /// than 6 lines), `custom_highlight_lines` will print the first line, then
556 /// dot dot dot, then last line, whereas `highlight_lines` prints the first
557 /// six lines.
558 fn custom_highlight_lines(w: &mut EmitterWriter,
559                           cm: &codemap::CodeMap,
560                           sp: Span,
561                           lvl: Level,
562                           lines: codemap::FileLines)
563                           -> old_io::IoResult<()> {
564     let fm = &*lines.file;
565
566     let lines = &lines.lines[];
567     if lines.len() > MAX_LINES {
568         if let Some(line) = fm.get_line(lines[0]) {
569             try!(write!(&mut w.dst, "{}:{} {}\n", fm.name,
570                         lines[0] + 1, line));
571         }
572         try!(write!(&mut w.dst, "...\n"));
573         let last_line_number = lines[lines.len() - 1];
574         if let Some(last_line) = fm.get_line(last_line_number) {
575             try!(write!(&mut w.dst, "{}:{} {}\n", fm.name,
576                         last_line_number + 1, last_line));
577         }
578     } else {
579         for &line_number in lines {
580             if let Some(line) = fm.get_line(line_number) {
581                 try!(write!(&mut w.dst, "{}:{} {}\n", fm.name,
582                             line_number + 1, line));
583             }
584         }
585     }
586     let last_line_start = format!("{}:{} ", fm.name, lines[lines.len()-1]+1);
587     let hi = cm.lookup_char_pos(sp.hi);
588     let skip = last_line_start.width(false);
589     let mut s = String::new();
590     for _ in 0..skip {
591         s.push(' ');
592     }
593     if let Some(orig) = fm.get_line(lines[0]) {
594         let iter = orig.chars().enumerate();
595         for (pos, ch) in iter {
596             // Span seems to use half-opened interval, so subtract 1
597             if pos >= hi.col.to_usize() - 1 { break; }
598             // Whenever a tab occurs on the previous line, we insert one on
599             // the error-point-squiggly-line as well (instead of a space).
600             // That way the squiggly line will usually appear in the correct
601             // position.
602             match ch {
603                 '\t' => s.push('\t'),
604                 c => for _ in 0..c.width(false).unwrap_or(0) {
605                     s.push(' ');
606                 },
607             }
608         }
609     }
610     s.push('^');
611     s.push('\n');
612     print_maybe_styled(w,
613                        &s[],
614                        term::attr::ForegroundColor(lvl.color()))
615 }
616
617 fn print_macro_backtrace(w: &mut EmitterWriter,
618                          cm: &codemap::CodeMap,
619                          sp: Span)
620                          -> old_io::IoResult<()> {
621     let cs = try!(cm.with_expn_info(sp.expn_id, |expn_info| match expn_info {
622         Some(ei) => {
623             let ss = ei.callee.span.map_or(String::new(), |span| cm.span_to_string(span));
624             let (pre, post) = match ei.callee.format {
625                 codemap::MacroAttribute => ("#[", "]"),
626                 codemap::MacroBang => ("", "!")
627             };
628             try!(print_diagnostic(w, &ss[], Note,
629                                   &format!("in expansion of {}{}{}", pre,
630                                           ei.callee.name,
631                                           post)[], None));
632             let ss = cm.span_to_string(ei.call_site);
633             try!(print_diagnostic(w, &ss[], Note, "expansion site", None));
634             Ok(Some(ei.call_site))
635         }
636         None => Ok(None)
637     }));
638     cs.map_or(Ok(()), |call_site| print_macro_backtrace(w, cm, call_site))
639 }
640
641 pub fn expect<T, M>(diag: &SpanHandler, opt: Option<T>, msg: M) -> T where
642     M: FnOnce() -> String,
643 {
644     match opt {
645         Some(t) => t,
646         None => diag.handler().bug(&msg()[]),
647     }
648 }