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}