1use indexmap::IndexSet;
2use serde_json::Value;
3use std::collections::HashMap;
4
5#[derive(Default, Clone)]
7pub struct VersionTracker {
8 versions: HashMap<String, u64>,
9}
10
11impl VersionTracker {
12 pub fn new() -> Self {
13 Self {
14 versions: HashMap::new(),
15 }
16 }
17
18 #[inline]
19 pub fn get(&self, path: &str) -> u64 {
20 self.versions.get(path).copied().unwrap_or(0)
21 }
22
23 #[inline]
24 pub fn bump(&mut self, path: &str, source: &str) {
25 let current = self.get(path);
26 if crate::utils::is_debug_cache_enabled() {
27 println!(
28 "[store_cache] BUMPING for {} -> {} ({})",
29 path,
30 current + 1,
31 source
32 );
33 }
34 self.versions.insert(path.to_string(), current + 1);
36 }
37
38 pub fn merge_from(&mut self, other: &VersionTracker) {
42 for (k, v) in &other.versions {
43 let current = self.versions.get(k).copied().unwrap_or(0);
44 self.versions.insert(k.clone(), current.max(*v));
45 }
46 }
47
48 pub fn merge_from_params(&mut self, other: &VersionTracker) {
52 for (k, v) in &other.versions {
53 if k.starts_with("/$params") {
54 let current = self.versions.get(k).copied().unwrap_or(0);
55 self.versions.insert(k.clone(), current.max(*v));
56 }
57 }
58 }
59
60 pub(crate) fn merge_excluding_prefix(&mut self, other: &VersionTracker, excluded_prefix: &str) {
62 for (k, v) in &other.versions {
63 if !k.starts_with(excluded_prefix) {
64 let current = self.versions.get(k).copied().unwrap_or(0);
65 self.versions.insert(k.clone(), current.max(*v));
66 }
67 }
68 }
69
70 pub fn any_bumped_with_prefix(&self, prefix: &str) -> bool {
73 self.versions
74 .iter()
75 .any(|(k, &v)| k.starts_with(prefix) && v > 0)
76 }
77
78 pub fn any_newly_bumped_with_prefix(&self, prefix: &str, baseline: &VersionTracker) -> bool {
82 self.versions
83 .iter()
84 .any(|(k, &v)| k.starts_with(prefix) && v > baseline.get(k))
85 }
86
87 pub fn versions(&self) -> impl Iterator<Item = (&str, &u64)> {
89 self.versions.iter().map(|(k, v)| (k.as_str(), v))
90 }
91}
92
93#[derive(Clone)]
95pub struct CacheEntry {
96 pub dep_versions: HashMap<String, u64>,
97 pub result: std::sync::Arc<Value>,
98 pub computed_for_item: Option<usize>,
103}
104
105#[derive(Default, Clone)]
107pub struct SubformItemCache {
108 pub data_versions: VersionTracker,
109 pub entries: HashMap<String, CacheEntry>,
110 pub item_snapshot: Value,
111 pub evaluated_schema: Option<Value>,
115}
116
117impl SubformItemCache {
118 pub fn new() -> Self {
119 Self {
120 data_versions: VersionTracker::new(),
121 entries: HashMap::new(),
122 item_snapshot: Value::Null,
123 evaluated_schema: None,
124 }
125 }
126}
127
128#[derive(Clone)]
130pub struct EvalCache {
131 pub data_versions: VersionTracker,
132 pub params_versions: VersionTracker,
133 pub entries: HashMap<String, CacheEntry>,
134
135 pub active_item_index: Option<usize>,
136 pub subform_caches: HashMap<usize, SubformItemCache>,
137
138 pub eval_generation: u64,
142 pub last_evaluated_generation: u64,
143
144 pub main_form_snapshot: Option<std::sync::Arc<Value>>,
148}
149
150impl Default for EvalCache {
151 fn default() -> Self {
152 Self::new()
153 }
154}
155
156impl EvalCache {
157 pub fn new() -> Self {
158 Self {
159 data_versions: VersionTracker::new(),
160 params_versions: VersionTracker::new(),
161 entries: HashMap::new(),
162 active_item_index: None,
163 subform_caches: HashMap::new(),
164 eval_generation: 0,
165 last_evaluated_generation: u64::MAX, main_form_snapshot: None,
167 }
168 }
169
170 pub fn clear(&mut self) {
171 self.data_versions = VersionTracker::new();
172 self.params_versions = VersionTracker::new();
173 self.entries.clear();
174 self.active_item_index = None;
175 self.subform_caches.clear();
176 self.eval_generation = 0;
177 self.last_evaluated_generation = u64::MAX;
178 self.main_form_snapshot = None;
179 }
180
181 pub fn prune_subform_caches(&mut self, current_count: usize) {
185 self.subform_caches.retain(|&idx, _| idx < current_count);
186 }
187
188 pub fn invalidate_params_tables_for_item(&mut self, idx: usize, table_keys: &[String]) {
195 for key in table_keys {
197 let data_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(key);
198 self.params_versions
199 .bump(&data_path, "invalidate_params_tables_for_item");
200 self.eval_generation += 1;
201 }
202
203 if let Some(item_cache) = self.subform_caches.get_mut(&idx) {
205 for key in table_keys {
206 item_cache.entries.remove(key);
207 }
208 }
209 }
210
211 pub fn needs_full_evaluation(&self) -> bool {
213 self.eval_generation != self.last_evaluated_generation
214 }
215
216 pub fn mark_evaluated(&mut self) {
218 self.last_evaluated_generation = self.eval_generation;
219 }
220
221 pub(crate) fn ensure_active_item_cache(&mut self, idx: usize) {
222 self.subform_caches
223 .entry(idx)
224 .or_insert_with(SubformItemCache::new);
225 }
226
227 pub fn set_active_item(&mut self, idx: usize) {
228 self.active_item_index = Some(idx);
229 self.ensure_active_item_cache(idx);
230 }
231
232 pub fn clear_active_item(&mut self) {
233 self.active_item_index = None;
234 }
235
236 pub fn store_snapshot_and_diff_versions(&mut self, old: &Value, new: &Value) {
238 if let Some(idx) = self.active_item_index {
239 self.ensure_active_item_cache(idx);
240 let sub_cache = self.subform_caches.get_mut(&idx).unwrap();
241 diff_and_update_versions(
242 &mut sub_cache.data_versions,
243 "",
244 old,
245 new,
246 "subform store_snapshot_and_diff_versions",
247 );
248 sub_cache.item_snapshot = new.clone();
249 } else {
250 diff_and_update_versions(
251 &mut self.data_versions,
252 "",
253 old,
254 new,
255 "store_snapshot_and_diff_versions",
256 );
257 }
258 }
259
260 pub fn get_active_snapshot(&self) -> Value {
261 if let Some(idx) = self.active_item_index {
262 self.subform_caches
263 .get(&idx)
264 .map(|c| c.item_snapshot.clone())
265 .unwrap_or(Value::Null)
266 } else {
267 Value::Null
268 }
269 }
270
271 pub fn diff_active_item(
272 &mut self,
273 field_key: &str,
274 old_sub_data: &Value,
275 new_sub_data: &Value,
276 ) {
277 if let Some(idx) = self.active_item_index {
278 self.ensure_active_item_cache(idx);
279 let sub_cache = self.subform_caches.get_mut(&idx).unwrap();
280
281 let empty = Value::Null;
283 let old_item = old_sub_data.get(field_key).unwrap_or(&empty);
284 let new_item = new_sub_data.get(field_key).unwrap_or(&empty);
285
286 diff_and_update_versions(
287 &mut sub_cache.data_versions,
288 &format!("/{}", field_key),
289 old_item,
290 new_item,
291 format!("diff_active_item {}", field_key).as_str(),
292 );
293 sub_cache.item_snapshot = new_sub_data.clone();
294 }
295 }
296
297 pub fn bump_data_version(&mut self, data_path: &str) {
298 self.eval_generation += 1;
301 if let Some(idx) = self.active_item_index {
302 if let Some(cache) = self.subform_caches.get_mut(&idx) {
303 cache.data_versions.bump(data_path, "bump_data_version1");
304 }
305 } else {
306 self.data_versions.bump(data_path, "bump_data_version2");
307 }
308 }
309
310 pub fn bump_params_version(&mut self, data_path: &str) {
311 self.params_versions.bump(data_path, "bump_params_version");
312 self.eval_generation += 1;
313 }
314
315 pub fn check_cache(&self, eval_key: &str, deps: &IndexSet<String>) -> Option<Value> {
321 self.check_cache_arc(eval_key, deps)
322 .map(|arc| (*arc).clone())
323 }
324
325 pub fn check_cache_arc(
326 &self,
327 eval_key: &str,
328 deps: &IndexSet<String>,
329 ) -> Option<std::sync::Arc<Value>> {
330 if let Some(idx) = self.active_item_index {
331 if let Some(cache) = self.subform_caches.get(&idx) {
333 if let Some(hit) =
334 self.validate_entry(eval_key, deps, &cache.entries, &cache.data_versions)
335 {
336 if crate::utils::is_debug_cache_enabled() {
337 println!("Cache HIT [T1 idx={}] {}", idx, eval_key);
338 }
339 return Some(hit);
340 }
341 }
342
343 let item_data_versions = self
345 .subform_caches
346 .get(&idx)
347 .map(|c| &c.data_versions)
348 .unwrap_or(&self.data_versions);
349
350 if let Some(entry) = self.entries.get(eval_key) {
351 let index_safe = match entry.computed_for_item {
352 None => entry.dep_versions.keys().all(|p| p.starts_with("/$params")),
354 Some(stored_idx) if stored_idx == idx => true,
355 _ => entry.dep_versions.keys().all(|p| p.starts_with("/$params")),
356 };
357 if index_safe {
358 let result =
359 self.validate_entry(eval_key, deps, &self.entries, item_data_versions);
360 if result.is_some() {
361 if crate::utils::is_debug_cache_enabled() {
362 println!(
363 "Cache HIT [T2 idx={} for={:?}] {}",
364 idx, entry.computed_for_item, eval_key
365 );
366 }
367 }
368 return result;
369 }
370 }
371
372 None
373 } else {
374 self.validate_entry(eval_key, deps, &self.entries, &self.data_versions)
375 }
376 }
377
378 pub fn check_table_cache(
382 &self,
383 eval_key: &str,
384 deps: &IndexSet<String>,
385 ) -> Option<std::sync::Arc<Value>> {
386 if let Some(idx) = self.active_item_index {
387 if let Some(cache) = self.subform_caches.get(&idx) {
389 if let Some(hit) =
390 self.validate_entry(eval_key, deps, &cache.entries, &cache.data_versions)
391 {
392 if crate::utils::is_debug_cache_enabled() {
393 println!("Cache HIT [T1 table idx={}] {}", idx, eval_key);
394 }
395 return Some(hit);
396 }
397 }
398
399 let has_changed_item_dep = self.subform_caches.get(&idx).is_some_and(|cache| {
402 deps.iter().any(|dep| {
403 let p = crate::jsoneval::path_utils::schema_path_to_data_pointer(dep);
404 if p.starts_with("/$params") {
405 false
406 } else {
407 cache.data_versions.get(&p) > self.data_versions.get(&p)
408 }
409 })
410 });
411 if has_changed_item_dep {
412 return None;
413 }
414
415
416 let result = self.validate_entry(eval_key, deps, &self.entries, &self.data_versions);
417 if result.is_some() {
418 if crate::utils::is_debug_cache_enabled() {
419 println!("Cache HIT [T2 table idx={}] {}", idx, eval_key);
420 }
421 }
422 result
423 } else {
424 self.validate_entry(eval_key, deps, &self.entries, &self.data_versions)
425 }
426 }
427
428 fn validate_entry(
429 &self,
430 eval_key: &str,
431 deps: &IndexSet<String>,
432 entries: &HashMap<String, CacheEntry>,
433 data_versions: &VersionTracker,
434 ) -> Option<std::sync::Arc<Value>> {
435 let entry = entries.get(eval_key)?;
436 for dep in deps {
437 let data_dep_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(dep);
438
439 let current_ver = if data_dep_path.starts_with("/$params") {
440 self.params_versions.get(&data_dep_path)
441 } else if let Some(idx) = self.active_item_index {
442 self.subform_caches
443 .get(&idx)
444 .map(|c| c.data_versions.get(&data_dep_path))
445 .filter(|&v| v > 0)
446 .unwrap_or_else(|| data_versions.get(&data_dep_path))
447 } else {
448 data_versions.get(&data_dep_path)
449 };
450
451 if let Some(&cached_ver) = entry.dep_versions.get(data_dep_path.as_ref()) {
452 if current_ver != cached_ver {
453 if crate::utils::is_debug_cache_enabled() {
454 println!(
455 "Cache MISS {}: dep {} changed ({} -> {})",
456 eval_key, data_dep_path, cached_ver, current_ver
457 );
458 }
459 return None;
460 }
461 } else {
462 if crate::utils::is_debug_cache_enabled() {
463 println!(
464 "Cache MISS {}: dep {} missing from cache entry",
465 eval_key, data_dep_path
466 );
467 }
468 return None;
469 }
470 }
471 if crate::utils::is_debug_cache_enabled() {
472 println!("Cache HIT {}", eval_key);
473 }
474 Some(std::sync::Arc::clone(&entry.result))
475 }
476
477 pub fn store_cache(&mut self, eval_key: &str, deps: &IndexSet<String>, result: Value) {
481 self.store_cache_arc(eval_key, deps, std::sync::Arc::new(result));
482 }
483
484 pub fn store_cache_arc(
486 &mut self,
487 eval_key: &str,
488 deps: &IndexSet<String>,
489 result: std::sync::Arc<Value>,
490 ) {
491 let mut dep_versions = HashMap::with_capacity(deps.len());
493 {
494 let data_versions = if let Some(idx) = self.active_item_index {
495 self.ensure_active_item_cache(idx);
496 &self.subform_caches[&idx].data_versions
497 } else {
498 &self.data_versions
499 };
500
501 for dep in deps {
502 let data_dep_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(dep);
503 let ver = if data_dep_path.starts_with("/$params") {
504 self.params_versions.get(&data_dep_path)
505 } else {
506 data_versions.get(&data_dep_path)
507 };
508 dep_versions.insert(data_dep_path.into_owned(), ver);
509 }
510 }
511
512 let computed_for_item = self.active_item_index;
514
515 if eval_key.starts_with("#/$params") {
517 let existing_result: Option<&Value> = if let Some(idx) = self.active_item_index {
518 self.entries
520 .get(eval_key)
521 .map(|e| e.result.as_ref())
522 .or_else(|| {
523 self.subform_caches
524 .get(&idx)
525 .and_then(|c| c.entries.get(eval_key))
526 .map(|e| e.result.as_ref())
527 })
528 } else {
529 self.entries.get(eval_key).map(|e| e.result.as_ref())
530 };
531
532 let value_changed = existing_result.map_or(true, |r| r != result.as_ref());
533
534 if value_changed {
535 let data_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(eval_key);
536
537 let mut current_path = data_path.as_ref();
540 let mut slash_count = current_path.matches('/').count();
541
542 while slash_count >= 3 {
543 self.params_versions.bump(current_path, "store_cache");
544 if let Some(last_slash) = current_path.rfind('/') {
545 current_path = ¤t_path[..last_slash];
546 slash_count -= 1;
547 } else {
548 break;
549 }
550 }
551
552 self.eval_generation += 1;
553 }
554 }
555
556 let entry = CacheEntry {
557 dep_versions,
558 result: std::sync::Arc::clone(&result),
559 computed_for_item,
560 };
561
562 if let Some(idx) = self.active_item_index {
563 self.subform_caches
565 .get_mut(&idx)
566 .unwrap()
567 .entries
568 .insert(eval_key.to_string(), entry.clone());
569
570 if eval_key.starts_with("#/$params") {
572 let t2_dep_versions: HashMap<String, u64> = entry
573 .dep_versions
574 .iter()
575 .map(|(path, &item_ver)| {
576 let parent_ver = if path.starts_with("/$params") {
577 item_ver } else {
579 self.data_versions.get(path)
581 };
582 (path.clone(), parent_ver)
583 })
584 .collect();
585
586 let t2_entry = CacheEntry {
587 dep_versions: t2_dep_versions,
588 result: std::sync::Arc::clone(&entry.result),
589 computed_for_item,
590 };
591 self.entries.insert(eval_key.to_string(), t2_entry);
592 }
593 } else {
594 self.entries.insert(eval_key.to_string(), entry);
595 }
596 }
597}
598
599pub(crate) fn diff_and_update_versions(
601 tracker: &mut VersionTracker,
602 pointer: &str,
603 old: &Value,
604 new: &Value,
605 source: &str,
606) {
607 let mut pointer_buf = String::with_capacity(128);
608 pointer_buf.push_str(pointer);
609 diff_and_update_versions_internal(tracker, &mut pointer_buf, old, new, source);
610}
611
612#[cfg(test)]
613mod cache_tests {
614 use super::{CacheEntry, EvalCache};
615 use indexmap::IndexSet;
616 use serde_json::json;
617 use std::collections::HashMap;
618 use std::sync::Arc;
619
620 #[test]
621 fn unchanged_active_item_reuses_global_table_with_item_dependency() {
622 let mut cache = EvalCache::new();
623 cache.set_active_item(1);
624
625 let eval_key = "#/$params/references/RIDER_RATE";
626 let deps = IndexSet::from_iter(["#/riders/properties/benefit".to_string()]);
627 cache.entries.insert(
628 eval_key.to_string(),
629 CacheEntry {
630 dep_versions: HashMap::from([("/riders/benefit".to_string(), 0)]),
631 result: Arc::new(json!([{"rate": 97}])),
632 computed_for_item: None,
633 },
634 );
635
636 assert_eq!(
637 cache.check_table_cache(eval_key, &deps),
638 Some(Arc::new(json!([{"rate": 97}]))),
639 "a scoped alias may reuse the parent result for its unchanged canonical rider"
640 );
641 }
642
643 #[test]
644 fn changed_active_item_does_not_reuse_global_table_with_item_dependency() {
645 let mut cache = EvalCache::new();
646 cache.set_active_item(1);
647 cache
648 .subform_caches
649 .get_mut(&1)
650 .expect("active item cache must exist")
651 .data_versions
652 .bump("/riders/benefit", "test rider input change");
653
654 let eval_key = "#/$params/references/RIDER_RATE";
655 let deps = IndexSet::from_iter(["#/riders/properties/benefit".to_string()]);
656 cache.entries.insert(
657 eval_key.to_string(),
658 CacheEntry {
659 dep_versions: HashMap::from([("/riders/benefit".to_string(), 0)]),
660 result: Arc::new(json!([{"rate": 97}])),
661 computed_for_item: None,
662 },
663 );
664
665 assert!(
666 cache.check_table_cache(eval_key, &deps).is_none(),
667 "a changed rider input must force item-scoped table recomputation"
668 );
669 }
670
671 #[test]
672 fn active_item_reuses_global_table_with_only_params_dependencies() {
673 let mut cache = EvalCache::new();
674 cache.set_active_item(1);
675
676 let eval_key = "#/$params/references/SHARED_RATE";
677 let deps = IndexSet::from_iter(["#/$params/others/currency".to_string()]);
678 cache.entries.insert(
679 eval_key.to_string(),
680 CacheEntry {
681 dep_versions: HashMap::from([("/$params/others/currency".to_string(), 0)]),
682 result: Arc::new(json!([{"rate": 10}])),
683 computed_for_item: None,
684 },
685 );
686
687 assert_eq!(
688 cache.check_table_cache(eval_key, &deps),
689 Some(Arc::new(json!([{"rate": 10}])))
690 );
691 }
692}
693
694fn diff_and_update_versions_internal(
695 tracker: &mut VersionTracker,
696 pointer: &mut String,
697 old: &Value,
698 new: &Value,
699 source: &str,
700) {
701 if old == new {
702 return;
703 }
704
705 if crate::utils::is_debug_cache_enabled() {
706 println!(
707 "[diff_and_update_versions_internal] {} pointer={}, old={:?}, new={:?}",
708 source, pointer, old, new
709 );
710 }
711
712 match (old, new) {
713 (Value::Object(a), Value::Object(b)) => {
714 for (key, a_val) in a {
715 if key == "$params" {
716 continue;
717 }
718 let b_val = b.get(key).unwrap_or(&Value::Null);
719 if a_val == b_val {
720 continue;
721 }
722
723 let old_len = pointer.len();
724 pointer.push('/');
725 if key.contains('~') || key.contains('/') {
726 let escaped_key = key.replace('~', "~0").replace('/', "~1");
727 pointer.push_str(&escaped_key);
728 } else {
729 pointer.push_str(key);
730 }
731 diff_and_update_versions_internal(tracker, pointer, a_val, b_val, source);
732 pointer.truncate(old_len);
733 }
734
735 for (key, b_val) in b {
736 if key == "$params" || a.contains_key(key) {
737 continue;
738 }
739 if b_val.is_null() {
740 continue;
741 }
742
743 let old_len = pointer.len();
744 pointer.push('/');
745 if key.contains('~') || key.contains('/') {
746 let escaped_key = key.replace('~', "~0").replace('/', "~1");
747 pointer.push_str(&escaped_key);
748 } else {
749 pointer.push_str(key);
750 }
751 diff_and_update_versions_internal(tracker, pointer, &Value::Null, b_val, source);
752 pointer.truncate(old_len);
753 }
754 }
755 (Value::Array(a), Value::Array(b)) => {
756 if a != b {
757 tracker.bump(pointer, source);
758 }
759 let max_len = a.len().max(b.len());
760 for i in 0..max_len {
761 let a_val = a.get(i).unwrap_or(&Value::Null);
762 let b_val = b.get(i).unwrap_or(&Value::Null);
763 if a_val == b_val {
764 continue;
765 }
766 let old_len = pointer.len();
767 use std::fmt::Write;
768 write!(pointer, "/{}", i).unwrap();
769 diff_and_update_versions_internal(tracker, pointer, a_val, b_val, source);
770 pointer.truncate(old_len);
771 }
772 }
773 (old_val, new_val) => {
774 if old_val != new_val {
775 if crate::utils::is_debug_cache_enabled() {
776 println!(
777 "[store_cache] Catch-all for {}: old={}, new={}",
778 pointer,
779 match old_val {
780 Value::Null => "Null",
781 Value::Bool(_) => "Bool",
782 Value::Number(_) => "Number",
783 Value::String(_) => "String",
784 Value::Array(_) => "Array",
785 Value::Object(_) => "Object",
786 },
787 match new_val {
788 Value::Null => "Null",
789 Value::Bool(_) => "Bool",
790 Value::Number(_) => "Number",
791 Value::String(_) => "String",
792 Value::Array(_) => "Array",
793 Value::Object(_) => "Object",
794 }
795 );
796 }
797 tracker.bump(pointer, "diff_and_update_versions_internal");
798
799 if old_val.is_object() || old_val.is_array() {
802 traverse_and_bump(tracker, pointer, old_val);
803 }
804 if new_val.is_object() || new_val.is_array() {
805 traverse_and_bump(tracker, pointer, new_val);
806 }
807 }
808 }
809 }
810}
811
812#[cfg(test)]
816mod tests {
817 use super::VersionTracker;
818
819 #[test]
820 fn merge_excluding_prefix_keeps_item_versions_isolated() {
821 let mut item = VersionTracker::new();
822 item.bump("/riders/wop_flag", "test");
823
824 let mut parent = VersionTracker::new();
825 parent.bump("/illustration/insured/phins_relation", "test");
826 parent.bump("/riders/wop_flag", "test");
827
828 item.merge_excluding_prefix(&parent, "/riders/");
829
830 assert_eq!(item.get("/illustration/insured/phins_relation"), 1);
831 assert_eq!(
832 item.get("/riders/wop_flag"),
833 1,
834 "another rider's parent-tracker bump must not alter this item's version"
835 );
836 }
837}
838
839fn traverse_and_bump(tracker: &mut VersionTracker, pointer: &mut String, val: &Value) {
840 match val {
841 Value::Object(map) => {
842 for (key, v) in map {
843 if key == "$params" {
844 continue; }
846 let escaped_key = key.replace('~', "~0").replace('/', "~1");
847 let old_len = pointer.len();
848 pointer.push('/');
849 pointer.push_str(&escaped_key);
850 tracker.bump(pointer, "traverse_and_bump1");
851 traverse_and_bump(tracker, pointer, v);
852 pointer.truncate(old_len);
853 }
854 }
855 Value::Array(arr) => {
856 for (i, v) in arr.iter().enumerate() {
857 let old_len = pointer.len();
858 use std::fmt::Write;
859 write!(pointer, "/{}", i).unwrap();
860 tracker.bump(pointer, "traverse_and_bump2");
861 traverse_and_bump(tracker, pointer, v);
862 pointer.truncate(old_len);
863 }
864 }
865 _ => {}
866 }
867}