about summary refs log tree commit diff
path: root/tvix/eval/src/errors.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/eval/src/errors.rs')
-rw-r--r--tvix/eval/src/errors.rs84
1 files changed, 80 insertions, 4 deletions
diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs
index ed161f4155f5..ccc2d8976440 100644
--- a/tvix/eval/src/errors.rs
+++ b/tvix/eval/src/errors.rs
@@ -158,6 +158,12 @@ pub enum ErrorKind {
     /// not actually implemented yet, and without which eval can not
     /// proceed.
     NotImplemented(&'static str),
+
+    /// Internal variant which should disappear during error construction.
+    WithContext {
+        context: String,
+        underlying: Box<ErrorKind>,
+    },
 }
 
 impl error::Error for Error {
@@ -242,8 +248,29 @@ impl From<serde_json::Error> for ErrorKind {
 
 #[derive(Clone, Debug)]
 pub struct Error {
-    pub kind: ErrorKind,
-    pub span: Span,
+    kind: ErrorKind,
+    span: Span,
+    contexts: Vec<String>,
+}
+
+impl Error {
+    pub fn new(mut kind: ErrorKind, span: Span) -> Self {
+        let mut contexts = vec![];
+        while let ErrorKind::WithContext {
+            context,
+            underlying,
+        } = kind
+        {
+            kind = *underlying;
+            contexts.push(context);
+        }
+
+        Error {
+            kind,
+            span,
+            contexts,
+        }
+    }
 }
 
 impl Display for ErrorKind {
@@ -434,6 +461,10 @@ to a missing value in the attribute set(s) included via `with`."#,
             ErrorKind::NotImplemented(feature) => {
                 write!(f, "feature not yet implemented in Tvix: {}", feature)
             }
+
+            ErrorKind::WithContext { .. } => {
+                panic!("internal ErrorKind::WithContext variant leaked")
+            }
         }
     }
 }
@@ -721,7 +752,8 @@ impl Error {
             | ErrorKind::Xml(_)
             | ErrorKind::TvixError(_)
             | ErrorKind::TvixBug { .. }
-            | ErrorKind::NotImplemented(_) => return None,
+            | ErrorKind::NotImplemented(_)
+            | ErrorKind::WithContext { .. } => return None,
         };
 
         Some(label.into())
@@ -782,11 +814,15 @@ impl Error {
             //
             // The error code for thunk forces is E017.
             ErrorKind::ThunkForce(ref err) => err.code(),
+
+            ErrorKind::WithContext { .. } => {
+                panic!("internal ErrorKind::WithContext variant leaked")
+            }
         }
     }
 
     fn spans(&self, source: &SourceCode) -> Vec<SpanLabel> {
-        match &self.kind {
+        let mut spans = match &self.kind {
             ErrorKind::ImportParseError { errors, file, .. } => {
                 spans_for_parse_errors(file, errors)
             }
@@ -840,7 +876,17 @@ impl Error {
                     style: SpanStyle::Primary,
                 }]
             }
+        };
+
+        for ctx in &self.contexts {
+            spans.push(SpanLabel {
+                label: Some(format!("while {}", ctx)),
+                span: self.span,
+                style: SpanStyle::Secondary,
+            });
         }
+
+        spans
     }
 
     /// Create the primary diagnostic for a given error.
@@ -869,3 +915,33 @@ impl Error {
         }
     }
 }
+
+// Convenience methods to add context on other types.
+pub trait AddContext {
+    /// Add context to the error-carrying type.
+    fn context<S: Into<String>>(self, ctx: S) -> Self;
+}
+
+impl AddContext for ErrorKind {
+    fn context<S: Into<String>>(self, ctx: S) -> Self {
+        ErrorKind::WithContext {
+            context: ctx.into(),
+            underlying: Box::new(self),
+        }
+    }
+}
+
+impl<T> AddContext for Result<T, ErrorKind> {
+    fn context<S: Into<String>>(self, ctx: S) -> Self {
+        self.map_err(|kind| kind.context(ctx))
+    }
+}
+
+impl<T> AddContext for Result<T, Error> {
+    fn context<S: Into<String>>(self, ctx: S) -> Self {
+        self.map_err(|err| Error {
+            kind: err.kind.context(ctx),
+            ..err
+        })
+    }
+}