tardar/
lib.rs

1//! **Tardar** is a Rust library that provides extensions for the [`miette`] crate. These
2//! extensions primarily provide more ergonomic diagnostic `Result`s and collation of
3//! `Diagnostic`s.
4//!
5//! ## Diagnostic Results
6//!
7//! [`DiagnosticResult`] is a [`Result`] type that accumulates and associates [`Diagnostic`]s with
8//! an output type `T` for both success and failure (`Ok` and `Err` variants). The `Ok` variant
9//! contains a [`Diagnosed`] with a `T` and **zero or more** non-error [`Diagnostic`]s. The `Err`
10//! variant contains an [`Error`] with **one or more** [`Diagnostic`]s, at least one of which is
11//! considered an error.
12//!
13//! Together with extension methods, [`DiagnosticResult`] supports fluent and ergonomic composition
14//! of **diagnostic functions**. Here, a diagnostic function is one that returns a
15//! [`DiagnosticResult`] or other container of [`Diagnostic`]s. For example, a library that parses
16//! a data structure or language from text may use diagnostic functions for parsing and analysis.
17//!
18//! ```rust
19//! use tardar::DiagnosticResult;
20//!
21//! # struct Checked<T>(T);
22//! # struct Ast<'x>(&'x str);
23//! #
24//! /// Parses an expression into an abstract syntax tree (AST).
25//! fn parse(expression: &str) -> DiagnosticResult<Ast<'_>> {
26//! #     tardar::Diagnosed::ok(Ast(expression)) /*
27//!     ...
28//! # */
29//! }
30//!
31//! /// Checks an AST for token, syntax, and rule correctness.
32//! fn check<'x>(tree: Ast<'x>) -> DiagnosticResult<Checked<Ast<'x>>> {
33//! #     tardar::Diagnosed::ok(Checked(tree)) /*
34//!     ...
35//! # */
36//! }
37//! ```
38//!
39//! These diagnostic functions can be composed with extension methods.
40//!
41//! ```rust
42//! use tardar::prelude::*;
43//! use tardar::DiagnosticResult;
44//!
45//! # struct Checked<T>(T);
46//! # struct Ast<'x>(&'x str);
47//! #
48//! # fn parse(expression: &str) -> DiagnosticResult<Ast<'_>> {
49//! #     tardar::Diagnosed::ok(Ast(expression))
50//! # }
51//! #
52//! # fn check<'x>(tree: Ast<'x>) -> DiagnosticResult<Checked<Ast<'x>>> {
53//! #     tardar::Diagnosed::ok(Checked(tree))
54//! # }
55//! #
56//! /// Parses an expression into a checked AST.
57//! pub fn parse_and_check(expression: &str) -> DiagnosticResult<Checked<Ast<'_>>> {
58//!     parse(expression).and_then_diagnose(check)
59//! }
60//! ```
61//!
62//! The `parse_and_check` function forwards the output of `parse` to `check` with
63//! [`and_then_diagnose`][`DiagnosticResultExt::and_then_diagnose`]. This function is much like the
64//! standard [`Result::and_then`], but accepts a diagnostic function and so preserves any input
65//! [`Diagnostic`]s. **If `parse` succeeds with some warnings but `check` fails with an error, then
66//! the output [`Error`] will contain both the warning and error [`Diagnostic`]s.**
67//!
68//! Other shapes of diagnostic functions can also be composed. For example, an analysis function
69//! may accept a shared reference and return an iterator rather than a result, since it cannot
70//! conceptually fail.
71//!
72//! ```rust
73//! use tardar::BoxedDiagnostic;
74//!
75//! # struct Checked<T>(T);
76//! # struct Ast<'x>(&'x str);
77//! #
78//! /// Analyzes a checked AST and returns non-error diagnostics.
79//! fn analyze<'x>(tree: &Checked<Ast<'x>>) -> impl Iterator<Item = BoxedDiagnostic> {
80//! #     Option::<_>::None.into_iter() /*
81//!     ...
82//! # */
83//! }
84//! ```
85//!
86//! This diagnostic function can too be composed into `parse_and_check` using extension methods.
87//!
88//! ```rust
89//! use tardar::prelude::*;
90//! use tardar::{BoxedDiagnostic, DiagnosticResult};
91//!
92//! # struct Checked<T>(T);
93//! # struct Ast<'x>(&'x str);
94//! #
95//! # fn parse(expression: &str) -> DiagnosticResult<Ast<'_>> {
96//! #     tardar::Diagnosed::ok(Ast(expression))
97//! # }
98//! #
99//! # fn check<'x>(tree: Ast<'x>) -> DiagnosticResult<Checked<Ast<'x>>> {
100//! #     tardar::Diagnosed::ok(Checked(tree))
101//! # }
102//! #
103//! # fn analyze<'x>(tree: &Checked<Ast<'x>>) -> impl Iterator<Item = BoxedDiagnostic> {
104//! #     Option::<_>::None.into_iter()
105//! # }
106//! #
107//! /// Parses an expression into a checked AST with analysis.
108//! pub fn parse_and_check(expression: &str) -> DiagnosticResult<Checked<Ast<'_>>> {
109//!     parse(expression)
110//!         .and_then_diagnose(check)
111//!         .diagnose_non_errors(analyze)
112//! }
113//! ```
114//!
115//! The output of `check` is forwarded to `analyze` with
116//! [`diagnose_non_errors`][`DiagnosticResult::diagnose_non_errors`]. This function is more bespoke
117//! and accepts a diagnostic function that itself accepts the output `T` by shared reference. Any
118//! [`Diagnostic`]s returned by the accepted function are interpreted as non-errors and are
119//! accumulated into the [`Diagnosed`].
120//!
121//! [`DiagnosticResult`]s can be constructed from related types, such as singular [`Result`] types
122//! and iterators with [`Diagnostic`] items. When extension functions like `and_then_diagnose` are
123//! not immediately compatible, it is often possible to perform conversions in a closure.
124//!
125//! ## Collation
126//!
127//! [`miette`] primarily groups [`Diagnostic`]s via [`Diagnostic::related`]. However, it can be
128//! inflexible or cumbersome to provide such an implementation and [`Diagnostic`]s are commonly and
129//! more easily organized into collections or iterators. [`Collation`] is a [`Diagnostic`] type
130//! that relates arbitrary [**non-empty** vectors][`Vec1`] and [slices][`Slice1`] of
131//! [`Diagnostic`]s.
132//!
133//! ```rust
134//! use tardar::{Diagnosed, DiagnosticResult, OwnedCollation};
135//!
136//! # struct Client;
137//! # struct Bssid;
138//! # struct ActiveScan;
139//! #
140//! /// Performs an active scan for the given BSSID.
141//! pub fn scan(
142//!     client: &Client,
143//!     bssid: Bssid,
144//! ) -> Result<ActiveScan, OwnedCollation> {
145//!     let result: DiagnosticResult<ActiveScan> = {
146//! #         Diagnosed::ok(ActiveScan) /*
147//!         ...
148//! # */
149//!     };
150//!     // The try operator `?` can be used here, because `Error` can be converted into
151//!     // `Collation`. If the result is `Err`, then the `Collation` relates the error diagnostics.
152//!     let scan = result.map(Diagnosed::into_output)?;
153//! # /*
154//!     ...
155//! # */
156//! #     Ok(scan)
157//! }
158//! ```
159//!
160//! Note that [`DiagnosticResult`]s accumulate [`Diagnostic`]s, but do not **relate** them by
161//! design: neither [`Diagnosed`] nor [`Error`] implement [`Diagnostic`].
162
163use miette::{Diagnostic, LabeledSpan, Severity, SourceCode};
164use mitsein::iter1::{Extend1, FromIterator1, IntoIterator1, Iterator1, IteratorExt as _};
165use mitsein::slice1::Slice1;
166use mitsein::vec1::Vec1;
167use std::error;
168use std::fmt::{self, Debug, Display, Formatter};
169use std::iter::{Chain, Flatten};
170use std::option;
171
172pub mod prelude {
173    pub use crate::{
174        BoxedDiagnosticExt as _, DiagnosticExt as _, DiagnosticResultExt as _, ErrorExt as _,
175        IteratorExt as _, ResultExt as _,
176    };
177}
178
179type Related<'d> = Flatten<option::IntoIter<Box<dyn Iterator<Item = &'d dyn Diagnostic> + 'd>>>;
180type HeadAndRelated<'d> = Chain<option::IntoIter<&'d dyn Diagnostic>, Related<'d>>;
181
182/// Extension methods for [`Diagnostic`] types.
183pub trait DiagnosticExt: Diagnostic {
184    /// Gets a [non-empty iterator][`Iterator1`] over the [`Diagnostic`]'s tree of related
185    /// [`Diagnostic`]s.
186    ///
187    /// [`Diagnostic`]s have zero or more related `Diagnostic`s. The returned iterator successively
188    /// calls [`Diagnostic::related`] and walks the tree to yield all related `Diagnostic`s. **The
189    /// iterator also yields `self`.**
190    fn tree(&self) -> Iterator1<Tree<'_>>;
191}
192
193impl<D> DiagnosticExt for D
194where
195    D: Diagnostic,
196{
197    fn tree(&self) -> Iterator1<Tree<'_>> {
198        // SAFETY: This `Tree` iterator must yield one or more items. `self` is pushed onto the
199        //         stack and is popped and yielded in the `Iterator` implementation for `Tree`, so
200        //         this `Tree` iterator always yields `self`.
201        unsafe {
202            Iterator1::from_iter_unchecked(Tree {
203                stack: vec![Some(self as &dyn Diagnostic)
204                    .into_iter()
205                    .chain(None.into_iter().flatten())],
206                related: None.into_iter().chain(None.into_iter().flatten()),
207            })
208        }
209    }
210}
211
212/// An [`Iterator`] over a tree of related [`Diagnostic`]'s.
213///
214/// See [`DiagnosticExt::tree`].
215pub struct Tree<'d> {
216    stack: Vec<HeadAndRelated<'d>>,
217    related: HeadAndRelated<'d>,
218}
219
220impl Debug for Tree<'_> {
221    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
222        formatter
223            .debug_struct("Tree")
224            .field("stack", &"..")
225            .field("related", &"..")
226            .finish()
227    }
228}
229
230impl<'d> Iterator for Tree<'d> {
231    type Item = &'d dyn Diagnostic;
232
233    fn next(&mut self) -> Option<Self::Item> {
234        loop {
235            if let Some(diagnostic) = self.related.next() {
236                self.stack.push(
237                    None.into_iter()
238                        .chain(diagnostic.related().into_iter().flatten()),
239                );
240                return Some(diagnostic);
241            }
242            else if let Some(related) = self.stack.pop() {
243                self.related = related;
244            }
245            else {
246                return None;
247            }
248        }
249    }
250}
251
252/// Extension methods for [`Error`][`error::Error`] types.
253pub trait ErrorExt: error::Error {
254    /// Gets an iterator over the sources of the `Error`.
255    ///
256    /// The returned iterator successively calls [`Error::source`][`error::Error::source`] to yield
257    /// a chain of error sources.
258    fn sources(&self) -> Sources<'_>;
259}
260
261impl<E> ErrorExt for E
262where
263    E: error::Error,
264{
265    fn sources(&self) -> Sources<'_> {
266        Sources {
267            source: self.source(),
268        }
269    }
270}
271
272/// An [`Iterator`] over sources of an [`Error`][`error::Error`].
273///
274/// See [`ErrorExt::sources`].
275#[derive(Debug)]
276pub struct Sources<'e> {
277    source: Option<&'e dyn error::Error>,
278}
279
280impl<'e> Iterator for Sources<'e> {
281    type Item = &'e dyn error::Error;
282
283    fn next(&mut self) -> Option<Self::Item> {
284        self.source.take().inspect(|next| {
285            self.source = next.source();
286        })
287    }
288}
289
290/// Extension methods for [`Iterator`] types.
291pub trait IteratorExt: Iterator {
292    /// Converts from an [`Iterator`] type of [`AsDiagnosticObject`] items into a non-error
293    /// `DiagnosticResult<(), Self::Item>`.
294    ///
295    /// The [`Diagnostic`] items of the iterator are interpreted as non-errors. Note that the
296    /// [`Severity`] is not examined and so the [`Diagnostic`]s may have error-level severities
297    /// despite being interpreted as non-errors.
298    fn into_non_error_diagnostic(self) -> DiagnosticResult<(), Self::Item>
299    where
300        Self: Sized,
301        Self::Item: AsDiagnosticObject;
302
303    /// Converts from an [`Iterator`] type of [`AsDiagnosticObject`] items into
304    /// `DiagnosticResult<(), Self::Item>` by [`Severity`].
305    ///
306    /// If any [`Diagnostic`] item has an [error `Severity`][`Severity::Error`], then the items are
307    /// interpreted as errors. Otherwise, the items are interpreted as non-errors.
308    fn into_diagnostic_by_severity(self) -> DiagnosticResult<(), Self::Item>
309    where
310        Self: Sized,
311        Self::Item: AsDiagnosticObject;
312}
313
314impl<I> IteratorExt for I
315where
316    I: Iterator,
317{
318    fn into_non_error_diagnostic(self) -> DiagnosticResult<(), Self::Item>
319    where
320        Self: Sized,
321        Self::Item: AsDiagnosticObject,
322    {
323        Ok(Diagnosed((), self.collect()))
324    }
325
326    fn into_diagnostic_by_severity(self) -> DiagnosticResult<(), Self::Item>
327    where
328        Self: Sized,
329        Self::Item: AsDiagnosticObject,
330    {
331        let diagnostics: Vec<_> = self.collect();
332        match Vec1::try_from(diagnostics) {
333            Ok(diagnostics) => {
334                if diagnostics
335                    .iter()
336                    .map(AsDiagnosticObject::as_diagnostic_object)
337                    .flat_map(Diagnostic::severity)
338                    .any(|severity| matches!(severity, Severity::Error))
339                {
340                    Err(Error(diagnostics))
341                }
342                else {
343                    Ok(Diagnosed((), diagnostics.into()))
344                }
345            }
346            _ => Diagnosed::ok(()),
347        }
348    }
349}
350
351/// Extension methods for [`Iterator1`].
352pub trait Iterator1Ext<I>: Sized {
353    /// Converts from an [`Iterator1`] of [`AsDiagnosticObject`] items into
354    /// `DiagnosticResult<(), I::Item>`.
355    ///
356    /// The [`Diagnostic`] items of the iterator are interpreted as errors. Note that the
357    /// [`Severity`] is not examined and so the [`Diagnostic`]s may have non-error severities
358    /// despite being interpreted as errors.
359    ///
360    /// To interpret the items as non-errors, first convert the [`Iterator1`] into an iterator and
361    /// then use [`into_non_error_diagnostic`][`IteratorExt::into_non_error_diagnostic`].
362    fn into_error_diagnostic(self) -> DiagnosticResult<(), I::Item>
363    where
364        I: Iterator + Sized,
365        I::Item: AsDiagnosticObject;
366}
367
368impl<I> Iterator1Ext<I> for Iterator1<I> {
369    fn into_error_diagnostic(self) -> DiagnosticResult<(), I::Item>
370    where
371        I: Iterator + Sized,
372        I::Item: AsDiagnosticObject,
373    {
374        Err(Error(self.collect1()))
375    }
376}
377
378/// Extension methods for [`Result`].
379pub trait ResultExt<T, E> {
380    /// Converts from `Result<T, E>` into `DiagnosticResult<T, E>`.
381    ///
382    /// The error type `E` must be convertible to a [`Diagnostic`] trait object and is interpreted
383    /// as an error. Note that the [`Severity`] is not examined and so the [`Diagnostic`] may have
384    /// a non-error severity despite being interpreted as an error.
385    fn into_error_diagnostic(self) -> DiagnosticResult<T, E>
386    where
387        E: AsDiagnosticObject;
388}
389
390impl<T, E> ResultExt<T, E> for Result<T, E> {
391    fn into_error_diagnostic(self) -> DiagnosticResult<T, E>
392    where
393        E: AsDiagnosticObject,
394    {
395        match self {
396            Ok(output) => Ok(Diagnosed(output, vec![])),
397            Err(error) => Err(Error(Vec1::from_one(error))),
398        }
399    }
400}
401
402/// A type that can be converted into a shared [`Diagnostic`] trait object through a reference.
403pub trait AsDiagnosticObject {
404    /// Converts `&self` to a [`Diagnostic`] trait object `&dyn Diagnostic`.
405    fn as_diagnostic_object(&self) -> &dyn Diagnostic;
406}
407
408impl<D> AsDiagnosticObject for &'_ D
409where
410    D: AsDiagnosticObject + ?Sized,
411{
412    fn as_diagnostic_object(&self) -> &dyn Diagnostic {
413        D::as_diagnostic_object(*self)
414    }
415}
416
417impl AsDiagnosticObject for dyn Diagnostic {
418    fn as_diagnostic_object(&self) -> &dyn Diagnostic {
419        self
420    }
421}
422
423/// A type that can be converted into a shared [non-empty slice][`Slice1`] of
424/// [`AsDiagnosticObject`]s through a reference.
425pub trait AsDiagnosticSlice1 {
426    /// The diagnostic type of items in the [`Slice1`].
427    type Diagnostic: AsDiagnosticObject;
428
429    /// Converts `&self` to a [`Slice1`] of [`AsDiagnosticObject`]s.
430    fn as_diagnostic_slice1(&self) -> &Slice1<Self::Diagnostic>;
431}
432
433impl<T> AsDiagnosticSlice1 for &'_ T
434where
435    T: AsDiagnosticSlice1 + ?Sized,
436{
437    type Diagnostic = T::Diagnostic;
438
439    fn as_diagnostic_slice1(&self) -> &Slice1<Self::Diagnostic> {
440        T::as_diagnostic_slice1(*self)
441    }
442}
443
444impl<D> AsDiagnosticSlice1 for Slice1<D>
445where
446    D: AsDiagnosticObject,
447{
448    type Diagnostic = D;
449
450    fn as_diagnostic_slice1(&self) -> &Slice1<Self::Diagnostic> {
451        self
452    }
453}
454
455impl<D> AsDiagnosticSlice1 for Vec1<D>
456where
457    D: AsDiagnosticObject,
458{
459    type Diagnostic = D;
460
461    fn as_diagnostic_slice1(&self) -> &Slice1<Self::Diagnostic> {
462        self
463    }
464}
465
466pub type BoxedDiagnostic = Box<dyn Diagnostic>;
467
468/// Extension methods for [`BoxedDiagnostic`].
469pub trait BoxedDiagnosticExt {
470    /// Constructs a [`BoxedDiagnostic`] from a [`Diagnostic`].
471    fn from_diagnostic<D>(diagnostic: D) -> Self
472    where
473        D: 'static + Diagnostic;
474}
475
476impl BoxedDiagnosticExt for BoxedDiagnostic {
477    fn from_diagnostic<D>(diagnostic: D) -> Self
478    where
479        D: 'static + Diagnostic,
480    {
481        Box::new(diagnostic) as Box<dyn Diagnostic>
482    }
483}
484
485impl AsDiagnosticObject for BoxedDiagnostic {
486    fn as_diagnostic_object(&self) -> &dyn Diagnostic {
487        self.as_ref()
488    }
489}
490
491/// `Result` that includes [`Diagnostic`]s on both success and failure.
492///
493/// On success, the `Ok` variant contains a [`Diagnosed`] with **zero or more** diagnostics and an
494/// output `T`. On failure, the `Err` variant contains an [`Error`] with **one or more**
495/// diagnostics, where at least one of the diagnostics has been interpreted as an error.
496pub type DiagnosticResult<T, D = BoxedDiagnostic> = Result<Diagnosed<T, D>, Error<D>>;
497
498/// Extension methods for [`DiagnosticResult`].
499pub trait DiagnosticResultExt<T, D> {
500    /// Converts from `DiagnosticResult<T, D>` into `Option<T>`.
501    ///
502    /// This function is similar to [`Result::ok`], but gets only the output `T` from the
503    /// [`Diagnosed`] in the `Ok` variant, **discarding any [`Diagnostic`]s**.
504    fn ok_output(self) -> Option<T>;
505
506    /// Maps `DiagnosticResult<T, D>` into `DiagnosticResult<U, D>` by applying a function over the
507    /// output `T` of the `Ok` variant.
508    ///
509    /// This function is similar to [`Result::map`], but maps only the non-diagnostic output `T`
510    /// from the `Ok` variant in [`DiagnosticResult`], ignoring diagnostics.
511    fn map_output<U, F>(self, f: F) -> DiagnosticResult<U, D>
512    where
513        F: FnOnce(T) -> U;
514
515    /// Calls the given non-error diagnostic function if the `DiagnosticResult` is `Ok` and
516    /// otherwise returns the `Err` variant of the `DiagnosticResult`.
517    ///
518    /// Unlike [`and_then_diagnose`], the given function accepts the output `T` by shared reference
519    /// and returns zero or more non-error [`Diagnostic`]s and so cannot itself invoke an error.
520    /// These [`Diagnostic`]s are accumulated into the [`Diagnosed`].
521    fn diagnose_non_errors<I, F>(self, f: F) -> Self
522    where
523        I: IntoIterator<Item = D>,
524        F: FnOnce(&T) -> I;
525
526    /// Calls the given diagnostic function if the `DiagnosticResult` is `Ok` and otherwise returns
527    /// the `Err` variant of the `DiagnosticResult`.
528    ///
529    /// This function is similar to [`Result::and_then`], but additionally forwards and collects
530    /// diagnostics.
531    fn and_then_diagnose<U, F>(self, f: F) -> DiagnosticResult<U, D>
532    where
533        F: FnOnce(T) -> DiagnosticResult<U, D>;
534
535    /// Gets an iterator over the associated [`Diagnostic`]s as trait objects.
536    fn diagnostics(&self) -> impl '_ + Iterator<Item = &'_ dyn Diagnostic>;
537
538    /// Gets a slice over the associated [`Diagnostic`]s.
539    fn as_diagnostic_slice(&self) -> &[D];
540
541    /// Returns `true` if the `DiagnosticResult` has one or more associated [`Diagnostic`]s.
542    fn has_diagnostics(&self) -> bool;
543}
544
545impl<T, D> DiagnosticResultExt<T, D> for DiagnosticResult<T, D>
546where
547    D: AsDiagnosticObject,
548{
549    fn ok_output(self) -> Option<T> {
550        match self {
551            Ok(Diagnosed(output, _)) => Some(output),
552            _ => None,
553        }
554    }
555
556    fn map_output<U, F>(self, f: F) -> DiagnosticResult<U, D>
557    where
558        F: FnOnce(T) -> U,
559    {
560        match self {
561            Ok(diagnosed) => Ok(diagnosed.map_output(f)),
562            Err(error) => Err(error),
563        }
564    }
565
566    fn diagnose_non_errors<I, F>(self, f: F) -> Self
567    where
568        I: IntoIterator<Item = D>,
569        F: FnOnce(&T) -> I,
570    {
571        match self {
572            Ok(diagnosed) => Ok(diagnosed.diagnose_non_errors(f)),
573            Err(error) => Err(error),
574        }
575    }
576
577    fn and_then_diagnose<U, F>(self, f: F) -> DiagnosticResult<U, D>
578    where
579        F: FnOnce(T) -> DiagnosticResult<U, D>,
580    {
581        match self {
582            Ok(diagnosed) => diagnosed.and_then_diagnose(f),
583            Err(diagnostics) => Err(diagnostics),
584        }
585    }
586
587    fn diagnostics(&self) -> impl '_ + Iterator<Item = &'_ dyn Diagnostic> {
588        self.as_diagnostic_slice()
589            .iter()
590            .map(AsDiagnosticObject::as_diagnostic_object)
591    }
592
593    fn as_diagnostic_slice(&self) -> &[D] {
594        match self {
595            Ok(ref diagnosed) => diagnosed.as_diagnostic_slice(),
596            Err(ref error) => error.as_diagnostic_slice1(),
597        }
598    }
599
600    fn has_diagnostics(&self) -> bool {
601        match self {
602            Ok(ref diagnosed) => diagnosed.has_diagnostics(),
603            Err(_) => true,
604        }
605    }
606}
607
608/// A diagnosed `T`.
609///
610/// `Diagnosed` pairs an output `T` with zero or more non-error [`Diagnostic`]s. In the strictest
611/// sense, non-error merely means here that no associated [`Diagnostic`]s prevented the
612/// construction of the output `T`. The [`Severity`] of the associated [`Diagnostic`]s is
613/// arbitrary.
614///
615/// See [`DiagnosticResult`].
616#[derive(Debug)]
617pub struct Diagnosed<T, D = BoxedDiagnostic>(pub T, pub Vec<D>);
618
619impl<T, D> Diagnosed<T, D>
620where
621    D: AsDiagnosticObject,
622{
623    /// Constructs a [`DiagnosticResult`] from an output `T` with no [`Diagnostic`]s.
624    pub const fn ok(output: T) -> DiagnosticResult<T, D> {
625        Ok(Diagnosed::from_output(output))
626    }
627
628    /// Constructs a `Diagnosed` from an output `T` with no [`Diagnostic`]s.
629    pub const fn from_output(output: T) -> Self {
630        Diagnosed(output, vec![])
631    }
632
633    /// Converts from `Diagnosed` into its output `T`, discarding any [`Diagnostic`]s.
634    pub fn into_output(self) -> T {
635        self.0
636    }
637
638    /// Converts from `Diagnosed` into its associated [`Diagnostic`]s, discarding the output `T`.
639    pub fn into_diagnostics(self) -> Vec<D> {
640        self.1
641    }
642
643    /// Maps `Diagnosed<T, D>` into `Diagnosed<U, D>` by applying a function over the output `T`.
644    pub fn map_output<U, F>(self, f: F) -> Diagnosed<U, D>
645    where
646        F: FnOnce(T) -> U,
647    {
648        let Diagnosed(output, diagnostics) = self;
649        Diagnosed(f(output), diagnostics)
650    }
651
652    /// Calls the given non-error diagnostic function with a shared reference to the output `T` and
653    /// accumulates any returned [`Diagnostic`]s.
654    pub fn diagnose_non_errors<I, F>(self, f: F) -> Self
655    where
656        I: IntoIterator<Item = D>,
657        F: FnOnce(&T) -> I,
658    {
659        let Diagnosed(output, mut diagnostics) = self;
660        diagnostics.extend(f(&output));
661        Diagnosed(output, diagnostics)
662    }
663
664    /// Calls the given diagnostic function with the output `T` and accumulates [`Diagnostic`]s
665    /// into a [`DiagnosticResult`].
666    pub fn and_then_diagnose<U, F>(self, f: F) -> DiagnosticResult<U, D>
667    where
668        F: FnOnce(T) -> DiagnosticResult<U, D>,
669    {
670        let Diagnosed(output, mut diagnostics) = self;
671        match f(output) {
672            Ok(Diagnosed(output, tail)) => {
673                diagnostics.extend(tail);
674                Ok(Diagnosed(output, diagnostics))
675            }
676            Err(Error(tail)) => Err(Error(diagnostics.extend_non_empty(tail))),
677        }
678    }
679
680    /// Converts the `Diagnosed` into the output `T` and a [`Collation`] of any associated
681    /// [`Diagnostic`]s.
682    pub fn collate(self) -> (T, Option<OwnedCollation<D>>) {
683        let Diagnosed(output, diagnostics) = self;
684        (
685            output,
686            Vec1::try_from(diagnostics).ok().map(Collation::from),
687        )
688    }
689
690    /// Gets the output `T`.
691    pub fn output(&self) -> &T {
692        &self.0
693    }
694
695    /// Gets an iterator over the associated [`Diagnostic`]s as trait objects.
696    pub fn diagnostics(&self) -> impl '_ + Iterator<Item = &'_ dyn Diagnostic> {
697        self.1.iter().map(AsDiagnosticObject::as_diagnostic_object)
698    }
699
700    /// Gets a slice over the associated [`Diagnostic`]s.
701    pub fn as_diagnostic_slice(&self) -> &[D] {
702        self.1.as_slice()
703    }
704
705    /// Returns `true` if the `Diagnosed` has one or more associated [`Diagnostic`]s.
706    pub fn has_diagnostics(&self) -> bool {
707        !self.1.is_empty()
708    }
709}
710
711/// A diagnostic error.
712///
713/// `Error` contains one or more [`Diagnostic`]s where at least one has been interpreted as an
714/// error. Any [`Diagnostic`] can be interpreted as an error and the [`Severity`] of
715/// [`Diagnostic`]s in an `Error` are arbitrary.
716///
717/// `Error` implements [the standard `Error` trait][`error::Error`] and displays its associated
718/// [`Diagnostic`]s serially when formatted.
719///
720/// See [`DiagnosticResult`].
721///
722/// # Relation to `Collation`
723///
724/// Both `Error` and [`Collation`] accumulate one or more [`Diagnostic`]s, but these types are
725/// distinct. `Error` is intended for continued accumulation of one or more **error**
726/// [`Diagnostic`]s via [`DiagnosticResult`]. `Error` is **not** itself a [`Diagnostic`], but
727/// exposes a collection of [`Diagnostic`]s from diagnostic functions. An `Error` [can be
728/// converted][`Error::collate`] into a [`Collation`] but not the other way around.
729#[repr(transparent)]
730pub struct Error<D = BoxedDiagnostic>(pub Vec1<D>);
731
732impl<D> Error<D>
733where
734    D: AsDiagnosticObject,
735{
736    /// Converts from `Error` into its [`Diagnostic`]s.
737    pub fn into_diagnostics(self) -> Vec1<D> {
738        self.0
739    }
740
741    /// Converts the `Error` into a [`Collation`] of its [`Diagnostic`]s.
742    pub fn collate(self) -> OwnedCollation<D> {
743        Collation::from(self)
744    }
745
746    /// Gets a non-empty iterator over the [`Diagnostic`]s as trait objects.
747    pub fn diagnostics(&self) -> Iterator1<impl '_ + Iterator<Item = &'_ dyn Diagnostic>> {
748        self.0.iter1().map(AsDiagnosticObject::as_diagnostic_object)
749    }
750
751    /// Gets a non-empty slice over the [`Diagnostic`]s.
752    pub fn as_diagnostic_slice1(&self) -> &Slice1<D> {
753        self.0.as_slice1()
754    }
755}
756
757impl<D> Debug for Error<D>
758where
759    D: AsDiagnosticObject,
760{
761    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
762        formatter.debug_tuple("Error").field(&"..").finish()
763    }
764}
765
766impl<D> Display for Error<D>
767where
768    D: AsDiagnosticObject,
769{
770    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
771        for diagnostic in self.diagnostics() {
772            writeln!(formatter, "{}", diagnostic)?;
773        }
774        Ok(())
775    }
776}
777
778impl<D> error::Error for Error<D> where D: AsDiagnosticObject {}
779
780impl<D> IntoIterator for Error<D>
781where
782    D: AsDiagnosticObject,
783{
784    type IntoIter = <Vec1<D> as IntoIterator>::IntoIter;
785    type Item = D;
786
787    fn into_iter(self) -> Self::IntoIter {
788        self.0.into_iter()
789    }
790}
791
792impl<D> IntoIterator1 for Error<D>
793where
794    D: AsDiagnosticObject,
795{
796    fn into_iter1(self) -> Iterator1<Self::IntoIter> {
797        self.0.into_iter1()
798    }
799}
800
801/// An owned [`Collation`].
802pub type OwnedCollation<D = BoxedDiagnostic> = Collation<Vec1<D>>;
803
804/// A borrowed [`Collation`].
805pub type BorrowedCollation<'c, D = BoxedDiagnostic> = Collation<&'c Slice1<D>>;
806
807/// A collated [`Diagnostic`] of one or more related [`Diagnostic`]s.
808///
809/// `Collation` relates an arbitrary non-empty vector or slice of [`Diagnostic`] trait objects via
810/// [`Diagnostic::related`]. The [`Diagnostic`] and [`Error`][`error::Error`] implementations
811/// forward to the head and the tail is exposed by [`Diagnostic::related`].
812///
813/// `Collation` implements [the standard `Error` trait][`error::Error`] and displays its associated
814/// [`Diagnostic`]s serially when formatted.
815///
816/// # Relation to `Error`
817///
818/// Both `Collation` and [`Error`] accumulate one or more [`Diagnostic`]s, but these types are
819/// distinct. `Collation` is itself a [`Diagnostic`] and is intended for relating a collection of
820/// otherwise disjoint [`Diagnostic`]s via [`Diagnostic::related`]. This relationship cannot be
821/// modified (only nested).
822#[repr(transparent)]
823pub struct Collation<T>(T);
824
825impl<T> Collation<T>
826where
827    T: AsDiagnosticSlice1,
828{
829    fn first(&self) -> &dyn Diagnostic {
830        self.0.as_diagnostic_slice1().first().as_diagnostic_object()
831    }
832
833    /// Gets an iterator over the codes of the collated [`Diagnostic`]s.
834    pub fn codes(&self) -> impl '_ + Iterator<Item = Box<dyn Display + '_>> {
835        self.0
836            .as_diagnostic_slice1()
837            .iter()
838            .map(AsDiagnosticObject::as_diagnostic_object)
839            .flat_map(Diagnostic::code)
840    }
841
842    /// Gets an iterator over the [`Severity`]s of the collated [`Diagnostic`]s.
843    pub fn severities(&self) -> impl '_ + Iterator<Item = Severity> {
844        self.0
845            .as_diagnostic_slice1()
846            .iter()
847            .map(AsDiagnosticObject::as_diagnostic_object)
848            .flat_map(Diagnostic::severity)
849    }
850}
851
852impl<T> Debug for Collation<T> {
853    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
854        formatter.debug_tuple("Collation").field(&"..").finish()
855    }
856}
857
858impl<T> Diagnostic for Collation<T>
859where
860    T: AsDiagnosticSlice1,
861{
862    fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
863        self.first().code()
864    }
865
866    fn severity(&self) -> Option<Severity> {
867        self.first().severity()
868    }
869
870    fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
871        self.first().help()
872    }
873
874    fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
875        self.first().url()
876    }
877
878    fn source_code(&self) -> Option<&dyn SourceCode> {
879        self.first().source_code()
880    }
881
882    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
883        self.first().labels()
884    }
885
886    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
887        self.0
888            .as_diagnostic_slice1()
889            .iter()
890            .skip(1)
891            .try_into_iter1()
892            .ok()
893            .map(|diagnostics| {
894                Box::new(
895                    diagnostics
896                        .into_iter()
897                        .map(AsDiagnosticObject::as_diagnostic_object),
898                ) as Box<dyn Iterator<Item = &dyn Diagnostic>>
899            })
900    }
901
902    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
903        self.first().diagnostic_source()
904    }
905}
906
907impl<T> Display for Collation<T>
908where
909    T: AsDiagnosticSlice1,
910{
911    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
912        for diagnostic in self
913            .0
914            .as_diagnostic_slice1()
915            .iter()
916            .map(AsDiagnosticObject::as_diagnostic_object)
917        {
918            writeln!(formatter, "{}", diagnostic)?;
919        }
920        Ok(())
921    }
922}
923
924impl<T> error::Error for Collation<T>
925where
926    T: AsDiagnosticSlice1,
927{
928    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
929        self.first().source()
930    }
931}
932
933impl<D> From<Error<D>> for Collation<Vec1<D>>
934where
935    D: AsDiagnosticObject,
936{
937    fn from(error: Error<D>) -> Self {
938        Collation::from(error.into_diagnostics())
939    }
940}
941
942impl<'c, D> From<&'c Slice1<D>> for Collation<&'c Slice1<D>>
943where
944    D: AsDiagnosticObject,
945{
946    fn from(diagnostics: &'c Slice1<D>) -> Self {
947        Collation(diagnostics)
948    }
949}
950
951impl<D> From<Vec1<D>> for Collation<Vec1<D>>
952where
953    D: AsDiagnosticObject,
954{
955    fn from(diagnostics: Vec1<D>) -> Self {
956        Collation(diagnostics)
957    }
958}
959
960impl<D> FromIterator1<D> for Collation<Vec1<D>>
961where
962    D: AsDiagnosticObject,
963{
964    fn from_iter1<I>(items: I) -> Self
965    where
966        I: IntoIterator1<Item = D>,
967    {
968        Collation::from(Vec1::from_iter1(items))
969    }
970}
971
972impl<'c, D> TryFrom<&'c [D]> for Collation<&'c Slice1<D>>
973where
974    D: AsDiagnosticObject,
975{
976    type Error = &'c [D];
977
978    fn try_from(diagnostics: &'c [D]) -> Result<Self, Self::Error> {
979        Slice1::try_from_slice(diagnostics).map(Collation::from)
980    }
981}
982
983impl<T, D> TryFrom<Diagnosed<T, D>> for Collation<Vec1<D>>
984where
985    D: AsDiagnosticObject,
986{
987    type Error = Diagnosed<T, D>;
988
989    fn try_from(diagnosed: Diagnosed<T, D>) -> Result<Self, Self::Error> {
990        let Diagnosed(output, diagnostics) = diagnosed;
991        Vec1::try_from(diagnostics)
992            .map(Collation::from)
993            .map_err(|diagnostics| Diagnosed(output, diagnostics))
994    }
995}
996
997impl<D> TryFrom<Vec<D>> for Collation<Vec1<D>>
998where
999    D: AsDiagnosticObject,
1000{
1001    type Error = Vec<D>;
1002
1003    fn try_from(diagnostics: Vec<D>) -> Result<Self, Self::Error> {
1004        Vec1::try_from(diagnostics).map(Collation::from)
1005    }
1006}