Skip to main content

dada_ir_ast/
diagnostic.rs

1use std::fmt::Display;
2
3use crate::{
4    DebugEvent, DebugEventPayload,
5    span::{AbsoluteSpan, Span},
6};
7use dada_util::debug;
8use salsa::{Accumulator, Update};
9use serde::Serialize;
10
11mod render;
12
13/// Signals that a diagnostic was reported at the given span.
14#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Update, Debug, Serialize)]
15pub struct Reported(pub AbsoluteSpan);
16
17impl Reported {
18    pub fn span(self, db: &dyn crate::Db) -> Span<'_> {
19        self.0.into_span(db)
20    }
21}
22
23/// Signals that this may complete or report a diagnostic.
24/// In practice we use this to mean an error.
25pub type Errors<T> = Result<T, Reported>;
26
27/// A diagnostic to be reported to the user.
28#[salsa::accumulator]
29#[derive(PartialEq, Eq, Hash, Serialize, Clone, Debug)]
30#[must_use]
31pub struct Diagnostic {
32    /// Level of the message.
33    pub level: Level,
34
35    /// Main location of the message.
36    pub span: AbsoluteSpan,
37
38    /// Message to be printed.
39    pub message: String,
40
41    /// Labels to be included.
42    /// Add labels with the `label` helper method.
43    pub labels: Vec<DiagnosticLabel>,
44
45    /// Child diagnostics.
46    pub children: Vec<Diagnostic>,
47}
48
49#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
50pub enum Level {
51    Note,
52    Help,
53    Info,
54    Warning,
55    Error,
56}
57
58/// A label to be included in the diagnostic.
59#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
60pub struct DiagnosticLabel {
61    /// Level of the label.
62    pub level: Level,
63
64    /// The span to be labeled.
65    /// Must have the same source file as the main diagnostic!
66    pub span: AbsoluteSpan,
67
68    /// Message to be printed for the label.
69    pub message: String,
70}
71
72#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize)]
73pub struct RenderOptions {
74    pub no_color: bool,
75}
76
77impl Diagnostic {
78    pub fn error<'db>(db: &'db dyn crate::Db, span: Span<'db>, message: impl Display) -> Self {
79        Self::new(db, Level::Error, span, message)
80    }
81
82    pub fn help<'db>(db: &'db dyn crate::Db, span: Span<'db>, message: impl Display) -> Self {
83        Self::new(db, Level::Help, span, message)
84    }
85
86    pub fn info<'db>(db: &'db dyn crate::Db, span: Span<'db>, message: impl Display) -> Self {
87        Self::new(db, Level::Info, span, message)
88    }
89
90    pub fn new<'db>(
91        db: &'db dyn crate::Db,
92        level: Level,
93        span: Span<'db>,
94        message: impl Display,
95    ) -> Self {
96        let message = message.to_string();
97        Diagnostic {
98            span: span.absolute_span(db),
99            level,
100            children: vec![],
101            message,
102            labels: vec![],
103        }
104    }
105
106    pub fn report(self, db: &dyn crate::Db) -> Reported {
107        debug!("reporting diagnostic", self);
108        let span = self.span;
109
110        if let Some(debug_tx) = db.debug_tx() {
111            debug_tx
112                .send(DebugEvent {
113                    url: span.source_file.url(db).clone(),
114                    start: span.start,
115                    end: span.end,
116                    payload: DebugEventPayload::Diagnostic(self.clone()),
117                })
118                .unwrap();
119        }
120
121        self.accumulate(db);
122
123        Reported(span)
124    }
125
126    pub fn label(
127        mut self,
128        db: &dyn crate::Db,
129        level: Level,
130        span: Span,
131        message: impl Display,
132    ) -> Self {
133        let span = span.absolute_span(db);
134        assert_eq!(self.span.source_file, span.source_file);
135        self.labels.push(DiagnosticLabel {
136            level,
137            span,
138            message: message.to_string(),
139        });
140        self
141    }
142
143    pub fn child(mut self, child: Diagnostic) -> Self {
144        self.children.push(child);
145        self
146    }
147
148    pub fn render(&self, db: &dyn crate::Db, opts: &RenderOptions) -> String {
149        render::render(db, opts, self)
150    }
151}
152
153pub fn report_all(db: &dyn crate::Db, diagnostics: Vec<Diagnostic>) {
154    for diagnostic in diagnostics {
155        diagnostic.report(db);
156    }
157}
158
159pub fn ordinal(n: usize) -> impl std::fmt::Display {
160    match n % 10 {
161        1 => format!("{n}st"),
162        2 => format!("{n}nd"),
163        3 => format!("{n}rd"),
164        _ => format!("{n}th"),
165    }
166}
167
168/// Create a value from a reported error.
169pub trait Err<'db> {
170    fn err(db: &'db dyn crate::Db, reported: Reported) -> Self;
171}
172
173impl<'db, T> Err<'db> for Errors<T> {
174    fn err(_db: &'db dyn crate::Db, reported: Reported) -> Self {
175        Err(reported)
176    }
177}
178
179impl<'db> Err<'db> for Reported {
180    fn err(_db: &'db dyn crate::Db, reported: Reported) -> Self {
181        reported
182    }
183}