1use crate::gen::fs;
2use crate::syntax;
3use codespan_reporting::diagnostic::{Diagnostic, Label};
4use codespan_reporting::files::SimpleFiles;
5use codespan_reporting::term::termcolor::{ColorChoice, StandardStream, WriteColor};
6use codespan_reporting::term::{self, Config};
7use std::borrow::Cow;
8use std::error::Error as StdError;
9use std::fmt::{self, Display};
10use std::io::{self, Write};
11use std::ops::Range;
12use std::path::{Path, PathBuf};
13use std::process;
14use std::str::Utf8Error;
15
16pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
17
18#[derive(Debug)]
19pub(crate) enum Error {
20 NoBridgeMod,
21 Fs(fs::Error),
22 Utf8(PathBuf, Utf8Error),
23 Syn(syn::Error),
24}
25
26impl Display for Error {
27 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28 match self {
29 Error::NoBridgeMod => write!(f, "no #[cxx::bridge] module found"),
30 Error::Fs(err) => err.fmt(f),
31 Error::Utf8(path, _) => write!(f, "Failed to read file `{}`", path.display()),
32 Error::Syn(err) => err.fmt(f),
33 }
34 }
35}
36
37impl StdError for Error {
38 fn source(&self) -> Option<&(dyn StdError + 'static)> {
39 match self {
40 Error::Fs(err) => err.source(),
41 Error::Utf8(_, err) => Some(err),
42 Error::Syn(err) => err.source(),
43 Error::NoBridgeMod => None,
44 }
45 }
46}
47
48impl From<fs::Error> for Error {
49 fn from(err: fs::Error) -> Self {
50 Error::Fs(err)
51 }
52}
53
54impl From<syn::Error> for Error {
55 fn from(err: syn::Error) -> Self {
56 Error::Syn(err)
57 }
58}
59
60pub(super) fn format_err(path: &Path, source: &str, error: Error) -> ! {
61 match error {
62 Error::Syn(syn_error) => {
63 let syn_error = sort_syn_errors(syn_error);
64 let writer = StandardStream::stderr(ColorChoice::Auto);
65 let ref mut stderr = writer.lock();
66 for error in syn_error {
67 let _ = writeln!(stderr);
68 display_syn_error(stderr, path, source, error);
69 }
70 }
71 Error::NoBridgeMod => {
72 let _ = writeln!(
73 io::stderr(),
74 "cxxbridge: no #[cxx::bridge] module found in {}",
75 path.display(),
76 );
77 }
78 _ => {
79 let _ = writeln!(io::stderr(), "cxxbridge: {}", report(error));
80 }
81 }
82 process::exit(1);
83}
84
85pub(crate) fn report(error: impl StdError) -> impl Display {
86 struct Report<E>(E);
87
88 impl<E: StdError> Display for Report<E> {
89 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
90 write!(formatter, "{}", self.0)?;
91 let mut error: &dyn StdError = &self.0;
92
93 while let Some(cause) = error.source() {
94 write!(formatter, "\n\nCaused by:\n {}", cause)?;
95 error = cause;
96 }
97
98 Ok(())
99 }
100 }
101
102 Report(error)
103}
104
105fn sort_syn_errors(error: syn::Error) -> Vec<syn::Error> {
106 let mut errors: Vec<_> = error.into_iter().collect();
107 errors.sort_by_key(|e| {
108 let start = e.span().start();
109 (start.line, start.column)
110 });
111 errors
112}
113
114fn display_syn_error(stderr: &mut dyn WriteColor, path: &Path, source: &str, error: syn::Error) {
115 let span = error.span();
116 let start = span.start();
117 let end = span.end();
118
119 let mut start_offset = 0;
120 for _ in 1..start.line {
121 start_offset += source[start_offset..].find('\n').unwrap() + 1;
122 }
123 let start_column = source[start_offset..]
124 .chars()
125 .take(start.column)
126 .map(char::len_utf8)
127 .sum::<usize>();
128 start_offset += start_column;
129
130 let mut end_offset = start_offset;
131 if start.line == end.line {
132 end_offset -= start_column;
133 } else {
134 for _ in 0..end.line - start.line {
135 end_offset += source[end_offset..].find('\n').unwrap() + 1;
136 }
137 }
138 end_offset += source[end_offset..]
139 .chars()
140 .take(end.column)
141 .map(char::len_utf8)
142 .sum::<usize>();
143
144 let mut path = path.to_string_lossy();
145 if path == "-" {
146 path = Cow::Borrowed(if cfg!(unix) { "/dev/stdin" } else { "stdin" });
147 }
148
149 let mut files = SimpleFiles::new();
150 let file = files.add(path, source);
151
152 let diagnostic = diagnose(file, start_offset..end_offset, error);
153
154 let config = Config::default();
155 let _ = term::emit(stderr, &config, &files, &diagnostic);
156}
157
158fn diagnose(file: usize, range: Range<usize>, error: syn::Error) -> Diagnostic<usize> {
159 let message = error.to_string();
160 let info = syntax::error::ERRORS
161 .iter()
162 .find(|e| message.contains(e.msg));
163 let mut diagnostic = Diagnostic::error().with_message(&message);
164 let mut label = Label::primary(file, range);
165 if let Some(info) = info {
166 label.message = info.label.map_or(message, str::to_owned);
167 diagnostic.labels.push(label);
168 diagnostic.notes.extend(info.note.map(str::to_owned));
169 } else {
170 label.message = message;
171 diagnostic.labels.push(label);
172 }
173 diagnostic.code = Some("cxxbridge".to_owned());
174 diagnostic
175}