summaryrefslogtreecommitdiff
path: root/src/diag.rs
blob: f04553107097a1eab4329aa79572853539fc64ea (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//! Diagnostics.

use std::fmt::{self, Display, Formatter};

use serde::{Deserialize, Serialize};

use crate::syntax::{Span, Spanned};

/// Early-return with a vec-boxed [`Error`].
#[macro_export]
macro_rules! bail {
    ($span:expr, $message:expr $(,)?) => {
        return Err($crate::diag::Error::boxed($span, $message,))
    };

    ($span:expr, $fmt:expr, $($arg:expr),+ $(,)?) => {
        bail!($span, format!($fmt, $($arg),+))
    };
}

/// The result type for typesetting and all its subpasses.
pub type TypResult<T> = Result<T, Box<Vec<Error>>>;

/// A result type with a string error message.
pub type StrResult<T> = Result<T, String>;

/// An error in a source file.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Error {
    /// The erroneous location in the source code.
    pub span: Span,
    /// A diagnostic message describing the problem.
    pub message: String,
    /// The trace of function calls leading to the error.
    pub trace: Vec<Spanned<Tracepoint>>,
}

impl Error {
    /// Create a new, bare error.
    pub fn new(span: Span, message: impl Into<String>) -> Self {
        Self {
            span,
            trace: vec![],
            message: message.into(),
        }
    }

    /// Create a boxed vector containing one error. The return value is suitable
    /// as the `Err` variant of a [`TypResult`].
    pub fn boxed(span: Span, message: impl Into<String>) -> Box<Vec<Self>> {
        Box::new(vec![Self::new(span, message)])
    }
}

/// A part of an error's [trace](Error::trace).
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub enum Tracepoint {
    /// A function call.
    Call(Option<String>),
    /// A module import.
    Import,
}

impl Display for Tracepoint {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self {
            Tracepoint::Call(Some(name)) => {
                write!(f, "error occured in this call of function `{}`", name)
            }
            Tracepoint::Call(None) => f.pad("error occured in this function call"),
            Tracepoint::Import => f.pad("error occured while importing this module"),
        }
    }
}

/// Convert a [`StrResult`] to a [`TypResult`] by adding span information.
pub trait At<T> {
    /// Add the span information.
    fn at(self, span: Span) -> TypResult<T>;
}

impl<T> At<T> for StrResult<T> {
    fn at(self, span: Span) -> TypResult<T> {
        self.map_err(|message| Error::boxed(span, message))
    }
}

/// Enrich a [`TypResult`] with a tracepoint.
pub trait Trace<T> {
    /// Add the tracepoint to all errors that lie outside the `span`.
    fn trace<F>(self, make_point: F, span: Span) -> Self
    where
        F: Fn() -> Tracepoint;
}

impl<T> Trace<T> for TypResult<T> {
    fn trace<F>(self, make_point: F, span: Span) -> Self
    where
        F: Fn() -> Tracepoint,
    {
        self.map_err(|mut errors| {
            for error in errors.iter_mut() {
                if !span.contains(error.span) {
                    error.trace.push(Spanned::new(make_point(), span));
                }
            }
            errors
        })
    }
}