1use crate::Span;
6use anstyle::{AnsiColor, Color};
7use std::{borrow::Cow, fmt, panic::Location};
8
9mod builder;
10pub use builder::{DiagBuilder, EmissionGuarantee};
11
12mod context;
13pub use context::{DiagCtxt, DiagCtxtFlags};
14
15mod emitter;
16#[cfg(feature = "json")]
17pub use emitter::JsonEmitter;
18pub use emitter::{
19 DynEmitter, Emitter, HumanBufferEmitter, HumanEmitter, LocalEmitter, SilentEmitter,
20};
21
22mod message;
23pub use message::{DiagMsg, MultiSpan, SpanLabel};
24
25pub struct EmittedDiagnostics(pub(crate) String);
29
30impl fmt::Debug for EmittedDiagnostics {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 f.write_str(&self.0)
33 }
34}
35
36impl fmt::Display for EmittedDiagnostics {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 f.write_str(&self.0)
39 }
40}
41
42impl std::error::Error for EmittedDiagnostics {}
43
44impl EmittedDiagnostics {
45 pub fn is_empty(&self) -> bool {
47 self.0.is_empty()
48 }
49}
50
51#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
54pub struct ErrorGuaranteed(());
55
56impl fmt::Debug for ErrorGuaranteed {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 f.write_str("ErrorGuaranteed")
59 }
60}
61
62impl ErrorGuaranteed {
63 #[inline]
67 pub const fn new_unchecked() -> Self {
68 Self(())
69 }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
75pub struct BugAbort;
76
77pub struct ExplicitBug;
80
81pub struct FatalAbort;
83
84#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
95pub struct DiagId {
96 s: Cow<'static, str>,
97}
98
99impl DiagId {
100 pub fn new_str(s: impl Into<Cow<'static, str>>) -> Self {
105 Self { s: s.into() }
106 }
107
108 #[doc(hidden)]
112 #[cfg_attr(debug_assertions, track_caller)]
113 pub fn new_from_macro(id: u32) -> Self {
114 debug_assert!((1..=9999).contains(&id), "error code must be in range 0001-9999");
115 Self { s: Cow::Owned(format!("{id:04}")) }
116 }
117
118 pub fn as_string(&self) -> String {
120 self.s.to_string()
121 }
122}
123
124#[macro_export]
133macro_rules! error_code {
134 ($id:literal) => {
135 $crate::diagnostics::DiagId::new_from_macro($id)
136 };
137}
138
139#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
141pub enum Level {
142 Bug,
146
147 Fatal,
152
153 Error,
158
159 Warning,
163
164 Note,
169
170 OnceNote,
174
175 Help,
180
181 OnceHelp,
185
186 FailureNote,
190
191 Allow,
195}
196
197impl Level {
198 pub fn to_str(self) -> &'static str {
200 match self {
201 Self::Bug => "error: internal compiler error",
202 Self::Fatal | Self::Error => "error",
203 Self::Warning => "warning",
204 Self::Note | Self::OnceNote => "note",
205 Self::Help | Self::OnceHelp => "help",
206 Self::FailureNote => "failure-note",
207 Self::Allow
208 => unreachable!(),
210 }
211 }
212
213 #[inline]
215 pub fn is_error(self) -> bool {
216 match self {
217 Self::Bug | Self::Fatal | Self::Error | Self::FailureNote => true,
218
219 Self::Warning
220 | Self::Note
221 | Self::OnceNote
222 | Self::Help
223 | Self::OnceHelp
224 | Self::Allow => false,
225 }
226 }
227
228 #[inline]
230 pub const fn style(self) -> anstyle::Style {
231 anstyle::Style::new().fg_color(self.color()).bold()
232 }
233
234 #[inline]
236 pub const fn color(self) -> Option<Color> {
237 match self.ansi_color() {
238 Some(c) => Some(Color::Ansi(c)),
239 None => None,
240 }
241 }
242
243 #[inline]
245 pub const fn ansi_color(self) -> Option<AnsiColor> {
246 match self {
248 Self::Bug | Self::Fatal | Self::Error => Some(AnsiColor::BrightRed),
249 Self::Warning => Some(AnsiColor::BrightYellow),
250 Self::Note | Self::OnceNote => Some(AnsiColor::BrightGreen),
251 Self::Help | Self::OnceHelp => Some(AnsiColor::BrightCyan),
252 Self::FailureNote | Self::Allow => None,
253 }
254 }
255}
256
257#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
258pub enum Style {
259 MainHeaderMsg,
260 HeaderMsg,
261 LineAndColumn,
262 LineNumber,
263 Quotation,
264 UnderlinePrimary,
265 UnderlineSecondary,
266 LabelPrimary,
267 LabelSecondary,
268 NoStyle,
269 Level(Level),
270 Highlight,
271 Addition,
272 Removal,
273}
274
275impl Style {
276 pub const fn to_color_spec(self, level: Level) -> anstyle::Style {
278 use AnsiColor::*;
279
280 const BRIGHT_BLUE: Color = Color::Ansi(if cfg!(windows) { BrightCyan } else { BrightBlue });
284 const GREEN: Color = Color::Ansi(BrightGreen);
285 const MAGENTA: Color = Color::Ansi(BrightMagenta);
286 const RED: Color = Color::Ansi(BrightRed);
287 const WHITE: Color = Color::Ansi(BrightWhite);
288
289 let s = anstyle::Style::new();
290 match self {
291 Self::Addition => s.fg_color(Some(GREEN)),
292 Self::Removal => s.fg_color(Some(RED)),
293 Self::LineAndColumn => s,
294 Self::LineNumber => s.fg_color(Some(BRIGHT_BLUE)).bold(),
295 Self::Quotation => s,
296 Self::MainHeaderMsg => if cfg!(windows) { s.fg_color(Some(WHITE)) } else { s }.bold(),
297 Self::UnderlinePrimary | Self::LabelPrimary => s.fg_color(level.color()).bold(),
298 Self::UnderlineSecondary | Self::LabelSecondary => s.fg_color(Some(BRIGHT_BLUE)).bold(),
299 Self::HeaderMsg | Self::NoStyle => s,
300 Self::Level(level2) => s.fg_color(level2.color()).bold(),
301 Self::Highlight => s.fg_color(Some(MAGENTA)).bold(),
302 }
303 }
304}
305
306#[derive(Clone, Debug, PartialEq, Hash)]
309pub struct SubDiagnostic {
310 pub level: Level,
311 pub messages: Vec<(DiagMsg, Style)>,
312 pub span: MultiSpan,
313}
314
315impl SubDiagnostic {
316 pub fn label(&self) -> Cow<'_, str> {
318 flatten_messages(&self.messages)
319 }
320}
321
322#[must_use]
324#[derive(Clone, Debug)]
325pub struct Diag {
326 pub(crate) level: Level,
327
328 pub messages: Vec<(DiagMsg, Style)>,
329 pub span: MultiSpan,
330 pub children: Vec<SubDiagnostic>,
331 pub code: Option<DiagId>,
332
333 pub created_at: &'static Location<'static>,
334}
335
336impl PartialEq for Diag {
337 fn eq(&self, other: &Self) -> bool {
338 self.keys() == other.keys()
339 }
340}
341
342impl std::hash::Hash for Diag {
343 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
344 self.keys().hash(state);
345 }
346}
347
348impl Diag {
349 #[track_caller]
351 pub fn new<M: Into<DiagMsg>>(level: Level, msg: M) -> Self {
352 Self::new_with_messages(level, vec![(msg.into(), Style::NoStyle)])
353 }
354
355 #[track_caller]
357 pub fn new_with_messages(level: Level, messages: Vec<(DiagMsg, Style)>) -> Self {
358 Self {
359 level,
360 messages,
361 code: None,
362 span: MultiSpan::new(),
363 children: vec![],
364 created_at: Location::caller(),
369 }
370 }
371
372 #[inline]
374 pub fn is_error(&self) -> bool {
375 self.level.is_error()
376 }
377
378 pub fn label(&self) -> Cow<'_, str> {
380 flatten_messages(&self.messages)
381 }
382
383 pub fn messages(&self) -> &[(DiagMsg, Style)] {
385 &self.messages
386 }
387
388 pub fn level(&self) -> Level {
390 self.level
391 }
392
393 pub fn id(&self) -> Option<String> {
395 self.code.as_ref().map(|code| code.as_string())
396 }
397
398 fn keys(&self) -> impl PartialEq + std::hash::Hash + '_ {
400 (
401 &self.level,
402 &self.messages,
403 &self.code,
405 &self.span,
406 &self.children,
409 )
410 }
411}
412
413impl Diag {
415 pub fn span(&mut self, span: impl Into<MultiSpan>) -> &mut Self {
417 self.span = span.into();
418 self
419 }
420
421 pub fn code(&mut self, code: impl Into<DiagId>) -> &mut Self {
423 self.code = Some(code.into());
424 self
425 }
426
427 pub fn span_label(&mut self, span: Span, label: impl Into<DiagMsg>) -> &mut Self {
436 self.span.push_span_label(span, label);
437 self
438 }
439
440 pub fn span_labels(
443 &mut self,
444 spans: impl IntoIterator<Item = Span>,
445 label: impl Into<DiagMsg>,
446 ) -> &mut Self {
447 let label = label.into();
448 for span in spans {
449 self.span_label(span, label.clone());
450 }
451 self
452 }
453
454 pub(crate) fn locations_note(&mut self, emitted_at: &Location<'_>) -> &mut Self {
456 let msg = format!(
457 "created at {},\n\
458 emitted at {}",
459 self.created_at, emitted_at
460 );
461 self.note(msg)
462 }
463}
464
465impl Diag {
467 pub fn warn(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
469 self.sub(Level::Warning, msg, MultiSpan::new())
470 }
471
472 pub fn span_warn(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
475 self.sub(Level::Warning, msg, span)
476 }
477
478 pub fn note(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
480 self.sub(Level::Note, msg, MultiSpan::new())
481 }
482
483 pub fn span_note(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
486 self.sub(Level::Note, msg, span)
487 }
488
489 pub fn highlighted_note(&mut self, messages: Vec<(impl Into<DiagMsg>, Style)>) -> &mut Self {
490 self.sub_with_highlights(Level::Note, messages, MultiSpan::new())
491 }
492
493 pub fn note_once(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
496 self.sub(Level::OnceNote, msg, MultiSpan::new())
497 }
498
499 pub fn span_note_once(
502 &mut self,
503 span: impl Into<MultiSpan>,
504 msg: impl Into<DiagMsg>,
505 ) -> &mut Self {
506 self.sub(Level::OnceNote, msg, span)
507 }
508
509 pub fn help(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
511 self.sub(Level::Help, msg, MultiSpan::new())
512 }
513
514 pub fn help_once(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
517 self.sub(Level::OnceHelp, msg, MultiSpan::new())
518 }
519
520 pub fn highlighted_help(&mut self, msgs: Vec<(impl Into<DiagMsg>, Style)>) -> &mut Self {
522 self.sub_with_highlights(Level::Help, msgs, MultiSpan::new())
523 }
524
525 pub fn span_help(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
528 self.sub(Level::Help, msg, span)
529 }
530
531 fn sub(
532 &mut self,
533 level: Level,
534 msg: impl Into<DiagMsg>,
535 span: impl Into<MultiSpan>,
536 ) -> &mut Self {
537 self.children.push(SubDiagnostic {
538 level,
539 messages: vec![(msg.into(), Style::NoStyle)],
540 span: span.into(),
541 });
542 self
543 }
544
545 fn sub_with_highlights(
546 &mut self,
547 level: Level,
548 messages: Vec<(impl Into<DiagMsg>, Style)>,
549 span: MultiSpan,
550 ) -> &mut Self {
551 let messages = messages.into_iter().map(|(m, s)| (m.into(), s)).collect();
552 self.children.push(SubDiagnostic { level, messages, span });
553 self
554 }
555}
556
557fn flatten_messages(messages: &[(DiagMsg, Style)]) -> Cow<'_, str> {
559 match messages {
560 [] => Cow::Borrowed(""),
561 [(message, _)] => Cow::Borrowed(message.as_str()),
562 messages => messages.iter().map(|(msg, _)| msg.as_str()).collect(),
563 }
564}