aranya_capi_codegen/
error.rs1#![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
35pub enum BuildError {
37 Syn(syn::Error),
39 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 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 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}