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.
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.
11 use std::cell::RefCell;
12 use std::collections::BTreeMap;
16 use ast::{Ident, Name};
18 use ext::base::{ExtCtxt, MacEager, MacResult};
19 use ext::build::AstBuilder;
23 use tokenstream::{TokenTree};
24 use util::small_vector::SmallVector;
26 use diagnostics::metadata::output_metadata;
30 // Maximum width of any line in an extended error description (inclusive).
31 const MAX_DESCRIPTION_WIDTH: usize = 80;
34 static REGISTERED_DIAGNOSTICS: RefCell<ErrorMap> = {
35 RefCell::new(BTreeMap::new())
39 /// Error information type.
40 pub struct ErrorInfo {
41 pub description: Option<Name>,
42 pub use_site: Option<Span>
45 /// Mapping from error codes to metadata.
46 pub type ErrorMap = BTreeMap<Name, ErrorInfo>;
48 fn with_registered_diagnostics<T, F>(f: F) -> T where
49 F: FnOnce(&mut ErrorMap) -> T,
51 REGISTERED_DIAGNOSTICS.with(move |slot| {
52 f(&mut *slot.borrow_mut())
56 pub fn expand_diagnostic_used<'cx>(ecx: &'cx mut ExtCtxt,
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,
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")
75 Some(ref mut info) => {
76 info.use_site = Some(span);
78 // Unregistered errors.
80 ecx.span_err(span, &format!(
81 "used diagnostic code {} not registered", code
86 MacEager::expr(ecx.expr_tuple(span, Vec::new()))
89 pub fn expand_register_diagnostic<'cx>(ecx: &'cx mut ExtCtxt,
91 token_tree: &[TokenTree])
92 -> Box<MacResult+'cx> {
93 let (code, description) = match (
99 (1, Some(&TokenTree::Token(_, token::Ident(ref code))), None, None) => {
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))
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",
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");
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
133 // Add the error to the map.
134 with_registered_diagnostics(|diagnostics| {
135 let info = ErrorInfo {
136 description: description,
139 if diagnostics.insert(code.name, info).is_some() {
140 ecx.span_err(span, &format!(
141 "diagnostic code {} already registered", code
145 let sym = Ident::with_empty_ctxt(Symbol::gensym(&format!(
146 "__register_diagnostic_{}", code
148 MacEager::items(SmallVector::many(vec![
159 pub fn expand_build_diagnostic_array<'cx>(ecx: &'cx mut ExtCtxt,
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]) {
167 &TokenTree::Token(_, token::Ident(ref crate_name)),
168 // DIAGNOSTICS ident.
169 &TokenTree::Token(_, token::Ident(ref name))
170 ) => (*&crate_name, name),
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,
179 &crate_name.name.as_str(),
181 ecx.span_bug(span, &format!(
182 "error writing metadata for triple `{}` and crate `{}`, error: {}, \
184 target_triple, crate_name, e.description(), e.cause()
189 ecx.span_err(span, &format!(
190 "failed to write metadata for crate `{}` because $CFG_COMPILER_HOST_TRIPLE is not set",
194 // Construct the output expression.
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)
206 (descriptions.len(), ecx.expr_vec(span, descriptions))
209 let static_ = ecx.lifetime(span, ecx.name_of("'static"));
210 let ty_str = ecx.ty_rptr(
212 ecx.ty_ident(span, ecx.ident_of("str")),
214 ast::Mutability::Immutable,
222 ast::TyKind::Tup(vec![ty_str.clone(), ty_str])
224 ecx.expr_usize(span, count),
228 MacEager::items(SmallVector::many(vec![
232 id: ast::DUMMY_NODE_ID,
233 node: ast::ItemKind::Const(
237 vis: ast::Visibility::Public,