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 _ => 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} 176