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