about summary refs log tree commit diff
path: root/tvix/eval/src/value
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2022-09-01T20·50+0300
committertazjin <tazjin@tvl.su>2022-09-08T07·59+0000
commit377ba19d75a0354c51d73dd38c4a29feefcc68e4 (patch)
tree75cc9b21752a29c5b6b35db7530540b84bb78642 /tvix/eval/src/value
parent197fe37daef242596900bcab948d6fc14348f910 (diff)
feat(tvix/eval): ensure all errors always carry a span r/4741
Previously error spans were optional because the information about
code spans was not available at runtime. Now that this information has
been added, the error type will always carry a span.

This change is very invasive all throughout the codebase. This is due
to the fact that many functions that are called *by* the VM expected
to return `EvalResult`, but this no longer works as the span
information is not available to those functions - only to the VM
itself.

To work around this the majority of these functions have been changed
to return `Result<T, ErrorKind>` instead and an accompanying macro in
the VM constructs the "real" error.

Note that this implementatino currently has a bug where errors
occuring within thunks will yield the location at which the thunk was
forced, not the location at which the error occured within the code.
This will be fixed soon, but the commit is large enough as is.

Change-Id: Ib1ecb81a4d09d464a95ea7ea9e589f3bd08d5202
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6408
Reviewed-by: sterni <sternenseemann@systemli.org>
Tested-by: BuildkiteCI
Diffstat (limited to 'tvix/eval/src/value')
-rw-r--r--tvix/eval/src/value/attrs.rs14
-rw-r--r--tvix/eval/src/value/builtin.rs6
-rw-r--r--tvix/eval/src/value/mod.rs73
-rw-r--r--tvix/eval/src/value/thunk.rs10
4 files changed, 39 insertions, 64 deletions
diff --git a/tvix/eval/src/value/attrs.rs b/tvix/eval/src/value/attrs.rs
index 2954f85220..d122f9155d 100644
--- a/tvix/eval/src/value/attrs.rs
+++ b/tvix/eval/src/value/attrs.rs
@@ -10,7 +10,7 @@ use std::collections::BTreeMap;
 use std::fmt::Display;
 use std::rc::Rc;
 
-use crate::errors::{ErrorKind, EvalResult};
+use crate::errors::ErrorKind;
 
 use super::string::NixString;
 use super::Value;
