rune/diagnostics/
mod.rs

1//! Diagnostics module for Rune.
2//!
3//! Diagnostics collects information about a source program in order to provide
4//! good human-readable diagnostics like errors, warnings, and hints.
5//!
6//! In order to collect diagnostics during compilation you must register a
7//! [Diagnostics] object with
8//! [Build::with_diagnostics][crate::Build::with_diagnostics].
9//!
10//! ```
11//! use rune::termcolor::{ColorChoice, StandardStream};
12//! use rune::{Sources, Diagnostics};
13//!
14//! let mut sources = Sources::new();
15//!
16//! let mut diagnostics = Diagnostics::new();
17//!
18//! let result = rune::prepare(&mut sources)
19//!     .with_diagnostics(&mut diagnostics)
20//!     .build();
21//!
22//! // We delay unwrapping result into a unit so that we can print diagnostics
23//! // even if they relate to the compilation failing.
24//! if !diagnostics.is_empty() {
25//!     let mut writer = StandardStream::stderr(ColorChoice::Always);
26//!     diagnostics.emit(&mut writer, &sources)?;
27//! }
28//!
29//! let unit = result?;
30//! # Ok::<_, rune::support::Error>(())
31//! ```
32
33pub use self::fatal::FatalDiagnostic;
34pub(crate) use self::fatal::FatalDiagnosticKind;
35mod fatal;
36
37pub use self::warning::WarningDiagnostic;
38pub(crate) use self::warning::WarningDiagnosticKind;
39mod warning;
40
41pub use self::runtime::RuntimeDiagnostic;
42pub(crate) use self::runtime::RuntimeDiagnosticKind;
43mod runtime;
44
45use core::fmt;
46
47use crate::alloc::string::TryToString;
48use crate::alloc::{self, Box, String, Vec};
49use crate::ast::Spanned;
50use crate::{Hash, SourceId};
51
52#[cfg(feature = "emit")]
53#[cfg_attr(rune_docsrs, doc(cfg(feature = "emit")))]
54mod emit;
55#[cfg(feature = "emit")]
56#[cfg_attr(rune_docsrs, doc(cfg(feature = "emit")))]
57#[doc(inline)]
58pub use self::emit::EmitError;
59
60/// A single diagnostic.
61#[derive(Debug)]
62#[non_exhaustive]
63pub enum Diagnostic {
64    /// A fatal diagnostic.
65    Fatal(FatalDiagnostic),
66    /// A warning diagnostic.
67    Warning(WarningDiagnostic),
68    /// A runtime diagnostic.
69    Runtime(RuntimeDiagnostic),
70}
71
72/// The diagnostics mode to use.
73#[derive(Debug, Clone, Copy)]
74enum Mode {
75    /// Collect all forms of diagnostics.
76    All,
77    /// Collect errors.
78    WithoutWarnings,
79}
80
81impl Mode {
82    /// If warnings are enabled.
83    fn warnings(self) -> bool {
84        matches!(self, Self::All)
85    }
86}
87
88/// Structure to collect compilation diagnostics.
89///
90/// If the project is compiled with the `emit` feature, you can make use of
91/// [Diagnostics::emit].
92///
93/// # Examples
94///
95/// ```,no_run
96/// use rune::{Sources, Diagnostics};
97/// use rune::termcolor::{StandardStream, ColorChoice};
98///
99/// let mut sources = Sources::new();
100/// let mut diagnostics = Diagnostics::new();
101///
102/// // use sources and diagnostics to compile a project.
103///
104/// if !diagnostics.is_empty() {
105///     let mut writer = StandardStream::stderr(ColorChoice::Always);
106///     diagnostics.emit(&mut writer, &sources)?;
107/// }
108/// # Ok::<_, rune::support::Error>(())
109/// ```
110#[derive(Debug)]
111pub struct Diagnostics {
112    diagnostics: Vec<Diagnostic>,
113    /// If warnings are collected or not.
114    mode: Mode,
115    /// Indicates if diagnostics indicates errors.
116    has_error: bool,
117    /// Indicates if diagnostics contains warnings.
118    has_warning: bool,
119}
120
121impl Diagnostics {
122    fn with_mode(mode: Mode) -> Self {
123        Self {
124            diagnostics: Vec::new(),
125            mode,
126            has_error: false,
127            has_warning: false,
128        }
129    }
130
131    /// Construct a new, empty collection of compilation warnings that is
132    /// disabled, i.e. any warnings added to it will be ignored.
133    ///
134    /// # Examples
135    ///
136    /// ```
137    /// use rune::{Diagnostics, SourceId};
138    /// use rune::ast::Span;
139    ///
140    /// let mut diagnostics = Diagnostics::without_warnings();
141    /// assert!(diagnostics.is_empty());
142    ///
143    /// // emit warnings
144    ///
145    /// assert!(diagnostics.is_empty());
146    /// let warning = diagnostics.into_diagnostics().into_iter().next();
147    /// assert!(matches!(warning, None));
148    /// ```
149    pub fn without_warnings() -> Self {
150        Self::with_mode(Mode::WithoutWarnings)
151    }
152
153    /// Construct a new, empty collection of compilation warnings.
154    ///
155    /// # Examples
156    ///
157    /// ```
158    /// use rune::{Diagnostics, SourceId};
159    /// use rune::ast::Span;
160    /// use rune::diagnostics::Diagnostic;
161    ///
162    /// let mut diagnostics = Diagnostics::new();
163    /// assert!(diagnostics.is_empty());
164    /// ```
165    pub fn new() -> Self {
166        Self::default()
167    }
168
169    /// Indicate if there is any diagnostics.
170    pub fn is_empty(&self) -> bool {
171        self.diagnostics.is_empty()
172    }
173
174    /// Check if diagnostics has any errors reported.
175    pub fn has_error(&self) -> bool {
176        self.has_error
177    }
178
179    /// Check if diagnostics has any warnings reported.
180    pub fn has_warning(&self) -> bool {
181        self.has_warning
182    }
183
184    /// Access underlying diagnostics.
185    pub fn diagnostics(&self) -> &[Diagnostic] {
186        &self.diagnostics
187    }
188
189    /// Convert into underlying diagnostics.
190    pub fn into_diagnostics(self) -> Vec<Diagnostic> {
191        self.diagnostics
192    }
193
194    /// Report an internal error.
195    ///
196    /// This should be used for programming invariants of the compiler which are
197    /// broken for some reason.
198    pub(crate) fn internal(
199        &mut self,
200        source_id: SourceId,
201        m: impl fmt::Display,
202    ) -> alloc::Result<()> {
203        self.error(source_id, FatalDiagnosticKind::Custom(m.try_to_string()?))
204    }
205
206    /// Indicate that a value is produced but never used.
207    pub(crate) fn not_used(
208        &mut self,
209        source_id: SourceId,
210        span: &dyn Spanned,
211        context: Option<&dyn Spanned>,
212    ) -> alloc::Result<()> {
213        self.warning(
214            source_id,
215            WarningDiagnosticKind::NotUsed {
216                span: span.span(),
217                context: context.map(Spanned::span),
218            },
219        )
220    }
221
222    /// Indicate that a value is produced but never used.
223    pub(crate) fn unreachable(
224        &mut self,
225        source_id: SourceId,
226        span: &dyn Spanned,
227        cause: &dyn Spanned,
228    ) -> alloc::Result<()> {
229        self.warning(
230            source_id,
231            WarningDiagnosticKind::Unreachable {
232                span: span.span(),
233                cause: cause.span(),
234            },
235        )
236    }
237
238    /// Indicate that a binding pattern might panic.
239    ///
240    /// Like `let (a, b) = value`.
241    pub(crate) fn let_pattern_might_panic(
242        &mut self,
243        source_id: SourceId,
244        span: &dyn Spanned,
245        context: Option<&dyn Spanned>,
246    ) -> alloc::Result<()> {
247        self.warning(
248            source_id,
249            WarningDiagnosticKind::LetPatternMightPanic {
250                span: span.span(),
251                context: context.map(Spanned::span),
252            },
253        )
254    }
255
256    /// Indicate that we encountered a template string without any expansion
257    /// groups.
258    ///
259    /// Like `` `Hello` ``.
260    pub(crate) fn template_without_expansions(
261        &mut self,
262        source_id: SourceId,
263        span: &dyn Spanned,
264        context: Option<&dyn Spanned>,
265    ) -> alloc::Result<()> {
266        self.warning(
267            source_id,
268            WarningDiagnosticKind::TemplateWithoutExpansions {
269                span: span.span(),
270                context: context.map(Spanned::span),
271            },
272        )
273    }
274
275    /// Add a warning indicating that the parameters of an empty tuple can be
276    /// removed when creating it.
277    ///
278    /// Like `None()`.
279    pub(crate) fn remove_tuple_call_parens(
280        &mut self,
281        source_id: SourceId,
282        span: &dyn Spanned,
283        variant: &dyn Spanned,
284        context: Option<&dyn Spanned>,
285    ) -> alloc::Result<()> {
286        self.warning(
287            source_id,
288            WarningDiagnosticKind::RemoveTupleCallParams {
289                span: span.span(),
290                variant: variant.span(),
291                context: context.map(Spanned::span),
292            },
293        )
294    }
295
296    /// Add a warning about an unecessary semi-colon.
297    pub(crate) fn unnecessary_semi_colon(
298        &mut self,
299        source_id: SourceId,
300        span: &dyn Spanned,
301    ) -> alloc::Result<()> {
302        self.warning(
303            source_id,
304            WarningDiagnosticKind::UnnecessarySemiColon { span: span.span() },
305        )
306    }
307
308    /// Add a warning about using a deprecated function
309    pub(crate) fn used_deprecated(
310        &mut self,
311        source_id: SourceId,
312        span: &dyn Spanned,
313        context: Option<&dyn Spanned>,
314        message: String,
315    ) -> alloc::Result<()> {
316        self.warning(
317            source_id,
318            WarningDiagnosticKind::UsedDeprecated {
319                span: span.span(),
320                context: context.map(Spanned::span),
321                message,
322            },
323        )
324    }
325
326    /// Add a warning about using a deprecated function
327    pub(crate) fn runtime_used_deprecated(&mut self, ip: usize, hash: Hash) -> alloc::Result<()> {
328        self.runtime_warning(ip, RuntimeDiagnosticKind::UsedDeprecated { hash })
329    }
330
331    /// Push a warning to the collection of diagnostics.
332    pub(crate) fn warning<T>(&mut self, source_id: SourceId, kind: T) -> alloc::Result<()>
333    where
334        WarningDiagnosticKind: From<T>,
335    {
336        if !self.mode.warnings() {
337            return Ok(());
338        }
339
340        self.diagnostics
341            .try_push(Diagnostic::Warning(WarningDiagnostic {
342                source_id,
343                kind: kind.into(),
344            }))?;
345
346        self.has_warning = true;
347        Ok(())
348    }
349
350    /// Push a runtime warning to the collection of diagnostics.
351    pub(crate) fn runtime_warning<T>(&mut self, ip: usize, kind: T) -> alloc::Result<()>
352    where
353        RuntimeDiagnosticKind: From<T>,
354    {
355        if !self.mode.warnings() {
356            return Ok(());
357        }
358
359        self.diagnostics
360            .try_push(Diagnostic::Runtime(RuntimeDiagnostic {
361                ip,
362                kind: kind.into(),
363            }))?;
364
365        self.has_warning = true;
366        Ok(())
367    }
368
369    /// Report an error.
370    pub(crate) fn error<T>(&mut self, source_id: SourceId, kind: T) -> alloc::Result<()>
371    where
372        FatalDiagnosticKind: From<T>,
373    {
374        let diagnostic = Diagnostic::Fatal(FatalDiagnostic {
375            source_id,
376            kind: Box::try_new(kind.into())?,
377        });
378
379        self.diagnostics.try_push(diagnostic)?;
380
381        self.has_error = true;
382        Ok(())
383    }
384}
385
386impl Default for Diagnostics {
387    fn default() -> Self {
388        Self::with_mode(Mode::All)
389    }
390}