]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/diagnostics/plugin.rs
Rollup merge of #41910 - mersinvald:master, r=Mark-Simulacrum
[rust.git] / src / libsyntax / diagnostics / plugin.rs
1 // Copyright 2014 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 use std::cell::RefCell;
12 use std::collections::BTreeMap;
13 use std::env;
14
15 use ast;
16 use ast::{Ident, Name};
17 use syntax_pos::Span;
18 use ext::base::{ExtCtxt, MacEager, MacResult};
19 use ext::build::AstBuilder;
20 use parse::token;
21 use ptr::P;
22 use symbol::Symbol;
23 use tokenstream::{TokenTree};
24 use util::small_vector::SmallVector;
25
26 use diagnostics::metadata::output_metadata;
27
28 pub use errors::*;
29
30 // Maximum width of any line in an extended error description (inclusive).
31 const MAX_DESCRIPTION_WIDTH: usize = 80;
32
33 thread_local! {
34     static REGISTERED_DIAGNOSTICS: RefCell<ErrorMap> = {
35         RefCell::new(BTreeMap::new())
36     }
37 }
38
39 /// Error information type.
40 pub struct ErrorInfo {
41     pub description: Option<Name>,
42     pub use_site: Option<Span>
43 }
44
45 /// Mapping from error codes to metadata.
46 pub type ErrorMap = BTreeMap<Name, ErrorInfo>;
47
48 fn with_registered_diagnostics<T, F>(f: F) -> T where
49     F: FnOnce(&mut ErrorMap) -> T,
50 {
51     REGISTERED_DIAGNOSTICS.with(move |slot| {
52         f(&mut *slot.borrow_mut())
53     })
54 }
55
56 pub fn expand_diagnostic_used<'cx>(ecx: &'cx mut ExtCtxt,
57                                    span: Span,
58                                    token_tree: &[TokenTree])
59                                    -> Box<MacResult+'cx> {
60     let code = match (token_tree.len(), token_tree.get(0)) {
61         (1, Some(&TokenTree::Token(_, token::Ident(code)))) => code,
62         _ => unreachable!()
63     };
64
65     with_registered_diagnostics(|diagnostics| {
66         match diagnostics.get_mut(&code.name) {
67             // Previously used errors.
68             Some(&mut ErrorInfo { description: _, use_site: Some(previous_span) }) => {
69                 ecx.struct_span_warn(span, &format!(
70                     "diagnostic code {} already used", code
71                 )).span_note(previous_span, "previous invocation")
72                   .emit();
73             }
74             // Newly used errors.
75             Some(ref mut info) => {
76                 info.use_site = Some(span);
77             }
78             // Unregistered errors.
79             None => {
80                 ecx.span_err(span, &format!(
81                     "used diagnostic code {} not registered", code
82                 ));
83             }
84         }
85     });
86     MacEager::expr(ecx.expr_tuple(span, Vec::new()))
87 }
88
89 pub fn expand_register_diagnostic<'cx>(ecx: &'cx mut ExtCtxt,
90                                        span: Span,
91                                        token_tree: &[TokenTree])
92                                        -> Box<MacResult+'cx> {
93     let (code, description) = match (
94         token_tree.len(),
95         token_tree.get(0),
96         token_tree.get(1),
97         token_tree.get(2)
98     ) {
99         (1, Some(&TokenTree::Token(_, token::Ident(ref code))), None, None) => {
100             (code, None)
101         },
102         (3, Some(&TokenTree::Token(_, token::Ident(ref code))),
103             Some(&TokenTree::Token(_, token::Comma)),
104             Some(&TokenTree::Token(_, token::Literal(token::StrRaw(description, _), None)))) => {
105             (code, Some(description))
106         }
107         _ => unreachable!()
108     };
109
110     // Check that the description starts and ends with a newline and doesn't
111     // overflow the maximum line width.
112     description.map(|raw_msg| {
113         let msg = raw_msg.as_str();
114         if !msg.starts_with("\n") || !msg.ends_with("\n") {
115             ecx.span_err(span, &format!(
116                 "description for error code {} doesn't start and end with a newline",
117                 code
118             ));
119         }
120
121         // URLs can be unavoidably longer than the line limit, so we allow them.
122         // Allowed format is: `[name]: https://www.rust-lang.org/`
123         let is_url = |l: &str| l.starts_with("[") && l.contains("]:") && l.contains("http");
124
125         if msg.lines().any(|line| line.len() > MAX_DESCRIPTION_WIDTH && !is_url(line)) {
126             ecx.span_err(span, &format!(
127                 "description for error code {} contains a line longer than {} characters.\n\
128                  if you're inserting a long URL use the footnote style to bypass this check.",
129                 code, MAX_DESCRIPTION_WIDTH
130             ));
131         }
132     });
133     // Add the error to the map.
134     with_registered_diagnostics(|diagnostics| {
135         let info = ErrorInfo {
136             description: description,
137             use_site: None
138         };
139         if diagnostics.insert(code.name, info).is_some() {
140             ecx.span_err(span, &format!(
141                 "diagnostic code {} already registered", code
142             ));
143         }
144     });
145     let sym = Ident::with_empty_ctxt(Symbol::gensym(&format!(
146         "__register_diagnostic_{}", code
147     )));
148     MacEager::items(SmallVector::many(vec![
149         ecx.item_mod(
150             span,
151             span,
152             sym,
153             Vec::new(),
154             Vec::new()
155         )
156     ]))
157 }
158
159 pub fn expand_build_diagnostic_array<'cx>(ecx: &'cx mut ExtCtxt,
160                                           span: Span,
161                                           token_tree: &[TokenTree])
162                                           -> Box<MacResult+'cx> {
163     assert_eq!(token_tree.len(), 3);
164     let (crate_name, name) = match (&token_tree[0], &token_tree[2]) {
165         (
166             // Crate name.
167             &TokenTree::Token(_, token::Ident(ref crate_name)),
168             // DIAGNOSTICS ident.
169             &TokenTree::Token(_, token::Ident(ref name))
170         ) => (*&crate_name, name),
171         _ => unreachable!()
172     };
173
174     // Output error metadata to `tmp/extended-errors/<target arch>/<crate name>.json`
175     if let Ok(target_triple) = env::var("CFG_COMPILER_HOST_TRIPLE") {
176         with_registered_diagnostics(|diagnostics| {
177             if let Err(e) = output_metadata(ecx,
178                                             &target_triple,
179                                             &crate_name.name.as_str(),
180                                             diagnostics) {
181                 ecx.span_bug(span, &format!(
182                     "error writing metadata for triple `{}` and crate `{}`, error: {}, \
183                      cause: {:?}",
184                     target_triple, crate_name, e.description(), e.cause()
185                 ));
186             }
187         });
188     } else {
189         ecx.span_err(span, &format!(
190             "failed to write metadata for crate `{}` because $CFG_COMPILER_HOST_TRIPLE is not set",
191             crate_name));
192     }
193
194     // Construct the output expression.
195     let (count, expr) =
196         with_registered_diagnostics(|diagnostics| {
197             let descriptions: Vec<P<ast::Expr>> =
198                 diagnostics.iter().filter_map(|(&code, info)| {
199                     info.description.map(|description| {
200                         ecx.expr_tuple(span, vec![
201                             ecx.expr_str(span, code),
202                             ecx.expr_str(span, description)
203                         ])
204                     })
205                 }).collect();
206             (descriptions.len(), ecx.expr_vec(span, descriptions))
207         });
208
209     let static_ = ecx.lifetime(span, ecx.name_of("'static"));
210     let ty_str = ecx.ty_rptr(
211         span,
212         ecx.ty_ident(span, ecx.ident_of("str")),
213         Some(static_),
214         ast::Mutability::Immutable,
215     );
216
217     let ty = ecx.ty(
218         span,
219         ast::TyKind::Array(
220             ecx.ty(
221                 span,
222                 ast::TyKind::Tup(vec![ty_str.clone(), ty_str])
223             ),
224             ecx.expr_usize(span, count),
225         ),
226     );
227
228     MacEager::items(SmallVector::many(vec![
229         P(ast::Item {
230             ident: *name,
231             attrs: Vec::new(),
232             id: ast::DUMMY_NODE_ID,
233             node: ast::ItemKind::Const(
234                 ty,
235                 expr,
236             ),
237             vis: ast::Visibility::Public,
238             span: span,
239         })
240     ]))
241 }