use crate::Span;
use anstyle::{AnsiColor, Color};
use std::{borrow::Cow, fmt, panic::Location};
mod builder;
pub use builder::{DiagnosticBuilder, EmissionGuarantee};
mod context;
pub use context::{DiagCtxt, DiagCtxtFlags};
mod emitter;
#[cfg(feature = "json")]
pub use emitter::JsonEmitter;
pub use emitter::{
DynEmitter, Emitter, HumanBufferEmitter, HumanEmitter, LocalEmitter, SilentEmitter,
};
mod message;
pub use message::{DiagnosticMessage, MultiSpan, SpanLabel};
pub struct EmittedDiagnostics(pub(crate) String);
impl fmt::Debug for EmittedDiagnostics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl fmt::Display for EmittedDiagnostics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for EmittedDiagnostics {}
impl EmittedDiagnostics {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ErrorGuaranteed(());
impl fmt::Debug for ErrorGuaranteed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ErrorGuaranteed")
}
}
impl ErrorGuaranteed {
#[inline]
pub const fn new_unchecked() -> Self {
Self(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BugAbort;
pub struct ExplicitBug;
pub struct FatalAbort;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DiagnosticId {
id: u32,
}
impl DiagnosticId {
#[doc(hidden)]
#[track_caller]
pub const fn new_from_macro(id: u32) -> Self {
assert!(id >= 1 && id <= 9999, "error code must be in range 0001-9999");
Self { id }
}
pub fn as_string(&self) -> String {
format!("{:04}", self.id)
}
}
#[macro_export]
macro_rules! error_code {
($id:literal) => {
const { $crate::diagnostics::DiagnosticId::new_from_macro($id) }
};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Level {
Bug,
Fatal,
Error,
Warning,
Note,
OnceNote,
Help,
OnceHelp,
FailureNote,
Allow,
}
impl Level {
pub fn to_str(self) -> &'static str {
match self {
Self::Bug => "error: internal compiler error",
Self::Fatal | Self::Error => "error",
Self::Warning => "warning",
Self::Note | Self::OnceNote => "note",
Self::Help | Self::OnceHelp => "help",
Self::FailureNote => "failure-note",
Self::Allow
=> unreachable!(),
}
}
#[inline]
pub fn is_error(self) -> bool {
match self {
Self::Bug | Self::Fatal | Self::Error | Self::FailureNote => true,
Self::Warning
| Self::Note
| Self::OnceNote
| Self::Help
| Self::OnceHelp
| Self::Allow => false,
}
}
#[inline]
pub const fn style(self) -> anstyle::Style {
anstyle::Style::new().fg_color(self.color()).bold()
}
#[inline]
pub const fn color(self) -> Option<Color> {
match self.ansi_color() {
Some(c) => Some(Color::Ansi(c)),
None => None,
}
}
#[inline]
pub const fn ansi_color(self) -> Option<AnsiColor> {
match self {
Self::Bug | Self::Fatal | Self::Error => Some(AnsiColor::BrightRed),
Self::Warning => Some(AnsiColor::BrightYellow),
Self::Note | Self::OnceNote => Some(AnsiColor::BrightGreen),
Self::Help | Self::OnceHelp => Some(AnsiColor::BrightCyan),
Self::FailureNote | Self::Allow => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Style {
MainHeaderMsg,
HeaderMsg,
LineAndColumn,
LineNumber,
Quotation,
UnderlinePrimary,
UnderlineSecondary,
LabelPrimary,
LabelSecondary,
NoStyle,
Level(Level),
Highlight,
Addition,
Removal,
}
impl Style {
pub const fn to_color_spec(self, level: Level) -> anstyle::Style {
use AnsiColor::*;
const BRIGHT_BLUE: Color = Color::Ansi(if cfg!(windows) { BrightCyan } else { BrightBlue });
const GREEN: Color = Color::Ansi(BrightGreen);
const MAGENTA: Color = Color::Ansi(BrightMagenta);
const RED: Color = Color::Ansi(BrightRed);
const WHITE: Color = Color::Ansi(BrightWhite);
let s = anstyle::Style::new();
match self {
Self::Addition => s.fg_color(Some(GREEN)),
Self::Removal => s.fg_color(Some(RED)),
Self::LineAndColumn => s,
Self::LineNumber => s.fg_color(Some(BRIGHT_BLUE)).bold(),
Self::Quotation => s,
Self::MainHeaderMsg => if cfg!(windows) { s.fg_color(Some(WHITE)) } else { s }.bold(),
Self::UnderlinePrimary | Self::LabelPrimary => s.fg_color(level.color()).bold(),
Self::UnderlineSecondary | Self::LabelSecondary => s.fg_color(Some(BRIGHT_BLUE)).bold(),
Self::HeaderMsg | Self::NoStyle => s,
Self::Level(level2) => s.fg_color(level2.color()).bold(),
Self::Highlight => s.fg_color(Some(MAGENTA)).bold(),
}
}
}
#[derive(Clone, Debug, PartialEq, Hash)]
pub struct SubDiagnostic {
pub level: Level,
pub messages: Vec<(DiagnosticMessage, Style)>,
pub span: MultiSpan,
}
impl SubDiagnostic {
pub fn label(&self) -> Cow<'_, str> {
flatten_messages(&self.messages)
}
}
#[must_use]
#[derive(Clone, Debug)]
pub struct Diagnostic {
pub(crate) level: Level,
pub messages: Vec<(DiagnosticMessage, Style)>,
pub span: MultiSpan,
pub children: Vec<SubDiagnostic>,
pub code: Option<DiagnosticId>,
pub created_at: &'static Location<'static>,
}
impl PartialEq for Diagnostic {
fn eq(&self, other: &Self) -> bool {
self.keys() == other.keys()
}
}
impl std::hash::Hash for Diagnostic {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.keys().hash(state);
}
}
impl Diagnostic {
#[track_caller]
pub fn new<M: Into<DiagnosticMessage>>(level: Level, msg: M) -> Self {
Self::new_with_messages(level, vec![(msg.into(), Style::NoStyle)])
}
#[track_caller]
pub fn new_with_messages(level: Level, messages: Vec<(DiagnosticMessage, Style)>) -> Self {
Self {
level,
messages,
code: None,
span: MultiSpan::new(),
children: vec![],
created_at: Location::caller(),
}
}
#[inline]
pub fn is_error(&self) -> bool {
self.level.is_error()
}
pub fn label(&self) -> Cow<'_, str> {
flatten_messages(&self.messages)
}
pub fn messages(&self) -> &[(DiagnosticMessage, Style)] {
&self.messages
}
pub fn level(&self) -> Level {
self.level
}
pub fn id(&self) -> Option<String> {
self.code.as_ref().map(|code| code.as_string())
}
fn keys(&self) -> impl PartialEq + std::hash::Hash + '_ {
(
&self.level,
&self.messages,
&self.code,
&self.span,
&self.children,
)
}
}
impl Diagnostic {
pub fn span(&mut self, span: impl Into<MultiSpan>) -> &mut Self {
self.span = span.into();
self
}
pub fn code(&mut self, code: impl Into<DiagnosticId>) -> &mut Self {
self.code = Some(code.into());
self
}
pub fn span_label(&mut self, span: Span, label: impl Into<DiagnosticMessage>) -> &mut Self {
self.span.push_span_label(span, label);
self
}
pub fn span_labels(
&mut self,
spans: impl IntoIterator<Item = Span>,
label: impl Into<DiagnosticMessage>,
) -> &mut Self {
let label = label.into();
for span in spans {
self.span_label(span, label.clone());
}
self
}
pub(crate) fn locations_note(&mut self, emitted_at: &Location<'_>) -> &mut Self {
let msg = format!(
"created at {},\n\
emitted at {}",
self.created_at, emitted_at
);
self.note(msg)
}
}
impl Diagnostic {
pub fn warn(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
self.sub(Level::Warning, msg, MultiSpan::new())
}
pub fn span_warn(
&mut self,
span: impl Into<MultiSpan>,
msg: impl Into<DiagnosticMessage>,
) -> &mut Self {
self.sub(Level::Warning, msg, span)
}
pub fn note(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
self.sub(Level::Note, msg, MultiSpan::new())
}
pub fn span_note(
&mut self,
span: impl Into<MultiSpan>,
msg: impl Into<DiagnosticMessage>,
) -> &mut Self {
self.sub(Level::Note, msg, span)
}
pub fn highlighted_note(
&mut self,
messages: Vec<(impl Into<DiagnosticMessage>, Style)>,
) -> &mut Self {
self.sub_with_highlights(Level::Note, messages, MultiSpan::new())
}
pub fn note_once(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
self.sub(Level::OnceNote, msg, MultiSpan::new())
}
pub fn span_note_once(
&mut self,
span: impl Into<MultiSpan>,
msg: impl Into<DiagnosticMessage>,
) -> &mut Self {
self.sub(Level::OnceNote, msg, span)
}
pub fn help(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
self.sub(Level::Help, msg, MultiSpan::new())
}
pub fn help_once(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
self.sub(Level::OnceHelp, msg, MultiSpan::new())
}
pub fn highlighted_help(
&mut self,
msgs: Vec<(impl Into<DiagnosticMessage>, Style)>,
) -> &mut Self {
self.sub_with_highlights(Level::Help, msgs, MultiSpan::new())
}
pub fn span_help(
&mut self,
span: impl Into<MultiSpan>,
msg: impl Into<DiagnosticMessage>,
) -> &mut Self {
self.sub(Level::Help, msg, span)
}
fn sub(
&mut self,
level: Level,
msg: impl Into<DiagnosticMessage>,
span: impl Into<MultiSpan>,
) -> &mut Self {
self.children.push(SubDiagnostic {
level,
messages: vec![(msg.into(), Style::NoStyle)],
span: span.into(),
});
self
}
fn sub_with_highlights(
&mut self,
level: Level,
messages: Vec<(impl Into<DiagnosticMessage>, Style)>,
span: MultiSpan,
) -> &mut Self {
let messages = messages.into_iter().map(|(m, s)| (m.into(), s)).collect();
self.children.push(SubDiagnostic { level, messages, span });
self
}
}
fn flatten_messages(messages: &[(DiagnosticMessage, Style)]) -> Cow<'_, str> {
match messages {
[] => Cow::Borrowed(""),
[(message, _)] => Cow::Borrowed(message.as_str()),
messages => messages.iter().map(|(msg, _)| msg.as_str()).collect(),
}
}