Skip to main content

compose_lens/diagnostic/
mod.rs

1//! Stable, source-aware diagnostics.
2
3use crate::source::SourceSpan;
4use std::fmt;
5
6/// A stable machine-readable diagnostic identifier.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
8pub struct DiagnosticCode(&'static str);
9
10impl DiagnosticCode {
11    /// Creates a diagnostic code from a stable static string.
12    #[must_use]
13    pub const fn new(value: &'static str) -> Self {
14        Self(value)
15    }
16
17    /// Returns the code as text.
18    #[must_use]
19    pub const fn as_str(self) -> &'static str {
20        self.0
21    }
22}
23
24impl fmt::Display for DiagnosticCode {
25    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
26        formatter.write_str(self.0)
27    }
28}
29
30/// The severity assigned to a diagnostic.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
32pub enum Severity {
33    /// The input cannot be interpreted as requested.
34    Error,
35    /// The input is usable but deserves attention.
36    Warning,
37    /// Additional information that does not indicate a problem.
38    Note,
39}
40
41/// The role of a source label in a diagnostic.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub enum LabelKind {
44    /// The location primarily responsible for the diagnostic.
45    Primary,
46    /// A related location that adds context.
47    Secondary,
48}
49
50/// A message attached to a source span.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct DiagnosticLabel {
53    kind: LabelKind,
54    span: SourceSpan,
55    message: String,
56}
57
58impl DiagnosticLabel {
59    /// Creates a primary source label.
60    #[must_use]
61    pub fn primary(span: SourceSpan, message: impl Into<String>) -> Self {
62        Self {
63            kind: LabelKind::Primary,
64            span,
65            message: message.into(),
66        }
67    }
68
69    /// Creates a secondary source label.
70    #[must_use]
71    pub fn secondary(span: SourceSpan, message: impl Into<String>) -> Self {
72        Self {
73            kind: LabelKind::Secondary,
74            span,
75            message: message.into(),
76        }
77    }
78
79    /// Returns the label role.
80    #[must_use]
81    pub const fn kind(&self) -> LabelKind {
82        self.kind
83    }
84
85    /// Returns the labeled source span.
86    #[must_use]
87    pub const fn span(&self) -> SourceSpan {
88        self.span
89    }
90
91    /// Returns the human-readable label.
92    #[must_use]
93    pub fn message(&self) -> &str {
94        &self.message
95    }
96}
97
98/// A structured diagnostic with stable identity and source labels.
99#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct Diagnostic {
101    code: DiagnosticCode,
102    severity: Severity,
103    message: String,
104    labels: Vec<DiagnosticLabel>,
105    notes: Vec<String>,
106}
107
108impl Diagnostic {
109    /// Creates a diagnostic without source labels.
110    #[must_use]
111    pub fn new(code: DiagnosticCode, severity: Severity, message: impl Into<String>) -> Self {
112        Self {
113            code,
114            severity,
115            message: message.into(),
116            labels: Vec::new(),
117            notes: Vec::new(),
118        }
119    }
120
121    /// Appends a source label.
122    #[must_use]
123    pub fn with_label(mut self, label: DiagnosticLabel) -> Self {
124        self.labels.push(label);
125        self
126    }
127
128    /// Appends a human-readable note.
129    #[must_use]
130    pub fn with_note(mut self, note: impl Into<String>) -> Self {
131        self.notes.push(note.into());
132        self
133    }
134
135    /// Returns the stable diagnostic code.
136    #[must_use]
137    pub const fn code(&self) -> DiagnosticCode {
138        self.code
139    }
140
141    /// Returns the severity.
142    #[must_use]
143    pub const fn severity(&self) -> Severity {
144        self.severity
145    }
146
147    /// Returns the human-readable summary.
148    #[must_use]
149    pub fn message(&self) -> &str {
150        &self.message
151    }
152
153    /// Returns the source labels in display order.
154    #[must_use]
155    pub fn labels(&self) -> &[DiagnosticLabel] {
156        &self.labels
157    }
158
159    /// Returns the additional notes in display order.
160    #[must_use]
161    pub fn notes(&self) -> &[String] {
162        &self.notes
163    }
164}