]> git.proxmox.com Git - rustc.git/blob - src/libsyntax/json.rs
New upstream version 1.23.0+dfsg1
[rustc.git] / src / libsyntax / json.rs
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.
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 serializing 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
22 use codemap::{CodeMap, FilePathMapping};
23 use syntax_pos::{self, MacroBacktrace, Span, SpanLabel, MultiSpan};
24 use errors::registry::Registry;
25 use errors::{DiagnosticBuilder, SubDiagnostic, CodeSuggestion, CodeMapper};
26 use errors::DiagnosticId;
27 use errors::emitter::Emitter;
28
29 use std::rc::Rc;
30 use std::io::{self, Write};
31 use std::vec;
32
33 use rustc_serialize::json::{as_json, as_pretty_json};
34
35 pub struct JsonEmitter {
36 dst: Box<Write + Send>,
37 registry: Option<Registry>,
38 cm: Rc<CodeMapper + 'static>,
39 pretty: bool,
40 }
41
42 impl JsonEmitter {
43 pub fn stderr(registry: Option<Registry>,
44 code_map: Rc<CodeMap>,
45 pretty: bool) -> JsonEmitter {
46 JsonEmitter {
47 dst: Box::new(io::stderr()),
48 registry,
49 cm: code_map,
50 pretty,
51 }
52 }
53
54 pub fn basic(pretty: bool) -> JsonEmitter {
55 let file_path_mapping = FilePathMapping::empty();
56 JsonEmitter::stderr(None, Rc::new(CodeMap::new(file_path_mapping)), pretty)
57 }
58
59 pub fn new(dst: Box<Write + Send>,
60 registry: Option<Registry>,
61 code_map: Rc<CodeMap>,
62 pretty: bool) -> JsonEmitter {
63 JsonEmitter {
64 dst,
65 registry,
66 cm: code_map,
67 pretty,
68 }
69 }
70 }
71
72 impl Emitter for JsonEmitter {
73 fn emit(&mut self, db: &DiagnosticBuilder) {
74 let data = Diagnostic::from_diagnostic_builder(db, self);
75 let result = if self.pretty {
76 writeln!(&mut self.dst, "{}", as_pretty_json(&data))
77 } else {
78 writeln!(&mut self.dst, "{}", as_json(&data))
79 };
80 if let Err(e) = result {
81 panic!("failed to print diagnostics: {:?}", e);
82 }
83 }
84 }
85
86 // The following data types are provided just for serialisation.
87
88 #[derive(RustcEncodable)]
89 struct Diagnostic {
90 /// The primary error message.
91 message: String,
92 code: Option<DiagnosticCode>,
93 /// "error: internal compiler error", "error", "warning", "note", "help".
94 level: &'static str,
95 spans: Vec<DiagnosticSpan>,
96 /// Associated diagnostic messages.
97 children: Vec<Diagnostic>,
98 /// The message as rustc would render it. Currently this is always `None`
99 rendered: Option<String>,
100 }
101
102 #[derive(RustcEncodable)]
103 struct DiagnosticSpan {
104 file_name: String,
105 byte_start: u32,
106 byte_end: u32,
107 /// 1-based.
108 line_start: usize,
109 line_end: usize,
110 /// 1-based, character offset.
111 column_start: usize,
112 column_end: usize,
113 /// Is this a "primary" span -- meaning the point, or one of the points,
114 /// where the error occurred?
115 is_primary: bool,
116 /// Source text from the start of line_start to the end of line_end.
117 text: Vec<DiagnosticSpanLine>,
118 /// Label that should be placed at this location (if any)
119 label: Option<String>,
120 /// If we are suggesting a replacement, this will contain text
121 /// that should be sliced in atop this span.
122 suggested_replacement: Option<String>,
123 /// Macro invocations that created the code at this span, if any.
124 expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
125 }
126
127 #[derive(RustcEncodable)]
128 struct DiagnosticSpanLine {
129 text: String,
130
131 /// 1-based, character offset in self.text.
132 highlight_start: usize,
133
134 highlight_end: usize,
135 }
136
137 #[derive(RustcEncodable)]
138 struct DiagnosticSpanMacroExpansion {
139 /// span where macro was applied to generate this code; note that
140 /// this may itself derive from a macro (if
141 /// `span.expansion.is_some()`)
142 span: DiagnosticSpan,
143
144 /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
145 macro_decl_name: String,
146
147 /// span where macro was defined (if known)
148 def_site_span: Option<DiagnosticSpan>,
149 }
150
151 #[derive(RustcEncodable)]
152 struct DiagnosticCode {
153 /// The code itself.
154 code: String,
155 /// An explanation for the code.
156 explanation: Option<&'static str>,
157 }
158
159 impl Diagnostic {
160 fn from_diagnostic_builder(db: &DiagnosticBuilder,
161 je: &JsonEmitter)
162 -> Diagnostic {
163 let sugg = db.suggestions.iter().map(|sugg| {
164 Diagnostic {
165 message: sugg.msg.clone(),
166 code: None,
167 level: "help",
168 spans: DiagnosticSpan::from_suggestion(sugg, je),
169 children: vec![],
170 rendered: None,
171 }
172 });
173 Diagnostic {
174 message: db.message(),
175 code: DiagnosticCode::map_opt_string(db.code.clone(), je),
176 level: db.level.to_str(),
177 spans: DiagnosticSpan::from_multispan(&db.span, je),
178 children: db.children.iter().map(|c| {
179 Diagnostic::from_sub_diagnostic(c, je)
180 }).chain(sugg).collect(),
181 rendered: None,
182 }
183 }
184
185 fn from_sub_diagnostic(db: &SubDiagnostic, je: &JsonEmitter) -> Diagnostic {
186 Diagnostic {
187 message: db.message(),
188 code: None,
189 level: db.level.to_str(),
190 spans: db.render_span.as_ref()
191 .map(|sp| DiagnosticSpan::from_multispan(sp, je))
192 .unwrap_or_else(|| DiagnosticSpan::from_multispan(&db.span, je)),
193 children: vec![],
194 rendered: None,
195 }
196 }
197 }
198
199 impl DiagnosticSpan {
200 fn from_span_label(span: SpanLabel,
201 suggestion: Option<&String>,
202 je: &JsonEmitter)
203 -> DiagnosticSpan {
204 Self::from_span_etc(span.span,
205 span.is_primary,
206 span.label,
207 suggestion,
208 je)
209 }
210
211 fn from_span_etc(span: Span,
212 is_primary: bool,
213 label: Option<String>,
214 suggestion: Option<&String>,
215 je: &JsonEmitter)
216 -> DiagnosticSpan {
217 // obtain the full backtrace from the `macro_backtrace`
218 // helper; in some ways, it'd be better to expand the
219 // backtrace ourselves, but the `macro_backtrace` helper makes
220 // some decision, such as dropping some frames, and I don't
221 // want to duplicate that logic here.
222 let backtrace = span.macro_backtrace().into_iter();
223 DiagnosticSpan::from_span_full(span,
224 is_primary,
225 label,
226 suggestion,
227 backtrace,
228 je)
229 }
230
231 fn from_span_full(span: Span,
232 is_primary: bool,
233 label: Option<String>,
234 suggestion: Option<&String>,
235 mut backtrace: vec::IntoIter<MacroBacktrace>,
236 je: &JsonEmitter)
237 -> DiagnosticSpan {
238 let start = je.cm.lookup_char_pos(span.lo());
239 let end = je.cm.lookup_char_pos(span.hi());
240 let backtrace_step = backtrace.next().map(|bt| {
241 let call_site =
242 Self::from_span_full(bt.call_site,
243 false,
244 None,
245 None,
246 backtrace,
247 je);
248 let def_site_span = bt.def_site_span.map(|sp| {
249 Self::from_span_full(sp,
250 false,
251 None,
252 None,
253 vec![].into_iter(),
254 je)
255 });
256 Box::new(DiagnosticSpanMacroExpansion {
257 span: call_site,
258 macro_decl_name: bt.macro_decl_name,
259 def_site_span,
260 })
261 });
262 DiagnosticSpan {
263 file_name: start.file.name.clone(),
264 byte_start: span.lo().0 - start.file.start_pos.0,
265 byte_end: span.hi().0 - start.file.start_pos.0,
266 line_start: start.line,
267 line_end: end.line,
268 column_start: start.col.0 + 1,
269 column_end: end.col.0 + 1,
270 is_primary,
271 text: DiagnosticSpanLine::from_span(span, je),
272 suggested_replacement: suggestion.cloned(),
273 expansion: backtrace_step,
274 label,
275 }
276 }
277
278 fn from_multispan(msp: &MultiSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
279 msp.span_labels()
280 .into_iter()
281 .map(|span_str| Self::from_span_label(span_str, None, je))
282 .collect()
283 }
284
285 fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter)
286 -> Vec<DiagnosticSpan> {
287 suggestion.substitutions
288 .iter()
289 .flat_map(|substitution| {
290 substitution.parts.iter().map(move |suggestion| {
291 let span_label = SpanLabel {
292 span: suggestion.span,
293 is_primary: true,
294 label: None,
295 };
296 DiagnosticSpan::from_span_label(span_label,
297 Some(&suggestion.snippet),
298 je)
299 })
300 })
301 .collect()
302 }
303 }
304
305 impl DiagnosticSpanLine {
306 fn line_from_filemap(fm: &syntax_pos::FileMap,
307 index: usize,
308 h_start: usize,
309 h_end: usize)
310 -> DiagnosticSpanLine {
311 DiagnosticSpanLine {
312 text: fm.get_line(index).map_or(String::new(), |l| l.into_owned()),
313 highlight_start: h_start,
314 highlight_end: h_end,
315 }
316 }
317
318 /// Create a list of DiagnosticSpanLines from span - each line with any part
319 /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
320 /// `span` within the line.
321 fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> {
322 je.cm.span_to_lines(span)
323 .map(|lines| {
324 let fm = &*lines.file;
325 lines.lines
326 .iter()
327 .map(|line| {
328 DiagnosticSpanLine::line_from_filemap(fm,
329 line.line_index,
330 line.start_col.0 + 1,
331 line.end_col.0 + 1)
332 })
333 .collect()
334 })
335 .unwrap_or_else(|_| vec![])
336 }
337 }
338
339 impl DiagnosticCode {
340 fn map_opt_string(s: Option<DiagnosticId>, je: &JsonEmitter) -> Option<DiagnosticCode> {
341 s.map(|s| {
342 let s = match s {
343 DiagnosticId::Error(s) => s,
344 DiagnosticId::Lint(s) => s,
345 };
346 let explanation = je.registry
347 .as_ref()
348 .and_then(|registry| registry.find_description(&s));
349
350 DiagnosticCode {
351 code: s,
352 explanation,
353 }
354 })
355 }
356 }