Skip to main content

maybe_fatal/
lib.rs

1//! Potentially fatal diagnostics and diagnostic handling for compilers.
2//!
3//! # Usage
4//!
5//! See the [examples] directory for some examples of how to use this crate.
6//!
7//! # Relationship to Other Crates
8//!
9//! This crate can be thought of as a high-level wrapper around the [ariadne] crate, as its core
10//! functionality is driven by it. It is also kind of a spiritual successor to the [rich-err] crate
11//! I wrote, but to be honest, I totally forgot that existed until I went to publish this one.
12//!
13//! There is also the [ariadnenum] crate, which fulfills a similar purpose to the
14//! [maybe-fatal-derive] crate in this repository. The primary difference is that
15//! [maybe-fatal-derive] was designed specifically for use in the context of this crate, whereas
16//! [ariadnenum] is meant for a workflow that is already using [ariadne] directly.
17//!
18//! One other crate worth mentioning is [wurm], which was similarly made for non-fatal error
19//! handling. I considered using that crate here, but it made more sense with how I had designed the
20//! [`Diagnostic`] type to just make my own stuff. Functionality relating to that can be found in
21//! the [`sink`] module.
22//!
23//! # Prerelease Status
24//!
25//! This is currently marked as being in beta, as I am still working on making sure the API is as
26//! ergonomic and flexible as possible. I would also like to add some more detailed documentation,
27//! unit tests, integration tests, and examples.
28//!
29//! [ariadnenum]: https://docs.rs/ariadnenum/latest/ariadnenum/
30//! [examples]: https://github.com/RosieTheGhostie/maybe-fatal/tree/main/examples
31//! [maybe-fatal-derive]: maybe_fatal_derive
32//! [rich-err]: https://docs.rs/rich-err/latest/rich_err/
33//! [wurm]: https://docs.rs/wurm/latest/wurm/
34
35#![cfg_attr(docsrs, feature(doc_cfg))]
36
37pub mod additional_attributes;
38pub mod code;
39pub mod prelude;
40pub mod sink;
41pub mod traits;
42
43pub use ariadne::{Config, Label};
44
45pub use classified::ClassifiedDiagnostic;
46pub use color_palette::ColorPalette;
47pub use context::Context;
48pub use severity::DiagnosticSeverity;
49
50mod classified;
51mod color_palette;
52mod context;
53mod severity;
54
55use core::borrow::Borrow;
56
57use code::DiagnosticCode;
58use traits::DiagnosticGroup;
59
60/// A contextualized message meant to assist the user in diagnosing and resolving issues.
61///
62/// This is intended for use in things like parsers and compilers, but they can be used in any
63/// text-based input processing.
64///
65/// Diagnostics are neutral by default; that is, they don't have an assigned
66/// [severity](DiagnosticSeverity). To assign a severity, use the [`classify`](Self::classify)
67/// method.
68pub struct Diagnostic<S, D = code::DefaultDiscriminant> {
69    /// The code identifying this kind of diagnostic.
70    ///
71    /// Diagnostics with identical codes are not necessarily equivalent. They may refer to different
72    /// spans and have different contextual information.
73    pub code: DiagnosticCode<D>,
74
75    /// The span this diagnostic refers to.
76    pub span: S,
77
78    /// A function that dynamically builds the diagnostic message.
79    message: Box<dyn FnOnce() -> String>,
80
81    /// Any contextual information that may accompany the message.
82    pub context_info: Vec<Context<S>>,
83}
84
85impl<S, D> Diagnostic<S, D> {
86    /// Constructs a new [`Diagnostic`] from the provided member of a [`DiagnosticGroup`].
87    pub fn new<T>(group_member: impl Borrow<T>, span: S) -> Self
88    where
89        T: DiagnosticGroup<D> + ?Sized,
90    {
91        let group_member = group_member.borrow();
92        Self {
93            code: group_member.diagnostic_code(),
94            span,
95            message: group_member.message(),
96            context_info: Vec::new(),
97        }
98    }
99
100    /// Labels a subspan of this diagnostic.
101    ///
102    /// See the documentation of [`Label`] for more details.
103    pub fn label(&mut self, label: Label<S>) -> &mut Self {
104        self.context_info.push(Context::Label(label));
105        self
106    }
107
108    /// Sequentially labels several subspans of this diagnostic.
109    ///
110    /// See the documentation of [`Label`] for more details.
111    pub fn labels(&mut self, labels: impl IntoIterator<Item = Label<S>>) -> &mut Self {
112        self.context_info
113            .extend(labels.into_iter().map(Context::Label));
114        self
115    }
116
117    /// Adds a note to this diagnostic.
118    pub fn note(&mut self, note: impl ToString) -> &mut Self {
119        self.context_info.push(Context::new_note(note));
120        self
121    }
122
123    /// Adds a help message to this diagnostic.
124    pub fn help(&mut self, help: impl ToString) -> &mut Self {
125        self.context_info.push(Context::new_help(help));
126        self
127    }
128
129    /// Classifies this diagnostic under the given severity.
130    pub const fn classify(self, severity: DiagnosticSeverity) -> ClassifiedDiagnostic<S, D> {
131        ClassifiedDiagnostic {
132            inner: self,
133            severity,
134        }
135    }
136
137    /// Reports this diagnostic using the given severity and configuration.
138    ///
139    /// See the [`ariadne`] documentation for more details.
140    pub fn report_with<C>(
141        self,
142        severity: DiagnosticSeverity,
143        config: Config,
144        cache: C,
145    ) -> std::io::Result<()>
146    where
147        S: ariadne::Span,
148        D: code::Discriminant,
149        C: ariadne::Cache<S::SourceId>,
150    {
151        let mut builder = ariadne::Report::build(severity.into(), self.span)
152            .with_config(config)
153            .with_code(self.code)
154            .with_message((self.message)());
155
156        for context in self.context_info {
157            context.add_to_report_builder(&mut builder);
158        }
159
160        builder.finish().eprint(cache)
161    }
162}
163
164#[cfg(any(docsrs, feature = "derive"))]
165mod macros {
166    #[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
167    pub use maybe_fatal_derive::*;
168}
169
170macro_rules! document_macro_reexports {
171    [$($derive_macro:ident),* $(,)?] => {
172        $(
173            #[cfg(any(docsrs, feature = "derive"))]
174            #[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
175            pub use maybe_fatal_derive::$derive_macro;
176        )*
177    };
178}
179
180document_macro_reexports![
181    Diagnose,
182    DiagnosticGroup,
183    DiagnosticInfoWrapper,
184    PartialDiagnose,
185];