Uh oh!
There was an error while loading. Please reload this page.
fix(hermitcrab): make metathesis switch-name order not matter - #471
Merged
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@## master #471 +/- ##
=======================================
Coverage 73.31% 73.32% =======================================
Files 445 445 Lines 37300 37310 +10 Branches 5115 5118 +3 =======================================
+ Hits 27346 27356 +10
Misses 8827 8827 Partials 1127 1127 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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).
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>
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>
johnml1135force-pushed
the
fix/metathesis-morph-annotation-sort
branch
from
August 18, 2026 10:53
01add43 to
03026edCompareUh oh!
There was an error while loading. Please reload this page.
johnml1135 added a commit
that referenced
this pull request
Aug 19, 2026
dataflow-obligations.tsv keys MC/DC to (writer attribute -> reader attribute) pairs, and that artifact fails in both directions: it emits four arms per chain where only one can be certified -- the arm is read off the reader attribute's spelling -- and it cannot see a condition no attribute reaches. gate-obligations.tsv is keyed to the 23 FailureReason gates, each carrying MC/DC's two arms: a Blocked arm where a word fails for that gate and severing its feeding construct flips it to a parse, and a Control arm where the same rule applies successfully so the block is attributable to the gate rather than to the rule never firing. The Blocked arm is stronger evidence than the old chain pairing because the trace NAMES the reason: idil is credited here to RequiredSyntacticFeatureStruct, the reason the engine actually gives, where the old ledger recorded a PartOfSpeech chain. 46 obligations, 42 worth covering, 9 arms evidenced -- but only ONE gate has both, so MC/DC is complete for 1 of 23. That number is what this ledger exists to make visible and the old one could not express. dataflow-obligations.tsv is kept as a cross-check on writer-reader chains, which the gate ledger does not measure, and the docs say which number is the claim. The published figure is a funnel rather than a fraction: 346 cells enumerated, 28 certifiable by the generator, 18 also producible by FieldWorks, 4 satisfied. The outstanding list is 14, not 342. Also retires the metathesis crash expectation. #471 fixed the comparator invariant violation and the fixture said its crash expectation should come out at that point; it did not, so rebasing onto master turned it red. A PanGloss adapter run had flagged it as an engine divergence and it was not one -- that engine produced exactly this result while our expectation was stale. A stale expectation does not read as stale; it reads as the other implementation being wrong.
15 tasks
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 freeto 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