sailfish_compiler/
error.rs

1use std::env;
2use std::fmt;
3use std::fs;
4use std::io;
5use std::path::{Path, PathBuf};
6
7#[non_exhaustive]
8#[derive(Debug)]
9pub enum ErrorKind {
10    FmtError(fmt::Error),
11    IoError(io::Error),
12    RustSyntaxError(syn::Error),
13    ConfigError(String),
14    ParseError(String),
15    AnalyzeError(String),
16    Unimplemented(String),
17    Other(String),
18}
19
20impl fmt::Display for ErrorKind {
21    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22        match *self {
23            ErrorKind::FmtError(ref e) => e.fmt(f),
24            ErrorKind::IoError(ref e) => e.fmt(f),
25            ErrorKind::RustSyntaxError(ref e) => write!(f, "Rust Syntax Error ({})", e),
26            ErrorKind::ConfigError(ref e) => write!(f, "Invalid configuration ({})", e),
27            ErrorKind::ParseError(ref msg) => write!(f, "Parse error ({})", msg),
28            ErrorKind::AnalyzeError(ref msg) => write!(f, "Analyzation error ({})", msg),
29            ErrorKind::Unimplemented(ref msg) => f.write_str(msg),
30            ErrorKind::Other(ref msg) => f.write_str(msg),
31        }
32    }
33}
34
35macro_rules! impl_errorkind_conversion {
36    ($source:ty, $kind:ident, $conv:expr, [ $($lifetimes:tt),* ]) => {
37        impl<$($lifetimes),*> From<$source> for ErrorKind {
38            #[inline]
39            fn from(other: $source) -> Self {
40                ErrorKind::$kind($conv(other))
41            }
42        }
43    };
44    ($source:ty, $kind:ident) => {
45        impl_errorkind_conversion!($source, $kind, std::convert::identity, []);
46    }
47}
48
49impl_errorkind_conversion!(fmt::Error, FmtError);
50impl_errorkind_conversion!(io::Error, IoError);
51impl_errorkind_conversion!(syn::Error, RustSyntaxError);
52impl_errorkind_conversion!(String, Other);
53impl_errorkind_conversion!(&'a str, Other, |s: &str| s.to_owned(), ['a]);
54
55#[derive(Debug, Default)]
56pub struct Error {
57    pub(crate) source_file: Option<PathBuf>,
58    pub(crate) source: Option<String>,
59    pub(crate) offset: Option<usize>,
60    pub(crate) chains: Vec<ErrorKind>,
61}
62
63impl Error {
64    pub fn from_kind(kind: ErrorKind) -> Self {
65        Self {
66            chains: vec![kind],
67            ..Self::default()
68        }
69    }
70
71    pub fn kind(&self) -> &ErrorKind {
72        self.chains.last().unwrap()
73    }
74
75    pub fn iter(&self) -> impl Iterator<Item = &ErrorKind> {
76        self.chains.iter().rev()
77    }
78}
79
80impl<T> From<T> for Error
81where
82    ErrorKind: From<T>,
83{
84    fn from(other: T) -> Self {
85        Self::from_kind(ErrorKind::from(other))
86    }
87}
88
89impl fmt::Display for Error {
90    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91        let source = match (self.source.as_ref(), self.source_file.as_deref()) {
92            (Some(s), _) => Some(s.to_owned()),
93            (None, Some(f)) => fs::read_to_string(f).ok(),
94            (None, None) => None,
95        };
96
97        writeln!(f, "{}", self.chains.last().unwrap())?;
98
99        for e in self.chains.iter().rev().skip(1) {
100            writeln!(f, "caused by: {}", e)?;
101        }
102
103        f.write_str("\n")?;
104
105        if let Some(ref source_file) = self.source_file {
106            let source_file =
107                if env::var("SAILFISH_INTEGRATION_TESTS").map_or(false, |s| s == "1") {
108                    match source_file.file_name() {
109                        Some(f) => Path::new(f),
110                        None => Path::new(""),
111                    }
112                } else {
113                    source_file
114                };
115            writeln!(f, "file: {}", source_file.display())?;
116        }
117
118        if let (Some(ref source), Some(offset)) = (source, self.offset) {
119            let (lineno, colno) = into_line_column(source, offset);
120            writeln!(f, "position: line {}, column {}\n", lineno, colno)?;
121
122            // TODO: display adjacent lines
123            let line = source.lines().nth(lineno - 1).unwrap();
124            let lpad = count_digits(lineno);
125
126            writeln!(f, "{:<lpad$} |", "", lpad = lpad)?;
127            writeln!(f, "{} | {}", lineno, line)?;
128            writeln!(
129                f,
130                "{:<lpad$} | {:<rpad$}^",
131                "",
132                "",
133                lpad = lpad,
134                rpad = colno - 1
135            )?;
136        }
137
138        Ok(())
139    }
140}
141
142impl std::error::Error for Error {}
143
144pub trait ResultExt<T> {
145    fn chain_err<F, EK>(self, kind: F) -> Result<T, Error>
146    where
147        F: FnOnce() -> EK,
148        EK: Into<ErrorKind>;
149}
150
151impl<T> ResultExt<T> for Result<T, Error> {
152    fn chain_err<F, EK>(self, kind: F) -> Result<T, Error>
153    where
154        F: FnOnce() -> EK,
155        EK: Into<ErrorKind>,
156    {
157        self.map_err(|mut e| {
158            e.chains.push(kind().into());
159            e
160        })
161    }
162}
163
164impl<T, E: Into<ErrorKind>> ResultExt<T> for Result<T, E> {
165    fn chain_err<F, EK>(self, kind: F) -> Result<T, Error>
166    where
167        F: FnOnce() -> EK,
168        EK: Into<ErrorKind>,
169    {
170        self.map_err(|e| {
171            let mut e = Error::from(e.into());
172            e.chains.push(kind().into());
173            e
174        })
175    }
176}
177
178fn into_line_column(source: &str, offset: usize) -> (usize, usize) {
179    assert!(
180        offset <= source.len(),
181        "Internal error: error position offset overflow (error code: 56066)"
182    );
183    let mut lineno = 1;
184    let mut colno = 1;
185    let mut current = 0;
186
187    for line in source.lines() {
188        let end = current + line.len() + 1;
189        if offset < end {
190            colno = offset - current + 1;
191            break;
192        }
193
194        lineno += 1;
195        current = end;
196    }
197
198    (lineno, colno)
199}
200
201fn count_digits(n: usize) -> usize {
202    let mut current = 10;
203    let mut digits = 1;
204
205    while current <= n {
206        current *= 10;
207        digits += 1;
208    }
209
210    digits
211}
212
213macro_rules! make_error {
214    ($kind:expr) => {
215        $crate::Error::from_kind($kind)
216    };
217    ($kind:expr, $($remain:tt)*) => {{
218        #[allow(unused_mut)]
219        let mut err = $crate::Error::from_kind($kind);
220        make_error!(@opt err $($remain)*);
221        err
222    }};
223    (@opt $var:ident $key:ident = $value:expr, $($remain:tt)*) => {
224        $var.$key = Some($value.into());
225        make_error!(@opt $var $($remain)*);
226    };
227    (@opt $var:ident $key:ident = $value:expr) => {
228        $var.$key = Some($value.into());
229    };
230    (@opt $var:ident $key:ident, $($remain:tt)*) => {
231        $var.$key = Some($key);
232        make_error!(@opt $var $($remain)*);
233    };
234    (@opt $var:ident $key:ident) => {
235        $var.$key = Some($key);
236    };
237    (@opt $var:ident) => {};
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243    use pretty_assertions::assert_eq;
244
245    #[test]
246    fn display_error() {
247        let mut err = make_error!(
248            ErrorKind::AnalyzeError("mismatched types".to_owned()),
249            source_file = PathBuf::from("apple.rs"),
250            source = "fn func() {\n    1\n}".to_owned(),
251            offset = 16usize
252        );
253        err.chains.push(ErrorKind::Other("some error".to_owned()));
254        assert!(matches!(err.kind(), &ErrorKind::Other(_)));
255        assert_eq!(
256            err.to_string(),
257            r#"some error
258caused by: Analyzation error (mismatched types)
259
260file: apple.rs
261position: line 2, column 5
262
263  |
2642 |     1
265  |     ^
266"#
267        );
268    }
269}