1 use crate::gen::fs;
2 use crate::syntax;
3 use codespan_reporting::diagnostic::{Diagnostic, Label};
4 use codespan_reporting::files::SimpleFiles;
5 use codespan_reporting::term::termcolor::{ColorChoice, StandardStream, WriteColor};
6 use codespan_reporting::term::{self, Config};
7 use std::borrow::Cow;
8 use std::error::Error as StdError;
9 use std::fmt::{self, Display};
10 use std::io::{self, Write};
11 use std::ops::Range;
12 use std::path::{Path, PathBuf};
13 use std::process;
14 use std::str::Utf8Error;
15 
16 pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
17 
18 #[derive(Debug)]
19 pub(crate) enum Error {
20     NoBridgeMod,
21     Fs(fs::Error),
22     Utf8(PathBuf, Utf8Error),
23     Syn(syn::Error),
24 }
25 
26 impl Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result27     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 
37 impl StdError for Error {
source(&self) -> Option<&(dyn StdError + 'static)>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             _ => None,
44         }
45     }
46 }
47 
48 impl From<fs::Error> for Error {
from(err: fs::Error) -> Self49     fn from(err: fs::Error) -> Self {
50         Error::Fs(err)
51     }
52 }
53 
54 impl From<syn::Error> for Error {
from(err: syn::Error) -> Self55     fn from(err: syn::Error) -> Self {
56         Error::Syn(err)
57     }
58 }
59 
format_err(path: &Path, source: &str, error: Error) -> !60 pub(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 
report(error: impl StdError) -> impl Display85 pub(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             Display::fmt(&self.0, formatter)?;
91             let mut error: &dyn StdError = &self.0;
92 
93             while let Some(cause) = error.source() {
94                 formatter.write_str("\n\nCaused by:\n    ")?;
95                 Display::fmt(cause, formatter)?;
96                 error = cause;
97             }
98 
99             Ok(())
100         }
101     }
102 
103     Report(error)
104 }
105 
sort_syn_errors(error: syn::Error) -> Vec<syn::Error>106 fn sort_syn_errors(error: syn::Error) -> Vec<syn::Error> {
107     let mut errors: Vec<_> = error.into_iter().collect();
108     errors.sort_by_key(|e| {
109         let start = e.span().start();
110         (start.line, start.column)
111     });
112     errors
113 }
114 
display_syn_error(stderr: &mut dyn WriteColor, path: &Path, source: &str, error: syn::Error)115 fn display_syn_error(stderr: &mut dyn WriteColor, path: &Path, source: &str, error: syn::Error) {
116     let span = error.span();
117     let start = span.start();
118     let end = span.end();
119 
120     let mut start_offset = 0;
121     for _ in 1..start.line {
122         start_offset += source[start_offset..].find('\n').unwrap() + 1;
123     }
124     let start_column = source[start_offset..]
125         .chars()
126         .take(start.column)
127         .map(char::len_utf8)
128         .sum::<usize>();
129     start_offset += start_column;
130 
131     let mut end_offset = start_offset;
132     if start.line == end.line {
133         end_offset -= start_column;
134     } else {
135         for _ in 0..end.line - start.line {
136             end_offset += source[end_offset..].find('\n').unwrap() + 1;
137         }
138     }
139     end_offset += source[end_offset..]
140         .chars()
141         .take(end.column)
142         .map(char::len_utf8)
143         .sum::<usize>();
144 
145     let mut path = path.to_string_lossy();
146     if path == "-" {
147         path = Cow::Borrowed(if cfg!(unix) { "/dev/stdin" } else { "stdin" });
148     }
149 
150     let mut files = SimpleFiles::new();
151     let file = files.add(path, source);
152 
153     let diagnostic = diagnose(file, start_offset..end_offset, error);
154 
155     let config = Config::default();
156     let _ = term::emit(stderr, &config, &files, &diagnostic);
157 }
158 
diagnose(file: usize, range: Range<usize>, error: syn::Error) -> Diagnostic<usize>159 fn diagnose(file: usize, range: Range<usize>, error: syn::Error) -> Diagnostic<usize> {
160     let message = error.to_string();
161     let info = syntax::error::ERRORS
162         .iter()
163         .find(|e| message.contains(e.msg));
164     let mut diagnostic = Diagnostic::error().with_message(&message);
165     let mut label = Label::primary(file, range);
166     if let Some(info) = info {
167         label.message = info.label.map_or(message, str::to_owned);
168         diagnostic.labels.push(label);
169         diagnostic.notes.extend(info.note.map(str::to_owned));
170     } else {
171         label.message = message;
172         diagnostic.labels.push(label);
173     }
174     diagnostic.code = Some("cxxbridge".to_owned());
175     diagnostic
176 }
177