Coverage Report

Created: 2025-01-09 07:53

/src/wasm-tools/crates/wast/src/error.rs
Line
Count
Source (jump to first uncovered line)
1
use crate::lexer::LexError;
2
use crate::token::Span;
3
use std::fmt;
4
use std::path::{Path, PathBuf};
5
use 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)]
17
pub struct Error {
18
    inner: Box<ErrorInner>,
19
}
20
21
#[derive(Debug)]
22
struct ErrorInner {
23
    text: Option<Text>,
24
    file: Option<PathBuf>,
25
    span: Span,
26
    kind: ErrorKind,
27
}
28
29
#[derive(Debug)]
30
struct Text {
31
    line: usize,
32
    col: usize,
33
    snippet: String,
34
}
35
36
#[derive(Debug)]
37
enum ErrorKind {
38
    Lex(LexError),
39
    Custom(String),
40
}
41
42
impl Error {
43
2.54k
    pub(crate) fn lex(span: Span, content: &str, kind: LexError) -> Error {
44
2.54k
        let mut ret = Error {
45
2.54k
            inner: Box::new(ErrorInner {
46
2.54k
                text: None,
47
2.54k
                file: None,
48
2.54k
                span,
49
2.54k
                kind: ErrorKind::Lex(kind),
50
2.54k
            }),
51
2.54k
        };
52
2.54k
        ret.set_text(content);
53
2.54k
        ret
54
2.54k
    }
55
56
1.08k
    pub(crate) fn parse(span: Span, content: &str, message: String) -> Error {
57
1.08k
        let mut ret = Error {
58
1.08k
            inner: Box::new(ErrorInner {
59
1.08k
                text: None,
60
1.08k
                file: None,
61
1.08k
                span,
62
1.08k
                kind: ErrorKind::Custom(message),
63
1.08k
            }),
64
1.08k
        };
65
1.08k
        ret.set_text(content);
66
1.08k
        ret
67
1.08k
    }
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
228
    pub fn new(span: Span, message: String) -> Error {
75
228
        Error {
76
228
            inner: Box::new(ErrorInner {
77
228
                text: None,
78
228
                file: None,
79
228
                span,
80
228
                kind: ErrorKind::Custom(message),
81
228
            }),
82
228
        }
83
228
    }
84
85
    /// Return the `Span` for this error.
86
0
    pub fn span(&self) -> Span {
87
0
        self.inner.span
88
0
    }
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
4.73k
    pub fn set_text(&mut self, contents: &str) {
97
4.73k
        if self.inner.text.is_some() {
98
1.08k
            return;
99
3.64k
        }
100
3.64k
        self.inner.text = Some(Text::new(contents, self.inner.span));
101
4.73k
    }
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
0
    pub fn set_path(&mut self, path: &Path) {
109
0
        if self.inner.file.is_some() {
110
0
            return;
111
0
        }
112
0
        self.inner.file = Some(path.to_path_buf());
113
0
    }
114
115
    /// Returns the underlying `LexError`, if any, that describes this error.
116
0
    pub fn lex_error(&self) -> Option<&LexError> {
117
0
        match &self.inner.kind {
118
0
            ErrorKind::Lex(e) => Some(e),
119
0
            _ => None,
120
        }
121
0
    }
122
123
    /// Returns the underlying message, if any, that describes this error.
124
0
    pub fn message(&self) -> String {
125
0
        match &self.inner.kind {
126
0
            ErrorKind::Lex(e) => e.to_string(),
127
0
            ErrorKind::Custom(e) => e.clone(),
128
        }
129
0
    }
130
}
131
132
impl fmt::Display for Error {
133
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134
0
        let err = match &self.inner.kind {
135
0
            ErrorKind::Lex(e) => e as &dyn fmt::Display,
136
0
            ErrorKind::Custom(e) => e as &dyn fmt::Display,
137
        };
138
0
        let text = match &self.inner.text {
139
0
            Some(text) => text,
140
            None => {
141
0
                return write!(f, "{} at byte offset {}", err, self.inner.span.offset);
142
            }
143
        };
144
0
        let file = self
145
0
            .inner
146
0
            .file
147
0
            .as_ref()
148
0
            .and_then(|p| p.to_str())
149
0
            .unwrap_or("<anon>");
150
0
        write!(
151
0
            f,
152
0
            "\
153
0
{err}
154
0
     --> {file}:{line}:{col}
155
0
      |
156
0
 {line:4} | {text}
157
0
      | {marker:>0$}",
158
0
            text.col + 1,
159
0
            file = file,
160
0
            line = text.line + 1,
161
0
            col = text.col + 1,
162
0
            err = err,
163
0
            text = text.snippet,
164
0
            marker = "^",
165
0
        )
166
0
    }
167
}
168
169
impl std::error::Error for Error {}
170
171
impl Text {
172
3.64k
    fn new(content: &str, span: Span) -> Text {
173
3.64k
        let (line, col) = span.linecol_in(content);
174
3.64k
        let contents = content.lines().nth(line).unwrap_or("");
175
3.64k
        let mut snippet = String::new();
176
36.8M
        for ch in contents.chars() {
177
36.8M
            match ch {
178
                // Replace tabs with spaces to render consistently
179
1.38M
                '\t' => {
180
1.38M
                    snippet.push_str("    ");
181
1.38M
                }
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
2.07k
                | '\u{2068}' | '\u{206c}' | '\u{2069}' => {}
186
187
35.4M
                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
3.64k
        let col = snippet.get(..col).map(|s| s.width()).unwrap_or(col);
194
3.64k
        Text { line, col, snippet }
195
3.64k
    }
196
}