DiffBuilder: fix reindexing of operations involving nested paths #175
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Fixes #138
Fixes #124
TL;DR
When
DiffBuilderoptimized a Remove + Add pair into a Move, some operations targeting nested positions within the same array were not reindexed correctly. The undo hooks only compared exact paths, leaving child paths (e.g., /0/x/1/y) unchanged even when /0/x is affected. This PR updates the undo logic to use prefix-based matching on parsed path segments and introduces helpers to safely adjust non-terminal path components.Background / Problem
You can reproduce #138 and #124 by repeatedly running:
The core failure: once an Add/Remove pair is collapsed into a Move, operations that refer to deeper locations in the same array (e.g.,
/0/x/1/y) were not having their indices updated, because the undo hooks only matched exact string paths.Example from #138: at the first relevant undo call,
path == '0/x'butself.path == '0/x/1/y'; the equality check fails, so the subsequent operation’s index never decrements.Root cause
_on_undo_remove/_on_undo_addusedif self.path == path, which only fires when the target is exactly the array path, not when the target is under that array.PatchOperationonly exposedkey(last path part), so we couldn’t adjust indices in the middle of the path (e.g., /0/x/1/y).What this changes
_on_undo_remove/_on_undo_addnow receivesub_partsand use_is_prefix(sub_parts, parts)to detect impact on nested paths, not just exact matches.PatchOperationgainsget_part/set_partand small increment/decrement helpers.MoveOperationmirrors these forfrompaths. This lets undo logic update indices in the middle of a path (not only the last part).DiffBuilderpassesop.pointer.parts[:-1]into the undo hooks so they can adjust the correct array index segment.MoveOperation.from_keyfromTypeErrortoValueError(consistent withkey).Design notes
keysemantics). Prefix checks at the parts-level avoid bugs caused by'/'and'~'in string paths.MoveOperationre-parses thefrompointer in its helper methods, mirroring the existingfrom_keypattern. Caching is a possible follow-up if we want.Backward compatibility
pathproperty even though internal logic now prefers parsed parts._on_undo_remove/_on_undo_addnow acceptsub_partsrather than a stringpath.Performance
_is_prefixisO(k)in path length, same as the prior equality check in practice.Tests
New regression tests (plus a variant with escaping):
test_issue_138— nested array index updates after a move optimization.test_issue_138b— same as above under a key containing'/'(escaping check).test_issue_124— similar failure mode with different shapes/ordering.All existing tests pass locally. Coverage is unchanged.
Migration / risk
_on_undohooks; they’ll need to switch to thesub_partsargument. Typical users of the public API are unaffected.