1 // Copyright 2012-2015 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.
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.
11 //! A JSON emitter for errors.
13 //! This works by converting errors to a simplified structural format (see the
14 //! structs at the start of the file) and then serialising them. These should
15 //! contain as much information about the error as possible.
17 //! The format of the JSON output should be considered *unstable*. For now the
18 //! structs at the end of this file (Diagnostic*) specify the error format.
20 // FIXME spec the JSON output properly.
22 use codemap
::{CodeMap, FilePathMapping}
;
23 use syntax_pos
::{self, MacroBacktrace, Span, SpanLabel, MultiSpan}
;
24 use errors
::registry
::Registry
;
25 use errors
::{DiagnosticBuilder, SubDiagnostic, RenderSpan, CodeSuggestion, CodeMapper}
;
26 use errors
::emitter
::Emitter
;
29 use std
::io
::{self, Write}
;
32 use rustc_serialize
::json
::as_json
;
34 pub struct JsonEmitter
{
35 dst
: Box
<Write
+ Send
>,
36 registry
: Option
<Registry
>,
37 cm
: Rc
<CodeMapper
+ '
static>,
41 pub fn stderr(registry
: Option
<Registry
>,
42 code_map
: Rc
<CodeMap
>) -> JsonEmitter
{
44 dst
: Box
::new(io
::stderr()),
50 pub fn basic() -> JsonEmitter
{
51 let file_path_mapping
= FilePathMapping
::empty();
52 JsonEmitter
::stderr(None
, Rc
::new(CodeMap
::new(file_path_mapping
)))
55 pub fn new(dst
: Box
<Write
+ Send
>,
56 registry
: Option
<Registry
>,
57 code_map
: Rc
<CodeMap
>) -> JsonEmitter
{
66 impl Emitter
for JsonEmitter
{
67 fn emit(&mut self, db
: &DiagnosticBuilder
) {
68 let data
= Diagnostic
::from_diagnostic_builder(db
, self);
69 if let Err(e
) = writeln
!(&mut self.dst
, "{}", as_json(&data
)) {
70 panic
!("failed to print diagnostics: {:?}", e
);
75 // The following data types are provided just for serialisation.
77 #[derive(RustcEncodable)]
79 /// The primary error message.
81 code
: Option
<DiagnosticCode
>,
82 /// "error: internal compiler error", "error", "warning", "note", "help".
84 spans
: Vec
<DiagnosticSpan
>,
85 /// Associated diagnostic messages.
86 children
: Vec
<Diagnostic
>,
87 /// The message as rustc would render it. Currently this is only
88 /// `Some` for "suggestions", but eventually it will include all
90 rendered
: Option
<String
>,
93 #[derive(RustcEncodable)]
94 struct DiagnosticSpan
{
101 /// 1-based, character offset.
104 /// Is this a "primary" span -- meaning the point, or one of the points,
105 /// where the error occurred?
107 /// Source text from the start of line_start to the end of line_end.
108 text
: Vec
<DiagnosticSpanLine
>,
109 /// Label that should be placed at this location (if any)
110 label
: Option
<String
>,
111 /// If we are suggesting a replacement, this will contain text
112 /// that should be sliced in atop this span. You may prefer to
113 /// load the fully rendered version from the parent `Diagnostic`,
115 suggested_replacement
: Option
<String
>,
116 /// Macro invocations that created the code at this span, if any.
117 expansion
: Option
<Box
<DiagnosticSpanMacroExpansion
>>,
120 #[derive(RustcEncodable)]
121 struct DiagnosticSpanLine
{
124 /// 1-based, character offset in self.text.
125 highlight_start
: usize,
127 highlight_end
: usize,
130 #[derive(RustcEncodable)]
131 struct DiagnosticSpanMacroExpansion
{
132 /// span where macro was applied to generate this code; note that
133 /// this may itself derive from a macro (if
134 /// `span.expansion.is_some()`)
135 span
: DiagnosticSpan
,
137 /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
138 macro_decl_name
: String
,
140 /// span where macro was defined (if known)
141 def_site_span
: Option
<DiagnosticSpan
>,
144 #[derive(RustcEncodable)]
145 struct DiagnosticCode
{
148 /// An explanation for the code.
149 explanation
: Option
<&'
static str>,
153 fn from_diagnostic_builder(db
: &DiagnosticBuilder
,
156 let sugg
= db
.suggestions
.iter().flat_map(|sugg
| {
157 je
.render(sugg
).into_iter().map(move |rendered
| {
159 message
: sugg
.msg
.clone(),
162 spans
: DiagnosticSpan
::from_suggestion(sugg
, je
),
164 rendered
: Some(rendered
),
169 message
: db
.message(),
170 code
: DiagnosticCode
::map_opt_string(db
.code
.clone(), je
),
171 level
: db
.level
.to_str(),
172 spans
: DiagnosticSpan
::from_multispan(&db
.span
, je
),
173 children
: db
.children
.iter().map(|c
| {
174 Diagnostic
::from_sub_diagnostic(c
, je
)
175 }).chain(sugg
).collect(),
180 fn from_sub_diagnostic(db
: &SubDiagnostic
, je
: &JsonEmitter
) -> Diagnostic
{
182 message
: db
.message(),
184 level
: db
.level
.to_str(),
185 spans
: db
.render_span
.as_ref()
186 .map(|sp
| DiagnosticSpan
::from_render_span(sp
, je
))
187 .unwrap_or_else(|| DiagnosticSpan
::from_multispan(&db
.span
, je
)),
194 impl DiagnosticSpan
{
195 fn from_span_label(span
: SpanLabel
,
196 suggestion
: Option
<&String
>,
199 Self::from_span_etc(span
.span
,
206 fn from_span_etc(span
: Span
,
208 label
: Option
<String
>,
209 suggestion
: Option
<&String
>,
212 // obtain the full backtrace from the `macro_backtrace`
213 // helper; in some ways, it'd be better to expand the
214 // backtrace ourselves, but the `macro_backtrace` helper makes
215 // some decision, such as dropping some frames, and I don't
216 // want to duplicate that logic here.
217 let backtrace
= span
.macro_backtrace().into_iter();
218 DiagnosticSpan
::from_span_full(span
,
226 fn from_span_full(span
: Span
,
228 label
: Option
<String
>,
229 suggestion
: Option
<&String
>,
230 mut backtrace
: vec
::IntoIter
<MacroBacktrace
>,
233 let start
= je
.cm
.lookup_char_pos(span
.lo
);
234 let end
= je
.cm
.lookup_char_pos(span
.hi
);
235 let backtrace_step
= backtrace
.next().map(|bt
| {
237 Self::from_span_full(bt
.call_site
,
243 let def_site_span
= bt
.def_site_span
.map(|sp
| {
244 Self::from_span_full(sp
,
251 Box
::new(DiagnosticSpanMacroExpansion
{
253 macro_decl_name
: bt
.macro_decl_name
,
254 def_site_span
: def_site_span
,
258 file_name
: start
.file
.name
.clone(),
259 byte_start
: span
.lo
.0,
261 line_start
: start
.line
,
263 column_start
: start
.col
.0 + 1,
264 column_end
: end
.col
.0 + 1,
265 is_primary
: is_primary
,
266 text
: DiagnosticSpanLine
::from_span(span
, je
),
267 suggested_replacement
: suggestion
.cloned(),
268 expansion
: backtrace_step
,
273 fn from_multispan(msp
: &MultiSpan
, je
: &JsonEmitter
) -> Vec
<DiagnosticSpan
> {
276 .map(|span_str
| Self::from_span_label(span_str
, None
, je
))
280 fn from_suggestion(suggestion
: &CodeSuggestion
, je
: &JsonEmitter
)
281 -> Vec
<DiagnosticSpan
> {
282 suggestion
.substitution_parts
284 .flat_map(|substitution
| {
285 substitution
.substitutions
.iter().map(move |suggestion
| {
286 let span_label
= SpanLabel
{
287 span
: substitution
.span
,
291 DiagnosticSpan
::from_span_label(span_label
,
299 fn from_render_span(rsp
: &RenderSpan
, je
: &JsonEmitter
) -> Vec
<DiagnosticSpan
> {
301 RenderSpan
::FullSpan(ref msp
) =>
302 DiagnosticSpan
::from_multispan(msp
, je
),
303 // regular diagnostics don't produce this anymore
304 // FIXME(oli_obk): remove it entirely
305 RenderSpan
::Suggestion(_
) => unreachable
!(),
310 impl DiagnosticSpanLine
{
311 fn line_from_filemap(fm
: &syntax_pos
::FileMap
,
315 -> DiagnosticSpanLine
{
317 text
: fm
.get_line(index
).map_or(String
::new(), |l
| l
.into_owned()),
318 highlight_start
: h_start
,
319 highlight_end
: h_end
,
323 /// Create a list of DiagnosticSpanLines from span - each line with any part
324 /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
325 /// `span` within the line.
326 fn from_span(span
: Span
, je
: &JsonEmitter
) -> Vec
<DiagnosticSpanLine
> {
327 je
.cm
.span_to_lines(span
)
329 let fm
= &*lines
.file
;
333 DiagnosticSpanLine
::line_from_filemap(fm
,
335 line
.start_col
.0 + 1,
340 .unwrap_or_else(|_
| vec
![])
344 impl DiagnosticCode
{
345 fn map_opt_string(s
: Option
<String
>, je
: &JsonEmitter
) -> Option
<DiagnosticCode
> {
348 let explanation
= je
.registry
350 .and_then(|registry
| registry
.find_description(&s
));
354 explanation
: explanation
,
361 fn render(&self, suggestion
: &CodeSuggestion
) -> Vec
<String
> {
362 suggestion
.splice_lines(&*self.cm
).iter().map(|line
| line
.0.to_owned()).collect()