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}