From 66b804b6ab638261e85d7c59da44c5976a924e50 Mon Sep 17 00:00:00 2001 From: Igor Malovitsa Date: Thu, 17 Sep 2026 03:35:25 +0000 Subject: [PATCH 1/6] Fix ProductZipper is_shared at a factor root A secondary factor's root isn't a child of the node above it, so the core zipper's parent lookup unwrapped None. Ask the factor's TrieRef instead. --- src/product_zipper.rs | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/product_zipper.rs b/src/product_zipper.rs index 9f2be3e9..0dce0faf 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -148,6 +148,14 @@ impl<'factor_z, 'trie, V: Clone + Send + Sync + Unpin, A: Allocator> ProductZipp self.enroll_next_factor(); } } + /// The secondary factor whose root node is the focus, if any. Its node is not a child of the + /// node above it, so the core zipper can't look it up. + fn factor_root(&self) -> Option<&TrieRef<'trie, V, A>> { + match self.factor_paths.last() { + Some(&start) if start == self.depth() => self.secondaries.get(self.factor_paths.len() - 1), + _ => None + } + } /// Internal method to make sure `self.factor_paths` is correct after an ascend method #[inline] fn fix_after_ascend(&mut self) { @@ -362,8 +370,19 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper } impl ZipperConcrete for ProductZipper<'_, '_, V, A> { - fn shared_node_id(&self) -> Option { self.z.shared_node_id() } - fn is_shared(&self) -> bool { self.z.is_shared() } + fn shared_node_id(&self) -> Option { + match self.factor_root() { + Some(_) if self.z.is_val() => None, + Some(factor) => factor.shared_node_id(), + None => self.z.shared_node_id(), + } + } + fn is_shared(&self) -> bool { + match self.factor_root() { + Some(factor) => factor.is_shared(), + None => self.z.is_shared(), + } + } } impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperPathBuffer for ProductZipper<'_, 'trie, V, A> { @@ -1975,6 +1994,24 @@ mod tests { |btm: &mut PathMap<()>, path: &[u8]| -> _ { ProductZipperG::new::<[ReadZipperUntracked<()>; 0]>(btm.read_zipper_at_path(path), []) }); + + /// `is_shared` and `shared_node_id` across factor boundaries + #[test] + fn product_zipper_is_shared_across_factors() { + let mut a = PathMap::::new(); + for p in [&[1u8, 2, 1][..], &[1, 2, 1, 0], &[1, 2, 1, 3, 3], &[0], &[2, 2]] { a.set_val_at(p, 7); } + let b = a.clone(); + let mut z = ProductZipper::new(a.read_zipper_at_path(&[1u8, 2, 1]), [b.read_zipper()]); + let mut factor_roots = 0; + while z.to_next_step() { + let _ = (z.is_shared(), z.shared_node_id()); + if z.factor_root().is_some() { + factor_roots += 1; + assert!(z.is_shared(), "{:?}", z.path()); + } + } + assert!(factor_roots > 0); + } } //POSSIBLE FUTURE DIRECTION: From 7905443725a1e279f6e70b2089ee81f0f20991c1 Mon Sep 17 00:00:00 2001 From: Igor Malovitsa Date: Thu, 17 Sep 2026 04:20:33 +0000 Subject: [PATCH 2/6] Fix the default k-path walk looping forever at a leaf With nothing below the base and no sibling, k_path_default_internal never reached its exit and spun. It also let k = 0 step sideways from the base. Stop when back at the base. --- src/zipper.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/zipper.rs b/src/zipper.rs index fcede695..f9986d94 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -1119,6 +1119,8 @@ fn k_path_default_internal(z: &mut if z.depth() == base_idx + k { return true } } } + //Back at the base: nothing (more) below it, and its own siblings are out of bounds + if z.depth() == base_idx { return false } //A sibling step replaces the last byte rather than adding one, so the observer sees the old //byte retracted before the new one arrives if let Some(byte) = z.to_next_sibling_byte() { @@ -3396,6 +3398,26 @@ pub(crate) mod read_zipper_core { } } + /// The default k-path walk ends when there is nothing below its base, and `k = 0` returns `false` + #[test] + fn default_k_path_walk_at_a_leaf() { + use crate::zipper::ProductZipperG; + let mut leaf = PathMap::::new(); + leaf.set_val_at(&[1u8], 1); + let empty = PathMap::::new(); + for (map, path) in [(&empty, &[][..]), (&leaf, &[1u8][..]), (&leaf, &[][..])] { + for k in 0..3 { + let mut z = ProductZipperG::new(map.read_zipper(), [empty.read_zipper()]); + z.descend_to(path); + let found = z.descend_first_k_path(k); + assert_eq!(found, map.val_count() > 0 && path.is_empty() && k == 1, "{path:?} k={k}"); + if !found { + assert_eq!(z.path(), path, "{path:?} k={k}"); + } + } + } + } + /// Validate we don't accidentially reallocate the path buffer when we don't need to #[test] fn read_zipper_reserve_buffer_test() { From 2c47b5b7c23808b5cac1cdfb6baf9e2929aa8c2c Mon Sep 17 00:00:00 2001 From: Igor Malovitsa Date: Thu, 17 Sep 2026 04:21:17 +0000 Subject: [PATCH 3/6] Fix ProductZipper factor bookkeeping at the root A factor was entered below a primary path that doesn't exist, and a sibling step at depth 0 dropped the factor record although the core zipper can't leave the factor there. Later moves then saw factor roots the zipper didn't know about, and is_shared unwrapped None. --- src/product_zipper.rs | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/product_zipper.rs b/src/product_zipper.rs index 0dce0faf..d4d3a8b9 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -136,7 +136,7 @@ impl<'factor_z, 'trie, V: Clone + Send + Sync + Unpin, A: Allocator> ProductZipp /// `product_zipper_test4` for more discussion. #[inline] fn ensure_descend_next_factor(&mut self) { - if self.factor_paths.len() < self.secondaries.len() && self.z.child_count() == 0 { + if self.factor_paths.len() < self.secondaries.len() && self.z.child_count() == 0 && self.z.path_exists() { //We don't want to push the same factor on the stack twice if let Some(factor_path_len) = self.factor_paths.last() { @@ -274,7 +274,8 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper moved } fn to_next_sibling_byte(&mut self) -> Option { - if self.factor_paths.last().cloned().unwrap_or(0) == self.depth() { + //Stepping sideways leaves a factor entered at this depth, but at the root there's no sideways + if self.depth() > 0 && self.factor_paths.last().cloned() == Some(self.depth()) { self.factor_paths.pop(); } let moved = self.z.to_next_sibling_byte(); @@ -282,7 +283,8 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper moved } fn to_prev_sibling_byte(&mut self) -> Option { - if self.factor_paths.last().cloned().unwrap_or(0) == self.depth() { + //Stepping sideways leaves a factor entered at this depth, but at the root there's no sideways + if self.depth() > 0 && self.factor_paths.last().cloned() == Some(self.depth()) { self.factor_paths.pop(); } let moved = self.z.to_prev_sibling_byte(); @@ -1995,6 +1997,34 @@ mod tests { ProductZipperG::new::<[ReadZipperUntracked<()>; 0]>(btm.read_zipper_at_path(path), []) }); + /// k-path walks and sibling steps keep factor bookkeeping in step with the core zipper, including + /// with a primary rooted at a missing path or at a leaf + #[test] + fn product_zipper_k_path_walk_from_the_root() { + let mut a = PathMap::<()>::new(); + for p in [&[2u8, 2, 0, 0][..], &[0xe7]] { a.set_val_at(p, ()); } + let b = a.clone(); + for root in [&[0xaau8, 0x77][..], &[0xe7u8][..], &[][..]] { + for k in 1..4 { + let mut z = ProductZipper::new(a.read_zipper_at_path(root), [b.read_zipper()]); + let mut paths = vec![]; + if z.descend_first_k_path(k) { + paths.push(z.path().to_vec()); + while paths.len() < 64 && z.to_next_k_path(k) { paths.push(z.path().to_vec()); } + } + assert_eq!(z.path(), &[] as &[u8], "{root:?} k={k}"); + let _ = (z.is_shared(), z.shared_node_id(), z.child_mask()); + assert!(paths.iter().all(|p| p.len() == k), "{root:?} k={k}: {paths:?}"); + if root == &[0xaau8, 0x77][..] { + assert!(paths.is_empty(), "a missing primary has no paths: {paths:?}"); + } + let mut z = ProductZipper::new(a.read_zipper_at_path(root), [b.read_zipper()]); + let _ = (z.to_next_sibling_byte(), z.to_prev_sibling_byte()); + let _ = (z.is_shared(), z.child_mask(), z.descend_first_byte(), z.is_shared()); + } + } + } + /// `is_shared` and `shared_node_id` across factor boundaries #[test] fn product_zipper_is_shared_across_factors() { From 56edab83364a203265fb97357b7b21cdabb7558d Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Fri, 25 Sep 2026 04:39:58 -0600 Subject: [PATCH 4/6] Reverting changes in zipper.rs, because they are in this PR via another PR that was already merged --- src/zipper.rs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/zipper.rs b/src/zipper.rs index 9eb77176..84f4fd6c 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -1119,7 +1119,6 @@ fn k_path_default_internal(z: &mut if z.depth() == base_idx + k { return true } } } - //Back at the base: nothing (more) below it, and its own siblings are out of bounds if z.depth() == base_idx { return false } if let Some(byte) = z.to_next_sibling_byte() { //A sibling step replaces the last byte rather than adding one, so the observer sees the old @@ -3399,26 +3398,6 @@ pub(crate) mod read_zipper_core { } } - /// The default k-path walk ends when there is nothing below its base, and `k = 0` returns `false` - #[test] - fn default_k_path_walk_at_a_leaf() { - use crate::zipper::ProductZipperG; - let mut leaf = PathMap::::new(); - leaf.set_val_at(&[1u8], 1); - let empty = PathMap::::new(); - for (map, path) in [(&empty, &[][..]), (&leaf, &[1u8][..]), (&leaf, &[][..])] { - for k in 0..3 { - let mut z = ProductZipperG::new(map.read_zipper(), [empty.read_zipper()]); - z.descend_to(path); - let found = z.descend_first_k_path(k); - assert_eq!(found, map.val_count() > 0 && path.is_empty() && k == 1, "{path:?} k={k}"); - if !found { - assert_eq!(z.path(), path, "{path:?} k={k}"); - } - } - } - } - /// `get_val_with_witness` agrees with `val` on owned read zippers, including at a root without a value #[test] fn read_zipper_owned_get_val_with_witness() { From dbfb1f69ec5982f080f475f2b902ed5bb1ccf21f Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Fri, 25 Sep 2026 05:04:56 -0600 Subject: [PATCH 5/6] Lifting up tests in this PR to a macro so they hit all PZ types Fixing to_next_sibling_byte and to_prev_sibling_byte on Generic and Dependent PZs when the zipper is at the root Adding test to demonstrate the need for an exists check in the Concrete PZ when descending across factors --- src/dependent_zipper.rs | 1 + src/product_zipper.rs | 121 +++++++++++++++++++++++++--------------- src/zipper.rs | 1 + 3 files changed, 77 insertions(+), 46 deletions(-) diff --git a/src/dependent_zipper.rs b/src/dependent_zipper.rs index 5620e2a4..1e5cb6c6 100644 --- a/src/dependent_zipper.rs +++ b/src/dependent_zipper.rs @@ -161,6 +161,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], /// a combination between `to_next_sibling` and `to_prev_sibling` fn to_sibling_byte(&mut self, next: bool) -> Option { + if self.depth() == 0 { return None } let byte = self.focus_byte()?; let ascended = self.ascend(1); debug_assert_eq!(ascended, 1, "must ascend"); diff --git a/src/product_zipper.rs b/src/product_zipper.rs index d4d3a8b9..8682b9d6 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -518,6 +518,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, /// a combination between `to_next_sibling` and `to_prev_sibling` fn to_sibling_byte(&mut self, next: bool) -> Option { + if self.depth() == 0 { return None } let byte = self.focus_byte()?; let ascended = self.ascend(1); debug_assert_eq!(ascended, 1, "must ascend"); @@ -966,9 +967,80 @@ mod tests { impl_product_zipper_tests!($mod, $ProductZipper, $convert, read_zipper); }; ($mod:ident, $ProductZipper:ident, $convert:ident, $read_zipper_u64:ident) => { + impl_product_zipper_tests!($mod, $ProductZipper, $convert, $read_zipper_u64, ignore_shared_root); + }; + ($mod:ident, $ProductZipper:ident, $convert:ident, $read_zipper_u64:ident, $check_shared_root:ident) => { // --- START OF MACRO GENERATED MOD --- pub mod $mod { use super::*; + #[test] + fn does_not_enter_factor_at_nonexistent_path() { + let primary = PathMap::from_iter([(b"a".as_slice(), ())]); + let secondary = PathMap::from_iter([(b"b".as_slice(), ())]); + $convert!(primary); + $convert!(secondary); + let mut pz = $ProductZipper::new(primary.read_zipper(), [secondary.read_zipper()]); + + pz.descend_to(b"x"); + assert!(!pz.path_exists()); + assert_eq!(pz.child_count(), 0); + assert_eq!(pz.descend_first_byte(), None); + assert_eq!(pz.path(), b"x"); + assert!(!pz.path_exists()); + assert_eq!(pz.focus_factor(), 0); + assert!(pz.path_indices().is_empty()); + } + + /// k-path walks and sibling steps keep factor bookkeeping in step with the core zipper, including + /// with a primary rooted at a missing path or at a leaf + #[test] + fn k_path_walk_from_the_root() { + let mut a = PathMap::<()>::new(); + for p in [&[2u8, 2, 0, 0][..], &[0xe7]] { a.set_val_at(p, ()); } + let b = a.clone(); + $convert!(a); + $convert!(b); + for root in [&[0xaau8, 0x77][..], &[0xe7u8][..], &[][..]] { + for k in 1..4 { + let mut z = $ProductZipper::new(a.read_zipper_at_path(root), [b.read_zipper()]); + let mut paths = vec![]; + if z.descend_first_k_path(k) { + paths.push(z.path().to_vec()); + while paths.len() < 64 && z.to_next_k_path(k) { paths.push(z.path().to_vec()); } + } + assert_eq!(z.path(), &[] as &[u8], "{root:?} k={k}"); + let _ = (z.is_shared(), z.shared_node_id(), z.child_mask()); + assert!(paths.iter().all(|p| p.len() == k), "{root:?} k={k}: {paths:?}"); + if root == &[0xaau8, 0x77][..] { + assert!(paths.is_empty(), "a missing primary has no paths: {paths:?}"); + } + let mut z = $ProductZipper::new(a.read_zipper_at_path(root), [b.read_zipper()]); + let _ = (z.to_next_sibling_byte(), z.to_prev_sibling_byte()); + let _ = (z.is_shared(), z.child_mask(), z.descend_first_byte(), z.is_shared()); + } + } + } + + /// `is_shared` and `shared_node_id` across factor boundaries + #[test] + fn is_shared_across_factors() { + let mut a = PathMap::::new(); + for p in [&[1u8, 2, 1][..], &[1, 2, 1, 0], &[1, 2, 1, 3, 3], &[0], &[2, 2]] { a.set_val_at(p, 7); } + let b = a.clone(); + $convert!(a); + $convert!(b); + let mut z = $ProductZipper::new(a.read_zipper_at_path(&[1u8, 2, 1]), [b.read_zipper()]); + let mut factor_roots = 0; + while z.to_next_step() { + let _ = (z.is_shared(), z.shared_node_id()); + if z.path_indices().last() == Some(&z.depth()) { + factor_roots += 1; + $check_shared_root!(z); + } + } + assert!(factor_roots > 0); + } + /// Builds a path long enough to span several trie nodes, so a `descend_until` over it is /// reported to a [PathObserver] as several separate segments fn long_path(len: usize) -> Vec { @@ -1893,7 +1965,9 @@ mod tests { } macro_rules! noop { ($x:ident) => {}; (*$x:ident) => {}; } - impl_product_zipper_tests!(pz_concrete, ProductZipper, noop); + macro_rules! ignore_shared_root { ($z:ident) => {}; } + macro_rules! assert_shared_root { ($z:ident) => { assert!($z.is_shared(), "{:?}", $z.path()); }; } + impl_product_zipper_tests!(pz_concrete, ProductZipper, noop, read_zipper, assert_shared_root); impl_product_zipper_tests!(pz_generic, ProductZipperG, noop); /// Adapts [DependentProductZipperG] to the `new(primary, [secondaries])` shape the shared @@ -1997,51 +2071,6 @@ mod tests { ProductZipperG::new::<[ReadZipperUntracked<()>; 0]>(btm.read_zipper_at_path(path), []) }); - /// k-path walks and sibling steps keep factor bookkeeping in step with the core zipper, including - /// with a primary rooted at a missing path or at a leaf - #[test] - fn product_zipper_k_path_walk_from_the_root() { - let mut a = PathMap::<()>::new(); - for p in [&[2u8, 2, 0, 0][..], &[0xe7]] { a.set_val_at(p, ()); } - let b = a.clone(); - for root in [&[0xaau8, 0x77][..], &[0xe7u8][..], &[][..]] { - for k in 1..4 { - let mut z = ProductZipper::new(a.read_zipper_at_path(root), [b.read_zipper()]); - let mut paths = vec![]; - if z.descend_first_k_path(k) { - paths.push(z.path().to_vec()); - while paths.len() < 64 && z.to_next_k_path(k) { paths.push(z.path().to_vec()); } - } - assert_eq!(z.path(), &[] as &[u8], "{root:?} k={k}"); - let _ = (z.is_shared(), z.shared_node_id(), z.child_mask()); - assert!(paths.iter().all(|p| p.len() == k), "{root:?} k={k}: {paths:?}"); - if root == &[0xaau8, 0x77][..] { - assert!(paths.is_empty(), "a missing primary has no paths: {paths:?}"); - } - let mut z = ProductZipper::new(a.read_zipper_at_path(root), [b.read_zipper()]); - let _ = (z.to_next_sibling_byte(), z.to_prev_sibling_byte()); - let _ = (z.is_shared(), z.child_mask(), z.descend_first_byte(), z.is_shared()); - } - } - } - - /// `is_shared` and `shared_node_id` across factor boundaries - #[test] - fn product_zipper_is_shared_across_factors() { - let mut a = PathMap::::new(); - for p in [&[1u8, 2, 1][..], &[1, 2, 1, 0], &[1, 2, 1, 3, 3], &[0], &[2, 2]] { a.set_val_at(p, 7); } - let b = a.clone(); - let mut z = ProductZipper::new(a.read_zipper_at_path(&[1u8, 2, 1]), [b.read_zipper()]); - let mut factor_roots = 0; - while z.to_next_step() { - let _ = (z.is_shared(), z.shared_node_id()); - if z.factor_root().is_some() { - factor_roots += 1; - assert!(z.is_shared(), "{:?}", z.path()); - } - } - assert!(factor_roots > 0); - } } //POSSIBLE FUTURE DIRECTION: diff --git a/src/zipper.rs b/src/zipper.rs index 84f4fd6c..15fd560d 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -1949,6 +1949,7 @@ pub(crate) mod read_zipper_core { fn is_val(&self) -> bool { self.is_val_internal() } + #[inline] fn child_count(&self) -> usize { debug_assert!(self.is_regularized()); self.focus_node.count_branches(self.node_key()) From 3fbcf531ea0390dec9cf996a496f2391f92abde6 Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Fri, 25 Sep 2026 05:59:21 -0600 Subject: [PATCH 6/6] The "fix" in the PR to avoid the panic was wrong. Implemented a conservative fix, but it leaves a lot of sharing on the table. This will be revisited in https://github.com/Adam-Vandervorst/PathMap/pull/136 --- src/product_zipper.rs | 72 +++++++++++++------------------------------ 1 file changed, 21 insertions(+), 51 deletions(-) diff --git a/src/product_zipper.rs b/src/product_zipper.rs index 8682b9d6..d437163e 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -148,14 +148,6 @@ impl<'factor_z, 'trie, V: Clone + Send + Sync + Unpin, A: Allocator> ProductZipp self.enroll_next_factor(); } } - /// The secondary factor whose root node is the focus, if any. Its node is not a child of the - /// node above it, so the core zipper can't look it up. - fn factor_root(&self) -> Option<&TrieRef<'trie, V, A>> { - match self.factor_paths.last() { - Some(&start) if start == self.depth() => self.secondaries.get(self.factor_paths.len() - 1), - _ => None - } - } /// Internal method to make sure `self.factor_paths` is correct after an ascend method #[inline] fn fix_after_ascend(&mut self) { @@ -274,8 +266,9 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper moved } fn to_next_sibling_byte(&mut self) -> Option { - //Stepping sideways leaves a factor entered at this depth, but at the root there's no sideways - if self.depth() > 0 && self.factor_paths.last().cloned() == Some(self.depth()) { + if self.depth() == 0 { return None } + //Stepping sideways leaves a factor entered at this depth. + if self.factor_paths.last().cloned() == Some(self.depth()) { self.factor_paths.pop(); } let moved = self.z.to_next_sibling_byte(); @@ -283,8 +276,9 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper moved } fn to_prev_sibling_byte(&mut self) -> Option { - //Stepping sideways leaves a factor entered at this depth, but at the root there's no sideways - if self.depth() > 0 && self.factor_paths.last().cloned() == Some(self.depth()) { + if self.depth() == 0 { return None } + //Stepping sideways leaves a factor entered at this depth. + if self.factor_paths.last().cloned() == Some(self.depth()) { self.factor_paths.pop(); } let moved = self.z.to_prev_sibling_byte(); @@ -372,17 +366,22 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper } impl ZipperConcrete for ProductZipper<'_, '_, V, A> { + //GOAT, this is a temporary fix to provide correctness at the expense of reporting sharing until + // https://github.com/Adam-Vandervorst/PathMap/pull/136 gets sorted. fn shared_node_id(&self) -> Option { - match self.factor_root() { - Some(_) if self.z.is_val() => None, - Some(factor) => factor.shared_node_id(), - None => self.z.shared_node_id(), + if self.factor_paths.len() == 0 { + self.z.shared_node_id() + } else { + None } } + //GOAT, this is a temporary fix to provide correctness at the expense of reporting sharing until + // https://github.com/Adam-Vandervorst/PathMap/pull/136 gets sorted. fn is_shared(&self) -> bool { - match self.factor_root() { - Some(factor) => factor.is_shared(), - None => self.z.is_shared(), + if self.factor_paths.len() == 0 { + self.z.is_shared() + } else { + false } } } @@ -967,9 +966,6 @@ mod tests { impl_product_zipper_tests!($mod, $ProductZipper, $convert, read_zipper); }; ($mod:ident, $ProductZipper:ident, $convert:ident, $read_zipper_u64:ident) => { - impl_product_zipper_tests!($mod, $ProductZipper, $convert, $read_zipper_u64, ignore_shared_root); - }; - ($mod:ident, $ProductZipper:ident, $convert:ident, $read_zipper_u64:ident, $check_shared_root:ident) => { // --- START OF MACRO GENERATED MOD --- pub mod $mod { use super::*; @@ -991,8 +987,8 @@ mod tests { assert!(pz.path_indices().is_empty()); } - /// k-path walks and sibling steps keep factor bookkeeping in step with the core zipper, including - /// with a primary rooted at a missing path or at a leaf + /// k-path walks stay within the requested depth, including with a primary rooted at a + /// missing path or at a leaf. #[test] fn k_path_walk_from_the_root() { let mut a = PathMap::<()>::new(); @@ -1009,36 +1005,12 @@ mod tests { while paths.len() < 64 && z.to_next_k_path(k) { paths.push(z.path().to_vec()); } } assert_eq!(z.path(), &[] as &[u8], "{root:?} k={k}"); - let _ = (z.is_shared(), z.shared_node_id(), z.child_mask()); assert!(paths.iter().all(|p| p.len() == k), "{root:?} k={k}: {paths:?}"); if root == &[0xaau8, 0x77][..] { assert!(paths.is_empty(), "a missing primary has no paths: {paths:?}"); } - let mut z = $ProductZipper::new(a.read_zipper_at_path(root), [b.read_zipper()]); - let _ = (z.to_next_sibling_byte(), z.to_prev_sibling_byte()); - let _ = (z.is_shared(), z.child_mask(), z.descend_first_byte(), z.is_shared()); - } - } - } - - /// `is_shared` and `shared_node_id` across factor boundaries - #[test] - fn is_shared_across_factors() { - let mut a = PathMap::::new(); - for p in [&[1u8, 2, 1][..], &[1, 2, 1, 0], &[1, 2, 1, 3, 3], &[0], &[2, 2]] { a.set_val_at(p, 7); } - let b = a.clone(); - $convert!(a); - $convert!(b); - let mut z = $ProductZipper::new(a.read_zipper_at_path(&[1u8, 2, 1]), [b.read_zipper()]); - let mut factor_roots = 0; - while z.to_next_step() { - let _ = (z.is_shared(), z.shared_node_id()); - if z.path_indices().last() == Some(&z.depth()) { - factor_roots += 1; - $check_shared_root!(z); } } - assert!(factor_roots > 0); } /// Builds a path long enough to span several trie nodes, so a `descend_until` over it is @@ -1965,9 +1937,7 @@ mod tests { } macro_rules! noop { ($x:ident) => {}; (*$x:ident) => {}; } - macro_rules! ignore_shared_root { ($z:ident) => {}; } - macro_rules! assert_shared_root { ($z:ident) => { assert!($z.is_shared(), "{:?}", $z.path()); }; } - impl_product_zipper_tests!(pz_concrete, ProductZipper, noop, read_zipper, assert_shared_root); + impl_product_zipper_tests!(pz_concrete, ProductZipper, noop); impl_product_zipper_tests!(pz_generic, ProductZipperG, noop); /// Adapts [DependentProductZipperG] to the `new(primary, [secondaries])` shape the shared