use std::borrow::Cow;
use crate::Span;
macro_rules! label {
($span:expr) => {
($span.to_owned().into(), None)
};
($span:expr, $message:expr) => {
($span.to_owned().into(), Some($message.into()))
};
($span:expr, $fmt:literal, $($arg:expr),+) => {
label!($span, format!($fmt, $($arg),+))
}
}
pub(crate) use label;
pub type CowStr = Cow<'static, str>;
pub type Label = (Span, Option<CowStr>);
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct SourceDiag {
pub severity: Severity,
pub stage: Stage,
pub message: CowStr,
source: Option<std::sync::Arc<dyn std::error::Error>>,
pub labels: Vec<Label>,
pub hints: Vec<CowStr>,
}
impl std::fmt::Display for SourceDiag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.message.fmt(f)
}
}
impl RichError for SourceDiag {
fn labels(&self) -> Cow<[Label]> {
self.labels.as_slice().into()
}
fn hints(&self) -> Cow<[CowStr]> {
self.hints.as_slice().into()
}
fn severity(&self) -> Severity {
self.severity
}
}
impl std::error::Error for SourceDiag {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_deref()
}
}
impl PartialEq for SourceDiag {
fn eq(&self, other: &Self) -> bool {
self.severity == other.severity && self.message == other.message
}
}
impl SourceDiag {
pub(crate) fn error(message: impl Into<CowStr>, label: Label, stage: Stage) -> Self {
Self {
severity: Severity::Error,
message: message.into(),
labels: vec![label],
hints: vec![],
source: None,
stage,
}
}
pub(crate) fn warning(message: impl Into<CowStr>, label: Label, stage: Stage) -> Self {
Self {
severity: Severity::Warning,
message: message.into(),
labels: vec![label],
hints: vec![],
source: None,
stage,
}
}
pub(crate) fn unlabeled(message: impl Into<CowStr>, severity: Severity, stage: Stage) -> Self {
Self {
severity,
stage,
message: message.into(),
source: None,
labels: vec![],
hints: vec![],
}
}
pub fn is_error(&self) -> bool {
self.severity == Severity::Error
}
pub fn is_warning(&self) -> bool {
self.severity == Severity::Warning
}
pub(crate) fn label(mut self, label: Label) -> Self {
self.add_label(label);
self
}
pub(crate) fn add_label(&mut self, label: Label) -> &mut Self {
self.labels.push(label);
self
}
pub(crate) fn hint(mut self, hint: impl Into<CowStr>) -> Self {
self.add_hint(hint);
self
}
pub(crate) fn add_hint(&mut self, hint: impl Into<CowStr>) -> &mut Self {
self.hints.push(hint.into());
self
}
pub(crate) fn set_source(mut self, source: impl std::error::Error + 'static) -> Self {
self.source = Some(std::sync::Arc::new(source));
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity {
Error,
Warning,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Stage {
Parse,
Analysis,
}
#[derive(Debug, Clone)]
pub struct SourceReport {
buf: Vec<SourceDiag>,
severity: Option<Severity>,
}
impl SourceReport {
pub(crate) fn empty() -> Self {
Self {
buf: Vec::new(),
severity: None,
}
}
pub(crate) fn push(&mut self, err: SourceDiag) {
debug_assert!(self.severity.is_none() || self.severity.is_some_and(|s| err.severity == s));
self.buf.push(err);
}
pub(crate) fn error(&mut self, w: SourceDiag) {
debug_assert_eq!(w.severity, Severity::Error);
self.push(w);
}
pub(crate) fn warn(&mut self, w: SourceDiag) {
debug_assert_eq!(w.severity, Severity::Warning);
self.push(w);
}
pub(crate) fn retain(&mut self, f: impl Fn(&SourceDiag) -> bool) {
self.buf.retain(f)
}
pub(crate) fn set_severity(&mut self, severity: Option<Severity>) {
debug_assert!(
severity.is_none()
|| severity.is_some_and(|s| self.buf.iter().all(|e| e.severity == s))
);
self.severity = severity;
}
pub fn severity(&self) -> Option<&Severity> {
self.severity.as_ref()
}
pub fn iter(&self) -> impl Iterator<Item = &SourceDiag> {
self.buf.iter()
}
pub fn errors(&self) -> impl Iterator<Item = &SourceDiag> {
self.iter().filter(|e| e.severity == Severity::Error)
}
pub fn warnings(&self) -> impl Iterator<Item = &SourceDiag> {
self.iter().filter(|e| e.severity == Severity::Warning)
}
pub fn has_errors(&self) -> bool {
match self.severity {
Some(Severity::Warning) => false,
Some(Severity::Error) => !self.buf.is_empty(),
None => self.errors().next().is_some(),
}
}
pub fn has_warnings(&self) -> bool {
match self.severity {
Some(Severity::Warning) => !self.buf.is_empty(),
Some(Severity::Error) => false,
None => self.warnings().next().is_some(),
}
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
pub fn unzip(self) -> (SourceReport, SourceReport) {
let (errors, warnings) = self.buf.into_iter().partition(SourceDiag::is_error);
(
Self {
buf: errors,
severity: Some(Severity::Error),
},
Self {
buf: warnings,
severity: Some(Severity::Warning),
},
)
}
pub fn remove_warnings(&mut self) {
self.buf.retain(SourceDiag::is_error)
}
pub fn into_vec(self) -> Vec<SourceDiag> {
self.buf
}
pub fn write(
&self,
file_name: &str,
source_code: &str,
color: bool,
w: &mut impl std::io::Write,
) -> std::io::Result<()> {
let mut cache = DummyCache::new(file_name, source_code);
for err in self.warnings() {
build_report(err, file_name, source_code, color).write(&mut cache, &mut *w)?;
}
for err in self.errors() {
build_report(err, file_name, source_code, color).write(&mut cache, &mut *w)?;
}
Ok(())
}
pub fn print(&self, file_name: &str, source_code: &str, color: bool) -> std::io::Result<()> {
self.write(file_name, source_code, color, &mut std::io::stdout().lock())
}
pub fn eprint(&self, file_name: &str, source_code: &str, color: bool) -> std::io::Result<()> {
self.write(file_name, source_code, color, &mut std::io::stderr().lock())
}
}
impl std::fmt::Display for SourceReport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for err in self.iter() {
err.fmt(f)?;
}
Ok(())
}
}
impl std::error::Error for SourceReport {}
#[derive(Debug)]
pub struct PassResult<T> {
output: Option<T>,
report: SourceReport,
}
impl<T> PassResult<T> {
pub(crate) fn new(output: Option<T>, report: SourceReport) -> Self {
Self { output, report }
}
pub fn has_output(&self) -> bool {
self.output.is_some()
}
pub fn report(&self) -> &SourceReport {
&self.report
}
pub fn is_valid(&self) -> bool {
self.has_output() && !self.report.has_errors()
}
pub fn output(&self) -> Option<&T> {
self.output.as_ref()
}
pub fn into_result(mut self) -> Result<(T, SourceReport), SourceReport> {
if !self.is_valid() {
return Err(self.report);
}
self.report.set_severity(Some(Severity::Warning));
Ok((self.output.unwrap(), self.report))
}
pub fn into_report(self) -> SourceReport {
self.report
}
pub fn take_output(&mut self) -> Option<T> {
self.output.take()
}
pub fn into_output(self) -> Option<T> {
self.output
}
pub fn unwrap_output(self) -> T {
self.output.unwrap()
}
pub fn into_tuple(self) -> (Option<T>, SourceReport) {
(self.output, self.report)
}
pub fn map<F, O>(self, f: F) -> PassResult<O>
where
F: FnOnce(T) -> O,
{
PassResult {
output: self.output.map(f),
report: self.report,
}
}
}
pub trait RichError: std::error::Error {
fn labels(&self) -> Cow<[Label]> {
Cow::Borrowed(&[])
}
fn hints(&self) -> Cow<[CowStr]> {
Cow::Borrowed(&[])
}
fn severity(&self) -> Severity {
Severity::Error
}
}
pub fn write_rich_error(
error: &dyn RichError,
file_name: &str,
source_code: &str,
color: bool,
w: impl std::io::Write,
) -> std::io::Result<()> {
let mut cache = DummyCache::new(file_name, source_code);
let report = build_report(error, file_name, source_code, color);
report.write(&mut cache, w)
}
fn build_report<'a>(
err: &'a dyn RichError,
file_name: &str,
src_code: &str,
color: bool,
) -> ariadne::Report<'a> {
use ariadne::{Color, ColorGenerator, Fmt, Label, Report};
let labels = err.labels();
let labels = labels
.iter()
.map(|(s, t)| (s.to_chars_span(src_code, file_name).range(), t))
.collect::<Vec<_>>();
let offset = labels.first().map(|l| l.0.start).unwrap_or_default();
let kind = match err.severity() {
Severity::Error => ariadne::ReportKind::Error,
Severity::Warning => ariadne::ReportKind::Warning,
};
let mut r =
Report::build(kind, (), offset).with_config(ariadne::Config::default().with_color(color));
if let Some(source) = err.source() {
let arrow_color = if color {
match kind {
ariadne::ReportKind::Error => Color::Red,
ariadne::ReportKind::Warning => Color::Yellow,
ariadne::ReportKind::Advice => Color::Fixed(147),
ariadne::ReportKind::Custom(_, c) => c,
}
} else {
Color::Default
};
let message = format!("{err}\n {} {source}", "╰▶ ".fg(arrow_color));
r.set_message(message);
} else {
r.set_message(err);
}
let mut c = ColorGenerator::new();
r.add_labels(labels.into_iter().enumerate().map(|(order, (span, text))| {
let mut l = Label::new(span)
.with_order(order as i32)
.with_color(c.next());
if let Some(text) = text {
l = l.with_message(text);
}
l
}));
let hints = err.hints();
let mut hints = hints.iter();
if let Some(help) = hints.next() {
r.set_help(help);
}
if let Some(note) = hints.next() {
r.set_note(note);
}
#[cfg(debug_assertions)]
if hints.next().is_some() {
tracing::warn!(
hints = ?err.hints(),
"this function only supports 2 hints, more will be ignored",
);
}
r.finish()
}
struct DummyCache(String, ariadne::Source);
impl DummyCache {
fn new(file_name: &str, src_code: &str) -> Self {
Self(file_name.into(), src_code.into())
}
}
impl ariadne::Cache<()> for DummyCache {
fn fetch(&mut self, _id: &()) -> Result<&ariadne::Source, Box<dyn std::fmt::Debug + '_>> {
Ok(&self.1)
}
fn display<'a>(&self, _id: &'a ()) -> Option<Box<dyn std::fmt::Display + 'a>> {
Some(Box::new(self.0.clone()))
}
}
pub trait Recover {
fn recover() -> Self;
}
impl<T> Recover for T
where
T: Default,
{
fn recover() -> Self {
Self::default()
}
}