rust/src/librustc_errors/emitter.rs
2016-07-14 07:57:46 -04:00

602 lines
21 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use self::Destination::*;
use syntax_pos::{COMMAND_LINE_SP, DUMMY_SP, Span, MultiSpan, LineInfo};
use registry;
use check_old_skool;
use {Level, RenderSpan, CodeSuggestion, DiagnosticBuilder, CodeMapper};
use RenderSpan::*;
use Level::*;
use snippet::{RenderedLineKind, SnippetData, Style, FormatMode};
use std::{cmp, fmt};
use std::io::prelude::*;
use std::io;
use std::rc::Rc;
use term;
/// Emitter trait for emitting errors.
pub trait Emitter {
/// Emit a structured diagnostic.
fn emit(&mut self, db: &DiagnosticBuilder);
}
impl Emitter for EmitterWriter {
fn emit(&mut self, db: &DiagnosticBuilder) {
self.emit_message(&FullSpan(db.span.clone()),
&db.message,
db.code.as_ref().map(|s| &**s),
db.level,
true,
true);
if check_old_skool() {
let db_span = FullSpan(db.span.clone());
for child in &db.children {
let render_span = child.render_span
.clone()
.unwrap_or_else(
|| FullSpan(child.span.clone()));
let (render_span, show_snippet) = match render_span.span().primary_span() {
None => (db_span.clone(), false),
_ => (render_span, true)
};
self.emit_message(&render_span,
&child.message,
None,
child.level,
false,
show_snippet);
}
} else {
for child in &db.children {
let render_span = child.render_span
.clone()
.unwrap_or_else(
|| FullSpan(child.span.clone()));
self.emit_message(&render_span,
&child.message,
None,
child.level,
false,
true);
}
}
}
}
/// maximum number of lines we will print for each error; arbitrary.
pub const MAX_HIGHLIGHT_LINES: usize = 6;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ColorConfig {
Auto,
Always,
Never,
}
impl ColorConfig {
fn use_color(&self) -> bool {
match *self {
ColorConfig::Always => true,
ColorConfig::Never => false,
ColorConfig::Auto => stderr_isatty(),
}
}
}
pub struct EmitterWriter {
dst: Destination,
registry: Option<registry::Registry>,
cm: Option<Rc<CodeMapper>>,
/// Is this the first error emitted thus far? If not, we emit a
/// `\n` before the top-level errors.
first: bool,
// For now, allow an old-school mode while we transition
format_mode: FormatMode
}
/// Do not use this for messages that end in `\n` use `println_maybe_styled` instead. See
/// `EmitterWriter::print_maybe_styled` for details.
macro_rules! print_maybe_styled {
($dst: expr, $style: expr, $($arg: tt)*) => {
$dst.print_maybe_styled(format_args!($($arg)*), $style, false)
}
}
macro_rules! println_maybe_styled {
($dst: expr, $style: expr, $($arg: tt)*) => {
$dst.print_maybe_styled(format_args!($($arg)*), $style, true)
}
}
impl EmitterWriter {
pub fn stderr(color_config: ColorConfig,
registry: Option<registry::Registry>,
code_map: Option<Rc<CodeMapper>>,
format_mode: FormatMode)
-> EmitterWriter {
if color_config.use_color() {
let dst = Destination::from_stderr();
EmitterWriter { dst: dst,
registry: registry,
cm: code_map,
first: true,
format_mode: format_mode.clone() }
} else {
EmitterWriter { dst: Raw(Box::new(io::stderr())),
registry: registry,
cm: code_map,
first: true,
format_mode: format_mode.clone() }
}
}
pub fn new(dst: Box<Write + Send>,
registry: Option<registry::Registry>,
code_map: Option<Rc<CodeMapper>>,
format_mode: FormatMode)
-> EmitterWriter {
EmitterWriter { dst: Raw(dst),
registry: registry,
cm: code_map,
first: true,
format_mode: format_mode.clone() }
}
fn emit_message(&mut self,
rsp: &RenderSpan,
msg: &str,
code: Option<&str>,
lvl: Level,
is_header: bool,
show_snippet: bool) {
match self.emit_message_(rsp, msg, code, lvl, is_header, show_snippet) {
Ok(()) => { }
Err(e) => panic!("failed to emit error: {}", e)
}
}
fn emit_message_(&mut self,
rsp: &RenderSpan,
msg: &str,
code: Option<&str>,
lvl: Level,
is_header: bool,
show_snippet: bool)
-> io::Result<()> {
let old_school = match self.format_mode {
FormatMode::NewErrorFormat => false,
FormatMode::OriginalErrorFormat => true,
FormatMode::EnvironmentSelected => check_old_skool()
};
if is_header {
if self.first {
self.first = false;
} else {
if !old_school {
write!(self.dst, "\n")?;
}
}
}
match code {
Some(code) if self.registry.as_ref()
.and_then(|registry| registry.find_description(code))
.is_some() => {
let code_with_explain = String::from("--explain ") + code;
if old_school {
let loc = match rsp.span().primary_span() {
Some(COMMAND_LINE_SP) | Some(DUMMY_SP) => "".to_string(),
Some(ps) => if let Some(ref cm) = self.cm {
cm.span_to_string(ps)
} else {
"".to_string()
},
None => "".to_string()
};
print_diagnostic(&mut self.dst, &loc, lvl, msg, Some(code))?
}
else {
print_diagnostic(&mut self.dst, "", lvl, msg, Some(&code_with_explain))?
}
}
_ => {
if old_school {
let loc = match rsp.span().primary_span() {
Some(COMMAND_LINE_SP) | Some(DUMMY_SP) => "".to_string(),
Some(ps) => if let Some(ref cm) = self.cm {
cm.span_to_string(ps)
} else {
"".to_string()
},
None => "".to_string()
};
print_diagnostic(&mut self.dst, &loc, lvl, msg, code)?
}
else {
print_diagnostic(&mut self.dst, "", lvl, msg, code)?
}
}
}
if !show_snippet {
return Ok(());
}
// Watch out for various nasty special spans; don't try to
// print any filename or anything for those.
match rsp.span().primary_span() {
Some(COMMAND_LINE_SP) | Some(DUMMY_SP) => {
return Ok(());
}
_ => { }
}
// Otherwise, print out the snippet etc as needed.
match *rsp {
FullSpan(ref msp) => {
self.highlight_lines(msp, lvl)?;
if let Some(primary_span) = msp.primary_span() {
self.print_macro_backtrace(primary_span)?;
}
}
Suggestion(ref suggestion) => {
self.highlight_suggestion(suggestion)?;
if let Some(primary_span) = rsp.span().primary_span() {
self.print_macro_backtrace(primary_span)?;
}
}
}
if old_school {
match code {
Some(code) if self.registry.as_ref()
.and_then(|registry| registry.find_description(code))
.is_some() => {
let loc = match rsp.span().primary_span() {
Some(COMMAND_LINE_SP) | Some(DUMMY_SP) => "".to_string(),
Some(ps) => if let Some(ref cm) = self.cm {
cm.span_to_string(ps)
} else {
"".to_string()
},
None => "".to_string()
};
let msg = "run `rustc --explain ".to_string() + &code.to_string() +
"` to see a detailed explanation";
print_diagnostic(&mut self.dst, &loc, Level::Help, &msg,
None)?
}
_ => ()
}
}
Ok(())
}
fn highlight_suggestion(&mut self, suggestion: &CodeSuggestion) -> io::Result<()>
{
use std::borrow::Borrow;
let primary_span = suggestion.msp.primary_span().unwrap();
if let Some(ref cm) = self.cm {
let lines = cm.span_to_lines(primary_span).unwrap();
assert!(!lines.lines.is_empty());
let complete = suggestion.splice_lines(cm.borrow());
let line_count = cmp::min(lines.lines.len(), MAX_HIGHLIGHT_LINES);
let display_lines = &lines.lines[..line_count];
let fm = &*lines.file;
// Calculate the widest number to format evenly
let max_digits = line_num_max_digits(display_lines.last().unwrap());
// print the suggestion without any line numbers, but leave
// space for them. This helps with lining up with previous
// snippets from the actual error being reported.
let mut lines = complete.lines();
for line in lines.by_ref().take(MAX_HIGHLIGHT_LINES) {
write!(&mut self.dst, "{0}:{1:2$} {3}\n",
fm.name, "", max_digits, line)?;
}
// if we elided some lines, add an ellipsis
if let Some(_) = lines.next() {
write!(&mut self.dst, "{0:1$} {0:2$} ...\n",
"", fm.name.len(), max_digits)?;
}
}
Ok(())
}
pub fn highlight_lines(&mut self,
msp: &MultiSpan,
lvl: Level)
-> io::Result<()>
{
// Check to see if we have any lines to highlight, exit early if not
match self.cm {
None => return Ok(()),
_ => ()
}
let old_school = match self.format_mode {
FormatMode::NewErrorFormat => false,
FormatMode::OriginalErrorFormat => true,
FormatMode::EnvironmentSelected => check_old_skool()
};
let mut snippet_data = SnippetData::new(self.cm.as_ref().unwrap().clone(),
msp.primary_span(),
self.format_mode.clone());
if old_school {
let mut output_vec = vec![];
for span_label in msp.span_labels() {
let mut snippet_data = SnippetData::new(self.cm.as_ref().unwrap().clone(),
Some(span_label.span),
self.format_mode.clone());
snippet_data.push(span_label.span,
span_label.is_primary,
span_label.label);
if span_label.is_primary {
output_vec.insert(0, snippet_data);
}
else {
output_vec.push(snippet_data);
}
}
for snippet_data in output_vec.iter() {
let rendered_lines = snippet_data.render_lines();
for rendered_line in &rendered_lines {
for styled_string in &rendered_line.text {
self.dst.apply_style(lvl, &rendered_line.kind, styled_string.style)?;
write!(&mut self.dst, "{}", styled_string.text)?;
self.dst.reset_attrs()?;
}
write!(&mut self.dst, "\n")?;
}
}
}
else {
for span_label in msp.span_labels() {
snippet_data.push(span_label.span,
span_label.is_primary,
span_label.label);
}
let rendered_lines = snippet_data.render_lines();
for rendered_line in &rendered_lines {
for styled_string in &rendered_line.text {
self.dst.apply_style(lvl, &rendered_line.kind, styled_string.style)?;
write!(&mut self.dst, "{}", styled_string.text)?;
self.dst.reset_attrs()?;
}
write!(&mut self.dst, "\n")?;
}
}
Ok(())
}
fn print_macro_backtrace(&mut self,
sp: Span)
-> io::Result<()> {
if let Some(ref cm) = self.cm {
for trace in cm.macro_backtrace(sp) {
let mut diag_string =
format!("in this expansion of {}", trace.macro_decl_name);
if let Some(def_site_span) = trace.def_site_span {
diag_string.push_str(
&format!(" (defined in {})",
cm.span_to_filename(def_site_span)));
}
let snippet = cm.span_to_string(trace.call_site);
print_diagnostic(&mut self.dst, &snippet, Note, &diag_string, None)?;
}
}
Ok(())
}
}
fn line_num_max_digits(line: &LineInfo) -> usize {
let mut max_line_num = line.line_index + 1;
let mut digits = 0;
while max_line_num > 0 {
max_line_num /= 10;
digits += 1;
}
digits
}
fn print_diagnostic(dst: &mut Destination,
topic: &str,
lvl: Level,
msg: &str,
code: Option<&str>)
-> io::Result<()> {
if !topic.is_empty() {
let old_school = check_old_skool();
if !old_school {
write!(dst, "{}: ", topic)?;
}
else {
write!(dst, "{} ", topic)?;
}
dst.reset_attrs()?;
}
dst.start_attr(term::Attr::Bold)?;
dst.start_attr(term::Attr::ForegroundColor(lvl.color()))?;
write!(dst, "{}", lvl.to_string())?;
dst.reset_attrs()?;
write!(dst, ": ")?;
dst.start_attr(term::Attr::Bold)?;
write!(dst, "{}", msg)?;
if let Some(code) = code {
let style = term::Attr::ForegroundColor(term::color::BRIGHT_MAGENTA);
print_maybe_styled!(dst, style, " [{}]", code.clone())?;
}
dst.reset_attrs()?;
write!(dst, "\n")?;
Ok(())
}
#[cfg(unix)]
fn stderr_isatty() -> bool {
use libc;
unsafe { libc::isatty(libc::STDERR_FILENO) != 0 }
}
#[cfg(windows)]
fn stderr_isatty() -> bool {
type DWORD = u32;
type BOOL = i32;
type HANDLE = *mut u8;
const STD_ERROR_HANDLE: DWORD = -12i32 as DWORD;
extern "system" {
fn GetStdHandle(which: DWORD) -> HANDLE;
fn GetConsoleMode(hConsoleHandle: HANDLE,
lpMode: *mut DWORD) -> BOOL;
}
unsafe {
let handle = GetStdHandle(STD_ERROR_HANDLE);
let mut out = 0;
GetConsoleMode(handle, &mut out) != 0
}
}
enum Destination {
Terminal(Box<term::StderrTerminal>),
Raw(Box<Write + Send>),
}
impl Destination {
fn from_stderr() -> Destination {
match term::stderr() {
Some(t) => Terminal(t),
None => Raw(Box::new(io::stderr())),
}
}
fn apply_style(&mut self,
lvl: Level,
_kind: &RenderedLineKind,
style: Style)
-> io::Result<()> {
match style {
Style::FileNameStyle |
Style::LineAndColumn => {
}
Style::LineNumber => {
self.start_attr(term::Attr::Bold)?;
self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?;
}
Style::Quotation => {
}
Style::OldSkoolNote => {
self.start_attr(term::Attr::Bold)?;
self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_GREEN))?;
}
Style::OldSkoolNoteText => {
self.start_attr(term::Attr::Bold)?;
}
Style::UnderlinePrimary | Style::LabelPrimary => {
self.start_attr(term::Attr::Bold)?;
self.start_attr(term::Attr::ForegroundColor(lvl.color()))?;
}
Style::UnderlineSecondary | Style::LabelSecondary => {
self.start_attr(term::Attr::Bold)?;
self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?;
}
Style::NoStyle => {
}
}
Ok(())
}
fn start_attr(&mut self, attr: term::Attr) -> io::Result<()> {
match *self {
Terminal(ref mut t) => { t.attr(attr)?; }
Raw(_) => { }
}
Ok(())
}
fn reset_attrs(&mut self) -> io::Result<()> {
match *self {
Terminal(ref mut t) => { t.reset()?; }
Raw(_) => { }
}
Ok(())
}
fn print_maybe_styled(&mut self,
args: fmt::Arguments,
color: term::Attr,
print_newline_at_end: bool)
-> io::Result<()> {
match *self {
Terminal(ref mut t) => {
t.attr(color)?;
// If `msg` ends in a newline, we need to reset the color before
// the newline. We're making the assumption that we end up writing
// to a `LineBufferedWriter`, which means that emitting the reset
// after the newline ends up buffering the reset until we print
// another line or exit. Buffering the reset is a problem if we're
// sharing the terminal with any other programs (e.g. other rustc
// instances via `make -jN`).
//
// Note that if `msg` contains any internal newlines, this will
// result in the `LineBufferedWriter` flushing twice instead of
// once, which still leaves the opportunity for interleaved output
// to be miscolored. We assume this is rare enough that we don't
// have to worry about it.
t.write_fmt(args)?;
t.reset()?;
if print_newline_at_end {
t.write_all(b"\n")
} else {
Ok(())
}
}
Raw(ref mut w) => {
w.write_fmt(args)?;
if print_newline_at_end {
w.write_all(b"\n")
} else {
Ok(())
}
}
}
}
}
impl Write for Destination {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
match *self {
Terminal(ref mut t) => t.write(bytes),
Raw(ref mut w) => w.write(bytes),
}
}
fn flush(&mut self) -> io::Result<()> {
match *self {
Terminal(ref mut t) => t.flush(),
Raw(ref mut w) => w.flush(),
}
}
}