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.rs150
1 files changed, 138 insertions, 12 deletions
diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs
index b87555d06d..39b105de98 100644
--- a/tvix/eval/src/errors.rs
+++ b/tvix/eval/src/errors.rs
@@ -1,16 +1,24 @@
 use std::fmt::Display;
 
+use codemap::{CodeMap, Span};
+use codemap_diagnostic::{Diagnostic, Emitter, Level, SpanLabel, SpanStyle};
+
+use crate::Value;
+
 #[derive(Clone, Debug)]
 pub enum ErrorKind {
+    /// These are user-generated errors through builtins.
+    Throw(String),
+    Abort(String),
+    AssertionFailed,
+
     DuplicateAttrsKey {
         key: String,
     },
 
     /// Attempted to specify an invalid key type (e.g. integer) in a
     /// dynamic attribute name.
-    InvalidAttributeName {
-        given: &'static str,
-    },
+    InvalidAttributeName(Value),
 
     AttributeNotFound {
         name: String,
@@ -30,7 +38,7 @@ pub enum ErrorKind {
     PathResolution(String),
 
     /// Dynamic keys are not allowed in let.
-    DynamicKeyInLet(rnix::SyntaxNode),
+    DynamicKeyInLet,
 
     /// Unknown variable in statically known scope.
     UnknownStaticVariable,
@@ -39,7 +47,7 @@ pub enum ErrorKind {
     UnknownDynamicVariable(String),
 
     /// User is defining the same variable twice at the same depth.
-    VariableAlreadyDefined(String),
+    VariableAlreadyDefined(Span),
 
     /// Attempt to call something that is not callable.
     NotCallable,
@@ -49,12 +57,6 @@ pub enum ErrorKind {
 
     ParseErrors(Vec<rnix::parser::ParseError>),
 
-    AssertionFailed,
-
-    /// These are user-generated errors through builtins.
-    Throw(String),
-    Abort(String),
-
     /// An error occured while forcing a thunk, and needs to be
     /// chained up.
     ThunkForce(Box<Error>),
@@ -68,7 +70,7 @@ pub enum ErrorKind {
 #[derive(Clone, Debug)]
 pub struct Error {
     pub kind: ErrorKind,
-    pub span: codemap::Span,
+    pub span: Span,
 }
 
 impl Display for Error {
@@ -78,3 +80,127 @@ impl Display for Error {
 }
 
 pub type EvalResult<T> = Result<T, Error>;
+
+impl Error {
+    pub fn fancy_format_str(&self, codemap: &CodeMap) -> String {
+        let mut out = vec![];
+        Emitter::vec(&mut out, Some(codemap)).emit(&[self.diagnostic(codemap)]);
+        String::from_utf8_lossy(&out).to_string()
+    }
+
+    /// Create the optional span label displayed as an annotation on
+    /// the underlined span of the error.
+    fn span_label(&self) -> Option<String> {
+        None
+    }
+
+    /// Create the primary error message displayed to users.
+    fn message(&self, codemap: &CodeMap) -> String {
+        match &self.kind {
+            ErrorKind::Throw(msg) => format!("error thrown: {}", msg),
+            ErrorKind::Abort(msg) => format!("evaluation aborted: {}", msg),
+            ErrorKind::AssertionFailed => "assertion failed".to_string(),
+
+            ErrorKind::DuplicateAttrsKey { key } => {
+                format!("attribute key '{}' already defined", key)
+            }
+
+            ErrorKind::InvalidAttributeName(val) => format!(
+                "found attribute name '{}' of type '{}', but attribute names must be strings",
+                val,
+                val.type_of()
+            ),
+
+            ErrorKind::AttributeNotFound { name } => format!(
+                "attribute with name '{}' could not be found in the set",
+                name
+            ),
+
+            ErrorKind::TypeError { expected, actual } => format!(
+                "expected value of type '{}', but found a '{}'",
+                expected, actual
+            ),
+
+            ErrorKind::Incomparable { lhs, rhs } => {
+                format!("can not compare a {} with a {}", lhs, rhs)
+            }
+
+            ErrorKind::PathResolution(err) => format!("could not resolve path: {}", err),
+
+            ErrorKind::DynamicKeyInLet => {
+                "dynamically evaluated keys are not allowed in let-bindings".to_string()
+            }
+
+            ErrorKind::UnknownStaticVariable => "variable not found".to_string(),
+
+            ErrorKind::UnknownDynamicVariable(name) => format!(
+                r#"variable '{}' could not be found
+
+Note that this occured within a `with`-expression. The problem may be related
+to a missing value in the attribute set(s) included via `with`."#,
+                name
+            ),
+
+            ErrorKind::VariableAlreadyDefined(_) => "variable has already been defined".to_string(),
+
+            ErrorKind::NotCallable => {
+                "this value is not callable (i.e. not a function or builtin)".to_string()
+            }
+
+            ErrorKind::InfiniteRecursion => "infinite recursion encountered".to_string(),
+
+            // TODO(tazjin): these errors should actually end up with
+            // individual spans etc.
+            ErrorKind::ParseErrors(errors) => format!("failed to parse Nix code: {:?}", errors),
+
+            // 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) => err.message(codemap),
+
+            ErrorKind::NotImplemented(feature) => {
+                format!("feature not yet implemented in Tvix: {}", feature)
+            }
+        }
+    }
+
+    /// Return the unique error code for this variant which can be
+    /// used to refer users to documentation.
+    fn code(&self) -> &'static str {
+        match self.kind {
+            ErrorKind::Throw(_) => "E001",
+            ErrorKind::Abort(_) => "E002",
+            ErrorKind::AssertionFailed => "E003",
+            ErrorKind::InvalidAttributeName { .. } => "E004",
+            ErrorKind::AttributeNotFound { .. } => "E005",
+            ErrorKind::TypeError { .. } => "E006",
+            ErrorKind::Incomparable { .. } => "E007",
+            ErrorKind::PathResolution(_) => "E008",
+            ErrorKind::DynamicKeyInLet => "E009",
+            ErrorKind::UnknownStaticVariable => "E010",
+            ErrorKind::UnknownDynamicVariable(_) => "E011",
+            ErrorKind::VariableAlreadyDefined(_) => "E012",
+            ErrorKind::NotCallable => "E013",
+            ErrorKind::InfiniteRecursion => "E014",
+            ErrorKind::ParseErrors(_) => "E015",
+            ErrorKind::DuplicateAttrsKey { .. } => "E016",
+            ErrorKind::ThunkForce(_) => "E017",
+            ErrorKind::NotImplemented(_) => "E999",
+        }
+    }
+
+    fn diagnostic(&self, codemap: &CodeMap) -> Diagnostic {
+        let span_label = SpanLabel {
+            label: self.span_label(),
+            span: self.span,
+            style: SpanStyle::Primary,
+        };
+
+        Diagnostic {
+            level: Level::Error,
+            message: self.message(codemap),
+            spans: vec![span_label],
+            code: Some(self.code().into()),
+        }
+    }
+}