diff options
Diffstat (limited to 'third_party/rust/bindgen/diagnostics.rs')
-rw-r--r-- | third_party/rust/bindgen/diagnostics.rs | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/third_party/rust/bindgen/diagnostics.rs b/third_party/rust/bindgen/diagnostics.rs new file mode 100644 index 0000000000..f765afe970 --- /dev/null +++ b/third_party/rust/bindgen/diagnostics.rs @@ -0,0 +1,189 @@ +//! Types and function used to emit pretty diagnostics for `bindgen`. +//! +//! The entry point of this module is the [`Diagnostic`] type. + +use std::fmt::Write; +use std::io::{self, BufRead, BufReader}; +use std::{borrow::Cow, fs::File}; + +use annotate_snippets::{ + display_list::{DisplayList, FormatOptions}, + snippet::{Annotation, Slice as ExtSlice, Snippet}, +}; + +use annotate_snippets::snippet::AnnotationType; + +#[derive(Clone, Copy, Debug)] +pub(crate) enum Level { + Error, + Warn, + Info, + Note, + Help, +} + +impl From<Level> for AnnotationType { + fn from(level: Level) -> Self { + match level { + Level::Error => Self::Error, + Level::Warn => Self::Warning, + Level::Info => Self::Info, + Level::Note => Self::Note, + Level::Help => Self::Help, + } + } +} + +/// A `bindgen` diagnostic. +#[derive(Default)] +pub(crate) struct Diagnostic<'a> { + title: Option<(Cow<'a, str>, Level)>, + slices: Vec<Slice<'a>>, + footer: Vec<(Cow<'a, str>, Level)>, +} + +impl<'a> Diagnostic<'a> { + /// Add a title to the diagnostic and set its type. + pub(crate) fn with_title( + &mut self, + title: impl Into<Cow<'a, str>>, + level: Level, + ) -> &mut Self { + self.title = Some((title.into(), level)); + self + } + + /// Add a slice of source code to the diagnostic. + pub(crate) fn add_slice(&mut self, slice: Slice<'a>) -> &mut Self { + self.slices.push(slice); + self + } + + /// Add a footer annotation to the diagnostic. This annotation will have its own type. + pub(crate) fn add_annotation( + &mut self, + msg: impl Into<Cow<'a, str>>, + level: Level, + ) -> &mut Self { + self.footer.push((msg.into(), level)); + self + } + + /// Print this diagnostic. + /// + /// The diagnostic is printed using `cargo:warning` if `bindgen` is being invoked by a build + /// script or using `eprintln` otherwise. + pub(crate) fn display(&self) { + std::thread_local! { + static INVOKED_BY_BUILD_SCRIPT: bool = std::env::var_os("CARGO_CFG_TARGET_ARCH").is_some(); + } + + let mut title = None; + let mut footer = vec![]; + let mut slices = vec![]; + if let Some((msg, level)) = &self.title { + title = Some(Annotation { + id: Some("bindgen"), + label: Some(msg.as_ref()), + annotation_type: (*level).into(), + }) + } + + for (msg, level) in &self.footer { + footer.push(Annotation { + id: None, + label: Some(msg.as_ref()), + annotation_type: (*level).into(), + }); + } + + // add additional info that this is generated by bindgen + // so as to not confuse with rustc warnings + footer.push(Annotation { + id: None, + label: Some("This diagnostic was generated by bindgen."), + annotation_type: AnnotationType::Info, + }); + + for slice in &self.slices { + if let Some(source) = &slice.source { + slices.push(ExtSlice { + source: source.as_ref(), + line_start: slice.line.unwrap_or_default(), + origin: slice.filename.as_deref(), + annotations: vec![], + fold: false, + }) + } + } + + let snippet = Snippet { + title, + footer, + slices, + opt: FormatOptions { + color: true, + ..Default::default() + }, + }; + let dl = DisplayList::from(snippet); + + if INVOKED_BY_BUILD_SCRIPT.with(Clone::clone) { + // This is just a hack which hides the `warning:` added by cargo at the beginning of + // every line. This should be fine as our diagnostics already have a colorful title. + // FIXME (pvdrz): Could it be that this doesn't work in other languages? + let hide_warning = "\r \r"; + let string = dl.to_string(); + for line in string.lines() { + println!("cargo:warning={}{}", hide_warning, line); + } + } else { + eprintln!("{}\n", dl); + } + } +} + +/// A slice of source code. +#[derive(Default)] +pub(crate) struct Slice<'a> { + source: Option<Cow<'a, str>>, + filename: Option<String>, + line: Option<usize>, +} + +impl<'a> Slice<'a> { + /// Set the source code. + pub(crate) fn with_source( + &mut self, + source: impl Into<Cow<'a, str>>, + ) -> &mut Self { + self.source = Some(source.into()); + self + } + + /// Set the file, line and column. + pub(crate) fn with_location( + &mut self, + mut name: String, + line: usize, + col: usize, + ) -> &mut Self { + write!(name, ":{}:{}", line, col) + .expect("Writing to a string cannot fail"); + self.filename = Some(name); + self.line = Some(line); + self + } +} + +pub(crate) fn get_line( + filename: &str, + line: usize, +) -> io::Result<Option<String>> { + let file = BufReader::new(File::open(filename)?); + if let Some(line) = file.lines().nth(line.wrapping_sub(1)) { + return line.map(Some); + } + + Ok(None) +} |