use crate::ast::*;
use crate::formatter::{Formatter, IntoFormatter};
pub use annotate_snippets::Level;
use itertools::Itertools;
use macros::VariantIndexArity;
use petgraph::algo::dijkstra::dijkstra;
use petgraph::prelude::DiGraphMap;
use std::cmp::{Ord, PartialOrd};
use std::collections::{HashMap, HashSet};
#[macro_export]
macro_rules! register_error {
($ctx:expr, crate($krate:expr), $span: expr, $($fmt:tt)*) => {{
let msg = format!($($fmt)*);
$ctx.span_err($krate, $span, &msg)
}};
($ctx:expr, $span: expr, $($fmt:tt)*) => {{
let msg = format!($($fmt)*);
$ctx.span_err($span, &msg)
}};
}
pub use register_error;
#[macro_export]
macro_rules! raise_error {
($($tokens:tt)*) => {{
return Err(register_error!($($tokens)*));
}};
}
pub use raise_error;
#[macro_export]
macro_rules! error_assert {
($ctx:expr, $span: expr, $b: expr) => {
if !$b {
$crate::errors::raise_error!($ctx, $span, "assertion failure: {:?}", stringify!($b));
}
};
($ctx:expr, $span: expr, $b: expr, $($fmt:tt)*) => {
if !$b {
$crate::errors::raise_error!($ctx, $span, $($fmt)*);
}
};
}
pub use error_assert;
#[macro_export]
macro_rules! sanity_check {
($ctx:expr, $span: expr, $b: expr) => {
if !$b {
$crate::errors::register_error!(
$ctx,
$span,
"assertion failure: {:?}",
stringify!($b)
);
}
};
($ctx:expr, $span: expr, $b: expr, $($fmt:tt)*) => {
if !$b {
$crate::errors::register_error!($ctx, $span, $($fmt)*);
}
};
}
pub use sanity_check;
#[derive(Debug)]
pub struct Error {
pub span: Span,
pub msg: String,
}
impl Error {
pub(crate) fn render(&self, krate: &TranslatedCrate, level: Level) -> String {
use annotate_snippets::*;
let msg_indent = format!("{level:?}: ").len();
let mut msg = self
.msg
.replace('\n', &format!("\n{}", str::repeat(" ", msg_indent)));
let span = self.span.span;
let origin;
let message = if let Some(file) = krate.files.get(span.file_id) {
if let Some(source) = &file.contents {
origin = format!("{}", file.name);
let snippet = Snippet::source(source)
.origin(&origin)
.fold(true)
.annotation(level.span(span.to_byte_range(source)));
level.title(&msg).snippet(snippet)
} else {
msg = format!(
"{msg}\n --> {}:{}:{}",
file.name,
span.beg.line,
span.beg.col + 1
);
level.title(&msg)
}
} else {
level.title(&msg)
};
let out = Renderer::styled().render(message).to_string();
out
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct DepSource {
pub src_id: AnyTransId,
pub span: Option<Span>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, VariantIndexArity)]
enum DepNode {
External(AnyTransId),
Local(AnyTransId, Span),
}
struct DepGraph {
dgraph: DiGraphMap<DepNode, ()>,
}
impl DepGraph {
fn new() -> Self {
DepGraph {
dgraph: DiGraphMap::new(),
}
}
fn insert_node(&mut self, n: DepNode) {
if !self.dgraph.contains_node(n) {
self.dgraph.add_node(n);
}
}
fn insert_edge(&mut self, from: DepNode, to: DepNode) {
self.insert_node(from);
self.insert_node(to);
if !self.dgraph.contains_edge(from, to) {
self.dgraph.add_edge(from, to, ());
}
}
}
impl std::fmt::Display for DepGraph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
for (from, to, _) in self.dgraph.all_edges() {
writeln!(f, "{from:?} -> {to:?}")?
}
Ok(())
}
}
pub struct ErrorCtx {
pub continue_on_failure: bool,
pub error_on_warnings: bool,
pub external_decls_with_errors: HashSet<AnyTransId>,
pub ignored_failed_decls: HashSet<AnyTransId>,
external_dep_graph: DepGraph,
pub def_id: Option<AnyTransId>,
pub def_id_is_local: bool,
pub error_count: usize,
}
impl ErrorCtx {
pub fn new(continue_on_failure: bool, error_on_warnings: bool) -> Self {
Self {
continue_on_failure,
error_on_warnings,
external_decls_with_errors: HashSet::new(),
ignored_failed_decls: HashSet::new(),
external_dep_graph: DepGraph::new(),
def_id: None,
def_id_is_local: false,
error_count: 0,
}
}
pub fn continue_on_failure(&self) -> bool {
self.continue_on_failure
}
pub(crate) fn has_errors(&self) -> bool {
self.error_count > 0
}
pub fn display_error(
&self,
krate: &TranslatedCrate,
span: Span,
level: Level,
msg: String,
) -> Error {
let error = Error { span, msg };
anstream::eprintln!("{}\n", error.render(krate, level));
error
}
pub fn span_err_no_register(&self, krate: &TranslatedCrate, span: Span, msg: String) -> Error {
let level = if self.error_on_warnings {
Level::Error
} else {
Level::Warning
};
self.display_error(krate, span, level, msg)
}
pub fn span_err(&mut self, krate: &TranslatedCrate, span: Span, msg: &str) -> Error {
let err = self.span_err_no_register(krate, span, msg.to_string());
self.error_count += 1;
if !self.def_id_is_local
&& let Some(id) = self.def_id
&& self.external_decls_with_errors.insert(id)
{
self.report_external_dep_error(krate, id);
}
if !self.continue_on_failure() {
panic!("{msg}");
}
err
}
pub fn ignore_failed_decl(&mut self, id: AnyTransId) {
self.ignored_failed_decls.insert(id);
}
pub fn register_dep_source(
&mut self,
src: &Option<DepSource>,
item_id: AnyTransId,
is_local: bool,
) {
if let Some(src) = src
&& src.src_id != item_id
&& !is_local
{
let src_node = DepNode::External(item_id);
self.external_dep_graph.insert_node(src_node);
let tgt_node = match src.span {
Some(span) => DepNode::Local(src.src_id, span),
None => DepNode::External(src.src_id),
};
self.external_dep_graph.insert_edge(src_node, tgt_node)
}
}
pub fn report_external_dep_error(&self, krate: &TranslatedCrate, id: AnyTransId) {
use annotate_snippets::*;
let graph = &self.external_dep_graph;
let reachable = dijkstra(&graph.dgraph, DepNode::External(id), None, &mut |_| 1);
trace!("id: {:?}\nreachable:\n{:?}", id, reachable);
let by_file: HashMap<FileId, Vec<Span>> = reachable
.iter()
.filter_map(|(n, _)| match n {
DepNode::External(_) => None,
DepNode::Local(_, span) => Some(*span),
})
.into_group_map_by(|span| span.span.file_id);
let mut by_file: Vec<(FileId, _, _, Vec<Span>)> = by_file
.into_iter()
.filter_map(|(file_id, mut spans)| {
spans.sort(); let file = krate.files.get(file_id)?;
let source = file.contents.as_ref()?;
let file_name = file.name.to_string();
Some((file_id, file_name, source, spans))
})
.collect();
by_file.sort_by_key(|(file_id, ..)| *file_id);
let level = Level::Note;
let snippets = by_file.iter().map(|(_, origin, source, spans)| {
Snippet::source(source)
.origin(&origin)
.fold(true)
.annotations(
spans
.iter()
.map(|span| level.span(span.span.to_byte_range(source))),
)
});
let msg = format!(
"the error occurred when translating `{}`, \
which is (transitively) used at the following location(s):",
krate.into_fmt().format_object(id)
);
let message = level.title(&msg).snippets(snippets);
let out = Renderer::styled().render(message).to_string();
anstream::eprintln!("{}", out);
}
}