about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/eval/src/compiler/mod.rs16
-rw-r--r--tvix/eval/src/errors.rs150
-rw-r--r--tvix/eval/src/value/attrs.rs6
3 files changed, 145 insertions, 27 deletions
diff --git a/tvix/eval/src/compiler/mod.rs b/tvix/eval/src/compiler/mod.rs
index 7868bced4653..440ac284c1a6 100644
--- a/tvix/eval/src/compiler/mod.rs
+++ b/tvix/eval/src/compiler/mod.rs
@@ -1114,21 +1114,17 @@ impl Compiler<'_, '_> {
             self.scope_mut().poison(global_ident, depth);
         }
 
-        let mut shadowed = false;
         for other in self.scope().locals.iter().rev() {
             if other.has_name(&name) && other.depth == depth {
-                shadowed = true;
+                self.emit_error(
+                    self.span_for(node),
+                    ErrorKind::VariableAlreadyDefined(other.span),
+                );
+
                 break;
             }
         }
 
-        if shadowed {
-            self.emit_error(
-                self.span_for(node),
-                ErrorKind::VariableAlreadyDefined(name.clone()),
-            );
-        }
-
         let span = self.span_for(node);
         self.scope_mut().declare_local(name, span)
     }
@@ -1264,7 +1260,7 @@ impl Compiler<'_, '_> {
         N: AstNode<Language = rnix::NixLanguage>,
     {
         Error {
-            kind: ErrorKind::DynamicKeyInLet(node.syntax().clone()),
+            kind: ErrorKind::DynamicKeyInLet,
             span: self.span_for(node),
         }
     }
diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs
index b87555d06d52..39b105de9812 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()),
+        }
+    }
+}
diff --git a/tvix/eval/src/value/attrs.rs b/tvix/eval/src/value/attrs.rs
index b8ae51bf48fc..4c18ad2f55b2 100644
--- a/tvix/eval/src/value/attrs.rs
+++ b/tvix/eval/src/value/attrs.rs
@@ -286,11 +286,7 @@ impl NixAttrs {
                     continue;
                 }
 
-                other => {
-                    return Err(ErrorKind::InvalidAttributeName {
-                        given: other.type_of(),
-                    })
-                }
+                other => return Err(ErrorKind::InvalidAttributeName(other)),
             }
         }