Skip to main content

driver_lang/
error.rs

1//! The error that halts a compilation run.
2
3use alloc::borrow::Cow;
4use core::fmt;
5
6/// The error that stops a compilation run.
7///
8/// A [`Stage`](crate::Stage) returns a `DriverError` from
9/// [`Stage::run`](crate::Stage::run) when it cannot produce its output: a phase
10/// that hit an unrecoverable condition, or a call to
11/// [`Session::abort_if_errors`](crate::Session::abort_if_errors) that found the
12/// session already holds errors. It is the *control-flow* failure that ends the
13/// pipeline, as distinct from a [`Diagnostic`](crate::Diagnostic), which is a
14/// *record* a stage emits and may keep going after.
15///
16/// The error carries the [`message`](Self::message) describing what went wrong and
17/// the [`stage`](Self::stage) it came from. A stage does not name itself: it
18/// returns `DriverError::new(reason)` with an empty stage, and the
19/// [`Pipeline`](crate::Pipeline) stamps in the name of the stage that failed as it
20/// propagates the error. Stamping only fills an *empty* name, so in a chain the
21/// innermost stage — the one that actually failed — keeps the attribution.
22///
23/// The reason is stored as a [`Cow<'static, str>`](alloc::borrow::Cow), so a
24/// static message costs no allocation and a computed one is owned only when built.
25///
26/// # Examples
27///
28/// A stage failing with a static reason:
29///
30/// ```
31/// use driver_lang::DriverError;
32///
33/// let err = DriverError::new("unresolved import");
34/// assert_eq!(err.message(), "unresolved import");
35/// assert_eq!(err.stage(), ""); // not stamped until it flows through a Pipeline
36/// ```
37///
38/// A computed reason:
39///
40/// ```
41/// use driver_lang::DriverError;
42///
43/// let path = "src/main";
44/// let err = DriverError::new(format!("cannot read `{path}.rs`"));
45/// assert_eq!(err.message(), "cannot read `src/main.rs`");
46/// ```
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct DriverError {
49    stage: &'static str,
50    message: Cow<'static, str>,
51}
52
53impl DriverError {
54    /// Create an error describing why a stage could not complete.
55    ///
56    /// Call this from inside [`Stage::run`](crate::Stage::run). The `message`
57    /// accepts a string literal (no allocation) or an owned
58    /// [`String`](alloc::string::String) (a computed reason). The failing stage's
59    /// name is filled in by the [`Pipeline`](crate::Pipeline) — you do not repeat
60    /// it here.
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// use driver_lang::DriverError;
66    ///
67    /// let from_literal = DriverError::new("boom");
68    /// let from_owned = DriverError::new(String::from("boom"));
69    /// assert_eq!(from_literal, from_owned);
70    /// ```
71    #[must_use]
72    pub fn new(message: impl Into<Cow<'static, str>>) -> Self {
73        Self {
74            stage: "",
75            message: message.into(),
76        }
77    }
78
79    /// The name of the stage that failed, or `""` if the error has not yet
80    /// propagated through a [`Pipeline`](crate::Pipeline).
81    ///
82    /// # Examples
83    ///
84    /// ```
85    /// use driver_lang::DriverError;
86    ///
87    /// assert_eq!(DriverError::new("boom").stage(), "");
88    /// ```
89    #[must_use]
90    #[inline]
91    pub fn stage(&self) -> &str {
92        self.stage
93    }
94
95    /// The reason the stage could not complete.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use driver_lang::DriverError;
101    ///
102    /// assert_eq!(DriverError::new("type error").message(), "type error");
103    /// ```
104    #[must_use]
105    #[inline]
106    pub fn message(&self) -> &str {
107        &self.message
108    }
109
110    /// Stamp the failing stage's name into the error, but only if it is not
111    /// already set. Called by the [`Pipeline`](crate::Pipeline) as it propagates a
112    /// failure; the first (innermost) stage to stamp wins, so the error names the
113    /// stage that actually failed rather than an outer wrapper.
114    #[must_use]
115    pub(crate) fn in_stage(mut self, name: &'static str) -> Self {
116        if self.stage.is_empty() {
117            self.stage = name;
118        }
119        self
120    }
121}
122
123impl fmt::Display for DriverError {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        if self.stage.is_empty() {
126            write!(f, "driver error: {}", self.message)
127        } else {
128            write!(f, "stage `{}` failed: {}", self.stage, self.message)
129        }
130    }
131}
132
133impl core::error::Error for DriverError {}
134
135#[cfg(test)]
136#[allow(clippy::unwrap_used, clippy::expect_used)]
137mod tests {
138    use super::*;
139    use alloc::string::{String, ToString};
140
141    #[test]
142    fn test_new_accepts_literal_and_owned() {
143        assert_eq!(DriverError::new("x"), DriverError::new(String::from("x")));
144    }
145
146    #[test]
147    fn test_message_is_preserved() {
148        assert_eq!(DriverError::new("type error").message(), "type error");
149    }
150
151    #[test]
152    fn test_stage_is_empty_before_stamping() {
153        assert_eq!(DriverError::new("boom").stage(), "");
154    }
155
156    #[test]
157    fn test_in_stage_stamps_empty_name() {
158        let err = DriverError::new("boom").in_stage("parse");
159        assert_eq!(err.stage(), "parse");
160        assert_eq!(err.message(), "boom");
161    }
162
163    #[test]
164    fn test_in_stage_does_not_overwrite_existing_name() {
165        let err = DriverError::new("boom").in_stage("lex").in_stage("parse");
166        assert_eq!(err.stage(), "lex");
167    }
168
169    #[test]
170    fn test_display_without_stage() {
171        assert_eq!(DriverError::new("boom").to_string(), "driver error: boom");
172    }
173
174    #[test]
175    fn test_display_with_stage() {
176        let err = DriverError::new("boom").in_stage("codegen");
177        assert_eq!(err.to_string(), "stage `codegen` failed: boom");
178    }
179
180    #[test]
181    fn test_error_trait_is_implemented() {
182        fn assert_error<E: core::error::Error>(_: &E) {}
183        assert_error(&DriverError::new("boom"));
184    }
185}