@@ -236,7 +236,7 @@ impl NixAttrs {
 
     /// Implement construction logic of an attribute set, to encapsulate
     /// logic about attribute set optimisations inside of this module.
-    pub fn construct(count: usize, mut stack_slice: Vec<Value>) -> EvalResult<Self> {
+    pub fn construct(count: usize, mut stack_slice: Vec<Value>) -> Result<Self, ErrorKind> {
         debug_assert!(
             stack_slice.len() == count * 2,
             "construct_attrs called with count == {}, but slice.len() == {}",
@@ -342,12 +342,11 @@ fn attempt_optimise_kv(slice: &mut [Value]) -> Option<NixAttrs> {
 
 // Set an attribute on an in-construction attribute set, while
 // checking against duplicate keys.
-fn set_attr(attrs: &mut NixAttrs, key: NixString, value: Value) -> EvalResult<()> {
+fn set_attr(attrs: &mut NixAttrs, key: NixString, value: Value) -> Result<(), ErrorKind> {
     match attrs.0.map_mut().entry(key) {
         btree_map::Entry::Occupied(entry) => Err(ErrorKind::DuplicateAttrsKey {
             key: entry.key().as_str().to_string(),
-        }
-        .into()),
+        }),
 
         btree_map::Entry::Vacant(entry) => {
             entry.insert(value);
@@ -367,7 +366,7 @@ fn set_nested_attr(
     key: NixString,
     mut path: Vec<NixString>,
     value: Value,
-) -> EvalResult<()> {
+) -> Result<(), ErrorKind> {
     // If there is no next key we are at the point where we
     // should insert the value itself.
     if path.is_empty() {
@@ -408,8 +407,7 @@ fn set_nested_attr(
             _ => {
                 return Err(ErrorKind::DuplicateAttrsKey {
                     key: entry.key().as_str().to_string(),
-                }
-                .into())
+                })
             }
         },
     }
diff --git a/tvix/eval/src/value/builtin.rs b/tvix/eval/src/value/builtin.rs
index e876c23555..d1248b1ec2 100644
--- a/tvix/eval/src/value/builtin.rs
+++ b/tvix/eval/src/value/builtin.rs
@@ -3,13 +3,13 @@
 //!
 //! Builtins are directly backed by Rust code operating on Nix values.
 
-use crate::errors::EvalResult;
+use crate::errors::ErrorKind;
 
 use super::Value;
 
 use std::fmt::{Debug, Display};
 
-pub type BuiltinFn = fn(arg: Vec<Value>) -> EvalResult<Value>;
+pub type BuiltinFn = fn(arg: Vec<Value>) -> Result<Value, ErrorKind>;
 
 /// Represents a single built-in function which directly executes Rust
 /// code that operates on a Nix value.
@@ -50,7 +50,7 @@ impl Builtin {
     /// Apply an additional argument to the builtin, which will either
     /// lead to execution of the function or to returning a partial
     /// builtin.
-    pub fn apply(mut self, arg: Value) -> EvalResult<Value> {
+    pub fn apply(mut self, arg: Value) -> Result<Value, ErrorKind> {
         self.partials.push(arg);
 
         if self.partials.len() == self.arity {
diff --git a/tvix/eval/src/value/mod.rs b/tvix/eval/src/value/mod.rs
index a8bfc164cd..5cfad2e66e 100644
--- a/tvix/eval/src/value/mod.rs
+++ b/tvix/eval/src/value/mod.rs
@@ -10,7 +10,7 @@ mod list;
 mod string;
 mod thunk;
 
-use crate::errors::{ErrorKind, EvalResult};
+use crate::errors::ErrorKind;
 use crate::opcode::StackIdx;
 pub use attrs::NixAttrs;
 pub use builtin::Builtin;
@@ -70,91 +70,59 @@ impl Value {
         }
     }
 
-    pub fn as_bool(&self) -> EvalResult<bool> {
+    pub fn as_bool(&self) -> Result<bool, ErrorKind> {
         match self {
             Value::Bool(b) => Ok(*b),
-            other => Err(ErrorKind::TypeError {
-                expected: "bool",
-                actual: other.type_of(),
-            }
-            .into()),
+            other => Err(type_error("bool", &other)),
         }
     }
 
-    pub fn as_attrs(&self) -> EvalResult<&NixAttrs> {
+    pub fn as_attrs(&self) -> Result<&NixAttrs, ErrorKind> {
         match self {
             Value::Attrs(attrs) => Ok(attrs),
-            other => Err(ErrorKind::TypeError {
-                expected: "set",
-                actual: other.type_of(),
-            }
-            .into()),
+            other => Err(type_error("set", &other)),
         }
     }
 
-    pub fn as_str(&self) -> EvalResult<&str> {
+    pub fn as_str(&self) -> Result<&str, ErrorKind> {
         match self {
             Value::String(s) => Ok(s.as_str()),
-            other => Err(ErrorKind::TypeError {
-                expected: "string",
-                actual: other.type_of(),
-            }
-            .into()),
+            other => Err(type_error("string", &other)),
         }
     }
 
-    pub fn as_list(&self) -> EvalResult<&NixList> {
+    pub fn as_list(&self) -> Result<&NixList, ErrorKind> {
         match self {
             Value::List(xs) => Ok(xs),
-            other => Err(ErrorKind::TypeError {
-                expected: "list",
-                actual: other.type_of(),
-            }
-            .into()),
+            other => Err(type_error("list", &other)),
         }
     }
 
-    pub fn to_string(self) -> EvalResult<NixString> {
+    pub fn to_string(self) -> Result<NixString, ErrorKind> {
         match self {
             Value::String(s) => Ok(s),
-            other => Err(ErrorKind::TypeError {
-                expected: "string",
-                actual: other.type_of(),
-            }
-            .into()),
+            other => Err(type_error("string", &other)),
         }
     }
 
-    pub fn to_attrs(self) -> EvalResult<Rc<NixAttrs>> {
+    pub fn to_attrs(self) -> Result<Rc<NixAttrs>, ErrorKind> {
         match self {
             Value::Attrs(s) => Ok(s),
-            other => Err(ErrorKind::TypeError {
-                expected: "set",
-                actual: other.type_of(),
-            }
-            .into()),
+            other => Err(type_error("set", &other)),
         }
     }
 
-    pub fn to_list(self) -> EvalResult<NixList> {
+    pub fn to_list(self) -> Result<NixList, ErrorKind> {
         match self {
             Value::List(l) => Ok(l),
-            other => Err(ErrorKind::TypeError {
-                expected: "list",
-                actual: other.type_of(),
-            }
-            .into()),
+            other => Err(type_error("list", &other)),
         }
     }
 
-    pub fn to_closure(self) -> EvalResult<Closure> {
+    pub fn to_closure(self) -> Result<Closure, ErrorKind> {
         match self {
             Value::Closure(c) => Ok(c),
-            other => Err(ErrorKind::TypeError {
-                expected: "lambda",
-                actual: other.type_of(),
-            }
-            .into()),
+            other => Err(type_error("lambda", &other)),
         }
     }
 
@@ -231,3 +199,10 @@ impl PartialEq for Value {
         }
     }
 }
+
+fn type_error(expected: &'static str, actual: &Value) -> ErrorKind {
+    ErrorKind::TypeError {
+        expected,
+        actual: actual.type_of(),
+    }
+}
diff --git a/tvix/eval/src/value/thunk.rs b/tvix/eval/src/value/thunk.rs
index 307eb23a75..c2552284fe 100644
--- a/tvix/eval/src/value/thunk.rs
+++ b/tvix/eval/src/value/thunk.rs
@@ -24,7 +24,7 @@ use std::{
     rc::Rc,
 };
 
-use crate::{errors::ErrorKind, upvalues::UpvalueCarrier, vm::VM, EvalResult, Value};
+use crate::{errors::ErrorKind, upvalues::UpvalueCarrier, vm::VM, Value};
 
 use super::Lambda;
 
@@ -64,7 +64,7 @@ impl Thunk {
     /// to it, providing memoization) through interior mutability. In
     /// case of nested thunks, the intermediate thunk representations
     /// are replaced.
-    pub fn force(&self, vm: &mut VM) -> EvalResult<()> {
+    pub fn force(&self, vm: &mut VM) -> Result<(), ErrorKind> {
         // Due to mutable borrowing rules, the following code can't
         // easily use a match statement or something like that; it
         // requires a bit of manual fiddling.
@@ -78,14 +78,16 @@ impl Thunk {
                 }
 
                 ThunkRepr::Evaluated(_) => return Ok(()),
-                ThunkRepr::Blackhole => return Err(ErrorKind::InfiniteRecursion.into()),
+                ThunkRepr::Blackhole => return Err(ErrorKind::InfiniteRecursion),
 
                 ThunkRepr::Suspended { .. } => {
                     if let ThunkRepr::Suspended { lambda, upvalues } =
                         std::mem::replace(&mut *thunk_mut, ThunkRepr::Blackhole)
                     {
                         vm.call(lambda, upvalues, 0);
-                        *thunk_mut = ThunkRepr::Evaluated(vm.run()?);
+                        // TODO: find a cheap way to actually retain
+                        // the original error span
+                        *thunk_mut = ThunkRepr::Evaluated(vm.run().map_err(|e| e.kind)?);
                     }
                 }
             }