// 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 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A JSON emitter for errors. //! //! This works by converting errors to a simplified structural format (see the //! structs at the start of the file) and then serialising them. These should //! contain as much information about the error as possible. //! //! The format of the JSON output should be considered *unstable*. For now the //! structs at the end of this file (Diagnostic*) specify the error format. // FIXME spec the JSON output properly. use codemap::CodeMap; use syntax_pos::{self, MacroBacktrace, Span, SpanLabel, MultiSpan}; use errors::registry::Registry; use errors::{DiagnosticBuilder, SubDiagnostic, RenderSpan, CodeSuggestion, CodeMapper}; use errors::emitter::Emitter; use std::rc::Rc; use std::io::{self, Write}; use std::vec; use rustc_serialize::json::as_json; pub struct JsonEmitter { dst: Box, registry: Option, cm: Rc, } impl JsonEmitter { pub fn basic() -> JsonEmitter { JsonEmitter::stderr(None, Rc::new(CodeMap::new())) } pub fn stderr(registry: Option, code_map: Rc) -> JsonEmitter { JsonEmitter { dst: Box::new(io::stderr()), registry: registry, cm: code_map, } } } impl Emitter for JsonEmitter { fn emit_struct(&mut self, db: &DiagnosticBuilder) { let data = Diagnostic::from_diagnostic_builder(db, self); if let Err(e) = writeln!(&mut self.dst, "{}", as_json(&data)) { panic!("failed to print diagnostics: {:?}", e); } } } // The following data types are provided just for serialisation. #[derive(RustcEncodable)] struct Diagnostic<'a> { /// The primary error message. message: &'a str, code: Option, /// "error: internal compiler error", "error", "warning", "note", "help". level: &'static str, spans: Vec, /// Associated diagnostic messages. children: Vec>, /// The message as rustc would render it. Currently this is only /// `Some` for "suggestions", but eventually it will include all /// snippets. rendered: Option, } #[derive(RustcEncodable)] struct DiagnosticSpan { file_name: String, byte_start: u32, byte_end: u32, /// 1-based. line_start: usize, line_end: usize, /// 1-based, character offset. column_start: usize, column_end: usize, /// Is this a "primary" span -- meaning the point, or one of the points, /// where the error occurred? is_primary: bool, /// Source text from the start of line_start to the end of line_end. text: Vec, /// Label that should be placed at this location (if any) label: Option, /// If we are suggesting a replacement, this will contain text /// that should be sliced in atop this span. You may prefer to /// load the fully rendered version from the parent `Diagnostic`, /// however. suggested_replacement: Option, /// Macro invocations that created the code at this span, if any. expansion: Option>, } #[derive(RustcEncodable)] struct DiagnosticSpanLine { text: String, /// 1-based, character offset in self.text. highlight_start: usize, highlight_end: usize, } #[derive(RustcEncodable)] struct DiagnosticSpanMacroExpansion { /// span where macro was applied to generate this code; note that /// this may itself derive from a macro (if /// `span.expansion.is_some()`) span: DiagnosticSpan, /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]") macro_decl_name: String, /// span where macro was defined (if known) def_site_span: Option, } #[derive(RustcEncodable)] struct DiagnosticCode { /// The code itself. code: String, /// An explanation for the code. explanation: Option<&'static str>, } impl<'a> Diagnostic<'a> { fn from_diagnostic_builder<'c>(db: &'c DiagnosticBuilder, je: &JsonEmitter) -> Diagnostic<'c> { Diagnostic { message: &db.message, code: DiagnosticCode::map_opt_string(db.code.clone(), je), level: db.level.to_str(), spans: DiagnosticSpan::from_multispan(&db.span, je), children: db.children.iter().map(|c| { Diagnostic::from_sub_diagnostic(c, je) }).collect(), rendered: None, } } fn from_sub_diagnostic<'c>(db: &'c SubDiagnostic, je: &JsonEmitter) -> Diagnostic<'c> { Diagnostic { message: &db.message, code: None, level: db.level.to_str(), spans: db.render_span.as_ref() .map(|sp| DiagnosticSpan::from_render_span(sp, je)) .unwrap_or_else(|| DiagnosticSpan::from_multispan(&db.span, je)), children: vec![], rendered: db.render_span.as_ref() .and_then(|rsp| je.render(rsp)), } } } impl DiagnosticSpan { fn from_span_label(span: SpanLabel, suggestion: Option<&String>, je: &JsonEmitter) -> DiagnosticSpan { Self::from_span_etc(span.span, span.is_primary, span.label, suggestion, je) } fn from_span_etc(span: Span, is_primary: bool, label: Option, suggestion: Option<&String>, je: &JsonEmitter) -> DiagnosticSpan { // obtain the full backtrace from the `macro_backtrace` // helper; in some ways, it'd be better to expand the // backtrace ourselves, but the `macro_backtrace` helper makes // some decision, such as dropping some frames, and I don't // want to duplicate that logic here. let backtrace = je.cm.macro_backtrace(span).into_iter(); DiagnosticSpan::from_span_full(span, is_primary, label, suggestion, backtrace, je) } fn from_span_full(span: Span, is_primary: bool, label: Option, suggestion: Option<&String>, mut backtrace: vec::IntoIter, je: &JsonEmitter) -> DiagnosticSpan { let start = je.cm.lookup_char_pos(span.lo); let end = je.cm.lookup_char_pos(span.hi); let backtrace_step = backtrace.next().map(|bt| { let call_site = Self::from_span_full(bt.call_site, false, None, None, backtrace, je); let def_site_span = bt.def_site_span.map(|sp| { Self::from_span_full(sp, false, None, None, vec![].into_iter(), je) }); Box::new(DiagnosticSpanMacroExpansion { span: call_site, macro_decl_name: bt.macro_decl_name, def_site_span: def_site_span, }) }); DiagnosticSpan { file_name: start.file.name.clone(), byte_start: span.lo.0, byte_end: span.hi.0, line_start: start.line, line_end: end.line, column_start: start.col.0 + 1, column_end: end.col.0 + 1, is_primary: is_primary, text: DiagnosticSpanLine::from_span(span, je), suggested_replacement: suggestion.cloned(), expansion: backtrace_step, label: label, } } fn from_multispan(msp: &MultiSpan, je: &JsonEmitter) -> Vec { msp.span_labels() .into_iter() .map(|span_str| Self::from_span_label(span_str, None, je)) .collect() } fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter) -> Vec { assert_eq!(suggestion.msp.span_labels().len(), suggestion.substitutes.len()); suggestion.msp.span_labels() .into_iter() .zip(&suggestion.substitutes) .map(|(span_label, suggestion)| { DiagnosticSpan::from_span_label(span_label, Some(suggestion), je) }) .collect() } fn from_render_span(rsp: &RenderSpan, je: &JsonEmitter) -> Vec { match *rsp { RenderSpan::FullSpan(ref msp) => DiagnosticSpan::from_multispan(msp, je), RenderSpan::Suggestion(ref suggestion) => DiagnosticSpan::from_suggestion(suggestion, je), } } } impl DiagnosticSpanLine { fn line_from_filemap(fm: &syntax_pos::FileMap, index: usize, h_start: usize, h_end: usize) -> DiagnosticSpanLine { DiagnosticSpanLine { text: fm.get_line(index).unwrap().to_owned(), highlight_start: h_start, highlight_end: h_end, } } /// Create a list of DiagnosticSpanLines from span - each line with any part /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the /// `span` within the line. fn from_span(span: Span, je: &JsonEmitter) -> Vec { je.cm.span_to_lines(span) .map(|lines| { let fm = &*lines.file; lines.lines .iter() .map(|line| { DiagnosticSpanLine::line_from_filemap(fm, line.line_index, line.start_col.0 + 1, line.end_col.0 + 1) }) .collect() }) .unwrap_or(vec![]) } } impl DiagnosticCode { fn map_opt_string(s: Option, je: &JsonEmitter) -> Option { s.map(|s| { let explanation = je.registry .as_ref() .and_then(|registry| registry.find_description(&s)); DiagnosticCode { code: s, explanation: explanation, } }) } } impl JsonEmitter { fn render(&self, render_span: &RenderSpan) -> Option { use std::borrow::Borrow; match *render_span { RenderSpan::FullSpan(_) => { None } RenderSpan::Suggestion(ref suggestion) => { Some(suggestion.splice_lines(self.cm.borrow())) } } } }