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}