summaryrefslogtreecommitdiff
path: root/dhall/src/error
diff options
context:
space:
mode:
authorNadrieril Feneanar2020-02-02 18:21:38 +0000
committerGitHub2020-02-02 18:21:38 +0000
commitb7b5a0d27eb0cccdcd0c532a7042b3514eacbe40 (patch)
treea65cf1d2a53d67d2ea8f808c9a8408a8f96713e7 /dhall/src/error
parent72a6fef65bb3d34be1f501a1f6de66fb8a54fa04 (diff)
parentf3681f7a32ddb78db4d564769b50b697c54ebeac (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.rs168
-rw-r--r--dhall/src/error/mod.rs12
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()