Skip to main content

aranya_capi_codegen/
error.rs

1//! Error reporting.
2//!
3//! Largely taken from [`cxx`].
4//!
5//! [`cxx`]: https://github.com/dtolnay/cxx/blob/a73ec2470e065d6d93817c6957e368c73a8c964d/gen/src/error.rs
6
7#![allow(
8    clippy::duplicated_attributes,
9    reason = "See https://github.com/rust-lang/rust-clippy/issues/13355"
10)]
11#![allow(clippy::arithmetic_side_effects, reason = "Borrowed code")]
12#![allow(clippy::unwrap_used, reason = "Borrowed code")]
13#![allow(clippy::toplevel_ref_arg, reason = "Borrowed code")]
14
15use std::{
16    borrow::Cow,
17    error::Error,
18    fmt,
19    io::{self, Write as _},
20    ops::Range,
21    path::Path,
22};
23
24use codespan_reporting::{
25    diagnostic::{Diagnostic, Label},
26    files::SimpleFiles,
27    term::{
28        self,
29        termcolor::{ColorChoice, StandardStream, WriteColor},
30    },
31};
32
33use crate::syntax::ERRORS;
34
35/// An error returned when building.
36pub enum BuildError {
37    /// An error from `syn`.
38    Syn(syn::Error),
39    /// Some other error.
40    Other(anyhow::Error),
41}
42
43impl Error for BuildError {
44    fn source(&self) -> Option<&(dyn Error + 'static)> {
45        match self {
46            Self::Syn(err) => Some(err),
47            Self::Other(err) => Some(err.as_ref()),
48        }
49    }
50}
51
52impl From<syn::Error> for BuildError {
53    fn from(err: syn::Error) -> Self {
54        Self::Syn(err)
55    }
56}
57
58impl From<anyhow::Error> for BuildError {
59    fn from(err: anyhow::Error) -> Self {
60        Self::Other(err)
61    }
62}
63
64impl fmt::Debug for BuildError {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        write!(f, "unable to generate C API")
67    }
68}
69
70impl fmt::Display for BuildError {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        write!(f, "{}", report(self))
73    }
74}
75
76impl BuildError {
77    /// Displays the error.
78    pub fn display(&self, path: &Path, source: &str) {
79        match self {
80            Self::Syn(err) => {
81                let errs = sort_syn_error(err);
82                let writer = StandardStream::stderr(ColorChoice::Auto);
83                let ref mut stderr = writer.lock();
84                for error in errs {
85                    let _ = writeln!(stderr);
86                    display_syn_error(stderr, path, source, error);
87                }
88            }
89            Self::Other(_) => {
90                let _ = writeln!(io::stderr(), "{self}");
91            }
92        }
93    }
94}
95
96fn report(err: impl Error) -> impl fmt::Display {
97    struct Report<E>(E);
98    impl<E: Error> fmt::Display for Report<E> {
99        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100            write!(f, "{}", self.0)?;
101            let mut error: &dyn Error = &self.0;
102
103            while let Some(cause) = error.source() {
104                write!(f, "\n\nCaused by:\n    {}", cause)?;
105                error = cause;
106            }
107
108            Ok(())
109        }
110    }
111    Report(err)
112}
113
114fn sort_syn_error(err: &syn::Error) -> Vec<syn::Error> {
115    let mut errors: Vec<_> = err.into_iter().collect();
116    errors.sort_by_key(|e| {
117        let start = e.span().start();
118        (start.line, start.column)
119    });
120    errors
121}
122
123fn display_syn_error(stderr: &mut dyn WriteColor, path: &Path, source: &str, err: syn::Error) {
124    let span = err.span();
125    let start = span.start();
126    let end = span.end();
127
128    let mut start_offset = 0;
129    for _ in 1..start.line {
130        start_offset += source[start_offset..].find('\n').unwrap() + 1;
131    }
132    let start_column = source[start_offset..]
133        .chars()
134        .take(start.column)
135        .map(char::len_utf8)
136        .sum::<usize>();
137    start_offset += start_column;
138
139    let mut end_offset = start_offset;
140    if start.line == end.line {
141        end_offset -= start_column;
142    } else {
143        for _ in 0..end.line - start.line {
144            end_offset += source[end_offset..].find('\n').unwrap() + 1;
145        }
146    }
147    end_offset += source[end_offset..]
148        .chars()
149        .take(end.column)
150        .map(char::len_utf8)
151        .sum::<usize>();
152
153    let mut path = path.to_string_lossy();
154    if path == "-" {
155        path = Cow::Borrowed(if cfg!(unix) { "/dev/stdin" } else { "stdin" });
156    }
157
158    let mut files = SimpleFiles::new();
159    let file = files.add(path, source);
160
161    let diagnostic = diagnose(file, start_offset..end_offset, err);
162
163    let mut config = term::Config::default();
164    // Make it a little easier to see on dark backgrounds.
165    config.styles.header_error.set_intense(true);
166    config.styles.line_number.set_intense(true);
167    config.styles.note_bullet.set_intense(true);
168    config.styles.primary_label_bug.set_intense(true);
169    config.styles.primary_label_error.set_intense(true);
170    config.styles.secondary_label.set_intense(true);
171    config.styles.source_border.set_intense(true);
172    let _ = term::emit(stderr, &config, &files, &diagnostic);
173}
174
175fn diagnose(file: usize, range: Range<usize>, error: syn::Error) -> Diagnostic<usize> {
176    let message = error.to_string();
177    let info = ERRORS.iter().find(|e| message.contains(e.msg));
178    let mut diagnostic = Diagnostic::error().with_message(&message);
179    let mut label = Label::primary(file, range);
180    if let Some(info) = info {
181        label.message = info.label.map_or(message, str::to_owned);
182        diagnostic.labels.push(label);
183        diagnostic.notes.extend(info.note.map(str::to_owned));
184    } else {
185        label.message = message;
186        diagnostic.labels.push(label);
187    }
188    diagnostic.code = Some("capi-codegen".to_owned());
189    diagnostic
190}