use std::cell::RefCell;
use std::collections::BTreeMap;
use std::env;
use crate::ast;
use crate::ast::{Ident, Name};
use crate::ext::base::{ExtCtxt, MacEager, MacResult};
use crate::ext::build::AstBuilder;
use crate::parse::token;
use crate::ptr::P;
use crate::symbol::Symbol;
use crate::syntax_pos::Span;
use crate::tokenstream::TokenTree;
use crate::util::small_vector::SmallVector;
use crate::diagnostics::metadata::output_metadata;
pub use crate::errors::*;
const MAX_DESCRIPTION_WIDTH: usize = 80;
thread_local! {
static REGISTERED_DIAGNOSTICS: RefCell<ErrorMap> = {
RefCell::new(BTreeMap::new())
}
}
pub struct ErrorInfo {
pub description: Option<Name>,
pub use_site: Option<Span>,
}
pub type ErrorMap = BTreeMap<Name, ErrorInfo>;
fn with_registered_diagnostics<T, F>(f: F) -> T
where
F: FnOnce(&mut ErrorMap) -> T,
{
REGISTERED_DIAGNOSTICS.with(move |slot| f(&mut *slot.borrow_mut()))
}
pub fn expand_diagnostic_used<'cx>(
ecx: &'cx mut ExtCtxt,
span: Span,
token_tree: &[TokenTree],
) -> Box<dyn MacResult + 'cx> {
let code = match (token_tree.len(), token_tree.get(0)) {
(1, Some(&TokenTree::Token(_, token::Ident(code)))) => code,
_ => unreachable!(),
};
with_registered_diagnostics(|diagnostics| {
match diagnostics.get_mut(&code.name) {
Some(&mut ErrorInfo {
description: _,
use_site: Some(previous_span),
}) => {
ecx.struct_span_warn(span, &format!("diagnostic code {} already used", code))
.span_note(previous_span, "previous invocation")
.emit();
}
Some(ref mut info) => {
info.use_site = Some(span);
}
None => {
ecx.span_err(
span,
&format!("used diagnostic code {} not registered", code),
);
}
}
});
MacEager::expr(ecx.expr_tuple(span, Vec::new()))
}
pub fn expand_register_diagnostic<'cx>(
ecx: &'cx mut ExtCtxt,
span: Span,
token_tree: &[TokenTree],
) -> Box<dyn MacResult + 'cx> {
let (code, description) = match (
token_tree.len(),
token_tree.get(0),
token_tree.get(1),
token_tree.get(2),
) {
(1, Some(&TokenTree::Token(_, token::Ident(ref code))), None, None) => (code, None),
(
3,
Some(&TokenTree::Token(_, token::Ident(ref code))),
Some(&TokenTree::Token(_, token::Comma)),
Some(&TokenTree::Token(_, token::Literal(token::StrRaw(description, _), None))),
) => (code, Some(description)),
_ => unreachable!(),
};
description.map(|raw_msg| {
let msg = raw_msg.as_str();
if !msg.starts_with("\n") || !msg.ends_with("\n") {
ecx.span_err(
span,
&format!(
"description for error code {} doesn't start and end with a newline",
code
),
);
}
let is_url = |l: &str| l.starts_with("[") && l.contains("]:") && l.contains("http");
if msg
.lines()
.any(|line| line.len() > MAX_DESCRIPTION_WIDTH && !is_url(line))
{
ecx.span_err(
span,
&format!(
"description for error code {} contains a line longer than {} characters.\n\
if you're inserting a long URL use the footnote style to bypass this check.",
code, MAX_DESCRIPTION_WIDTH
),
);
}
});
with_registered_diagnostics(|diagnostics| {
let info = ErrorInfo {
description: description,
use_site: None,
};
if diagnostics.insert(code.name, info).is_some() {
ecx.span_err(
span,
&format!("diagnostic code {} already registered", code),
);
}
});
let sym = Ident::with_empty_ctxt(Symbol::gensym(&format!("__register_diagnostic_{}", code)));
MacEager::items(SmallVector::many(vec![ecx.item_mod(
span,
span,
sym,
Vec::new(),
Vec::new(),
)]))
}
pub fn expand_build_diagnostic_array<'cx>(
ecx: &'cx mut ExtCtxt,
span: Span,
token_tree: &[TokenTree],
) -> Box<dyn MacResult + 'cx> {
assert_eq!(token_tree.len(), 3);
let (crate_name, name) = match (&token_tree[0], &token_tree[2]) {
(
&TokenTree::Token(_, token::Ident(ref crate_name)),
&TokenTree::Token(_, token::Ident(ref name)),
) => (*&crate_name, name),
_ => unreachable!(),
};
if let Ok(target_triple) = env::var("CFG_COMPILER_HOST_TRIPLE") {
with_registered_diagnostics(|diagnostics| {
if let Err(e) =
output_metadata(ecx, &target_triple, &crate_name.name.as_str(), diagnostics)
{
ecx.span_bug(
span,
&format!(
"error writing metadata for triple `{}` and crate `{}`, error: {}, \
cause: {:?}",
target_triple,
crate_name,
e.to_string(),
e.source()
),
);
}
});
} else {
ecx.span_err(
span,
&format!(
"failed to write metadata for crate `{}` because $CFG_COMPILER_HOST_TRIPLE is not set",
crate_name),
);
}
let (count, expr) = with_registered_diagnostics(|diagnostics| {
let descriptions: Vec<P<ast::Expr>> = diagnostics
.iter()
.filter_map(|(&code, info)| {
info.description.map(|description| {
ecx.expr_tuple(
span,
vec![ecx.expr_str(span, code), ecx.expr_str(span, description)],
)
})
})
.collect();
(descriptions.len(), ecx.expr_vec(span, descriptions))
});
let static_ = ecx.lifetime(span, Ident::from_str("'static"));
let ty_str = ecx.ty_rptr(
span,
ecx.ty_ident(span, ecx.ident_of("str")),
Some(static_),
ast::Mutability::Immutable,
);
let ty = ecx.ty(
span,
ast::TyKind::Array(
ecx.ty(span, ast::TyKind::Tup(vec![ty_str.clone(), ty_str])),
ecx.expr_usize(span, count),
),
);
MacEager::items(SmallVector::many(vec![P(ast::Item {
ident: *name,
attrs: Vec::new(),
id: ast::DUMMY_NODE_ID,
node: ast::ItemKind::Const(ty, expr),
vis: ast::Visibility::Public,
span: span,
})]))
}