about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2022-08-11T11·56+0300
committertazjin <tazjin@tvl.su>2022-08-26T09·02+0000
commit671915837aee2908431b1d1908352fc0ab9cd628 (patch)
tree51dbc9390b14a8b41079b6d19e13060da6927724
parentaaa994137a89ab9d37fb83091dc7d24937898491 (diff)
fix(tvix/eval): add operation to assert boolean type r/4491
This operation is required because both sides of the logical operators
are strictly evaluated by Nix, even if the resulting value is not used
further.

For example, in our implementation of `&&`, if the left-hand side is
`true`, then the result of the expression is simply the right-hand
side value. This value must be asserted to be a boolean for the
semantics of the language to work correctly.

Change-Id: I34f5364f2a444753fa1d8b0a1a2b2d9cdf7c6700
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6157
Tested-by: BuildkiteCI
Reviewed-by: sterni <sternenseemann@systemli.org>
Reviewed-by: grfn <grfn@gws.fyi>
-rw-r--r--tvix/eval/src/compiler.rs3
-rw-r--r--tvix/eval/src/opcode.rs3
-rw-r--r--tvix/eval/src/value/mod.rs4
-rw-r--r--tvix/eval/src/vm.rs14
4 files changed, 24 insertions, 0 deletions
diff --git a/tvix/eval/src/compiler.rs b/tvix/eval/src/compiler.rs
index 90a6155b6b62..1bfd765a07cd 100644
--- a/tvix/eval/src/compiler.rs
+++ b/tvix/eval/src/compiler.rs
@@ -350,6 +350,7 @@ impl Compiler {
         self.compile(node.rhs().unwrap())?;
 
         self.patch_jump(end_idx);
+        self.chunk.add_op(OpCode::OpAssertBool);
 
         Ok(())
     }
@@ -370,6 +371,7 @@ impl Compiler {
         self.chunk.add_op(OpCode::OpPop);
         self.compile(node.rhs().unwrap())?;
         self.patch_jump(end_idx);
+        self.chunk.add_op(OpCode::OpAssertBool);
 
         Ok(())
     }
@@ -390,6 +392,7 @@ impl Compiler {
         self.chunk.add_op(OpCode::OpPop);
         self.compile(node.rhs().unwrap())?;
         self.patch_jump(end_idx);
+        self.chunk.add_op(OpCode::OpAssertBool);
 
         Ok(())
     }
diff --git a/tvix/eval/src/opcode.rs b/tvix/eval/src/opcode.rs
index f577bc985d3a..4831a71ebd6e 100644
--- a/tvix/eval/src/opcode.rs
+++ b/tvix/eval/src/opcode.rs
@@ -53,4 +53,7 @@ pub enum OpCode {
 
     // Strings
     OpInterpolate(usize),
+
+    // Type assertion operators
+    OpAssertBool,
 }
diff --git a/tvix/eval/src/value/mod.rs b/tvix/eval/src/value/mod.rs
index 4a343797480e..99eb4c657ba5 100644
--- a/tvix/eval/src/value/mod.rs
+++ b/tvix/eval/src/value/mod.rs
@@ -91,6 +91,10 @@ impl Value {
             }),
         }
     }
+
+    pub fn is_bool(&self) -> bool {
+        matches!(self, Value::Bool(_))
+    }
 }
 
 impl Display for Value {
diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs
index ed040cf91743..3e8509187b4d 100644
--- a/tvix/eval/src/vm.rs
+++ b/tvix/eval/src/vm.rs
@@ -187,6 +187,20 @@ impl VM {
                         self.ip += offset;
                     }
                 }
+
+                // These assertion operations error out if the stack
+                // top is not of the expected type. This is necessary
+                // to implement some specific behaviours of Nix
+                // exactly.
+                OpCode::OpAssertBool => {
+                    let val = self.peek(0);
+                    if !val.is_bool() {
+                        return Err(Error::TypeError {
+                            expected: "bool",
+                            actual: val.type_of(),
+                        });
+                    }
+                }
             }
 
             if self.ip == self.chunk.code.len() {