inf_wast/
error.rs

1use crate::lexer::LexError;
2use crate::token::Span;
3use std::fmt;
4use std::path::{Path, PathBuf};
5use unicode_width::UnicodeWidthStr;
6
7/// A convenience error type to tie together all the detailed errors produced by
8/// this crate.
9///
10/// This type can be created from a [`LexError`]. This also contains
11/// storage for file/text information so a nice error can be rendered along the
12/// same lines of rustc's own error messages (minus the color).
13///
14/// This type is typically suitable for use in public APIs for consumers of this
15/// crate.
16#[derive(Debug)]
17pub struct Error {
18    inner: Box<ErrorInner>,
19}
20
21#[derive(Debug)]
22struct ErrorInner {
23    text: Option<Text>,
24    file: Option<PathBuf>,
25    span: Span,
26    kind: ErrorKind,
27}
28
29#[derive(Debug)]
30struct Text {
31    line: usize,
32    col: usize,
33    snippet: String,
34}
35
36#[derive(Debug)]
37enum ErrorKind {
38    Lex(LexError),
39    Custom(String),
40}
41
42impl Error {
43    pub(crate) fn lex(span: Span, content: &str, kind: LexError) -> Error {
44        let mut ret = Error {
45            inner: Box::new(ErrorInner {
46                text: None,
47                file: None,
48                span,
49                kind: ErrorKind::Lex(kind),
50            }),
51        };
52        ret.set_text(content);
53        ret
54    }
55
56    pub(crate) fn parse(span: Span, content: &str, message: String) -> Error {
57        let mut ret = Error {
58            inner: Box::new(ErrorInner {
59                text: None,
60                file: None,
61                span,
62                kind: ErrorKind::Custom(message),
63            }),
64        };
65        ret.set_text(content);
66        ret
67    }
68
69    /// Creates a new error with the given `message` which is targeted at the
70    /// given `span`
71    ///
72    /// Note that you'll want to ensure that `set_text` or `set_path` is called
73    /// on the resulting error to improve the rendering of the error message.
74    pub fn new(span: Span, message: String) -> Error {
75        Error {
76            inner: Box::new(ErrorInner {
77                text: None,
78                file: None,
79                span,
80                kind: ErrorKind::Custom(message),
81            }),
82        }
83    }
84
85    /// Return the `Span` for this error.
86    pub fn span(&self) -> Span {
87        self.inner.span
88    }
89
90    /// To provide a more useful error this function can be used to extract
91    /// relevant textual information about this error into the error itself.
92    ///
93    /// The `contents` here should be the full text of the original file being
94    /// parsed, and this will extract a sub-slice as necessary to render in the
95    /// `Display` implementation later on.
96    pub fn set_text(&mut self, contents: &str) {
97        if self.inner.text.is_some() {
98            return;
99        }
100        self.inner.text = Some(Text::new(contents, self.inner.span));
101    }
102
103    /// To provide a more useful error this function can be used to set
104    /// the file name that this error is associated with.
105    ///
106    /// The `path` here will be stored in this error and later rendered in the
107    /// `Display` implementation.
108    pub fn set_path(&mut self, path: &Path) {
109        if self.inner.file.is_some() {
110            return;
111        }
112        self.inner.file = Some(path.to_path_buf());
113    }
114
115    /// Returns the underlying `LexError`, if any, that describes this error.
116    pub fn lex_error(&self) -> Option<&LexError> {
117        match &self.inner.kind {
118            ErrorKind::Lex(e) => Some(e),
119            _ => None,
120        }
121    }
122
123    /// Returns the underlying message, if any, that describes this error.
124    pub fn message(&self) -> String {
125        match &self.inner.kind {
126            ErrorKind::Lex(e) => e.to_string(),
127            ErrorKind::Custom(e) => e.clone(),
128        }
129    }
130}
131
132impl fmt::Display for Error {
133    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134        let err = match &self.inner.kind {
135            ErrorKind::Lex(e) => e as &dyn fmt::Display,
136            ErrorKind::Custom(e) => e as &dyn fmt::Display,
137        };
138        let text = match &self.inner.text {
139            Some(text) => text,
140            None => {
141                return write!(f, "{} at byte offset {}", err, self.inner.span.offset);
142            }
143        };
144        let file = self
145            .inner
146            .file
147            .as_ref()
148            .and_then(|p| p.to_str())
149            .unwrap_or("<anon>");
150        write!(
151            f,
152            "\
153{err}
154     --> {file}:{line}:{col}
155      |
156 {line:4} | {text}
157      | {marker:>0$}",
158            text.col + 1,
159            file = file,
160            line = text.line + 1,
161            col = text.col + 1,
162            err = err,
163            text = text.snippet,
164            marker = "^",
165        )
166    }
167}
168
169impl std::error::Error for Error {}
170
171impl Text {
172    fn new(content: &str, span: Span) -> Text {
173        let (line, col) = span.linecol_in(content);
174        let contents = content.lines().nth(line).unwrap_or("");
175        let mut snippet = String::new();
176        for ch in contents.chars() {
177            match ch {
178                // Replace tabs with spaces to render consistently
179                '\t' => {
180                    snippet.push_str("    ");
181                }
182                // these codepoints change how text is rendered so for clarity
183                // in error messages they're dropped.
184                '\u{202a}' | '\u{202b}' | '\u{202d}' | '\u{202e}' | '\u{2066}' | '\u{2067}'
185                | '\u{2068}' | '\u{206c}' | '\u{2069}' => {}
186
187                c => snippet.push(c),
188            }
189        }
190        // Use the `unicode-width` crate to figure out how wide the snippet, up
191        // to our "column", actually is. That'll tell us how many spaces to
192        // place before the `^` character that points at the problem
193        let col = snippet.get(..col).map(|s| s.width()).unwrap_or(col);
194        Text { line, col, snippet }
195    }
196}