]>
Commit | Line | Data |
---|---|---|
a7813a04 | 1 | // Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT |
9cc50fc6 SL |
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 | //! A JSON emitter for errors. | |
12 | //! | |
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. | |
16 | //! | |
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. | |
19 | ||
20 | // FIXME spec the JSON output properly. | |
21 | ||
7cac9316 | 22 | use codemap::{CodeMap, FilePathMapping}; |
3157f602 XL |
23 | use syntax_pos::{self, MacroBacktrace, Span, SpanLabel, MultiSpan}; |
24 | use errors::registry::Registry; | |
5bcae85e | 25 | use errors::{DiagnosticBuilder, SubDiagnostic, RenderSpan, CodeSuggestion, CodeMapper}; |
9cc50fc6 SL |
26 | use errors::emitter::Emitter; |
27 | ||
28 | use std::rc::Rc; | |
29 | use std::io::{self, Write}; | |
a7813a04 | 30 | use std::vec; |
9cc50fc6 SL |
31 | |
32 | use rustc_serialize::json::as_json; | |
33 | ||
34 | pub struct JsonEmitter { | |
35 | dst: Box<Write + Send>, | |
36 | registry: Option<Registry>, | |
3157f602 | 37 | cm: Rc<CodeMapper + 'static>, |
9cc50fc6 SL |
38 | } |
39 | ||
40 | impl JsonEmitter { | |
c30ab7b3 SL |
41 | pub fn stderr(registry: Option<Registry>, |
42 | code_map: Rc<CodeMap>) -> JsonEmitter { | |
43 | JsonEmitter { | |
44 | dst: Box::new(io::stderr()), | |
45 | registry: registry, | |
46 | cm: code_map, | |
47 | } | |
48 | } | |
49 | ||
9cc50fc6 | 50 | pub fn basic() -> JsonEmitter { |
7cac9316 XL |
51 | let file_path_mapping = FilePathMapping::empty(); |
52 | JsonEmitter::stderr(None, Rc::new(CodeMap::new(file_path_mapping))) | |
9cc50fc6 SL |
53 | } |
54 | ||
c30ab7b3 SL |
55 | pub fn new(dst: Box<Write + Send>, |
56 | registry: Option<Registry>, | |
57 | code_map: Rc<CodeMap>) -> JsonEmitter { | |
9cc50fc6 | 58 | JsonEmitter { |
c30ab7b3 | 59 | dst: dst, |
9cc50fc6 SL |
60 | registry: registry, |
61 | cm: code_map, | |
62 | } | |
63 | } | |
64 | } | |
65 | ||
66 | impl Emitter for JsonEmitter { | |
5bcae85e | 67 | fn emit(&mut self, db: &DiagnosticBuilder) { |
9cc50fc6 SL |
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); | |
71 | } | |
72 | } | |
73 | } | |
74 | ||
75 | // The following data types are provided just for serialisation. | |
76 | ||
77 | #[derive(RustcEncodable)] | |
32a655c1 | 78 | struct Diagnostic { |
9cc50fc6 | 79 | /// The primary error message. |
32a655c1 | 80 | message: String, |
9cc50fc6 SL |
81 | code: Option<DiagnosticCode>, |
82 | /// "error: internal compiler error", "error", "warning", "note", "help". | |
83 | level: &'static str, | |
7453a54e | 84 | spans: Vec<DiagnosticSpan>, |
a7813a04 | 85 | /// Associated diagnostic messages. |
32a655c1 | 86 | children: Vec<Diagnostic>, |
a7813a04 XL |
87 | /// The message as rustc would render it. Currently this is only |
88 | /// `Some` for "suggestions", but eventually it will include all | |
89 | /// snippets. | |
90 | rendered: Option<String>, | |
9cc50fc6 SL |
91 | } |
92 | ||
93 | #[derive(RustcEncodable)] | |
94 | struct DiagnosticSpan { | |
95 | file_name: String, | |
96 | byte_start: u32, | |
97 | byte_end: u32, | |
98 | /// 1-based. | |
99 | line_start: usize, | |
100 | line_end: usize, | |
101 | /// 1-based, character offset. | |
102 | column_start: usize, | |
103 | column_end: usize, | |
a7813a04 XL |
104 | /// Is this a "primary" span -- meaning the point, or one of the points, |
105 | /// where the error occurred? | |
106 | is_primary: bool, | |
54a0048b SL |
107 | /// Source text from the start of line_start to the end of line_end. |
108 | text: Vec<DiagnosticSpanLine>, | |
a7813a04 XL |
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`, | |
114 | /// however. | |
115 | suggested_replacement: Option<String>, | |
116 | /// Macro invocations that created the code at this span, if any. | |
117 | expansion: Option<Box<DiagnosticSpanMacroExpansion>>, | |
54a0048b SL |
118 | } |
119 | ||
120 | #[derive(RustcEncodable)] | |
121 | struct DiagnosticSpanLine { | |
122 | text: String, | |
a7813a04 | 123 | |
54a0048b SL |
124 | /// 1-based, character offset in self.text. |
125 | highlight_start: usize, | |
a7813a04 | 126 | |
54a0048b | 127 | highlight_end: usize, |
9cc50fc6 SL |
128 | } |
129 | ||
a7813a04 XL |
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, | |
136 | ||
137 | /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]") | |
138 | macro_decl_name: String, | |
139 | ||
140 | /// span where macro was defined (if known) | |
141 | def_site_span: Option<DiagnosticSpan>, | |
142 | } | |
143 | ||
9cc50fc6 SL |
144 | #[derive(RustcEncodable)] |
145 | struct DiagnosticCode { | |
146 | /// The code itself. | |
147 | code: String, | |
148 | /// An explanation for the code. | |
149 | explanation: Option<&'static str>, | |
150 | } | |
151 | ||
32a655c1 SL |
152 | impl Diagnostic { |
153 | fn from_diagnostic_builder(db: &DiagnosticBuilder, | |
154 | je: &JsonEmitter) | |
155 | -> Diagnostic { | |
7cac9316 XL |
156 | let sugg = db.suggestions.iter().flat_map(|sugg| { |
157 | je.render(sugg).into_iter().map(move |rendered| { | |
158 | Diagnostic { | |
159 | message: sugg.msg.clone(), | |
160 | code: None, | |
161 | level: "help", | |
162 | spans: DiagnosticSpan::from_suggestion(sugg, je), | |
163 | children: vec![], | |
164 | rendered: Some(rendered), | |
165 | } | |
166 | }) | |
167 | }); | |
9cc50fc6 | 168 | Diagnostic { |
32a655c1 | 169 | message: db.message(), |
9cc50fc6 SL |
170 | code: DiagnosticCode::map_opt_string(db.code.clone(), je), |
171 | level: db.level.to_str(), | |
a7813a04 | 172 | spans: DiagnosticSpan::from_multispan(&db.span, je), |
9cc50fc6 SL |
173 | children: db.children.iter().map(|c| { |
174 | Diagnostic::from_sub_diagnostic(c, je) | |
7cac9316 | 175 | }).chain(sugg).collect(), |
a7813a04 | 176 | rendered: None, |
9cc50fc6 SL |
177 | } |
178 | } | |
179 | ||
32a655c1 | 180 | fn from_sub_diagnostic(db: &SubDiagnostic, je: &JsonEmitter) -> Diagnostic { |
9cc50fc6 | 181 | Diagnostic { |
32a655c1 | 182 | message: db.message(), |
9cc50fc6 SL |
183 | code: None, |
184 | level: db.level.to_str(), | |
7453a54e SL |
185 | spans: db.render_span.as_ref() |
186 | .map(|sp| DiagnosticSpan::from_render_span(sp, je)) | |
a7813a04 | 187 | .unwrap_or_else(|| DiagnosticSpan::from_multispan(&db.span, je)), |
9cc50fc6 | 188 | children: vec![], |
7cac9316 | 189 | rendered: None, |
9cc50fc6 SL |
190 | } |
191 | } | |
192 | } | |
193 | ||
194 | impl DiagnosticSpan { | |
a7813a04 XL |
195 | fn from_span_label(span: SpanLabel, |
196 | suggestion: Option<&String>, | |
197 | je: &JsonEmitter) | |
198 | -> DiagnosticSpan { | |
199 | Self::from_span_etc(span.span, | |
200 | span.is_primary, | |
201 | span.label, | |
202 | suggestion, | |
203 | je) | |
9cc50fc6 SL |
204 | } |
205 | ||
a7813a04 XL |
206 | fn from_span_etc(span: Span, |
207 | is_primary: bool, | |
208 | label: Option<String>, | |
209 | suggestion: Option<&String>, | |
210 | je: &JsonEmitter) | |
211 | -> DiagnosticSpan { | |
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. | |
cc61c64b | 217 | let backtrace = span.macro_backtrace().into_iter(); |
a7813a04 XL |
218 | DiagnosticSpan::from_span_full(span, |
219 | is_primary, | |
220 | label, | |
221 | suggestion, | |
222 | backtrace, | |
223 | je) | |
224 | } | |
225 | ||
226 | fn from_span_full(span: Span, | |
227 | is_primary: bool, | |
228 | label: Option<String>, | |
229 | suggestion: Option<&String>, | |
230 | mut backtrace: vec::IntoIter<MacroBacktrace>, | |
231 | je: &JsonEmitter) | |
232 | -> DiagnosticSpan { | |
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| { | |
236 | let call_site = | |
237 | Self::from_span_full(bt.call_site, | |
238 | false, | |
239 | None, | |
240 | None, | |
241 | backtrace, | |
242 | je); | |
243 | let def_site_span = bt.def_site_span.map(|sp| { | |
244 | Self::from_span_full(sp, | |
245 | false, | |
246 | None, | |
247 | None, | |
248 | vec![].into_iter(), | |
249 | je) | |
250 | }); | |
251 | Box::new(DiagnosticSpanMacroExpansion { | |
252 | span: call_site, | |
253 | macro_decl_name: bt.macro_decl_name, | |
254 | def_site_span: def_site_span, | |
255 | }) | |
256 | }); | |
257 | DiagnosticSpan { | |
258 | file_name: start.file.name.clone(), | |
259 | byte_start: span.lo.0, | |
260 | byte_end: span.hi.0, | |
261 | line_start: start.line, | |
262 | line_end: end.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, | |
269 | label: label, | |
9cc50fc6 SL |
270 | } |
271 | } | |
9cc50fc6 | 272 | |
a7813a04 XL |
273 | fn from_multispan(msp: &MultiSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> { |
274 | msp.span_labels() | |
275 | .into_iter() | |
276 | .map(|span_str| Self::from_span_label(span_str, None, je)) | |
277 | .collect() | |
278 | } | |
279 | ||
280 | fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter) | |
281 | -> Vec<DiagnosticSpan> { | |
7cac9316 XL |
282 | suggestion.substitution_parts |
283 | .iter() | |
284 | .flat_map(|substitution| { | |
285 | substitution.substitutions.iter().map(move |suggestion| { | |
286 | let span_label = SpanLabel { | |
287 | span: substitution.span, | |
288 | is_primary: true, | |
289 | label: None, | |
290 | }; | |
291 | DiagnosticSpan::from_span_label(span_label, | |
292 | Some(suggestion), | |
293 | je) | |
294 | }) | |
a7813a04 XL |
295 | }) |
296 | .collect() | |
297 | } | |
298 | ||
299 | fn from_render_span(rsp: &RenderSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> { | |
300 | match *rsp { | |
301 | RenderSpan::FullSpan(ref msp) => | |
302 | DiagnosticSpan::from_multispan(msp, je), | |
7cac9316 XL |
303 | // regular diagnostics don't produce this anymore |
304 | // FIXME(oli_obk): remove it entirely | |
305 | RenderSpan::Suggestion(_) => unreachable!(), | |
54a0048b SL |
306 | } |
307 | } | |
308 | } | |
309 | ||
310 | impl DiagnosticSpanLine { | |
3157f602 | 311 | fn line_from_filemap(fm: &syntax_pos::FileMap, |
54a0048b SL |
312 | index: usize, |
313 | h_start: usize, | |
314 | h_end: usize) | |
315 | -> DiagnosticSpanLine { | |
316 | DiagnosticSpanLine { | |
041b39d2 | 317 | text: fm.get_line(index).map_or(String::new(), |l| l.into_owned()), |
54a0048b SL |
318 | highlight_start: h_start, |
319 | highlight_end: h_end, | |
320 | } | |
321 | } | |
322 | ||
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. | |
a7813a04 XL |
326 | fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> { |
327 | je.cm.span_to_lines(span) | |
328 | .map(|lines| { | |
329 | let fm = &*lines.file; | |
330 | lines.lines | |
331 | .iter() | |
332 | .map(|line| { | |
333 | DiagnosticSpanLine::line_from_filemap(fm, | |
334 | line.line_index, | |
335 | line.start_col.0 + 1, | |
336 | line.end_col.0 + 1) | |
337 | }) | |
338 | .collect() | |
339 | }) | |
7cac9316 | 340 | .unwrap_or_else(|_| vec![]) |
54a0048b SL |
341 | } |
342 | } | |
343 | ||
9cc50fc6 SL |
344 | impl DiagnosticCode { |
345 | fn map_opt_string(s: Option<String>, je: &JsonEmitter) -> Option<DiagnosticCode> { | |
346 | s.map(|s| { | |
347 | ||
348 | let explanation = je.registry | |
349 | .as_ref() | |
350 | .and_then(|registry| registry.find_description(&s)); | |
351 | ||
352 | DiagnosticCode { | |
353 | code: s, | |
354 | explanation: explanation, | |
355 | } | |
356 | }) | |
357 | } | |
358 | } | |
a7813a04 XL |
359 | |
360 | impl JsonEmitter { | |
7cac9316 | 361 | fn render(&self, suggestion: &CodeSuggestion) -> Vec<String> { |
041b39d2 | 362 | suggestion.splice_lines(&*self.cm).iter().map(|line| line.0.to_owned()).collect() |
a7813a04 XL |
363 | } |
364 | } | |
365 |