Skip to main content

driver_lang/
session.rs

1//! The ambient state of one compilation run.
2
3use alloc::borrow::Cow;
4use alloc::vec::Vec;
5
6use crate::diagnostic::Diagnostic;
7use crate::error::DriverError;
8
9/// The ambient state threaded through one compilation run.
10///
11/// Every [`Stage`](crate::Stage) in a pipeline receives `&mut Session<C>`. The
12/// session is where the shared, cross-stage state lives: the compilation
13/// *configuration* (`C` — target, options, input paths, whatever a language
14/// needs), and the *diagnostics* every stage emits as it works. It is the one
15/// piece of state a driver carries from the first phase to the last, so a later
16/// stage can read what an earlier one recorded.
17///
18/// The session is generic over the configuration type `C` and never inspects it —
19/// it hands out `&C` / `&mut C` and otherwise leaves it alone. This keeps the
20/// crate free of any opinion about *what* a language's configuration looks like.
21///
22/// Diagnostics are stored in emission order, and the count of
23/// [`error`](crate::Severity::Error)-severity diagnostics is maintained
24/// incrementally, so [`error_count`](Self::error_count) and
25/// [`has_errors`](Self::has_errors) are O(1) — a driver can check them between
26/// every phase without re-scanning the list.
27///
28/// # Examples
29///
30/// ```
31/// use driver_lang::{Diagnostic, Session};
32///
33/// // The configuration is whatever a language needs; here, a target triple.
34/// let mut session = Session::new("x86_64-unknown-linux-gnu");
35///
36/// session.warn("unused import `std::mem`");
37/// session.error("cannot find type `Foo`");
38///
39/// assert_eq!(session.config(), &"x86_64-unknown-linux-gnu");
40/// assert_eq!(session.error_count(), 1);   // the warning does not count
41/// assert_eq!(session.diagnostics().len(), 2);
42/// assert!(session.has_errors());
43/// ```
44#[derive(Debug, Clone)]
45pub struct Session<C> {
46    config: C,
47    diagnostics: Vec<Diagnostic>,
48    error_count: usize,
49}
50
51impl<C> Session<C> {
52    /// Create a session over a configuration, with no diagnostics recorded yet.
53    ///
54    /// # Examples
55    ///
56    /// ```
57    /// use driver_lang::Session;
58    ///
59    /// let session = Session::new(());
60    /// assert!(!session.has_errors());
61    /// assert!(session.diagnostics().is_empty());
62    /// ```
63    #[must_use]
64    pub fn new(config: C) -> Self {
65        Self {
66            config,
67            diagnostics: Vec::new(),
68            error_count: 0,
69        }
70    }
71
72    /// Borrow the compilation configuration.
73    ///
74    /// # Examples
75    ///
76    /// ```
77    /// use driver_lang::Session;
78    ///
79    /// let session = Session::new(42u32);
80    /// assert_eq!(*session.config(), 42);
81    /// ```
82    #[must_use]
83    #[inline]
84    pub fn config(&self) -> &C {
85        &self.config
86    }
87
88    /// Mutably borrow the compilation configuration, so a stage can update options
89    /// that later stages read.
90    #[must_use]
91    #[inline]
92    pub fn config_mut(&mut self) -> &mut C {
93        &mut self.config
94    }
95
96    /// Consume the session and return the configuration, discarding the recorded
97    /// diagnostics. Use [`take_diagnostics`](Self::take_diagnostics) first if you
98    /// need to keep them.
99    ///
100    /// # Examples
101    ///
102    /// ```
103    /// use driver_lang::Session;
104    ///
105    /// let session = Session::new(String::from("opts"));
106    /// let config = session.into_config();
107    /// assert_eq!(config, "opts");
108    /// ```
109    #[must_use]
110    pub fn into_config(self) -> C {
111        self.config
112    }
113
114    /// Record a [`Diagnostic`], updating the error count if it is an error.
115    ///
116    /// Returns `&mut Self` so emissions can be chained. This is the one path by
117    /// which diagnostics enter the session; the [`error`](Self::error),
118    /// [`warn`](Self::warn), and [`note`](Self::note) shorthands all route through
119    /// it.
120    ///
121    /// # Examples
122    ///
123    /// ```
124    /// use driver_lang::{Diagnostic, Session};
125    ///
126    /// let mut session = Session::new(());
127    /// session
128    ///     .emit(Diagnostic::error("first"))
129    ///     .emit(Diagnostic::warning("second"));
130    ///
131    /// assert_eq!(session.diagnostics().len(), 2);
132    /// assert_eq!(session.error_count(), 1);
133    /// ```
134    pub fn emit(&mut self, diagnostic: Diagnostic) -> &mut Self {
135        if diagnostic.is_error() {
136            // Saturating: a run that somehow emits usize::MAX errors should report
137            // a huge count, not wrap to zero and look successful.
138            self.error_count = self.error_count.saturating_add(1);
139        }
140        self.diagnostics.push(diagnostic);
141        self
142    }
143
144    /// Emit an [`error`](crate::Severity::Error)-severity diagnostic.
145    ///
146    /// Shorthand for `self.emit(Diagnostic::error(message))`.
147    ///
148    /// # Examples
149    ///
150    /// ```
151    /// use driver_lang::Session;
152    ///
153    /// let mut session = Session::new(());
154    /// session.error("cannot find value `x`");
155    /// assert!(session.has_errors());
156    /// ```
157    pub fn error(&mut self, message: impl Into<Cow<'static, str>>) -> &mut Self {
158        self.emit(Diagnostic::error(message))
159    }
160
161    /// Emit a [`warning`](crate::Severity::Warning)-severity diagnostic.
162    ///
163    /// Shorthand for `self.emit(Diagnostic::warning(message))`.
164    pub fn warn(&mut self, message: impl Into<Cow<'static, str>>) -> &mut Self {
165        self.emit(Diagnostic::warning(message))
166    }
167
168    /// Emit a [`note`](crate::Severity::Note)-severity diagnostic.
169    ///
170    /// Shorthand for `self.emit(Diagnostic::note(message))`.
171    pub fn note(&mut self, message: impl Into<Cow<'static, str>>) -> &mut Self {
172        self.emit(Diagnostic::note(message))
173    }
174
175    /// All diagnostics recorded so far, in emission order.
176    ///
177    /// # Examples
178    ///
179    /// ```
180    /// use driver_lang::Session;
181    ///
182    /// let mut session = Session::new(());
183    /// session.note("a").warn("b");
184    /// let messages: Vec<_> = session.diagnostics().iter().map(|d| d.message()).collect();
185    /// assert_eq!(messages, ["a", "b"]);
186    /// ```
187    #[must_use]
188    #[inline]
189    pub fn diagnostics(&self) -> &[Diagnostic] {
190        &self.diagnostics
191    }
192
193    /// How many [`error`](crate::Severity::Error)-severity diagnostics have been
194    /// emitted. Maintained incrementally, so this is O(1).
195    #[must_use]
196    #[inline]
197    pub fn error_count(&self) -> usize {
198        self.error_count
199    }
200
201    /// Whether any error has been emitted. O(1).
202    ///
203    /// # Examples
204    ///
205    /// ```
206    /// use driver_lang::Session;
207    ///
208    /// let mut session = Session::new(());
209    /// assert!(!session.has_errors());
210    /// session.warn("just a warning");
211    /// assert!(!session.has_errors());
212    /// session.error("a real error");
213    /// assert!(session.has_errors());
214    /// ```
215    #[must_use]
216    #[inline]
217    pub fn has_errors(&self) -> bool {
218        self.error_count != 0
219    }
220
221    /// Stop the run if any error has been emitted.
222    ///
223    /// This is the driver's checkpoint primitive: emit diagnostics through a phase,
224    /// then call `abort_if_errors` to turn accumulated errors into the
225    /// [`DriverError`] that ends the pipeline — the "aborting due to N previous
226    /// errors" step at the end of a compiler phase. Because a stage can emit
227    /// several errors before this is checked, one checkpoint can report many
228    /// diagnostics at once instead of stopping at the first.
229    ///
230    /// Returns `Ok(())` when the session is clean, so `session.abort_if_errors()?`
231    /// inside [`Stage::run`](crate::Stage::run) is a no-op on a healthy run and a
232    /// clean stop otherwise. The returned error has no stage name yet; the
233    /// [`Pipeline`](crate::Pipeline) stamps in the stage that made the call.
234    ///
235    /// # Errors
236    ///
237    /// Returns a [`DriverError`] reporting the error count when
238    /// [`has_errors`](Self::has_errors) is `true`.
239    ///
240    /// # Examples
241    ///
242    /// ```
243    /// use driver_lang::Session;
244    ///
245    /// let mut session = Session::new(());
246    /// assert!(session.abort_if_errors().is_ok());
247    ///
248    /// session.error("first");
249    /// session.error("second");
250    /// let err = session.abort_if_errors().unwrap_err();
251    /// assert_eq!(err.message(), "aborting due to 2 previous errors");
252    /// ```
253    pub fn abort_if_errors(&self) -> Result<(), DriverError> {
254        if self.error_count == 0 {
255            return Ok(());
256        }
257        let noun = if self.error_count == 1 {
258            "error"
259        } else {
260            "errors"
261        };
262        Err(DriverError::new(alloc::format!(
263            "aborting due to {} previous {noun}",
264            self.error_count
265        )))
266    }
267
268    /// Remove and return all recorded diagnostics, resetting the error count to
269    /// zero. The configuration is left untouched.
270    ///
271    /// Use this to drain diagnostics for rendering between runs, or to hand them
272    /// off before [`into_config`](Self::into_config) discards the session.
273    ///
274    /// # Examples
275    ///
276    /// ```
277    /// use driver_lang::Session;
278    ///
279    /// let mut session = Session::new(());
280    /// session.error("boom");
281    /// let drained = session.take_diagnostics();
282    ///
283    /// assert_eq!(drained.len(), 1);
284    /// assert!(!session.has_errors());          // count reset
285    /// assert!(session.diagnostics().is_empty());
286    /// ```
287    #[must_use]
288    pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
289        self.error_count = 0;
290        core::mem::take(&mut self.diagnostics)
291    }
292}
293
294#[cfg(test)]
295#[allow(clippy::unwrap_used, clippy::expect_used)]
296mod tests {
297    use super::*;
298    use crate::severity::Severity;
299    use alloc::vec::Vec;
300
301    #[test]
302    fn test_new_session_is_clean() {
303        let session = Session::new(());
304        assert!(!session.has_errors());
305        assert_eq!(session.error_count(), 0);
306        assert!(session.diagnostics().is_empty());
307    }
308
309    #[test]
310    fn test_config_accessors() {
311        let mut session = Session::new(1u32);
312        assert_eq!(*session.config(), 1);
313        *session.config_mut() = 2;
314        assert_eq!(*session.config(), 2);
315        assert_eq!(session.into_config(), 2);
316    }
317
318    #[test]
319    fn test_emit_counts_only_errors() {
320        let mut session = Session::new(());
321        session
322            .emit(Diagnostic::error("e"))
323            .emit(Diagnostic::warning("w"))
324            .emit(Diagnostic::note("n"));
325        assert_eq!(session.diagnostics().len(), 3);
326        assert_eq!(session.error_count(), 1);
327    }
328
329    #[test]
330    fn test_shorthands_set_expected_severity() {
331        let mut session = Session::new(());
332        session.error("e").warn("w").note("n");
333        let severities: Vec<_> = session.diagnostics().iter().map(|d| d.severity()).collect();
334        assert_eq!(
335            severities,
336            [Severity::Error, Severity::Warning, Severity::Note]
337        );
338    }
339
340    #[test]
341    fn test_diagnostics_preserve_emission_order() {
342        let mut session = Session::new(());
343        session.note("a").error("b").warn("c");
344        let messages: Vec<_> = session.diagnostics().iter().map(|d| d.message()).collect();
345        assert_eq!(messages, ["a", "b", "c"]);
346    }
347
348    #[test]
349    fn test_abort_if_errors_ok_when_clean() {
350        let mut session = Session::new(());
351        session.warn("only a warning");
352        assert!(session.abort_if_errors().is_ok());
353    }
354
355    #[test]
356    fn test_abort_if_errors_singular_message() {
357        let mut session = Session::new(());
358        session.error("one");
359        let err = session.abort_if_errors().unwrap_err();
360        assert_eq!(err.message(), "aborting due to 1 previous error");
361    }
362
363    #[test]
364    fn test_abort_if_errors_plural_message() {
365        let mut session = Session::new(());
366        session.error("one").error("two").error("three");
367        let err = session.abort_if_errors().unwrap_err();
368        assert_eq!(err.message(), "aborting due to 3 previous errors");
369    }
370
371    #[test]
372    fn test_take_diagnostics_drains_and_resets() {
373        let mut session = Session::new(());
374        session.error("a").warn("b");
375        let drained = session.take_diagnostics();
376        assert_eq!(drained.len(), 2);
377        assert!(!session.has_errors());
378        assert_eq!(session.error_count(), 0);
379        assert!(session.diagnostics().is_empty());
380    }
381}