about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/eval/src/value/attrs.rs47
-rw-r--r--tvix/eval/src/vm.rs12
2 files changed, 36 insertions, 23 deletions
diff --git a/tvix/eval/src/value/attrs.rs b/tvix/eval/src/value/attrs.rs
index 922aa0c0d3..62fb9b710c 100644
--- a/tvix/eval/src/value/attrs.rs
+++ b/tvix/eval/src/value/attrs.rs
@@ -151,28 +151,34 @@ impl PartialEq for NixAttrs {
 
 impl NixAttrs {
     // Update one attribute set with the values of the other.
-    pub fn update(&self, other: &Self) -> Self {
+    pub fn update(self, other: Self) -> Self {
+        // Short-circuit on some optimal cases:
         match (&self.0, &other.0) {
-            // Short-circuit on some optimal cases:
-            (AttrsRep::Empty, AttrsRep::Empty) => NixAttrs(AttrsRep::Empty),
-            (AttrsRep::Empty, _) => other.clone(),
-            (_, AttrsRep::Empty) => self.clone(),
-            (AttrsRep::KV { .. }, AttrsRep::KV { .. }) => other.clone(),
-
-            // Slightly more advanced, but still optimised updates
-            (AttrsRep::Map(m), AttrsRep::KV { name, value }) => {
-                let mut m = m.clone();
-                m.insert(NixString::NAME, name.clone());
-                m.insert(NixString::VALUE, value.clone());
+            (AttrsRep::Empty, AttrsRep::Empty) => return self,
+            (AttrsRep::Empty, _) => return other,
+            (_, AttrsRep::Empty) => return self,
+            (AttrsRep::KV { .. }, AttrsRep::KV { .. }) => return other,
+
+            // Explicitly handle all branches instead of falling
+            // through, to ensure that we get at least some compiler
+            // errors if variants are modified.
+            (AttrsRep::Map(_), AttrsRep::Map(_))
+            | (AttrsRep::Map(_), AttrsRep::KV { .. })
+            | (AttrsRep::KV { .. }, AttrsRep::Map(_)) => {}
+        };
+
+        // Slightly more advanced, but still optimised updates
+        match (self.0, other.0) {
+            (AttrsRep::Map(mut m), AttrsRep::KV { name, value }) => {
+                m.insert(NixString::NAME, name);
+                m.insert(NixString::VALUE, value);
                 NixAttrs(AttrsRep::Map(m))
             }
 
-            (AttrsRep::KV { name, value }, AttrsRep::Map(m)) => {
-                let mut m = m.clone();
-
+            (AttrsRep::KV { name, value }, AttrsRep::Map(mut m)) => {
                 match m.entry(NixString::NAME) {
                     btree_map::Entry::Vacant(e) => {
-                        e.insert(name.clone());
+                        e.insert(name);
                     }
 
                     btree_map::Entry::Occupied(_) => { /* name from `m` has precedence */ }
@@ -180,7 +186,7 @@ impl NixAttrs {
 
                 match m.entry(NixString::VALUE) {
                     btree_map::Entry::Vacant(e) => {
-                        e.insert(value.clone());
+                        e.insert(value);
                     }
 
                     btree_map::Entry::Occupied(_) => { /* value from `m` has precedence */ }
@@ -190,12 +196,13 @@ impl NixAttrs {
             }
 
             // Plain merge of maps.
-            (AttrsRep::Map(m1), AttrsRep::Map(m2)) => {
-                let mut m1 = m1.clone();
-                let mut m2 = m2.clone();
+            (AttrsRep::Map(mut m1), AttrsRep::Map(mut m2)) => {
                 m1.append(&mut m2);
                 NixAttrs(AttrsRep::Map(m1))
             }
+
+            // Cases handled above by the borrowing match:
+            _ => unreachable!(),
         }
     }
 
diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs
index 65534006d0..6de9cd03c4 100644
--- a/tvix/eval/src/vm.rs
+++ b/tvix/eval/src/vm.rs
@@ -203,10 +203,10 @@ impl VM {
                 OpCode::OpAttrPath(count) => self.run_attr_path(count)?,
 
                 OpCode::OpAttrsUpdate => {
-                    let rhs = self.pop().to_attrs()?;
-                    let lhs = self.pop().to_attrs()?;
+                    let rhs = unwrap_or_clone_rc(self.pop().to_attrs()?);
+                    let lhs = unwrap_or_clone_rc(self.pop().to_attrs()?);
 
-                    self.push(Value::Attrs(Rc::new(lhs.update(&rhs))))
+                    self.push(Value::Attrs(Rc::new(lhs.update(rhs))))
                 }
 
                 OpCode::OpAttrsSelect => {
@@ -414,6 +414,12 @@ impl VM {
     }
 }
 
+// TODO: use Rc::unwrap_or_clone once it is stabilised.
+// https://doc.rust-lang.org/std/rc/struct.Rc.html#method.unwrap_or_clone
+fn unwrap_or_clone_rc<T: Clone>(rc: Rc<T>) -> T {
+    Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone())
+}
+
 pub fn run_lambda(lambda: Lambda) -> EvalResult<Value> {
     let mut vm = VM {
         frames: vec![],