1use crate::Span;
6use anstyle::{AnsiColor, Color};
7use std::{
8 borrow::Cow,
9 fmt::{self, Write},
10 panic::Location,
11};
12
13mod builder;
14pub use builder::{DiagBuilder, EmissionGuarantee};
15
16mod context;
17pub use context::{DiagCtxt, DiagCtxtFlags};
18
19mod emitter;
20#[cfg(feature = "json")]
21pub use emitter::JsonEmitter;
22pub use emitter::{
23 DynEmitter, Emitter, HumanBufferEmitter, HumanEmitter, LocalEmitter, SilentEmitter,
24};
25
26mod message;
27pub use message::{DiagMsg, MultiSpan, SpanLabel};
28
29pub struct EmittedDiagnostics(pub(crate) String);
33
34impl fmt::Debug for EmittedDiagnostics {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 f.write_str(&self.0)
37 }
38}
39
40impl fmt::Display for EmittedDiagnostics {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 f.write_str(&self.0)
43 }
44}
45
46impl std::error::Error for EmittedDiagnostics {}
47
48impl EmittedDiagnostics {
49 pub fn is_empty(&self) -> bool {
51 self.0.is_empty()
52 }
53}
54
55#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
58pub struct ErrorGuaranteed(());
59
60impl fmt::Debug for ErrorGuaranteed {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 f.write_str("ErrorGuaranteed")
63 }
64}
65
66impl ErrorGuaranteed {
67 #[inline]
71 pub const fn new_unchecked() -> Self {
72 Self(())
73 }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
79pub struct BugAbort;
80
81pub struct ExplicitBug;
84
85pub struct FatalAbort;
87
88#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
99pub struct DiagId {
100 s: Cow<'static, str>,
101}
102
103impl DiagId {
104 pub fn new_str(s: impl Into<Cow<'static, str>>) -> Self {
109 Self { s: s.into() }
110 }
111
112 #[doc(hidden)]
116 #[cfg_attr(debug_assertions, track_caller)]
117 pub fn new_from_macro(id: u32) -> Self {
118 debug_assert!((1..=9999).contains(&id), "error code must be in range 0001-9999");
119 Self { s: Cow::Owned(format!("{id:04}")) }
120 }
121
122 pub fn as_string(&self) -> String {
124 self.s.to_string()
125 }
126}
127
128#[macro_export]
137macro_rules! error_code {
138 ($id:literal) => {
139 $crate::diagnostics::DiagId::new_from_macro($id)
140 };
141}
142
143#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
145pub enum Level {
146 Bug,
150
151 Fatal,
156
157 Error,
162
163 Warning,
167
168 Note,
173
174 OnceNote,
178
179 Help,
184
185 OnceHelp,
189
190 FailureNote,
194
195 Allow,
199}
200
201impl Level {
202 pub fn to_str(self) -> &'static str {
204 match self {
205 Self::Bug => "error: internal compiler error",
206 Self::Fatal | Self::Error => "error",
207 Self::Warning => "warning",
208 Self::Note | Self::OnceNote => "note",
209 Self::Help | Self::OnceHelp => "help",
210 Self::FailureNote => "failure-note",
211 Self::Allow
212 => unreachable!(),
214 }
215 }
216
217 #[inline]
219 pub fn is_error(self) -> bool {
220 match self {
221 Self::Bug | Self::Fatal | Self::Error | Self::FailureNote => true,
222
223 Self::Warning
224 | Self::Note
225 | Self::OnceNote
226 | Self::Help
227 | Self::OnceHelp
228 | Self::Allow => false,
229 }
230 }
231
232 #[inline]
234 pub const fn style(self) -> anstyle::Style {
235 anstyle::Style::new().fg_color(self.color()).bold()
236 }
237
238 #[inline]
240 pub const fn color(self) -> Option<Color> {
241 match self.ansi_color() {
242 Some(c) => Some(Color::Ansi(c)),
243 None => None,
244 }
245 }
246
247 #[inline]
249 pub const fn ansi_color(self) -> Option<AnsiColor> {
250 match self {
252 Self::Bug | Self::Fatal | Self::Error => Some(AnsiColor::BrightRed),
253 Self::Warning => Some(AnsiColor::BrightYellow),
254 Self::Note | Self::OnceNote => Some(AnsiColor::BrightGreen),
255 Self::Help | Self::OnceHelp => Some(AnsiColor::BrightCyan),
256 Self::FailureNote | Self::Allow => None,
257 }
258 }
259}
260
261#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
262pub enum Style {
263 MainHeaderMsg,
264 HeaderMsg,
265 LineAndColumn,
266 LineNumber,
267 Quotation,
268 UnderlinePrimary,
269 UnderlineSecondary,
270 LabelPrimary,
271 LabelSecondary,
272 NoStyle,
273 Level(Level),
274 Highlight,
275 Addition,
276 Removal,
277}
278
279impl Style {
280 pub const fn to_color_spec(self, level: Level) -> anstyle::Style {
282 use AnsiColor::*;
283
284 const BRIGHT_BLUE: Color = Color::Ansi(if cfg!(windows) { BrightCyan } else { BrightBlue });
288 const GREEN: Color = Color::Ansi(BrightGreen);
289 const MAGENTA: Color = Color::Ansi(BrightMagenta);
290 const RED: Color = Color::Ansi(BrightRed);
291 const WHITE: Color = Color::Ansi(BrightWhite);
292
293 let s = anstyle::Style::new();
294 match self {
295 Self::Addition => s.fg_color(Some(GREEN)),
296 Self::Removal => s.fg_color(Some(RED)),
297 Self::LineAndColumn => s,
298 Self::LineNumber => s.fg_color(Some(BRIGHT_BLUE)).bold(),
299 Self::Quotation => s,
300 Self::MainHeaderMsg => if cfg!(windows) { s.fg_color(Some(WHITE)) } else { s }.bold(),
301 Self::UnderlinePrimary | Self::LabelPrimary => s.fg_color(level.color()).bold(),
302 Self::UnderlineSecondary | Self::LabelSecondary => s.fg_color(Some(BRIGHT_BLUE)).bold(),
303 Self::HeaderMsg | Self::NoStyle => s,
304 Self::Level(level2) => s.fg_color(level2.color()).bold(),
305 Self::Highlight => s.fg_color(Some(MAGENTA)).bold(),
306 }
307 }
308}
309
310#[derive(Clone, Debug, PartialEq, Hash)]
313pub struct SubDiagnostic {
314 pub level: Level,
315 pub messages: Vec<(DiagMsg, Style)>,
316 pub span: MultiSpan,
317}
318
319impl SubDiagnostic {
320 pub fn label(&self) -> Cow<'_, str> {
322 flatten_messages(&self.messages, false, self.level)
323 }
324
325 pub fn label_with_style(&self, supports_color: bool) -> Cow<'_, str> {
327 flatten_messages(&self.messages, supports_color, self.level)
328 }
329}
330
331#[must_use]
333#[derive(Clone, Debug)]
334pub struct Diag {
335 pub(crate) level: Level,
336
337 pub messages: Vec<(DiagMsg, Style)>,
338 pub span: MultiSpan,
339 pub children: Vec<SubDiagnostic>,
340 pub code: Option<DiagId>,
341
342 pub created_at: &'static Location<'static>,
343}
344
345impl PartialEq for Diag {
346 fn eq(&self, other: &Self) -> bool {
347 self.keys() == other.keys()
348 }
349}
350
351impl std::hash::Hash for Diag {
352 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
353 self.keys().hash(state);
354 }
355}
356
357impl Diag {
358 #[track_caller]
360 pub fn new<M: Into<DiagMsg>>(level: Level, msg: M) -> Self {
361 Self::new_with_messages(level, vec![(msg.into(), Style::NoStyle)])
362 }
363
364 #[track_caller]
366 pub fn new_with_messages(level: Level, messages: Vec<(DiagMsg, Style)>) -> Self {
367 Self {
368 level,
369 messages,
370 code: None,
371 span: MultiSpan::new(),
372 children: vec![],
373 created_at: Location::caller(),
378 }
379 }
380
381 #[inline]
383 pub fn is_error(&self) -> bool {
384 self.level.is_error()
385 }
386
387 pub fn label(&self) -> Cow<'_, str> {
389 flatten_messages(&self.messages, false, self.level)
390 }
391
392 pub fn label_with_style(&self, supports_color: bool) -> Cow<'_, str> {
394 flatten_messages(&self.messages, supports_color, self.level)
395 }
396
397 pub fn messages(&self) -> &[(DiagMsg, Style)] {
399 &self.messages
400 }
401
402 pub fn level(&self) -> Level {
404 self.level
405 }
406
407 pub fn id(&self) -> Option<String> {
409 self.code.as_ref().map(|code| code.as_string())
410 }
411
412 fn keys(&self) -> impl PartialEq + std::hash::Hash + '_ {
414 (
415 &self.level,
416 &self.messages,
417 &self.code,
419 &self.span,
420 &self.children,
423 )
424 }
425}
426
427impl Diag {
429 pub fn span(&mut self, span: impl Into<MultiSpan>) -> &mut Self {
431 self.span = span.into();
432 self
433 }
434
435 pub fn code(&mut self, code: impl Into<DiagId>) -> &mut Self {
437 self.code = Some(code.into());
438 self
439 }
440
441 pub fn span_label(&mut self, span: Span, label: impl Into<DiagMsg>) -> &mut Self {
450 self.span.push_span_label(span, label);
451 self
452 }
453
454 pub fn span_labels(
457 &mut self,
458 spans: impl IntoIterator<Item = Span>,
459 label: impl Into<DiagMsg>,
460 ) -> &mut Self {
461 let label = label.into();
462 for span in spans {
463 self.span_label(span, label.clone());
464 }
465 self
466 }
467
468 pub(crate) fn locations_note(&mut self, emitted_at: &Location<'_>) -> &mut Self {
470 let msg = format!(
471 "created at {},\n\
472 emitted at {}",
473 self.created_at, emitted_at
474 );
475 self.note(msg)
476 }
477}
478
479impl Diag {
481 pub fn warn(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
483 self.sub(Level::Warning, msg, MultiSpan::new())
484 }
485
486 pub fn span_warn(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
489 self.sub(Level::Warning, msg, span)
490 }
491
492 pub fn note(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
494 self.sub(Level::Note, msg, MultiSpan::new())
495 }
496
497 pub fn span_note(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
500 self.sub(Level::Note, msg, span)
501 }
502
503 pub fn highlighted_note(&mut self, messages: Vec<(impl Into<DiagMsg>, Style)>) -> &mut Self {
504 self.sub_with_highlights(Level::Note, messages, MultiSpan::new())
505 }
506
507 pub fn note_once(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
510 self.sub(Level::OnceNote, msg, MultiSpan::new())
511 }
512
513 pub fn span_note_once(
516 &mut self,
517 span: impl Into<MultiSpan>,
518 msg: impl Into<DiagMsg>,
519 ) -> &mut Self {
520 self.sub(Level::OnceNote, msg, span)
521 }
522
523 pub fn help(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
525 self.sub(Level::Help, msg, MultiSpan::new())
526 }
527
528 pub fn help_once(&mut self, msg: impl Into<DiagMsg>) -> &mut Self {
531 self.sub(Level::OnceHelp, msg, MultiSpan::new())
532 }
533
534 pub fn highlighted_help(&mut self, msgs: Vec<(impl Into<DiagMsg>, Style)>) -> &mut Self {
536 self.sub_with_highlights(Level::Help, msgs, MultiSpan::new())
537 }
538
539 pub fn span_help(&mut self, span: impl Into<MultiSpan>, msg: impl Into<DiagMsg>) -> &mut Self {
542 self.sub(Level::Help, msg, span)
543 }
544
545 fn sub(
546 &mut self,
547 level: Level,
548 msg: impl Into<DiagMsg>,
549 span: impl Into<MultiSpan>,
550 ) -> &mut Self {
551 self.children.push(SubDiagnostic {
552 level,
553 messages: vec![(msg.into(), Style::NoStyle)],
554 span: span.into(),
555 });
556 self
557 }
558
559 fn sub_with_highlights(
560 &mut self,
561 level: Level,
562 messages: Vec<(impl Into<DiagMsg>, Style)>,
563 span: MultiSpan,
564 ) -> &mut Self {
565 let messages = messages.into_iter().map(|(m, s)| (m.into(), s)).collect();
566 self.children.push(SubDiagnostic { level, messages, span });
567 self
568 }
569}
570
571fn flatten_messages(messages: &[(DiagMsg, Style)], with_style: bool, level: Level) -> Cow<'_, str> {
573 if with_style {
574 match messages {
575 [] => Cow::Borrowed(""),
576 [(msg, Style::NoStyle)] => Cow::Borrowed(msg.as_str()),
577 [(msg, style)] => {
578 let mut res = String::new();
579 write_fmt(&mut res, msg, style, level);
580 Cow::Owned(res)
581 }
582 messages => {
583 let mut res = String::new();
584 for (msg, style) in messages {
585 match style {
586 Style::NoStyle => res.push_str(msg.as_str()),
587 _ => write_fmt(&mut res, msg, style, level),
588 }
589 }
590 Cow::Owned(res)
591 }
592 }
593 } else {
594 match messages {
595 [] => Cow::Borrowed(""),
596 [(message, _)] => Cow::Borrowed(message.as_str()),
597 messages => messages.iter().map(|(msg, _)| msg.as_str()).collect(),
598 }
599 }
600}
601
602fn write_fmt(output: &mut String, msg: &DiagMsg, style: &Style, level: Level) {
603 let ansi_style = style.to_color_spec(level);
604 write!(output, "{}{}{}", ansi_style.render(), msg.as_str(), ansi_style.render_reset()).unwrap();
605}
606
607#[cfg(test)]
608mod tests {
609 use super::*;
610
611 #[test]
612 fn test_styled_messages() {
613 let mut diag = Diag::new(Level::Note, "test");
615
616 diag.highlighted_note(vec![
617 ("plain text ", Style::NoStyle),
618 ("removed", Style::Removal),
619 (" middle ", Style::NoStyle),
620 ("added", Style::Addition),
621 ]);
622
623 let sub = &diag.children[0];
624
625 let plain = sub.label();
627 assert_eq!(plain, "plain text removed middle added");
628
629 let styled = sub.label_with_style(true);
631 assert_eq!(
632 styled.to_string(),
633 "plain text \u{1b}[91mremoved\u{1b}[0m middle \u{1b}[92madded\u{1b}[0m".to_string()
634 );
635 }
636}