pintc/
error.rs

1mod compile_error;
2mod handler;
3mod lex_error;
4mod parse_error;
5
6use crate::span::{empty_span, Span, Spanned};
7use ariadne::{FnCache, Label, Report, ReportKind, Source};
8use std::fmt::{Display, Formatter, Result, Write};
9use thiserror::Error;
10use yansi::{Color, Paint, Style};
11
12pub(super) use compile_error::CompileError;
13pub(super) use compile_error::LargeTypeError;
14pub use handler::{ErrorEmitted, Handler};
15pub(super) use lex_error::LexError;
16pub(super) use parse_error::ParseError;
17
18/// An error label used for pretty printing error messages to the terminal
19pub struct ErrorLabel {
20    pub(super) message: String,
21    pub(super) span: Span,
22    pub(super) color: Color,
23}
24
25/// A general compile error
26#[derive(Error, Debug)]
27pub enum Error {
28    #[error("{error}")]
29    Lex { span: Span, error: LexError },
30    #[error("{error}")]
31    Parse { error: ParseError },
32    #[error("{error}")]
33    Compile { error: CompileError },
34    #[error("{child}")]
35    MacroBodyWrapper {
36        child: Box<Self>,
37        macro_name: String,
38        macro_span: Span,
39    },
40    #[error("compiler internal error: {msg}")]
41    Internal { msg: String, span: Span },
42}
43
44#[derive(Debug)]
45pub struct Errors(pub Vec<Error>);
46
47impl Display for Errors {
48    fn fmt(&self, f: &mut Formatter) -> Result {
49        write!(
50            f,
51            "{}",
52            self.0
53                .iter()
54                .map(|err| err.display_raw())
55                .collect::<String>()
56                .trim_end()
57        )
58    }
59}
60
61/// Types that implement this trait can be pretty printed to the terminal using the `ariadne` crate
62/// by calling the `print()` method.
63pub trait ReportableError
64where
65    Self: std::fmt::Display + Spanned,
66{
67    /// A list of error labels for emitting diagnostics at multiple span locations
68    fn labels(&self) -> Vec<ErrorLabel>;
69
70    /// A helpful "note" about the error
71    fn note(&self) -> Option<String>;
72
73    /// A unique error code
74    fn code(&self) -> Option<String>;
75
76    /// Additional information to help the user address the diagnostic
77    fn help(&self) -> Option<String>;
78
79    /// Pretty print an error to the terminal
80    fn print(&self) {
81        let filepaths_and_sources = self
82            .labels()
83            .iter()
84            .map(|label| {
85                let filepath = format!("{}", label.span.context().display());
86                let source = std::fs::read_to_string(filepath.clone()).unwrap_or("<none>".into());
87                (filepath, source)
88            })
89            .collect::<Vec<(String, String)>>();
90
91        let error_file: &str = &format!("{}", self.span().context().display());
92        let mut report_builder =
93            Report::build(ReportKind::Error, (error_file, self.span().range.clone()))
94                .with_message(format!("{}", self.bold()))
95                .with_labels(
96                    self.labels()
97                        .iter()
98                        .enumerate()
99                        .map(|(index, label)| {
100                            let filepath: &str = &filepaths_and_sources[index].0;
101                            let mut style = Style::new().bold();
102                            style.foreground = Some(label.color);
103                            Label::new((filepath, label.span.start()..label.span.end()))
104                                .with_message(label.message.clone().paint(style))
105                                .with_color(label.color)
106                        })
107                        .collect::<Vec<_>>(),
108                );
109
110        if let Some(code) = self.code() {
111            report_builder = report_builder.with_code(code);
112        }
113
114        if let Some(note) = self.note() {
115            report_builder = report_builder.with_note(note);
116        }
117
118        if let Some(help) = self.help() {
119            report_builder = report_builder.with_help(help);
120        }
121
122        report_builder
123            .finish()
124            .eprint(
125                FnCache::new(|id: &&str| Err(Box::new(format!("Failed to fetch source '{id}'"))))
126                    .with_sources(
127                        filepaths_and_sources
128                            .iter()
129                            .map(|(id, s)| (&id[..], Source::from(s)))
130                            .collect(),
131                    ),
132            )
133            .unwrap();
134    }
135
136    fn display_raw(&self) -> String {
137        self.to_string()
138            + "\n"
139            + &self.labels().iter().fold(String::new(), |mut acc, label| {
140                writeln!(
141                    &mut acc,
142                    "@{}..{}: {}",
143                    label.span.start(),
144                    label.span.end(),
145                    label.message
146                )
147                .expect("Failed to write label to string");
148                acc
149            })
150            + &self
151                .note()
152                .map_or(String::new(), |note| format!("{note}\n"))
153            + &self
154                .help()
155                .map_or(String::new(), |help| format!("{help}\n"))
156    }
157}
158
159impl ReportableError for Error {
160    fn labels(&self) -> Vec<ErrorLabel> {
161        use Error::*;
162        match self {
163            Lex { error, span } => match error {
164                // For lex errors, insert the labels here for now because we don't have access to
165                // `span` in `LexError` just yet.
166                LexError::InvalidToken => vec![ErrorLabel {
167                    message: "invalid token".into(),
168                    span: span.clone(),
169                    color: Color::Red,
170                }],
171            },
172            Parse { error } => error.labels(),
173            Compile { error } => error.labels(),
174            MacroBodyWrapper {
175                child,
176                macro_name,
177                macro_span,
178            } => {
179                let mut labels = child.labels();
180                labels.push(ErrorLabel {
181                    message: format!("when making macro call to '{macro_name}'"),
182                    span: macro_span.clone(),
183                    color: Color::Yellow,
184                });
185                labels
186            }
187            Internal { msg, span } => {
188                if span == &empty_span() {
189                    Vec::new()
190                } else {
191                    vec![ErrorLabel {
192                        message: msg.to_string(),
193                        span: span.clone(),
194                        color: Color::Red,
195                    }]
196                }
197            }
198        }
199    }
200
201    fn note(&self) -> Option<String> {
202        use Error::*;
203        match self {
204            Lex { .. } => None,
205            Parse { error } => error.note(),
206            Compile { error } => error.note(),
207            MacroBodyWrapper { child, .. } => child.note(),
208            Internal { .. } => None,
209        }
210    }
211
212    fn code(&self) -> Option<String> {
213        use Error::*;
214        match self {
215            Lex { .. } => None,
216            Parse { error } => error.code().map(|code| format!("P{code}")),
217            Compile { error } => error.code().map(|code| format!("C{code}")),
218            MacroBodyWrapper { child, .. } => child.code(),
219            Internal { .. } => None,
220        }
221    }
222
223    fn help(&self) -> Option<String> {
224        use Error::*;
225        match self {
226            Lex { .. } => None,
227            Parse { error } => error.help(),
228            Compile { error } => error.help(),
229            MacroBodyWrapper { child, .. } => child.help(),
230            Internal { .. } => None,
231        }
232    }
233}
234
235impl Spanned for Error {
236    fn span(&self) -> &Span {
237        use Error::*;
238        match &self {
239            Lex { span, .. } => span,
240            Parse { error } => error.span(),
241            Compile { error } => error.span(),
242            MacroBodyWrapper { child, .. } => child.span(),
243            Internal { span, .. } => span,
244        }
245    }
246}
247
248/// Print a list of [`Error`] using the `ariadne` crate
249pub fn print_errors(errs: &Errors) {
250    for err in &errs.0 {
251        err.print();
252    }
253}
254
255/// A simple wrapper around `anyhow::bail!` that prints a different message based on a the number
256/// of compile errors.
257#[macro_export]
258macro_rules! pintc_bail {
259    ($number_of_errors: expr, $filepath: expr) => {
260        if $number_of_errors == 1 {
261            anyhow::bail!(
262                "could not compile `{}` due to previous error",
263                format!("{}", $filepath.display())
264            )
265        } else {
266            anyhow::bail!(
267                "could not compile `{}` due to {} previous errors",
268                format!("{}", $filepath.display()),
269                $number_of_errors
270            )
271        }
272    };
273}