From 6716354c6d2487ddd21bf655d604c71672b6c0ee Mon Sep 17 00:00:00 2001 From: Igor Malovitsa Date: Wed, 16 Sep 2026 22:11:19 +0000 Subject: [PATCH] Fix write zipper node stack after prune_path prune_path walked the node stack up to find where pruning stops but left it there when the zipper did not move, so the next write through the focus went to the wrong node (get_val_or_set_mut panicked). Walk the stack back down. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_019R2H8fnco29asY2v3TPbtF --- src/write_zipper.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/write_zipper.rs b/src/write_zipper.rs index 0892e475..86716f7c 100644 --- a/src/write_zipper.rs +++ b/src/write_zipper.rs @@ -2572,6 +2572,9 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC if should_ascend { self.key.prefix_buf.truncate(temp_path.len()); + } else if ascended { + //The zipper didn't move, so restore the node stack to the focus + self.descend_to_internal(); } pruned_bytes @@ -3710,6 +3713,50 @@ mod tests { assert_eq!(btm2.path_exists_at(&[0, 255, 1]), false); } + /// A write after `prune_path` (or `meet_into(.., true)`) must reach the focus node + #[test] + fn write_zipper_write_after_prune_path_below_a_graft() { + let build = || { + let mut m0 = PathMap::::new(); + let mut m1 = PathMap::::new(); + m1.set_val_at(&[1u8, 0, 0, 0, 0], 7); + m0.create_path(&[0u8, 0]); + m1.create_path(&[1u8]); + (m0, m1) + }; + + // prune_path directly + let (mut m0, m1) = build(); + { + let mut wz = m0.write_zipper_at_path(&[0u8, 0]); + let rz = m1.read_zipper_at_path(&[1u8]); + wz.graft(&rz); + wz.descend_last_byte(); + wz.remove_branches(false); + wz.prune_path(); + assert_eq!(wz.path(), &[0u8]); + assert_eq!(*wz.get_val_or_set_mut_with(|| 3), 3); + assert_eq!(wz.val(), Some(&3)); + } + assert_eq!(m0.get_val_at(&[0u8, 0, 0]), Some(&3)); + assert_eq!(m0.val_count(), 1); + + // through meet_into with prune + let (mut m0, m1) = build(); + { + let mut wz = m0.write_zipper_at_path(&[0u8, 0]); + let rz = m1.read_zipper_at_path(&[1u8]); + wz.graft(&rz); + wz.descend_last_byte(); + wz.meet_into(&rz, true); + assert_eq!(wz.path(), &[0u8]); + assert_eq!(*wz.get_val_or_set_mut_with(|| 3), 3); + assert_eq!(wz.val(), Some(&3)); + } + assert_eq!(m0.get_val_at(&[0u8, 0, 0]), Some(&3)); + } + + /// Tests whether the [WriteZipper::subtract_into] operation will do the right thing with the root value #[test] fn write_zipper_subtract_into_test1() {