257 lines
6.5 KiB
Rust
257 lines
6.5 KiB
Rust
use std::fmt;
|
|
use std::fs;
|
|
use std::io;
|
|
use std::path::PathBuf;
|
|
|
|
#[non_exhaustive]
|
|
#[derive(Debug)]
|
|
pub enum ErrorKind {
|
|
FmtError(fmt::Error),
|
|
IoError(io::Error),
|
|
RustSyntaxError(syn::Error),
|
|
ParseError(String),
|
|
AnalyzeError(String),
|
|
Unimplemented(String),
|
|
Other(String),
|
|
}
|
|
|
|
impl fmt::Display for ErrorKind {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match *self {
|
|
ErrorKind::FmtError(ref e) => e.fmt(f),
|
|
ErrorKind::IoError(ref e) => e.fmt(f),
|
|
ErrorKind::RustSyntaxError(ref e) => write!(f, "Rust Syntax Error: {}", e),
|
|
ErrorKind::ParseError(ref msg) => write!(f, "Parse error: {}", msg),
|
|
ErrorKind::AnalyzeError(ref msg) => write!(f, "Analyzation error: {}", msg),
|
|
ErrorKind::Unimplemented(ref msg) => f.write_str(&**msg),
|
|
ErrorKind::Other(ref msg) => f.write_str(&**msg),
|
|
}
|
|
}
|
|
}
|
|
|
|
macro_rules! impl_errorkind_conversion {
|
|
($source:ty, $kind:ident, $conv:expr, [ $($lifetimes:tt),* ]) => {
|
|
impl<$($lifetimes),*> From<$source> for ErrorKind {
|
|
#[inline]
|
|
fn from(other: $source) -> Self {
|
|
ErrorKind::$kind($conv(other))
|
|
}
|
|
}
|
|
};
|
|
($source:ty, $kind:ident) => {
|
|
impl_errorkind_conversion!($source, $kind, std::convert::identity, []);
|
|
}
|
|
}
|
|
|
|
impl_errorkind_conversion!(fmt::Error, FmtError);
|
|
impl_errorkind_conversion!(io::Error, IoError);
|
|
impl_errorkind_conversion!(syn::Error, RustSyntaxError);
|
|
impl_errorkind_conversion!(String, Other);
|
|
impl_errorkind_conversion!(&'a str, Other, |s: &str| s.to_owned(), ['a]);
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct Error {
|
|
pub(crate) source_file: Option<PathBuf>,
|
|
pub(crate) source: Option<String>,
|
|
pub(crate) offset: Option<usize>,
|
|
pub(crate) chains: Vec<ErrorKind>,
|
|
}
|
|
|
|
impl Error {
|
|
pub fn from_kind(kind: ErrorKind) -> Self {
|
|
Self {
|
|
chains: vec![kind],
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
pub fn kind(&self) -> &ErrorKind {
|
|
self.chains.last().unwrap()
|
|
}
|
|
|
|
pub fn iter(&self) -> impl Iterator<Item = &ErrorKind> {
|
|
self.chains.iter().rev()
|
|
}
|
|
}
|
|
|
|
impl<T> From<T> for Error
|
|
where
|
|
ErrorKind: From<T>,
|
|
{
|
|
fn from(other: T) -> Self {
|
|
Self::from_kind(ErrorKind::from(other))
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Error {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let source = match (self.source.as_ref(), self.source_file.as_deref()) {
|
|
(Some(s), _) => Some(s.to_owned()),
|
|
(None, Some(f)) => fs::read_to_string(f).ok(),
|
|
(None, None) => None,
|
|
};
|
|
|
|
writeln!(f, "{}", self.chains.last().unwrap())?;
|
|
|
|
for e in self.chains.iter().rev().skip(1) {
|
|
writeln!(f, "Caused by: {}", e)?;
|
|
}
|
|
|
|
f.write_str("\n")?;
|
|
|
|
if let Some(ref source_file) = self.source_file {
|
|
writeln!(f, "file: {}", source_file.display())?;
|
|
}
|
|
|
|
if let (Some(ref source), Some(offset)) = (source, self.offset) {
|
|
let (lineno, colno) = into_line_column(source, offset);
|
|
writeln!(f, "position: line {}, column {}\n", lineno, colno)?;
|
|
|
|
// TODO: display adjacent lines
|
|
let line = source.lines().nth(lineno - 1).unwrap();
|
|
let lpad = count_digits(lineno);
|
|
|
|
writeln!(f, "{:<lpad$} |", "", lpad = lpad)?;
|
|
writeln!(f, "{} | {}", lineno, line)?;
|
|
writeln!(
|
|
f,
|
|
"{:<lpad$} | {:<rpad$}^",
|
|
"",
|
|
"",
|
|
lpad = lpad,
|
|
rpad = colno - 1
|
|
)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for Error {}
|
|
|
|
pub trait ResultExt<T> {
|
|
fn chain_err<F, EK>(self, kind: F) -> Result<T, Error>
|
|
where
|
|
F: FnOnce() -> EK,
|
|
EK: Into<ErrorKind>;
|
|
}
|
|
|
|
impl<T> ResultExt<T> for Result<T, Error> {
|
|
fn chain_err<F, EK>(self, kind: F) -> Result<T, Error>
|
|
where
|
|
F: FnOnce() -> EK,
|
|
EK: Into<ErrorKind>,
|
|
{
|
|
self.map_err(|mut e| {
|
|
e.chains.push(kind().into());
|
|
e
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<T, E: Into<ErrorKind>> ResultExt<T> for Result<T, E> {
|
|
fn chain_err<F, EK>(self, kind: F) -> Result<T, Error>
|
|
where
|
|
F: FnOnce() -> EK,
|
|
EK: Into<ErrorKind>,
|
|
{
|
|
self.map_err(|e| {
|
|
let mut e = Error::from(e.into());
|
|
e.chains.push(kind().into());
|
|
e
|
|
})
|
|
}
|
|
}
|
|
|
|
fn into_line_column(source: &str, offset: usize) -> (usize, usize) {
|
|
assert!(
|
|
offset <= source.len(),
|
|
"Internal error: error position offset overflow (error code: 56066)"
|
|
);
|
|
let mut lineno = 1;
|
|
let mut colno = 1;
|
|
let mut current = 0;
|
|
|
|
for line in source.lines() {
|
|
let end = current + line.len() + 1;
|
|
if offset < end {
|
|
colno = offset - current + 1;
|
|
break;
|
|
}
|
|
|
|
lineno += 1;
|
|
current = end;
|
|
}
|
|
|
|
(lineno, colno)
|
|
}
|
|
|
|
fn count_digits(n: usize) -> usize {
|
|
let mut current = 10;
|
|
let mut digits = 1;
|
|
|
|
while current <= n {
|
|
current *= 10;
|
|
digits += 1;
|
|
}
|
|
|
|
digits
|
|
}
|
|
|
|
macro_rules! make_error {
|
|
($kind:expr) => {
|
|
$crate::Error::from_kind($kind)
|
|
};
|
|
($kind:expr, $($remain:tt)*) => {{
|
|
#[allow(unused_mut)]
|
|
let mut err = $crate::Error::from_kind($kind);
|
|
make_error!(@opt err $($remain)*);
|
|
err
|
|
}};
|
|
(@opt $var:ident $key:ident = $value:expr, $($remain:tt)*) => {
|
|
$var.$key = Some($value.into());
|
|
make_error!(@opt $var $($remain)*);
|
|
};
|
|
(@opt $var:ident $key:ident = $value:expr) => {
|
|
$var.$key = Some($value.into());
|
|
};
|
|
(@opt $var:ident $key:ident, $($remain:tt)*) => {
|
|
$var.$key = Some($key);
|
|
make_error!(@opt $var $($remain)*);
|
|
};
|
|
(@opt $var:ident $key:ident) => {
|
|
$var.$key = Some($key);
|
|
};
|
|
(@opt $var:ident) => {};
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
#[test]
|
|
fn display_error() {
|
|
let mut err = make_error!(
|
|
ErrorKind::AnalyzeError("mismatched types".to_owned()),
|
|
source_file = PathBuf::from("apple.rs"),
|
|
source = "fn func() {\n 1\n}".to_owned(),
|
|
offset = 16usize
|
|
);
|
|
err.chains.push(ErrorKind::Other("some error".to_owned()));
|
|
assert_eq!(
|
|
err.to_string(),
|
|
r#"some error
|
|
Caused by: Analyzation error: mismatched types
|
|
|
|
file: apple.rs
|
|
position: line 2, column 5
|
|
|
|
|
|
|
2 | 1
|
|
| ^
|
|
"#
|
|
);
|
|
}
|
|
}
|