diff options
author | Nadrieril Feneanar | 2020-02-02 18:21:38 +0000 |
---|---|---|
committer | GitHub | 2020-02-02 18:21:38 +0000 |
commit | b7b5a0d27eb0cccdcd0c532a7042b3514eacbe40 (patch) | |
tree | a65cf1d2a53d67d2ea8f808c9a8408a8f96713e7 /dhall/src/error | |
parent | 72a6fef65bb3d34be1f501a1f6de66fb8a54fa04 (diff) | |
parent | f3681f7a32ddb78db4d564769b50b697c54ebeac (diff) |
Merge pull request #127 from Nadrieril/nicer-type-errors
Enable multiple locations for type errors
Diffstat (limited to 'dhall/src/error')
-rw-r--r-- | dhall/src/error/builder.rs | 168 | ||||
-rw-r--r-- | dhall/src/error/mod.rs | 12 |
2 files changed, 171 insertions, 9 deletions
diff --git a/dhall/src/error/builder.rs b/dhall/src/error/builder.rs new file mode 100644 index 0000000..39f8dfb --- /dev/null +++ b/dhall/src/error/builder.rs @@ -0,0 +1,168 @@ +use annotate_snippets::{ + display_list::DisplayList, + formatter::DisplayListFormatter, + snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, +}; + +use crate::syntax::{ParsedSpan, Span}; + +#[derive(Debug, Clone, Default)] +pub struct ErrorBuilder { + title: FreeAnnotation, + annotations: Vec<SpannedAnnotation>, + footer: Vec<FreeAnnotation>, + /// Inducate that the current builder has already been consumed and consuming it again should + /// panic. + consumed: bool, +} + +#[derive(Debug, Clone)] +struct SpannedAnnotation { + span: ParsedSpan, + message: String, + annotation_type: AnnotationType, +} + +#[derive(Debug, Clone)] +struct FreeAnnotation { + message: String, + annotation_type: AnnotationType, +} + +impl SpannedAnnotation { + fn into_annotation(self) -> SourceAnnotation { + SourceAnnotation { + label: self.message, + annotation_type: self.annotation_type, + range: self.span.as_char_range(), + } + } +} + +impl FreeAnnotation { + fn into_annotation(self) -> Annotation { + Annotation { + label: Some(self.message), + id: None, + annotation_type: self.annotation_type, + } + } +} + +/// A builder that uses the annotate_snippets library to display nice error messages about source +/// code locations. +impl ErrorBuilder { + pub fn new(message: impl ToString) -> Self { + ErrorBuilder { + title: FreeAnnotation { + message: message.to_string(), + annotation_type: AnnotationType::Error, + }, + annotations: Vec::new(), + footer: Vec::new(), + consumed: false, + } + } + + pub fn span_annot( + &mut self, + span: Span, + message: impl ToString, + annotation_type: AnnotationType, + ) -> &mut Self { + // Ignore spans not coming from a source file + let span = match span { + Span::Parsed(span) => span, + _ => return self, + }; + self.annotations.push(SpannedAnnotation { + span, + message: message.to_string(), + annotation_type, + }); + self + } + pub fn footer_annot( + &mut self, + message: impl ToString, + annotation_type: AnnotationType, + ) -> &mut Self { + self.footer.push(FreeAnnotation { + message: message.to_string(), + annotation_type, + }); + self + } + + pub fn span_err( + &mut self, + span: Span, + message: impl ToString, + ) -> &mut Self { + self.span_annot(span, message, AnnotationType::Error) + } + pub fn span_help( + &mut self, + span: Span, + message: impl ToString, + ) -> &mut Self { + self.span_annot(span, message, AnnotationType::Help) + } + pub fn help(&mut self, message: impl ToString) -> &mut Self { + self.footer_annot(message, AnnotationType::Help) + } + pub fn note(&mut self, message: impl ToString) -> &mut Self { + self.footer_annot(message, AnnotationType::Note) + } + + // TODO: handle multiple files + pub fn format(&mut self) -> String { + if self.consumed { + panic!("tried to format the same ErrorBuilder twice") + } + let this = std::mem::replace(self, ErrorBuilder::default()); + self.consumed = true; + drop(self); // Get rid of the self reference so we don't use it by mistake. + + let slices = if this.annotations.is_empty() { + Vec::new() + } else { + let input = this.annotations[0].span.to_input(); + let annotations = this + .annotations + .into_iter() + .map(|annot| annot.into_annotation()) + .collect(); + vec![Slice { + source: input, + line_start: 1, // TODO + origin: Some("<current file>".to_string()), + fold: true, + annotations, + }] + }; + let footer = this + .footer + .into_iter() + .map(|annot| annot.into_annotation()) + .collect(); + + let snippet = Snippet { + title: Some(this.title.into_annotation()), + slices, + footer, + }; + let dl = DisplayList::from(snippet); + let dlf = DisplayListFormatter::new(true, false); + format!("{}", dlf.format(&dl)) + } +} + +impl Default for FreeAnnotation { + fn default() -> Self { + FreeAnnotation { + message: String::new(), + annotation_type: AnnotationType::Error, + } + } +} diff --git a/dhall/src/error/mod.rs b/dhall/src/error/mod.rs index 4df018d..6ea7d0c 100644 --- a/dhall/src/error/mod.rs +++ b/dhall/src/error/mod.rs @@ -1,10 +1,12 @@ use std::io::Error as IOError; use crate::semantics::resolve::ImportStack; -use crate::semantics::Value; use crate::syntax::{Import, ParseError}; use crate::NormalizedExpr; +mod builder; +pub(crate) use builder::*; + pub type Result<T> = std::result::Result<T, Error>; #[derive(Debug)] @@ -46,8 +48,6 @@ pub struct TypeError { #[derive(Debug)] pub(crate) enum TypeMessage { // UnboundVariable(Span), - InvalidInputType(Value), - InvalidOutputType(Value), // NotAFunction(Value), // TypeMismatch(Value, Value, Value), // AnnotMismatch(Value, Value), @@ -97,12 +97,6 @@ impl std::fmt::Display for TypeError { use TypeMessage::*; let msg = match &self.message { // UnboundVariable(var) => var.error("Type error: Unbound variable"), - InvalidInputType(v) => { - v.span().error("Type error: Invalid function input") - } - InvalidOutputType(v) => { - v.span().error("Type error: Invalid function output") - } // NotAFunction(v) => v.span().error("Type error: Not a function"), // TypeMismatch(x, y, z) => { // x.span() |