Skip to main content

cranelift_isle/
error.rs

1//! Error types.
2
3use std::sync::Arc;
4
5use crate::{files::Files, lexer::Pos};
6
7/// A collection of errors from attempting to compile some ISLE source files.
8#[derive(Debug)]
9pub struct Errors {
10    /// The individual errors.
11    pub errors: Vec<Error>,
12    pub(crate) files: Arc<Files>,
13}
14
15impl std::error::Error for Errors {}
16
17impl std::fmt::Display for Errors {
18    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
19        if self.errors.is_empty() {
20            return Ok(());
21        }
22        let diagnostics = Vec::from_iter(self.errors.iter().map(|e| {
23            let message = match e {
24                Error::IoError { context, .. } => context.clone(),
25                Error::ParseError { msg, .. } => format!("parse error: {msg}"),
26                Error::TypeError { msg, .. } => format!("type error: {msg}"),
27                Error::UnreachableError { msg, .. } => format!("unreachable rule: {msg}"),
28                Error::OverlapError { msg, .. } => format!("overlap error: {msg}"),
29                Error::RecursionError { msg, .. } => format!("recursion error: {msg}"),
30                Error::ShadowedError { .. } => {
31                    "more general higher-priority rule shadows other rules".to_string()
32                }
33            };
34
35            let labels = match e {
36                Error::IoError { .. } => vec![],
37
38                Error::ParseError { span, .. }
39                | Error::TypeError { span, .. }
40                | Error::UnreachableError { span, .. }
41                | Error::RecursionError { span, .. } => {
42                    vec![Label::primary(span.from.file, span)]
43                }
44
45                Error::OverlapError { rules, .. } => {
46                    let mut labels = vec![Label::primary(rules[0].from.file, &rules[0])];
47                    labels.extend(
48                        rules[1..]
49                            .iter()
50                            .map(|span| Label::secondary(span.from.file, span)),
51                    );
52                    labels
53                }
54
55                Error::ShadowedError { shadowed, mask } => {
56                    let mut labels = vec![Label::primary(mask.from.file, mask)];
57                    labels.extend(
58                        shadowed
59                            .iter()
60                            .map(|span| Label::secondary(span.from.file, span)),
61                    );
62                    labels
63                }
64            };
65
66            let mut sources = Vec::new();
67            let mut source = e.source();
68            while let Some(e) = source {
69                sources.push(format!("{e:?}"));
70                source = std::error::Error::source(e);
71            }
72
73            Diagnostic::error()
74                .with_message(message)
75                .with_labels(labels)
76                .with_notes(sources)
77        }));
78        self.emit(f, diagnostics)?;
79        if self.errors.len() > 1 {
80            writeln!(f, "found {} errors", self.errors.len())?;
81        }
82        Ok(())
83    }
84}
85
86/// Errors produced by ISLE.
87#[derive(Debug)]
88pub enum Error {
89    /// An I/O error.
90    IoError {
91        /// The underlying I/O error.
92        error: std::io::Error,
93        /// The context explaining what caused the I/O error.
94        context: String,
95    },
96
97    /// The input ISLE source has a parse error.
98    ParseError {
99        /// The error message.
100        msg: String,
101
102        /// The location of the parse error.
103        span: Span,
104    },
105
106    /// The input ISLE source has a type error.
107    TypeError {
108        /// The error message.
109        msg: String,
110
111        /// The location of the type error.
112        span: Span,
113    },
114
115    /// The rule can never match any input.
116    UnreachableError {
117        /// The error message.
118        msg: String,
119
120        /// The location of the unreachable rule.
121        span: Span,
122    },
123
124    /// The rules mentioned overlap in the input they accept.
125    OverlapError {
126        /// The error message.
127        msg: String,
128
129        /// The locations of all the rules that overlap. When there are more than two rules
130        /// present, the first rule is the one with the most overlaps (likely a fall-through
131        /// wildcard case).
132        rules: Vec<Span>,
133    },
134
135    /// Recursive rules error. Term is recursive without explicit opt-in.
136    RecursionError {
137        /// The error message.
138        msg: String,
139
140        /// The location of the term declaration.
141        span: Span,
142    },
143
144    /// The rules can never match because another rule will always match first.
145    ShadowedError {
146        /// The locations of the unmatchable rules.
147        shadowed: Vec<Span>,
148
149        /// The location of the rule that shadows them.
150        mask: Span,
151    },
152}
153
154impl Errors {
155    /// Create new Errors
156    pub fn new(errors: Vec<Error>, files: Arc<Files>) -> Self {
157        Self { errors, files }
158    }
159
160    /// Create `isle::Errors` from the given I/O error and context.
161    pub fn from_io(error: std::io::Error, context: impl Into<String>) -> Self {
162        Errors {
163            errors: vec![Error::IoError {
164                error,
165                context: context.into(),
166            }],
167            files: Arc::new(Files::default()),
168        }
169    }
170
171    #[cfg(feature = "fancy-errors")]
172    fn emit(
173        &self,
174        f: &mut std::fmt::Formatter,
175        diagnostics: Vec<Diagnostic<usize>>,
176    ) -> std::fmt::Result {
177        use codespan_reporting::term::termcolor;
178        let w = termcolor::BufferWriter::stderr(termcolor::ColorChoice::Auto);
179        let mut b = w.buffer();
180        let mut files = codespan_reporting::files::SimpleFiles::new();
181        for (name, source) in self
182            .files
183            .file_names
184            .iter()
185            .zip(self.files.file_texts.iter())
186        {
187            files.add(name, source);
188        }
189        for diagnostic in diagnostics {
190            codespan_reporting::term::emit(&mut b, &Default::default(), &files, &diagnostic)
191                .map_err(|_| std::fmt::Error)?;
192        }
193        let b = b.into_inner();
194        let b = std::str::from_utf8(&b).map_err(|_| std::fmt::Error)?;
195        f.write_str(b)
196    }
197
198    #[cfg(not(feature = "fancy-errors"))]
199    fn emit(
200        &self,
201        f: &mut std::fmt::Formatter,
202        diagnostics: Vec<Diagnostic<usize>>,
203    ) -> std::fmt::Result {
204        let pos = |file_id: usize, offset| {
205            let ends = self.files.file_line_map(file_id).unwrap();
206            let line0 = ends.line(offset);
207            let text = &self.files.file_texts[file_id];
208            let start = line0.checked_sub(1).map_or(0, |prev| ends[prev]);
209            let end = ends.get(line0).copied().unwrap_or(text.len());
210            let col = offset - start + 1;
211            format!(
212                "{}:{}:{}: {}",
213                self.files.file_names[file_id],
214                line0 + 1,
215                col,
216                &text[start..end]
217            )
218        };
219        for diagnostic in diagnostics {
220            writeln!(f, "{}", diagnostic.message)?;
221            for label in diagnostic.labels {
222                f.write_str(&pos(label.file_id, label.range.start))?;
223            }
224            for note in diagnostic.notes {
225                writeln!(f, "{note}")?;
226            }
227            writeln!(f)?;
228        }
229        Ok(())
230    }
231}
232
233/// Builder for the `isle::Errors`.
234pub struct ErrorsBuilder(Errors);
235
236impl ErrorsBuilder {
237    /// Start building an [Errors] object.
238    pub fn new() -> Self {
239        Self(Errors {
240            errors: Vec::new(),
241            files: Arc::new(Files::default()),
242        })
243    }
244
245    /// Return the built [Errors] object.
246    pub fn build(self) -> Errors {
247        self.0
248    }
249
250    /// Set the `errors` field of the under-construction [Errors] object.
251    pub fn errors(mut self, errors: Vec<Error>) -> Self {
252        self.0.errors = errors;
253        self
254    }
255
256    /// Set the `errors` field of the under-construction [Errors] object to a single error.
257    pub fn error(self, error: Error) -> Self {
258        self.errors(vec![error])
259    }
260
261    /// Set the `files` field of the under-construction [Errors] object.
262    pub fn files(mut self, files: Arc<Files>) -> Self {
263        self.0.files = files;
264        self
265    }
266}
267
268impl Error {
269    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
270        match self {
271            Error::IoError { error, .. } => Some(error),
272            _ => None,
273        }
274    }
275}
276
277/// A span in a given source.
278#[derive(Clone, Debug)]
279pub struct Span {
280    /// The byte offset of the start of the span.
281    pub from: Pos,
282    /// The byte offset of the end of the span.
283    pub to: Pos,
284}
285
286impl Span {
287    /// Create a new span that covers one character at the given offset.
288    pub fn new_single(pos: Pos) -> Span {
289        Span {
290            from: pos,
291            // This is a slight hack (we don't actually look at the
292            // file to find line/col of next char); but the `to`
293            // position only matters for pretty-printed errors and only
294            // the offset is used in that case.
295            to: Pos {
296                file: pos.file,
297                offset: pos.offset + 1,
298            },
299        }
300    }
301}
302
303impl From<&Span> for std::ops::Range<usize> {
304    fn from(span: &Span) -> Self {
305        span.from.offset..span.to.offset
306    }
307}
308
309use diagnostic::{Diagnostic, Label};
310
311#[cfg(feature = "fancy-errors")]
312use codespan_reporting::diagnostic;
313
314#[cfg(not(feature = "fancy-errors"))]
315/// Minimal versions of types from codespan-reporting.
316mod diagnostic {
317    use std::ops::Range;
318
319    pub struct Diagnostic<FileId> {
320        pub message: String,
321        pub labels: Vec<Label<FileId>>,
322        pub notes: Vec<String>,
323    }
324
325    impl<FileId> Diagnostic<FileId> {
326        pub fn error() -> Self {
327            Self {
328                message: String::new(),
329                labels: Vec::new(),
330                notes: Vec::new(),
331            }
332        }
333
334        pub fn with_message(mut self, message: impl Into<String>) -> Self {
335            self.message = message.into();
336            self
337        }
338
339        pub fn with_labels(mut self, labels: Vec<Label<FileId>>) -> Self {
340            self.labels = labels;
341            self
342        }
343
344        pub fn with_notes(mut self, notes: Vec<String>) -> Self {
345            self.notes = notes;
346            self
347        }
348    }
349
350    pub struct Label<FileId> {
351        pub file_id: FileId,
352        pub range: Range<usize>,
353    }
354
355    impl<FileId> Label<FileId> {
356        pub fn primary(file_id: FileId, range: impl Into<Range<usize>>) -> Self {
357            Self {
358                file_id,
359                range: range.into(),
360            }
361        }
362
363        pub fn secondary(file_id: FileId, range: impl Into<Range<usize>>) -> Self {
364            Self::primary(file_id, range)
365        }
366    }
367}