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 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}