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