#![doc(html_root_url = "https://docs.rs/rucc-diag/0.2.4")]
mod source;
pub use crate::source::{FileId, Loc, SourceBytes, SourceFile, SourceMap, SourceMapFull};
use std::fmt;
pub type BytePos = u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Span {
pub lo: BytePos,
pub hi: BytePos,
}
impl Span {
#[inline]
pub const fn new(lo: BytePos, hi: BytePos) -> Self {
assert!(lo <= hi, "reversed span");
Self { lo, hi }
}
#[inline]
pub const fn empty_at(at: BytePos) -> Self {
Self { lo: at, hi: at }
}
pub const DUMMY: Self = Self { lo: BytePos::MAX, hi: BytePos::MAX };
#[inline]
pub const fn is_dummy(self) -> bool {
self.lo == BytePos::MAX
}
#[inline]
pub const fn len(self) -> u32 {
self.hi - self.lo
}
#[inline]
pub const fn is_empty(self) -> bool {
self.lo == self.hi
}
#[inline]
pub fn to(self, other: Self) -> Self {
if self.is_dummy() {
return other;
}
if other.is_dummy() {
return self;
}
Self { lo: self.lo.min(other.lo), hi: self.hi.max(other.hi) }
}
#[inline]
pub const fn contains(self, pos: BytePos) -> bool {
!self.is_dummy() && self.lo <= pos && pos < self.hi
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Severity {
Note,
Help,
Warning,
Error,
Ice,
}
impl Severity {
#[inline]
pub const fn is_fatal(self) -> bool {
matches!(self, Severity::Error | Severity::Ice)
}
pub const fn as_str(self) -> &'static str {
match self {
Severity::Note => "note",
Severity::Help => "help",
Severity::Warning => "warning",
Severity::Error => "error",
Severity::Ice => "internal compiler error",
}
}
}
impl fmt::Display for Severity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub severity: Severity,
pub code: Option<&'static str>,
pub message: String,
pub span: Span,
pub children: Vec<Diagnostic>,
}
impl Diagnostic {
pub fn new(severity: Severity, message: impl Into<String>, span: Span) -> Self {
Self { severity, code: None, message: message.into(), span, children: Vec::new() }
}
pub fn error(message: impl Into<String>, span: Span) -> Self {
Self::new(Severity::Error, message, span)
}
pub fn warning(message: impl Into<String>, span: Span) -> Self {
Self::new(Severity::Warning, message, span)
}
#[must_use]
pub fn with_code(mut self, code: &'static str) -> Self {
self.code = Some(code);
self
}
#[must_use]
pub fn note(mut self, message: impl Into<String>, span: Span) -> Self {
self.children.push(Self::new(Severity::Note, message, span));
self
}
#[must_use]
pub fn help(mut self, message: impl Into<String>, span: Span) -> Self {
self.children.push(Self::new(Severity::Help, message, span));
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn joining_spans_covers_both() {
let a = Span::new(4, 9);
let b = Span::new(20, 22);
assert_eq!(a.to(b), Span::new(4, 22));
assert_eq!(b.to(a), Span::new(4, 22));
}
#[test]
fn joining_with_a_dummy_keeps_the_real_one() {
let a = Span::new(4, 9);
assert_eq!(a.to(Span::DUMMY), a);
assert_eq!(Span::DUMMY.to(a), a);
}
#[test]
fn a_dummy_span_contains_nothing() {
assert!(!Span::DUMMY.contains(0));
assert!(!Span::DUMMY.contains(BytePos::MAX));
}
#[test]
fn an_empty_span_is_not_a_dummy_span() {
let e = Span::empty_at(0);
assert!(e.is_empty());
assert!(!e.is_dummy());
}
#[test]
fn severity_orders_by_how_bad_it_is() {
assert!(Severity::Error > Severity::Warning);
assert!(Severity::Ice > Severity::Error);
assert!(Severity::Warning > Severity::Note);
}
#[test]
fn only_errors_and_ices_suppress_output() {
assert!(Severity::Error.is_fatal());
assert!(Severity::Ice.is_fatal());
assert!(!Severity::Warning.is_fatal());
}
#[test]
fn a_diagnostic_carries_its_children() {
let d = Diagnostic::error("expected an expression", Span::new(1, 2))
.with_code("E0001")
.note("in this macro expansion", Span::new(0, 8))
.help("did you mean a compound literal", Span::DUMMY);
assert_eq!(d.code, Some("E0001"));
assert_eq!(d.children.len(), 2);
assert_eq!(d.children[0].severity, Severity::Note);
assert_eq!(d.children[1].severity, Severity::Help);
}
#[test]
#[should_panic(expected = "reversed span")]
fn a_reversed_span_is_rejected() {
let _ = Span::new(9, 4);
}
}