#[cfg(doc)]
use crate::{
arg_err, arg_err_noloc, arg_error, arg_error_noloc, input_err, input_err_noloc, input_error,
input_error_noloc, type_to_trait, verify_err, verify_err_noloc, verify_error,
verify_error_noloc,
};
use std::{
backtrace::{Backtrace, BacktraceStatus},
fmt::Display,
};
use downcast_rs::{Downcast, impl_downcast};
use thiserror::Error;
use crate::{
context::Context,
location::{Located, Location},
printable::{Printable, State},
utils::trait_cast::any_to_trait,
};
pub trait AnyError: std::error::Error + Send + Sync + 'static + Downcast {}
impl<T: std::error::Error + Send + Sync + 'static> AnyError for T {}
impl_downcast!(AnyError);
#[derive(Debug, Error)]
pub enum ErrorKind {
#[error("invalid input program")]
InvalidInput,
#[error("verification failed")]
VerificationFailed,
#[error("invalid argument")]
InvalidArgument,
}
#[derive(Debug)]
pub struct Error {
pub kind: ErrorKind,
pub err: Box<dyn AnyError>,
pub loc: Location,
pub backtrace: Backtrace,
}
impl std::error::Error for Error {}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Compilation error: {}.\n{}", self.kind, self.err)
}
}
impl Printable for Error {
fn fmt(
&self,
ctx: &Context,
_state: &State,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
if self.loc.is_unknown() {
writeln!(f, "Compilation error: {}.", self.kind,)
} else {
writeln!(
f,
"[{}] Compilation error: {}.",
self.loc.disp(ctx),
self.kind,
)
}?;
if let Some(self_val) = self.err.downcast_ref::<Error>() {
write!(f, "{}", self_val.disp(ctx))?;
} else if let Some(self_val) = any_to_trait::<dyn Printable>((*self.err).as_any()) {
write!(f, "{}", self_val.disp(ctx))?;
} else {
write!(f, "{}", self.err)?;
if self.backtrace.status() == BacktraceStatus::Captured {
write!(f, "\nError backtrace:\n{}", self.backtrace)?;
}
}
Ok(())
}
}
impl Located for Error {
fn loc(&self) -> Location {
self.loc.clone()
}
fn set_loc(&mut self, loc: Location) {
self.loc = loc;
}
}
pub type Result<T> = std::result::Result<T, Error>;
impl<T: Printable> Printable for Result<T> {
fn fmt(
&self,
ctx: &Context,
state: &State,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
match self {
Ok(val) => val.fmt(ctx, state, f),
Err(err) => Printable::fmt(err, ctx, state, f),
}
}
}
pub trait ExpectOk<T> {
#[track_caller]
fn expect_ok(self, ctx: &Context) -> T;
}
impl<T> ExpectOk<T> for Result<T> {
fn expect_ok(self, ctx: &Context) -> T {
match self {
Ok(val) => val,
Err(err) => {
panic!("{}", err.disp(ctx))
}
}
}
}
#[doc(hidden)]
#[derive(Debug, Error)]
#[error("{0}")]
pub struct StringError(pub String);
#[macro_export]
macro_rules! create_error {
($loc: expr, $kind: expr, $str: literal $($t:tt)*) => {
$crate::create_error!($loc, $kind, $crate::result::StringError(format!($str $($t)*)))
};
($loc: expr, $kind: expr, $err: expr) => {
$crate::result::Error {
kind: $kind,
err: Box::new($err),
loc: $loc,
backtrace: std::backtrace::Backtrace::capture(),
}
};
}
#[macro_export]
macro_rules! create_err {
($loc: expr, $kind: expr, $str: literal $($t:tt)*) => {
$crate::create_err!($loc, $kind, $crate::result::StringError(format!($str $($t)*)))
};
($loc: expr, $kind: expr, $err: expr) => {
Err($crate::create_error!($loc, $kind, $err))
};
}
#[macro_export]
macro_rules! verify_error {
($loc: expr, $($t:tt)*) => {
$crate::create_error!($loc, $crate::result::ErrorKind::VerificationFailed, $($t)*)
}
}
#[macro_export]
macro_rules! verify_err {
($loc: expr, $($t:tt)*) => {
$crate::create_err!($loc, $crate::result::ErrorKind::VerificationFailed, $($t)*)
}
}
#[macro_export]
macro_rules! input_error {
($loc: expr, $($t:tt)*) => {
$crate::create_error!($loc, $crate::result::ErrorKind::InvalidInput, $($t)*)
}
}
#[macro_export]
macro_rules! input_err {
($loc: expr, $($t:tt)*) => {
$crate::create_err!($loc, $crate::result::ErrorKind::InvalidInput, $($t)*)
}
}
#[macro_export]
macro_rules! arg_error {
($loc: expr, $($t:tt)*) => {
$crate::create_error!($loc, $crate::result::ErrorKind::InvalidArgument, $($t)*)
}
}
#[macro_export]
macro_rules! arg_err {
($loc: expr, $($t:tt)*) => {
$crate::create_err!($loc, $crate::result::ErrorKind::InvalidArgument, $($t)*)
}
}
#[macro_export]
macro_rules! verify_error_noloc {
($($t:tt)*) => {
$crate::create_error!($crate::location::Location::Unknown, $crate::result::ErrorKind::VerificationFailed, $($t)*)
}
}
#[macro_export]
macro_rules! verify_err_noloc {
($($t:tt)*) => {
$crate::create_err!($crate::location::Location::Unknown, $crate::result::ErrorKind::VerificationFailed, $($t)*)
}
}
#[macro_export]
macro_rules! input_error_noloc {
($($t:tt)*) => {
$crate::create_error!($crate::location::Location::Unknown, $crate::result::ErrorKind::InvalidInput, $($t)*)
}
}
#[macro_export]
macro_rules! input_err_noloc {
($($t:tt)*) => {
$crate::create_err!($crate::location::Location::Unknown, $crate::result::ErrorKind::InvalidInput, $($t)*)
}
}
#[macro_export]
macro_rules! arg_error_noloc {
($($t:tt)*) => {
$crate::create_error!($crate::location::Location::Unknown, $crate::result::ErrorKind::InvalidArgument, $($t)*)
}
}
#[macro_export]
macro_rules! arg_err_noloc {
($($t:tt)*) => {
$crate::create_err!($crate::location::Location::Unknown, $crate::result::ErrorKind::InvalidArgument, $($t)*)
}
}
#[cfg(test)]
mod tests {
use combine::stream::position::{Positioner, SourcePosition};
use expect_test::expect;
use thiserror::Error;
use crate::{
context::Context,
location::{Location, Source},
printable::Printable,
type_to_trait,
};
#[derive(Debug, Error)]
#[error("Test error")]
pub struct TestErr;
#[derive(Debug, Error)]
pub struct PrintableErr(String);
impl std::fmt::Display for PrintableErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Display: {}", self.0)
}
}
impl Printable for PrintableErr {
fn fmt(
&self,
_ctx: &Context,
_state: &crate::printable::State,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
write!(f, "Printable: {}", self.0)
}
}
type_to_trait!(PrintableErr, Printable);
#[test]
fn wrapped_err() {
let ctx = &mut Context::new();
let src = Source::new_from_file(ctx, "/tmp/test.pliron".into());
let pos1 = SourcePosition::default();
let loc1 = Location::SrcPos { src, pos: pos1 };
let mut pos2 = SourcePosition::default();
pos2.update(&' ');
let loc2 = Location::SrcPos { pos: pos2, src };
let res = input_error!(loc2, TestErr);
let wrapped_res = input_error!(loc1, res);
let expected_err_msg = expect![[r#"
["/tmp/test.pliron": line: 1, column: 1] Compilation error: invalid input program.
["/tmp/test.pliron": line: 1, column: 2] Compilation error: invalid input program.
Test error"#]];
let actual_err = wrapped_res.disp(ctx).to_string();
expected_err_msg.assert_eq(&actual_err);
}
#[test]
fn printable_err() {
let ctx = &mut Context::new();
let src = Source::new_from_file(ctx, "/tmp/test.pliron".into());
let pos = SourcePosition::default();
let loc = Location::SrcPos { src, pos };
let res = input_error!(loc, PrintableErr("Test error".to_string()));
let expected_err_msg = expect![[r#"
["/tmp/test.pliron": line: 1, column: 1] Compilation error: invalid input program.
Printable: Test error"#]];
let actual_err = res.disp(ctx).to_string();
expected_err_msg.assert_eq(&actual_err);
}
}