]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/diagnostics/plugin.rs
2f7e4a161450ada19bf8ecb6d902ce1c8b6f391a
[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
14 use ast;
15 use ast::{Ident, Name, TokenTree};
16 use codemap::Span;
17 use ext::base::{ExtCtxt, MacEager, MacResult};
18 use ext::build::AstBuilder;
19 use parse::token;
20 use ptr::P;
21 use util::small_vector::SmallVector;
22
23 // Maximum width of any line in an extended error description (inclusive).
24 const MAX_DESCRIPTION_WIDTH: usize = 80;
25
26 thread_local! {
27     static REGISTERED_DIAGNOSTICS: RefCell<ErrorMap> = {
28         RefCell::new(BTreeMap::new())
29     }
30 }
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 fn with_registered_diagnostics<T, F>(f: F) -> T where
42     F: FnOnce(&mut ErrorMap) -> T,
43 {
44     REGISTERED_DIAGNOSTICS.with(move |slot| {
45         f(&mut *slot.borrow_mut())
46     })
47 }
48
49 pub fn expand_diagnostic_used<'cx>(ecx: &'cx mut ExtCtxt,
50                                    span: Span,
51                                    token_tree: &[TokenTree])
52                                    -> Box<MacResult+'cx> {
53     let code = match (token_tree.len(), token_tree.get(0)) {
54         (1, Some(&ast::TtToken(_, token::Ident(code, _)))) => code,
55         _ => unreachable!()
56     };
57
58     with_registered_diagnostics(|diagnostics| {
59         match diagnostics.get_mut(&code.name) {
60             // Previously used errors.
61             Some(&mut ErrorInfo { description: _, use_site: Some(previous_span) }) => {
62                 ecx.span_warn(span, &format!(
63                     "diagnostic code {} already used", &token::get_ident(code)
64                 ));
65                 ecx.span_note(previous_span, "previous invocation");
66             }
67             // Newly used errors.
68             Some(ref mut info) => {
69                 info.use_site = Some(span);
70             }
71             // Unregistered errors.
72             None => {
73                 ecx.span_err(span, &format!(
74                     "used diagnostic code {} not registered", &token::get_ident(code)
75                 ));
76             }
77         }
78     });
79     MacEager::expr(ecx.expr_tuple(span, Vec::new()))
80 }
81
82 pub fn expand_register_diagnostic<'cx>(ecx: &'cx mut ExtCtxt,
83                                        span: Span,
84                                        token_tree: &[TokenTree])
85                                        -> Box<MacResult+'cx> {
86     let (code, description) = match (
87         token_tree.len(),
88         token_tree.get(0),
89         token_tree.get(1),
90         token_tree.get(2)
91     ) {
92         (1, Some(&ast::TtToken(_, token::Ident(ref code, _))), None, None) => {
93             (code, None)
94         },
95         (3, Some(&ast::TtToken(_, token::Ident(ref code, _))),
96             Some(&ast::TtToken(_, token::Comma)),
97             Some(&ast::TtToken(_, token::Literal(token::StrRaw(description, _), None)))) => {
98             (code, Some(description))
99         }
100         _ => unreachable!()
101     };
102
103     // Check that the description starts and ends with a newline and doesn't
104     // overflow the maximum line width.
105     description.map(|raw_msg| {
106         let msg = raw_msg.as_str();
107         if !msg.starts_with("\n") || !msg.ends_with("\n") {
108             ecx.span_err(span, &format!(
109                 "description for error code {} doesn't start and end with a newline",
110                 token::get_ident(*code)
111             ));
112         }
113
114         // URLs can be unavoidably longer than the line limit, so we allow them.
115         // Allowed format is: `[name]: http://rust-lang.org/`
116         let is_url = |l: &str| l.starts_with('[') && l.contains("]:") && l.contains("http");
117
118         if msg.lines().any(|line| line.len() > MAX_DESCRIPTION_WIDTH && !is_url(line)) {
119             ecx.span_err(span, &format!(
120                 "description for error code {} contains a line longer than {} characters.\n\
121                  if you're inserting a long URL use the footnote style to bypass this check.",
122                 token::get_ident(*code), MAX_DESCRIPTION_WIDTH
123             ));
124         }
125     });
126     // Add the error to the map.
127     with_registered_diagnostics(|diagnostics| {
128         let info = ErrorInfo {
129             description: description,
130             use_site: None
131         };
132         if diagnostics.insert(code.name, info).is_some() {
133             ecx.span_err(span, &format!(
134                 "diagnostic code {} already registered", &token::get_ident(*code)
135             ));
136         }
137     });
138     let sym = Ident::new(token::gensym(&(
139         "__register_diagnostic_".to_string() + &token::get_ident(*code)
140     )));
141     MacEager::items(SmallVector::many(vec![
142         ecx.item_mod(
143             span,
144             span,
145             sym,
146             Vec::new(),
147             Vec::new()
148         )
149     ]))
150 }
151
152 pub fn expand_build_diagnostic_array<'cx>(ecx: &'cx mut ExtCtxt,
153                                           span: Span,
154                                           token_tree: &[TokenTree])
155                                           -> Box<MacResult+'cx> {
156     assert_eq!(token_tree.len(), 3);
157     let (_crate_name, name) = match (&token_tree[0], &token_tree[2]) {
158         (
159             // Crate name.
160             &ast::TtToken(_, token::Ident(ref crate_name, _)),
161             // DIAGNOSTICS ident.
162             &ast::TtToken(_, token::Ident(ref name, _))
163         ) => (crate_name.as_str(), name),
164         _ => unreachable!()
165     };
166
167     // FIXME (#25705): we used to ensure error code uniqueness and
168     // output error description JSON metadata here, but the approach
169     // employed was too brittle.
170
171     // Construct the output expression.
172     let (count, expr) =
173         with_registered_diagnostics(|diagnostics| {
174             let descriptions: Vec<P<ast::Expr>> =
175                 diagnostics.iter().filter_map(|(code, info)| {
176                     info.description.map(|description| {
177                         ecx.expr_tuple(span, vec![
178                             ecx.expr_str(span, token::get_name(*code)),
179                             ecx.expr_str(span, token::get_name(description))
180                         ])
181                     })
182                 }).collect();
183             (descriptions.len(), ecx.expr_vec(span, descriptions))
184         });
185
186     let static_ = ecx.lifetime(span, ecx.name_of("'static"));
187     let ty_str = ecx.ty_rptr(
188         span,
189         ecx.ty_ident(span, ecx.ident_of("str")),
190         Some(static_),
191         ast::MutImmutable,
192     );
193
194     let ty = ecx.ty(
195         span,
196         ast::TyFixedLengthVec(
197             ecx.ty(
198                 span,
199                 ast::TyTup(vec![ty_str.clone(), ty_str])
200             ),
201             ecx.expr_usize(span, count),
202         ),
203     );
204
205     MacEager::items(SmallVector::many(vec![
206         P(ast::Item {
207             ident: name.clone(),
208             attrs: Vec::new(),
209             id: ast::DUMMY_NODE_ID,
210             node: ast::ItemConst(
211                 ty,
212                 expr,
213             ),
214             vis: ast::Public,
215             span: span,
216         })
217     ]))
218 }