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;
15 use ast::{Ident, Name, TokenTree};
17 use ext::base::{ExtCtxt, MacEager, MacResult};
18 use ext::build::AstBuilder;
21 use util::small_vector::SmallVector;
23 // Maximum width of any line in an extended error description (inclusive).
24 const MAX_DESCRIPTION_WIDTH: usize = 80;
27 static REGISTERED_DIAGNOSTICS: RefCell<ErrorMap> = {
28 RefCell::new(BTreeMap::new())
32 /// Error information type.
33 pub struct ErrorInfo {
34 pub description: Option<Name>,
35 pub use_site: Option<Span>
38 /// Mapping from error codes to metadata.
39 pub type ErrorMap = BTreeMap<Name, ErrorInfo>;
41 fn with_registered_diagnostics<T, F>(f: F) -> T where
42 F: FnOnce(&mut ErrorMap) -> T,
44 REGISTERED_DIAGNOSTICS.with(move |slot| {
45 f(&mut *slot.borrow_mut())
49 pub fn expand_diagnostic_used<'cx>(ecx: &'cx mut ExtCtxt,
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,
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)
65 ecx.span_note(previous_span, "previous invocation");
68 Some(ref mut info) => {
69 info.use_site = Some(span);
71 // Unregistered errors.
73 ecx.span_err(span, &format!(
74 "used diagnostic code {} not registered", &token::get_ident(code)
79 MacEager::expr(ecx.expr_tuple(span, Vec::new()))
82 pub fn expand_register_diagnostic<'cx>(ecx: &'cx mut ExtCtxt,
84 token_tree: &[TokenTree])
85 -> Box<MacResult+'cx> {
86 let (code, description) = match (
92 (1, Some(&ast::TtToken(_, token::Ident(ref code, _))), None, None) => {
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))
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)
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");
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
126 // Add the error to the map.
127 with_registered_diagnostics(|diagnostics| {
128 let info = ErrorInfo {
129 description: description,
132 if diagnostics.insert(code.name, info).is_some() {
133 ecx.span_err(span, &format!(
134 "diagnostic code {} already registered", &token::get_ident(*code)
138 let sym = Ident::new(token::gensym(&(
139 "__register_diagnostic_".to_string() + &token::get_ident(*code)
141 MacEager::items(SmallVector::many(vec![
152 pub fn expand_build_diagnostic_array<'cx>(ecx: &'cx mut ExtCtxt,
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]) {
160 &ast::TtToken(_, token::Ident(ref crate_name, _)),
161 // DIAGNOSTICS ident.
162 &ast::TtToken(_, token::Ident(ref name, _))
163 ) => (crate_name.as_str(), name),
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.
171 // Construct the output expression.
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))
183 (descriptions.len(), ecx.expr_vec(span, descriptions))
186 let static_ = ecx.lifetime(span, ecx.name_of("'static"));
187 let ty_str = ecx.ty_rptr(
189 ecx.ty_ident(span, ecx.ident_of("str")),
196 ast::TyFixedLengthVec(
199 ast::TyTup(vec![ty_str.clone(), ty_str])
201 ecx.expr_usize(span, count),
205 MacEager::items(SmallVector::many(vec![
209 id: ast::DUMMY_NODE_ID,
210 node: ast::ItemConst(