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.rs129
1 files changed, 81 insertions, 48 deletions
diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs
index 2f00ecab8543..de17f8d332ed 100644
--- a/tvix/eval/src/errors.rs
+++ b/tvix/eval/src/errors.rs
@@ -81,9 +81,13 @@ pub enum ErrorKind {
 
     ParseErrors(Vec<rnix::parser::ParseError>),
 
-    /// An error occured while forcing a thunk, and needs to be
-    /// chained up.
-    ThunkForce(Box<Error>),
+    /// An error occured while executing some native code (e.g. a
+    /// builtin), and needs to be chained up.
+    NativeError(Box<Error>),
+
+    /// An error occured while executing Tvix bytecode, but needs to
+    /// be chained up.
+    BytecodeError(Box<Error>),
 
     /// Given type can't be coerced to a string in the respective context
     NotCoercibleToString {
@@ -175,7 +179,7 @@ pub enum ErrorKind {
 impl error::Error for Error {
     fn source(&self) -> Option<&(dyn error::Error + 'static)> {
         match &self.kind {
-            ErrorKind::ThunkForce(err) => err.source(),
+            ErrorKind::NativeError(err) | ErrorKind::BytecodeError(err) => err.source(),
             ErrorKind::ParseErrors(err) => err.first().map(|e| e as &dyn error::Error),
             ErrorKind::ParseIntError(err) => Some(err),
             ErrorKind::ImportParseError { errors, .. } => {
@@ -216,15 +220,6 @@ impl From<XmlError> for ErrorKind {
     }
 }
 
-/// Implementation used if errors occur while forcing thunks (which
-/// can potentially be threaded through a few contexts, i.e. nested
-/// thunks).
-impl From<Error> for ErrorKind {
-    fn from(e: Error) -> Self {
-        Self::ThunkForce(Box::new(e))
-    }
-}
-
 impl From<io::Error> for ErrorKind {
     fn from(e: io::Error) -> Self {
         ErrorKind::IO {
@@ -239,7 +234,7 @@ impl ErrorKind {
     pub fn is_catchable(&self) -> bool {
         match self {
             Self::Throw(_) | Self::AssertionFailed | Self::NixPathResolution(_) => true,
-            Self::ThunkForce(err) => err.kind.is_catchable(),
+            Self::NativeError(err) | Self::BytecodeError(err) => err.kind.is_catchable(),
             _ => false,
         }
     }
@@ -361,10 +356,8 @@ to a missing value in the attribute set(s) included via `with`."#,
             // Errors themselves ignored here & handled in Self::spans instead
             ErrorKind::ParseErrors(_) => write!(f, "failed to parse Nix code:"),
 
-            // TODO(tazjin): trace through the whole chain of thunk
-            // forcing errors with secondary spans, instead of just
-            // delegating to the inner error
-            ErrorKind::ThunkForce(err) => write!(f, "{err}"),
+            ErrorKind::NativeError(_) => write!(f, "while evaluating this native code"),
+            ErrorKind::BytecodeError(_) => write!(f, "while evaluating this Nix code"),
 
             ErrorKind::NotCoercibleToString { kind, from } => {
                 let kindly = match kind {
@@ -757,7 +750,8 @@ impl Error {
             | ErrorKind::NotCallable(_)
             | ErrorKind::InfiniteRecursion
             | ErrorKind::ParseErrors(_)
-            | ErrorKind::ThunkForce(_)
+            | ErrorKind::NativeError(_)
+            | ErrorKind::BytecodeError(_)
             | ErrorKind::NotCoercibleToString { .. }
             | ErrorKind::NotAnAbsolutePath(_)
             | ErrorKind::ParseIntError(_)
@@ -831,12 +825,9 @@ impl Error {
             // Placeholder error while Tvix is under construction.
             ErrorKind::NotImplemented(_) => "E999",
 
-            // TODO: thunk force errors should yield a chained
-            // diagnostic, but until then we just forward the error
-            // code from the inner error.
-            //
-            // The error code for thunk forces is E017.
-            ErrorKind::ThunkForce(ref err) => err.code(),
+            // Chained errors should yield the code of the innermost
+            // error.
+            ErrorKind::NativeError(ref err) | ErrorKind::BytecodeError(ref err) => err.code(),
 
             ErrorKind::WithContext { .. } => {
                 panic!("internal ErrorKind::WithContext variant leaked")
@@ -855,27 +846,6 @@ impl Error {
                 spans_for_parse_errors(&file, errors)
             }
 
-            // Unwrap thunk errors to the innermost one
-            // TODO: limit the number of intermediates!
-            ErrorKind::ThunkForce(err) => {
-                let mut labels = err.spans(source);
-
-                // Only add this thunk to the "cause chain" if it span isn't
-                // exactly identical to the next-higher level, which is very
-                // common for the last thunk in a chain.
-                if let Some(label) = labels.last() {
-                    if label.span != self.span {
-                        labels.push(SpanLabel {
-                            label: Some("while evaluating this".into()),
-                            span: self.span,
-                            style: SpanStyle::Secondary,
-                        });
-                    }
-                }
-
-                labels
-            }
-
             ErrorKind::UnexpectedArgument { formals_span, .. } => {
                 vec![
                     SpanLabel {
@@ -926,19 +896,82 @@ impl Error {
     /// any) of an error.
     fn diagnostics(&self, source: &SourceCode) -> Vec<Diagnostic> {
         match &self.kind {
-            ErrorKind::ThunkForce(err) => err.diagnostics(source),
-
             ErrorKind::ImportCompilerError { errors, .. } => {
                 let mut out = vec![self.diagnostic(source)];
                 out.extend(errors.iter().map(|e| e.diagnostic(source)));
                 out
             }
 
+            // When encountering either of these error kinds, we are dealing
+            // with the top of an error chain.
+            //
+            // An error chain creates a list of diagnostics which provide trace
+            // information.
+            //
+            // We don't know how deep this chain is, so we avoid recursing in
+            // this function while unrolling the chain.
+            ErrorKind::NativeError(next) | ErrorKind::BytecodeError(next) => {
+                // Accumulated diagnostics to return.
+                let mut diagnostics: Vec<Diagnostic> = vec![];
+
+                // The next (inner) error to add to the diagnostics, after this
+                // one.
+                let mut next = *next.clone();
+
+                // Diagnostic message for *this* error.
+                let mut this_message = self.to_string();
+
+                // Primary span for *this* error.
+                let mut this_span = self.span;
+
+                // Diagnostic spans for *this* error.
+                let mut this_spans = self.spans(source);
+
+                loop {
+                    if is_new_span(
+                        this_span,
+                        diagnostics.last().and_then(|last| last.spans.last()),
+                    ) {
+                        diagnostics.push(Diagnostic {
+                            level: Level::Note,
+                            message: this_message,
+                            spans: this_spans,
+                            code: None, // only the top-level error has one
+                        });
+                    }
+
+                    this_message = next.to_string();
+                    this_span = next.span;
+                    this_spans = next.spans(source);
+
+                    match next.kind {
+                        ErrorKind::NativeError(inner) | ErrorKind::BytecodeError(inner) => {
+                            next = *inner;
+                            continue;
+                        }
+                        _ => {
+                            diagnostics.extend(next.diagnostics(source));
+                            break;
+                        }
+                    }
+                }
+
+                diagnostics
+            }
+
             _ => vec![self.diagnostic(source)],
         }
     }
 }
 
+// Check if this error is in a different span from its immediate ancestor.
+fn is_new_span(this_span: Span, parent: Option<&SpanLabel>) -> bool {
+    match parent {
+        None => true,
+        Some(parent) => parent.span != this_span,
+    }
+}
+
 // Convenience methods to add context on other types.
 pub trait AddContext {
     /// Add context to the error-carrying type.