fix(hermitcrab): make metathesis switch-name order not matter - #471
Open
johnml1135 wants to merge 4 commits into
Open
fix(hermitcrab): make metathesis switch-name order not matter#471johnml1135 wants to merge 4 commits into
johnml1135 wants to merge 4 commits into
Conversation
A MetathesisRule whose leftSwitch names the EARLIER of the two pattern groups threw InvalidOperationException "Failed to compare two elements in the array", wrapping ArgumentException "Only nodes from the same list can be compared", instead of producing an analysis. Both metathesis specs silently required the opposite orientation, and every existing test names the later group as the left switch, so the intuitive order was never exercised. SynthesisMetathesisRuleSpec.ApplyRhs splices by moving one group's nodes out to the right and then moving the other into the gap. That only works when the group it is handed first is the LATER of the two in shape order. Handed them the other way round, the second move re-anchors a group after its own end, which detaches nodes and leaves child annotation ranges spanning two lists; the subsequent OrderBy over those ranges is what throws. AnalysisMetathesisRuleSpec builds its pattern in leftSwitch-then-rightSwitch order, but that pattern has to match the SURFACE, where the two groups appear in the opposite order from the underlying form. With the switch names the other way round the analysis pattern matched nothing, so even without the crash there was no analysis. Both now order by pattern position rather than by name, so a rule behaves identically whichever way its switch names are written. Rules that already named the later group first are unaffected: for them the normalization is a no-op. Direction is NOT involved; this reproduces under both LeftToRight and RightToLeft. The added test differs from SimpleRule only in swapping the two switch names, and must produce the same result. Verified that reverting either half alone reintroduces a distinct failure: without the synthesis change the crash returns, and without the analysis change the word gets no analysis. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #471 +/- ##
=======================================
Coverage 73.33% 73.34%
=======================================
Files 445 445
Lines 37317 37327 +10
Branches 5118 5121 +3
=======================================
+ Hits 27367 27377 +10
Misses 8825 8825
Partials 1125 1125 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Rationale, history and the reproduction belong in the PR body, not in the source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sked for A middle-group case with the switch names reversed, a right-to-left variant, and a Success guard so the new comparison does not dereference an unmatched capture. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…one line Keeps the load-bearing ordering rationale; drops the extra sentence about unmatched captures, which the adjacent Success checks already make plain. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ddaspit
approved these changes
Aug 12, 2026
ddaspit
left a comment
Contributor
There was a problem hiding this comment.
@ddaspit reviewed 3 files and all commit messages, and made 1 comment.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on johnml1135).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Problem
A
MetathesisRulewhoseleftSwitchnames the earlier of its two pattern groups throws instead of producing an analysis:Both metathesis specs silently required the opposite orientation. Every pre-existing test in
MetathesisRuleTestsnames the later group as the left switch, so the intuitive order was never exercised.Directionis not involved — this reproduces under bothLeftToRightandRightToLeft.Cause
MoveNodesAfteradvances itscuranchor after every iterated node, whether or not that node was physically moved. In the failing orientationbeforeRightGroupends up equal toleftGroup.Range.End, so for a single-node group that node is simultaneously the move's anchor and the node being removed in the same iteration.node.Remove()sets itsListto null and the followingcur.AddAfter(node)never restores it. The orphaned node then reachesmorph.Children.OrderBy(ann => ann.Range), andShapeNode.CompareTothrows because the two nodes belong to different lists.Separately,
AnalysisMetathesisRuleSpecbuilds its pattern inleftSwitch-then-rightSwitchorder, but that pattern must match the surface, where the two groups appear in the opposite order from the underlying form. With the switch names reversed the analysis pattern matched nothing — so even with the crash fixed there was still no analysis.Fix
Both specs order the two switch groups by pattern position rather than by name, so a rule behaves identically whichever way its switch names are written.
For rules that already name the later group first the normalization is a no-op: the swap condition is false, and
AnalysisMetathesisRuleSpecemits the same order it always did.Verification
Full
SIL.Machine.Morphology.HermitCrab.Testssuite: 71/71 pass.dotnet csharpier checkclean.Each half of the fix was reverted independently to confirm both are load-bearing:
SimpleRule_LeftSwitchNamesEarlierGroupand its right-to-left variant reproduce the reported crashTests added:
SimpleRule_LeftSwitchNamesEarlierGroup— differs fromSimpleRuleonly in the switch-name order, and must give the same resultSimpleRule_LeftSwitchNamesEarlierGroup_RightToLeft— same, withDirection.RightToLeftComplexRule_LeftSwitchNamesEarlierGroup— reversed naming with a group between the two switches. Note this one passes even without the synthesis change; it guards the analysis-side ordering and the middle-group case rather than reproducing the crash.Scope, and what this does not close
This fixes the switch-name-order crash class. Two pre-existing hazards in the same code are left untouched and are not claimed to be fixed:
MoveNodesAfterstill advancescurpast a skipped non-Segmentnode, so a switch group whose own range spans a boundary or anchor node could still anchor subsequent moves off a stale position. No current rule shape hits this.Range.Start.beforeRightGroup's.Prevalready dereferenced it before this change; the new comparison is guarded withGroupCapture.Successso it does not add a second such site, but the underlying assumption that both switches always match is unchanged.How it was found
While building generated-coverage fixtures for the HermitCrab XML surface on the
conformance-frameworkbranch: a fixture written to exerciseMetathesisRule@multipleApplicationOrderwrote its switch names in the natural order and hit this. The ordering attribute turned out to be irrelevant.🤖 Generated with Claude Code
This change is