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