use std::fmt;
use std::path::PathBuf;
use std::sync::Mutex;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Severity {
Note,
Warning,
Error,
}
impl fmt::Display for Severity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let text = match self {
Self::Note => "note",
Self::Warning => "warning",
Self::Error => "error",
};
f.write_str(text)
}
}
#[derive(Clone, Debug, Default)]
pub struct Location {
pub file: PathBuf,
pub member: Option<String>,
pub section: Option<String>,
pub offset: Option<u64>,
pub source: Option<SourceLocation>,
}
#[derive(Clone, Debug)]
pub struct SourceLocation {
pub file: String,
pub line: u32,
}
impl fmt::Display for Location {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(source) = &self.source {
write!(f, "{}:{} (", source.file, source.line)?;
}
write!(f, "{}", self.file.display())?;
if let Some(member) = &self.member {
write!(f, "({member})")?;
}
if let Some(section) = &self.section {
write!(f, ":({section}")?;
if let Some(offset) = self.offset {
write!(f, "+{offset:#x}")?;
}
f.write_str(")")?;
}
if self.source.is_some() {
f.write_str(")")?;
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct Diagnostic {
pub severity: Severity,
pub message: String,
pub locations: Vec<Location>,
pub details: Vec<String>,
pub notes: Vec<String>,
pub order: u64,
}
impl Diagnostic {
pub fn new(severity: Severity, message: impl Into<String>) -> Self {
Self {
severity,
message: message.into(),
locations: Vec::new(),
details: Vec::new(),
notes: Vec::new(),
order: u64::MAX,
}
}
pub fn error(message: impl Into<String>) -> Self {
Self::new(Severity::Error, message)
}
pub fn warning(message: impl Into<String>) -> Self {
Self::new(Severity::Warning, message)
}
#[must_use]
pub fn at(mut self, location: Location) -> Self {
self.locations.push(location);
self
}
#[must_use]
pub fn detail(mut self, detail: impl Into<String>) -> Self {
self.details.push(detail.into());
self
}
#[must_use]
pub fn note(mut self, note: impl Into<String>) -> Self {
self.notes.push(note.into());
self
}
#[must_use]
pub fn order(mut self, order: u64) -> Self {
self.order = order;
self
}
}
pub trait DiagnosticSink: Send + Sync {
fn emit(&self, diagnostic: Diagnostic);
fn error_count(&self) -> usize;
}
#[derive(Debug, Default)]
pub struct Collect {
diagnostics: Mutex<Vec<Diagnostic>>,
errors: AtomicUsize,
}
impl Collect {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn take_sorted(&self) -> Vec<Diagnostic> {
let mut diagnostics = std::mem::take(&mut *self.diagnostics.lock().expect("poisoned"));
diagnostics.sort_by_key(|diagnostic| diagnostic.order);
diagnostics
}
}
impl DiagnosticSink for Collect {
fn emit(&self, diagnostic: Diagnostic) {
if diagnostic.severity == Severity::Error {
self.errors.fetch_add(1, Ordering::Relaxed);
}
self.diagnostics.lock().expect("poisoned").push(diagnostic);
}
fn error_count(&self) -> usize {
self.errors.load(Ordering::Relaxed)
}
}
#[derive(Debug)]
pub struct Stderr {
program: String,
errors: AtomicUsize,
}
impl Stderr {
#[must_use]
pub fn new(program: impl Into<String>) -> Self {
Self {
program: program.into(),
errors: AtomicUsize::new(0),
}
}
}
impl DiagnosticSink for Stderr {
fn emit(&self, diagnostic: Diagnostic) {
if diagnostic.severity == Severity::Error {
self.errors.fetch_add(1, Ordering::Relaxed);
}
let mut text = format!(
"{}: {}: {}\n",
self.program, diagnostic.severity, diagnostic.message
);
for location in &diagnostic.locations {
text.push_str(&format!(">>> referenced by {location}\n"));
}
for detail in &diagnostic.details {
text.push_str(&format!(">>> {detail}\n"));
}
for note in &diagnostic.notes {
text.push_str(&format!(">>> note: {note}\n"));
}
eprint!("{text}");
}
fn error_count(&self) -> usize {
self.errors.load(Ordering::Relaxed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn collect_counts_errors_and_sorts() {
let sink = Collect::new();
sink.emit(Diagnostic::warning("second").order(2));
sink.emit(Diagnostic::error("first").order(1));
assert_eq!(sink.error_count(), 1);
let diagnostics = sink.take_sorted();
assert_eq!(diagnostics[0].message, "first");
assert_eq!(diagnostics[1].message, "second");
}
#[test]
fn location_renders_like_lld() {
let location = Location {
file: PathBuf::from("main.o"),
section: Some(".text".into()),
offset: Some(0x1a),
..Location::default()
};
assert_eq!(location.to_string(), "main.o:(.text+0x1a)");
}
}